more fix for #H5056 - achievement tracking

The followup message about the fix for #5056 was trapped by the spam
filter so didn't reach us for a while.

xlogfile has an extra field to track various achievements made during
the game it logs, two of which are fully exploring the gnomish mines
and fully exploring sokoban.  Those are accomplished by finding the
special 'prize' item on the final level of their branch:  luckstone
for mines and bag of holding or amulet of reflecition for sokoban.
3.6.0 had a bug where any item of the target type found anywhere in
the dungeon resulted in achieving the relevant goal.  A post-3.6.1 fix
for that required that the item be found on the end level of the branch
and attempted to require that it an item explicitly placed there by the
special level loader, but the latter aspect had a bug which meant that
random items of the appropriate type placed on final level would count
as the prize.  Chance of extra luckstones on mines' end is fairly high,
so potential for false completion of the achievement was also high.

The second complaint was that since the achievement was only recorded
if the special prize item was found on final level, then if a monster
took it to another level then the achievement became impossible.  (Not
true, the player could take it back, drop it, and pick it up again, but
that is admittedly a pretty silly hoop to jump through.)  On the other
hand, if a monster removed the item before the hero found it, then a
case could be made that the hero hadn't really fully explored the
level.  However, this fix records the achievement no matter where the
hero picks up the item.  The final level must be entered--otherwise no
monster could possibly acquire and transport the item--but it isn't
guaranteed to have been fully explored.  Big deal....

The prize could also be acquired in bones data.  Before the second
portion of this fix, that wouldn't have mattered.  But now it does, so
clear the prize indicator when saving bones unless it happens to be the
same level where that item is created (impossible for sokoban, where no
bones are left; not sure offhand about mines' end).  The former prize
stone or bag or amulet becomes an ordinary one of its type.

This can all be done in a much cleaner fashion once we give up on the
current save file compatability.  Putting obj->o_id values into new
context.mines_prize and context.soko_prize, plus a hack to mkobj() to
not reuse those two values if the o_id counter ever wraps back to 0,
would cover most of the details.  Adding an achievement tracking flag
to lev_comp's object handling for use by the special level loader
would cover most of the rest.
This commit is contained in:
PatR
2017-10-24 00:37:21 -07:00
parent a8fb18d6fe
commit 58477b33f4
7 changed files with 98 additions and 45 deletions

View File

@@ -521,6 +521,7 @@ hero poly'd into vampire could drain monster down to 0 HP without killing it,
fix mention_walls reporting secret doors as solid stone
jumping over water unintentionally moved hero through that water, causing
drowning if not able to water walk or fly
try again to fix achievement recording bug with mines and sokoban prizes
Platform- and/or Interface-Specific Fixes

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 flag.h $NHDT-Date: 1505214875 2017/09/12 11:14:35 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.123 $ */
/* NetHack 3.6 flag.h $NHDT-Date: 1508827590 2017/10/24 06:46:30 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.129 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed. See license for details. */
@@ -349,25 +349,24 @@ struct instance_flags {
int wc_fontsiz_menu; /* font size for the menu window */
int wc_fontsiz_text; /* font size for text windows */
int wc_scroll_amount; /* scroll this amount at scroll_margin */
int wc_scroll_margin; /* scroll map when this far from
the edge */
int wc_scroll_margin; /* scroll map when this far from the edge */
int wc_map_mode; /* specify map viewing options, mostly
for backward compatibility */
* for backward compatibility */
int wc_player_selection; /* method of choosing character */
boolean wc_splash_screen; /* display an opening splash screen or not */
boolean wc_popup_dialog; /* put queries in pop up dialogs instead of
in the message window */
* in the message window */
boolean wc_eight_bit_input; /* allow eight bit input */
boolean wc_mouse_support; /* allow mouse support */
boolean wc2_fullscreen; /* run fullscreen */
boolean wc2_softkeyboard; /* use software keyboard */
boolean wc2_wraptext; /* wrap text */
boolean wc2_selectsaved; /* display a menu of user's saved games */
boolean wc2_darkgray; /* try to use dark-gray color for black glyphs */
boolean wc2_hitpointbar; /* show graphical bar representing hit points */
boolean cmdassist; /* provide detailed assistance for some commands */
boolean clicklook; /* allow right-clicking for look */
boolean obsolete; /* obsolete options can point at this, it isn't used */
boolean wc2_darkgray; /* try to use dark-gray color for black glyphs */
boolean wc2_hitpointbar; /* show graphical bar representing hit points */
boolean cmdassist; /* provide detailed assistance for some commands */
boolean clicklook; /* allow right-clicking for look */
boolean obsolete; /* obsolete options can point at this, it isn't used */
struct autopickup_exception *autopickup_exceptions[2];
#define AP_LEAVE 0
#define AP_GRAB 1
@@ -381,6 +380,11 @@ struct instance_flags {
Bitfield(save_uswallow, 1);
Bitfield(save_uinwater, 1);
Bitfield(save_uburied, 1);
/* item types used to acomplish "special achievements"; find the target
object and you'll be flagged as having achieved something... */
short mines_prize_type; /* luckstone */
short soko_prize_type1; /* bag of holding or */
short soko_prize_type2; /* amulet of reflection */
};
/*

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 obj.h $NHDT-Date: 1456618994 2016/02/28 00:23:14 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.53 $ */
/* NetHack 3.6 obj.h $NHDT-Date: 1508827590 2017/10/24 06:46:30 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.60 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed. See license for details. */
@@ -343,13 +343,19 @@ struct obj {
&& !undiscovered_artifact(ART_EYES_OF_THE_OVERWORLD)))
#define pair_of(o) ((o)->otyp == LENSES || is_gloves(o) || is_boots(o))
/* 'PRIZE' values override obj->corpsenm so prizes mustn't be object types
which use that field for monster type (or other overloaded purpose) */
#define MINES_PRIZE 1
#define SOKO_PRIZE1 2
#define SOKO_PRIZE2 3
#define is_mines_prize(o) \
((o)->otyp == LUCKSTONE && Is_mineend_level(&u.uz))
#define is_soko_prize(o) \
(((o)->otyp == AMULET_OF_REFLECTION \
|| (o)->otyp == BAG_OF_HOLDING) \
&& Is_sokoend_level(&u.uz))
((o)->otyp == iflags.mines_prize_type \
&& (o)->record_achieve_special == MINES_PRIZE)
#define is_soko_prize(o) \
(((o)->otyp == iflags.soko_prize_type1 \
&& (o)->record_achieve_special == SOKO_PRIZE1) \
|| ((o)->otyp == iflags.soko_prize_type2 \
&& (o)->record_achieve_special == SOKO_PRIZE2))
/* Flags for get_obj_location(). */
#define CONTAINED_TOO 0x1

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 bones.c $NHDT-Date: 1503309019 2017/08/21 09:50:19 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.70 $ */
/* NetHack 3.6 bones.c $NHDT-Date: 1508827591 2017/10/24 06:46:31 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.71 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985,1993. */
/* NetHack may be freely redistributed. See license for details. */
@@ -124,7 +124,7 @@ boolean restore;
otmp->spe = 1;
#endif
} else if (otmp->otyp == EGG) {
otmp->spe = 0;
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
@@ -133,24 +133,30 @@ boolean restore;
} else if (otmp->otyp == CORPSE || otmp->otyp == STATUE) {
int mnum = otmp->corpsenm;
/* Discard incarnation details of unique
monsters (by passing null instead of otmp
for object), shopkeepers (by passing false
for revival flag), temple priests, and
vault guards in order to prevent corpse
revival or statue reanimation. */
/* Discard incarnation details of unique monsters
(by passing null instead of otmp for object),
shopkeepers (by passing false for revival flag),
temple priests, and vault guards in order to
prevent corpse revival or statue reanimation. */
if (has_omonst(otmp)
&& cant_revive(&mnum, FALSE, (struct obj *) 0)) {
free_omonst(otmp);
/* mnum is now either human_zombie or
doppelganger; for corpses of uniques,
we need to force the transformation
now rather than wait until a revival
attempt, otherwise eating this corpse
/* mnum is now either human_zombie or doppelganger;
for corpses of uniques, we need to force the
transformation now rather than wait until a
revival attempt, otherwise eating this corpse
would behave as if it remains unique */
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 (otmp->otyp == AMULET_OF_YENDOR) {
/* no longer the real Amulet */
otmp->otyp = FAKE_AMULET_OF_YENDOR;
@@ -183,8 +189,8 @@ sanitize_name(namebuf)
char *namebuf;
{
int c;
boolean strip_8th_bit =
!strcmp(windowprocs.name, "tty") && !iflags.wc_eight_bit_input;
boolean strip_8th_bit = (!strcmp(windowprocs.name, "tty")
&& !iflags.wc_eight_bit_input);
/* it's tempting to skip this for single-user platforms, since
only the current player could have left these bones--except

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 invent.c $NHDT-Date: 1498078873 2017/06/21 21:01:13 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.214 $ */
/* NetHack 3.6 invent.c $NHDT-Date: 1508827592 2017/10/24 06:46:32 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.220 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed. See license for details. */
@@ -504,12 +504,21 @@ struct obj *obj;
}
set_artifact_intrinsic(obj, 1, W_ART);
}
if (is_mines_prize(obj) && obj->record_achieve_special) {
/* "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) */
if (is_mines_prize(obj)) {
u.uachieve.mines_luckstone = 1;
obj->record_achieve_special = 0;
} else if (is_soko_prize(obj) && obj->record_achieve_special) {
obj->record_achieve_special = NON_PM;
} else if (is_soko_prize(obj)) {
u.uachieve.finish_sokoban = 1;
obj->record_achieve_special = 0;
obj->record_achieve_special = NON_PM;
}
}

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 options.c $NHDT-Date: 1507846854 2017/10/12 22:20:54 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.315 $ */
/* NetHack 3.6 options.c $NHDT-Date: 1508827592 2017/10/24 06:46:32 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.316 $ */
/* Copyright (c) Stichting Mathematisch Centrum, Amsterdam, 1985. */
/* NetHack may be freely redistributed. See license for details. */
@@ -724,6 +724,12 @@ initoptions_init()
iflags.travelcc.x = iflags.travelcc.y = -1;
/* 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;
/* 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);

View File

@@ -1,4 +1,4 @@
/* NetHack 3.6 sp_lev.c $NHDT-Date: 1449269920 2015/12/04 22:58:40 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.77 $ */
/* NetHack 3.6 sp_lev.c $NHDT-Date: 1508827593 2017/10/24 06:46:33 $ $NHDT-Branch: NetHack-3.6.0 $:$NHDT-Revision: 1.89 $ */
/* Copyright (c) 1989 by Jean-Christophe Collet */
/* NetHack may be freely redistributed. See license for details. */
@@ -1881,6 +1881,7 @@ struct mkroom *croom;
}
} else {
struct obj *cobj = container_obj[container_idx - 1];
remove_object(otmp);
if (cobj) {
(void) add_to_container(cobj, otmp);
@@ -1941,12 +1942,32 @@ struct mkroom *croom;
}
}
/* Nasty hack here: try to determine if this is the Mines or Sokoban
* "prize" and then set record_achieve_special (maps to corpsenm)
* for the object. That field will later be checked to find out if
* the player obtained the prize. */
if (is_mines_prize(otmp) || is_soko_prize(otmp)) {
otmp->record_achieve_special = 1;
if (o->id != -1) {
static const char prize_warning[] = "multiple prizes on %s level";
static int mcount = 0, scount = 0;
/* 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) {
otmp->record_achieve_special = MINES_PRIZE;
if (++mcount > 1)
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;
if (++scount > 1)
impossible(prize_warning, "sokoban end");
} else if (otmp->otyp == iflags.soko_prize_type2) {
otmp->record_achieve_special = SOKO_PRIZE2;
if (++scount > 1)
impossible(prize_warning, "sokoban end");
}
}
}
stackobj(otmp);