/* * File: item_use.cc * Summary: Functions for making use of inventory items. * Written by: Linley Henzell */ #include "AppHdr.h" #include "item_use.h" #include #include #include #include #include "externs.h" #include "abl-show.h" #include "artefact.h" #include "beam.h" #include "cio.h" #include "cloud.h" #include "colour.h" #include "command.h" #include "coordit.h" #include "debug.h" #include "decks.h" #include "delay.h" #include "describe.h" #include "directn.h" #include "effects.h" #include "env.h" #include "map_knowledge.h" #include "fight.h" #include "food.h" #include "godabil.h" #include "goditem.h" #include "invent.h" #include "it_use2.h" #include "it_use3.h" #include "items.h" #include "itemname.h" #include "itemprop.h" #include "los.h" #include "macro.h" #include "message.h" #include "misc.h" #include "mon-behv.h" #include "mon-util.h" #include "mon-place.h" #include "terrain.h" #include "mgen_data.h" #include "coord.h" #include "mon-stuff.h" #include "notes.h" #include "options.h" #include "ouch.h" #include "player.h" #include "quiver.h" #include "religion.h" #include "shopping.h" #include "skills.h" #include "skills2.h" #include "spells1.h" #include "spells2.h" #include "spells3.h" #include "spells4.h" #include "spl-book.h" #include "spl-cast.h" #include "spl-mis.h" #include "spl-util.h" #include "state.h" #include "stuff.h" #include "areas.h" #include "teleport.h" #include "transform.h" #include "traps.h" #include "tutorial.h" #include "view.h" #include "shout.h" #include "viewchar.h" #include "viewgeom.h" #include "xom.h" static bool _drink_fountain(); static bool _handle_enchant_weapon( enchant_stat_type which_stat, bool quiet = false, int item_slot = -1 ); static bool _handle_enchant_armour( int item_slot = -1 ); static int _fire_prompt_for_item(std::string& err); static bool _fire_validate_item(int selected, std::string& err); // Rather messy - we've gathered all the can't-wield logic from wield_weapon() // here. bool can_wield(item_def *weapon, bool say_reason, bool ignore_temporary_disability) { #define SAY(x) if (say_reason) { x; } else if (!ignore_temporary_disability && you.berserk()) { SAY(canned_msg(MSG_TOO_BERSERK)); return (false); } if (!can_equip( EQ_WEAPON, ignore_temporary_disability )) { SAY(mpr("You can't wield anything in your present form.")); return (false); } if (!ignore_temporary_disability && you.weapon() && you.weapon()->base_type == OBJ_WEAPONS && you.weapon()->cursed()) { SAY(mpr("You can't unwield your weapon to draw a new one!")); return (false); } // If we don't have an actual weapon to check, return now. if (!weapon) return (true); for (int i = EQ_CLOAK; i <= EQ_AMULET; i++) { if (you.equip[i] != -1 && &you.inv[you.equip[i]] == weapon) { SAY(mpr("You are wearing that object!")); return (false); } } // All non-weapons only need a shield check. if (weapon->base_type != OBJ_WEAPONS) { if (!ignore_temporary_disability && is_shield_incompatible(*weapon)) { SAY(mpr("You can't wield that with a shield.")); return (false); } else return (true); } if (you.body_size(PSIZE_TORSO) < SIZE_LARGE && item_mass(*weapon) >= 300) { SAY(mpr("That's too large and heavy for you to wield.")); return (false); } // Small species wielding large weapons... if (you.body_size(PSIZE_BODY) < SIZE_MEDIUM && !check_weapon_wieldable_size(*weapon, you.body_size(PSIZE_BODY))) { SAY(mpr("That's too large for you to wield.")); return (false); } if (you.undead_or_demonic() && is_holy_item(*weapon)) { if (say_reason) { mpr("This weapon is holy and will not allow you to wield it."); // If it's a standard weapon, you know its ego now. if (!is_artefact(*weapon) && !is_blessed(*weapon) && !item_type_known(*weapon)) { set_ident_flags(*weapon, ISFLAG_KNOW_TYPE); if (in_inventory(*weapon)) mpr(weapon->name(DESC_INVENTORY_EQUIP).c_str()); } } return (false); } if (you.hunger_state < HS_FULL && you.hunger < you_max_hunger() - 500 // ghouls && get_weapon_brand(*weapon) == SPWPN_VAMPIRICISM && you.species != SP_VAMPIRE && you.species != SP_MUMMY) { if (say_reason) { mpr("As you grasp it, you feel a great hunger. Being not satiated, you stop."); // If it's a standard weapon, you know its ego now. if (!is_artefact(*weapon) && !is_blessed(*weapon) && !item_type_known(*weapon)) { set_ident_flags(*weapon, ISFLAG_KNOW_TYPE); if (in_inventory(*weapon)) mpr(weapon->name(DESC_INVENTORY_EQUIP).c_str()); } } return (false); } if (!ignore_temporary_disability && is_shield_incompatible(*weapon)) { SAY(mpr("You can't wield that with a shield.")); return (false); } // We can wield this weapon. Phew! return (true); #undef SAY } static bool _valid_weapon_swap(const item_def &item) { // Weapons and staves are valid weapons. if (item.base_type == OBJ_WEAPONS || item.base_type == OBJ_STAVES) return (true); // Some misc. items need to be wielded to be evoked. if (is_deck(item) || item.base_type == OBJ_MISCELLANY && item.sub_type == MISC_LANTERN_OF_SHADOWS) { return (true); } // Some missiles need to be wielded for spells. if (item.base_type == OBJ_MISSILES) { if (item.sub_type == MI_STONE) return (you.has_spell(SPELL_SANDBLAST)); if (item.sub_type == MI_ARROW) return (you.has_spell(SPELL_STICKS_TO_SNAKES)); return (false); } // Bone Shards. if (item.base_type == OBJ_CORPSES) { return (item.sub_type == CORPSE_SKELETON && you.has_spell(SPELL_BONE_SHARDS)); } // Sublimation of Blood. if (!you.has_spell(SPELL_SUBLIMATION_OF_BLOOD)) return (false); if (item.base_type == OBJ_FOOD) return (item.sub_type == FOOD_CHUNK); if (item.base_type == OBJ_POTIONS && item_type_known(item)) { return (item.sub_type == POT_BLOOD || item.sub_type == POT_BLOOD_COAGULATED); } return (false); } // If force is true, don't check weapon inscriptions. // (Assuming the player was already prompted for that.) bool wield_weapon(bool auto_wield, int slot, bool show_weff_messages, bool force, bool show_unwield_msg) { if (inv_count() < 1) { canned_msg(MSG_NOTHING_CARRIED); return (false); } // Look for conditions like berserking that could prevent wielding // weapons. if (!can_wield(NULL, true)) return (false); int item_slot = 0; // default is 'a' if (auto_wield) { if (item_slot == you.equip[EQ_WEAPON]) item_slot = 1; // backup is 'b' if (slot != -1) // allow external override item_slot = slot; } // If the swap slot has a bad (but valid) item in it, // the swap will be to bare hands. const bool good_swap = (item_slot == PROMPT_GOT_SPECIAL || _valid_weapon_swap(you.inv[item_slot])); // Prompt if not using the auto swap command, or if the swap slot // is empty. if (item_slot != PROMPT_GOT_SPECIAL && (!auto_wield || !you.inv[item_slot].is_valid() || !good_swap)) { if (!auto_wield) { item_slot = prompt_invent_item( "Wield which item (- for none, * to show all)?", MT_INVLIST, OSEL_WIELD, true, true, true, '-', -1, NULL, OPER_WIELD); } else item_slot = PROMPT_GOT_SPECIAL; } if (prompt_failed(item_slot)) return (false); else if (item_slot == you.equip[EQ_WEAPON]) { mpr("You are already wielding that!"); return (true); } // Now we really change weapons! (Most likely, at least...) if (you.duration[DUR_SURE_BLADE]) { mpr("The bond with your blade fades away."); you.duration[DUR_SURE_BLADE] = 0; } // Reset the warning counter. you.received_weapon_warning = false; if (item_slot == PROMPT_GOT_SPECIAL) // '-' or bare hands { if (const item_def* wpn = you.weapon()) { // Can we safely unwield this item? if (has_warning_inscription(*wpn, OPER_WIELD)) { const std::string prompt = "Really unwield " + wpn->name(DESC_INVENTORY) + "?"; if (!yesno(prompt.c_str(), false, 'n')) return (false); } if (!unwield_item(show_weff_messages)) return (false); if (show_unwield_msg) canned_msg(MSG_EMPTY_HANDED); // Switching to bare hands is extra fast. you.turn_is_over = true; you.time_taken *= 3; you.time_taken /= 10; } else mpr("You are already empty-handed."); return (true); } item_def& new_wpn(you.inv[item_slot]); if (!can_wield(&new_wpn, true)) return (false); // For non-auto_wield cases checked above. if (auto_wield && !force && !check_warning_inscriptions(new_wpn, OPER_WIELD)) { return (false); } // Make sure that wielding the weapon won't kill the player. if (!safe_to_remove_or_wear(new_wpn, false)) return (false); // Unwield any old weapon. if (you.weapon() && !unwield_item(show_weff_messages)) return (false); const unsigned int old_talents = your_talents(false).size(); // Go ahead and wield the weapon. you.equip[EQ_WEAPON] = item_slot; // Any oddness on wielding taken care of here. wield_effects(item_slot, show_weff_messages); mpr(new_wpn.name(DESC_INVENTORY_EQUIP).c_str()); // Warn player about low str/dex or throwing skill. if (show_weff_messages) wield_warning(); if (Tutorial.tutorial_left && your_talents(false).size() > old_talents) learned_something_new(TUT_NEW_ABILITY_ITEM); // Time calculations. you.time_taken /= 2; you.wield_change = true; you.m_quiver->on_weapon_changed(); you.turn_is_over = true; return (true); } static const char *shield_base_name(const item_def *shield) { return (shield->sub_type == ARM_BUCKLER? "buckler" : "shield"); } static const char *shield_impact_degree(int impact) { return (impact > 160 ? "severely " : impact > 130 ? "significantly " : impact > 110 ? "" : NULL); } static void warn_launcher_shield_slowdown(const item_def &launcher) { const int slowspeed = launcher_final_speed(launcher, you.shield()) * player_speed() / 100; const int normspeed = launcher_final_speed(launcher, NULL) * player_speed() / 100; // Don't warn the player unless the slowdown is real. if (slowspeed > normspeed) { const char *slow_degree = shield_impact_degree(slowspeed * 100 / normspeed); if (slow_degree) { mprf(MSGCH_WARN, "Your %s %sslows your rate of fire.", shield_base_name(you.shield()), slow_degree); } } } // Warn if your shield is greatly impacting the effectiveness of your weapon? void warn_shield_penalties() { if (!you.shield()) return; // Warnings are limited to bows and quarterstaves at the moment. const item_def *weapon = you.weapon(); if (!weapon) return; if (is_range_weapon(*weapon)) warn_launcher_shield_slowdown(*weapon); else if (weapon->base_type == OBJ_WEAPONS && weapon_skill(*weapon) == SK_STAVES) { mprf(MSGCH_WARN, "Your %s severely limits your weapon's effectiveness.", shield_base_name(you.shield())); } } // Provide a function for handling initial wielding of 'special' // weapons, or those whose function is annoying to reproduce in // other places *cough* auto-butchering *cough*. {gdl} void wield_effects(int item_wield_2, bool showMsgs) { unsigned char special = 0; item_def &item = you.inv[item_wield_2]; const bool artefact = is_artefact(item); const bool known_cursed = item_known_cursed(item); // And here we finally get to the special effects of wielding. {dlb} switch (item.base_type) { case OBJ_MISCELLANY: { if (item.sub_type == MISC_LANTERN_OF_SHADOWS) { if (showMsgs) mpr("The area is filled with flickering shadows."); you.current_vision -= 2; set_los_radius(you.current_vision); you.attribute[ATTR_SHADOWS] = 1; } else if (item.sub_type == MISC_HORN_OF_GERYON) set_ident_flags(item, ISFLAG_IDENT_MASK); break; } case OBJ_STAVES: { if (item.sub_type == STAFF_POWER) { calc_mp(); set_ident_type(item, ID_KNOWN_TYPE); set_ident_flags(item, ISFLAG_EQ_WEAPON_MASK); mpr("You feel your mana capacity increase."); } else if (!maybe_identify_staff(item)) { // Give curse status when wielded. // Right now that's always "uncursed". -- bwr set_ident_flags(item, ISFLAG_KNOW_CURSE); } // Automatically identify rods; you can do this by wielding and then // evoking them, so do it automatically instead. We don't need to give // a message either, as the game will do that automatically. {due} if (item_is_rod(item)) { if (!item_type_known(item)) { set_ident_type( OBJ_STAVES, item.sub_type, ID_KNOWN_TYPE ); set_ident_flags( item, ISFLAG_KNOW_TYPE ); } if (!item_ident( item, ISFLAG_KNOW_PLUSES)) set_ident_flags( item, ISFLAG_KNOW_PLUSES ); } break; } case OBJ_WEAPONS: { if (showMsgs) { if (is_holy_item(item) && you.religion == GOD_YREDELEMNUL) mpr("You really shouldn't be using a holy item like this."); else if (is_unholy_item(item) && is_good_god(you.religion)) mpr("You really shouldn't be using an unholy item like this."); else if (is_evil_item(item) && is_good_god(you.religion)) mpr("You really shouldn't be using an evil item like this."); else if (is_chaotic_item(item) && you.religion == GOD_ZIN) mpr("You really shouldn't be using a chaotic item like this."); else if (is_hasty_item(item) && you.religion == GOD_CHEIBRIADOS) mpr("You really shouldn't be using a fast item like this."); } // Call unrandrt equip func before item is identified. if (artefact) use_artefact(item_wield_2, &showMsgs); const bool was_known = item_type_known(item); bool known_recurser = false; set_ident_flags(item, ISFLAG_EQ_WEAPON_MASK); special = item.special; if (artefact) { special = artefact_wpn_property(item, ARTP_BRAND); if (!was_known) { item.flags |= ISFLAG_NOTED_ID; if (Options.autoinscribe_artefacts) add_autoinscription(item, artefact_auto_inscription(item)); // Make a note of it. take_note(Note(NOTE_ID_ITEM, 0, 0, item.name(DESC_NOCAP_A).c_str(), origin_desc(item).c_str())); } else known_recurser = artefact_known_wpn_property(item, ARTP_CURSED); } if (special != SPWPN_NORMAL) { // message first if (showMsgs) { switch (special) { case SPWPN_FLAMING: mpr("It bursts into flame!"); break; case SPWPN_FREEZING: mpr("It glows with a cold blue light!"); break; case SPWPN_HOLY_WRATH: mpr("It softly glows with a divine radiance!"); break; case SPWPN_ELECTROCUTION: if (!silenced(you.pos())) { mpr("You hear the crackle of electricity.", MSGCH_SOUND); } else mpr("You see sparks fly."); break; case SPWPN_ORC_SLAYING: mpr((you.species == SP_HILL_ORC) ? "You feel a sudden desire to commit suicide." : "You feel a sudden desire to kill orcs!"); break; case SPWPN_DRAGON_SLAYING: mpr(player_genus(GENPC_DRACONIAN) || you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON ? "You feel a sudden desire to commit suicide." : "You feel a sudden desire to slay dragons!"); break; case SPWPN_VENOM: mpr("It begins to drip with poison!"); break; case SPWPN_PROTECTION: mpr("You feel protected!"); break; case SPWPN_EVASION: mpr("You feel nimbler!"); break; case SPWPN_DRAINING: mpr("You sense an unholy aura."); break; case SPWPN_SPEED: mprf("Your %s tingle!", you.hand_name(true).c_str()); break; case SPWPN_FLAME: mpr("It bursts into flame!"); break; case SPWPN_FROST: mpr("It is covered in frost."); break; case SPWPN_VAMPIRICISM: if(you.species == SP_VAMPIRE) { mpr("You feel a bloodthirsty glee!"); break; } if (you.is_undead == US_ALIVE) { mpr("You feel a dreadful hunger."); // takes player from Full to Hungry make_hungry(4500, false, false); } else { mpr("You feel an empty sense of dread."); } break; case SPWPN_RETURNING: mpr("It wiggles slightly."); break; case SPWPN_PAIN: if(you.skills[SK_NECROMANCY] == 0) mpr("You have a feeling of ineptitude."); else if(you.skills[SK_NECROMANCY] <= 4) mpr("Pain shudders through your arm!"); else mpr("A searing pain shoots up your arm!"); break; case SPWPN_CHAOS: mpr("It is briefly surrounded by a scintillating aura " "of random colours."); break; case SPWPN_PENETRATION: mprf("Your %s briefly pass through it before you manage " "to get a firm grip on it.", you.hand_name(true).c_str()); break; case SPWPN_REAPING: mpr("It is briefly surrounded by shifting shadows."); break; default: break; } } // effect second switch (special) { case SPWPN_PROTECTION: you.redraw_armour_class = true; break; case SPWPN_DISTORTION: mpr("Space warps around you for a moment!"); if (!was_known) { // Xom loves it when you ID a distortion weapon this way, // and even more so if he gifted the weapon himself. god_type god; if (origin_is_god_gift(item, &god) && god == GOD_XOM) xom_is_stimulated(255); else xom_is_stimulated(128); } break; default: break; } } if (item.cursed()) { mpr("It sticks to your hand!"); int amusement = 16; if (!known_cursed && !known_recurser) { amusement *= 2; god_type god; if (origin_is_god_gift(item, &god) && god == GOD_XOM) amusement *= 2; } const int wpn_skill = weapon_skill(item.base_type, item.sub_type); if (wpn_skill != SK_FIGHTING && you.skills[wpn_skill] == 0) amusement *= 2; xom_is_stimulated(amusement); } break; } default: break; } if (showMsgs) warn_shield_penalties(); you.attribute[ATTR_WEAPON_SWAP_INTERRUPTED] = 0; } //--------------------------------------------------------------- // // armour_prompt // // Prompt the user for some armour. Returns true if the user picked // something legit. // //--------------------------------------------------------------- bool armour_prompt(const std::string & mesg, int *index, operation_types oper) { ASSERT(index != NULL); bool succeeded = false; int slot; if (inv_count() < 1) canned_msg(MSG_NOTHING_CARRIED); else if (you.berserk()) canned_msg(MSG_TOO_BERSERK); else { int selector = OBJ_ARMOUR; if (oper == OPER_TAKEOFF && !Options.equip_unequip) selector = OSEL_WORN_ARMOUR; slot = prompt_invent_item( mesg.c_str(), MT_INVLIST, selector, true, true, true, 0, -1, NULL, oper ); if (!prompt_failed(slot)) { *index = slot; succeeded = true; } } return (succeeded); } static bool cloak_is_being_removed( void ) { if (current_delay_action() != DELAY_ARMOUR_OFF) return (false); if (you.delay_queue.front().parm1 != you.equip[ EQ_CLOAK ]) return (false); return (true); } //--------------------------------------------------------------- // // wear_armour // //--------------------------------------------------------------- void wear_armour(int slot) // slot is for tiles { if (player_in_bat_form()) { mpr("You can't wear anything in your present form."); return; } int armour_wear_2 = 0; if (slot != -1) armour_wear_2 = slot; else if (!armour_prompt("Wear which item?", &armour_wear_2, OPER_WEAR)) return; // Wear the armour. if (safe_to_remove_or_wear( you.inv[armour_wear_2], wearing_slot(armour_wear_2) )) { do_wear_armour( armour_wear_2, false ); } } static int armour_equip_delay(const item_def &item) { int delay = property( item, PARM_AC ); // Shields are comparatively easy to wear. if (is_shield( item )) delay = delay / 2 + 1; if (delay < 1) delay = 1; return (delay); } bool can_wear_armour(const item_def &item, bool verbose, bool ignore_temporary) { const object_class_type base_type = item.base_type; if (base_type != OBJ_ARMOUR) { if (verbose) mpr("You can't wear that."); return (false); } bool can_wear = true; const int sub_type = item.sub_type; const equipment_type slot = get_armour_slot(item); if (sub_type == ARM_NAGA_BARDING) can_wear = (you.species == SP_NAGA); else if (sub_type == ARM_CENTAUR_BARDING) can_wear = (you.species == SP_CENTAUR); else { can_wear = (fit_armour_size(item, you.body_size(PSIZE_TORSO, ignore_temporary)) == 0); } if (!can_wear) { if (verbose) mpr("You can't wear that!"); return (false); } if (sub_type == ARM_GLOVES) { if (you.has_claws(false) >= 3) { if (verbose) mpr( "You can't wear gloves with your huge claws!" ); return (false); } } if (sub_type == ARM_BOOTS) { if (player_mutation_level(MUT_HOOVES)) { if (verbose) mpr("You can't wear boots with hooves!"); return (false); } if (player_mutation_level(MUT_TALONS)) { if (verbose) mpr("Boots don't fit your talons!"); return (false); } if (you.species == SP_NAGA) { if (verbose) mpr("You can't wear that!"); return (false); } if (!ignore_temporary && you.swimming() && you.species == SP_MERFOLK) { if (verbose) mpr("You don't currently have feet!"); return (false); } } if (you.species == SP_NAGA && sub_type == ARM_NAGA_BARDING && (ignore_temporary || !player_is_shapechanged())) { // It fits. return (true); } else if (you.species == SP_CENTAUR && sub_type == ARM_CENTAUR_BARDING && (ignore_temporary || !player_is_shapechanged())) { // It fits. return (true); } else if (slot == EQ_HELMET) { // Soft helmets (caps and wizard hats) always fit. if (!is_hard_helmet( item )) return (true); if (player_mutation_level(MUT_HORNS)) { if (verbose) mpr("You can't wear that with your horns!"); return (false); } if (player_mutation_level(MUT_BEAK)) { if (verbose) mpr("You can't wear that with your beak!"); return (false); } } if (!can_equip( slot, ignore_temporary )) { if (verbose) mpr("You can't wear that in your present form."); return (false); } // Giant races and draconians. if (you.body_size(PSIZE_TORSO, ignore_temporary) >= SIZE_LARGE || player_genus(GENPC_DRACONIAN)) { if (sub_type >= ARM_LEATHER_ARMOUR && sub_type <= ARM_PLATE_MAIL || sub_type == ARM_GLOVES || sub_type == ARM_BOOTS || sub_type == ARM_BUCKLER || sub_type == ARM_CRYSTAL_PLATE_MAIL || is_hard_helmet(item)) { if (verbose) mpr("This armour doesn't fit on your body."); return (false); } } // Tiny races. if (you.body_size(PSIZE_TORSO, ignore_temporary) <= SIZE_LITTLE) { if ((sub_type >= ARM_LEATHER_ARMOUR && sub_type <= ARM_PLATE_MAIL) || sub_type == ARM_GLOVES || sub_type == ARM_BOOTS || sub_type == ARM_SHIELD || sub_type == ARM_LARGE_SHIELD || sub_type == ARM_CRYSTAL_PLATE_MAIL || is_hard_helmet(item)) { if (verbose) mpr("This armour doesn't fit on your body."); return (false); } } return (true); } bool do_wear_armour(int item, bool quiet) { const item_def &invitem = you.inv[item]; if (!invitem.is_valid()) { if (!quiet) mpr("You don't have any such object."); return (false); } if (!can_wear_armour(invitem, !quiet, false)) return (false); const equipment_type slot = get_armour_slot(invitem); if (item == you.equip[EQ_WEAPON]) { if (!quiet) mpr("You are wielding that object!"); return (false); } if (wearing_slot(item)) { if (Options.equip_unequip) return (!takeoff_armour(item)); else { mpr("You're already wearing that object!"); return (false); } } // if you're wielding something, if (you.weapon() // attempting to wear a shield, && is_shield(invitem) && is_shield_incompatible(*you.weapon(), &invitem)) { if (!quiet) mpr("You'd need three hands to do that!"); return (false); } bool removed_cloak = false; int cloak = -1; // Removing body armour requires removing the cloak first. if (slot == EQ_BODY_ARMOUR && you.equip[EQ_CLOAK] != -1 && !cloak_is_being_removed()) { if (you.equip[EQ_BODY_ARMOUR] != -1 && you.inv[you.equip[EQ_BODY_ARMOUR]].cursed()) { if (!quiet) { mprf("%s is stuck to your body!", you.inv[you.equip[EQ_BODY_ARMOUR]].name(DESC_CAP_YOUR) .c_str()); } return (false); } if (!you.inv[you.equip[EQ_CLOAK]].cursed()) { cloak = you.equip[EQ_CLOAK]; if (!takeoff_armour(you.equip[EQ_CLOAK])) return (false); removed_cloak = true; } else { if (!quiet) mpr("Your cloak prevents you from wearing the armour."); return (false); } } if ((slot == EQ_CLOAK || slot == EQ_HELMET || slot == EQ_GLOVES || slot == EQ_BOOTS || slot == EQ_SHIELD || slot == EQ_BODY_ARMOUR) && you.equip[slot] != -1) { if (!takeoff_armour(you.equip[slot])) return (false); } if (!safe_to_remove_or_wear(invitem, false)) return (false); you.turn_is_over = true; const int delay = armour_equip_delay(invitem); if (delay) start_delay(DELAY_ARMOUR_ON, delay, item); if (removed_cloak) start_delay(DELAY_ARMOUR_ON, 1, cloak); return (true); } bool takeoff_armour(int item) { const item_def& invitem = you.inv[item]; if (invitem.base_type != OBJ_ARMOUR) { mpr("You aren't wearing that!"); return (false); } if (you.berserk()) { canned_msg(MSG_TOO_BERSERK); return (false); } const equipment_type slot = get_armour_slot(invitem); if (!you_tran_can_wear(invitem) && invitem.link == you.equip[slot]) { mprf("%s is melded into your body!", invitem.name(DESC_CAP_YOUR).c_str()); return (false); } if (!wearing_slot(item)) { if (Options.equip_unequip) return do_wear_armour(item, true); else { mpr("You aren't wearing that object!"); return (false); } } // If we get here, we're wearing the item. if (invitem.cursed()) { mprf("%s is stuck to your body!", invitem.name(DESC_CAP_YOUR).c_str()); return (false); } if (!safe_to_remove_or_wear(invitem, true)) return (false); bool removed_cloak = false; int cloak = -1; if (slot == EQ_BODY_ARMOUR) { if (you.equip[EQ_CLOAK] != -1 && !cloak_is_being_removed()) { if (!you.inv[you.equip[EQ_CLOAK]].cursed()) { cloak = you.equip[ EQ_CLOAK ]; if (!takeoff_armour(you.equip[EQ_CLOAK])) return (false); removed_cloak = true; } else { mpr("Your cloak prevents you from removing the armour."); return (false); } } } else { switch (slot) { case EQ_SHIELD: case EQ_CLOAK: case EQ_HELMET: case EQ_GLOVES: case EQ_BOOTS: if (item != you.equip[slot]) { mpr("You aren't wearing that!"); return (false); } break; default: break; } } you.turn_is_over = true; const int delay = armour_equip_delay(invitem); start_delay(DELAY_ARMOUR_OFF, delay, item); if (removed_cloak) start_delay(DELAY_ARMOUR_ON, 1, cloak); return (true); } bool item_is_quivered(const item_def &item) { return (item.link == you.m_quiver->get_fire_item()); } int get_next_fire_item(int current, int direction) { std::vector fire_order; you.m_quiver->get_fire_order(fire_order); if (fire_order.size() == 0) return -1; if (current == -1) return fire_order[0]; for (unsigned i = 0; i < fire_order.size(); i++) { if (fire_order[i] == current) { unsigned next = (i + direction + fire_order.size()) % fire_order.size(); return fire_order[next]; } } return fire_order[0]; } class fire_target_behaviour : public targetting_behaviour { public: fire_target_behaviour() : m_slot(-1), selected_from_inventory(false), need_prompt(false), chosen_ammo(false) { m_slot = you.m_quiver->get_fire_item(&m_noitem_reason); } // targetting_behaviour API virtual command_type get_command(int key = -1); virtual bool should_redraw(); virtual void mark_ammo_nonchosen(); void message_ammo_prompt(const std::string* pre_text = 0); public: int m_slot; std::string m_noitem_reason; bool selected_from_inventory; bool need_prompt; bool chosen_ammo; }; void fire_target_behaviour::message_ammo_prompt(const std::string* pre_text) { const int next_item = get_next_fire_item(m_slot, +1); bool no_other_items = (next_item == -1 || next_item == m_slot); mesclr(); if (pre_text) mpr(pre_text->c_str()); std::ostringstream msg; if (m_slot == -1) msg << "Firing "; else { const item_def& item_def = you.inv[m_slot]; const launch_retval projected = is_launched(&you, you.weapon(), item_def); if (projected == LRET_FUMBLED) msg << "Awkwardly throwing "; else if (projected == LRET_LAUNCHED) msg << "Firing "; else if (projected == LRET_THROWN) msg << "Throwing "; else msg << "Buggy "; } msg << (no_other_items ? "(i - inventory)" : "(i - inventory. (,) - cycle)") << ": "; if (m_slot == -1) { msg << "" << m_noitem_reason << ""; } else { const char* colour = (selected_from_inventory ? "lightgrey" : "w"); msg << "<" << colour << ">" << you.inv[m_slot].name(DESC_INVENTORY_EQUIP) << ""; } formatted_message_history(tagged_string_substr(msg.str(), 0, crawl_view.msgsz.x), MSGCH_PROMPT); } bool fire_target_behaviour::should_redraw() { if (need_prompt) { need_prompt = false; return (true); } return (false); } void fire_target_behaviour::mark_ammo_nonchosen() { chosen_ammo = false; } command_type fire_target_behaviour::get_command(int key) { if (key == -1) key = get_key(); switch (key) { case '(': case CONTROL('N'): case ')': case CONTROL('P'): { const int direction = (key == CONTROL('P') || key == ')') ? -1 : +1; const int next = get_next_fire_item(m_slot, direction); if (next != m_slot && next != -1) { m_slot = next; selected_from_inventory = false; chosen_ammo = true; } // Do this stuff unconditionally to make the prompt redraw. message_ammo_prompt(); need_prompt = true; return (CMD_NO_CMD); } case 'i': { std::string err; const int selected = _fire_prompt_for_item(err); if (selected >= 0 && _fire_validate_item(selected, err)) { m_slot = selected; selected_from_inventory = true; chosen_ammo = true; } message_ammo_prompt( err.length() ? &err : NULL ); need_prompt = true; return (CMD_NO_CMD); } case '?': show_targetting_help(); redraw_screen(); message_ammo_prompt(); need_prompt = true; return (CMD_NO_CMD); } return targetting_behaviour::get_command(key); } static bool _fire_choose_item_and_target(int& slot, dist& target, bool teleport = false) { fire_target_behaviour beh; const bool was_chosen = (slot != -1); if (was_chosen) { std::string warn; if (!_fire_validate_item(slot, warn)) { mpr(warn.c_str()); return (false); } // Force item to be the prechosen one. beh.m_slot = slot; } beh.message_ammo_prompt(); direction(target, DIR_NONE, TARG_HOSTILE, -1, false, !teleport, true, false, NULL, &beh); if (beh.m_slot == -1) { canned_msg(MSG_OK); return (false); } if (!target.isValid) { if (target.isCancel) canned_msg(MSG_OK); return (false); } you.m_quiver->on_item_fired(you.inv[beh.m_slot], beh.chosen_ammo); you.redraw_quiver = true; slot = beh.m_slot; return (true); } // Bring up an inventory screen and have user choose an item. // Returns an item slot, or -1 on abort/failure // On failure, returns error text, if any. static int _fire_prompt_for_item(std::string& err) { if (inv_count() < 1) { // canned_msg(MSG_NOTHING_CARRIED); // Hmmm... err = "You aren't carrying anything."; return -1; } int slot = prompt_invent_item( "Fire/throw which item? (* to show all)", MT_INVLIST, OSEL_THROWABLE, true, true, true, 0, -1, NULL, OPER_FIRE ); if (slot == PROMPT_ABORT || slot == PROMPT_NOTHING) { err = "Nothing selected."; return -1; } return slot; } // Returns false and err text if this item can't be fired. static bool _fire_validate_item(int slot, std::string &err) { if (slot == you.equip[EQ_WEAPON] && you.inv[slot].base_type == OBJ_WEAPONS && you.inv[slot].cursed()) { err = "That weapon is stuck to your hand!"; return (false); } else if (wearing_slot(slot)) { err = "You are wearing that object!"; return (false); } return (true); } // Returns true if warning is given. static bool _fire_warn_if_impossible() { // FIXME: merge this into transform_can_equip_slot() const int trans = you.attribute[ATTR_TRANSFORMATION]; // If you can't wield it, you can't throw it. if (trans == TRAN_SPIDER || trans == TRAN_BLADE_HANDS || trans == TRAN_ICE_BEAST || trans == TRAN_DRAGON || trans == TRAN_BAT) { canned_msg(MSG_PRESENT_FORM); return (true); } if (you.attribute[ATTR_HELD]) { const item_def *weapon = you.weapon(); if (!weapon || !is_range_weapon(*weapon)) { mpr("You cannot throw anything while held in a net!"); return (true); } else if (weapon->sub_type != WPN_BLOWGUN) { mprf("You cannot shoot with your %s while held in a net!", weapon->name(DESC_BASENAME).c_str()); return (true); } // Else shooting is possible. } if (you.berserk()) { canned_msg(MSG_TOO_BERSERK); return (true); } return (false); } int get_ammo_to_shoot(int item, dist &target, bool teleport) { if (_fire_warn_if_impossible()) { flush_input_buffer( FLUSH_ON_FAILURE ); return (-1); } if (!_fire_choose_item_and_target(item, target, teleport)) return (-1); std::string warn; if (!_fire_validate_item(item, warn)) { mpr(warn.c_str()); return (-1); } return (item); } // If item == -1, prompt the user. // If item passed, it will be put into the quiver. void fire_thing(int item) { dist target; item = get_ammo_to_shoot(item, target); if (item == -1) return; if (check_warning_inscriptions(you.inv[item], OPER_FIRE)) { bolt beam; throw_it( beam, item, false, 0, &target ); } } // Basically does what throwing used to do: throw an item without changing // the quiver. void throw_item_no_quiver() { if (_fire_warn_if_impossible()) { flush_input_buffer( FLUSH_ON_FAILURE ); return; } if (inv_count() < 1) { canned_msg(MSG_NOTHING_CARRIED); return; } std::string warn; int slot = _fire_prompt_for_item(warn); if (slot == -1) { canned_msg(MSG_OK); return; } if (!_fire_validate_item(slot, warn)) { mpr(warn.c_str()); return; } // Okay, item is valid. bolt beam; throw_it( beam, slot ); } // Returns delay multiplier numerator (denominator should be 100) for the // launcher with the currently equipped shield. int launcher_shield_slowdown(const item_def &launcher, const item_def *shield) { int speed_adjust = 100; if (!shield) return (speed_adjust); const int shield_type = shield->sub_type; hands_reqd_type hands = hands_reqd(launcher, you.body_size()); switch (hands) { default: case HANDS_ONE: break; case HANDS_HALF: speed_adjust = shield_type == ARM_BUCKLER ? 105 : shield_type == ARM_SHIELD ? 125 : 150; break; case HANDS_TWO: speed_adjust = shield_type == ARM_BUCKLER ? 125 : shield_type == ARM_SHIELD ? 150 : 200; break; } // Adjust for shields skill. if (speed_adjust > 100) speed_adjust -= ((speed_adjust - 100) * 5 / 10) * you.skills[SK_SHIELDS] / 27; return (speed_adjust); } // Returns the attack cost of using the launcher, taking skill and shields // into consideration. NOTE: You must pass in the shield; if you send in // NULL, this function assumes no shield is in use. int launcher_final_speed(const item_def &launcher, const item_def *shield) { const int str_weight = weapon_str_weight( launcher ); const int dex_weight = 10 - str_weight; const skill_type launcher_skill = range_skill( launcher ); const int shoot_skill = you.skills[launcher_skill]; const int bow_brand = get_weapon_brand( launcher ); int speed_base = 10 * property( launcher, PWPN_SPEED ); int speed_min = 70; int speed_stat = str_weight * you.strength + dex_weight * you.dex; // Reduce runaway bow overpoweredness. if (launcher_skill == SK_BOWS) speed_min = 60; if (shield) { const int speed_adjust = launcher_shield_slowdown(launcher, shield); // Shields also reduce the speed cap. speed_base = speed_base * speed_adjust / 100; speed_min = speed_min * speed_adjust / 100; } // Do the same when trying to shoot while held in a net // (only possible with blowguns). if (you.attribute[ATTR_HELD]) { int speed_adjust = 105; // Analogous to buckler and one-handed weapon. speed_adjust -= ((speed_adjust - 100) * 5 / 10) * you.skills[SK_THROWING] / 27; // Also reduce the speed cap. speed_base = speed_base * speed_adjust / 100; speed_min = speed_min * speed_adjust / 100; } int speed = speed_base - 4 * shoot_skill * speed_stat / 250; if (speed < speed_min) speed = speed_min; if (bow_brand == SPWPN_SPEED) { // Speed nerf as per 4.1. Even with the nerf, bows of speed are the // best bows, bar none. speed = 2 * speed / 3; } return (speed); } // Determines if the end result of the combined launcher + ammo brands a // fire/frost beam. // positive: frost, negative: flame, zero: neither bool elemental_missile_beam(int launcher_brand, int ammo_brand) { if (launcher_brand == SPWPN_CHAOS || ammo_brand == SPMSL_CHAOS) return (true); int element = (launcher_brand == SPWPN_FROST + ammo_brand == SPMSL_FROST - launcher_brand == SPWPN_FLAME - ammo_brand == SPMSL_FLAME); return (element != 0); } static bool _poison_hit_victim(bolt& beam, actor* victim, int dmg, int corpse) { if (!victim->alive() || victim->res_poison() > 0) return (false); if (beam.is_tracer) return (true); int levels = 0; actor* agent = beam.agent(); if (dmg > 0 || beam.ench_power == AUTOMATIC_HIT && x_chance_in_y(90 - 3 * victim->armour_class(), 100)) { levels = 1 + random2(3); } if (levels <= 0) return (false); victim->poison(agent, levels); return (true); } static bool _item_penetrates_victim(const bolt &beam, const actor *victim, int &used) { if (beam.aimed_at_feet) return (false); used = 0; return (true); } static bool _silver_damages_victim(bolt &beam, actor* victim, int &dmg, std::string &dmg_msg) { if (victim->undead_or_demonic() || victim->is_chaotic()) { dmg *= 2; if (!beam.is_tracer && you.can_see(victim)) dmg_msg = "The silver sears " + victim->name(DESC_NOCAP_THE) + "!"; } return (false); } static bool _reaping_hit_victim(bolt& beam, actor* victim, int dmg, int corpse) { if (beam.is_tracer || victim->alive() || corpse == -1 || corpse == NON_ITEM || victim->atype() == ACT_PLAYER) { return (false); } return (mons_reaped(beam.agent(), dynamic_cast(victim))); } static bool _dispersal_hit_victim(bolt& beam, actor* victim, int dmg, int corpse) { const actor* agent = beam.agent(); if (!victim->alive() || victim == agent) return (false); if (beam.is_tracer) return (true); const bool was_seen = you.can_see(victim); const bool no_sanct = victim->kill_alignment() == KC_OTHER; coord_def pos, pos2; int tries = 0; do { if (!random_near_space(victim->pos(), pos, false, true, false, no_sanct)) { return (false); } } while (!victim->is_habitable(pos) && tries++ < 100); if (!victim->is_habitable(pos)) return (false); tries = 0; do random_near_space(victim->pos(), pos2, false, true, false, no_sanct); while (!victim->is_habitable(pos2) && tries++ < 100); if (!victim->is_habitable(pos2)) return (false); // Pick the square further away from the agent. const coord_def from = agent->pos(); if (in_bounds(pos2) && grid_distance(pos2, from) > grid_distance(pos, from)) { pos = pos2; } if (pos == victim->pos()) return (false); const coord_def oldpos = victim->pos(); if (victim->atype() == ACT_PLAYER) { // Leave a purple cloud. place_cloud(CLOUD_TLOC_ENERGY, you.pos(), 1 + random2(3), KC_YOU); victim->moveto(pos); mpr("You blink!"); } else { monsters *mon = dynamic_cast(victim); if (!(mon->flags & MF_WAS_IN_VIEW)) mon->seen_context = "thin air"; mon->move_to_pos(pos); // Leave a purple cloud. place_cloud(CLOUD_TLOC_ENERGY, oldpos, 1 + random2(3), victim->kill_alignment()); mon->apply_location_effects(oldpos); mon->check_redraw(oldpos); const bool seen = you.can_see(mon); const std::string name = mon->name(DESC_CAP_THE); if (was_seen && seen) mprf("%s blinks!", name.c_str()); else if (was_seen && !seen) mprf("%s vanishes!", name.c_str()); } return (true); } static bool _electricity_water_explosion(const bolt &beam, const actor *victim, int &used) { used = 1000; ASSERT(!beam.is_tracer); if (you.can_see(victim)) mpr("Electricity arcs through the water!"); conduct_electricity(victim->pos(), beam.agent()); return (true); } static bool _charged_hit_victim(bolt &beam, actor* victim, int &dmg, std::string &dmg_msg) { if (victim->airborne() || victim->res_elec() > 0 || !one_chance_in(3)) return (false); // A hack and code duplication, but that's easier than adding accounting // for each of multiple brands. if (victim->id() == MONS_SIXFIRHY) { if (!beam.is_tracer) victim->heal(10 + random2(15), false); // physical damage is still done } else dmg += 10 + random2(15); if (beam.is_tracer) return (false); if (you.can_see(victim)) if (victim->atype() == ACT_PLAYER) dmg_msg = "You are electrocuted!"; else if (victim->id() == MONS_SIXFIRHY) dmg_msg = victim->name(DESC_CAP_THE) + " is charged up!"; else dmg_msg = "There is a sudden explosion of sparks!"; if (feat_is_water(grd(victim->pos()))) { beam.range_funcs.insert(beam.range_funcs.begin(), _electricity_water_explosion); } return (false); } static bool _blessed_hit_victim(bolt &beam, actor* victim, int &dmg, std::string &dmg_msg) { if (victim->undead_or_demonic()) { dmg += 1 + (random2(dmg * 15) / 10); if (!beam.is_tracer && you.can_see(victim)) dmg_msg = victim->name(DESC_CAP_THE) + " " + victim->conj_verb("convulse") + "!"; } return (false); } int _blowgun_power_roll (bolt &beam) { actor* agent = beam.agent(); int base_power = 0; int blowgun_base = 0; if (agent->atype() == ACT_MONSTER) { monsters* mons = static_cast(agent); base_power += mons->hit_dice; blowgun_base += (*mons).launcher()->plus; } else { base_power += you.skills[SK_THROWING]; blowgun_base += (you.weapon())->plus; } return (base_power + blowgun_base); } bool _blowgun_check (bolt &beam, actor* victim, bool message = true) { actor* agent = beam.agent(); if (agent->atype() == ACT_MONSTER) return (true); monsters* mons = static_cast(victim); int skill = you.skills[SK_THROWING]; int enchantment = (you.weapon())->plus; // You have a really minor chance of hitting with no skills or good // enchants. if (mons->hit_dice < 15 && random2(100) <= 2) return (true); int resist_roll = 2 + random2(4 + skill + enchantment); dprf("Brand rolled %d against monster HD: %d.", resist_roll, mons->hit_dice); if (resist_roll < mons->hit_dice) { simple_monster_message(mons, " resists!"); return (false); } return (true); } static bool _paralysis_hit_victim (bolt& beam, actor* victim, int dmg, int corpse) { if (beam.is_tracer) return (false); if (!_blowgun_check(beam, victim)) return (false); int blowgun_power = _blowgun_power_roll(beam); victim->paralyse(beam.agent(), 5 + random2(blowgun_power)); return (true); } static bool _sleep_hit_victim (bolt& beam, actor* victim, int dmg, int corpse) { if (beam.is_tracer) return (false); if (!_blowgun_check(beam, victim)) return (false); int blowgun_power = _blowgun_power_roll(beam); victim->put_to_sleep(beam.agent(), 5 + random2(blowgun_power)); return (true); } static bool _confusion_hit_victim (bolt &beam, actor* victim, int dmg, int corpse) { if (beam.is_tracer) return (false); if (!_blowgun_check(beam, victim)) return (false); int blowgun_power = _blowgun_power_roll(beam); victim->confuse(beam.agent(), 5 + random2(blowgun_power)); return (true); } static bool _slow_hit_victim (bolt &beam, actor* victim, int dmg, int corpse) { if (beam.is_tracer) return (false); if (!_blowgun_check(beam, victim)) return (false); int blowgun_power = _blowgun_power_roll(beam); victim->slow_down(beam.agent(), 5 + random2(blowgun_power)); return (true); } static bool _sickness_hit_victim (bolt &beam, actor* victim, int dmg, int corpse) { if (beam.is_tracer) return (false); if (!_blowgun_check(beam, victim)) return (false); int blowgun_power = _blowgun_power_roll(beam); victim->sicken(40 + random2(blowgun_power)); return (true); } static bool _rage_hit_victim (bolt &beam, actor* victim, int dmg, int corpse) { if (beam.is_tracer) return (false); if (!_blowgun_check(beam, victim)) return (false); if (victim->atype() == ACT_MONSTER) { monsters* mons = static_cast(victim); mons->go_frenzy(); } else victim->go_berserk(false); return (true); } bool setup_missile_beam(const actor *agent, bolt &beam, item_def &item, std::string &ammo_name, bool &returning) { dungeon_char_type zapsym = DCHAR_SPACE; switch (item.base_type) { case OBJ_WEAPONS: zapsym = DCHAR_FIRED_WEAPON; break; case OBJ_MISSILES: zapsym = DCHAR_FIRED_MISSILE; break; case OBJ_ARMOUR: zapsym = DCHAR_FIRED_ARMOUR; break; case OBJ_WANDS: zapsym = DCHAR_FIRED_STICK; break; case OBJ_FOOD: zapsym = DCHAR_FIRED_CHUNK; break; case OBJ_UNKNOWN_I: zapsym = DCHAR_FIRED_BURST; break; case OBJ_SCROLLS: zapsym = DCHAR_FIRED_SCROLL; break; case OBJ_JEWELLERY: zapsym = DCHAR_FIRED_TRINKET; break; case OBJ_POTIONS: zapsym = DCHAR_FIRED_FLASK; break; case OBJ_UNKNOWN_II: zapsym = DCHAR_FIRED_ZAP; break; case OBJ_BOOKS: zapsym = DCHAR_FIRED_BOOK; break; case OBJ_STAVES: zapsym = DCHAR_FIRED_STICK; break; default: break; } beam.type = dchar_glyph(zapsym); beam.was_missile = true; returning = get_weapon_brand(item) == SPWPN_RETURNING || get_ammo_brand(item) == SPMSL_RETURNING; if (agent->atype() == ACT_PLAYER) { beam.attitude = ATT_FRIENDLY; beam.beam_source = NON_MONSTER; beam.smart_monster = true; beam.thrower = KILL_YOU_MISSILE; } else { const monsters *mon = dynamic_cast(agent); beam.attitude = mons_attitude(mon); beam.beam_source = mon->mindex(); beam.smart_monster = (mons_intel(mon) >= I_NORMAL); beam.thrower = KILL_MON_MISSILE; } beam.item = &item; beam.source = agent->pos(); beam.colour = item.colour; beam.flavour = BEAM_MISSILE; beam.is_beam = false; beam.aux_source.clear(); beam.can_see_invis = agent->can_see_invisible(); item_def *launcher = const_cast(agent)->weapon(0); if (launcher && !item.launched_by(*launcher)) launcher = NULL; const unrandart_entry* entry = launcher && is_unrandom_artefact(*launcher) ? get_unrand_entry(launcher->special) : NULL; if (entry && entry->fight_func.launch) { setup_missile_type sm = entry->fight_func.launch(launcher, &beam, &ammo_name, &returning); switch(sm) { case SM_CONTINUE: break; case SM_FINISHED: return (false); case SM_CANCEL: return (true); } } int bow_brand = SPWPN_NORMAL; if (launcher != NULL) bow_brand = get_weapon_brand(*launcher); int ammo_brand = get_ammo_brand(item); // Launcher brand does not affect ammunition. if (ammo_brand != SPMSL_NORMAL && bow_brand != SPWPN_NORMAL) { // But not for Nessos. if (agent->atype() == ACT_MONSTER) { const monsters* mon = static_cast(agent); if (mon->type != MONS_NESSOS) bow_brand = SPWPN_NORMAL; } else bow_brand = SPWPN_NORMAL; } bool poisoned = (bow_brand == SPWPN_VENOM || ammo_brand == SPMSL_POISONED); const bool exploding = (ammo_brand == SPMSL_EXPLODING); const bool penetrating = (bow_brand == SPWPN_PENETRATION || ammo_brand == SPMSL_PENETRATION); const bool silver = (ammo_brand == SPMSL_SILVER); const bool disperses = (ammo_brand == SPMSL_DISPERSAL); const bool reaping = (bow_brand == SPWPN_REAPING || ammo_brand == SPMSL_REAPING); const bool charged = bow_brand == SPWPN_ELECTROCUTION; const bool blessed = bow_brand == SPWPN_HOLY_WRATH; const bool paralysis = ammo_brand == SPMSL_PARALYSIS; const bool slow = ammo_brand == SPMSL_SLOW; const bool sleep = ammo_brand == SPMSL_SLEEP; const bool confusion = ammo_brand == SPMSL_CONFUSION; const bool sickness = ammo_brand == SPMSL_SICKNESS; const bool rage = ammo_brand == SPMSL_RAGE; ASSERT(!exploding || !is_artefact(item)); beam.name = item.name(DESC_PLAIN, false, false, false); // Print type of item as influenced by launcher. item_def ammo = item; // The chief advantage here is the extra damage this does // against susceptible creatures. // Note: weapons & ammo of eg fire are not cumulative // ammo of fire and weapons of frost don't work together, // and vice versa. // Note that bow_brand is known since the bow is equipped. // Chaos overides flame and frost/ice. if (bow_brand == SPWPN_CHAOS || ammo_brand == SPMSL_CHAOS) { // Chaos can't be poisoned, since that might conflict with // the random healing effect or overlap with the random // poisoning effect. poisoned = false; if (item.special == SPWPN_VENOM || item.special == SPMSL_CURARE) item.special = SPMSL_NORMAL; beam.effect_known = false; beam.flavour = BEAM_CHAOS; beam.name += " of chaos"; beam.colour = ETC_RANDOM; ammo.special = SPMSL_CHAOS; } else if ((bow_brand == SPWPN_FLAME || ammo_brand == SPMSL_FLAME) && ammo_brand != SPMSL_FROST && bow_brand != SPWPN_FROST) { beam.flavour = BEAM_FIRE; beam.name += " of flame"; beam.colour = RED; ammo.special = SPMSL_FLAME; } else if ((bow_brand == SPWPN_FROST || ammo_brand == SPMSL_FROST) && ammo_brand != SPMSL_FLAME && bow_brand != SPWPN_FLAME) { beam.flavour = BEAM_COLD; beam.name += " of frost"; beam.colour = WHITE; ammo.special = SPMSL_FROST; } ASSERT(beam.flavour == BEAM_MISSILE || !is_artefact(item)); ammo_name = ammo.name(DESC_PLAIN); if (silver) beam.damage_funcs.push_back(_silver_damages_victim); if (poisoned) beam.hit_funcs.push_back(_poison_hit_victim); if (penetrating) { beam.range_funcs.push_back(_item_penetrates_victim); beam.hit_verb = "pierces through"; } if (reaping) beam.hit_funcs.push_back(_reaping_hit_victim); if (disperses) beam.hit_funcs.push_back(_dispersal_hit_victim); if (charged) beam.damage_funcs.push_back(_charged_hit_victim); if (blessed) beam.damage_funcs.push_back(_blessed_hit_victim); // New needle brands have no affect when thrown without launcher. if (launcher != NULL) { if (paralysis) beam.hit_funcs.push_back(_paralysis_hit_victim); if (slow) beam.hit_funcs.push_back(_slow_hit_victim); if (sleep) beam.hit_funcs.push_back(_sleep_hit_victim); if (confusion) beam.hit_funcs.push_back(_confusion_hit_victim); if (sickness) beam.hit_funcs.push_back(_sickness_hit_victim); if (rage) beam.hit_funcs.push_back(_rage_hit_victim); } if (reaping && ammo.special != SPMSL_REAPING) { beam.name = "shadowy " + beam.name; ammo_name = "shadowy " + ammo_name; } if (disperses && ammo.special != SPMSL_DISPERSAL) { beam.name = "dispersing " + beam.name; ammo_name = "dispersing " + ammo_name; } // XXX: This doesn't make sense, but it works. if (poisoned && ammo.special != SPMSL_POISONED) { ammo_name = "poisoned " + ammo_name; } if (penetrating && ammo.special != SPMSL_PENETRATION) { beam.name = "penetrating " + beam.name; ammo_name = "penetrating " + ammo_name; } if (silver && ammo.special != SPMSL_SILVER) { beam.name = "silvery " + beam.name; ammo_name = "silvery " + ammo_name; } if (blessed) { beam.name = "blessed " + beam.name; ammo_name = "blessed " + ammo_name; } // Do this here so that we get all the name mods except for a // redundant "exploding". if (exploding) { bolt *expl = new bolt(beam); expl->is_explosion = true; expl->damage = dice_def(2, 5); expl->ex_size = 1; if (beam.flavour == BEAM_MISSILE) { expl->flavour = BEAM_FRAG; expl->name += " fragments"; const std::string short_name = ammo.name(DESC_PLAIN, false, false, false, false, ISFLAG_IDENT_MASK | ISFLAG_COSMETIC_MASK | ISFLAG_RACIAL_MASK); expl->name = replace_all(expl->name, ammo.name(DESC_PLAIN), short_name); } expl->name = "explosion of " + expl->name; beam.special_explosion = expl; } if (exploding && ammo.special != SPMSL_EXPLODING) { beam.name = "exploding " + beam.name; ammo_name = "exploding " + ammo_name; } if (beam.flavour != BEAM_MISSILE) { returning = false; beam.type = dchar_glyph(DCHAR_FIRED_BOLT); } if (!is_artefact(item)) ammo_name = article_a(ammo_name, true); else ammo_name = "the " + ammo_name; return (false); } // XXX This is a bit too generous, as it lets the player determine // that the bolt of fire he just shot from a flaming bow is actually // a poison arrow. Hopefully this isn't too abusable. static bool determines_ammo_brand(int bow_brand, int ammo_brand) { if (bow_brand == SPWPN_FLAME && ammo_brand == SPMSL_FLAME) return (false); if (bow_brand == SPWPN_FROST && ammo_brand == SPMSL_FROST) return (false); if (bow_brand == SPWPN_VENOM && ammo_brand == SPMSL_POISONED) return (false); if (bow_brand == SPWPN_CHAOS && ammo_brand == SPMSL_CHAOS) return (false); if (bow_brand == SPWPN_PENETRATION && ammo_brand == SPMSL_PENETRATION) return (false); if (bow_brand == SPWPN_REAPING && ammo_brand == SPMSL_REAPING) return (false); return (true); } static int stat_adjust(int value, int stat, int statbase, const int maxmult = 160, const int minmult = 40) { int multiplier = (statbase + (stat - statbase) / 2) * 100 / statbase; if (multiplier > maxmult) multiplier = maxmult; else if (multiplier < minmult) multiplier = minmult; if (multiplier > 100) value = value * (100 + random2avg(multiplier - 100, 2)) / 100; else if (multiplier < 100) value = value * (100 - random2avg(100 - multiplier, 2)) / 100; return (value); } static int str_adjust_thrown_damage(int dam) { return stat_adjust(dam, you.strength, 15, 160, 90); } static int dex_adjust_thrown_tohit(int hit) { return stat_adjust(hit, you.dex, 13, 160, 90); } static void identify_floor_missiles_matching(item_def mitem, int idflags) { mitem.flags &= ~idflags; for (int y = 0; y < GYM; ++y) for (int x = 0; x < GXM; ++x) for (stack_iterator si(coord_def(x,y)); si; ++si) { if ((si->flags & ISFLAG_THROWN) && items_stack(*si, mitem)) si->flags |= idflags; } } void _merge_ammo_in_inventory(int slot) { if (!you.inv[slot].is_valid()) return; bool done_anything = false; for (int i = 0; i < ENDOFPACK; ++i) { if (i == slot || !you.inv[i].is_valid()) continue; // Merge with the thrower slot. This could be a bad // thing if you're wielding IDed ammo and firing from // an unIDed stack...but that's a pretty remote case. if (items_stack(you.inv[i], you.inv[slot])) { if (!done_anything) mpr("You combine your ammunition."); inc_inv_item_quantity(slot, you.inv[i].quantity, true); dec_inv_item_quantity(i, you.inv[i].quantity); done_anything = true; } } } void throw_noise(actor* act, const bolt &pbolt, const item_def &ammo) { const item_def* launcher = act->weapon(); if (launcher == NULL || launcher->base_type != OBJ_WEAPONS) return; if (is_launched(act, launcher, ammo) != LRET_LAUNCHED) return; // Throwing and blowguns are silent... int level = 0; const char* msg = NULL; switch(launcher->sub_type) { case WPN_BLOWGUN: return; case WPN_SLING: level = 1; msg = "You hear a whirring sound."; break; case WPN_BOW: level = 5; msg = "You hear a twanging sound."; break; case WPN_LONGBOW: level = 6; msg = "You hear a loud twanging sound."; break; case WPN_CROSSBOW: level = 7; msg = "You hear a thunk."; break; default: DEBUGSTR("Invalid launcher '%s'", launcher->name(DESC_PLAIN).c_str()); return; } if (act->atype() == ACT_PLAYER || you.can_see(act)) msg = NULL; noisy(level, act->pos(), msg, act->mindex()); } // throw_it - currently handles player throwing only. Monster // throwing is handled in mon-act:_mons_throw() // Note: If teleport is true, assume that pbolt is already set up, // and teleport the projectile onto the square. // // Return value is only relevant if dummy_target is non-NULL, and returns // true if dummy_target is hit. bool throw_it(bolt &pbolt, int throw_2, bool teleport, int acc_bonus, dist *target) { dist thr; int shoot_skill = 0; bool ammo_ided = false; // launcher weapon sub-type weapon_type lnchType; int baseHit = 0, baseDam = 0; // from thrown or ammo int ammoHitBonus = 0, ammoDamBonus = 0; // from thrown or ammo int lnchHitBonus = 0, lnchDamBonus = 0; // special add from launcher int exHitBonus = 0, exDamBonus = 0; // 'extra' bonus from skill/dex/str int effSkill = 0; // effective launcher skill int dice_mult = 100; bool returning = false; // Item can return to pack. bool did_return = false; // Returning item actually does return to pack. int slayDam = 0; if (you.confused()) thr.target = you.pos() + coord_def(random2(13)-6, random2(13)-6); else if (target) thr = *target; else { direction(thr, DIR_NONE, TARG_HOSTILE); if (!thr.isValid) { if (thr.isCancel) canned_msg(MSG_OK); return (false); } } pbolt.set_target(thr); item_def& thrown = you.inv[throw_2]; ASSERT(thrown.is_valid()); // Figure out if we're thrown or launched. const launch_retval projected = is_launched(&you, you.weapon(), thrown); // Making a copy of the item: changed only for venom launchers. item_def item = thrown; item.quantity = 1; item.slot = index_to_letter(item.link); // Items that get a temporary brand from a player spell lose the // brand as soon as the player lets go of the item. Can't call // unwield_item() yet since the beam might get canceled. if (you.duration[DUR_WEAPON_BRAND] && projected != LRET_LAUNCHED && throw_2 == you.equip[EQ_WEAPON]) { set_item_ego_type(item, OBJ_WEAPONS, SPWPN_NORMAL); } std::string ammo_name; if (setup_missile_beam(&you, pbolt, item, ammo_name, returning)) { you.turn_is_over = false; return (false); } // Did we know the ammo's brand before throwing it? const bool ammo_brand_known = item_type_known(thrown); // Get the ammo/weapon type. Convenience. const object_class_type wepClass = thrown.base_type; const int wepType = thrown.sub_type; // Determine range. int max_range = 0; int range = 0; if (projected) { if (wepType == MI_LARGE_ROCK) { range = 1 + random2( you.strength / 5 ); max_range = you.strength / 5; if (you.can_throw_large_rocks()) { range += random_range(4, 7); max_range += 7; } } else if (wepType == MI_THROWING_NET) { max_range = range = 2 + you.body_size(PSIZE_BODY); } else { max_range = range = LOS_RADIUS; } } else { // Range based on mass & strength, between 1 and 9. max_range = range = std::max(you.strength-item_mass(thrown)/10 + 3, 1); } range = std::min(range, LOS_RADIUS); max_range = std::min(max_range, LOS_RADIUS); // For the tracer, use max_range. For the actual shot, use range. pbolt.range = max_range; // Don't do the tracing when using Portaled Projectile, or when confused. if (!teleport && !you.confused()) { // Set values absurdly high to make sure the tracer will // complain if we're attempting to fire through allies. pbolt.hit = 100; pbolt.damage = dice_def(1, 100); // Init tracer variables. pbolt.foe_info.reset(); pbolt.friend_info.reset(); pbolt.foe_ratio = 100; pbolt.is_tracer = true; pbolt.fire(); // Should only happen if the player answered 'n' to one of those // "Fire through friendly?" prompts. if (pbolt.beam_cancelled) { canned_msg(MSG_OK); you.turn_is_over = false; if (pbolt.special_explosion != NULL) delete pbolt.special_explosion; return (false); } pbolt.hit = 0; pbolt.damage = dice_def(); } pbolt.is_tracer = false; // Use real range for firing. pbolt.range = range; bool unwielded = false; if (throw_2 == you.equip[EQ_WEAPON] && thrown.quantity == 1) { if (!wield_weapon(true, PROMPT_GOT_SPECIAL, true, false, false)) return (false); unwielded = true; } // Now start real firing! origin_set_unknown(item); if (is_blood_potion(item) && thrown.quantity > 1) { // Initialise thrown potion with oldest potion in stack. long val = remove_oldest_blood_potion(thrown); val -= you.num_turns; item.props.clear(); init_stack_blood_potions(item, val); } // Even though direction is allowed, we're throwing so we // want to use tx, ty to make the missile fly to map edge. if (!teleport) pbolt.set_target(thr); // Get the launcher class, type. Convenience. if (!you.weapon()) lnchType = NUM_WEAPONS; else lnchType = static_cast(you.weapon()->sub_type); // baseHit and damage for generic objects baseHit = std::min(0, you.strength - item_mass(item) / 10); baseDam = item_mass(item) / 100; // special: might be throwing generic weapon; // use base wep. damage, w/ penalty if (wepClass == OBJ_WEAPONS) baseDam = std::max(0, property(item, PWPN_DAMAGE) - 4); // Extract weapon/ammo bonuses due to magic. ammoHitBonus = item.plus; ammoDamBonus = item.plus2; int bow_brand = SPWPN_NORMAL; if (projected == LRET_LAUNCHED) bow_brand = get_weapon_brand(*you.weapon()); const int ammo_brand = get_ammo_brand( item ); // CALCULATIONS FOR LAUNCHED WEAPONS if (projected == LRET_LAUNCHED) { const item_def &launcher = *you.weapon(); // Extract launcher bonuses due to magic. lnchHitBonus = launcher.plus; lnchDamBonus = launcher.plus2; const int item_base_dam = property( item, PWPN_DAMAGE ); const int lnch_base_dam = property( launcher, PWPN_DAMAGE ); const skill_type launcher_skill = range_skill( launcher ); baseHit = property( launcher, PWPN_HIT ); baseDam = lnch_base_dam + random2(1 + item_base_dam); // Slings are terribly weakened otherwise. if (lnch_base_dam == 0) baseDam = item_base_dam; // If we've a zero base damage + an elemental brand, up the damage // slightly so the brand has something to work with. This should // only apply to needles. if (!baseDam && elemental_missile_beam(bow_brand, ammo_brand)) baseDam = 4; // [dshaligram] This is a horrible hack - we force beam.cc to consider // this beam "needle-like". (XXX) if (wepClass == OBJ_MISSILES && wepType == MI_NEEDLE) pbolt.ench_power = AUTOMATIC_HIT; dprf( "Base hit == %d; Base damage == %d " "(item %d + launcher %d)", baseHit, baseDam, item_base_dam, lnch_base_dam); // Fix ammo damage bonus, since missiles only use inv_plus. ammoDamBonus = ammoHitBonus; // Check for matches; dwarven, elven, orcish. if (!(get_equip_race(*you.weapon()) == 0)) { if (get_equip_race(*you.weapon()) == get_equip_race(item)) { baseHit++; baseDam++; // elves with elven bows if (get_equip_race(*you.weapon()) == ISFLAG_ELVEN && player_genus(GENPC_ELVEN)) { baseHit++; } } } // Lower accuracy if held in a net. if (you.attribute[ATTR_HELD]) baseHit--; // For all launched weapons, maximum effective specific skill // is twice throwing skill. This models the fact that no matter // how 'good' you are with a bow, if you know nothing about // trajectories you're going to be a damn poor bowman. Ditto // for crossbows and slings. // [dshaligram] Throwing now two parts launcher skill, one part // ranged combat. Removed the old model which is... silly. // [jpeg] Throwing now only affects actual throwing weapons, // i.e. not launched ones. (Sep 10, 2007) shoot_skill = you.skills[launcher_skill]; effSkill = shoot_skill; const int speed = launcher_final_speed(launcher, you.shield()); dprf("Final launcher speed: %d", speed); you.time_taken = speed * you.time_taken / 100; // [dshaligram] Improving missile weapons: // - Remove the strength/enchantment cap where you need to be strong // to exploit a launcher bonus. // - Add on launcher and missile pluses to extra damage. // [dshaligram] This can get large... exDamBonus = lnchDamBonus + random2(1 + ammoDamBonus); exDamBonus = (exDamBonus > 0 ? random2(exDamBonus + 1) : -random2(-exDamBonus + 1)); exHitBonus = (lnchHitBonus > 0 ? random2(lnchHitBonus + 1) : -random2(-lnchHitBonus + 1)); // Identify ammo type if the information is there. Note // that the bow is always type-identified because it's // wielded. if (determines_ammo_brand(bow_brand, ammo_brand)) { set_ident_flags(item, ISFLAG_KNOW_TYPE); if (ammo_brand != SPMSL_NORMAL) { set_ident_flags(you.inv[throw_2], ISFLAG_KNOW_TYPE); ammo_ided = true; } } // Removed 2 random2(2)s from each of the learning curves, but // left slings because they're hard enough to develop without // a good source of shot in the dungeon. switch (launcher_skill) { case SK_SLINGS: { // Slings are really easy to learn because they're not // really all that good, and it's harder to get ammo anyway. exercise(SK_SLINGS, 1 + random2avg(3, 2)); // Sling bullets are designed for slinging and easier to aim. if (wepType == MI_SLING_BULLET) baseHit += 4; exHitBonus += (effSkill * 3) / 2; // Strength is good if you're using a nice sling. int strbonus = (10 * (you.strength - 10)) / 9; strbonus = (strbonus * (2 * baseDam + ammoDamBonus)) / 20; // cap strbonus = std::min(lnchDamBonus + 1, strbonus); exDamBonus += strbonus; // Add skill for slings... helps to find those vulnerable spots. dice_mult = dice_mult * (14 + random2(1 + effSkill)) / 14; // Now kill the launcher damage bonus. lnchDamBonus = std::min(0, lnchDamBonus); break; } // Blowguns take a _very_ steady hand; a lot of the bonus // comes from dexterity. (Dex bonus here as well as below.) case SK_THROWING: baseHit -= 2; exercise(SK_THROWING, (coinflip()? 2 : 1)); exHitBonus += (effSkill * 3) / 2 + you.dex / 2; // No extra damage for blowguns. // exDamBonus = 0; // Now kill the launcher damage and ammo bonuses. lnchDamBonus = std::min(0, lnchDamBonus); ammoDamBonus = std::min(0, ammoDamBonus); break; case SK_BOWS: { baseHit -= 3; exercise(SK_BOWS, (coinflip()? 2 : 1)); exHitBonus += (effSkill * 2); // Strength is good if you're using a nice bow. int strbonus = (10 * (you.strength - 10)) / 4; strbonus = (strbonus * (2 * baseDam + ammoDamBonus)) / 20; // Cap; reduced this cap, because we don't want to allow // the extremely-strong to quadruple the enchantment bonus. strbonus = std::min(lnchDamBonus + 1, strbonus); exDamBonus += strbonus; // Add in skill for bows - helps you to find those vulnerable spots. // exDamBonus += effSkill; dice_mult = dice_mult * (17 + random2(1 + effSkill)) / 17; // Now kill the launcher damage bonus. lnchDamBonus = std::min(0, lnchDamBonus); break; } // Crossbows are easy for unskilled people. case SK_CROSSBOWS: exercise(SK_CROSSBOWS, (coinflip()? 2 : 1)); baseHit++; exHitBonus += (3 * effSkill) / 2 + 6; // exDamBonus += effSkill * 2 / 3 + 4; dice_mult = dice_mult * (22 + random2(1 + effSkill)) / 22; default: break; } if (bow_brand == SPWPN_VORPAL) { // Vorpal brand adds 30% damage bonus. Increased from 25% // because at 25%, vorpal brand is completely inferior to // speed. At 30% it's marginally better than speed when // fighting monsters with very heavy armour. dice_mult = dice_mult * 130 / 100; } if (ammo_brand == SPMSL_STEEL) dice_mult = dice_mult * 150 / 100; // ID check. Can't ID off teleported projectiles, uh, because // it's too weird. Also it messes up the messages. if (item_ident(*you.weapon(), ISFLAG_KNOW_PLUSES)) { if (!teleport && !item_ident(you.inv[throw_2], ISFLAG_KNOW_PLUSES) && x_chance_in_y(shoot_skill, 100)) { set_ident_flags( item, ISFLAG_KNOW_PLUSES ); set_ident_flags( you.inv[throw_2], ISFLAG_KNOW_PLUSES ); ammo_ided = true; identify_floor_missiles_matching(item, ISFLAG_KNOW_PLUSES); mprf("You are firing %s.", you.inv[throw_2].name(DESC_NOCAP_A).c_str()); } } else if (!teleport && x_chance_in_y(shoot_skill, 100)) { item_def& weapon = *you.weapon(); set_ident_flags(weapon, ISFLAG_KNOW_PLUSES); mprf("You are wielding %s.", weapon.name(DESC_NOCAP_A).c_str()); more(); you.wield_change = true; } } // check for returning ammo from launchers if (returning && projected == LRET_LAUNCHED) { switch (lnchType) { case WPN_CROSSBOW: if (returning && !one_chance_in(1 + skill_bump(SK_CROSSBOWS))) did_return = true; break; case WPN_SLING: if (returning && !one_chance_in(1 + skill_bump(SK_SLINGS))) did_return = true; break; case WPN_BOW: case WPN_LONGBOW: if (returning && !one_chance_in(1 + skill_bump(SK_BOWS))) did_return = true; break; case WPN_BLOWGUN: if (returning && !one_chance_in(1 + skill_bump(SK_THROWING))) did_return = true; break; default: break; } } // CALCULATIONS FOR THROWN WEAPONS if (projected == LRET_THROWN) { returning = returning && !teleport; if (returning && !one_chance_in(1 + skill_bump(SK_THROWING))) did_return = true; baseHit = 0; // Missiles only use inv_plus. if (wepClass == OBJ_MISSILES) ammoDamBonus = ammoHitBonus; // All weapons that use 'throwing' go here. if (wepClass == OBJ_WEAPONS || (wepClass == OBJ_MISSILES && (wepType == MI_STONE || wepType == MI_LARGE_ROCK || wepType == MI_DART || wepType == MI_JAVELIN))) { // Elves with elven weapons. if (get_equip_race(item) == ISFLAG_ELVEN && player_genus(GENPC_ELVEN)) { baseHit++; } // Give an appropriate 'tohit': // * hand axes and clubs are -5 // * daggers are +1 // * spears are -1 // * rocks are 0 if (wepClass == OBJ_WEAPONS) { switch (wepType) { case WPN_DAGGER: baseHit++; break; case WPN_SPEAR: baseHit--; break; default: baseHit -= 5; break; } } else if (wepClass == OBJ_MISSILES) { switch (wepType) { case MI_DART: baseHit += 2; break; case MI_JAVELIN: baseHit++; break; default: break; } } exHitBonus = you.skills[SK_THROWING] * 2; baseDam = property(item, PWPN_DAMAGE); // Dwarves/orcs with dwarven/orcish weapons. if (get_equip_race(item) == ISFLAG_DWARVEN && player_genus(GENPC_DWARVEN) || get_equip_race(item) == ISFLAG_ORCISH && you.species == SP_HILL_ORC) { baseDam++; } exDamBonus = (10 * (you.skills[SK_THROWING] / 2 + you.strength - 10)) / 12; // Now, exDamBonus is a multiplier. The full multiplier // is applied to base damage, but only a third is applied // to the magical modifier. exDamBonus = (exDamBonus * (3 * baseDam + ammoDamBonus)) / 30; } if (wepClass == OBJ_MISSILES) { // Identify ammo type. set_ident_flags(you.inv[throw_2], ISFLAG_KNOW_TYPE); ammo_ided = true; switch (wepType) { case MI_LARGE_ROCK: if (you.can_throw_large_rocks()) baseHit = 1; break; case MI_DART: // Darts also using throwing skills, now. exHitBonus += skill_bump(SK_THROWING); exDamBonus += you.skills[SK_THROWING] * 3 / 5; // exercise skills exercise(SK_THROWING, 1 + random2avg(3, 2)); break; case MI_JAVELIN: // Javelins use throwing skill. exHitBonus += skill_bump(SK_THROWING); exDamBonus += you.skills[SK_THROWING] * 3 / 5; // Adjust for strength and dex. exDamBonus = str_adjust_thrown_damage(exDamBonus); exHitBonus = dex_adjust_thrown_tohit(exHitBonus); // High dex helps damage a bit, too (aim for weak spots). exDamBonus = stat_adjust(exDamBonus, you.dex, 20, 150, 100); // Javelins train throwing quickly. exercise(SK_THROWING, 1 + coinflip()); break; case MI_THROWING_NET: // Nets use throwing skill. They don't do any damage! baseDam = 0; exDamBonus = 0; ammoDamBonus = 0; // ...but accuracy is important for this one. baseHit = 1; exHitBonus += (skill_bump(SK_THROWING) * 7 / 2); // Adjust for strength and dex. exHitBonus = dex_adjust_thrown_tohit(exHitBonus); // Nets train throwing. exercise(SK_THROWING, 1); break; } } // [dshaligram] The defined base damage applies only when used // for launchers. Hand-thrown stones and darts do only half // base damage. Yet another evil 4.0ism. if (wepClass == OBJ_MISSILES && (wepType == MI_DART || wepType == MI_STONE)) { baseDam = div_rand_round(baseDam, 2); } // exercise skill if (coinflip()) exercise(SK_THROWING, 1); // ID check if (!teleport && !item_ident(you.inv[throw_2], ISFLAG_KNOW_PLUSES) && x_chance_in_y(you.skills[SK_THROWING], 100)) { set_ident_flags( item, ISFLAG_KNOW_PLUSES ); set_ident_flags( you.inv[throw_2], ISFLAG_KNOW_PLUSES ); identify_floor_missiles_matching(item, ISFLAG_KNOW_PLUSES); ammo_ided = true; mprf("You are throwing %s.", you.inv[throw_2].name(DESC_NOCAP_A).c_str()); } } if (pbolt.flavour != BEAM_MISSILE) // [dshaligram] Branded arrows are much stronger. dice_mult = (dice_mult * 150) / 100; // Dexterity bonus, and possible skill increase for silly throwing. if (projected) { if (wepType != MI_LARGE_ROCK && wepType != MI_THROWING_NET) { exHitBonus += you.dex / 2; // slaying bonuses if (wepType != MI_NEEDLE) { slayDam = slaying_bonus(PWPN_DAMAGE, true); slayDam = (slayDam < 0 ? -random2(1 - slayDam) : random2(1 + slayDam)); } exHitBonus += slaying_bonus(PWPN_HIT, true); } } else // LRET_FUMBLED { if (one_chance_in(20)) exercise(SK_THROWING, 1); exHitBonus = you.dex / 4; if (wepClass == OBJ_MISSILES && wepType == MI_NEEDLE) { // Throwing needles is now seriously frowned upon; it's difficult // to grip a fiddly little needle, and not penalising it cheapens // blowguns. exHitBonus -= (30 - you.skills[SK_THROWING]) / 3; baseHit -= (30 - you.skills[SK_THROWING]) / 3; dprf("Needle base hit = %d, exHitBonus = %d", baseHit, exHitBonus); } } // FINALISE tohit and damage if (exHitBonus >= 0) pbolt.hit = baseHit + random2avg(exHitBonus + 1, 2); else pbolt.hit = baseHit - random2avg(0 - (exHitBonus - 1), 2); if (exDamBonus >= 0) pbolt.damage = dice_def(1, baseDam + random2(exDamBonus + 1)); else pbolt.damage = dice_def(1, baseDam - random2(0 - (exDamBonus - 1))); pbolt.damage.size = dice_mult * pbolt.damage.size / 100; pbolt.damage.size += slayDam; // Only add bonuses if we're throwing something sensible. if (projected || wepClass == OBJ_WEAPONS) { pbolt.hit += ammoHitBonus + lnchHitBonus; pbolt.damage.size += ammoDamBonus + lnchDamBonus; } // Add in bonus (only from Portal Projectile for now). if (acc_bonus != DEBUG_COOKIE) pbolt.hit += acc_bonus; scale_dice(pbolt.damage); dprf( "H:%d+%d;a%dl%d. D:%d+%d;a%dl%d -> %d,%dd%d", baseHit, exHitBonus, ammoHitBonus, lnchHitBonus, baseDam, exDamBonus, ammoDamBonus, lnchDamBonus, pbolt.hit, pbolt.damage.num, pbolt.damage.size ); // Create message. mprf( "%s %s%s %s.", teleport ? "Magically, you" : "You", projected ? "" : "awkwardly ", projected == LRET_LAUNCHED ? "shoot" : "throw", ammo_name.c_str() ); // Ensure we're firing a 'missile'-type beam. pbolt.is_beam = false; pbolt.is_tracer = false; // Mark this item as thrown if it's a missile, so that we'll pick it up // when we walk over it. if (wepClass == OBJ_MISSILES || wepClass == OBJ_WEAPONS) item.flags |= ISFLAG_THROWN; bool hit = false; if (teleport) { // Violating encapsulation somewhat...oh well. pbolt.use_target_as_pos = true; pbolt.affect_cell(); pbolt.affect_endpoint(); if (!did_return && acc_bonus != DEBUG_COOKIE) pbolt.drop_object(); } else { if (Tutorial.tutorial_left) Tutorial.tut_throw_counter++; // Dropping item copy, since the launched item might be different. pbolt.drop_item = !did_return; pbolt.fire(); // The item can be destroyed before returning. if (did_return && thrown_object_destroyed(&item, pbolt.target)) did_return = false; } if (bow_brand == SPWPN_CHAOS || ammo_brand == SPMSL_CHAOS) { did_god_conduct(DID_CHAOS, 2 + random2(3), bow_brand == SPWPN_CHAOS || ammo_brand_known); } if (bow_brand == SPWPN_REAPING || ammo_brand == SPMSL_REAPING) { did_god_conduct(DID_NECROMANCY, 2, bow_brand == SPWPN_REAPING || ammo_brand_known); } if (ammo_brand == SPMSL_RAGE) { did_god_conduct(DID_HASTY, 6 + random2(3), ammo_brand_known); } if (did_return) { // Fire beam in reverse. pbolt.setup_retrace(); viewwindow(false); pbolt.fire(); msg::stream << item.name(DESC_CAP_THE) << " returns to your pack!" << std::endl; // Player saw the item return. if (!is_artefact(you.inv[throw_2])) { // Since this only happens for non-artefacts, also mark properties // as known. set_ident_flags(you.inv[throw_2], ISFLAG_KNOW_TYPE | ISFLAG_KNOW_PROPERTIES); } } else { // Should have returned but didn't. if (returning && item_type_known(you.inv[throw_2])) { msg::stream << item.name(DESC_CAP_THE) << " fails to return to your pack!" << std::endl; } dec_inv_item_quantity(throw_2, 1); if (unwielded) canned_msg(MSG_EMPTY_HANDED); } throw_noise(&you, pbolt, thrown); // ...any monster nearby can see that something has been thrown, even // if it didn't make any noise. alert_nearby_monsters(); if (ammo_ided) _merge_ammo_in_inventory(throw_2); you.turn_is_over = true; if (pbolt.special_explosion != NULL) delete pbolt.special_explosion; return (hit); } bool thrown_object_destroyed(item_def *item, const coord_def& where) { ASSERT(item != NULL); int chance = 0; std::string name = item->name(DESC_PLAIN, false, true, false); // Exploding missiles are always destroyed. if (name.find("explod") != std::string::npos) return (true); if (item->base_type == OBJ_MISSILES) { int brand = get_ammo_brand(*item); if (brand == SPMSL_CHAOS || brand == SPMSL_DISPERSAL) return (true); // [dshaligram] Removed influence of Throwing on ammo preservation. // The effect is nigh impossible to perceive. switch (item->sub_type) { case MI_NEEDLE: chance = (brand == SPMSL_CURARE ? 3 : 6); break; case MI_SLING_BULLET: case MI_STONE: case MI_ARROW: case MI_BOLT: chance = 4; break; case MI_DART: chance = 3; break; case MI_JAVELIN: chance = 10; break; case MI_THROWING_NET: // Doesn't get destroyed by throwing. break; case MI_LARGE_ROCK: default: chance = 25; break; } if (brand == SPMSL_STEEL) chance *= 10; if (brand == SPMSL_FLAME) chance /= 2; if (brand == SPMSL_FROST) chance /= 2; if (brand == SPMSL_REAPING) chance /= 4; } // Enchanted projectiles get an extra shot at avoiding // destruction: plus / (1 + plus) chance of survival. bool destroyed = (chance == 0) ? false : (one_chance_in(chance) && one_chance_in(item->plus + 1)); return destroyed; } void jewellery_wear_effects(item_def &item) { item_type_id_state_type ident = ID_TRIED_TYPE; artefact_prop_type fake_rap = ARTP_NUM_PROPERTIES; bool learn_pluses = false; // Randart jewellery shouldn't auto-ID just because the base type // is known. Somehow the player should still be told, preferably // by message. (jpeg) const bool artefact = is_artefact(item); const bool known_pluses = item_ident(item, ISFLAG_KNOW_PLUSES); const bool known_cursed = item_known_cursed(item); const bool known_bad = (item_type_known(item) && item_value(item) <= 2); switch (item.sub_type) { case RING_FIRE: case RING_HUNGER: case RING_ICE: case RING_LIFE_PROTECTION: case RING_POISON_RESISTANCE: case RING_PROTECTION_FROM_COLD: case RING_PROTECTION_FROM_FIRE: case RING_PROTECTION_FROM_MAGIC: case RING_SUSTAIN_ABILITIES: case RING_SUSTENANCE: case RING_SLAYING: case RING_WIZARDRY: case RING_TELEPORT_CONTROL: break; case RING_SEE_INVISIBLE: // We might have to turn autopickup back on again. // TODO: Check all monsters in LOS. If any of them are invisible // (and thus become visible once the ring is worn), the ring // should be autoidentified. if (item_type_known(item)) autotoggle_autopickup(false); break; case RING_PROTECTION: you.redraw_armour_class = true; if (item.plus != 0) { if (!artefact) ident = ID_KNOWN_TYPE; else if (!known_pluses) { mprf("You feel %s.", item.plus > 0 ? "well-protected" : "more vulnerable"); } learn_pluses = true; } break; case RING_INVISIBILITY: if (!you.duration[DUR_INVIS]) { mpr("You become transparent for a moment."); if (artefact) fake_rap = ARTP_INVISIBLE; else ident = ID_KNOWN_TYPE; } break; case RING_EVASION: you.redraw_evasion = true; if (item.plus != 0) { if (!artefact) ident = ID_KNOWN_TYPE; else if (!known_pluses) mprf("You feel %s.", item.plus > 0? "nimbler" : "more awkward"); learn_pluses = true; } break; case RING_STRENGTH: if (item.plus) { modify_stat(STAT_STRENGTH, item.plus, false, item); if (artefact) fake_rap = ARTP_STRENGTH; else ident = ID_KNOWN_TYPE; learn_pluses = true; } break; case RING_DEXTERITY: if (item.plus) { modify_stat(STAT_DEXTERITY, item.plus, false, item); if (artefact) fake_rap = ARTP_DEXTERITY; else ident = ID_KNOWN_TYPE; learn_pluses = true; } break; case RING_INTELLIGENCE: if (item.plus) { modify_stat(STAT_INTELLIGENCE, item.plus, false, item); if (artefact) fake_rap = ARTP_INTELLIGENCE; else ident = ID_KNOWN_TYPE; learn_pluses = true; } break; case RING_MAGICAL_POWER: mpr("You feel your mana capacity increase."); calc_mp(); if (artefact) fake_rap = ARTP_MAGICAL_POWER; else ident = ID_KNOWN_TYPE; break; case RING_LEVITATION: if (!scan_artefacts(ARTP_LEVITATE)) { if (you.airborne()) mpr("You feel vaguely more buoyant than before."); else mpr("You feel buoyant."); if (artefact) fake_rap = ARTP_LEVITATE; else ident = ID_KNOWN_TYPE; } break; case RING_TELEPORTATION: mpr("You feel slightly jumpy."); if (artefact) fake_rap = ARTP_CAUSE_TELEPORTATION; else ident = ID_KNOWN_TYPE; break; case AMU_RAGE: if (!scan_artefacts(ARTP_BERSERK)) { mpr("You feel a brief urge to hack something to bits."); if (artefact) fake_rap = ARTP_BERSERK; else ident = ID_KNOWN_TYPE; } break; case AMU_FAITH: if (you.religion != GOD_NO_GOD) { mpr("You feel a surge of divine interest.", MSGCH_GOD); ident = ID_KNOWN_TYPE; } break; case AMU_THE_GOURMAND: // What's this supposed to achieve? (jpeg) you.duration[DUR_GOURMAND] = 0; break; case AMU_CONTROLLED_FLIGHT: if (you.duration[DUR_LEVITATION] && !extrinsic_amulet_effect(AMU_CONTROLLED_FLIGHT)) { ident = ID_KNOWN_TYPE; } break; case AMU_GUARDIAN_SPIRIT: if (player_spirit_shield()<2) { set_mp(0, false); mpr("You feel your power drawn to a protective spirit."); if (you.species == SP_DEEP_DWARF) mpr("Now linked to your health, your magic stops regenerating."); ident = ID_KNOWN_TYPE; } break; case RING_REGENERATION: // To be exact, bloodless vampires should get the id only after they // drink anything. Not worth complicating the code, IMHO. [1KB] if (player_mutation_level(MUT_SLOW_HEALING) < 3) ident = ID_KNOWN_TYPE; break; } // Artefacts have completely different appearance than base types // so we don't allow them to make the base types known. if (artefact) { bool show_msgs = true; use_artefact(item, &show_msgs); if (learn_pluses && (item.plus != 0 || item.plus2 != 0)) set_ident_flags(item, ISFLAG_KNOW_PLUSES); if (fake_rap != ARTP_NUM_PROPERTIES) artefact_wpn_learn_prop(item, fake_rap); if (!item.props.exists("jewellery_tried") || !item.props["jewellery_tried"].get_bool()) { item.props["jewellery_tried"].get_bool() = true; } } else { set_ident_type(item, ident); if (ident == ID_KNOWN_TYPE) set_ident_flags(item, ISFLAG_EQ_JEWELLERY_MASK); } if (item.cursed()) { mprf("Oops, that %s feels deathly cold.", jewellery_is_amulet(item)? "amulet" : "ring"); learned_something_new(TUT_YOU_CURSED); int amusement = 32; if (!known_cursed && !known_bad) { amusement *= 2; god_type god; if (origin_is_god_gift(item, &god) && god == GOD_XOM) amusement *= 2; } xom_is_stimulated(amusement); } // Cursed or not, we know that since we've put the ring on. set_ident_flags(item, ISFLAG_KNOW_CURSE); mpr(item.name(DESC_INVENTORY_EQUIP).c_str()); } static int _prompt_ring_to_remove(int new_ring) { const item_def *left = you.slot_item(EQ_LEFT_RING); const item_def *right = you.slot_item(EQ_RIGHT_RING); if (left->cursed() && right->cursed()) { mprf("You're already wearing two cursed rings!"); return (-1); } mesclr(); mprf("Wearing %s.", you.inv[new_ring].name(DESC_NOCAP_A).c_str()); const char lslot = index_to_letter(left->link); const char rslot = index_to_letter(right->link); mprf(MSGCH_PROMPT, "You're wearing two rings. Remove which one? (%c/%c/</Esc)", lslot, rslot); mprf(" < or %s", left->name(DESC_INVENTORY).c_str()); mprf(" > or %s", right->name(DESC_INVENTORY).c_str()); // Deactivate choice from tile inventory. // FIXME: We need to be able to get the choice (item letter) // *without* the choice taking action by itself! mouse_control mc(MOUSE_MODE_MORE); int c; do c = getch(); while (c != lslot && c != rslot && c != '<' && c != '>' && c != ESCAPE && c != ' '); mesclr(); if (c == ESCAPE || c == ' ') return (-1); const int eqslot = (c == lslot || c == '<') ? EQ_LEFT_RING : EQ_RIGHT_RING; if (!check_warning_inscriptions(you.inv[you.equip[eqslot]], OPER_REMOVE)) return (-1); return (you.equip[eqslot]); } // Checks whether a to-be-worn or to-be-removed item affects // character stats and whether wearing/removing it could be fatal. // If so, warns the player, or just returns false if quiet is true. bool safe_to_remove_or_wear(const item_def &item, bool remove, bool quiet) { int prop_str = 0; int prop_dex = 0; int prop_int = 0; bool prop_lev = false; bool fatal_liquid = false; dungeon_feature_type gridhere = grd(you.pos()); // Don't warn when putting on an unknown item. if (item.base_type == OBJ_JEWELLERY && item_ident(item, ISFLAG_KNOW_PLUSES)) { switch (item.sub_type) { case RING_STRENGTH: if (item.plus != 0) prop_str = item.plus; break; case RING_DEXTERITY: if (item.plus != 0) prop_dex = item.plus; break; case RING_INTELLIGENCE: if (item.plus != 0) prop_int = item.plus; break; case RING_LEVITATION: prop_lev = true; break; default: break; } } if (item.base_type == OBJ_ARMOUR && item_type_known(item)) { switch (item.special) { case SPARM_STRENGTH: prop_str = 3; break; case SPARM_INTELLIGENCE: prop_int = 3; break; case SPARM_DEXTERITY: prop_dex = 3; break; case SPARM_LEVITATION: prop_lev = true; break; default: break; } } if (is_artefact(item)) { prop_str += artefact_known_wpn_property(item, ARTP_STRENGTH); prop_int += artefact_known_wpn_property(item, ARTP_INTELLIGENCE); prop_dex += artefact_known_wpn_property(item, ARTP_DEXTERITY); prop_lev = prop_lev || artefact_known_wpn_property(item, ARTP_LEVITATE); if (!remove && artefact_known_wpn_property(item, ARTP_EYESIGHT)) { // We might have to turn autopickup back on again. // This is not optimal, in that it could also happen if we do // not know the property (in which case it should become known). autotoggle_autopickup(false); } } if (you.species == SP_MERFOLK && feat_is_water(gridhere)) { // Falling into water could lose boots and cause fatal stat loss. // There is a complication here. Suppose you are taking off a // randart with +Lev and a stat modification, while wearing boots // that modify the same stat. We have to consider losing both at // the same time. // This only handles removal, which is OK, because levitating is // never fatal. you.strength -= prop_str; you.intel -= prop_int; you.dex -= prop_dex; bool safe = merfolk_change_is_safe(); you.strength += prop_str; you.intel += prop_int; you.dex += prop_dex; if (!safe) fatal_liquid = true; } if (gridhere == DNGN_LAVA || (gridhere == DNGN_DEEP_WATER && !beogh_water_walk())) { // We'd fall into water! See if we would drown. coord_def empty; if (you.attribute[ATTR_TRANSFORMATION] == TRAN_STATUE || (carrying_capacity() / 2) <= you.burden || !empty_surrounds(you.pos(), DNGN_FLOOR, 1, false, empty)) fatal_liquid = true; if (gridhere == DNGN_LAVA) { int res = player_res_fire(false); if (res <= 0 || (110 / res) >= you.hp) fatal_liquid = true; } } if (you.permanent_levitation() && (item.base_type != OBJ_ARMOUR || item.sub_type != ARM_BOOTS)) { fatal_liquid = false; // we won't fall! } if (remove) { if (prop_str >= you.strength || prop_int >= you.intel || prop_dex >= you.dex || prop_lev && fatal_liquid) { if (!quiet) { mprf(MSGCH_WARN, "%s this item would be fatal, so you refuse " "to do that.", (item.base_type == OBJ_WEAPONS ? "Unwielding" : "Removing")); } return (false); } } else // put on { if (-prop_str >= you.strength || -prop_int >= you.intel || -prop_dex >= you.dex) { if (!quiet) { mprf(MSGCH_WARN, "%s this item would be fatal, so you refuse " "to do that.", (item.base_type == OBJ_WEAPONS ? "Wielding" : "Wearing")); } return (false); } } return (true); } // Assumptions: // you.inv[ring_slot] is a valid ring. // EQ_LEFT_RING and EQ_RIGHT_RING are both occupied, and ring_slot is not // one of the worn rings. // // Does not do amulets. static bool _swap_rings(int ring_slot) { const item_def* lring = you.slot_item(EQ_LEFT_RING); const item_def* rring = you.slot_item(EQ_RIGHT_RING); if (lring->cursed() && rring->cursed()) { mprf("You're already wearing two cursed rings!"); return (false); } int unwanted; // Don't prompt if both rings are of the same type. if (lring->sub_type == rring->sub_type && lring->plus == rring->plus && lring->plus2 == rring->plus2 && !is_artefact(*lring) && !is_artefact(*rring)) { if (lring->cursed()) unwanted = you.equip[EQ_RIGHT_RING]; else unwanted = you.equip[EQ_LEFT_RING]; } else { // Ask the player which existing ring is persona non grata. unwanted = _prompt_ring_to_remove(ring_slot); } if (unwanted == -1) { canned_msg(MSG_OK); return (false); } if (!remove_ring(unwanted, false)) return (false); // Check that the new ring won't kill us. if (!safe_to_remove_or_wear(you.inv[ring_slot], false)) return (false); // Put on the new ring. start_delay(DELAY_JEWELLERY_ON, 1, ring_slot); return (true); } bool puton_item(int item_slot) { item_def& item = you.inv[item_slot]; if (item_slot == you.equip[EQ_LEFT_RING] || item_slot == you.equip[EQ_RIGHT_RING] || item_slot == you.equip[EQ_AMULET]) { // "Putting on" an equipped item means taking it off. if (Options.equip_unequip) return (!remove_ring(item_slot)); else { mpr("You're already wearing that object!"); return (false); } } if (item_slot == you.equip[EQ_WEAPON]) { mpr("You are wielding that object."); return (false); } if (item.base_type != OBJ_JEWELLERY) { mpr("You can only put on jewellery."); return (false); } const bool lring = (you.slot_item(EQ_LEFT_RING) != NULL); const bool rring = (you.slot_item(EQ_RIGHT_RING) != NULL); const bool is_amulet = jewellery_is_amulet(item); if (!is_amulet) // i.e. it's a ring { const item_def* gloves = you.slot_item(EQ_GLOVES); if (gloves && gloves->cursed()) { mpr("You can't take your gloves off to put on a ring!"); return (false); } if (lring && rring) return _swap_rings(item_slot); } else if (item_def* amulet = you.slot_item(EQ_AMULET)) { // Remove the previous one. if (!check_warning_inscriptions(*amulet, OPER_REMOVE) || !remove_ring(you.equip[EQ_AMULET], true)) { return (false); } // Check that the new amulet won't kill us. if (!safe_to_remove_or_wear(item, false)) return (false); // Put on the new amulet. start_delay(DELAY_JEWELLERY_ON, 1, item_slot); // Assume it's going to succeed. return (true); } // Check that it won't kill us. if (!safe_to_remove_or_wear(item, false)) return (false); equipment_type hand_used; if (is_amulet) { hand_used = EQ_AMULET; } else { // First ring always goes on left hand. hand_used = EQ_LEFT_RING; // ... unless we're already wearing a ring on the left hand. if (lring && !rring) hand_used = EQ_RIGHT_RING; } const unsigned int old_talents = your_talents(false).size(); // Actually equip the item. you.equip[hand_used] = item_slot; // And calculate the effects. jewellery_wear_effects(item); if (Tutorial.tutorial_left && your_talents(false).size() > old_talents) learned_something_new(TUT_NEW_ABILITY_ITEM); // Putting on jewellery is as fast as wielding weapons. you.time_taken /= 2; you.turn_is_over = true; return (true); } bool puton_ring(int slot) { if (player_in_bat_form()) { mpr("You can't put on anything in your present form."); return (false); } int item_slot; if (inv_count() < 1) { canned_msg(MSG_NOTHING_CARRIED); return (false); } if (you.berserk()) { canned_msg(MSG_TOO_BERSERK); return (false); } if (slot != -1) item_slot = slot; else { item_slot = prompt_invent_item( "Put on which piece of jewellery?", MT_INVLIST, OBJ_JEWELLERY, true, true, true, 0, -1, NULL, OPER_PUTON ); } if (prompt_failed(item_slot)) return (false); return puton_item(item_slot); } void remove_amulet_of_faith(item_def &item) { if (you.religion != GOD_NO_GOD && you.religion != GOD_XOM) { simple_god_message(" seems less interested in you."); const int piety_loss = div_rand_round(you.piety, 3); // Piety penalty for removing the Amulet of Faith. if (you.piety - piety_loss > 10) { mprf(MSGCH_GOD, "%s leaches power out of you as you remove it.", item.name(DESC_CAP_YOUR).c_str()); dprf("%s: piety leach: %d", item.name(DESC_PLAIN).c_str(), piety_loss); lose_piety(piety_loss); } } } void jewellery_remove_effects(item_def &item, bool mesg) { // The ring/amulet must already be removed from you.equip at this point. // Turn off show_uncursed before getting the item name, because this item // was just removed, and the player knows it's uncursed. const bool old_showuncursed = Options.show_uncursed; Options.show_uncursed = false; if (mesg) mprf("You remove %s.", item.name(DESC_NOCAP_YOUR).c_str()); Options.show_uncursed = old_showuncursed; switch (item.sub_type) { case RING_FIRE: case RING_HUNGER: case RING_ICE: case RING_LIFE_PROTECTION: case RING_POISON_RESISTANCE: case RING_PROTECTION_FROM_COLD: case RING_PROTECTION_FROM_FIRE: case RING_PROTECTION_FROM_MAGIC: case RING_REGENERATION: case RING_SEE_INVISIBLE: case RING_SLAYING: case RING_SUSTAIN_ABILITIES: case RING_SUSTENANCE: case RING_TELEPORTATION: case RING_WIZARDRY: case RING_TELEPORT_CONTROL: break; case RING_PROTECTION: you.redraw_armour_class = true; break; case RING_EVASION: you.redraw_evasion = true; break; case RING_STRENGTH: modify_stat(STAT_STRENGTH, -item.plus, false, item, true); break; case RING_DEXTERITY: modify_stat(STAT_DEXTERITY, -item.plus, false, item, true); break; case RING_INTELLIGENCE: modify_stat(STAT_INTELLIGENCE, -item.plus, false, item, true); break; case RING_LEVITATION: if (you.duration[DUR_LEVITATION] && !you.permanent_levitation()) you.duration[DUR_LEVITATION] = 1; break; case RING_INVISIBILITY: if (you.duration[DUR_INVIS]) you.duration[DUR_INVIS] = 1; break; case RING_MAGICAL_POWER: mpr("You feel your mana capacity decrease."); // dec_max_mp(9); break; case AMU_THE_GOURMAND: you.duration[DUR_GOURMAND] = 0; break; case AMU_FAITH: remove_amulet_of_faith(item); break; case AMU_GUARDIAN_SPIRIT: if (you.species == SP_DEEP_DWARF) mpr("Your magic begins regenerating once more."); } if (is_artefact(item)) unuse_artefact(item, &mesg); // Must occur after ring is removed. -- bwr calc_mp(); } bool remove_ring(int slot, bool announce) { if (player_in_bat_form()) { mpr("You can't wear or remove anything in your present form."); return (false); } equipment_type hand_used = EQ_NONE; int ring_wear_2; if (you.equip[EQ_LEFT_RING] == -1 && you.equip[EQ_RIGHT_RING] == -1 && you.equip[EQ_AMULET] == -1) { mpr("You aren't wearing any rings or amulets."); return (false); } if (you.berserk()) { canned_msg(MSG_TOO_BERSERK); return (false); } if (you.equip[EQ_GLOVES] != -1 && you.inv[you.equip[EQ_GLOVES]] .cursed() && you.equip[EQ_AMULET] == -1) { mpr("You can't take your gloves off to remove any rings!"); return (false); } if (you.equip[EQ_LEFT_RING] != -1 && you.equip[EQ_RIGHT_RING] == -1 && you.equip[EQ_AMULET] == -1) { hand_used = EQ_LEFT_RING; } if (you.equip[EQ_LEFT_RING] == -1 && you.equip[EQ_RIGHT_RING] != -1 && you.equip[EQ_AMULET] == -1) { hand_used = EQ_RIGHT_RING; } if (you.equip[EQ_LEFT_RING] == -1 && you.equip[EQ_RIGHT_RING] == -1 && you.equip[EQ_AMULET] != -1) { hand_used = EQ_AMULET; } if (hand_used == EQ_NONE) { const int equipn = (slot == -1)? prompt_invent_item("Remove which piece of jewellery?", MT_INVLIST, OBJ_JEWELLERY, true, true, true, 0, -1, NULL, OPER_REMOVE) : slot; if (prompt_failed(equipn)) return (false); if (you.inv[equipn].base_type != OBJ_JEWELLERY) { mpr("That isn't a piece of jewellery."); return (false); } if (you.equip[EQ_LEFT_RING] == equipn) hand_used = EQ_LEFT_RING; else if (you.equip[EQ_RIGHT_RING] == equipn) hand_used = EQ_RIGHT_RING; else if (you.equip[EQ_AMULET] == equipn) hand_used = EQ_AMULET; else { mpr("You aren't wearing that."); return (false); } } if (!check_warning_inscriptions(you.inv[you.equip[hand_used]], OPER_REMOVE)) { canned_msg(MSG_OK); return (false); } if (you.equip[EQ_GLOVES] != -1 && you.inv[you.equip[EQ_GLOVES]] .cursed() && (hand_used == EQ_LEFT_RING || hand_used == EQ_RIGHT_RING)) { mpr("You can't take your gloves off to remove any rings!"); return (false); } if (you.equip[hand_used] == -1) { mpr("I don't think you really meant that."); return (false); } if (you.inv[you.equip[hand_used]] .cursed()) { if (announce) { mprf("%s is stuck to you!", you.inv[you.equip[hand_used]].name(DESC_CAP_YOUR).c_str()); } else mpr("It's stuck to you!"); set_ident_flags(you.inv[you.equip[hand_used]], ISFLAG_KNOW_CURSE); return (false); } ring_wear_2 = you.equip[hand_used]; // Remove the ring. if (!safe_to_remove_or_wear(you.inv[ring_wear_2], true)) return (false); you.equip[hand_used] = -1; jewellery_remove_effects(you.inv[ring_wear_2]); you.time_taken /= 2; you.turn_is_over = true; return (true); } int _wand_range(zap_type ztype) { // FIXME: Eventually we should have sensible values here. return (8); } int _max_wand_range() { return (8); } static bool _dont_use_invis() { if (!you.backlit()) return (false); if (you.haloed()) { mpr("You can't turn invisible."); return (true); } else if (get_contamination_level() > 0 && !yesno("Invisibility will do you no good right now; " "use anyways?", false, 'n')) { return (true); } return (false); } void zap_wand(int slot) { if (player_in_bat_form()) { canned_msg(MSG_PRESENT_FORM); return; } bolt beam; dist zap_wand; int item_slot; // Unless the character knows the type of the wand, the targetting // system will default to enemies. -- [ds] targ_mode_type targ_mode = TARG_HOSTILE; beam.obvious_effect = false; if (inv_count() < 1) { canned_msg(MSG_NOTHING_CARRIED); return; } if (you.berserk()) { canned_msg(MSG_TOO_BERSERK); return; } if (slot != -1) item_slot = slot; else { item_slot = prompt_invent_item("Zap which item?", MT_INVLIST, OBJ_WANDS, true, true, true, 0, -1, NULL, OPER_ZAP); } if (prompt_failed(item_slot)) return; item_def& wand = you.inv[item_slot]; if (wand.base_type != OBJ_WANDS) { canned_msg(MSG_NOTHING_HAPPENS); return; } // If you happen to be wielding the wand, its display might change. if (you.equip[EQ_WEAPON] == item_slot) you.wield_change = true; const zap_type type_zapped = wand.zap(); bool has_charges = true; if (wand.plus < 1) { if (wand.plus2 == ZAPCOUNT_EMPTY) { mpr("This wand has no charges."); return; } has_charges = false; } const bool alreadyknown = item_type_known(wand); const bool alreadytried = item_type_tried(wand); bool invis_enemy = false; const bool dangerous = player_in_a_dangerous_place(&invis_enemy); if (!alreadyknown) beam.effect_known = false; else { switch (wand.sub_type) { case WAND_DIGGING: case WAND_TELEPORTATION: targ_mode = TARG_ANY; break; case WAND_HEALING: if (you.religion == GOD_ELYVILON) { targ_mode = TARG_ANY; break; } // else intentional fall-through case WAND_HASTING: case WAND_INVISIBILITY: targ_mode = TARG_FRIEND; break; default: targ_mode = TARG_HOSTILE; break; } } int tracer_range = (alreadyknown && wand.sub_type != WAND_RANDOM_EFFECTS) ? _wand_range(type_zapped) : _max_wand_range(); direction(zap_wand, DIR_NONE, targ_mode, tracer_range); if (!zap_wand.isValid) { if (zap_wand.isCancel) canned_msg(MSG_OK); return; } if (alreadyknown && zap_wand.target == you.pos()) { if (wand.sub_type == WAND_TELEPORTATION && scan_artefacts(ARTP_PREVENT_TELEPORTATION, false)) { mpr("You cannot teleport right now."); return; } else if (wand.sub_type == WAND_INVISIBILITY && _dont_use_invis()) { return; } } if (!has_charges) { canned_msg(MSG_NOTHING_HAPPENS); // It's an empty wand; inscribe it that way. wand.plus2 = ZAPCOUNT_EMPTY; you.turn_is_over = true; return; } if (you.confused()) zap_wand.target = you.pos() + coord_def(random2(13)-6, random2(13)-6); if (wand.sub_type == WAND_RANDOM_EFFECTS) beam.effect_known = false; beam.source = you.pos(); beam.attitude = ATT_FRIENDLY; beam.set_target(zap_wand); bool aimed_at_self = (beam.target == you.pos()); // Check whether we may hit friends, use "safe" values for random effects // and unknown wands (highest possible range, and unresistable beam // flavour). Don't use the tracer if firing at self. if (!aimed_at_self) { beam.range = tracer_range; if (!player_tracer(beam.effect_known ? type_zapped : ZAP_DEBUGGING_RAY, 2 * (you.skills[SK_EVOCATIONS] - 1), beam, beam.effect_known ? 0 : 17)) { return; } } // Zapping the wand isn't risky if you aim it away from all monsters // and yourself, unless there's a nearby invisible enemy and you're // trying to hit it at random. const bool risky = dangerous && (beam.friend_info.count || beam.foe_info.count || invis_enemy || aimed_at_self); if (risky && alreadyknown && wand.sub_type == WAND_RANDOM_EFFECTS) { // Xom loves it when you use a Wand of Random Effects and // there is a dangerous monster nearby... xom_is_stimulated(255); } // Reset range. beam.range = _wand_range(type_zapped); #ifdef WIZARD if (you.wizard) { std::string str = wand.inscription; int wiz_range = strip_number_tag(str, "range:"); if (wiz_range != TAG_UNFOUND) beam.range = wiz_range; } #endif // zapping() updates beam. zapping( type_zapped, 30 + roll_dice(2, you.skills[SK_EVOCATIONS]), beam ); // Take off a charge. wand.plus--; // Zap counts count from the last recharge. if (wand.plus2 == ZAPCOUNT_MAX_CHARGED || wand.plus2 == ZAPCOUNT_RECHARGED) wand.plus2 = 0; // Increment zap count. if (wand.plus2 >= 0) wand.plus2++; // Identify if necessary. if (!alreadyknown && (beam.obvious_effect || type_zapped == ZAP_FIREBALL)) { set_ident_type(wand, ID_KNOWN_TYPE); if (wand.sub_type == WAND_RANDOM_EFFECTS) mpr("You feel that this wand is rather unreliable."); mpr(wand.name(DESC_INVENTORY_EQUIP).c_str()); } else set_ident_type(wand, ID_TRIED_TYPE); if (item_type_known(wand) && (item_ident(wand, ISFLAG_KNOW_PLUSES) || you.skills[SK_EVOCATIONS] > 5 + random2(15))) { if (!item_ident(wand, ISFLAG_KNOW_PLUSES)) { mpr("Your skill with magical items lets you calculate " "the power of this device..."); } mprf("This wand has %d charge%s left.", wand.plus, wand.plus == 1 ? "" : "s"); set_ident_flags(wand, ISFLAG_KNOW_PLUSES); } exercise(SK_EVOCATIONS, 1); alert_nearby_monsters(); if (!alreadyknown && !alreadytried && risky) { // Xom loves it when you use an unknown wand and there is a // dangerous monster nearby... xom_is_stimulated(255); } you.turn_is_over = true; } void prompt_inscribe_item() { if (inv_count() < 1) { mpr("You don't have anything to inscribe."); return; } int item_slot = prompt_invent_item("Inscribe which item?", MT_INVLIST, OSEL_ANY); if (prompt_failed(item_slot)) return; inscribe_item(you.inv[item_slot], true); } void drink(int slot) { if (you.is_undead == US_UNDEAD) { mpr("You can't drink."); return; } if (slot == -1) { const dungeon_feature_type feat = grd(you.pos()); if (feat >= DNGN_FOUNTAIN_BLUE && feat <= DNGN_FOUNTAIN_BLOOD) if (_drink_fountain()) return; } if (inv_count() == 0) { canned_msg(MSG_NOTHING_CARRIED); return; } if (you.berserk()) { canned_msg(MSG_TOO_BERSERK); return; } if (player_in_bat_form()) { canned_msg(MSG_PRESENT_FORM); return; } if (slot == -1) { slot = prompt_invent_item("Drink which item?", MT_INVLIST, OBJ_POTIONS, true, true, true, 0, -1, NULL, OPER_QUAFF); if (prompt_failed(slot)) return; } item_def& potion = you.inv[slot]; if (potion.base_type != OBJ_POTIONS) { mpr("You can't drink that!"); return; } const bool alreadyknown = item_type_known(potion); if (alreadyknown && you.hunger_state == HS_ENGORGED && (is_blood_potion(potion) || potion.sub_type == POT_PORRIDGE)) { mpr("You are much too full right now."); return; } if (alreadyknown && potion.sub_type == POT_INVISIBILITY && _dont_use_invis()) { return; } if (alreadyknown && potion.sub_type == POT_BERSERK_RAGE && !berserk_check_wielded_weapon()) { return; } // The "> 1" part is to reduce the amount of times that Xom is // stimulated when you are a low-level 1 trying your first unknown // potions on monsters. const bool dangerous = (player_in_a_dangerous_place() && you.experience_level > 1); // Identify item and type. if (potion_effect(static_cast(potion.sub_type), 40, true, alreadyknown)) { set_ident_flags(potion, ISFLAG_IDENT_MASK); set_ident_type(potion, ID_KNOWN_TYPE); } else { set_ident_type(potion, ID_TRIED_TYPE); } if (!alreadyknown && dangerous) { // Xom loves it when you drink an unknown potion and there is // a dangerous monster nearby... xom_is_stimulated(255); } if (is_blood_potion(potion)) { // Always drink oldest potion. remove_oldest_blood_potion(potion); } dec_inv_item_quantity(slot, 1); you.turn_is_over = true; if (you.species != SP_VAMPIRE) lessen_hunger(40, true); } bool _drink_fountain() { const dungeon_feature_type feat = grd(you.pos()); if (feat < DNGN_FOUNTAIN_BLUE || feat > DNGN_FOUNTAIN_BLOOD) return (false); if (you.flight_mode() == FL_LEVITATE) { mpr("You're floating high above the fountain."); return (false); } if (you.berserk()) { canned_msg(MSG_TOO_BERSERK); return (true); } potion_type fountain_effect = POT_WATER; if (feat == DNGN_FOUNTAIN_BLUE) { if (!yesno("Drink from the fountain?", true, 'n')) return (false); mpr("You drink the pure, clear water."); } else if (feat == DNGN_FOUNTAIN_BLOOD) { if (!yesno("Drink from the fountain of blood?", true, 'n')) return (false); mpr("You drink the blood."); fountain_effect = POT_BLOOD; } else { if (!yesno("Drink from the sparkling fountain?", true, 'n')) return (false); mpr("You drink the sparkling water."); fountain_effect = static_cast( random_choose_weighted(467, POT_WATER, 48, POT_DECAY, 40, POT_MUTATION, 40, POT_HEALING, 40, POT_HEAL_WOUNDS, 40, POT_SPEED, 40, POT_MIGHT, 40, POT_AGILITY, 40, POT_BRILLIANCE, 32, POT_DEGENERATION, 27, POT_LEVITATION, 27, POT_POISON, 27, POT_SLOWING, 27, POT_PARALYSIS, 27, POT_CONFUSION, 27, POT_INVISIBILITY, 20, POT_MAGIC, 20, POT_RESTORE_ABILITIES, 20, POT_RESISTANCE, 20, POT_STRONG_POISON, 20, POT_BERSERK_RAGE, 4, POT_GAIN_STRENGTH, 4, POT_GAIN_INTELLIGENCE, 4, POT_GAIN_DEXTERITY, 0)); } if (fountain_effect != POT_WATER && fountain_effect != POT_BLOOD) xom_is_stimulated(64); // Good gods do not punish for bad random effects. However, they do // punish drinking from a fountain of blood. potion_effect(fountain_effect, 100, true, feat != DNGN_FOUNTAIN_SPARKLING); bool gone_dry = false; if (feat == DNGN_FOUNTAIN_BLUE) { if (one_chance_in(20)) gone_dry = true; } else if (feat == DNGN_FOUNTAIN_BLOOD) { // High chance of drying up, to prevent abuse. if (one_chance_in(3)) gone_dry = true; } else // sparkling fountain { if (one_chance_in(10)) gone_dry = true; else if (random2(50) > 40) { // Turn fountain into a normal fountain without any message // but the glyph colour gives it away (lightblue vs. blue). grd(you.pos()) = DNGN_FOUNTAIN_BLUE; set_terrain_changed(you.pos()); } } if (gone_dry) { mpr("The fountain dries up!"); grd(you.pos()) = static_cast(feat + DNGN_DRY_FOUNTAIN_BLUE - DNGN_FOUNTAIN_BLUE); set_terrain_changed(you.pos()); crawl_state.cancel_cmd_repeat(); } you.turn_is_over = true; return (true); } // Returns true if a message has already been printed (which will identify // the scroll.) static bool _vorpalise_weapon() { if (!you.weapon()) return (false); // Check if you're wielding a brandable weapon. item_def& wpn = *you.weapon(); if (wpn.base_type != OBJ_WEAPONS || wpn.sub_type == WPN_BLOWGUN || is_artefact(wpn)) { return (false); } you.wield_change = true; // If there's no brand, make it vorpal. if (get_weapon_brand(wpn) == SPWPN_NORMAL) { alert_nearby_monsters(); mprf("%s emits a brilliant flash of light!", wpn.name(DESC_CAP_YOUR).c_str()); set_item_ego_type(wpn, OBJ_WEAPONS, SPWPN_VORPAL); return (true); } // If there's a permanent brand, fail. if (you.duration[DUR_WEAPON_BRAND] == 0) return (false); // There's a temporary brand, attempt to make it permanent. const std::string itname = wpn.name(DESC_CAP_YOUR); bool success = true; bool msg = true; switch (get_weapon_brand(wpn)) { case SPWPN_VORPAL: if (get_vorpal_type(wpn) != DVORP_CRUSHING) mprf("%s's sharpness seems more permanent.", itname.c_str()); else mprf("%s's heaviness feels very stable.", itname.c_str()); break; case SPWPN_FLAME: case SPWPN_FLAMING: mprf("%s is engulfed in an explosion of flames!", itname.c_str()); immolation(10, IMMOLATION_SPELL, you.pos(), true, &you); break; case SPWPN_FROST: case SPWPN_FREEZING: if (get_weapon_brand(wpn) == SPWPN_FROST) mprf("%s is covered with a thick layer of frost!", itname.c_str()); else mprf("%s glows brilliantly blue for a moment.", itname.c_str()); cast_refrigeration(60); break; case SPWPN_DRAINING: mprf("%s thirsts for the lives of mortals!", itname.c_str()); drain_exp(); break; case SPWPN_VENOM: mprf("%s seems more permanently poisoned.", itname.c_str()); cast_toxic_radiance(); break; case SPWPN_PAIN: // Can't fix pain brand (balance)...you just get tormented. mprf("%s shrieks out in agony!", itname.c_str()); torment_monsters(you.pos(), 0, TORMENT_GENERIC); success = false; // This is only naughty if you know you're doing it. // XXX: assumes this can only happen from Vorpalise Weapon scroll. did_god_conduct(DID_NECROMANCY, 10, get_ident_type(OBJ_SCROLLS, SCR_VORPALISE_WEAPON) == ID_KNOWN_TYPE); break; case SPWPN_DISTORTION: // [dshaligram] Attempting to fix a distortion brand gets you a free // distortion effect, and no permabranding. Sorry, them's the breaks. mprf("%s twongs alarmingly.", itname.c_str()); // from unwield_item MiscastEffect(&you, NON_MONSTER, SPTYP_TRANSLOCATION, 9, 90, "distortion affixation"); success = false; break; default: success = false; msg = false; break; } if (success) you.duration[DUR_WEAPON_BRAND] = 0; return (msg); } bool enchant_weapon(enchant_stat_type which_stat, bool quiet, item_def &wpn) { bool to_hit = (which_stat == ENCHANT_TO_HIT); // Cannot be enchanted nor uncursed. if (!is_enchantable_weapon(wpn, true, to_hit)) { if (!quiet) canned_msg( MSG_NOTHING_HAPPENS ); return (false); } const bool is_cursed = wpn.cursed(); // Missiles only have one stat. if (wpn.base_type == OBJ_MISSILES) { which_stat = ENCHANT_TO_HIT; to_hit = true; } int enchant_level = (to_hit ? wpn.plus : wpn.plus2); // Even if not affected, it may be uncursed. if (!is_enchantable_weapon(wpn, false, to_hit) || enchant_level >= 4 && x_chance_in_y(enchant_level, MAX_WPN_ENCHANT)) { if (is_cursed) { if (!quiet) { mprf("%s glows silver for a moment.", wpn.name(DESC_CAP_YOUR).c_str()); } do_uncurse_item(wpn); return (true); } else { if (!quiet) canned_msg(MSG_NOTHING_HAPPENS); // Xom thinks it's funny if enchantment is possible but fails. if (is_enchantable_weapon(wpn, false, to_hit)) xom_is_stimulated(32); return (false); } } // Get item name now before changing enchantment. std::string iname = wpn.name(DESC_CAP_YOUR); if (wpn.base_type == OBJ_WEAPONS) { if (to_hit) { if (!quiet) mprf("%s glows green for a moment.", iname.c_str()); wpn.plus++; } else { if (!quiet) mprf("%s glows red for a moment.", iname.c_str()); wpn.plus2++; } } else if (wpn.base_type == OBJ_MISSILES) { if (!quiet) { mprf("%s glow%s red for a moment.", iname.c_str(), wpn.quantity > 1 ? "" : "s"); } wpn.plus++; } if (is_cursed) do_uncurse_item(wpn); return (true); } static bool _handle_enchant_weapon(enchant_stat_type which_stat, bool quiet, int item_slot) { if (item_slot == -1) item_slot = you.equip[ EQ_WEAPON ]; if (item_slot == -1) { canned_msg(MSG_NOTHING_HAPPENS); return (false); } item_def& wpn(you.inv[item_slot]); bool result = enchant_weapon(which_stat, quiet, wpn); you.wield_change = true; return result; } bool enchant_armour(int &ac_change, bool quiet, item_def &arm) { ac_change = 0; // Cannot be enchanted nor uncursed. if (!is_enchantable_armour(arm, true)) { if (!quiet) canned_msg( MSG_NOTHING_HAPPENS ); return (false); } const bool is_cursed = arm.cursed(); // Turn hides into mails where applicable. // NOTE: It is assumed that armour which changes in this way does // not change into a form of armour with a different evasion modifier. if (armour_is_hide(arm, false)) { if (!quiet) { mprf("%s glows purple and changes!", arm.name(DESC_CAP_YOUR).c_str()); } ac_change = property(arm, PARM_AC); hide2armour(arm); ac_change = property(arm, PARM_AC) - ac_change; if (is_cursed) do_uncurse_item(arm); // No additional enchantment. return (true); } // Even if not affected, it may be uncursed. if (!is_enchantable_armour(arm, false) || arm.plus > MAX_SEC_ENCHANT && x_chance_in_y(arm.plus, MAX_ARM_ENCHANT)) { if (is_cursed) { if (!quiet) { mprf("%s glows silver for a moment.", arm.name(DESC_CAP_YOUR).c_str()); } do_uncurse_item(arm); return (true); } else { if (!quiet) canned_msg(MSG_NOTHING_HAPPENS); // Xom thinks it's funny if enchantment is possible but fails. if (is_enchantable_armour(arm, false)) xom_is_stimulated(32); return (false); } } // Output message before changing enchantment and curse status. if (!quiet) { mprf("%s glows green for a moment.", arm.name(DESC_CAP_YOUR).c_str()); } arm.plus++; ac_change++; if (is_cursed) do_uncurse_item(arm); return (true); } static bool _handle_enchant_armour(int item_slot) { do { if (item_slot == -1) { item_slot = prompt_invent_item("Enchant which item?", MT_INVLIST, OSEL_ENCH_ARM, true, true, false); } if (prompt_failed(item_slot)) return (false); item_def& arm(you.inv[item_slot]); if (!is_enchantable_armour(arm, true, true)) { mpr("Choose some type of armour to enchant, or Esc to abort."); if (Options.auto_list) more(); item_slot = -1; continue; } // Okay, we may actually (attempt to) enchant something. int ac_change; bool result = enchant_armour(ac_change, false, arm); if (ac_change) you.redraw_armour_class = true; return (result); } while (true); return (false); } static void handle_read_book(int item_slot) { item_def& book(you.inv[item_slot]); if (book.sub_type == BOOK_DESTRUCTION) { if (silenced(you.pos())) mpr("This book does not work if you cannot read it aloud!"); else tome_of_power(item_slot); return; } else if (book.sub_type == BOOK_MANUAL) { skill_manual(item_slot); return; } while (true) { // Spellbook const int ltr = read_book( book, RBOOK_READ_SPELL ); if (ltr < 'a' || ltr > 'h') //jmf: was 'g', but 8=h { mesclr(true); return; } const spell_type spell = which_spell_in_book(book, letter_to_index(ltr)); if (spell == SPELL_NO_SPELL) { mesclr(true); return; } describe_spell(spell, &book); // Player memorised spell which was being looked at. if (you.turn_is_over) return; } } // For unidentified scrolls of recharging, identify and enchant armour // offer full choice of inventory and only identify the scroll if you chose // something that is affected by the scroll. Once they're identified, you'll // get the limited inventory listing. // Returns true if the scroll had an obvious effect and should be identified. static bool _scroll_modify_item(item_def scroll) { ASSERT(scroll.base_type == OBJ_SCROLLS); // Get the slot of the scroll just read. int item_slot = scroll.slot; // Get the slot of the item the scroll is to be used on. // Ban the scroll's own slot from the prompt to avoid the stupid situation // where you use identify on itself. item_slot = prompt_invent_item("Use on which item? (\\ to view known items)", MT_INVLIST, OSEL_ANY, true, true, false, 0, item_slot, NULL, OPER_ANY, true); if (prompt_failed(item_slot)) return (false); item_def &item = you.inv[item_slot]; switch (scroll.sub_type) { case SCR_IDENTIFY: if (!fully_identified(item)) { mpr("This is a scroll of identify!"); identify(-1, item_slot); return (true); } break; case SCR_RECHARGING: if (item_is_rechargeable(item, false, true)) { // Might still fail on highly enchanted weapons of electrocution. // (If so, already prints the "Nothing happens" message.) if (recharge_wand(item_slot)) return (true); return (false); } break; case SCR_ENCHANT_ARMOUR: if (is_enchantable_armour(item, true)) { // Might still fail because of already high enchantment. // (If so, already prints the "Nothing happens" message.) if (_handle_enchant_armour(item_slot)) return (true); return (false); } break; default: mprf("Buggy scroll %d can't modify item!", scroll.sub_type); break; } // Oops, wrong item... canned_msg(MSG_NOTHING_HAPPENS); return (false); } static void _vulnerability_scroll() { // First cast antimagic on yourself. antimagic(); // List of magical enchantments which will be dispelled. const enchant_type lost_enchantments[] = { ENCH_SLOW, ENCH_HASTE, ENCH_SWIFT, ENCH_MIGHT, ENCH_FEAR, ENCH_CONFUSION, ENCH_INVIS, ENCH_CORONA, ENCH_CHARM, ENCH_PARALYSIS, ENCH_PETRIFYING, ENCH_PETRIFIED }; mon_enchant lowered_mr(ENCH_LOWERED_MR, 1, KC_YOU, 40); // Go over all creatures in LOS. for (radius_iterator ri(you.pos(), LOS_RADIUS); ri; ++ri) { if (monsters* mon = monster_at(*ri)) { // Dispel all magical enchantments. for (unsigned int i = 0; i < ARRAYSZ(lost_enchantments); ++i) mon->del_ench(lost_enchantments[i], true, true); // If relevant, monsters have their MR halved. if (!mons_immune_magic(mon)) mon->add_ench(lowered_mr); // Annoying but not enough to turn friendlies against you. if (!mon->wont_attack()) behaviour_event(mon, ME_ANNOY, MHITYOU); } } you.set_duration(DUR_LOWERED_MR, 40, 0, "Magic dampens around you!"); } void read_scroll(int slot) { if (you.berserk()) { canned_msg(MSG_TOO_BERSERK); return; } if (player_in_bat_form()) { canned_msg(MSG_PRESENT_FORM); return; } if (inv_count() < 1) { canned_msg(MSG_NOTHING_CARRIED); return; } int item_slot = (slot != -1) ? slot : prompt_invent_item( "Read which item?", MT_INVLIST, OBJ_SCROLLS, true, true, true, 0, -1, NULL, OPER_READ ); if (prompt_failed(item_slot)) return; item_def& scroll = you.inv[item_slot]; if (scroll.base_type != OBJ_BOOKS && scroll.base_type != OBJ_SCROLLS) { mpr("You can't read that!"); crawl_state.zero_turns_taken(); return; } // Here we try to read a book {dlb}: if (scroll.base_type == OBJ_BOOKS) { handle_read_book( item_slot ); return; } if (silenced(you.pos())) { mpr("Magic scrolls do not work when you're silenced!"); crawl_state.zero_turns_taken(); return; } // Ok - now we FINALLY get to read a scroll !!! {dlb} you.turn_is_over = true; // Imperfect vision prevents players from reading actual content {dlb}: if (player_mutation_level(MUT_BLURRY_VISION) && x_chance_in_y(player_mutation_level(MUT_BLURRY_VISION), 5)) { mpr((player_mutation_level(MUT_BLURRY_VISION) == 3 && one_chance_in(3)) ? "This scroll appears to be blank." : "The writing blurs in front of your eyes."); return; } // Decrement and handle inventory if any scroll other than paper {dlb}: const scroll_type which_scroll = static_cast(scroll.sub_type); const bool alreadyknown = item_type_known(scroll); if (alreadyknown && (which_scroll == SCR_BLINKING || which_scroll == SCR_TELEPORTATION) && scan_artefacts(ARTP_PREVENT_TELEPORTATION, false)) { mpr("You cannot teleport right now."); return; } if (which_scroll != SCR_PAPER && (which_scroll != SCR_IMMOLATION || you.confused())) { mpr("As you read the scroll, it crumbles to dust."); // Actual removal of scroll done afterwards. -- bwr } const bool dangerous = player_in_a_dangerous_place(); // Scrolls of paper are also exempted from this handling {dlb}: if (which_scroll != SCR_PAPER) { if (you.confused()) { random_uselessness(item_slot); dec_inv_item_quantity(item_slot, 1); return; } if (!you.skills[SK_SPELLCASTING]) exercise(SK_SPELLCASTING, (coinflip()? 2 : 1)); } // It is the exception, not the rule, that the scroll will not // be identified. {dlb} bool id_the_scroll = true; // to prevent unnecessary repetition bool tried_on_item = false; // used to modify item (?EA, ?RC, ?ID) bool bad_effect = false; // for Xom: result is bad (or at least dangerous) switch (which_scroll) { case SCR_PAPER: // Remember, paper scrolls handled as special case above, too. mpr("This scroll appears to be blank."); if (player_mutation_level(MUT_BLURRY_VISION) == 3) id_the_scroll = false; break; case SCR_RANDOM_USELESSNESS: random_uselessness(item_slot); break; case SCR_BLINKING: blink(1000, false); break; case SCR_TELEPORTATION: you_teleport(); break; case SCR_REMOVE_CURSE: if (!remove_curse(false)) id_the_scroll = false; break; case SCR_DETECT_CURSE: if (!detect_curse(false)) id_the_scroll = false; break; case SCR_ACQUIREMENT: mpr("This is a scroll of acquirement!"); more(); acquirement(OBJ_RANDOM, AQ_SCROLL); break; case SCR_FEAR: { int fear_influenced = 0; mass_enchantment(ENCH_FEAR, 1000, MHITYOU, NULL, &fear_influenced); id_the_scroll = fear_influenced; break; } case SCR_NOISE: noisy(25, you.pos(), "You hear a loud clanging noise!"); break; case SCR_SUMMONING: { const int monster = create_monster( mgen_data(MONS_ABOMINATION_SMALL, BEH_FRIENDLY, &you, 0, 0, you.pos(), MHITYOU, MG_FORCE_BEH)); if (monster != -1) { mpr("A horrible Thing appears!"); player_angers_monster(&menv[monster]); } else { canned_msg(MSG_NOTHING_HAPPENS); id_the_scroll = false; } break; } case SCR_FOG: mpr("The scroll dissolves into smoke."); big_cloud(random_smoke_type(), KC_YOU, you.pos(), 50, 8 + random2(8)); break; case SCR_MAGIC_MAPPING: if (you.level_type == LEVEL_PANDEMONIUM) { if (!item_type_known(scroll)) mpr("You feel momentarily disoriented."); else mpr("Your Earth magic cannot map Pandemonium."); } else magic_mapping(50, 90 + random2(11), false); break; case SCR_TORMENT: torment(TORMENT_SCROLL, you.pos()); // This is only naughty if you know you're doing it. did_god_conduct(DID_NECROMANCY, 10, item_type_known(scroll)); bad_effect = true; break; case SCR_IMMOLATION: mpr("The scroll explodes in your hands!"); // Doesn't destroy scrolls anymore, so no special check needed. (jpeg) immolation(10, IMMOLATION_SCROLL, you.pos(), alreadyknown, &you); bad_effect = true; more(); break; case SCR_CURSE_WEAPON: if (!you.weapon() || you.weapon()->base_type != OBJ_WEAPONS || you.weapon()->cursed()) { canned_msg(MSG_NOTHING_HAPPENS); id_the_scroll = false; } else { // Also sets wield_change. do_curse_item( *you.weapon(), false ); learned_something_new(TUT_YOU_CURSED); bad_effect = true; } break; // Everything [in the switch] below this line is a nightmare {dlb}: case SCR_ENCHANT_WEAPON_I: id_the_scroll = _handle_enchant_weapon(ENCHANT_TO_HIT); break; case SCR_ENCHANT_WEAPON_II: id_the_scroll = _handle_enchant_weapon(ENCHANT_TO_DAM); break; case SCR_ENCHANT_WEAPON_III: if (you.weapon()) { item_def& wpn = *you.weapon(); const bool is_cursed = wpn.cursed(); if (wpn.base_type != OBJ_WEAPONS && wpn.base_type != OBJ_MISSILES || !is_cursed && !is_enchantable_weapon(wpn, true, true) && !is_enchantable_weapon(wpn, true, false)) { canned_msg(MSG_NOTHING_HAPPENS); id_the_scroll = false; break; } // It's a weapon or stack of missiles that is not an artefact // and not fully enchanted, or at least needs to be uncursed. // Get item name now before changing enchantment. std::string iname = wpn.name(DESC_CAP_YOUR); // Uncursing is always possible. bool success = is_cursed; if (_handle_enchant_weapon(ENCHANT_TO_HIT, true)) success = true; if (is_enchantable_weapon(wpn, true, true) && coinflip() && _handle_enchant_weapon(ENCHANT_TO_HIT, true)) { success = true; } // Only weapons use the second stat. if (wpn.base_type == OBJ_WEAPONS) { if (_handle_enchant_weapon(ENCHANT_TO_DAM, true)) success = true; if (is_enchantable_weapon(wpn, true, false) && coinflip() && _handle_enchant_weapon(ENCHANT_TO_DAM, true)) { success = true; } } if (is_cursed) do_uncurse_item(wpn); if (success) { mprf("%s glow%s bright yellow for a while.", iname.c_str(), wpn.quantity > 1 ? "" : "s"); } else { canned_msg(MSG_NOTHING_HAPPENS); id_the_scroll = false; } } else { canned_msg(MSG_NOTHING_HAPPENS); id_the_scroll = false; } break; case SCR_VORPALISE_WEAPON: id_the_scroll = _vorpalise_weapon(); if (!id_the_scroll) canned_msg(MSG_NOTHING_HAPPENS); break; case SCR_IDENTIFY: if (!item_type_known(scroll)) { id_the_scroll = _scroll_modify_item(scroll); if (!id_the_scroll) tried_on_item = true; } else identify(-1); break; case SCR_RECHARGING: if (!item_type_known(scroll)) { id_the_scroll = _scroll_modify_item(scroll); if (!id_the_scroll) tried_on_item = true; } else recharge_wand(-1); break; case SCR_ENCHANT_ARMOUR: if (!item_type_known(scroll)) { id_the_scroll = _scroll_modify_item(scroll); if (!id_the_scroll) tried_on_item = true; } else _handle_enchant_armour(-1); break; case SCR_CURSE_ARMOUR: { // make sure there's something to curse first int count = 0; int affected = EQ_WEAPON; for (int i = EQ_CLOAK; i <= EQ_BODY_ARMOUR; i++) { if (you.equip[i] != -1 && !you.inv[you.equip[i]].cursed()) { count++; if (one_chance_in(count)) affected = i; } } if (affected == EQ_WEAPON) { canned_msg(MSG_NOTHING_HAPPENS); id_the_scroll = false; break; } // Make the name before we curse it. do_curse_item( you.inv[you.equip[affected]], false ); learned_something_new(TUT_YOU_CURSED); bad_effect = true; break; } case SCR_HOLY_WORD: { int pow = 100; if (is_good_god(you.religion)) { pow += (you.religion == GOD_SHINING_ONE) ? you.piety : you.piety / 2; } const bool success = holy_word(pow, HOLY_WORD_SCROLL, you.pos(), !item_type_known(scroll), &you); if (!success) { canned_msg(MSG_NOTHING_HAPPENS); id_the_scroll = false; } // This is only naughty if you know you're doing it, or if it's // succeeded, in which case you'll know for next time. if (item_type_known(scroll) || success) did_god_conduct(DID_HOLY, 10, item_type_known(scroll)); break; } case SCR_SILENCE: cast_silence(25); break; case SCR_VULNERABILITY: _vulnerability_scroll(); break; default: mpr("Read a buggy scroll, please report this."); break; } set_ident_type(scroll, id_the_scroll ? ID_KNOWN_TYPE : tried_on_item ? ID_TRIED_ITEM_TYPE : ID_TRIED_TYPE); // Finally, destroy and identify the scroll. if (which_scroll != SCR_PAPER) { if (id_the_scroll) set_ident_flags(scroll, ISFLAG_KNOW_TYPE); // for notes dec_inv_item_quantity( item_slot, 1 ); } if (!alreadyknown && dangerous) { // Xom loves it when you read an unknown scroll and there is a // dangerous monster nearby... (though not as much as potions // since there are no *really* bad scrolls, merely useless ones). xom_is_stimulated(bad_effect ? 128 : 64); } } void examine_object(void) { int item_slot = prompt_invent_item( "Examine which item?", MT_INVLIST, -1, true, true, true, 0, -1, NULL, OPER_EXAMINE ); if (prompt_failed(item_slot)) return; describe_item( you.inv[item_slot], true ); redraw_screen(); mesclr(true); } // end original_name() void use_artefact(unsigned char item_wield_2, bool *show_msgs) { use_artefact( you.inv[ item_wield_2 ], show_msgs ); } void use_artefact(item_def &item, bool *show_msgs, bool unmeld) { #define unknown_proprt(prop) (proprt[(prop)] && !known[(prop)]) ASSERT( is_artefact( item ) ); // Call unrandart equip function first, so that it can modify the // artefact's properties before they're applied. if (is_unrandom_artefact( item )) { const unrandart_entry *entry = get_unrand_entry(item.special); if (entry->equip_func) entry->equip_func(&item, show_msgs, unmeld); if (entry->world_reacts_func) { equipment_type eq = get_item_slot(item.base_type, item.sub_type); you.unrand_reacts |= (1 << eq); } } const bool alreadyknown = item_type_known(item); const bool dangerous = player_in_a_dangerous_place(); artefact_properties_t proprt; artefact_known_props_t known; artefact_wpn_properties( item, proprt, known ); // Only give property messages for previously unknown properties. if (proprt[ARTP_AC]) { you.redraw_armour_class = true; if (!known[ARTP_AC]) { mprf("You feel %s.", proprt[ARTP_AC] > 0? "well-protected" : "more vulnerable"); artefact_wpn_learn_prop(item, ARTP_AC); } } if (proprt[ARTP_EVASION]) { you.redraw_evasion = true; if (!known[ARTP_EVASION]) { mprf("You feel somewhat %s.", proprt[ARTP_EVASION] > 0? "nimbler" : "more awkward"); artefact_wpn_learn_prop(item, ARTP_EVASION); } } if (proprt[ARTP_MAGICAL_POWER] && !known[ARTP_MAGICAL_POWER]) { mprf("You feel your mana capacity %s.", proprt[ARTP_MAGICAL_POWER] > 0? "increase" : "decrease"); artefact_wpn_learn_prop(item, ARTP_MAGICAL_POWER); } // Modify ability scores. // Output result even when identified (because of potential fatality). modify_stat( STAT_STRENGTH, proprt[ARTP_STRENGTH], false, item ); modify_stat( STAT_INTELLIGENCE, proprt[ARTP_INTELLIGENCE], false, item ); modify_stat( STAT_DEXTERITY, proprt[ARTP_DEXTERITY], false, item ); const artefact_prop_type stat_props[3] = {ARTP_STRENGTH, ARTP_INTELLIGENCE, ARTP_DEXTERITY}; for (int i = 0; i < 3; i++) if (unknown_proprt(stat_props[i])) artefact_wpn_learn_prop(item, stat_props[i]); // For evokable stuff, check whether other equipped items yield // the same ability. If not, and if the ability granted hasn't // already been discovered, give a message. if (unknown_proprt(ARTP_LEVITATE) && !items_give_ability(item.link, ARTP_LEVITATE)) { if (you.airborne()) mpr("You feel vaguely more buoyant than before."); else mpr("You feel buoyant."); artefact_wpn_learn_prop(item, ARTP_LEVITATE); } if (unknown_proprt(ARTP_INVISIBLE) && !you.duration[DUR_INVIS]) { mpr("You become transparent for a moment."); artefact_wpn_learn_prop(item, ARTP_INVISIBLE); } if (unknown_proprt(ARTP_BERSERK) && !items_give_ability(item.link, ARTP_BERSERK)) { mpr("You feel a brief urge to hack something to bits."); artefact_wpn_learn_prop(item, ARTP_BERSERK); } if (!unmeld && !item.cursed() && proprt[ARTP_CURSED] > 0 && one_chance_in(proprt[ARTP_CURSED])) { do_curse_item( item, false ); artefact_wpn_learn_prop(item, ARTP_CURSED); } if (proprt[ARTP_NOISES]) you.attribute[ATTR_NOISES] = 1; if (!alreadyknown && Options.autoinscribe_artefacts) add_autoinscription(item, artefact_auto_inscription(item)); if (!alreadyknown && dangerous) { // Xom loves it when you use an unknown random artefact and // there is a dangerous monster nearby... xom_is_stimulated(128); } // Let's try this here instead of up there. if (proprt[ARTP_MAGICAL_POWER]) calc_mp(); #undef unknown_proprt } bool wearing_slot(int inv_slot) { for (int i = EQ_CLOAK; i <= EQ_AMULET; ++i) if (inv_slot == you.equip[i]) return (true); return (false); } bool item_blocks_teleport(bool permit_id) { return (scan_artefacts(ARTP_PREVENT_TELEPORTATION) || stasis_blocks_effect(permit_id, NULL)); } bool stasis_blocks_effect(bool identify, const char *msg, int noise, const char *silenced_msg) { if (wearing_amulet(AMU_STASIS)) { item_def *amulet = you.slot_item(EQ_AMULET); if (msg) { const std::string name(amulet? amulet->name(DESC_CAP_YOUR) : "Something"); const std::string message = make_stringf(msg, name.c_str()); if (noise) { if (!noisy(noise, you.pos(), message.c_str()) && silenced_msg) { mprf(silenced_msg, name.c_str()); } } else { mpr(message.c_str()); } } // In all cases, the amulet auto-ids if requested. if (amulet && identify) set_ident_type(*amulet, ID_KNOWN_TYPE); return (true); } return (false); } #ifdef USE_TILE // Interactive menu for item drop/use. void tile_item_use_floor(int idx) { if (mitm[idx].base_type == OBJ_CORPSES && mitm[idx].sub_type != CORPSE_SKELETON && !food_is_rotten(mitm[idx])) { butchery(idx); } } void tile_item_pickup(int idx) { pickup_single_item(idx, mitm[idx].quantity); } void tile_item_drop(int idx) { drop_item(idx, you.inv[idx].quantity); } void tile_item_eat_floor(int idx) { if (mitm[idx].base_type == OBJ_CORPSES && you.species == SP_VAMPIRE || mitm[idx].base_type == OBJ_FOOD && you.is_undead != US_UNDEAD && you.species != SP_VAMPIRE) { if (can_ingest(mitm[idx].base_type, mitm[idx].sub_type, false)) eat_floor_item(idx); } } void tile_item_use_secondary(int idx) { const item_def item = you.inv[idx]; if (item.base_type == OBJ_WEAPONS && is_throwable(&you, item)) { if (check_warning_inscriptions(item, OPER_FIRE)) fire_thing(idx); // fire weapons } else if (you.equip[EQ_WEAPON] == idx) { wield_weapon(true, PROMPT_GOT_SPECIAL); // unwield } else if (_valid_weapon_swap(item)) { // secondary wield for several spells and such wield_weapon(true, idx); // wield } } void tile_item_use(int idx) { const item_def item = you.inv[idx]; // Equipped? bool equipped = false; bool equipped_weapon = false; for (unsigned int i = 0; i < NUM_EQUIP; i++) { if (you.equip[i] == idx) { equipped = true; if (i == EQ_WEAPON) equipped_weapon = true; break; } } // Special case for folks who are wielding something // that they shouldn't be wielding. // Note that this is only a problem for equipables // (otherwise it would only waste a turn) if (you.equip[EQ_WEAPON] == idx && (item.base_type == OBJ_ARMOUR || item.base_type == OBJ_JEWELLERY)) { wield_weapon(true, PROMPT_GOT_SPECIAL); return; } const int type = item.base_type; // Use it switch (type) { case OBJ_WEAPONS: case OBJ_STAVES: case OBJ_MISCELLANY: case OBJ_WANDS: // Wield any unwielded item of these types. if (!equipped && (type == OBJ_WEAPONS || type == OBJ_STAVES || is_deck(item) || type == OBJ_MISCELLANY && item.sub_type == MISC_LANTERN_OF_SHADOWS)) { wield_weapon(true, idx); return; } // Evoke misc. items, rods, or wands. if (item_is_evokable(item)) { evoke_item(idx); return; } // Unwield wielded items. if (equipped) wield_weapon(true, PROMPT_GOT_SPECIAL); // unwield return; case OBJ_MISSILES: if (check_warning_inscriptions(item, OPER_FIRE)) fire_thing(idx); return; case OBJ_ARMOUR: if (player_in_bat_form()) { mpr("You can't wear or remove anything in your present form."); return; } if (equipped && !equipped_weapon) { if (check_warning_inscriptions(item, OPER_TAKEOFF)) takeoff_armour(idx); } else if (check_warning_inscriptions(item, OPER_WEAR)) wear_armour(idx); return; case OBJ_CORPSES: if (you.species != SP_VAMPIRE || item.sub_type == CORPSE_SKELETON || food_is_rotten(item)) { break; } // intentional fall-through for Vampires case OBJ_FOOD: if (check_warning_inscriptions(item, OPER_EAT)) eat_food(idx); return; case OBJ_BOOKS: if (item.sub_type == BOOK_MANUAL || item.sub_type == BOOK_DESTRUCTION || you.skills[SK_SPELLCASTING] == 0) { if (check_warning_inscriptions(item, OPER_READ)) handle_read_book(idx); } // else it's a spellbook else if (check_warning_inscriptions(item, OPER_MEMORISE)) learn_spell(); // offers all spells, might not be what we want return; case OBJ_SCROLLS: if (check_warning_inscriptions(item, OPER_READ)) read_scroll(idx); return; case OBJ_JEWELLERY: if (equipped && !equipped_weapon) { if (check_warning_inscriptions(item, OPER_REMOVE)) remove_ring(idx); } else if (check_warning_inscriptions(item, OPER_PUTON)) puton_ring(idx); return; case OBJ_POTIONS: if (check_warning_inscriptions(item, OPER_QUAFF)) drink(idx); return; default: return; } } #endif