From dcd44cc0f1f98e469253e7f44ca29baf8abd79bd Mon Sep 17 00:00:00 2001
From: copperwater <aosdict@gmail.com>
Date: Sat, 22 Jun 2024 19:36:50 -0400
Subject: [PATCH] Fix: two cases of Bifrost not being restored correctly

First bug is issue #212: polymorphing the nemesis and then killing it
meant the killed_nemesis flag never got set, so level updates based on
that flag never triggered. The fix for this is to track the nemesis's
monster ID similar to how it works for the quest leader. This came with
a few tweaks:
- The nemesis's corpse will keep its monster traits.
- The nemesis cannot be tamed.
- You get the big alignment record bonus for killing them.
- They will still talk in their polymorphed form if their form is
  capable of speech.

The second bug involved if the player managed to kill Surtur before
loading Val-loca. This was because of an oversight when the
visited_after_event flag was added - it was not added in
clear_level_structures() and therefore the locate level was registering
as having already processed the post-Surtur-kill change.

Closes #212
---
 doc/xnh-changelog-9.0.md |  2 ++
 include/quest.h          |  1 +
 src/dog.c                |  2 ++
 src/makemon.c            |  2 ++
 src/mklev.c              |  1 +
 src/mon.c                |  7 +++++--
 src/quest.c              | 14 ++++++++------
 src/sounds.c             |  2 ++
 src/zap.c                |  5 ++++-
 9 files changed, 27 insertions(+), 9 deletions(-)

diff --git a/doc/xnh-changelog-9.0.md b/doc/xnh-changelog-9.0.md
index ffb69a254..79c2e838d 100644
--- a/doc/xnh-changelog-9.0.md
+++ b/doc/xnh-changelog-9.0.md
@@ -65,3 +65,5 @@ changes:
 
 ### Architectural changes
 
+- The monster ID of the quest nemesis is now tracked similar to the quest
+  leader's.
diff --git a/include/quest.h b/include/quest.h
index e6df98c2e..d13327d86 100644
--- a/include/quest.h
+++ b/include/quest.h
@@ -35,6 +35,7 @@ struct q_score {              /* Quest "scorecard" */
     Bitfield(godgend, 2); /* deity's gender */
 
     unsigned leader_m_id;
+    unsigned nemesis_m_id;
 };
 
 #define MIN_QUEST_ALIGN 20 /* at least this align.record to start */
diff --git a/src/dog.c b/src/dog.c
index 407f6743c..15ba98c4c 100644
--- a/src/dog.c
+++ b/src/dog.c
@@ -1167,6 +1167,8 @@ tamedog(struct monst *mtmp, struct obj *obj, boolean pacify_only)
 
     if (mtmp->m_id == gq.quest_status.leader_m_id)
         return FALSE;
+    if (mtmp->m_id == gq.quest_status.nemesis_m_id)
+        return FALSE;
 
     if (pacify_only) {
        return FALSE;
diff --git a/src/makemon.c b/src/makemon.c
index 06fc7365b..36e7d5fdf 100644
--- a/src/makemon.c
+++ b/src/makemon.c
@@ -1307,6 +1307,8 @@ makemon(
     set_mon_data(mtmp, ptr); /* mtmp->data = ptr; */
     if (ptr->msound == MS_LEADER && quest_info(MS_LEADER) == mndx)
         gq.quest_status.leader_m_id = mtmp->m_id;
+    if (ptr->msound == MS_NEMESIS && quest_info(MS_NEMESIS) == mndx)
+        gq.quest_status.nemesis_m_id = mtmp->m_id;
     mtmp->mnum = mndx;
 
     /* set up level and hit points */
diff --git a/src/mklev.c b/src/mklev.c
index d80263b6f..c4cfdc578 100644
--- a/src/mklev.c
+++ b/src/mklev.c
@@ -1067,6 +1067,7 @@ clear_level_structures(void)
     gl.level.flags.arboreal = 0;
     gl.level.flags.wizard_bones = 0;
     gl.level.flags.outdoors = 0;
+    gl.level.flags.visited_after_event = 0;
 
     gn.nroom = 0;
     gr.rooms[0].hx = -1;
diff --git a/src/mon.c b/src/mon.c
index dabfef3d0..4e820cadd 100644
--- a/src/mon.c
+++ b/src/mon.c
@@ -505,9 +505,10 @@ pm_to_cham(int mndx)
 #define KEEPTRAITS(mon)                                                  \
     ((mon)->isshk || (mon)->mtame || unique_corpstat((mon)->data)        \
      || is_reviver((mon)->data)                                          \
-        /* normally quest leader will be unique, */                      \
+        /* normally quest leader/nemesis will be unique, */              \
         /* but he or she might have been polymorphed  */                 \
      || (mon)->m_id == gq.quest_status.leader_m_id                       \
+     || (mon)->m_id == gq.quest_status.nemesis_m_id                      \
         /* special cancellation handling for these */                    \
      || (dmgtype((mon)->data, AD_SEDU) || dmgtype((mon)->data, AD_SSEX)))
 
@@ -3091,6 +3092,8 @@ mondead(struct monst *mtmp)
     /* if it's a (possibly polymorphed) quest leader, mark him as dead */
     if (mtmp->m_id == gq.quest_status.leader_m_id)
         gq.quest_status.killed_leader = TRUE;
+    if (mtmp->m_id == gq.quest_status.nemesis_m_id)
+        gq.quest_status.killed_nemesis = TRUE;
 #ifdef MAIL_STRUCTURES
     /* if the mail daemon dies, no more mail delivery.  -3. */
     if (mndx == PM_MAIL_DAEMON)
@@ -3680,7 +3683,7 @@ xkilled(
         change_luck(-20);
         pline("That was %sa bad idea...",
               u.uevent.qcompleted ? "probably " : "");
-    } else if (mdat->msound == MS_NEMESIS) { /* Real good! */
+    } else if (mtmp->m_id == gq.quest_status.nemesis_m_id) { /* Real good! */
         if (!gq.quest_status.killed_leader)
             adjalign((int) (ALIGNLIM / 4));
     } else if (mdat->msound == MS_GUARDIAN) { /* Bad */
diff --git a/src/quest.c b/src/quest.c
index 17b692e25..9bf3f3d04 100644
--- a/src/quest.c
+++ b/src/quest.c
@@ -481,10 +481,11 @@ quest_chat(struct monst *mtmp)
             setmangry(mtmp, FALSE);
         return;
     }
-    switch (mtmp->data->msound) {
-    case MS_NEMESIS:
+    if (mtmp->m_id == Qstat(nemesis_m_id) && mtmp->data->msound > MS_ANIMAL) {
         chat_with_nemesis();
-        break;
+        return;
+    }
+    switch (mtmp->data->msound) {
     case MS_GUARDIAN:
         chat_with_guardian();
         break;
@@ -500,10 +501,11 @@ quest_talk(struct monst *mtmp)
         leader_speaks(mtmp);
         return;
     }
-    switch (mtmp->data->msound) {
-    case MS_NEMESIS:
+    if (mtmp->m_id == Qstat(nemesis_m_id) && mtmp->data->msound > MS_ANIMAL) {
         nemesis_speaks();
-        break;
+        return;
+    }
+    switch (mtmp->data->msound) {
     case MS_DJINNI:
         prisoner_speaks(mtmp);
         break;
diff --git a/src/sounds.c b/src/sounds.c
index ed9526658..96633d13b 100644
--- a/src/sounds.c
+++ b/src/sounds.c
@@ -775,6 +775,8 @@ domonnoise(register struct monst* mtmp)
     /* leader might be poly'd; if he can still speak, give leader speech */
     if (mtmp->m_id == gq.quest_status.leader_m_id && msound > MS_ANIMAL)
         msound = MS_LEADER;
+    if (mtmp->m_id == gq.quest_status.nemesis_m_id && msound > MS_ANIMAL)
+        msound = MS_NEMESIS;
     /* make sure it's your role's quest guardian; adjust if not */
     else if (msound == MS_GUARDIAN && ptr != &mons[gu.urole.guardnum])
         msound = mons[genus(monsndx(ptr), 1)].msound;
diff --git a/src/zap.c b/src/zap.c
index 22e801256..98d7bee0c 100644
--- a/src/zap.c
+++ b/src/zap.c
@@ -723,11 +723,14 @@ montraits(
            (we cleared it when loading bones) */
         if (mtmp->m_id) {
             mtmp2->m_id = mtmp->m_id;
-            /* might be bringing quest leader back to life */
+            /* might be bringing quest leader/nemesis back to life */
             if (gq.quest_status.killed_leader
                 /* killed_leader implies leader_m_id is valid */
                 && mtmp2->m_id == gq.quest_status.leader_m_id)
                 gq.quest_status.killed_leader = FALSE;
+            if (gq.quest_status.killed_nemesis
+                && mtmp2->m_id == gq.quest_status.nemesis_m_id)
+                gq.quest_status.killed_nemesis = FALSE;
         }
         mtmp2->mx = mtmp->mx;
         mtmp2->my = mtmp->my;