refactor: unify hepl and fedhas ally protection
authoradvil <rawlins@gmail.com>
Fri, 30 Apr 2021 14:37:56 +0000 (10:37 -0400)
committeradvil <rawlins@gmail.com>
Fri, 30 Apr 2021 14:37:56 +0000 (10:37 -0400)
Historically, these allies were treated quite differently, but
ab74d117c10 put them closer together by allowing some attacks to pass
through ancestors. However, like the original fedhas protection
implementation, this check is sprinkled all over the place leading to
inconsistent behavior between special cases and reports like #1925 (deja
vu for those who did the original fedhas bugfixing).

This commit continues the trend by doing the checks in exactly the same
places, leading to cleaner code and a more consistent set of religious
ally protections. Unlike the previous commit that refactors fedhas, this
commit is an expansion: hepl ally protections now cover a whole bunch of
cases (various spells, clouds, etc) that weren't considered in previous
commits, and hep allies should now be immune to all sorts of area spells
like refrigeration. But it is much cleaner from the player's
perspective, and code perspective, to give these two cases exactly the
same protection, once shoot-through is allowed for ancestors in the
first place.

One awkward case I am still aware of is OoD behavior, where the ancestor
is protected from damage but seems to block the orbs (with or without a
shield).

In the long run it would be nice to continue generalizing this code to
cover demonic guardians, conj projectiles, etc., but the more general it
is, the harder it is to get the messaging right for all cases, so I
aborted trying to do that for now.

Resolves #1925

crawl-ref/source/beam.cc
crawl-ref/source/god-abil.cc
crawl-ref/source/monster.cc
crawl-ref/source/religion.cc
crawl-ref/source/spl-summoning.cc

index 371c25e..2b82130 100644 (file)
@@ -4762,16 +4762,11 @@ void bolt::affect_monster(monster* mon)
 
     if (shoot_through_monster(*this, mon) && !is_tracer)
     {
-        // FIXME: Could use a better message, something about
-        // dodging that doesn't sound excessively weird would be
-        // nice.
         if (you.see_cell(mon->pos()))
         {
             if (testbits(mon->flags, MF_DEMONIC_GUARDIAN))
                 mpr("Your demonic guardian avoids your attack.");
-            else if (mons_is_hepliaklqana_ancestor(mon->type))
-                mprf("%s avoids your attack.", mon->name(DESC_THE).c_str());
-            else if (!bush_immune(*mon))
+            else
                 god_protects(agent(), mon, false); // messaging
         }
     }
@@ -5036,7 +5031,7 @@ void bolt::affect_monster(monster* mon)
         //
         // FIXME: Should be a better way of doing this. For now, we are
         // just falsifying the death report... -cao
-        else if (flavour == BEAM_SPORE && god_protects(mon))
+        else if (flavour == BEAM_SPORE && god_protects(mon) && fedhas_protects(mon))
         {
             if (mon->attitude == ATT_FRIENDLY)
                 mon->attitude = ATT_HOSTILE;
@@ -5212,7 +5207,7 @@ bool ench_flavour_affects_monster(beam_type flavour, const monster* mon,
 
     // These are special allies whose loyalty can't be so easily bent
     case BEAM_CHARM:
-        rc = !(mons_is_hepliaklqana_ancestor(mon->type)
+        rc = !(god_protects(mon)
                || testbits(mon->flags, MF_DEMONIC_GUARDIAN));
         break;
 
@@ -6680,8 +6675,7 @@ bool shoot_through_monster(const bolt& beam, const monster* victim)
         return false;
     return god_protects(originator, victim)
            || (originator->is_player()
-               && (testbits(victim->flags, MF_DEMONIC_GUARDIAN)
-                   || mons_is_hepliaklqana_ancestor(victim->type)));
+               && testbits(victim->flags, MF_DEMONIC_GUARDIAN));
 }
 
 /**
index 645998c..689b515 100644 (file)
@@ -5577,7 +5577,7 @@ static void _transfer_drain_nearby(coord_def destination)
     for (adjacent_iterator it(destination); it; ++it)
     {
         monster* mon = monster_at(*it);
-        if (!mon || mons_is_hepliaklqana_ancestor(mon->type))
+        if (!mon || god_protects(mon))
             continue;
 
         const int dur = random_range(60, 150);
index 7eee8fa..77b7d4f 100644 (file)
@@ -5074,7 +5074,7 @@ bool monster::can_go_frenzy(bool check_sleep) const
         return false;
 
     // These allies have a special loyalty
-    if (mons_is_hepliaklqana_ancestor(type)
+    if (god_protects(this)
         || testbits(flags, MF_DEMONIC_GUARDIAN))
     {
         return false;
@@ -6612,5 +6612,6 @@ bool monster::angered_by_attacks() const
             && type != MONS_SPELLFORGED_SERVITOR
             && !mons_is_conjured(type)
             && !testbits(flags, MF_DEMONIC_GUARDIAN)
-            && !mons_is_hepliaklqana_ancestor(type);
+            // allied fed plants, hep ancestor:
+            && !god_protects(this);
 }
index 673d5fe..a709b72 100644 (file)
@@ -2653,11 +2653,21 @@ static bool _fedhas_protects_species(monster_type mc)
            && mons_class_holiness(mc) & MH_PLANT;
 }
 
+/// Whether fedhas would protect `target` from harm if called on to do so.
 bool fedhas_protects(const monster *target)
 {
     return target && _fedhas_protects_species(mons_base_type(*target));
 }
 
+/**
+ * Does some god protect monster `target` from harm triggered by `agent`?
+ * @param agent  The source of the damage. If nullptr, the damage has no source.
+ *               (Currently, no god does protect in this case.)
+ * @param target A monster that is the target of the damage.
+ * @param quiet  If false, do messaging to indicate that target has escaped
+ *               damage.
+ * @return       Whether target should escape damage.
+ */
 bool god_protects(const actor *agent, const monster *target, bool quiet)
 {
     // The alignment check is to allow a penanced player to continue to fight 
@@ -2687,15 +2697,32 @@ bool god_protects(const actor *agent, const monster *target, bool quiet)
         }
         return true;
     }
+
+    if (agent && target && agent->is_player()
+        && mons_is_hepliaklqana_ancestor(target->type))
+    {
+        // TODO: this message does not work very well for all sorts of attacks
+        // should this be a god message?
+        if (!quiet && you.can_see(*target))
+            mprf("%s avoids your attack.", target->name(DESC_THE).c_str());
+        return true;
+    }
     return false;
 }
 
+/**
+ * Does some god protect monster `target` from harm triggered by the player?
+ * @param target A monster that is the target of the damage.
+ * @param quiet  If false, do messaging to indicate that target has escaped
+ *               damage.
+ * @return       Whether target should escape damage.
+ */
 bool god_protects(const monster *target, bool quiet)
 {
     return god_protects(&you, target, quiet);
 }
 
-// Fedhas neutralises most plants and fungi
+/// Whether Fedhas would set `target` to a neutral attitude
 bool fedhas_neutralises(const monster& target)
 {
     return mons_is_plant(target)
index ead22f8..d79ba2b 100644 (file)
@@ -628,6 +628,7 @@ bool tukima_affects(const actor &target)
            && !is_range_weapon(*wpn)
            && !is_special_unrandom_artefact(*wpn)
            && !mons_class_is_animated_weapon(target.type)
+           // XX use god_protects here. But, need to know the caster too...
            && !mons_is_hepliaklqana_ancestor(target.type);
 }