/* * File: abl-show.cc * Summary: Functions related to special abilities. * Written by: Linley Henzell */ #include "AppHdr.h" #include "abl-show.h" #include #include #include #include #include #include "externs.h" #include "abyss.h" #include "artefact.h" #include "beam.h" #include "database.h" #include "decks.h" #include "delay.h" #include "describe.h" #include "directn.h" #include "effects.h" #include "env.h" #include "food.h" #include "godabil.h" #include "it_use2.h" #include "macro.h" #include "message.h" #include "menu.h" #include "misc.h" #include "mon-place.h" #include "mgen_data.h" #include "mutation.h" #include "notes.h" #include "ouch.h" #include "player.h" #include "religion.h" #include "skills.h" #include "species.h" #include "spl-cast.h" #include "spl-util.h" #include "spells1.h" #include "spells2.h" #include "spells3.h" #include "spells4.h" #include "state.h" #include "stuff.h" #include "areas.h" #include "transform.h" #include "tutorial.h" #ifdef UNIX #include "libunix.h" #endif enum ability_flag_type { ABFLAG_NONE = 0x00000000, ABFLAG_BREATH = 0x00000001, // ability uses DUR_BREATH_WEAPON ABFLAG_DELAY = 0x00000002, // ability has its own delay (ie recite) ABFLAG_PAIN = 0x00000004, // ability must hurt player (ie torment) ABFLAG_PIETY = 0x00000008, // ability has its own piety cost ABFLAG_EXHAUSTION = 0x00000010, // fails if you.exhausted ABFLAG_INSTANT = 0x00000020, // doesn't take time to use ABFLAG_PERMANENT_HP = 0x00000040, // costs permanent HPs ABFLAG_PERMANENT_MP = 0x00000080, // costs permanent MPs ABFLAG_CONF_OK = 0x00000100, // can use even if confused ABFLAG_FRUIT = 0x00000200, // ability requires fruit ABFLAG_VARIABLE_FRUIT = 0x00000400 // ability requires fruit or piety }; static int _find_ability_slot( ability_type which_ability ); static bool _activate_talent(const talent& tal); static bool _do_ability(const ability_def& abil); static void _pay_ability_costs(const ability_def& abil); static std::string _describe_talent(const talent& tal); // this all needs to be split into data/util/show files // and the struct mechanism here needs to be rewritten (again) // along with the display routine to piece the strings // together dynamically ... I'm getting to it now {dlb} // it makes more sense to think of them as an array // of structs than two arrays that share common index // values -- well, doesn't it? {dlb} // declaring this const messes up externs later, so don't do it ability_type god_abilities[MAX_NUM_GODS][MAX_GOD_ABILITIES] = { // no god { ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_NON_ABILITY }, // Zin { ABIL_ZIN_RECITE, ABIL_ZIN_VITALISATION, ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_ZIN_SANCTUARY }, // TSO { ABIL_NON_ABILITY, ABIL_TSO_DIVINE_SHIELD, ABIL_NON_ABILITY, ABIL_TSO_CLEANSING_FLAME, ABIL_TSO_SUMMON_DIVINE_WARRIOR }, // Kikubaaqudgha { ABIL_KIKU_RECEIVE_CORPSES, ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_NON_ABILITY }, // Yredelemnul { ABIL_YRED_ANIMATE_REMAINS, ABIL_YRED_RECALL_UNDEAD_SLAVES, ABIL_YRED_ANIMATE_DEAD, ABIL_YRED_DRAIN_LIFE, ABIL_YRED_ENSLAVE_SOUL }, // Xom { ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_NON_ABILITY }, // Vehumet { ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_NON_ABILITY }, // Okawaru { ABIL_OKAWARU_MIGHT, ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_OKAWARU_HASTE }, // Makhleb { ABIL_NON_ABILITY, ABIL_MAKHLEB_MINOR_DESTRUCTION, ABIL_MAKHLEB_LESSER_SERVANT_OF_MAKHLEB, ABIL_MAKHLEB_MAJOR_DESTRUCTION, ABIL_MAKHLEB_GREATER_SERVANT_OF_MAKHLEB }, // Sif Muna { ABIL_SIF_MUNA_CHANNEL_ENERGY, ABIL_SIF_MUNA_FORGET_SPELL, ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_NON_ABILITY }, // Trog { ABIL_TROG_BERSERK, ABIL_TROG_REGEN_MR, ABIL_NON_ABILITY, ABIL_TROG_BROTHERS_IN_ARMS, ABIL_NON_ABILITY }, // Nemelex { ABIL_NEMELEX_DRAW_ONE, ABIL_NEMELEX_PEEK_TWO, ABIL_NEMELEX_TRIPLE_DRAW, ABIL_NEMELEX_MARK_FOUR, ABIL_NEMELEX_STACK_FIVE }, // Elyvilon { ABIL_ELYVILON_LESSER_HEALING_OTHERS, ABIL_ELYVILON_PURIFICATION, ABIL_ELYVILON_GREATER_HEALING_OTHERS, ABIL_ELYVILON_RESTORATION, ABIL_ELYVILON_DIVINE_VIGOUR }, // Lugonu { ABIL_LUGONU_ABYSS_EXIT, ABIL_LUGONU_BEND_SPACE, ABIL_LUGONU_BANISH, ABIL_LUGONU_CORRUPT, ABIL_LUGONU_ABYSS_ENTER }, // Beogh { ABIL_NON_ABILITY, ABIL_BEOGH_SMITING, ABIL_NON_ABILITY, ABIL_BEOGH_RECALL_ORCISH_FOLLOWERS, ABIL_NON_ABILITY }, // Jiyva { ABIL_JIYVA_CALL_JELLY, ABIL_NON_ABILITY, ABIL_NON_ABILITY, ABIL_JIYVA_SLIMIFY, ABIL_JIYVA_CURE_BAD_MUTATION }, // Fedhas { ABIL_FEDHAS_EVOLUTION, ABIL_FEDHAS_SUNLIGHT, ABIL_FEDHAS_PLANT_RING, ABIL_FEDHAS_SPAWN_SPORES, ABIL_FEDHAS_RAIN}, // Cheibriados { ABIL_NON_ABILITY, ABIL_CHEIBRIADOS_TIME_BEND, ABIL_NON_ABILITY, ABIL_CHEIBRIADOS_SLOUCH, ABIL_CHEIBRIADOS_TIME_STEP }, }; // The description screen was way out of date with the actual costs. // This table puts all the information in one place... -- bwr // // The four numerical fields are: MP, HP, food, and piety. // Note: food_cost = val + random2avg( val, 2 ) // piety_cost = val + random2( (val + 1) / 2 + 1 ); static const ability_def Ability_List[] = { // NON_ABILITY should always come first { ABIL_NON_ABILITY, "No ability", 0, 0, 0, 0, ABFLAG_NONE }, { ABIL_SPIT_POISON, "Spit Poison", 0, 0, 40, 0, ABFLAG_BREATH }, { ABIL_TELEPORTATION, "Teleportation", 0, 100, 200, 0, ABFLAG_NONE }, { ABIL_BLINK, "Blink", 0, 50, 50, 0, ABFLAG_NONE }, { ABIL_BREATHE_FIRE, "Breathe Fire", 0, 0, 125, 0, ABFLAG_BREATH }, { ABIL_BREATHE_FROST, "Breathe Frost", 0, 0, 125, 0, ABFLAG_BREATH }, { ABIL_BREATHE_POISON, "Breathe Poison Gas", 0, 0, 125, 0, ABFLAG_BREATH }, { ABIL_BREATHE_LIGHTNING, "Breathe Lightning", 0, 0, 125, 0, ABFLAG_BREATH }, { ABIL_BREATHE_POWER, "Breathe Power", 0, 0, 125, 0, ABFLAG_BREATH }, { ABIL_BREATHE_STICKY_FLAME, "Breathe Sticky Flame", 0, 0, 125, 0, ABFLAG_BREATH }, { ABIL_BREATHE_STEAM, "Breathe Steam", 0, 0, 75, 0, ABFLAG_BREATH }, { ABIL_TRAN_BAT, "Bat Form", 2, 0, 0, 0, ABFLAG_NONE }, { ABIL_SPIT_ACID, "Spit Acid", 0, 0, 125, 0, ABFLAG_NONE }, { ABIL_FLY, "Fly", 3, 0, 100, 0, ABFLAG_NONE }, { ABIL_SUMMON_MINOR_DEMON, "Summon Minor Demon", 0, 50, 75, 0, ABFLAG_NONE }, { ABIL_SUMMON_DEMON, "Summon Demon", 0, 150, 150, 0, ABFLAG_NONE }, { ABIL_HELLFIRE, "Hellfire", 0, 350, 200, 0, ABFLAG_NONE }, { ABIL_TORMENT, "Torment", 0, 100, 250, 0, ABFLAG_PAIN }, { ABIL_RAISE_DEAD, "Raise Dead", 0, 75, 150, 0, ABFLAG_NONE }, { ABIL_CONTROL_DEMON, "Control Demon", 0, 275, 100, 0, ABFLAG_NONE }, { ABIL_CHANNELING, "Channeling", 0, 15, 30, 0, ABFLAG_NONE }, { ABIL_THROW_FLAME, "Throw Flame", 0, 20, 50, 0, ABFLAG_NONE }, { ABIL_THROW_FROST, "Throw Frost", 0, 20, 50, 0, ABFLAG_NONE }, { ABIL_BOLT_OF_DRAINING, "Bolt of Draining", 0, 175, 100, 0, ABFLAG_NONE }, // FLY_II used to have ABFLAG_EXHAUSTION, but that's somewhat meaningless // as exhaustion's only (and designed) effect is preventing Berserk. - bwr { ABIL_FLY_II, "Fly", 0, 0, 25, 0, ABFLAG_NONE }, { ABIL_DELAYED_FIREBALL, "Release Delayed Fireball", 0, 0, 0, 0, ABFLAG_INSTANT }, { ABIL_MUMMY_RESTORATION, "Self-Restoration", 1, 0, 0, 0, ABFLAG_PERMANENT_MP }, // EVOKE abilities use Evocations and come from items: // Mapping, Teleportation, and Blink can also come from mutations // so we have to distinguish them (see above). The off items // below are labeled EVOKE because they only work now if the // player has an item with the evocable power (not just because // you used a wand, potion, or miscast effect). I didn't see // any reason to label them as "Evoke" in the text, they don't // use or train Evocations (the others do). -- bwr { ABIL_EVOKE_TELEPORTATION, "Evoke Teleportation", 3, 0, 200, 0, ABFLAG_NONE }, { ABIL_EVOKE_BLINK, "Evoke Blink", 1, 0, 50, 0, ABFLAG_NONE }, { ABIL_RECHARGING, "Device Recharging", 1, 0, 0, 0, ABFLAG_PERMANENT_MP }, { ABIL_EVOKE_BERSERK, "Evoke Berserk Rage", 0, 0, 0, 0, ABFLAG_NONE }, { ABIL_EVOKE_TURN_INVISIBLE, "Evoke Invisibility", 2, 0, 250, 0, ABFLAG_NONE }, { ABIL_EVOKE_TURN_VISIBLE, "Turn Visible", 0, 0, 0, 0, ABFLAG_NONE }, { ABIL_EVOKE_LEVITATE, "Evoke Levitation", 1, 0, 100, 0, ABFLAG_NONE }, { ABIL_EVOKE_STOP_LEVITATING, "Stop Levitating", 0, 0, 0, 0, ABFLAG_NONE }, { ABIL_END_TRANSFORMATION, "End Transformation", 0, 0, 0, 0, ABFLAG_NONE }, // INVOCATIONS: // Zin { ABIL_ZIN_SUSTENANCE, "Sustenance", 0, 0, 0, 0, ABFLAG_PIETY }, { ABIL_ZIN_RECITE, "Recite", 3, 0, 0, 0, ABFLAG_DELAY }, { ABIL_ZIN_VITALISATION, "Vitalisation", 0, 0, 100, 2, ABFLAG_CONF_OK }, { ABIL_ZIN_SANCTUARY, "Sanctuary", 7, 0, 150, 15, ABFLAG_NONE }, { ABIL_ZIN_CURE_ALL_MUTATIONS, "Cure All Mutations", 0, 0, 0, 0, ABFLAG_NONE }, // The Shining One { ABIL_TSO_DIVINE_SHIELD, "Divine Shield", 3, 0, 50, 2, ABFLAG_NONE }, { ABIL_TSO_CLEANSING_FLAME, "Cleansing Flame", 5, 0, 100, 2, ABFLAG_NONE }, { ABIL_TSO_SUMMON_DIVINE_WARRIOR, "Summon Divine Warrior", 8, 0, 150, 4, ABFLAG_NONE }, // Kikubaaqudgha { ABIL_KIKU_RECEIVE_CORPSES, "Receive Corpses", 3, 0, 50, 2, ABFLAG_NONE }, // Yredelemnul { ABIL_YRED_INJURY_MIRROR, "Injury Mirror", 0, 0, 0, 0, ABFLAG_PIETY }, { ABIL_YRED_ANIMATE_REMAINS, "Animate Remains", 1, 0, 100, 0, ABFLAG_NONE }, { ABIL_YRED_RECALL_UNDEAD_SLAVES, "Recall Undead Slaves", 2, 0, 50, 0, ABFLAG_NONE }, { ABIL_YRED_ANIMATE_DEAD, "Animate Dead", 3, 0, 100, 1, ABFLAG_NONE }, { ABIL_YRED_DRAIN_LIFE, "Drain Life", 6, 0, 200, 2, ABFLAG_NONE }, { ABIL_YRED_ENSLAVE_SOUL, "Enslave Soul", 8, 0, 150, 4, ABFLAG_NONE }, // Okawaru { ABIL_OKAWARU_MIGHT, "Might", 2, 0, 50, 1, ABFLAG_NONE }, { ABIL_OKAWARU_HASTE, "Haste", 5, 0, 100, generic_cost::fixed(5), ABFLAG_NONE }, // Makhleb { ABIL_MAKHLEB_MINOR_DESTRUCTION, "Minor Destruction", 1, 0, 20, 0, ABFLAG_NONE }, { ABIL_MAKHLEB_LESSER_SERVANT_OF_MAKHLEB, "Lesser Servant of Makhleb", 2, 0, 50, 1, ABFLAG_NONE }, { ABIL_MAKHLEB_MAJOR_DESTRUCTION, "Major Destruction", 4, 0, 100, 2, ABFLAG_NONE }, { ABIL_MAKHLEB_GREATER_SERVANT_OF_MAKHLEB, "Greater Servant of Makhleb", 6, 0, 100, 3, ABFLAG_NONE }, // Sif Muna { ABIL_SIF_MUNA_CHANNEL_ENERGY, "Channel Energy", 0, 0, 100, 0, ABFLAG_NONE }, { ABIL_SIF_MUNA_FORGET_SPELL, "Forget Spell", 5, 0, 0, 8, ABFLAG_NONE }, // Trog { ABIL_TROG_BURN_SPELLBOOKS, "Burn Spellbooks", 0, 0, 10, 0, ABFLAG_NONE }, { ABIL_TROG_BERSERK, "Berserk", 0, 0, 200, 0, ABFLAG_NONE }, { ABIL_TROG_REGEN_MR, "Trog's Hand", 0, 0, 50, generic_cost::range(2, 3), ABFLAG_NONE }, { ABIL_TROG_BROTHERS_IN_ARMS, "Brothers in Arms", 0, 0, 100, generic_cost::range(5, 6), ABFLAG_NONE }, // Elyvilon { ABIL_ELYVILON_DESTROY_WEAPONS, "Destroy Weapons", 0, 0, 0, 0, ABFLAG_NONE }, { ABIL_ELYVILON_LESSER_HEALING_SELF, "Lesser Self-Healing", 1, 0, 100, generic_cost::range(0, 1), ABFLAG_CONF_OK }, { ABIL_ELYVILON_LESSER_HEALING_OTHERS, "Lesser Healing", 1, 0, 100, 0, ABFLAG_CONF_OK }, { ABIL_ELYVILON_PURIFICATION, "Purification", 2, 0, 150, 1, ABFLAG_CONF_OK }, { ABIL_ELYVILON_GREATER_HEALING_SELF, "Greater Self-Healing", 2, 0, 250, 2, ABFLAG_CONF_OK }, { ABIL_ELYVILON_GREATER_HEALING_OTHERS, "Greater Healing", 2, 0, 250, 2, ABFLAG_CONF_OK }, { ABIL_ELYVILON_RESTORATION, "Restoration", 3, 0, 400, 3, ABFLAG_CONF_OK }, { ABIL_ELYVILON_DIVINE_VIGOUR, "Divine Vigour", 0, 0, 600, 6, ABFLAG_CONF_OK }, // Lugonu { ABIL_LUGONU_ABYSS_EXIT, "Depart the Abyss", 1, 0, 150, 10, ABFLAG_NONE }, { ABIL_LUGONU_BEND_SPACE, "Bend Space", 1, 0, 50, 0, ABFLAG_PAIN }, { ABIL_LUGONU_BANISH, "Banish", 4, 0, 200, generic_cost::range(3, 4), ABFLAG_NONE }, { ABIL_LUGONU_CORRUPT, "Corrupt", 7, scaling_cost::fixed(5), 500, generic_cost::range(10, 14), ABFLAG_NONE }, { ABIL_LUGONU_ABYSS_ENTER, "Enter the Abyss", 9, 0, 500, generic_cost::fixed(35), ABFLAG_PAIN }, // Nemelex { ABIL_NEMELEX_DRAW_ONE, "Draw One", 2, 0, 0, 0, ABFLAG_NONE }, { ABIL_NEMELEX_PEEK_TWO, "Peek at Two", 3, 0, 0, 1, ABFLAG_INSTANT }, { ABIL_NEMELEX_TRIPLE_DRAW, "Triple Draw", 2, 0, 100, 2, ABFLAG_NONE }, { ABIL_NEMELEX_MARK_FOUR, "Mark Four", 4, 0, 125, 5, ABFLAG_NONE }, { ABIL_NEMELEX_STACK_FIVE, "Stack Five", 5, 0, 250, 10, ABFLAG_NONE }, // Beogh { ABIL_BEOGH_SMITING, "Smiting", 3, 0, 80, generic_cost::fixed(3), ABFLAG_NONE }, { ABIL_BEOGH_RECALL_ORCISH_FOLLOWERS, "Recall Orcish Followers", 2, 0, 50, 0, ABFLAG_NONE }, // Jiyva { ABIL_JIYVA_CALL_JELLY, "Request Jelly", 2, 0, 20, 1, ABFLAG_NONE }, { ABIL_JIYVA_JELLY_SHIELD, "Jelly Shield", 0, 0, 0, 0, ABFLAG_PIETY }, { ABIL_JIYVA_SLIMIFY, "Slimify", 4, 0, 100, 8, ABFLAG_NONE }, { ABIL_JIYVA_CURE_BAD_MUTATION, "Cure Bad Mutation", 8, 0, 200, 15, ABFLAG_NONE }, // Fedhas { ABIL_FEDHAS_FUNGAL_BLOOM, "Decomposition", 0, 0, 0, 0, ABFLAG_NONE }, { ABIL_FEDHAS_EVOLUTION, "Evolution", 2, 0, 0, 0, ABFLAG_VARIABLE_FRUIT}, { ABIL_FEDHAS_SUNLIGHT, "Sunlight", 2, 0, 0, 0, ABFLAG_NONE}, { ABIL_FEDHAS_PLANT_RING, "Growth", 2, 0, 0, 0, ABFLAG_FRUIT}, { ABIL_FEDHAS_SPAWN_SPORES, "Reproduction", 4, 0, 50, 2, ABFLAG_NONE}, { ABIL_FEDHAS_RAIN, "Rain", 4, 0, 100, 4, ABFLAG_NONE}, // Cheibriados { ABIL_CHEIBRIADOS_PONDEROUSIFY, "Make Ponderous", 2, 0, 0, 0, ABFLAG_NONE }, { ABIL_CHEIBRIADOS_TIME_BEND, "Bend Time", 3, 0, 50, 1, ABFLAG_NONE }, { ABIL_CHEIBRIADOS_SLOUCH, "Slouch", 5, 0, 100, 5, ABFLAG_NONE }, { ABIL_CHEIBRIADOS_TIME_STEP, "Step From Time", 10, 0, 200, 10, ABFLAG_NONE }, { ABIL_HARM_PROTECTION, "Protection From Harm", 0, 0, 0, 0, ABFLAG_NONE }, { ABIL_HARM_PROTECTION_II, "Reliable Protection From Harm", 0, 0, 0, 0, ABFLAG_PIETY }, { ABIL_RENOUNCE_RELIGION, "Renounce Religion", 0, 0, 0, 0, ABFLAG_NONE }, }; const ability_def & get_ability_def(ability_type abil) { for (unsigned int i = 0; i < sizeof(Ability_List) / sizeof(Ability_List[0]); i++) { if (Ability_List[i].ability == abil) return (Ability_List[i]); } return (Ability_List[0]); } bool string_matches_ability_name(const std::string& key) { for (int i = ABIL_SPIT_POISON; i <= ABIL_RENOUNCE_RELIGION; ++i) { const ability_def abil = get_ability_def((ability_type) i); if (abil.ability == ABIL_NON_ABILITY) continue; const std::string name = lowercase_string(ability_name(abil.ability)); if (name.find(key) != std::string::npos) return (true); } return (false); } std::string print_abilities() { std::string text = "\na: "; const std::vector talents = your_talents(false); if (talents.empty()) text += "no special abilities"; else { for (unsigned int i = 0; i < talents.size(); ++i) { if (i) text += ", "; text += ability_name(talents[i].which); } } return text; } const std::string make_cost_description(ability_type ability) { const ability_def& abil = get_ability_def(ability); std::ostringstream ret; if (abil.mp_cost) { ret << abil.mp_cost; if (abil.flags & ABFLAG_PERMANENT_MP) ret << " Permanent"; ret << " MP"; } if (abil.hp_cost) { if (!ret.str().empty()) ret << ", "; ret << abil.hp_cost.cost(you.hp_max); if (abil.flags & ABFLAG_PERMANENT_HP) ret << " Permanent"; ret << " HP"; } if (abil.food_cost && you.is_undead != US_UNDEAD && (you.is_undead != US_SEMI_UNDEAD || you.hunger_state > HS_STARVING)) { if (!ret.str().empty()) ret << ", "; ret << "Food"; // randomised and amount hidden from player } if (abil.piety_cost) { if (!ret.str().empty()) ret << ", "; ret << "Piety"; // randomised and amount hidden from player } if (abil.flags & ABFLAG_BREATH) { if (!ret.str().empty()) ret << ", "; ret << "Breath"; } if (abil.flags & ABFLAG_DELAY) { if (!ret.str().empty()) ret << ", "; ret << "Delay"; } if (abil.flags & ABFLAG_PAIN) { if (!ret.str().empty()) ret << ", "; ret << "Pain"; } if (abil.flags & ABFLAG_PIETY) { if (!ret.str().empty()) ret << ", "; ret << "Piety"; } if (abil.flags & ABFLAG_EXHAUSTION) { if (!ret.str().empty()) ret << ", "; ret << "Exhaustion"; } if (abil.flags & ABFLAG_INSTANT) { if (!ret.str().empty()) ret << ", "; ret << "Instant"; // not really a cost, more of a bonus -bwr } if (abil.flags & ABFLAG_FRUIT) { if (!ret.str().empty()) ret << ", "; ret << "Fruit"; } if (abil.flags & ABFLAG_VARIABLE_FRUIT) { if (!ret.str().empty()) ret << ", "; ret << "Fruit or Piety"; } // If we haven't output anything so far, then the effect has no cost if (ret.str().empty()) ret << "None"; return (ret.str()); } static talent _get_talent(ability_type ability, bool check_confused) { ASSERT(ability != ABIL_NON_ABILITY); talent result; result.which = ability; int failure = 0; bool perfect = false; // is perfect bool invoc = false; if (check_confused) { const ability_def &abil = get_ability_def(result.which); if (you.confused() && !testbits(abil.flags, ABFLAG_CONF_OK)) { result.which = ABIL_NON_ABILITY; return result; } } // Look through the table to see if there's a preference, else // find a new empty slot for this ability. -- bwr const int index = _find_ability_slot( ability ); if (index != -1) result.hotkey = index_to_letter(index); else result.hotkey = 0; // means 'find later on' switch (ability) { // begin spell abilities case ABIL_DELAYED_FIREBALL: case ABIL_MUMMY_RESTORATION: perfect = true; failure = 0; break; // begin species abilities - some are mutagenic, too {dlb} case ABIL_SPIT_POISON: failure = ((you.species == SP_NAGA) ? 20 : 40) - 10 * player_mutation_level(MUT_SPIT_POISON) - you.experience_level; break; case ABIL_BREATHE_FIRE: failure = ((you.species == SP_RED_DRACONIAN) ? 30 : 50) - 10 * player_mutation_level(MUT_BREATHE_FLAMES) - you.experience_level; if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON) failure -= 20; break; case ABIL_BREATHE_FROST: case ABIL_BREATHE_POISON: case ABIL_SPIT_ACID: case ABIL_BREATHE_LIGHTNING: case ABIL_BREATHE_POWER: case ABIL_BREATHE_STICKY_FLAME: failure = 30 - you.experience_level; if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON) failure -= 20; break; case ABIL_BREATHE_STEAM: failure = 20 - you.experience_level; if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON) failure -= 20; break; case ABIL_FLY: // this is for kenku {dlb} failure = 45 - (3 * you.experience_level); break; case ABIL_FLY_II: // this is for draconians {dlb} failure = 45 - (you.experience_level + you.strength); break; case ABIL_TRAN_BAT: failure = 45 - (2 * you.experience_level); break; case ABIL_RECHARGING: // this is for deep dwarves {1KB} failure = 45 - (2 * you.experience_level); break; // end species abilities (some mutagenic) // begin demonic powers {dlb} case ABIL_THROW_FLAME: case ABIL_THROW_FROST: failure = 10 - you.experience_level; break; case ABIL_SUMMON_MINOR_DEMON: failure = 27 - you.experience_level; break; case ABIL_CHANNELING: case ABIL_BOLT_OF_DRAINING: failure = 30 - you.experience_level; break; case ABIL_CONTROL_DEMON: failure = 35 - you.experience_level; break; case ABIL_SUMMON_DEMON: failure = 40 - you.experience_level; break; case ABIL_HELLFIRE: case ABIL_RAISE_DEAD: failure = 50 - you.experience_level; break; case ABIL_TORMENT: failure = 60 - you.experience_level; break; case ABIL_BLINK: // Allowing perfection makes the third level matter much more perfect = true; failure = 48 - (12 * player_mutation_level(MUT_BLINK)) - you.experience_level / 2; break; case ABIL_TELEPORTATION: failure = ((player_mutation_level(MUT_TELEPORT_AT_WILL) > 1) ? 30 : 50) - you.experience_level; break; // end demonic powers {dlb} // begin transformation abilities {dlb} case ABIL_END_TRANSFORMATION: perfect = true; failure = 0; break; // end transformation abilities {dlb} // begin item abilities - some possibly mutagenic {dlb} case ABIL_EVOKE_TURN_INVISIBLE: case ABIL_EVOKE_TELEPORTATION: failure = 60 - 2 * you.skills[SK_EVOCATIONS]; break; case ABIL_EVOKE_TURN_VISIBLE: case ABIL_EVOKE_STOP_LEVITATING: perfect = true; failure = 0; break; case ABIL_EVOKE_LEVITATE: case ABIL_EVOKE_BLINK: failure = 40 - 2 * you.skills[SK_EVOCATIONS]; break; case ABIL_EVOKE_BERSERK: failure = 50 - 2 * you.skills[SK_EVOCATIONS]; if (you.species == SP_TROLL) failure -= 30; else if (player_genus(GENPC_DWARVEN) || you.species == SP_HILL_ORC || player_genus(GENPC_OGRE)) { failure -= 10; } break; // end item abilities - some possibly mutagenic {dlb} // begin invocations {dlb} case ABIL_ELYVILON_PURIFICATION: invoc = true; failure = 20 - (you.piety / 20) - (5 * you.skills[SK_INVOCATIONS]); break; case ABIL_ZIN_RECITE: case ABIL_BEOGH_RECALL_ORCISH_FOLLOWERS: case ABIL_OKAWARU_MIGHT: case ABIL_ELYVILON_LESSER_HEALING_SELF: case ABIL_ELYVILON_LESSER_HEALING_OTHERS: case ABIL_LUGONU_ABYSS_EXIT: case ABIL_JIYVA_CALL_JELLY: case ABIL_FEDHAS_SUNLIGHT: case ABIL_FEDHAS_EVOLUTION: invoc = true; failure = 30 - (you.piety / 20) - (6 * you.skills[SK_INVOCATIONS]); break; // destroying stuff doesn't train anything case ABIL_ELYVILON_DESTROY_WEAPONS: case ABIL_FEDHAS_FUNGAL_BLOOM: invoc = true; failure = 0; break; case ABIL_TROG_BURN_SPELLBOOKS: invoc = true; failure = 0; break; // These three are Trog abilities... Invocations means nothing -- bwr case ABIL_TROG_BERSERK: // piety >= 30 invoc = true; failure = 30 - you.piety; // starts at 0% break; case ABIL_TROG_REGEN_MR: // piety >= 50 invoc = true; failure = 80 - you.piety; // starts at 30% break; case ABIL_TROG_BROTHERS_IN_ARMS: // piety >= 100 invoc = true; failure = 160 - you.piety; // starts at 60% break; case ABIL_YRED_ANIMATE_REMAINS: case ABIL_CHEIBRIADOS_PONDEROUSIFY: invoc = true; failure = 40 - (you.piety / 20) - (3 * you.skills[SK_INVOCATIONS]); break; case ABIL_ZIN_VITALISATION: case ABIL_TSO_DIVINE_SHIELD: case ABIL_BEOGH_SMITING: case ABIL_MAKHLEB_MINOR_DESTRUCTION: case ABIL_SIF_MUNA_FORGET_SPELL: case ABIL_KIKU_RECEIVE_CORPSES: case ABIL_YRED_ANIMATE_DEAD: case ABIL_MAKHLEB_LESSER_SERVANT_OF_MAKHLEB: case ABIL_ELYVILON_GREATER_HEALING_SELF: case ABIL_ELYVILON_GREATER_HEALING_OTHERS: case ABIL_LUGONU_BEND_SPACE: case ABIL_JIYVA_SLIMIFY: case ABIL_FEDHAS_PLANT_RING: invoc = true; failure = 40 - (you.piety / 20) - (5 * you.skills[SK_INVOCATIONS]); break; case ABIL_SIF_MUNA_CHANNEL_ENERGY: invoc = true; failure = 40 - you.intel - you.skills[SK_INVOCATIONS]; break; case ABIL_YRED_RECALL_UNDEAD_SLAVES: case ABIL_CHEIBRIADOS_TIME_BEND: invoc = true; failure = 50 - (you.piety / 20) - (you.skills[SK_INVOCATIONS] * 4); break; case ABIL_LUGONU_BANISH: invoc = true; failure = 60 - (you.piety / 20) - (5 * you.skills[SK_INVOCATIONS]); break; case ABIL_MAKHLEB_MAJOR_DESTRUCTION: case ABIL_FEDHAS_SPAWN_SPORES: case ABIL_YRED_DRAIN_LIFE: case ABIL_CHEIBRIADOS_SLOUCH: invoc = true; failure = 60 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); break; case ABIL_TSO_CLEANSING_FLAME: case ABIL_ELYVILON_RESTORATION: case ABIL_OKAWARU_HASTE: case ABIL_MAKHLEB_GREATER_SERVANT_OF_MAKHLEB: case ABIL_LUGONU_CORRUPT: case ABIL_FEDHAS_RAIN: invoc = true; failure = 70 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); break; case ABIL_ZIN_SANCTUARY: case ABIL_TSO_SUMMON_DIVINE_WARRIOR: case ABIL_YRED_ENSLAVE_SOUL: case ABIL_ELYVILON_DIVINE_VIGOUR: case ABIL_LUGONU_ABYSS_ENTER: case ABIL_JIYVA_CURE_BAD_MUTATION: case ABIL_CHEIBRIADOS_TIME_STEP: invoc = true; failure = 80 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); break; case ABIL_NEMELEX_STACK_FIVE: invoc = true; failure = 80 - (you.piety / 25) - (4 * you.skills[SK_EVOCATIONS]); break; case ABIL_NEMELEX_MARK_FOUR: invoc = true; failure = 70 - (you.piety * 2 / 45) - (9 * you.skills[SK_EVOCATIONS] / 2); break; case ABIL_NEMELEX_TRIPLE_DRAW: invoc = true; failure = 60 - (you.piety / 20) - (5 * you.skills[SK_EVOCATIONS]); break; case ABIL_NEMELEX_PEEK_TWO: invoc = true; failure = 40 - (you.piety / 20) - (5 * you.skills[SK_EVOCATIONS]); break; case ABIL_NEMELEX_DRAW_ONE: invoc = true; perfect = true; // Tactically important to allow perfection failure = 50 - (you.piety / 20) - (5 * you.skills[SK_EVOCATIONS]); break; case ABIL_RENOUNCE_RELIGION: invoc = true; perfect = true; failure = 0; break; // end invocations {dlb} default: failure = -1; break; } // Perfect abilities are things which can go down to a 0% // failure rate (e.g., Renounce Religion.) if (failure <= 0 && !perfect) failure = 1; if (failure > 100) failure = 100; result.fail = failure; result.is_invocation = invoc; return result; } std::vector get_ability_names() { std::vector talents = your_talents(false); std::vector result; for (unsigned int i = 0; i < talents.size(); ++i) result.push_back(get_ability_def(talents[i].which).name); return result; } static void _print_talent_description(const talent& tal) { clrscr(); const std::string& name = get_ability_def(tal.which).name; // XXX: The suffix is necessary to distinguish between similarly // named spells. Yes, this is a hack. std::string lookup = getLongDescription(name + "ability"); if (lookup.empty()) { // Try again without the suffix. lookup = getLongDescription(name); } if (lookup.empty()) // Still nothing found? cprintf("No description found."); else { std::ostringstream data; data << name << "$$" << lookup; print_description(data.str()); } if (getch() == 0) getch(); clrscr(); } bool activate_ability() { if (you.berserk()) { canned_msg(MSG_TOO_BERSERK); return (false); } std::vector talents = your_talents(false); if (talents.empty()) { // Give messages if the character cannot use innate talents right now. // * Vampires can't turn into bats when full of blood. // * Permanent flying (Kenku) cannot be turned off. if (you.species == SP_VAMPIRE && you.experience_level >= 3) mpr("Sorry, you're too full to transform right now."); else if (you.species == SP_KENKU && you.experience_level >= 5 || player_mutation_level(MUT_BIG_WINGS)) { if (you.flight_mode() == FL_LEVITATE) mpr("You can only start flying from the ground."); else if (you.flight_mode() == FL_FLY) mpr("You're already flying!"); } else mpr("Sorry, you're not good enough to have a special ability."); crawl_state.zero_turns_taken(); return (false); } if (you.confused()) { talents = your_talents(true); if (talents.empty()) { mpr("You're too confused!"); crawl_state.zero_turns_taken(); return (false); } } int selected = -1; while (selected < 0) { msg::streams(MSGCH_PROMPT) << "Use which ability? (? or * to list) " << std::endl; const int keyin = get_ch(); if (keyin == '?' || keyin == '*') { selected = choose_ability_menu(talents); if (selected == -1) { canned_msg( MSG_OK ); return (false); } } else if (keyin == ESCAPE || keyin == ' ' || keyin == '\r' || keyin == '\n') { canned_msg( MSG_OK ); return (false); } else if ( isalpha(keyin) ) { // Try to find the hotkey. for (unsigned int i = 0; i < talents.size(); ++i) { if (talents[i].hotkey == keyin) { selected = static_cast(i); break; } } // If we can't, cancel out. if (selected < 0) { mpr("You can't do that."); crawl_state.zero_turns_taken(); return (false); } } } return _activate_talent(talents[selected]); } // Check prerequisites for a number of abilities. // Abort any attempt if these cannot be met, without losing the turn. // TODO: Many more cases need to be added! static bool _check_ability_possible(const ability_def& abil, bool hungerCheck = true) { // Don't insta-starve the player. // (Happens at 100, losing consciousness possible from 500 downward.) if (hungerCheck && you.species != SP_VAMPIRE) { const int expected_hunger = you.hunger - abil.food_cost * 2; dprf("hunger: %d, max. food_cost: %d, expected hunger: %d", you.hunger, abil.food_cost * 2, expected_hunger); // Safety margin for natural hunger, mutations etc. if (expected_hunger <= 150) { mpr("You're too hungry."); return (false); } } switch (abil.ability) { case ABIL_ZIN_RECITE: { const int result = check_recital_audience(); if (result < 0) { mpr("There's no appreciative audience!"); return (false); } else if (result < 1) { mpr("There's no-one here to preach to!"); return (false); } return (true); } case ABIL_ZIN_CURE_ALL_MUTATIONS: return (how_mutated()); case ABIL_ZIN_SANCTUARY: if (env.sanctuary_time) { mpr("There's already a sanctuary in place on this level."); return (false); } return (true); case ABIL_ELYVILON_PURIFICATION: if (!you.disease && !you.rotting && !you.duration[DUR_POISONING] && !you.duration[DUR_CONF] && !you.duration[DUR_SLOW] && !you.duration[DUR_PARALYSIS] && !you.petrified()) { mpr("Nothing ails you!"); return (false); } return (true); case ABIL_ELYVILON_RESTORATION: case ABIL_MUMMY_RESTORATION: if (you.strength == you.max_strength && you.intel == you.max_intel && you.dex == you.max_dex && (abil.ability == ABIL_MUMMY_RESTORATION || !player_rotted())) { mprf("You don't need to restore your stats%s!", abil.ability == ABIL_ELYVILON_RESTORATION ? " or hit points" : ""); return (false); } return (true); case ABIL_LUGONU_ABYSS_EXIT: if (you.level_type != LEVEL_ABYSS) { mpr("You aren't in the Abyss!"); return (false); } return (true); case ABIL_LUGONU_CORRUPT: return (!is_level_incorruptible()); case ABIL_LUGONU_ABYSS_ENTER: if (you.level_type == LEVEL_ABYSS) { mpr("You're already here!"); return (false); } else if (you.level_type == LEVEL_PANDEMONIUM) { mpr("That doesn't work from Pandemonium."); return (false); } return (true); case ABIL_SIF_MUNA_FORGET_SPELL: if (you.spell_no == 0) { mpr("You don't know any spells."); return (false); } return (true); case ABIL_SPIT_POISON: case ABIL_BREATHE_FIRE: case ABIL_BREATHE_FROST: case ABIL_BREATHE_POISON: case ABIL_BREATHE_LIGHTNING: case ABIL_BREATHE_POWER: case ABIL_BREATHE_STICKY_FLAME: case ABIL_BREATHE_STEAM: if (you.duration[DUR_BREATH_WEAPON]) { canned_msg(MSG_CANNOT_DO_YET); return (false); } return (true); case ABIL_EVOKE_BLINK: if (scan_artefacts(ARTP_PREVENT_TELEPORTATION, false)) { return (yesno("You cannot teleport right now. Try anyway?", true, 'n')); } return (true); case ABIL_EVOKE_BERSERK: case ABIL_TROG_BERSERK: if (you.hunger_state < HS_SATIATED) { mpr("You're too hungry to berserk."); return (false); } return (you.can_go_berserk(true) && berserk_check_wielded_weapon()); case ABIL_FLY_II: if (you.duration[DUR_EXHAUSTED]) { mpr("You're too exhausted to fly."); return (false); } else if (you.burden_state != BS_UNENCUMBERED) { mpr("You're carrying too much weight to fly."); return (false); } return (true); case ABIL_TORMENT: if (you.is_undead) { mpr("The unliving cannot use this ability."); return (false); } return (true); case ABIL_EVOKE_TURN_INVISIBLE: // ring, randarts, darkness items if (you.hunger_state < HS_SATIATED) { mpr("You're too hungry to turn invisible."); return (false); } return (true); default: return (true); } } static bool _activate_talent(const talent& tal) { // Doing these would outright kill the player due to stat drain. if (tal.which == ABIL_TRAN_BAT) { if (you.strength <= 5) { mpr("You lack the strength for this transformation.", MSGCH_WARN); crawl_state.zero_turns_taken(); return (false); } } else if (tal.which == ABIL_END_TRANSFORMATION && player_in_bat_form() && you.dex <= 5) { mpr("Turning back with such low dexterity would be fatal!", MSGCH_WARN); more(); crawl_state.zero_turns_taken(); return (false); } if ((tal.which == ABIL_EVOKE_BERSERK || tal.which == ABIL_TROG_BERSERK) && !you.can_go_berserk(true)) { crawl_state.zero_turns_taken(); return (false); } // Some abilities don't need a hunger check. bool hungerCheck = true; switch (tal.which) { case ABIL_RENOUNCE_RELIGION: case ABIL_EVOKE_STOP_LEVITATING: case ABIL_EVOKE_TURN_VISIBLE: case ABIL_END_TRANSFORMATION: case ABIL_DELAYED_FIREBALL: case ABIL_MUMMY_RESTORATION: case ABIL_TRAN_BAT: hungerCheck = false; break; default: break; } if (hungerCheck && you.species != SP_VAMPIRE && you.hunger_state == HS_STARVING) { mpr("You're too hungry."); crawl_state.zero_turns_taken(); return (false); } const ability_def& abil = get_ability_def(tal.which); // Check that we can afford to pay the costs. // Note that mutation shenanigans might leave us with negative MP, // so don't fail in that case if there's no MP cost. if (abil.mp_cost > 0 && !enough_mp(abil.mp_cost, false, !(abil.flags & ABFLAG_PERMANENT_MP))) { crawl_state.zero_turns_taken(); return (false); } if (!enough_hp(abil.hp_cost.cost(you.hp_max), false)) { crawl_state.zero_turns_taken(); return (false); } if (!_check_ability_possible(abil, hungerCheck)) { crawl_state.zero_turns_taken(); return (false); } // No turning back now... {dlb} if (random2avg(100, 3) < tal.fail) { mpr("You fail to use your ability."); you.turn_is_over = true; return (false); } const bool success = _do_ability(abil); if (success) _pay_ability_costs(abil); return (success); } int _calc_breath_ability_range(ability_type ability) { // Following monster draconian abilities. switch (ability) { case ABIL_BREATHE_FIRE: return 6; case ABIL_BREATHE_FROST: return 6; case ABIL_BREATHE_POISON: return 7; case ABIL_BREATHE_LIGHTNING: return 8; case ABIL_SPIT_ACID: return 8; case ABIL_BREATHE_POWER: return 8; case ABIL_BREATHE_STICKY_FLAME: return 5; case ABIL_BREATHE_STEAM: return 7; default: ASSERT(!"Bad breath type!"); break; } return (-2); } static bool _do_ability(const ability_def& abil) { int power; dist abild; bolt beam; dist spd; // Note: the costs will not be applied until after this switch // statement... it's assumed that only failures have returned! - bwr switch (abil.ability) { case ABIL_MUMMY_RESTORATION: { mpr("You infuse your body with magical energy."); bool did_restore = restore_stat(STAT_ALL, 0, false); const int oldhpmax = you.hp_max; unrot_hp( 100 ); if (you.hp_max > oldhpmax) did_restore = true; // If nothing happened, don't take one max MP, don't use a turn. if (!did_restore) { canned_msg(MSG_NOTHING_HAPPENS); return (false); } break; } case ABIL_RECHARGING: if (!recharge_wand(-1)) return (false); // fail message is already given break; case ABIL_DELAYED_FIREBALL: { // Note: Power level of ball calculated at release. - bwr const int pow = calc_spell_power(SPELL_DELAYED_FIREBALL, true); const int fake_range = spell_range(SPELL_FIREBALL, pow, false); if (!spell_direction(spd, beam, DIR_NONE, TARG_HOSTILE, fake_range)) return (false); beam.range = spell_range(SPELL_FIREBALL, pow, true); if (!fireball(pow, beam)) return (false); // Only one allowed, since this is instantaneous. - bwr you.attribute[ATTR_DELAYED_FIREBALL] = 0; break; } case ABIL_SPIT_POISON: // Naga + spit poison mutation { const int pow = you.experience_level + player_mutation_level(MUT_SPIT_POISON) * 5 + (you.species == SP_NAGA) * 10; beam.range = 6; // following Venom Bolt if (!spell_direction(abild, beam) || !player_tracer(ZAP_SPIT_POISON, pow, beam)) { return (false); } else { zapping(ZAP_SPIT_POISON, pow, beam); you.set_duration(DUR_BREATH_WEAPON, 3 + random2(5) ); } break; } case ABIL_EVOKE_TELEPORTATION: // ring of teleportation case ABIL_TELEPORTATION: // teleport mut if (player_mutation_level(MUT_TELEPORT_AT_WILL) == 3) you_teleport_now(true, true); // instant and to new area of Abyss else you_teleport(); if (abil.ability == ABIL_EVOKE_TELEPORTATION) exercise(SK_EVOCATIONS, 1); break; case ABIL_BREATHE_FIRE: case ABIL_BREATHE_FROST: case ABIL_BREATHE_POISON: case ABIL_BREATHE_LIGHTNING: case ABIL_SPIT_ACID: case ABIL_BREATHE_POWER: case ABIL_BREATHE_STICKY_FLAME: case ABIL_BREATHE_STEAM: beam.range = _calc_breath_ability_range(abil.ability); if (!spell_direction(abild, beam)) return (false); switch (abil.ability) { case ABIL_BREATHE_FIRE: power = you.experience_level; power += player_mutation_level(MUT_BREATHE_FLAMES) * 4; if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON) power += 12; snprintf(info, INFO_SIZE, "You breathe fire%c", (power < 15) ? '.':'!'); if (!zapping(ZAP_BREATHE_FIRE, power, beam, true, info)) return (false); break; case ABIL_BREATHE_FROST: if (!zapping(ZAP_BREATHE_FROST, you.experience_level, beam, true, "You exhale a wave of freezing cold.")) { return (false); } break; case ABIL_BREATHE_POISON: if (!zapping(ZAP_BREATHE_POISON, you.experience_level, beam, true, "You exhale a blast of poison gas.")) { return (false); } break; case ABIL_BREATHE_LIGHTNING: if (!zapping(ZAP_LIGHTNING, (you.experience_level * 2), beam, true, "You spit a bolt of lightning.")) { return (false); } break; case ABIL_SPIT_ACID: if (!zapping(ZAP_BREATHE_ACID, you.experience_level, beam, true, "You spit acid.")) { return (false); } break; case ABIL_BREATHE_POWER: if (!zapping(ZAP_BREATHE_POWER, you.experience_level, beam, true, "You spit a bolt of incandescent energy.")) { return (false); } break; case ABIL_BREATHE_STICKY_FLAME: if (!zapping(ZAP_STICKY_FLAME, you.experience_level, beam, true, "You spit a glob of burning liquid.")) { return (false); } break; case ABIL_BREATHE_STEAM: if (!zapping(ZAP_BREATHE_STEAM, you.experience_level, beam, true, "You exhale a blast of scalding steam.")) { return (false); } break; default: break; } if (abil.ability != ABIL_SPIT_ACID) { you.increase_duration(DUR_BREATH_WEAPON, 3 + random2(4) + random2(30 - you.experience_level) / 2); if (abil.ability == ABIL_BREATHE_STEAM) you.duration[DUR_BREATH_WEAPON] /= 2; } break; case ABIL_EVOKE_BLINK: // randarts case ABIL_BLINK: // mutation random_blink(true); if (abil.ability == ABIL_EVOKE_BLINK) exercise(SK_EVOCATIONS, 1); break; case ABIL_EVOKE_BERSERK: // amulet of rage, randarts // Only exercise if berserk succeeds. // Because of the test above, this should always happen, // but I'm leaving it in - haranp if (go_berserk(true)) exercise(SK_EVOCATIONS, 1); break; // Fly (kenku) - eventually becomes permanent (see acr.cc). case ABIL_FLY: cast_fly(you.experience_level * 4); if (you.experience_level > 14) mpr("You feel very comfortable in the air."); break; // Fly (Draconians, or anything else with wings). case ABIL_FLY_II: cast_fly(you.experience_level * 2); break; // DEMONIC POWERS: case ABIL_SUMMON_MINOR_DEMON: summon_lesser_demon(you.experience_level * 4); break; case ABIL_SUMMON_DEMON: summon_common_demon(you.experience_level * 4); break; case ABIL_HELLFIRE: if (your_spells(SPELL_HELLFIRE_BURST, you.experience_level * 5, false) == SPRET_ABORT) return (false); break; case ABIL_TORMENT: torment(TORMENT_GENERIC, you.pos()); break; case ABIL_RAISE_DEAD: animate_dead(&you, you.experience_level * 5, BEH_FRIENDLY, MHITYOU, &you); break; case ABIL_CONTROL_DEMON: beam.range = LOS_RADIUS; if (!spell_direction(abild, beam) || !zapping(ZAP_CONTROL_DEMON, you.experience_level * 5, beam, true)) { return (false); } break; case ABIL_CHANNELING: mpr("You channel some magical energy."); inc_mp(1 + random2(5), false); break; case ABIL_THROW_FLAME: case ABIL_THROW_FROST: // Taking ranges from the equivalent spells. beam.range = (abil.ability == ABIL_THROW_FLAME ? 7 : 8); if (!spell_direction(abild, beam)) return (false); if (!zapping((abil.ability == ABIL_THROW_FLAME ? ZAP_FLAME : ZAP_FROST), you.experience_level * 3, beam, true)) { return (false); } break; case ABIL_BOLT_OF_DRAINING: // Taking range from Bolt of Draining. beam.range = 6; if (!spell_direction(abild, beam)) return (false); if (!zapping(ZAP_NEGATIVE_ENERGY, you.experience_level * 6, beam, true)) return (false); break; case ABIL_EVOKE_TURN_INVISIBLE: // ring, randarts, darkness items potion_effect(POT_INVISIBILITY, 2 * you.skills[SK_EVOCATIONS] + 5); contaminate_player( 1 + random2(3), true ); exercise(SK_EVOCATIONS, 1); break; case ABIL_EVOKE_TURN_VISIBLE: mpr("You feel less transparent."); you.duration[DUR_INVIS] = 1; break; case ABIL_EVOKE_LEVITATE: // ring, boots, randarts potion_effect(POT_LEVITATION, 2 * you.skills[SK_EVOCATIONS] + 30); exercise(SK_EVOCATIONS, 1); break; case ABIL_EVOKE_STOP_LEVITATING: mpr("You feel heavy."); you.duration[DUR_LEVITATION] = 1; break; case ABIL_END_TRANSFORMATION: mpr("You feel almost normal."); you.set_duration(DUR_TRANSFORMATION, 2); break; // INVOCATIONS: case ABIL_ZIN_SUSTENANCE: // Activated via prayer elsewhere. break; case ABIL_ZIN_RECITE: { // up to (60 + 40)/2 = 50 const int pow = (2*skill_bump(SK_INVOCATIONS) + you.piety / 5) / 2; start_delay(DELAY_RECITE, 3, pow, you.hp); exercise(SK_INVOCATIONS, 2); break; } case ABIL_ZIN_VITALISATION: if (cast_vitalisation()) exercise(SK_INVOCATIONS, (coinflip() ? 3 : 2)); break; case ABIL_ZIN_SANCTUARY: if (cast_sanctuary(you.skills[SK_INVOCATIONS] * 4)) exercise(SK_INVOCATIONS, 5 + random2(8)); break; case ABIL_ZIN_CURE_ALL_MUTATIONS: zin_remove_all_mutations(); break; case ABIL_TSO_DIVINE_SHIELD: cast_divine_shield(); exercise(SK_INVOCATIONS, (coinflip() ? 3 : 2)); break; case ABIL_TSO_CLEANSING_FLAME: cleansing_flame(10 + (you.skills[SK_INVOCATIONS] * 7) / 6, CLEANSING_FLAME_INVOCATION, you.pos(), &you); exercise(SK_INVOCATIONS, 3 + random2(6)); break; case ABIL_TSO_SUMMON_DIVINE_WARRIOR: summon_holy_warrior(you.skills[SK_INVOCATIONS] * 4, GOD_SHINING_ONE); exercise(SK_INVOCATIONS, 8 + random2(10)); break; case ABIL_KIKU_RECEIVE_CORPSES: receive_corpses(you.skills[SK_INVOCATIONS] * 4, you.pos()); exercise(SK_INVOCATIONS, (coinflip() ? 3 : 2)); break; case ABIL_YRED_INJURY_MIRROR: // Activated via prayer elsewhere. break; case ABIL_YRED_ANIMATE_REMAINS: mpr("You attempt to give life to the dead..."); if (animate_remains(you.pos(), CORPSE_BODY, BEH_FRIENDLY, MHITYOU, &you, "", GOD_YREDELEMNUL) < 0) { mpr("There are no remains here to animate!"); } exercise(SK_INVOCATIONS, 2 + random2(4)); break; case ABIL_YRED_RECALL_UNDEAD_SLAVES: recall(1); exercise(SK_INVOCATIONS, 1); break; case ABIL_YRED_ANIMATE_DEAD: mpr("You call on the dead to walk for you..."); animate_dead(&you, 1 + you.skills[SK_INVOCATIONS], BEH_FRIENDLY, MHITYOU, &you, "", GOD_YREDELEMNUL); exercise(SK_INVOCATIONS, 2 + random2(4)); break; case ABIL_YRED_DRAIN_LIFE: drain_life(you.skills[SK_INVOCATIONS]); exercise(SK_INVOCATIONS, 2 + random2(4)); break; case ABIL_YRED_ENSLAVE_SOUL: { god_acting gdact; beam.range = LOS_RADIUS; if (!spell_direction(spd, beam)) return (false); if (!zapping(ZAP_ENSLAVE_SOUL, you.skills[SK_INVOCATIONS] * 4, beam, true)) { return (false); } exercise(SK_INVOCATIONS, 8 + random2(10)); break; } case ABIL_SIF_MUNA_CHANNEL_ENERGY: mpr("You channel some magical energy."); inc_mp(1 + random2(you.skills[SK_INVOCATIONS] / 4 + 2), false); exercise(SK_INVOCATIONS, 1 + random2(3)); break; case ABIL_OKAWARU_MIGHT: potion_effect(POT_MIGHT, you.skills[SK_INVOCATIONS] * 8); exercise(SK_INVOCATIONS, 1 + random2(3)); break; case ABIL_OKAWARU_HASTE: potion_effect(POT_SPEED, you.skills[SK_INVOCATIONS] * 8); exercise(SK_INVOCATIONS, 3 + random2(7)); break; case ABIL_MAKHLEB_MINOR_DESTRUCTION: if (!spell_direction(spd, beam)) return (false); power = you.skills[SK_INVOCATIONS] + random2(1 + you.skills[SK_INVOCATIONS]) + random2(1 + you.skills[SK_INVOCATIONS]); // Since the actual beam is random, check with BEAM_MMISSILE and the // highest range possible (electricity). if (!player_tracer(ZAP_DEBUGGING_RAY, power, beam, 13)) return (false); switch (random2(5)) { case 0: beam.range = 7; zapping(ZAP_FLAME, power, beam); break; case 1: beam.range = 8; zapping(ZAP_PAIN, power, beam); break; case 2: beam.range = 5; zapping(ZAP_STONE_ARROW, power, beam); break; case 3: beam.range = 13; zapping(ZAP_ELECTRICITY, power, beam); break; case 4: beam.range = 8; zapping(ZAP_BREATHE_ACID, power/2, beam); break; } exercise(SK_INVOCATIONS, 1); break; case ABIL_MAKHLEB_LESSER_SERVANT_OF_MAKHLEB: summon_demon_type(static_cast(MONS_NEQOXEC + random2(5)), 20 + you.skills[SK_INVOCATIONS] * 3, GOD_MAKHLEB); exercise(SK_INVOCATIONS, 2 + random2(3)); break; case ABIL_MAKHLEB_MAJOR_DESTRUCTION: if (!spell_direction(spd, beam)) return (false); power = you.skills[SK_INVOCATIONS] * 3 + random2( 1 + you.skills[SK_INVOCATIONS] ) + random2( 1 + you.skills[SK_INVOCATIONS] ); // Since the actual beam is random, check with BEAM_MMISSILE and the // highest range possible (orb of electricity). if (!player_tracer(ZAP_DEBUGGING_RAY, power, beam, 20)) return (false); { zap_type ztype = ZAP_DEBUGGING_RAY; switch (random2(7)) { case 0: beam.range = 6; ztype = ZAP_FIRE; break; case 1: beam.range = 6; ztype = ZAP_FIREBALL; break; case 2: beam.range = 10; ztype = ZAP_LIGHTNING; break; case 3: beam.range = 5; ztype = ZAP_STICKY_FLAME; break; case 4: beam.range = 5; ztype = ZAP_IRON_SHOT; break; case 5: beam.range = 6; ztype = ZAP_NEGATIVE_ENERGY; break; case 6: beam.range = 20; ztype = ZAP_ORB_OF_ELECTRICITY; break; } zapping(ztype, power, beam); } exercise(SK_INVOCATIONS, 3 + random2(5)); break; case ABIL_MAKHLEB_GREATER_SERVANT_OF_MAKHLEB: summon_demon_type(static_cast(MONS_EXECUTIONER + random2(5)), 20 + you.skills[SK_INVOCATIONS] * 3, GOD_MAKHLEB); exercise(SK_INVOCATIONS, 6 + random2(6)); break; case ABIL_TROG_BURN_SPELLBOOKS: if (!trog_burn_spellbooks()) return (false); break; case ABIL_TROG_BERSERK: // Trog abilities don't use or train invocations. go_berserk(true); break; case ABIL_TROG_REGEN_MR: // Trog abilities don't use or train invocations. cast_regen(you.piety / 2, true); break; case ABIL_TROG_BROTHERS_IN_ARMS: // Trog abilities don't use or train invocations. summon_berserker(you.piety + random2(you.piety/4) - random2(you.piety/4), GOD_TROG); break; case ABIL_SIF_MUNA_FORGET_SPELL: if (!cast_selective_amnesia(true)) return (false); break; case ABIL_ELYVILON_DESTROY_WEAPONS: if (!ely_destroy_weapons()) return (false); break; case ABIL_ELYVILON_LESSER_HEALING_SELF: case ABIL_ELYVILON_LESSER_HEALING_OTHERS: { const bool self = (abil.ability == ABIL_ELYVILON_LESSER_HEALING_SELF); if (cast_healing(3 + (you.skills[SK_INVOCATIONS] / 6), true, self ? you.pos() : coord_def(0, 0), !self, self ? TARG_NUM_MODES : TARG_HOSTILE) < 0) { return (false); } exercise(SK_INVOCATIONS, 1); break; } case ABIL_ELYVILON_PURIFICATION: purification(); exercise(SK_INVOCATIONS, 2 + random2(3)); break; case ABIL_ELYVILON_GREATER_HEALING_SELF: case ABIL_ELYVILON_GREATER_HEALING_OTHERS: { const bool self = (abil.ability == ABIL_ELYVILON_GREATER_HEALING_SELF); if (cast_healing(10 + (you.skills[SK_INVOCATIONS] / 3), true, self ? you.pos() : coord_def(0, 0), !self, self ? TARG_NUM_MODES : TARG_HOSTILE) < 0) { return (false); } exercise(SK_INVOCATIONS, 3 + random2(5)); break; } case ABIL_ELYVILON_RESTORATION: restore_stat(STAT_ALL, 0, false); unrot_hp(100); exercise(SK_INVOCATIONS, 4 + random2(6)); break; case ABIL_ELYVILON_DIVINE_VIGOUR: if (!cast_divine_vigour()) return (false); exercise(SK_INVOCATIONS, 6 + random2(10)); break; case ABIL_LUGONU_ABYSS_EXIT: banished(DNGN_EXIT_ABYSS); exercise(SK_INVOCATIONS, 8 + random2(10)); break; case ABIL_LUGONU_BEND_SPACE: lugonu_bends_space(); exercise(SK_INVOCATIONS, 2 + random2(3)); break; case ABIL_LUGONU_BANISH: beam.range = LOS_RADIUS; if (!spell_direction(spd, beam)) return (false); if (beam.target == you.pos()) { mpr("You cannot banish yourself!"); return (false); } if (!zapping(ZAP_BANISHMENT, 16 + you.skills[SK_INVOCATIONS] * 8, beam, true)) { return (false); } exercise(SK_INVOCATIONS, 3 + random2(5)); break; case ABIL_LUGONU_CORRUPT: if (!lugonu_corrupt_level(300 + you.skills[SK_INVOCATIONS] * 15)) return (false); exercise(SK_INVOCATIONS, 5 + random2(5)); break; case ABIL_LUGONU_ABYSS_ENTER: { // Move permanent hp/mp loss from leaving to entering the Abyss. (jpeg) const int maxloss = std::max(2, div_rand_round(you.hp_max, 30)); // Lose permanent HP dec_max_hp(random_range(1, maxloss)); // Paranoia. if (you.hp_max < 1) you.hp_max = 1; // Deflate HP. set_hp( 1 + random2(you.hp), false ); // Lose 1d2 permanent MP. rot_mp(coinflip() ? 2 : 1); // Deflate MP. if (you.magic_points) set_mp(random2(you.magic_points), false); bool note_status = notes_are_active(); activate_notes(false); // This banishment shouldn't be noted. banished(DNGN_ENTER_ABYSS); activate_notes(note_status); break; } case ABIL_NEMELEX_DRAW_ONE: if (!choose_deck_and_draw()) return (false); exercise(SK_EVOCATIONS, 1 + random2(2)); break; case ABIL_NEMELEX_PEEK_TWO: if (!deck_peek()) return (false); exercise(SK_EVOCATIONS, 2 + random2(2)); break; case ABIL_NEMELEX_TRIPLE_DRAW: if (!deck_triple_draw()) return (false); exercise(SK_EVOCATIONS, 3 + random2(3)); break; case ABIL_NEMELEX_MARK_FOUR: if (!deck_mark()) return (false); exercise(SK_EVOCATIONS, 4 + random2(4)); break; case ABIL_NEMELEX_STACK_FIVE: if (!deck_stack()) return (false); exercise(SK_EVOCATIONS, 5 + random2(5)); break; case ABIL_BEOGH_SMITING: if (your_spells(SPELL_SMITING, (2 + skill_bump(SK_INVOCATIONS)) * 6, false) == SPRET_ABORT) { return (false); } exercise(SK_INVOCATIONS, (coinflip() ? 3 : 2)); break; case ABIL_BEOGH_RECALL_ORCISH_FOLLOWERS: recall(2); exercise(SK_INVOCATIONS, 1); break; case ABIL_FEDHAS_FUNGAL_BLOOM: { int count = fungal_bloom(); if (!count) { canned_msg(MSG_NOTHING_HAPPENS); return (false); } simple_god_message(" appreciates your contribution to the " "ecosystem.", GOD_FEDHAS); // We are following the blood god sacrifice piety gain model, given as: // if (random2(level + 10) > 5) // piety_change = 1; // where level = 10 // so the chance of gaining 1 piety from a corpse sacrifice to a blood // god is (14/20 == 70/100) // // Doubling the expected value per sacrifice to approximate the // extra piety gain blood god worshipers get for the initial kill. // -cao int piety_gain = binomial_generator(count*2, 70); gain_piety(piety_gain); break; } case ABIL_FEDHAS_SUNLIGHT: sunlight(); exercise(SK_INVOCATIONS, 2 + random2(3)); break; case ABIL_FEDHAS_PLANT_RING: if (!plant_ring_from_fruit()) { canned_msg(MSG_NOTHING_HAPPENS); return (false); } exercise(SK_INVOCATIONS, 2 + random2(3)); break; case ABIL_FEDHAS_RAIN: if (!rain(you.pos())) { canned_msg(MSG_NOTHING_HAPPENS); return (false); } exercise(SK_INVOCATIONS, 2 + random2(3)); break; case ABIL_FEDHAS_SPAWN_SPORES: if (!corpse_spores()) { canned_msg(MSG_NOTHING_HAPPENS); return (false); } exercise(SK_INVOCATIONS, 2 + random2(3)); break; case ABIL_FEDHAS_EVOLUTION: if (!evolve_flora()) { return (false); } exercise(SK_INVOCATIONS, 2 + random2(3)); break; case ABIL_TRAN_BAT: if (!transform(100, TRAN_BAT)) { crawl_state.zero_turns_taken(); return (false); } break; case ABIL_JIYVA_CALL_JELLY: { mgen_data mg(MONS_JELLY, BEH_STRICT_NEUTRAL, 0, 0, 0, you.pos(), MHITNOT, 0, GOD_JIYVA); mg.non_actor_summoner = "Jiyva"; if (create_monster(mg) == -1) return (false); exercise(SK_INVOCATIONS, 1 + random2(3)); break; } case ABIL_JIYVA_JELLY_SHIELD: // Activated via prayer elsewhere. break; case ABIL_JIYVA_SLIMIFY: { const item_def* const weapon = you.weapon(); const std::string msg = (weapon) ? weapon->name(DESC_NOCAP_YOUR) : ("your " + you.hand_name(true)); mprf(MSGCH_DURATION, "A thick mucus forms on %s.", msg.c_str()); you.increase_duration(DUR_SLIMIFY, you.skills[SK_INVOCATIONS] * 3 / 2 + 3, 100); exercise(SK_INVOCATIONS, 3 + random2(5)); break; } case ABIL_JIYVA_CURE_BAD_MUTATION: if (jiyva_remove_bad_mutation()) exercise(SK_INVOCATIONS, 5 + random2(5)); break; case ABIL_HARM_PROTECTION: case ABIL_HARM_PROTECTION_II: // Activated via prayer elsewhere. break; case ABIL_CHEIBRIADOS_PONDEROUSIFY: if (!ponderousify_armour()) return (false); break; case ABIL_CHEIBRIADOS_TIME_STEP: cheibriados_time_step(you.skills[SK_INVOCATIONS]*you.piety/10); exercise(SK_INVOCATIONS, 5 + random2(5)); break; case ABIL_CHEIBRIADOS_TIME_BEND: cheibriados_time_bend(16 + you.skills[SK_INVOCATIONS] * 8); exercise(SK_INVOCATIONS, 2 + random2(3)); break; case ABIL_CHEIBRIADOS_SLOUCH: mpr("You can feel time thicken."); dprf("your speed is %d", player_movement_speed()); exercise(SK_INVOCATIONS, 4 + random2(4)); cheibriados_slouch(0); break; case ABIL_RENOUNCE_RELIGION: if (yesno("Really renounce your faith, foregoing its fabulous benefits?", false, 'n') && yesno("Are you sure you won't change your mind later?", false, 'n')) { excommunication(); } else canned_msg(MSG_OK); break; case ABIL_NON_ABILITY: mpr("Sorry, you can't do that."); break; } return (true); } static void _pay_ability_costs(const ability_def& abil) { // currently only delayed fireball is instantaneous -- bwr you.turn_is_over = !(abil.flags & ABFLAG_INSTANT); const int food_cost = abil.food_cost + random2avg(abil.food_cost, 2); const int piety_cost = abil.piety_cost.cost(); const int hp_cost = abil.hp_cost.cost(you.hp_max); dprf("Cost: mp=%d; hp=%d; food=%d; piety=%d", abil.mp_cost, hp_cost, food_cost, piety_cost ); if (abil.mp_cost) { dec_mp( abil.mp_cost ); if (abil.flags & ABFLAG_PERMANENT_MP) rot_mp(1); } if (abil.hp_cost) { dec_hp( hp_cost, false ); if (abil.flags & ABFLAG_PERMANENT_HP) rot_hp(1); } if (food_cost) make_hungry( food_cost, false, true ); if (piety_cost) lose_piety( piety_cost ); } int choose_ability_menu(const std::vector& talents) { Menu abil_menu(MF_SINGLESELECT | MF_ANYPRINTABLE, "ability"); abil_menu.set_highlighter(NULL); abil_menu.set_title( new MenuEntry(" Ability - do what? " "Cost Success")); abil_menu.set_title( new MenuEntry(" Ability - describe what? " "Cost Success"), false); abil_menu.set_flags(MF_SINGLESELECT | MF_ANYPRINTABLE | MF_ALWAYS_SHOW_MORE); if (Tutorial.tutorial_left) { // XXX: This could be buggy if you manage to pick up lots and // lots of abilities during the tutorial. abil_menu.set_more(tut_abilities_info()); } else { abil_menu.set_more(formatted_string::parse_string( "Press '!' or '?' to toggle " "between ability selection and description.")); } abil_menu.action_cycle = Menu::CYCLE_TOGGLE; abil_menu.menu_action = Menu::ACT_EXECUTE; int numbers[52]; for (int i = 0; i < 52; ++i) numbers[i] = i; bool found_invocations = false; // First add all non-invocations. for (unsigned int i = 0; i < talents.size(); ++i) { if (talents[i].is_invocation) found_invocations = true; else { MenuEntry* me = new MenuEntry(_describe_talent(talents[i]), MEL_ITEM, 1, talents[i].hotkey); me->data = &numbers[i]; abil_menu.add_entry(me); } } if (found_invocations) { abil_menu.add_entry(new MenuEntry(" Invocations - ", MEL_SUBTITLE)); for (unsigned int i = 0; i < talents.size(); ++i) { if (talents[i].is_invocation) { MenuEntry* me = new MenuEntry(_describe_talent(talents[i]), MEL_ITEM, 1, talents[i].hotkey); me->data = &numbers[i]; abil_menu.add_entry(me); } } } while (true) { std::vector sel = abil_menu.show(false); if (!crawl_state.doing_prev_cmd_again) redraw_screen(); if (sel.empty()) return -1; ASSERT(sel.size() == 1); ASSERT(sel[0]->hotkeys.size() == 1); int selected = *(static_cast(sel[0]->data)); if (abil_menu.menu_action == Menu::ACT_EXAMINE) _print_talent_description(talents[selected]); else return (*(static_cast(sel[0]->data))); } } const char* ability_name(ability_type ability) { return get_ability_def(ability).name; } static std::string _describe_talent(const talent& tal) { ASSERT( tal.which != ABIL_NON_ABILITY ); std::ostringstream desc; desc << std::left << std::setw(32) << ability_name(tal.which) << std::setw(24) << make_cost_description(tal.which) << std::setw(10) << failure_rate_to_string(tal.fail); return desc.str(); } static void _add_talent(std::vector& vec, const ability_type ability, bool check_confused) { const talent t = _get_talent(ability, check_confused); if (t.which != ABIL_NON_ABILITY) vec.push_back(t); } std::vector your_talents(bool check_confused) { std::vector talents; // Species-based abilities. if (you.species == SP_MUMMY && you.experience_level >= 13) _add_talent(talents, ABIL_MUMMY_RESTORATION, check_confused); if (you.species == SP_DEEP_DWARF) _add_talent(talents, ABIL_RECHARGING, check_confused); // Spit Poison. Nontransformed nagas can upgrade to Breathe Poison. // Transformed nagas, or non-nagas, can only get Spit Poison. if (you.species == SP_NAGA && (!transform_changed_physiology() || you.attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER)) { _add_talent(talents, player_mutation_level(MUT_BREATHE_POISON) ? ABIL_BREATHE_POISON : ABIL_SPIT_POISON, check_confused); } else if (player_mutation_level(MUT_SPIT_POISON) || player_mutation_level(MUT_BREATHE_POISON)) { _add_talent(talents, ABIL_SPIT_POISON, check_confused); } if (player_genus(GENPC_DRACONIAN) && you.experience_level >= 7) { ability_type ability = ABIL_NON_ABILITY; switch (you.species) { case SP_GREEN_DRACONIAN: ability = ABIL_BREATHE_POISON; break; case SP_RED_DRACONIAN: ability = ABIL_BREATHE_FIRE; break; case SP_WHITE_DRACONIAN: ability = ABIL_BREATHE_FROST; break; case SP_YELLOW_DRACONIAN: ability = ABIL_SPIT_ACID; break; case SP_BLACK_DRACONIAN: ability = ABIL_BREATHE_LIGHTNING; break; case SP_PURPLE_DRACONIAN: ability = ABIL_BREATHE_POWER; break; case SP_PALE_DRACONIAN: ability = ABIL_BREATHE_STEAM; break; case SP_MOTTLED_DRACONIAN: ability = ABIL_BREATHE_STICKY_FLAME; break; default: break; } // Draconians don't maintain their original breath weapons // if shapechanged, but green draconians do get spit poison // in spider form. if (transform_changed_physiology()) { if (you.species == SP_GREEN_DRACONIAN && you.attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER) { ability = ABIL_SPIT_POISON; // spit, not breath } else ability = ABIL_NON_ABILITY; } if (ability != ABIL_NON_ABILITY) _add_talent(talents, ability, check_confused); } if (you.species == SP_VAMPIRE && you.experience_level >= 3 && you.hunger_state <= HS_SATIATED && you.attribute[ATTR_TRANSFORMATION] != TRAN_BAT) { _add_talent(talents, ABIL_TRAN_BAT, check_confused); } if (!you.airborne() && !transform_changed_physiology()) { // Kenku can fly, but only from the ground // (until level 15, when it becomes permanent until revoked). // jmf: "upgrade" for draconians -- expensive flight if (you.species == SP_KENKU && you.experience_level >= 5) _add_talent(talents, ABIL_FLY, check_confused); else if (player_genus(GENPC_DRACONIAN) && player_mutation_level(MUT_BIG_WINGS)) { _add_talent(talents, ABIL_FLY_II, check_confused); } } // Mutations. if (player_mutation_level(MUT_SUMMON_MINOR_DEMONS)) _add_talent(talents, ABIL_SUMMON_MINOR_DEMON, check_confused); if (player_mutation_level(MUT_SUMMON_DEMONS)) _add_talent(talents, ABIL_SUMMON_DEMON, check_confused); if (player_mutation_level(MUT_HURL_HELLFIRE)) _add_talent(talents, ABIL_HELLFIRE, check_confused); if (player_mutation_level(MUT_CALL_TORMENT)) _add_talent(talents, ABIL_TORMENT, check_confused); if (player_mutation_level(MUT_RAISE_DEAD)) _add_talent(talents, ABIL_RAISE_DEAD, check_confused); if (player_mutation_level(MUT_CONTROL_DEMONS)) _add_talent(talents, ABIL_CONTROL_DEMON, check_confused); if (player_mutation_level(MUT_CHANNEL_HELL)) _add_talent(talents, ABIL_CHANNELING, check_confused); if (player_mutation_level(MUT_THROW_FLAMES)) _add_talent(talents, ABIL_THROW_FLAME, check_confused); if (player_mutation_level(MUT_THROW_FROST)) _add_talent(talents, ABIL_THROW_FROST, check_confused); if (player_mutation_level(MUT_SMITE)) _add_talent(talents, ABIL_BOLT_OF_DRAINING, check_confused); if (you.duration[DUR_TRANSFORMATION] && !you.transform_uncancellable) { _add_talent(talents, ABIL_END_TRANSFORMATION, check_confused); } if (player_mutation_level(MUT_BLINK)) _add_talent(talents, ABIL_BLINK, check_confused); if (player_mutation_level(MUT_TELEPORT_AT_WILL)) _add_talent(talents, ABIL_TELEPORTATION, check_confused); // Religious abilities. if (you.religion == GOD_ELYVILON) _add_talent(talents, ABIL_ELYVILON_DESTROY_WEAPONS, check_confused); else if (you.religion == GOD_TROG) _add_talent(talents, ABIL_TROG_BURN_SPELLBOOKS, check_confused); else if (you.religion == GOD_FEDHAS) _add_talent(talents, ABIL_FEDHAS_FUNGAL_BLOOM, check_confused); else if (you.religion == GOD_CHEIBRIADOS) _add_talent(talents, ABIL_CHEIBRIADOS_PONDEROUSIFY, check_confused); // Gods take abilities away until penance completed. -- bwr // God abilities generally don't work while silenced (they require // invoking the god), but Nemelex is an exception. if (!player_under_penance() && (!silenced(you.pos()) || you.religion == GOD_NEMELEX_XOBEH)) { for (int i = 0; i < MAX_GOD_ABILITIES; ++i) { if (you.piety >= piety_breakpoint(i)) { const ability_type abil = god_abilities[you.religion][i]; if (abil != ABIL_NON_ABILITY) { _add_talent(talents, abil, check_confused); if (abil == ABIL_ELYVILON_LESSER_HEALING_OTHERS) { _add_talent(talents, ABIL_ELYVILON_LESSER_HEALING_SELF, check_confused); } else if (abil == ABIL_ELYVILON_GREATER_HEALING_OTHERS) { _add_talent(talents, ABIL_ELYVILON_GREATER_HEALING_SELF, check_confused); } } } } if (you.religion == GOD_ZIN && !you.num_gifts[GOD_ZIN] && you.piety > 160) { _add_talent(talents, ABIL_ZIN_CURE_ALL_MUTATIONS, check_confused); } } // And finally, the ability to opt-out of your faith {dlb}: if (you.religion != GOD_NO_GOD && !silenced( you.pos() )) _add_talent(talents, ABIL_RENOUNCE_RELIGION, check_confused); //jmf: Check for breath weapons - they're exclusive of each other, I hope! // Make better ones come first. if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON || player_mutation_level(MUT_BREATHE_FLAMES)) { _add_talent(talents, ABIL_BREATHE_FIRE, check_confused); } // Checking for unreleased Delayed Fireball. if (you.attribute[ ATTR_DELAYED_FIREBALL ]) _add_talent(talents, ABIL_DELAYED_FIREBALL, check_confused); // Evocations from items. if (scan_artefacts(ARTP_BLINK)) _add_talent(talents, ABIL_EVOKE_BLINK, check_confused); if (wearing_amulet(AMU_RAGE) || scan_artefacts(ARTP_BERSERK)) _add_talent(talents, ABIL_EVOKE_BERSERK, check_confused); if (player_equip( EQ_RINGS, RING_INVISIBILITY ) || player_equip_ego_type( EQ_ALL_ARMOUR, SPARM_DARKNESS ) || scan_artefacts( ARTP_INVISIBLE )) { // Now you can only turn invisibility off if you have an // activatable item. Wands and potions will have to time // out. -- bwr if (you.duration[DUR_INVIS]) _add_talent(talents, ABIL_EVOKE_TURN_VISIBLE, check_confused); else _add_talent(talents, ABIL_EVOKE_TURN_INVISIBLE, check_confused); } // Note: This ability only applies to this counter. if (player_equip( EQ_RINGS, RING_LEVITATION ) || player_equip_ego_type( EQ_BOOTS, SPARM_LEVITATION ) || scan_artefacts( ARTP_LEVITATE )) { // Has no effect on permanently flying Kenku. if (!you.permanent_flight()) { // Now you can only turn levitation off if you have an // activatable item. Potions and miscast effects will // have to time out (this makes the miscast effect actually // a bit annoying). -- bwr _add_talent(talents, you.duration[DUR_LEVITATION] ? ABIL_EVOKE_STOP_LEVITATING : ABIL_EVOKE_LEVITATE, check_confused); } } if (player_equip( EQ_RINGS, RING_TELEPORTATION )) { _add_talent(talents, ABIL_EVOKE_TELEPORTATION, check_confused); } // Find hotkeys for the non-hotkeyed talents. for (unsigned int i = 0; i < talents.size(); ++i) { // Skip preassigned hotkeys. if (talents[i].hotkey != 0) continue; // Try to find a free hotkey for i, starting from Z. for (int k = 51; k >= 0; ++k) { const int kkey = index_to_letter(k); bool good_key = true; // Check that it doesn't conflict with other hotkeys. for (unsigned int j = 0; j < talents.size(); ++j) { if (talents[j].hotkey == kkey) { good_key = false; break; } } if (good_key) { talents[i].hotkey = k; you.ability_letter_table[k] = talents[i].which; break; } } // In theory, we could be left with an unreachable ability // here (if you have 53 or more abilities simultaneously). } return talents; } // Note: we're trying for a behaviour where the player gets // to keep their assigned invocation slots if they get excommunicated // and then rejoin (but if they spend time with another god we consider // the old invocation slots void and erase them). We also try to // protect any bindings the character might have made into the // traditional invocation slots (A-E and X). -- bwr static void _set_god_ability_helper(ability_type abil, char letter) { int i; const int index = letter_to_index(letter); for (i = 0; i < 52; i++) if (you.ability_letter_table[i] == abil) break; if (i == 52) // Ability is not already assigned. { // If slot is unoccupied, move in. if (you.ability_letter_table[index] == ABIL_NON_ABILITY) you.ability_letter_table[index] = abil; } } // Return GOD_NO_GOD if it isn't a god ability, otherwise return // the index of the god. static int _is_god_ability(int abil) { if (abil == ABIL_NON_ABILITY) return (GOD_NO_GOD); for (int i = 0; i < MAX_NUM_GODS; ++i) for (int j = 0; j < MAX_GOD_ABILITIES; ++j) { if (god_abilities[i][j] == abil) return (i); } return (GOD_NO_GOD); } void set_god_ability_slots() { ASSERT(you.religion != GOD_NO_GOD); _set_god_ability_helper(ABIL_RENOUNCE_RELIGION, 'X'); // Clear out other god invocations. for (int i = 0; i < 52; i++) { const int god = _is_god_ability(you.ability_letter_table[i]); if (god != GOD_NO_GOD && god != you.religion) you.ability_letter_table[i] = ABIL_NON_ABILITY; } // Finally, add in current god's invocations in traditional slots. int num = 0; for (int i = 0; i < MAX_GOD_ABILITIES; ++i) { if (god_abilities[you.religion][i] != ABIL_NON_ABILITY) { _set_god_ability_helper(god_abilities[you.religion][i], 'a' + num++); if (you.religion == GOD_ELYVILON) { if (god_abilities[you.religion][i] == ABIL_ELYVILON_LESSER_HEALING_OTHERS) { _set_god_ability_helper(ABIL_ELYVILON_LESSER_HEALING_SELF, 'a' + num++); } else if (god_abilities[you.religion][i] == ABIL_ELYVILON_GREATER_HEALING_OTHERS) { _set_god_ability_helper(ABIL_ELYVILON_GREATER_HEALING_SELF, 'a' + num++); } } } } } // Returns an index (0-51) if successful, -1 if you should // just use the next one. static int _find_ability_slot(ability_type which_ability) { for (int slot = 0; slot < 52; slot++) if (you.ability_letter_table[slot] == which_ability) return (slot); // No requested slot, find new one and make it preferred. // Skip over a-e (invocations), or a-g for Elyvilon. const int first_slot = (you.religion == GOD_ELYVILON ? 7 : 5); for (int slot = first_slot; slot < 52; ++slot) { if (you.ability_letter_table[slot] == ABIL_NON_ABILITY) { you.ability_letter_table[slot] = which_ability; return (slot); } } // If we can't find anything else, try a-e. for (int slot = first_slot - 1; slot >= 0; --slot) { if (you.ability_letter_table[slot] == ABIL_NON_ABILITY) { you.ability_letter_table[slot] = which_ability; return (slot); } } // All letters are assigned. return (-1); } //////////////////////////////////////////////////////////////////////// // generic_cost int generic_cost::cost() const { return (base + (add > 0 ? random2avg(add, rolls) : 0)); } int scaling_cost::cost(int max) const { return (value < 0) ? (-value) : ((value * max + 500) / 1000); }