Reforge the Chains IV: Shatter the Chains
authorEdgar A. Bering IV <trizor@gmail.com>
Mon, 15 Feb 2021 19:01:42 +0000 (21:01 +0200)
committerEdgar A. Bering IV <trizor@gmail.com>
Fri, 26 Feb 2021 15:24:45 +0000 (17:24 +0200)
To go with the new curse scheme, uncursing becomes available to any
cursed Ash worshipper.

Uncursing removes and destroys the item (triggering all on remove
effects, no secret disto unwield tech). It also forgoes the currently
offered curse and re-sets the curse timer (to make sure uncursing is
felt).

15 files changed:
crawl-ref/source/ability-type.h
crawl-ref/source/ability.cc
crawl-ref/source/dat/descript/ability.txt
crawl-ref/source/dat/descript/gods.txt
crawl-ref/source/god-abil.cc
crawl-ref/source/god-abil.h
crawl-ref/source/item-prop.cc
crawl-ref/source/item-use.cc
crawl-ref/source/player-equip.cc
crawl-ref/source/religion.cc
crawl-ref/source/tags.cc
crawl-ref/source/transform.cc
crawl-ref/source/wiz-item.cc
crawl-ref/source/wiz-item.h
crawl-ref/source/wizard.cc

index 1145f2e..012f34c 100644 (file)
@@ -194,6 +194,7 @@ enum ability_type
 #else
     ABIL_ASHENZARI_CURSE = 1160,
 #endif
+    ABIL_ASHENZARI_UNCURSE,
     // Dithmenos
     ABIL_DITHMENOS_SHADOW_STEP = 1170,
     ABIL_DITHMENOS_SHADOW_FORM,
index 013caaf..15a591c 100644 (file)
@@ -94,7 +94,7 @@ enum class abflag
     instant             = 0x00000020, // doesn't take time to use
     conf_ok             = 0x00000040, // can use even if confused
     variable_mp         = 0x00000080, // costs a variable amount of MP
-    identify_scroll     = 0x00000100, // Uses ?id
+    curse               = 0x00000100, // Destroys a cursed item
     max_hp_drain        = 0x00000200, // drains max hit points
     gold                = 0x00000400, // costs gold
     sacrifice           = 0x00000800, // sacrifice (Ru)
@@ -518,6 +518,8 @@ static const ability_def Ability_List[] =
     // Ashenzari
     { ABIL_ASHENZARI_CURSE, "Curse Item",
         0, 0, 0, {fail_basis::invo}, abflag::none },
+    { ABIL_ASHENZARI_UNCURSE, "Shatter the Chains",
+        0, 0, 0, {fail_basis::invo}, abflag::curse },
 
     // Dithmenos
     { ABIL_DITHMENOS_SHADOW_STEP, "Shadow Step",
@@ -790,8 +792,8 @@ const string make_cost_description(ability_type ability)
         ret += ", Max HP drain";
     }
 
-    if (abil.flags & abflag::identify_scroll)
-        ret += ", Scroll of identify";
+    if (abil.flags & abflag::curse)
+        ret += ", Cursed item";
 
     if (abil.flags & abflag::gold)
     {
@@ -882,10 +884,10 @@ static const string _detailed_cost_description(ability_type ability)
             ret << "variable";
     }
 
-    if (abil.flags & abflag::identify_scroll)
+    if (abil.flags & abflag::curse)
     {
         have_cost = true;
-        ret << "\nOne scroll of identify";
+        ret << "\nOne cursed item";
     }
 
     if (!have_cost)
@@ -2912,6 +2914,12 @@ static spret _do_ability(const ability_def& abil, bool fail, dist *target)
         break;
     }
 
+    case ABIL_ASHENZARI_UNCURSE:
+        fail_check();
+        if (!ashenzari_uncurse_item())
+            return spret::abort;
+        break;
+
     case ABIL_DITHMENOS_SHADOW_STEP:
         if (_abort_if_stationary() || cancel_harmful_move(false))
             return spret::abort;
@@ -3741,8 +3749,13 @@ vector<ability_type> get_god_abilities(bool ignore_silence, bool ignore_piety,
         if (any_sacrifices)
             abilities.push_back(ABIL_RU_REJECT_SACRIFICES);
     }
-    if (you_worship(GOD_ASHENZARI) && you.props.exists(AVAILABLE_CURSE_KEY))
-        abilities.push_back(ABIL_ASHENZARI_CURSE);
+    if (you_worship(GOD_ASHENZARI))
+    {
+        if (you.props.exists(AVAILABLE_CURSE_KEY))
+            abilities.push_back(ABIL_ASHENZARI_CURSE);
+        if (ignore_piety || you.piety > ASHENZARI_BASE_PIETY )
+            abilities.push_back(ABIL_ASHENZARI_UNCURSE);
+    }
     // XXX: should we check ignore_piety?
     if (you_worship(GOD_HEPLIAKLQANA)
         && piety_rank() >= 2 && !you.props.exists(HEPLIAKLQANA_ALLY_TYPE_KEY))
index f7a3cda..39fc992 100644 (file)
@@ -542,7 +542,12 @@ Removes a randomly selected bad mutation.
 # Ashenzari
 Curse Item ability
 
-Curses a chosen item, at the cost of a scroll of identify.
+Curses a chosen equipped item, binding it to the wearer until the curse
+and the item both are broken.
+%%%%
+Shatter The Chains ability
+
+Destroys a chosen cursed equipped item, relieving the wearer of the curse.
 %%%%
 # Dithmenos
 Shadow Step ability
index ae20e26..a88f319 100644 (file)
@@ -16,11 +16,7 @@ Ashenzari provides worshippers with a bounty of knowledge about their
 surroundings. Followers are able to glimpse the shape of the dungeon around
 them, and can sense portals to other realms. They can detect nearby items and
 traps, and can sense monsters and gain an indication of how dangerous they
-are. While equipped with cursed equipment, they can also identify on sight the
-properties of other items of that type that are found.
-
-Ashenzari's followers can use scrolls of identification to remove curses on
-their items, channeling the immense power of their deity's incomparable vision.
+are.
 %%%%
 Beogh
 
index 35058da..fd218d8 100644 (file)
@@ -13,6 +13,8 @@
 
 #include "act-iter.h"
 #include "areas.h"
+#include "artefact.h"
+#include "art-enum.h"
 #include "attitude-change.h"
 #include "bloodspatter.h"
 #include "branch.h"
@@ -2215,6 +2217,58 @@ bool ashenzari_curse_item()
     return true;
 }
 
+/**
+ * Give a prompt to uncurse (and destroy an item).
+ *
+ * Player can abort without penalty.
+ *
+ * @return      Whether the player uncursed anything.
+ */
+bool ashenzari_uncurse_item()
+{
+    int item_slot = prompt_invent_item("Uncurse and destroy which item?",
+                                       menu_type::invlist,
+                                       OSEL_CURSED_WORN, OPER_ANY,
+                                       invprompt_flag::escape_only);
+    if (prompt_failed(item_slot))
+        return false;
+
+    item_def& item(you.inv[item_slot]);
+
+    if (is_unrandom_artefact(item, UNRAND_FINGER_AMULET)
+        && you.equip[EQ_RING_AMULET] != -1)
+    {
+        mprf(MSGCH_PROMPT, "You must shatter the curse binding the ring to "
+                           "the amulet's finger first!");
+        return false;
+    }
+
+    if (!yesno(make_stringf("Really remove and destroy %s?%s",
+                            item.name(DESC_THE).c_str(),
+                            you.props.exists(AVAILABLE_CURSE_KEY) ?
+                                " Ashenzari will withdraw the offered vision "
+                                "and curse!"
+                                : "").c_str(),
+                            false, 'n'))
+    {
+        canned_msg(MSG_OK);
+        return false;
+    }
+
+    mprf("You shatter the curse binding %s!", item.name(DESC_THE).c_str());
+    unequip_item(item_equip_slot(you.inv[item_slot]));
+    ash_check_bondage();
+
+    if (you.props.exists(AVAILABLE_CURSE_KEY))
+    {
+        simple_god_message(" withdraws the vision and curse.");
+        you.props.erase(AVAILABLE_CURSE_KEY);
+        you.props[ASHENZARI_CURSE_PROGRESS_KEY] = 0;
+    }
+
+    return true;
+}
+
 bool can_convert_to_beogh()
 {
     if (silenced(you.pos()))
index fa350e7..7aab237 100644 (file)
@@ -130,6 +130,7 @@ void cheibriados_time_step(int pow);
 
 void ashenzari_offer_new_curse();
 bool ashenzari_curse_item();
+bool ashenzari_uncurse_item();
 
 bool can_convert_to_beogh();
 void spare_beogh_convert();
index caccf4b..9a24ab2 100644 (file)
@@ -906,30 +906,6 @@ void do_curse_item(item_def &item, bool quiet)
 }
 
 /**
- * Attempt to un-curse the given item.
- *
- * @param item      The item in question.
- * @param check_bondage     Whether to update the player's Ash bondage status.
- *                          (Ash ?rc delays this until later.)
- */
-void do_uncurse_item(item_def &item, bool check_bondage)
-{
-    const bool in_inv = in_inventory(item);
-    if (!item.cursed())
-        return;
-
-    if (in_inv && you.equip[EQ_WEAPON] == item.link)
-    {
-        // Redraw the weapon.
-        you.wield_change = true;
-    }
-    item.flags &= (~ISFLAG_CURSED);
-
-    if (check_bondage && in_inv)
-        ash_check_bondage();
-}
-
-/**
  * Make a net stationary (because it currently traps a victim).
  *
  * @param item The net item.
index f81e0e8..fe035c1 100644 (file)
@@ -3222,10 +3222,7 @@ void read_scroll(item_def& scroll)
             // Do this here so it doesn't turn up in the ID menu.
             set_ident_type(scroll, true);
         }
-        if (have_passive(passive_t::want_curses))
-            cancel_scroll = !remove_curse(alreadyknown, alreadyknown ? pre_succ_msg : "");
-        else
-            cancel_scroll = !_identify(alreadyknown, pre_succ_msg, link);
+        cancel_scroll = !_identify(alreadyknown, pre_succ_msg, link);
         break;
 
     case SCR_ENCHANT_ARMOUR:
index 1a53d72..fefdaea 100644 (file)
@@ -62,7 +62,6 @@ void equip_item(equipment_type slot, int item_slot, bool msg)
     you.equip[slot] = item_slot;
 
     equip_effect(slot, item_slot, false, msg);
-    ash_check_bondage();
     you.gear_change = true;
 }
 
@@ -198,6 +197,9 @@ void unequip_effect(equipment_type slot, int item_slot, bool meld, bool msg)
         _unequip_armour_effect(item, meld, slot);
     else if (slot >= EQ_FIRST_JEWELLERY && slot <= EQ_LAST_JEWELLERY)
         _unequip_jewellery_effect(item, msg, meld, slot);
+
+    if (item.cursed() && !meld)
+        destroy_item(item);
 }
 
 ///////////////////////////////////////////////////////////
index 56556f2..6fbf40c 100644 (file)
@@ -56,6 +56,7 @@
 #include "nearby-danger.h"
 #include "notes.h"
 #include "output.h"
+#include "player-equip.h"
 #include "player-stats.h"
 #include "prompt.h"
 #include "randbook.h"
@@ -2728,11 +2729,10 @@ static void _ash_uncurse()
             continue;
         if (!uncursed)
         {
-            mprf(MSGCH_GOD, GOD_ASHENZARI, "Your curses crumble away.");
+            mprf(MSGCH_GOD, GOD_ASHENZARI, "Your curses shatter.");
             uncursed = true;
         }
-        item_def &item = you.inv[slot];
-        do_uncurse_item(item, false);
+        unequip_item(static_cast<equipment_type>(eq_typ));
     }
 }
 
@@ -3189,15 +3189,6 @@ static void _god_welcome_handle_gear()
         flash_view_delay(UA_PLAYER, god_colour(you.religion), 300);
     }
 
-    if (you_worship(GOD_ASHENZARI))
-    {
-        if (!item_type_known(OBJ_SCROLLS, SCR_IDENTIFY))
-        {
-            set_ident_type(OBJ_SCROLLS, SCR_IDENTIFY, true);
-            pack_item_identify_message(OBJ_SCROLLS, SCR_IDENTIFY);
-        }
-    }
-
     if (have_passive(passive_t::identify_items))
         auto_id_inventory();
 
index ba754b9..3ab6cd8 100644 (file)
@@ -5178,7 +5178,7 @@ void unmarshallItem(reader &th, item_def &item)
         item.charges = 0;
 
     if (th.getMinorVersion() < TAG_MINOR_UNCURSE && item.cursed())
-        do_uncurse_item(item, false);
+        item.flags &= (~ISFLAG_CURSED);
 
     // turn old hides into the corresponding armour
     static const map<int, armour_type> hide_to_armour = {
index 8180b03..dcec961 100644 (file)
@@ -1185,10 +1185,18 @@ static void _remove_equipment(const set<equipment_type>& removed,
                 unequip = true;
         }
 
-        mprf("%s %s%s %s", equip->name(DESC_YOUR).c_str(),
+        const string msg = make_stringf("%s %s%s %s",
+             equip->name(DESC_YOUR).c_str(),
              unequip ? "fall" : "meld",
              equip->quantity > 1 ? "" : "s",
-             unequip ? "away!" : "into your body.");
+             unequip ? "away" : "into your body.");
+
+        if (you_worship(GOD_ASHENZARI) && unequip)
+            mprf(MSGCH_GOD, "%s, shattering the curse!", msg.c_str());
+        else if (unequip)
+            mprf("%s!", msg.c_str());
+        else
+            mpr(msg);
 
         if (unequip)
         {
index 81d40d1..a68315e 100644 (file)
@@ -706,37 +706,6 @@ void wizard_make_object_randart()
     mprf_nocap("%s", item.name(DESC_INVENTORY_EQUIP).c_str());
 }
 
-// Returns whether an item of this type can be cursed.
-static bool _item_type_can_be_cursed(int type)
-{
-    return type == OBJ_WEAPONS || type == OBJ_ARMOUR || type == OBJ_JEWELLERY
-           || type == OBJ_STAVES;
-}
-
-void wizard_uncurse_item()
-{
-    const int i = prompt_invent_item("(Un)curse which item?",
-                                     menu_type::invlist, OSEL_ANY);
-
-    if (!prompt_failed(i))
-    {
-        item_def& item(you.inv[i]);
-
-        if (item.cursed())
-            do_uncurse_item(item);
-        else
-        {
-            if (!_item_type_can_be_cursed(item.base_type))
-            {
-                mpr("That type of item cannot be cursed.");
-                return;
-            }
-            do_curse_item(item);
-        }
-        mprf_nocap("%s", item.name(DESC_INVENTORY_EQUIP).c_str());
-    }
-}
-
 void wizard_identify_pack()
 {
     mpr("You feel a rush of knowledge.");
index 13d6fa1..ec91e76 100644 (file)
@@ -10,7 +10,6 @@ void wizard_create_spec_object_by_name();
 void wizard_tweak_object();
 void wizard_make_object_randart();
 void wizard_value_item();
-void wizard_uncurse_item();
 void wizard_create_all_artefacts(bool override_unique = true);
 void wizard_identify_pack();
 void wizard_unidentify_pack();
index 8aab0a5..f9891e2 100644 (file)
@@ -75,7 +75,6 @@ static void _do_wizard_command(int wiz_command)
         break;
 
     case 'c': wizard_draw_card(); break;
-    case 'C': wizard_uncurse_item(); break;
     case CONTROL('C'): die("Intentional crash");
 
     case 'd': wizard_level_travel(true); break;
@@ -483,7 +482,6 @@ int list_wizard_commands(bool do_redraw_screen)
                        "\n"
                        "<yellow>Item related commands</yellow>\n"
                        "<w>a</w>      acquirement\n"
-                       "<w>C</w>      (un)curse item\n"
                        "<w>i</w>/<w>I</w>    identify/unidentify inventory\n"
                        "<w>y</w>/<w>Y</w>    id/unid item types+level items\n"
                        "<w>o</w>/<w>%</w>    create an object\n"