From 1e2c1facf6bbc405a85a641b19d12dbe1dd32032 Mon Sep 17 00:00:00 2001
From: Zannick <jokeserver@gmail.com>
Date: Sat, 31 Jan 2015 13:58:07 -0800
Subject: [PATCH] Prevent infinite recursion via frenzied twins.

- Don't attempt to make a frenzied twin hostile since it won't work.
- Don't convert one twin if the other dies insane.
- Refactor some common twin-finding code into a separate function.
- Hold off on upgrading them until after they come out of it. Duvessa
  won't be able to berserk while insane or exhausted anyways, and Dowan
  can't really cast while insane, so this mostly affects when Dowan's
  spell set changes and the message printed.
- A twin that comes out of frenzy reverts to the same attitude as their
  twin (or hostile if that twin is still frenzied or dead). This, I think,
  makes the most sense, since the only sources of frenzy are: the monster
  spell (which neither has), the AoE player spell, and the needle. And,
  let's face it, if both get frenzied by a monster using needles, it's
  probably the player's fault.
- Add "frenzied and insane" to monsters' debug descriptions.
---
 crawl-ref/source/describe.cc  | 19 +++++++--
 crawl-ref/source/mon-death.cc | 94 ++++++++++++++++++-------------------------
 crawl-ref/source/mon-death.h  |  1 +
 crawl-ref/source/mon-ench.cc  | 11 ++++-
 crawl-ref/source/view.cc      | 34 ++++++++--------
 5 files changed, 84 insertions(+), 75 deletions(-)

diff --git a/crawl-ref/source/describe.cc b/crawl-ref/source/describe.cc
index 52608af..56c9003 100644
--- a/crawl-ref/source/describe.cc
+++ b/crawl-ref/source/describe.cc
@@ -4050,10 +4050,21 @@ void get_monster_db_desc(const monster_info& mi, describe_info &inf,
         attitude.emplace_back("wont_attack");
     if (!attitude.empty())
     {
-        inf.body << "; " << comma_separated_line(attitude.begin(),
-                                                 attitude.end(),
-                                                 "; ", "; ");
-    }
+        if (mons.has_ench(ENCH_INSANE))
+        {
+            inf.body << "; frenzied and insane (otherwise "
+                     << comma_separated_line(attitude.begin(), attitude.end(),
+                                             "; ", "; ")
+                     << ")";
+        }
+        else
+        {
+            inf.body << "; " << comma_separated_line(attitude.begin(),
+                                                     attitude.end(),
+                                                     "; ", "; ");
+        }
+    } else if (mons.has_ench(ENCH_INSANE))
+        inf.body << "; frenzied and insane";
 
     const monster_spells &hspell_pass = mons.spells;
     bool found_spell = false;
diff --git a/crawl-ref/source/mon-death.cc b/crawl-ref/source/mon-death.cc
index 97d744c..8cd2721 100644
--- a/crawl-ref/source/mon-death.cc
+++ b/crawl-ref/source/mon-death.cc
@@ -3199,6 +3199,30 @@ bool mons_is_elven_twin(const monster* mons)
 }
 
 /**
+ * Find Duvessa or Dowan, given the other.
+ *
+ * @param mons    The monster whose twin we seek.
+ * @return        A pointer to the other elven twin, or nullptr if the twin
+ *                no longer exists, or if given a mons that is not an elven twin.
+**/
+monster* mons_find_elven_twin_of(const monster* mons)
+{
+    if (!mons_is_elven_twin(mons))
+        return nullptr;
+
+    for (monster_iterator mi; mi; ++mi)
+    {
+        if (*mi == mons)
+            continue;
+
+        if (mons_is_elven_twin(*mi))
+            return *mi;
+    }
+
+    return nullptr;
+}
+
+/**
  * Perform functional changes Dowan or Duvessa upon the other's death.
  *
  * This functional is called when either Dowan or Duvessa are killed or
@@ -3214,33 +3238,21 @@ void elven_twin_died(monster* twin, bool in_transit, killer_type killer, int kil
 {
     // Sometimes, if you pacify one twin near a staircase, they leave
     // in the same turn. Convert, in those instances.
-    if (twin->neutral())
+    if (twin->neutral() && !twin->has_ench(ENCH_INSANE))
     {
         elven_twins_pacify(twin);
         return;
     }
 
-    monster* mons = nullptr;
-
-    for (monster_iterator mi; mi; ++mi)
-    {
-        if (*mi == twin)
-            continue;
-
-        // Don't consider already neutralised monsters.
-        if ((*mi)->good_neutral())
-            continue;
-
-        if (mons_is_elven_twin(*mi))
-        {
-            mons = *mi;
-            break;
-        }
-    }
+    monster* mons = mons_find_elven_twin_of(twin);
 
     if (!mons)
         return;
 
+    // Don't consider already neutralised monsters.
+    if (mons->good_neutral())
+        return;
+
     // Okay, let them climb stairs now.
     mons->props["can_climb"] = true;
     if (!in_transit)
@@ -3298,7 +3310,7 @@ void elven_twin_died(monster* twin, bool in_transit, killer_type killer, int kil
     }
 
     // Finally give them new energy
-    if (mons_near(mons))
+    if (mons_near(mons) && !mons->has_ench(ENCH_INSANE))
         elven_twin_energize(mons);
     else
         mons->props[ELVEN_ENERGIZE_KEY] = true;
@@ -3329,28 +3341,14 @@ void elven_twin_energize(monster* mons)
 **/
 void elven_twins_pacify(monster* twin)
 {
-    monster* mons = nullptr;
-
-    for (monster_iterator mi; mi; ++mi)
-    {
-        if (*mi == twin)
-            continue;
-
-        // Don't consider already neutralised monsters.
-        if ((*mi)->neutral())
-            continue;
-
-        if (mons_is_elven_twin(*mi))
-        {
-            mons = *mi;
-            break;
-        }
-    }
+    monster* mons = mons_find_elven_twin_of(twin);
 
     if (!mons)
         return;
 
-    ASSERT(!mons->neutral());
+    // Don't consider already neutralised monsters.
+    if (mons->neutral())
+        return;
 
     if (you_worship(GOD_ELYVILON))
         gain_piety(random2(mons->max_hit_points / (2 + you.piety / 20)), 2);
@@ -3373,27 +3371,15 @@ void elven_twins_pacify(monster* twin)
 **/
 void elven_twins_unpacify(monster* twin)
 {
-    monster* mons = nullptr;
-
-    for (monster_iterator mi; mi; ++mi)
-    {
-        if (*mi == twin)
-            continue;
-
-        // Don't consider already neutralised monsters.
-        if (!(*mi)->neutral())
-            continue;
-
-        if (mons_is_elven_twin(*mi))
-        {
-            mons = *mi;
-            break;
-        }
-    }
+    monster* mons = mons_find_elven_twin_of(twin);
 
     if (!mons)
         return;
 
+    // Don't consider already neutralised monsters.
+    if (!mons->neutral() || mons->has_ench(ENCH_INSANE))
+        return;
+
     behaviour_event(mons, ME_WHACK, &you, you.pos(), false);
 }
 
diff --git a/crawl-ref/source/mon-death.h b/crawl-ref/source/mon-death.h
index a196763..98d6e0f 100644
--- a/crawl-ref/source/mon-death.h
+++ b/crawl-ref/source/mon-death.h
@@ -70,6 +70,7 @@ bool mons_is_mons_class(const monster* mons, monster_type type);
 void pikel_band_neutralise();
 
 bool mons_is_elven_twin(const monster* mons);
+monster* mons_find_elven_twin_of(const monster* mons);
 void elven_twin_died(monster* twin, bool in_transit, killer_type killer, int killer_index);
 void elven_twin_energize(monster* mons);
 void elven_twins_pacify(monster* twin);
diff --git a/crawl-ref/source/mon-ench.cc b/crawl-ref/source/mon-ench.cc
index 667a6d6..f876ec9 100644
--- a/crawl-ref/source/mon-ench.cc
+++ b/crawl-ref/source/mon-ench.cc
@@ -475,7 +475,16 @@ void monster::remove_enchantment_effect(const mon_enchant &me, bool quiet)
         break;
 
     case ENCH_INSANE:
-        attitude = static_cast<mon_attitude_type>(props["old_attitude"].get_short());
+        if (mons_is_elven_twin(this))
+        {
+            monster* twin = mons_find_elven_twin_of(this);
+            if (twin && !twin->has_ench(ENCH_INSANE))
+                attitude = twin->attitude;
+            else
+                attitude = ATT_HOSTILE;
+        }
+        else
+            attitude = static_cast<mon_attitude_type>(props["old_attitude"].get_short());
         mons_att_changed(this);
         break;
 
diff --git a/crawl-ref/source/view.cc b/crawl-ref/source/view.cc
index bb5afa1..9c6f453 100644
--- a/crawl-ref/source/view.cc
+++ b/crawl-ref/source/view.cc
@@ -151,24 +151,26 @@ void seen_monsters_react(int stealth)
         gozag_check_bribe(*mi);
         slime_convert(*mi);
 
-        // Trigger Duvessa & Dowan upgrades
-        if (mi->props.exists(ELVEN_ENERGIZE_KEY))
-        {
-            mi->props.erase(ELVEN_ENERGIZE_KEY);
-            elven_twin_energize(*mi);
-        }
+        if (!mi->has_ench(ENCH_INSANE)) {
+            // Trigger Duvessa & Dowan upgrades
+            if (mi->props.exists(ELVEN_ENERGIZE_KEY))
+            {
+                mi->props.erase(ELVEN_ENERGIZE_KEY);
+                elven_twin_energize(*mi);
+            }
 #if TAG_MAJOR_VERSION == 34
-        else if (mi->props.exists(OLD_DUVESSA_ENERGIZE_KEY))
-        {
-            mi->props.erase(OLD_DUVESSA_ENERGIZE_KEY);
-            elven_twin_energize(*mi);
-        }
-        else if (mi->props.exists(OLD_DOWAN_ENERGIZE_KEY))
-        {
-            mi->props.erase(OLD_DOWAN_ENERGIZE_KEY);
-            elven_twin_energize(*mi);
-        }
+            else if (mi->props.exists(OLD_DUVESSA_ENERGIZE_KEY))
+            {
+                mi->props.erase(OLD_DUVESSA_ENERGIZE_KEY);
+                elven_twin_energize(*mi);
+            }
+            else if (mi->props.exists(OLD_DOWAN_ENERGIZE_KEY))
+            {
+                mi->props.erase(OLD_DOWAN_ENERGIZE_KEY);
+                elven_twin_energize(*mi);
+            }
 #endif
+        }
     }
 }
 
-- 
1.9.1

