From 600261d81f14bdc256373bc8a7c20a62414e6a4a Mon Sep 17 00:00:00 2001 From: PatR Date: Thu, 3 Jan 2019 17:37:00 -0800 Subject: [PATCH] fix github #172 - ^T inconsistencies; add m^T Fixes #172 Casting teleport-away via ^T used different requirements for energy, strength, and hunger than casting it via 'Z'. The strength and hunger requirements were more stringent, the energy one more lenient. When it rejected a cast attempt due to any of those, it used up the move, but 'Z' didn't. When testing my fix, I wanted an easier way than a debugger to control how ^T interacts with wizard mode, so finally got around to a first cut at being able to invoke it via wizard mode but not override those energy/strength/hunger requirements. It uses the 'm' prefix to ask for a menu. 'm^T' gives four options about how to teleport. (There are other permutations which aren't handled.) Also noticed while testing: ^T wouldn't attempt to cast teleport-away if you didn't know the corresponding spellbook. 'Z' will attempt that because it is possible to forget a book and still know its spell. --- doc/fixes36.2 | 9 ++- include/extern.h | 3 +- src/cmd.c | 6 +- src/spell.c | 62 ++++++++++++++- src/teleport.c | 201 ++++++++++++++++++++++++++++++++++++----------- 5 files changed, 230 insertions(+), 51 deletions(-) diff --git a/doc/fixes36.2 b/doc/fixes36.2 index beeed33a7..015ce2c20 100644 --- a/doc/fixes36.2 +++ b/doc/fixes36.2 @@ -1,4 +1,4 @@ -$NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.221 $ $NHDT-Date: 1546467443 2019/01/02 22:17:23 $ +$NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.222 $ $NHDT-Date: 1546565812 2019/01/04 01:36:52 $ This fixes36.2 file is here to capture information about updates in the 3.6.x lineage following the release of 3.6.1 in April 2018. Please note, however, @@ -322,6 +322,11 @@ since knives became stackable in 3.6.0, fake player monsters could be given using 'O' to attempt to set bouldersym to a monster letter or warning digit while it still had its default value would override the display value for it to be ('\0') after 'badoption' feedback +when ^T resorted to the teleport-away spell if hero didn't have intrinsic + telepotation, it used different hunger/strength/energy requirements + than casting with 'Z'; ^T also required that the corresponding book + be known even though knowing and casting a spell should be (and is + with 'Z') possible after forgetting the spellbook due to amnesia Fixes to Post-3.6.1 Problems that Were Exposed Via git Repository @@ -493,6 +498,8 @@ when sortloot is enabled, gems are grouped in subsets (1) unseen gems and (4) identified glass, (5) unseen stones (includes unseen rocks), (6) seen but unidentified gray stones, (7) identified gray stones, and (8) seen rocks (IDed/unIDed not applicable) +in wizard mode, ^T can be preceded by 'm' prefix in order to test teleporting + without having wizard mode override various restrictions NetHack Community Patches (or Variation) Included diff --git a/include/extern.h b/include/extern.h index dbef64c22..8e3f54294 100644 --- a/include/extern.h +++ b/include/extern.h @@ -1,4 +1,4 @@ -/* NetHack 3.6 extern.h $NHDT-Date: 1545964581 2018/12/28 02:36:21 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.678 $ */ +/* NetHack 3.6 extern.h $NHDT-Date: 1546565812 2019/01/04 01:36:52 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.680 $ */ /* Copyright (c) Steve Creps, 1988. */ /* NetHack may be freely redistributed. See license for details. */ @@ -2329,6 +2329,7 @@ E void NDECL(age_spells); E int NDECL(docast); E int FDECL(spell_skilltype, (int)); E int FDECL(spelleffects, (int, BOOLEAN_P)); +E int FDECL(tport_spell, (int)); E void NDECL(losespells); E int NDECL(dovspell); E void FDECL(initialspell, (struct obj *)); diff --git a/src/cmd.c b/src/cmd.c index 04df10458..061b15690 100644 --- a/src/cmd.c +++ b/src/cmd.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 cmd.c $NHDT-Date: 1546038393 2018/12/28 23:06:33 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.323 $ */ +/* NetHack 3.6 cmd.c $NHDT-Date: 1546565813 2019/01/04 01:36:53 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.324 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2013. */ /* NetHack may be freely redistributed. See license for details. */ @@ -4408,8 +4408,8 @@ int NDECL((*cmd_func)); || cmd_func == doloot /* travel: pop up a menu of interesting targets in view */ || cmd_func == dotravel - /* wizard mode ^V */ - || cmd_func == wiz_level_tele + /* 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; diff --git a/src/spell.c b/src/spell.c index a7fd388ca..9cbb9a480 100644 --- a/src/spell.c +++ b/src/spell.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 spell.c $NHDT-Date: 1542765363 2018/11/21 01:56:03 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.87 $ */ +/* 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 $ */ /* Copyright (c) M. Stephenson 1988 */ /* NetHack may be freely redistributed. See license for details. */ @@ -923,6 +923,10 @@ 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) { @@ -1269,6 +1273,62 @@ throwspell() return 1; } +/* 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; +{ + static struct tport_hideaway { + struct spell savespell; + int tport_indx; + } save_tport; + int i; +/* also defined in teleport.c */ +#define NOOP_SPELL 0 +#define HIDE_SPELL 1 +#define ADD_SPELL 2 +#define UNHIDESPELL 3 +#define REMOVESPELL 4 + + for (i = 0; i < MAXSPELL; i++) + if (spellid(i) == SPE_TELEPORT_AWAY || spellid(i) == NO_SPELL) + break; + if (i == MAXSPELL) { + impossible("tport_spell: spellbook full"); + /* wizard mode ^T is not able to honor player's menu choice */ + } else if (spellid(i) == NO_SPELL) { + if (what == HIDE_SPELL || what == REMOVESPELL) { + 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; + save_tport.tport_indx = MAXSPELL; /* burn bridge... */ + } else if (what == ADD_SPELL) { + save_tport.savespell = 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; + return REMOVESPELL; /* operation needed to reverse */ + } + } else { /* spellid(i) == SPE_TELEPORT_AWAY */ + if (what == ADD_SPELL || what == UNHIDESPELL) { + save_tport.tport_indx = MAXSPELL; + } else if (what == REMOVESPELL) { + /*assert( i == save_tport.tport_indx );*/ + spl_book[i] = save_tport.savespell; + save_tport.tport_indx = MAXSPELL; + } else if (what == HIDE_SPELL) { + save_tport.savespell = spl_book[i]; + save_tport.tport_indx = i; + spl_book[i].sp_id = NO_SPELL; + return UNHIDESPELL; /* operation needed to reverse */ + } + } + return NOOP_SPELL; +} + /* forget a random selection of known spells due to amnesia; 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 */ diff --git a/src/teleport.c b/src/teleport.c index 9aedc0c3e..e2a6c32dc 100644 --- a/src/teleport.c +++ b/src/teleport.c @@ -1,4 +1,4 @@ -/* NetHack 3.6 teleport.c $NHDT-Date: 1544401270 2018/12/10 00:21:10 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.81 $ */ +/* NetHack 3.6 teleport.c $NHDT-Date: 1546565815 2019/01/04 01:36:55 $ $NHDT-Branch: NetHack-3.6.2-beta01 $:$NHDT-Revision: 1.82 $ */ /* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */ /*-Copyright (c) Robert Patrick Rankin, 2011. */ /* NetHack may be freely redistributed. See license for details. */ @@ -496,17 +496,114 @@ struct obj *scroll; return result; } +/* ^T command; 'm ^T' == choose among several teleport modes */ int dotelecmd() { - return dotele((wizard) ? TRUE : FALSE); + long save_HTele, save_ETele; + int res, added, hidden; + boolean ignore_restrictions = FALSE; +/* also defined in spell.c */ +#define NOOP_SPELL 0 +#define HIDE_SPELL 1 +#define ADD_SPELL 2 +#define UNHIDESPELL 3 +#define REMOVESPELL 4 + + /* normal mode; ignore 'm' prefix if it was given */ + if (!wizard) + return dotele(FALSE); + + added = hidden = NOOP_SPELL; + save_HTele = HTeleportation, save_ETele = ETeleportation; + if (!iflags.menu_requested) { + ignore_restrictions = TRUE; + } else { + static const struct tporttypes { + char menulet; + const char *menudesc; + } tports[] = { + /* + * Potential combinations: + * 1) attempt ^T without intrinsic, not know spell; + * 2) via intrinsic, not know spell, obey restrictions; + * 3) via intrinsic, not know spell, ignore restrictions; + * 4) via intrinsic, know spell, obey restrictions; + * 5) via intrinsic, know spell, ignore restrictions; + * 6) via spell, not have intrinsic, obey restrictions; + * 7) via spell, not have intrinsic, ignore restrictions; + * 8) force, obey other restrictions; + * 9) force, ignore restrictions. + * We only support the 1st (t), 2nd (n), 6th (s), and 9th (w). + * + * This ignores the fact that there is an experience level + * (or poly-form) requirement which might make normal ^T fail. + */ + { 'n', "normal ^T on demand; no spell, obey restrictions" }, + { 's', "via spellcast; no intrinsic teleport" }, + { 't', "try ^T without having it; no spell" }, + { 'w', "debug mode; ignore restrictions" }, /* trad wizard mode */ + }; + menu_item *picks = (menu_item *) 0; + anything any; + winid win; + int i, tmode; + + win = create_nhwindow(NHW_MENU); + start_menu(win); + any = 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, MENU_UNSELECTED); + } + end_menu(win, "Which way do you want to teleport?"); + if (select_menu(win, PICK_ONE, &picks) > 0) { + tmode = picks[0].item.a_int; + free((genericptr_t) picks); + } else { + return 0; + } + destroy_nhwindow(win); + switch (tmode) { + case 'n': + HTeleportation |= I_SPECIAL; /* confer intrinsic teleportation */ + hidden = tport_spell(HIDE_SPELL); /* hide teleport-away */ + break; + case 's': + HTeleportation = ETeleportation = 0L; /* suppress intrinsic */ + added = tport_spell(ADD_SPELL); /* add teleport-away */ + break; + case 't': + HTeleportation = ETeleportation = 0L; /* suppress intrinsic */ + hidden = tport_spell(HIDE_SPELL); /* hide teleport-away */ + break; + case 'w': + ignore_restrictions = TRUE; + break; + } + } + + /* if dotele() can be fatal, final disclosure might lie about + intrinsic teleportation; we should be able to live with that + since the menu finagling is only applicable in wizard mode */ + res = dotele(ignore_restrictions); + + HTeleportation = save_HTele; + ETeleportation = save_ETele; + if (added != NOOP_SPELL || hidden != NOOP_SPELL) + /* can't both be non-NOOP so addition will yield the non-NOOP one */ + (void) tport_spell(added + hidden - NOOP_SPELL); + + return res; } int dotele(break_the_rules) -boolean break_the_rules; +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); @@ -517,9 +614,9 @@ boolean break_the_rules; 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') + if (yn("Jump in?") == 'n') { trap = 0; - else { + } else { deltrap(trap); newsym(u.ux, u.uy); } @@ -534,58 +631,72 @@ boolean break_the_rules; if (!Teleportation || (u.ulevel < (Role_if(PM_WIZARD) ? 8 : 12) && !can_teleport(youmonst.data))) { - /* Try to use teleport away spell. */ - if (objects[SPE_TELEPORT_AWAY].oc_name_known && !Confusion) - for (sp_no = 0; sp_no < MAXSPELL; sp_no++) - if (spl_book[sp_no].sp_id == SPE_TELEPORT_AWAY) { - castit = TRUE; - break; - } - if (!break_the_rules) { - if (!castit) { - if (!Teleportation) - You("don't know that spell."); - else - You("are not able to teleport at will."); - return 0; - } + /* Try to use teleport away spell. + 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; + /* casting isn't inhibited by being Stunned (...it ought to be) */ + castit = (sp_no < MAXSPELL && !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"); + return 0; } } - if (u.uhunger <= 100 || ACURR(A_STR) < 6) { - if (!break_the_rules) { - You("lack the strength %s.", - castit ? "for a teleport spell" : "to teleport"); - return 1; - } + cantdoit = 0; + /* 3.6.2: the magic numbers for hunger, strength, and energy + have been changed to match the ones used in spelleffects(). + Also, failing these tests used to return 1 and use a move + even though casting failure due to these reasons doesn't. + [Note: this spellev() is different from the one in spell.c + but they both yield the same result.] */ +#define spellev(spell_otyp) ((int) objects[spell_otyp].oc_level) + energy = 5 * spellev(SPE_TELEPORT_AWAY); + if (break_the_rules) { + if (!castit) + energy = 0; + /* spell will cost more if carrying the Amulet, but the + amount is rnd(2 * energy) so we can't know by how much; + average is twice the normal cost, but could be triple; + 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) { + cantdoit = "are too weak from hunger"; + } else if (ACURR(A_STR) < 4) { + cantdoit = "lack the strength"; + } else if (energy > u.uen) { + cantdoit = "lack the energy"; } - - energy = objects[SPE_TELEPORT_AWAY].oc_level * 7 / 2 - 2; - if (u.uen <= energy) { - if (break_the_rules) - energy = u.uen; - else { - You("lack the energy %s.", - castit ? "for a teleport spell" : "to teleport"); - return 1; - } + if (cantdoit) { + You("%s %s.", cantdoit, + castit ? "for a teleport spell" : "to teleport"); + return 0; + } else if (check_capacity( + "Your concentration falters from carrying so much.")) { + return 1; /* this failure in spelleffects() also uses the move */ } - if (check_capacity( - "Your concentration falters from carrying so much.")) - return 1; - if (castit) { + /* energy cost is deducted in spelleffects() */ exercise(A_WIS, TRUE); if (spelleffects(sp_no, TRUE)) return 1; else if (!break_the_rules) return 0; } else { - if (!break_the_rules) { - u.uen -= energy; - context.botl = 1; - } + /* bypassing spelleffects(); apply energy cost directly */ + u.uen -= energy; + context.botl = 1; } } @@ -596,7 +707,7 @@ boolean break_the_rules; tele(); (void) next_to_u(); } else { - You1(shudder_for_moment); + You("%s", shudder_for_moment); return 0; } if (!trap) -- 2.40.0