From 4693832f9d2a2ece40217c6adadc5855645312fb Mon Sep 17 00:00:00 2001 From: haranp Date: Tue, 8 May 2007 19:29:27 +0000 Subject: Cleaned up ability handling. It's still rather hacky, but not nearly as bad as the old horror. git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@1425 c06c8d41-db1a-0410-9941-cceddc491573 --- crawl-ref/source/abl-show.cc | 2695 +++++++++++++++++++++--------------------- 1 file changed, 1315 insertions(+), 1380 deletions(-) (limited to 'crawl-ref/source/abl-show.cc') diff --git a/crawl-ref/source/abl-show.cc b/crawl-ref/source/abl-show.cc index ea7e619a8d..f16b3fa699 100644 --- a/crawl-ref/source/abl-show.cc +++ b/crawl-ref/source/abl-show.cc @@ -25,6 +25,7 @@ #include "abl-show.h" #include +#include #include #include #include @@ -41,6 +42,7 @@ #include "it_use2.h" #include "macro.h" #include "message.h" +#include "menu.h" #include "misc.h" #include "mon-util.h" #include "monplace.h" @@ -74,17 +76,13 @@ // 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} -struct talent -{ - int which; - int fail; - bool is_invocation; -}; -static FixedVector< talent, 52 > Curr_abil; - -static bool insert_ability( int which_ability, bool check_conf ); static void lugonu_bends_space(); +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); // declaring this const messes up externs later, so don't do it ability_type god_abilities[MAX_NUM_GODS][MAX_GOD_ABILITIES] = @@ -146,7 +144,7 @@ ability_type god_abilities[MAX_NUM_GODS][MAX_GOD_ABILITIES] = // 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 struct ability_def Ability_List[] = +static const ability_def Ability_List[] = { // NON_ABILITY should always come first { ABIL_NON_ABILITY, "No ability", 0, 0, 0, 0, ABFLAG_NONE }, @@ -282,10 +280,11 @@ static const struct ability_def Ability_List[] = }; -const struct ability_def & get_ability_def( int abil ) +const struct ability_def & get_ability_def( ability_type abil ) /****************************************************/ { - for (unsigned int i = 0; i < sizeof( Ability_List ); i++) + for (unsigned int i = 0; + i < sizeof(Ability_List) / sizeof(Ability_List[0]); i++) { if (Ability_List[i].ability == abil) return (Ability_List[i]); @@ -294,40 +293,30 @@ const struct ability_def & get_ability_def( int abil ) return (Ability_List[0]); } - -const char * get_ability_name_by_index( char index ) -/**************************************************/ -{ - const struct ability_def &abil = get_ability_def( Curr_abil[index].which ); - - return (abil.name); -} - std::string print_abilities() { std::string text = "\na: "; - bool have_any = false; - if (generate_abilities(false)) + const std::vector talents = your_talents(false); + + if ( talents.empty() ) + text += "no special abilities"; + else { - for (int i = 0; i < 52; ++i) - if (Curr_abil[i].which != ABIL_NON_ABILITY) - { - if (have_any) - text += ", "; - text += get_ability_name_by_index(i); - have_any = true; - } + for (unsigned int i = 0; i < talents.size(); ++i) + { + if (i) + text += ", "; + text += get_ability_def(talents[i].which).name; + } } - if (!have_any) - text += "no special abilities"; return text; } -const std::string make_cost_description( const struct ability_def &abil ) -/***********************************************************************/ +const std::string make_cost_description( ability_type ability ) { + const ability_def& abil = get_ability_def(ability); std::ostringstream ret; if (abil.mp_cost) { @@ -339,7 +328,7 @@ const std::string make_cost_description( const struct ability_def &abil ) if (abil.hp_cost) { - if (ret.str().length()) + if (!ret.str().empty()) ret << ", "; ret << abil.hp_cost; @@ -350,7 +339,7 @@ const std::string make_cost_description( const struct ability_def &abil ) if (abil.food_cost) { - if (ret.str().length()) + if (!ret.str().empty()) ret << ", "; ret << "Food"; // randomized and amount hidden from player @@ -358,7 +347,7 @@ const std::string make_cost_description( const struct ability_def &abil ) if (abil.piety_cost) { - if (ret.str().length()) + if (!ret.str().empty()) ret << ", "; ret << "Piety"; // randomized and amount hidden from player @@ -366,7 +355,7 @@ const std::string make_cost_description( const struct ability_def &abil ) if (abil.flags & ABFLAG_BREATH) { - if (ret.str().length()) + if (!ret.str().empty()) ret << ", "; ret << "Breath"; @@ -374,7 +363,7 @@ const std::string make_cost_description( const struct ability_def &abil ) if (abil.flags & ABFLAG_DELAY) { - if (ret.str().length()) + if (!ret.str().empty()) ret << ", "; ret << "Delay"; @@ -382,7 +371,7 @@ const std::string make_cost_description( const struct ability_def &abil ) if (abil.flags & ABFLAG_PAIN) { - if (ret.str().length()) + if (!ret.str().empty()) ret << ", "; ret << "Pain"; @@ -390,7 +379,7 @@ const std::string make_cost_description( const struct ability_def &abil ) if (abil.flags & ABFLAG_EXHAUSTION) { - if (ret.str().length()) + if (!ret.str().empty()) ret << ", "; ret << "Exhaustion"; @@ -398,1727 +387,1673 @@ const std::string make_cost_description( const struct ability_def &abil ) if (abil.flags & ABFLAG_INSTANT) { - if (ret.str().length()) + if (!ret.str().empty()) ret << ", "; ret << "Instant"; // not really a cost, more of a bonus -bwr } // If we haven't output anything so far, then the effect has no cost - if (!ret.str().length()) + if (ret.str().empty()) ret << "None"; return (ret.str()); } -std::vector get_ability_names() -{ - std::vector abils; - if (generate_abilities(false)) - { - for (int i = 0; i < 52; ++i) - { - if (Curr_abil[i].which != ABIL_NON_ABILITY) - abils.push_back( get_ability_name_by_index(i) ); - } - } - return (abils); -} - -/* - Activates a menu which gives player access to all of their non-spell - special abilities - e.g. nagas' spit poison, or the Invocations you get - from worshipping. Generated dynamically - the function checks to see which - abilities you have every time. - */ -bool activate_ability(void) -/*************************/ +static talent get_talent(ability_type ability, bool check_confused) { - unsigned char keyin = 0; - - int power; - struct dist abild; - struct bolt beam; - struct dist spd; - - if (you.berserker) - { - canned_msg(MSG_TOO_BERSERK); - return (false); - } - - // populate the array of structs {dlb}: - if (!generate_abilities(false)) - { - mpr("Sorry, you're not good enough to have a special ability."); - return (false); - } + ASSERT(ability != ABIL_NON_ABILITY); - if (!generate_abilities(you.conf)) - { - mpr("You're too confused!"); - return (false); - } + talent result; + result.which = ability; - bool need_redraw = false; - bool need_prompt = true; - bool need_getch = true; + int failure = 0; + bool perfect = false; // is perfect + bool invoc = false; - for (;;) + if (check_confused) { - if (need_redraw) - { - mesclr( true ); - redraw_screen(); - } - - if (need_prompt) - mpr( "Use which ability? (? or * to list)", MSGCH_PROMPT ); - - if (need_getch) - keyin = get_ch(); - - need_redraw = false; - need_prompt = true; - need_getch = true; - - if (isalpha( keyin )) - { - break; - } - else if (keyin == '?' || keyin == '*') - { - keyin = show_abilities(); - - need_getch = false; - need_redraw = true; - need_prompt = true; - } - else if (keyin == ESCAPE || keyin == ' ' - || keyin == '\r' || keyin == '\n') + const ability_def &abil = get_ability_def(result.which); + if (you.conf && !testbits(abil.flags, ABFLAG_CONF_OK)) { - canned_msg( MSG_OK ); - return (false); + result.which = ABIL_NON_ABILITY; + return result; } } - if (!isalpha( keyin )) - { - mpr("You can't do that."); - return (false); - } - - const int abil_used = letter_to_index(keyin); - - if (Curr_abil[abil_used].which == -1) + // 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) { - mpr("You can't do that."); - return (false); - } + // begin spell abilities + case ABIL_DELAYED_FIREBALL: + case ABIL_MUMMY_RESTORATION: + perfect = true; + failure = 0; + break; - // some abilities don't need a hunger check - bool hungerCheck = true; - switch (Curr_abil[abil_used].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: - hungerCheck = false; - break; - default: - break; - } + // begin species abilities - some are mutagenic, too {dlb} + case ABIL_GLAMOUR: + failure = 50 - (you.experience_level * 2); + break; - if (hungerCheck && you.hunger_state < HS_HUNGRY) - { - mpr("You're too hungry."); - return (false); - } + case ABIL_SPIT_POISON: + failure = ((you.species == SP_NAGA) ? 20 : 40) + - 10 * you.mutation[MUT_SPIT_POISON] + - you.experience_level; + break; - // no turning back now... {dlb} - const struct ability_def abil = get_ability_def(Curr_abil[abil_used].which); + case ABIL_EVOKE_MAPPING: + failure = 30 - you.skills[SK_EVOCATIONS]; + break; - if (random2avg(100, 3) < Curr_abil[abil_used].fail) - { - mpr("You fail to use your ability."); - you.turn_is_over = true; - return (false); - } + case ABIL_MAPPING: + failure = 40 - 10 * you.mutation[MUT_MAPPING] - you.experience_level; + break; - if (!enough_mp( abil.mp_cost, false )) - return (false); + case ABIL_BREATHE_FIRE: + failure = ((you.species == SP_RED_DRACONIAN) ? 30 : 50) + - 10 * you.mutation[MUT_BREATHE_FLAMES] + - you.experience_level; - if (!enough_hp( abil.hp_cost, false )) - return (false); + if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON) + failure -= 20; + break; - // 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, false ); + 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; - const int oldhpmax = you.hp_max; - unrot_hp( 100 ); - if (you.hp_max > oldhpmax) - did_restore = true; + if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON) + failure -= 20; + break; - // If nothing happened, don't take one max MP, don't use a turn. - if (!did_restore) - { - canned_msg(MSG_NOTHING_HAPPENS); - return (false); - } + case ABIL_BREATHE_STEAM: + failure = 20 - you.experience_level; + if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON) + failure -= 20; break; - } - - case ABIL_DELAYED_FIREBALL: - { - if (spell_direction(spd, beam, DIR_NONE, TARG_ENEMY) == -1) - return (false); - - // Note: power level of ball calculated at release -- bwr - fireball( calc_spell_power( SPELL_DELAYED_FIREBALL, true ), beam ); - // only one allowed since this is instantaneous -- bwr - you.attribute[ ATTR_DELAYED_FIREBALL ] = 0; + case ABIL_FLY: // this is for kenku {dlb} + failure = 45 - (3 * you.experience_level); break; - } - case ABIL_GLAMOUR: - if (you.duration[DUR_GLAMOUR]) - { - canned_msg(MSG_CANNOT_DO_YET); - return (false); - } + case ABIL_FLY_II: // this is for draconians {dlb} + failure = 45 - (you.experience_level + you.strength); + break; + // end species abilties (some mutagenic) - mpr("You use your Elvish wiles."); + // begin demonic powers {dlb} + case ABIL_THROW_FLAME: + case ABIL_THROW_FROST: + failure = 10 - you.experience_level; + break; - cast_glamour( 10 + random2(you.experience_level) - + random2(you.experience_level) ); + case ABIL_SUMMON_MINOR_DEMON: + failure = 27 - you.experience_level; + break; - you.duration[DUR_GLAMOUR] = 20 + random2avg(13, 3); + case ABIL_CHANNELING: + case ABIL_BOLT_OF_DRAINING: + failure = 30 - you.experience_level; break; - case ABIL_SPIT_POISON: // Naga + spit poison mutation - if (you.duration[DUR_BREATH_WEAPON]) - { - canned_msg(MSG_CANNOT_DO_YET); - return (false); - } - else if (spell_direction(abild, beam) == -1) - { - return (false); - } - else - { - mpr("You spit poison."); + case ABIL_CONTROL_DEMON: + failure = 35 - you.experience_level; + break; - zapping( ZAP_SPIT_POISON, - you.experience_level - + you.mutation[MUT_SPIT_POISON] * 5 - + (you.species == SP_NAGA) * 10, - beam ); + case ABIL_SUMMON_DEMONS: + failure = 40 - you.experience_level; + break; - you.duration[DUR_BREATH_WEAPON] = 3 + random2(5); - } + case ABIL_TO_PANDEMONIUM: + failure = 57 - (you.experience_level * 2); break; - case ABIL_EVOKE_MAPPING: // randarts - mpr("You sense your surroundings."); + case ABIL_HELLFIRE: + case ABIL_RAISE_DEAD: + failure = 50 - you.experience_level; + break; - magic_mapping( 3 + roll_dice( 2, you.skills[SK_EVOCATIONS] ), - 40 + roll_dice( 2, you.skills[SK_EVOCATIONS] ) ); + case ABIL_TORMENT: + failure = 60 - you.experience_level; + break; - exercise( SK_EVOCATIONS, 1 ); + case ABIL_BLINK: + failure = 30 - (10 * you.mutation[MUT_BLINK]) - you.experience_level; break; - case ABIL_MAPPING: // Gnome + sense surrounds mut - mpr("You sense your surroundings."); + case ABIL_TELEPORTATION: + failure = ((you.mutation[MUT_TELEPORT_AT_WILL] > 1) ? 30 : 50) + - you.experience_level; + break; + // end demonic powers {dlb} - magic_mapping( 3 + roll_dice( 2, you.experience_level ) - + you.mutation[MUT_MAPPING] * 10, - 40 + roll_dice( 2, you.experience_level ) ); + // begin transformation abilities {dlb} + case ABIL_END_TRANSFORMATION: + perfect = true; + failure = 0; break; - case ABIL_EVOKE_TELEPORTATION: // ring of teleportation - case ABIL_TELEPORTATION: // teleport mut - if (you.mutation[MUT_TELEPORT_AT_WILL] == 3) - you_teleport2( true, true ); // instant and to new area of Abyss - else - you_teleport(); - - if (abil.ability == ABIL_EVOKE_TELEPORTATION) - exercise( SK_EVOCATIONS, 1 ); + case ABIL_BREATHE_HELLFIRE: + failure = 32 - you.experience_level; + 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_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: - if (you.duration[DUR_BREATH_WEAPON] - && abil.ability != ABIL_SPIT_ACID) - { - canned_msg(MSG_CANNOT_DO_YET); - return (false); - } - else if (spell_direction( abild, beam ) == -1) - { - return (false); - } - - switch (Curr_abil[abil_used].which) - { - case ABIL_BREATHE_FIRE: - power = you.experience_level; - power += you.mutation[MUT_BREATHE_FLAMES] * 4; - - if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON) - power += 12; - - // don't check for hell serpents - they get hell fire, - // never regular fire (GDL) - mprf("You breathe fire%c", (power < 15)?'.':'!'); - - zapping( ZAP_BREATHE_FIRE, power, beam); - break; - - case ABIL_BREATHE_FROST: - mpr("You exhale a wave of freezing cold."); - zapping(ZAP_BREATHE_FROST, you.experience_level, beam); - break; - - case ABIL_BREATHE_POISON: - mpr("You exhale a blast of poison gas."); - zapping(ZAP_BREATHE_POISON, you.experience_level, beam); - break; - - case ABIL_BREATHE_LIGHTNING: - mpr("You spit a bolt of lightning."); - zapping(ZAP_LIGHTNING, (you.experience_level * 2), beam); - break; - - case ABIL_SPIT_ACID: - mpr("You spit acid."); - zapping(ZAP_BREATHE_ACID, you.experience_level, beam); - break; - - case ABIL_BREATHE_POWER: - mpr("You spit a bolt of incandescent energy."); - zapping(ZAP_BREATHE_POWER, you.experience_level, beam); - break; - - case ABIL_BREATHE_STICKY_FLAME: - mpr("You spit a glob of burning liquid."); - zapping(ZAP_STICKY_FLAME, you.experience_level, beam); - break; + case ABIL_EVOKE_TURN_VISIBLE: + case ABIL_EVOKE_STOP_LEVITATING: + perfect = true; + failure = 0; + break; - case ABIL_BREATHE_STEAM: - mpr("You exhale a blast of scalding steam."); - zapping(ZAP_BREATHE_STEAM, you.experience_level, beam); - 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 (abil.ability != ABIL_SPIT_ACID) + if (you.species == SP_TROLL) + failure -= 30; + else if (player_genus(GENPC_DWARVEN) || you.species == SP_HILL_ORC + || you.species == SP_OGRE) { - you.duration[DUR_BREATH_WEAPON] = - 3 + random2(4) + random2(30 - you.experience_level) / 2; - - if (Curr_abil[abil_used].which == ABIL_BREATHE_STEAM) - you.duration[DUR_BREATH_WEAPON] /= 2; + failure -= 10; } break; + // end item abilities - some possibly mutagenic {dlb} - case ABIL_EVOKE_BLINK: // randarts - case ABIL_BLINK: // mutation - random_blink(true); - - if (abil.ability == ABIL_EVOKE_BLINK) - exercise( SK_EVOCATIONS, 1 ); + // begin invocations {dlb} + case ABIL_ELYVILON_PURIFICATION: + invoc = true; + failure = 20 - (you.piety / 20) - (5 * you.skills[SK_INVOCATIONS]); break; - case ABIL_EVOKE_BERSERK: // amulet of rage, randarts - if (you.hunger_state < HS_SATIATED) - { - mpr("You're too hungry to berserk."); - return (false); - } - - // only exercise if berserk succeeds - if ( go_berserk(true) ) - exercise( SK_EVOCATIONS, 1 ); + case ABIL_ZIN_REPEL_UNDEAD: + case ABIL_TSO_REPEL_UNDEAD: + case ABIL_KIKU_RECALL_UNDEAD_SLAVES: + case ABIL_OKAWARU_MIGHT: + case ABIL_ELYVILON_LESSER_HEALING: + invoc = true; + failure = 30 - (you.piety / 20) - (6 * you.skills[SK_INVOCATIONS]); 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."); - you.levitation = 100; - you.duration[DUR_CONTROLLED_FLIGHT] = 100; - } + // 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_FLY_II: // Fly (Draconians, or anything else with wings) - if (you.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); - } - else - { - cast_fly( you.experience_level * 2 ); - // you.attribute[ATTR_EXPENSIVE_FLIGHT] = 1; // unused - } + case ABIL_TROG_MIGHT: // piety >= 50 + invoc = true; + failure = 80 - you.piety; // starts at 30% break; - // DEMONIC POWERS: - case ABIL_SUMMON_MINOR_DEMON: - summon_ice_beast_etc( you.experience_level * 4, - summon_any_demon(DEMON_LESSER) ); + case ABIL_TROG_HASTE_SELF: // piety >= 100 + invoc = true; + failure = 160 - you.piety; // starts at 60% break; - case ABIL_SUMMON_DEMONS: - summon_ice_beast_etc( you.experience_level * 4, - summon_any_demon(DEMON_COMMON) ); + case ABIL_YRED_ANIMATE_CORPSE: + invoc = true; + failure = 40 - (you.piety / 20) - (3 * you.skills[SK_INVOCATIONS]); break; - case ABIL_HELLFIRE: - if (your_spells(SPELL_HELLFIRE, - 20 + you.experience_level, false) == SPRET_ABORT) - return (false); + case ABIL_ZIN_HEALING: + case ABIL_TSO_SMITING: + case ABIL_OKAWARU_HEALING: + case ABIL_MAKHLEB_MINOR_DESTRUCTION: + case ABIL_SIF_MUNA_FORGET_SPELL: + case ABIL_KIKU_ENSLAVE_UNDEAD: + case ABIL_YRED_ANIMATE_DEAD: + case ABIL_MAKHLEB_LESSER_SERVANT_OF_MAKHLEB: + case ABIL_ELYVILON_HEALING: + invoc = true; + failure = 40 - (you.piety / 20) - (5 * you.skills[SK_INVOCATIONS]); break; - case ABIL_TORMENT: - if (you.is_undead) - { - mpr("The unliving cannot use this ability."); - return (false); - } + case ABIL_SIF_MUNA_CHANNEL_ENERGY: + invoc = true; + failure = 40 - you.intel - you.skills[SK_INVOCATIONS]; + break; - torment(TORMENT_GENERIC, you.x_pos, you.y_pos); + case ABIL_YRED_RECALL_UNDEAD: + invoc = true; + failure = 50 - (you.piety / 20) - (you.skills[SK_INVOCATIONS] * 4); break; - case ABIL_RAISE_DEAD: - your_spells(SPELL_ANIMATE_DEAD, you.experience_level * 5, false); + case ABIL_ZIN_PESTILENCE: + case ABIL_TSO_ANNIHILATE_UNDEAD: + invoc = true; + failure = 60 - (you.piety / 20) - (5 * you.skills[SK_INVOCATIONS]); break; - case ABIL_CONTROL_DEMON: - if (spell_direction(abild, beam) == -1) - { - return (false); - } + case ABIL_MAKHLEB_MAJOR_DESTRUCTION: + case ABIL_YRED_DRAIN_LIFE: + invoc = true; + failure = 60 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); + break; - zapping(ZAP_CONTROL_DEMON, you.experience_level * 5, beam); + case ABIL_ZIN_HOLY_WORD: + case ABIL_TSO_CLEANSING_FLAME: + case ABIL_ELYVILON_RESTORATION: + case ABIL_YRED_CONTROL_UNDEAD: + case ABIL_OKAWARU_HASTE: + case ABIL_MAKHLEB_GREATER_SERVANT_OF_MAKHLEB: + invoc = true; + failure = 70 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); break; - case ABIL_TO_PANDEMONIUM: - if (you.level_type == LEVEL_PANDEMONIUM) - { - mpr("You're already here."); - return (false); - } + case ABIL_ZIN_SUMMON_GUARDIAN: + case ABIL_TSO_SUMMON_DAEVA: + case ABIL_KIKU_INVOKE_DEATH: + case ABIL_ELYVILON_GREATER_HEALING: + invoc = true; + failure = 80 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); + break; - banished(DNGN_ENTER_PANDEMONIUM); + //jmf: following for to-be-created gods + case ABIL_CHARM_SNAKE: + invoc = true; + failure = 40 - (you.piety / 20) - (3 * you.skills[SK_INVOCATIONS]); break; - case ABIL_CHANNELING: - mpr("You channel some magical energy."); - inc_mp(1 + random2(5), false); + case ABIL_TRAN_SERPENT_OF_HELL: + invoc = true; + failure = 80 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); break; - case ABIL_THROW_FLAME: - case ABIL_THROW_FROST: - if (spell_direction(abild, beam) == -1) - { - return (false); - } + case ABIL_ROTTING: + invoc = true; + failure = 60 - (you.piety / 20) - (5 * you.skills[SK_INVOCATIONS]); + break; - zapping( (Curr_abil[abil_used].which == ABIL_THROW_FLAME ? ZAP_FLAME - : ZAP_FROST), - you.experience_level * 3, - beam ); + case ABIL_TORMENT_II: + invoc = true; + failure = 70 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); break; - case ABIL_BOLT_OF_DRAINING: - if (spell_direction(abild, beam) == -1) - { - return (false); - } + case ABIL_RENOUNCE_RELIGION: + invoc = true; + perfect = true; + failure = 0; + break; - zapping(ZAP_NEGATIVE_ENERGY, you.experience_level * 6, beam); + // end invocations {dlb} + default: + failure = -1; break; + } - 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); - } + // Perfect abilities are things like "renounce religion", which + // shouldn't have a failure rate ever. -- bwr + if (failure <= 0 && !perfect) + failure = 1; - potion_effect( POT_INVISIBILITY, 2 * you.skills[SK_EVOCATIONS] + 5 ); - contaminate_player( 1 + random2(3) ); - exercise( SK_EVOCATIONS, 1 ); - break; + if (failure > 100) + failure = 100; - case ABIL_EVOKE_TURN_VISIBLE: - mpr("You feel less transparent."); - you.invis = 1; - break; + result.fail = failure; + result.is_invocation = invoc; - case ABIL_EVOKE_LEVITATE: // ring, boots, randarts - potion_effect( POT_LEVITATION, 2 * you.skills[SK_EVOCATIONS] + 30 ); - exercise( SK_EVOCATIONS, 1 ); - break; + return result; - case ABIL_EVOKE_STOP_LEVITATING: - mpr("You feel heavy."); - you.levitation = 1; - break; +} - case ABIL_END_TRANSFORMATION: - mpr("You feel almost normal."); - you.duration[DUR_TRANSFORMATION] = 2; - break; +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; +} - // INVOCATIONS: - case ABIL_ZIN_REPEL_UNDEAD: - case ABIL_TSO_REPEL_UNDEAD: - turn_undead(you.piety); +bool activate_ability() +{ + if (you.berserker) + { + canned_msg(MSG_TOO_BERSERK); + return (false); + } - if (!you.duration[DUR_REPEL_UNDEAD]) - mpr( "You feel a holy aura protecting you." ); + std::vector talents = your_talents(false); + if ( talents.empty() ) + { + mpr("Sorry, you're not good enough to have a special ability."); + return false; + } + if ( you.conf ) + { + talents = your_talents(true); + if ( talents.empty() ) + { + mpr("You're too confused!"); + return false; + } + } - you.duration[DUR_REPEL_UNDEAD] += 8 - + roll_dice(2, 2 * you.skills[SK_INVOCATIONS]); + int selected = -1; + while ( selected < 0 ) + { + msg::streams(MSGCH_PROMPT) << "Use which ability? (? or * to list)" + << std::endl; - if (you.duration[ DUR_REPEL_UNDEAD ] > 50) - you.duration[ DUR_REPEL_UNDEAD ] = 50; + const int keyin = get_ch(); - exercise(SK_INVOCATIONS, 1); - break; + if ( keyin == '?' || keyin == '*' ) + { + selected = choose_ability_menu(talents); + } + 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; + } + } - case ABIL_ZIN_HEALING: - if (!cast_healing( 3 + (you.skills[SK_INVOCATIONS] / 6) )) + // if we can't, cancel out + if ( selected < 0 ) + { + mpr("You can't do that."); + return (false); + } + } + } + + return activate_talent(talents[selected]); +} + +static bool activate_talent(const talent& tal) +{ + // 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: + hungerCheck = false; break; + default: + break; + } - exercise(SK_INVOCATIONS, 1 + random2(3)); - break; + if (hungerCheck && you.hunger_state < HS_HUNGRY) + { + mpr("You're too hungry."); + return (false); + } - case ABIL_ZIN_PESTILENCE: - mpr( "You call forth a swarm of pestilential beasts!" ); + const ability_def& abil = get_ability_def(tal.which); - if (!summon_swarm( you.skills[SK_INVOCATIONS] * 8, false, true )) - mpr( "Nothing seems to have answered your call." ); + // check that we can afford to pay the costs + if (!enough_mp( abil.mp_cost, false )) + return (false); - exercise( SK_INVOCATIONS, 2 + random2(4) ); - break; + if (!enough_hp( abil.hp_cost, false )) + return (false); - case ABIL_ZIN_HOLY_WORD: - holy_word( you.skills[SK_INVOCATIONS] * 8 ); - exercise(SK_INVOCATIONS, 3 + random2(5)); - break; + // 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); + } - case ABIL_ZIN_SUMMON_GUARDIAN: - summon_ice_beast_etc(you.skills[SK_INVOCATIONS] * 4, MONS_ANGEL, true); - exercise(SK_INVOCATIONS, 8 + random2(10)); - break; + const bool success = do_ability(abil); + if ( success ) + pay_ability_costs(abil); + return success; +} - case ABIL_TSO_SMITING: - if (your_spells( SPELL_SMITING, (2 + skill_bump(SK_INVOCATIONS)) * 6, - false ) == SPRET_ABORT) - return (false); - exercise( SK_INVOCATIONS, (coinflip()? 3 : 2) ); - break; +static bool do_ability(const ability_def& abil) +{ + int power; + struct dist abild; + struct bolt beam; + struct dist spd; - case ABIL_TSO_ANNIHILATE_UNDEAD: - if (spell_direction(spd, beam) == -1) + // 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, 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); } - zapping(ZAP_DISPEL_UNDEAD, you.skills[SK_INVOCATIONS] * 6, beam); - exercise(SK_INVOCATIONS, 2 + random2(4)); break; + } + + case ABIL_DELAYED_FIREBALL: + { + if (spell_direction(spd, beam, DIR_NONE, TARG_ENEMY) == -1) + return (false); + + // Note: power level of ball calculated at release -- bwr + fireball( calc_spell_power( SPELL_DELAYED_FIREBALL, true ), beam ); + + // only one allowed since this is instantaneous -- bwr + you.attribute[ ATTR_DELAYED_FIREBALL ] = 0; + break; + } - case ABIL_TSO_CLEANSING_FLAME: - if (spell_direction(spd, beam) == -1) + case ABIL_GLAMOUR: + if (you.duration[DUR_GLAMOUR]) { + canned_msg(MSG_CANNOT_DO_YET); return (false); } - zapping(ZAP_CLEANSING_FLAME, 20 + you.skills[SK_INVOCATIONS] * 6, beam); - exercise(SK_INVOCATIONS, 3 + random2(6)); - break; + mpr("You use your Elvish wiles."); - case ABIL_TSO_SUMMON_DAEVA: - summon_ice_beast_etc(you.skills[SK_INVOCATIONS] * 4, MONS_DAEVA, true); - exercise(SK_INVOCATIONS, 8 + random2(10)); - break; + cast_glamour( 10 + random2(you.experience_level) + + random2(you.experience_level) ); - case ABIL_KIKU_RECALL_UNDEAD_SLAVES: - recall(1); - exercise(SK_INVOCATIONS, 1); + you.duration[DUR_GLAMOUR] = 20 + random2avg(13, 3); break; - case ABIL_KIKU_ENSLAVE_UNDEAD: - if (spell_direction(spd, beam) == -1) + case ABIL_SPIT_POISON: // Naga + spit poison mutation + if (you.duration[DUR_BREATH_WEAPON]) + { + canned_msg(MSG_CANNOT_DO_YET); + return (false); + } + else if (spell_direction(abild, beam) == -1) { return (false); } + else + { + mpr("You spit poison."); - zapping( ZAP_ENSLAVE_UNDEAD, you.skills[SK_INVOCATIONS] * 8, beam ); - exercise(SK_INVOCATIONS, 5 + random2(5)); - break; + zapping( ZAP_SPIT_POISON, + you.experience_level + + you.mutation[MUT_SPIT_POISON] * 5 + + (you.species == SP_NAGA) * 10, + beam ); - case ABIL_KIKU_INVOKE_DEATH: - summon_ice_beast_etc( - 20 + you.skills[SK_INVOCATIONS] * 3, MONS_REAPER, true); - exercise(SK_INVOCATIONS, 10 + random2(14)); + you.duration[DUR_BREATH_WEAPON] = 3 + random2(5); + } break; - case ABIL_YRED_ANIMATE_CORPSE: - mpr("You call on the dead to walk for you..."); + case ABIL_EVOKE_MAPPING: // randarts + mpr("You sense your surroundings."); - animate_a_corpse( you.x_pos, you.y_pos, BEH_FRIENDLY, - you.pet_target, CORPSE_BODY ); - - exercise(SK_INVOCATIONS, 2 + random2(4)); - break; - - case ABIL_YRED_RECALL_UNDEAD: - recall(1); - exercise(SK_INVOCATIONS, 2 + random2(4)); - break; - - case ABIL_YRED_ANIMATE_DEAD: - mpr("You call on the dead to walk for you..."); - - animate_dead( 1 + you.skills[SK_INVOCATIONS], BEH_FRIENDLY, - you.pet_target, 1 ); - - 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; + magic_mapping( 3 + roll_dice( 2, you.skills[SK_EVOCATIONS] ), + 40 + roll_dice( 2, you.skills[SK_EVOCATIONS] ) ); - case ABIL_YRED_CONTROL_UNDEAD: - mass_enchantment( ENCH_CHARM, you.skills[SK_INVOCATIONS] * 8, MHITYOU ); - exercise(SK_INVOCATIONS, 3 + random2(4)); + exercise( SK_EVOCATIONS, 1 ); 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_MAPPING: // Gnome + sense surrounds mut + mpr("You sense your surroundings."); - case ABIL_OKAWARU_MIGHT: - potion_effect( POT_MIGHT, you.skills[SK_INVOCATIONS] * 8 ); - exercise(SK_INVOCATIONS, 1 + random2(3)); + magic_mapping( 3 + roll_dice( 2, you.experience_level ) + + you.mutation[MUT_MAPPING] * 10, + 40 + roll_dice( 2, you.experience_level ) ); break; - case ABIL_OKAWARU_HEALING: - if (!cast_healing( 3 + (you.skills[SK_INVOCATIONS] / 6) )) - break; - - exercise(SK_INVOCATIONS, 2 + random2(5)); - break; + case ABIL_EVOKE_TELEPORTATION: // ring of teleportation + case ABIL_TELEPORTATION: // teleport mut + if (you.mutation[MUT_TELEPORT_AT_WILL] == 3) + you_teleport2( true, true ); // instant and to new area of Abyss + else + you_teleport(); - case ABIL_OKAWARU_HASTE: - potion_effect( POT_SPEED, you.skills[SK_INVOCATIONS] * 8 ); - exercise(SK_INVOCATIONS, 3 + random2(7)); + if (abil.ability == ABIL_EVOKE_TELEPORTATION) + exercise( SK_EVOCATIONS, 1 ); break; - case ABIL_MAKHLEB_MINOR_DESTRUCTION: - if (spell_direction(spd, beam) == -1) + 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: + if (you.duration[DUR_BREATH_WEAPON] + && abil.ability != ABIL_SPIT_ACID) { + canned_msg(MSG_CANNOT_DO_YET); return (false); } - - power = you.skills[SK_INVOCATIONS] - + random2( 1 + you.skills[SK_INVOCATIONS] ) - + random2( 1 + you.skills[SK_INVOCATIONS] ); - - switch (random2(5)) + else if (spell_direction( abild, beam ) == -1) { - case 0: zapping( ZAP_FLAME, power, beam ); break; - case 1: zapping( ZAP_PAIN, power, beam ); break; - case 2: zapping( ZAP_STONE_ARROW, power, beam ); break; - case 3: zapping( ZAP_ELECTRICITY, power, beam ); break; - case 4: zapping( ZAP_BREATHE_ACID, power / 2, beam ); break; + return (false); } - exercise(SK_INVOCATIONS, 1); - break; + switch (abil.ability) + { + case ABIL_BREATHE_FIRE: + power = you.experience_level; + power += you.mutation[MUT_BREATHE_FLAMES] * 4; - case ABIL_MAKHLEB_LESSER_SERVANT_OF_MAKHLEB: - summon_ice_beast_etc( 20 + you.skills[SK_INVOCATIONS] * 3, - MONS_NEQOXEC + random2(5), true ); + if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON) + power += 12; - exercise(SK_INVOCATIONS, 2 + random2(3)); - break; + // don't check for hell serpents - they get hell fire, + // never regular fire (GDL) + mprf("You breathe fire%c", (power < 15)?'.':'!'); - case ABIL_MAKHLEB_MAJOR_DESTRUCTION: - if (spell_direction(spd, beam) == -1) - { - return (false); - } + zapping( ZAP_BREATHE_FIRE, power, beam); + break; - power = you.skills[SK_INVOCATIONS] * 3 - + random2( 1 + you.skills[SK_INVOCATIONS] ) - + random2( 1 + you.skills[SK_INVOCATIONS] ); + case ABIL_BREATHE_FROST: + mpr("You exhale a wave of freezing cold."); + zapping(ZAP_BREATHE_FROST, you.experience_level, beam); + break; - switch (random2(8)) - { - case 0: zapping( ZAP_FIRE, power, beam ); break; - case 1: zapping( ZAP_FIREBALL, power, beam ); break; - case 2: zapping( ZAP_LIGHTNING, power, beam ); break; - case 3: zapping( ZAP_NEGATIVE_ENERGY, power, beam ); break; - case 4: zapping( ZAP_STICKY_FLAME, power, beam ); break; - case 5: zapping( ZAP_IRON_BOLT, power, beam ); break; - case 6: zapping( ZAP_ORB_OF_ELECTRICITY, power, beam ); break; + case ABIL_BREATHE_POISON: + mpr("You exhale a blast of poison gas."); + zapping(ZAP_BREATHE_POISON, you.experience_level, beam); + break; - case 7: - you.attribute[ATTR_DIVINE_LIGHTNING_PROTECTION] = 1; - mpr("Makhleb hurls a blast of lightning!"); + case ABIL_BREATHE_LIGHTNING: + mpr("You spit a bolt of lightning."); + zapping(ZAP_LIGHTNING, (you.experience_level * 2), beam); + break; - // make a divine lightning bolt... - beam.beam_source = NON_MONSTER; - beam.type = SYM_BURST; - beam.damage = dice_def( 3, 30 ); - beam.flavour = BEAM_ELECTRICITY; - beam.target_x = you.x_pos; - beam.target_y = you.y_pos; - beam.name = "blast of lightning"; - beam.colour = LIGHTCYAN; - beam.thrower = KILL_YOU; - beam.aux_source = "Makhleb's lightning strike"; - beam.ex_size = 1 + you.skills[SK_INVOCATIONS] / 8; - beam.is_tracer = false; + case ABIL_SPIT_ACID: + mpr("You spit acid."); + zapping(ZAP_BREATHE_ACID, you.experience_level, beam); + break; - // ... and fire! - explosion(beam); + case ABIL_BREATHE_POWER: + mpr("You spit a bolt of incandescent energy."); + zapping(ZAP_BREATHE_POWER, you.experience_level, beam); + break; - // protection down - mpr("Your divine protection wanes."); - you.attribute[ATTR_DIVINE_LIGHTNING_PROTECTION] = 0; + case ABIL_BREATHE_STICKY_FLAME: + mpr("You spit a glob of burning liquid."); + zapping(ZAP_STICKY_FLAME, you.experience_level, beam); + break; + + case ABIL_BREATHE_STEAM: + mpr("You exhale a blast of scalding steam."); + zapping(ZAP_BREATHE_STEAM, you.experience_level, beam); + break; + + default: break; } - exercise(SK_INVOCATIONS, 3 + random2(5)); + if (abil.ability != ABIL_SPIT_ACID) + { + you.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_MAKHLEB_GREATER_SERVANT_OF_MAKHLEB: - summon_ice_beast_etc( 20 + you.skills[SK_INVOCATIONS] * 3, - MONS_EXECUTIONER + random2(5), - true ); + case ABIL_EVOKE_BLINK: // randarts + case ABIL_BLINK: // mutation + random_blink(true); - exercise(SK_INVOCATIONS, 6 + random2(6)); + if (abil.ability == ABIL_EVOKE_BLINK) + exercise( SK_EVOCATIONS, 1 ); break; - case ABIL_TROG_BERSERK: - // Trog abilities don't use or train invocations. + case ABIL_EVOKE_BERSERK: // amulet of rage, randarts if (you.hunger_state < HS_SATIATED) { mpr("You're too hungry to berserk."); return (false); } - go_berserk(true); + // only exercise if berserk succeeds + if ( go_berserk(true) ) + exercise( SK_EVOCATIONS, 1 ); break; - case ABIL_TROG_MIGHT: - // Trog abilities don't use or train invocations. - potion_effect( POT_MIGHT, 150 ); - break; + // fly (kenku) -- eventually becomes permanent (see acr.cc) + case ABIL_FLY: + cast_fly( you.experience_level * 4 ); - case ABIL_TROG_HASTE_SELF: - // Trog abilities don't use or train invocations. - potion_effect( POT_SPEED, 150 ); + if (you.experience_level > 14) + { + mpr("You feel very comfortable in the air."); + you.levitation = 100; + you.duration[DUR_CONTROLLED_FLIGHT] = 100; + } break; - case ABIL_SIF_MUNA_FORGET_SPELL: - cast_selective_amnesia(true); + case ABIL_FLY_II: // Fly (Draconians, or anything else with wings) + if (you.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); + } + else + { + cast_fly( you.experience_level * 2 ); + // you.attribute[ATTR_EXPENSIVE_FLIGHT] = 1; // unused + } break; - case ABIL_ELYVILON_LESSER_HEALING: - if (!cast_healing( 3 + (you.skills[SK_INVOCATIONS] / 6) )) - break; - - exercise( SK_INVOCATIONS, 1 ); + // DEMONIC POWERS: + case ABIL_SUMMON_MINOR_DEMON: + summon_ice_beast_etc( you.experience_level * 4, + summon_any_demon(DEMON_LESSER) ); break; - case ABIL_ELYVILON_PURIFICATION: - purification(); - exercise( SK_INVOCATIONS, 2 + random2(3) ); + case ABIL_SUMMON_DEMONS: + summon_ice_beast_etc( you.experience_level * 4, + summon_any_demon(DEMON_COMMON) ); break; - case ABIL_ELYVILON_HEALING: - if (!cast_healing( 10 + (you.skills[SK_INVOCATIONS] / 3) )) - break; - - exercise( SK_INVOCATIONS, 3 + random2(5) ); + case ABIL_HELLFIRE: + if (your_spells(SPELL_HELLFIRE, + 20 + you.experience_level, false) == SPRET_ABORT) + return (false); break; - case ABIL_ELYVILON_RESTORATION: - restore_stat( STAT_ALL, false ); - unrot_hp( 100 ); + case ABIL_TORMENT: + if (you.is_undead) + { + mpr("The unliving cannot use this ability."); + return (false); + } - exercise( SK_INVOCATIONS, 4 + random2(6) ); + torment(TORMENT_GENERIC, you.x_pos, you.y_pos); break; - case ABIL_ELYVILON_GREATER_HEALING: - if (!cast_healing( 20 + you.skills[SK_INVOCATIONS] * 2 )) - break; - - exercise( SK_INVOCATIONS, 6 + random2(10) ); + case ABIL_RAISE_DEAD: + your_spells(SPELL_ANIMATE_DEAD, you.experience_level * 5, false); break; - case ABIL_LUGONU_ABYSS_EXIT: - if ( you.level_type != LEVEL_ABYSS ) + case ABIL_CONTROL_DEMON: + if (spell_direction(abild, beam) == -1) { - mpr("You aren't in the Abyss!"); - return false; // don't incur costs + return (false); } - banished(DNGN_EXIT_ABYSS); - exercise(SK_INVOCATIONS, 8 + random2(10)); - - // Lose 1d2 permanent HP - you.hp_max -= (coinflip() ? 2 : 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); - break; - - case ABIL_LUGONU_BEND_SPACE: - lugonu_bends_space(); - exercise(SK_INVOCATIONS, 2 + random2(3)); - break; - case ABIL_LUGONU_SUMMON_DEMONS: - { - int ndemons = 1 + you.skills[SK_INVOCATIONS] / 4; - if (ndemons > 5) - ndemons = 5; - - for ( int i = 0; i < ndemons; ++i ) - summon_ice_beast_etc( 20 + you.skills[SK_INVOCATIONS] * 3, - summon_any_demon(DEMON_COMMON), true); - - exercise(SK_INVOCATIONS, 6 + random2(6)); + zapping(ZAP_CONTROL_DEMON, you.experience_level * 5, beam); break; - } - case ABIL_LUGONU_ABYSS_ENTER: - if (you.level_type == LEVEL_ABYSS) + case ABIL_TO_PANDEMONIUM: + if (you.level_type == LEVEL_PANDEMONIUM) { mpr("You're already here."); - return false; - } - else if (you.level_type == LEVEL_PANDEMONIUM) - { - mpr("That doesn't work from Pandemonium."); - return false; + return (false); } - activate_notes(false); // this banishment shouldn't be noted - banished(DNGN_ENTER_ABYSS); - activate_notes(true); + banished(DNGN_ENTER_PANDEMONIUM); break; - //jmf: intended as invocations from evil god(s): - case ABIL_CHARM_SNAKE: - cast_snake_charm( you.experience_level * 2 - + you.skills[SK_INVOCATIONS] * 3 ); - - exercise(SK_INVOCATIONS, 2 + random2(4)); + case ABIL_CHANNELING: + mpr("You channel some magical energy."); + inc_mp(1 + random2(5), false); break; - case ABIL_TRAN_SERPENT_OF_HELL: - transform(10 + (you.experience_level * 2) + - (you.skills[SK_INVOCATIONS] * 3), TRAN_SERPENT_OF_HELL); + case ABIL_THROW_FLAME: + case ABIL_THROW_FROST: + if (spell_direction(abild, beam) == -1) + { + return (false); + } - exercise(SK_INVOCATIONS, 6 + random2(9)); + zapping( (abil.ability == ABIL_THROW_FLAME ? ZAP_FLAME : ZAP_FROST), + you.experience_level * 3, + beam ); break; - case ABIL_BREATHE_HELLFIRE: - if (you.duration[DUR_BREATH_WEAPON]) + case ABIL_BOLT_OF_DRAINING: + if (spell_direction(abild, beam) == -1) { - canned_msg(MSG_CANNOT_DO_YET); return (false); } - if (your_spells( SPELL_HELLFIRE, - 20 + you.experience_level, false ) == SPRET_ABORT) - return (false); - - you.duration[DUR_BREATH_WEAPON] += - 3 + random2(5) + random2(30 - you.experience_level); - break; - - case ABIL_ROTTING: - cast_rotting(you.experience_level * 2 + you.skills[SK_INVOCATIONS] * 3); - exercise(SK_INVOCATIONS, 2 + random2(4)); + zapping(ZAP_NEGATIVE_ENERGY, you.experience_level * 6, beam); break; - case ABIL_TORMENT_II: - if (you.is_undead) + case ABIL_EVOKE_TURN_INVISIBLE: // ring, randarts, darkness items + if (you.hunger_state < HS_SATIATED) { - mpr("The unliving cannot use this ability."); + mpr("You're too hungry to turn invisible."); return (false); } - torment(TORMENT_GENERIC, you.x_pos, you.y_pos); - exercise(SK_INVOCATIONS, 2 + random2(4)); + potion_effect( POT_INVISIBILITY, 2 * you.skills[SK_EVOCATIONS] + 5 ); + contaminate_player( 1 + random2(3) ); + exercise( SK_EVOCATIONS, 1 ); break; - case ABIL_RENOUNCE_RELIGION: - if (yesno("Really renounce your faith, foregoing its fabulous benefits?") - && yesno( "Are you sure you won't change your mind later?" )) - { - excommunication(); - } - else - { - canned_msg(MSG_OK); - } + case ABIL_EVOKE_TURN_VISIBLE: + mpr("You feel less transparent."); + you.invis = 1; break; - default: - mpr("Sorry, you can't do that."); + case ABIL_EVOKE_LEVITATE: // ring, boots, randarts + potion_effect( POT_LEVITATION, 2 * you.skills[SK_EVOCATIONS] + 30 ); + exercise( SK_EVOCATIONS, 1 ); break; - } - // currently only delayed fireball is instantaneous -- bwr - you.turn_is_over = !(abil.flags & ABFLAG_INSTANT); + case ABIL_EVOKE_STOP_LEVITATING: + mpr("You feel heavy."); + you.levitation = 1; + break; - // All failures should have returned by this point, so we'll - // apply the costs -- its not too neat, but it works for now. -- bwr - const int food_cost = abil.food_cost + random2avg(abil.food_cost, 2); - const int piety_cost = abil.piety_cost + random2((abil.piety_cost + 1) / 2 + 1); + case ABIL_END_TRANSFORMATION: + mpr("You feel almost normal."); + you.duration[DUR_TRANSFORMATION] = 2; + break; -#if DEBUG_DIAGNOSTICS - mprf(MSGCH_DIAGNOSTICS, "Cost: mp=%d; hp=%d; food=%d; piety=%d", - abil.mp_cost, abil.hp_cost, food_cost, piety_cost ); -#endif + // INVOCATIONS: + case ABIL_ZIN_REPEL_UNDEAD: + case ABIL_TSO_REPEL_UNDEAD: + turn_undead(you.piety); - if (abil.mp_cost) - { - dec_mp( abil.mp_cost ); - if (abil.flags & ABFLAG_PERMANENT_MP) - rot_mp(1); - } + if (!you.duration[DUR_REPEL_UNDEAD]) + mpr( "You feel a holy aura protecting you." ); - if (abil.hp_cost) - { - dec_hp( abil.hp_cost, false ); - if (abil.flags & ABFLAG_PERMANENT_HP) - rot_hp(1); - } + you.duration[DUR_REPEL_UNDEAD] += 8 + + roll_dice(2, 2 * you.skills[SK_INVOCATIONS]); - if (food_cost) - make_hungry( food_cost, false ); + if (you.duration[ DUR_REPEL_UNDEAD ] > 50) + you.duration[ DUR_REPEL_UNDEAD ] = 50; - if (piety_cost) - lose_piety( piety_cost ); + exercise(SK_INVOCATIONS, 1); + break; + + case ABIL_ZIN_HEALING: + if (!cast_healing( 3 + (you.skills[SK_INVOCATIONS] / 6) )) + break; - return (true); -} // end activate_ability() + exercise(SK_INVOCATIONS, 1 + random2(3)); + break; + case ABIL_ZIN_PESTILENCE: + mpr( "You call forth a swarm of pestilential beasts!" ); -// Lists any abilities the player may possess -char show_abilities( void ) -/*************************/ -{ - int loopy = 0; - char lines = 0; - unsigned char anything = 0; - char ki; - bool can_invoke = false; + if (!summon_swarm( you.skills[SK_INVOCATIONS] * 8, false, true )) + mpr( "Nothing seems to have answered your call." ); - const int num_lines = get_number_of_lines(); + exercise( SK_INVOCATIONS, 2 + random2(4) ); + break; - for (loopy = 0; loopy < 52; loopy++) - { - if (Curr_abil[loopy].is_invocation) - { - can_invoke = true; - break; - } - } + case ABIL_ZIN_HOLY_WORD: + holy_word( you.skills[SK_INVOCATIONS] * 8 ); + exercise(SK_INVOCATIONS, 3 + random2(5)); + break; + case ABIL_ZIN_SUMMON_GUARDIAN: + summon_ice_beast_etc(you.skills[SK_INVOCATIONS] * 4, MONS_ANGEL, true); + exercise(SK_INVOCATIONS, 8 + random2(10)); + break; - clrscr(); - cprintf(" Ability Cost Success"); - lines++; + case ABIL_TSO_SMITING: + if (your_spells( SPELL_SMITING, (2 + skill_bump(SK_INVOCATIONS)) * 6, + false ) == SPRET_ABORT) + return (false); + exercise( SK_INVOCATIONS, (coinflip()? 3 : 2) ); + break; - for (int do_invoke = 0; do_invoke < (can_invoke ? 2 : 1); do_invoke++) - { - if (do_invoke) + case ABIL_TSO_ANNIHILATE_UNDEAD: + if (spell_direction(spd, beam) == -1) { - anything++; - textcolor(BLUE); - cprintf(EOL " Invocations - "); - textcolor(LIGHTGREY); - lines++; + return (false); } - for (loopy = 0; loopy < 52; loopy++) - { - if (lines > num_lines - 2) - { - gotoxy(1, num_lines); - cprintf("-more-"); - - ki = getch(); - - if (ki == ESCAPE) - { - return (ESCAPE); - } - - if (ki >= 'A' && ki <= 'z') - { - return (ki); - } + zapping(ZAP_DISPEL_UNDEAD, you.skills[SK_INVOCATIONS] * 6, beam); + exercise(SK_INVOCATIONS, 2 + random2(4)); + break; - if (ki == 0) - ki = getch(); + case ABIL_TSO_CLEANSING_FLAME: + if (spell_direction(spd, beam) == -1) + { + return (false); + } - lines = 0; - clrscr(); - gotoxy(1, 1); - anything = 0; - } + zapping(ZAP_CLEANSING_FLAME, 20 + you.skills[SK_INVOCATIONS] * 6, beam); + exercise(SK_INVOCATIONS, 3 + random2(6)); + break; - if (Curr_abil[loopy].which != ABIL_NON_ABILITY - && (do_invoke == Curr_abil[loopy].is_invocation)) - { - anything++; + case ABIL_TSO_SUMMON_DAEVA: + summon_ice_beast_etc(you.skills[SK_INVOCATIONS] * 4, MONS_DAEVA, true); + exercise(SK_INVOCATIONS, 8 + random2(10)); + break; - if (lines > 0) - cprintf(EOL); + case ABIL_KIKU_RECALL_UNDEAD_SLAVES: + recall(1); + exercise(SK_INVOCATIONS, 1); + break; - lines++; + case ABIL_KIKU_ENSLAVE_UNDEAD: + if (spell_direction(spd, beam) == -1) + { + return (false); + } - const struct ability_def abil = get_ability_def( Curr_abil[loopy].which ); + zapping( ZAP_ENSLAVE_UNDEAD, you.skills[SK_INVOCATIONS] * 8, beam ); + exercise(SK_INVOCATIONS, 5 + random2(5)); + break; - cprintf( " %c - %s", index_to_letter(loopy), abil.name ); - - // Output costs: - gotoxy( 35, wherey() ); - - std::string cost_str = make_cost_description( abil ); - - if (cost_str.length() > 24) - cost_str = cost_str.substr( 0, 24 ); - - cprintf( "%s", cost_str.c_str() ); + case ABIL_KIKU_INVOKE_DEATH: + summon_ice_beast_etc( + 20 + you.skills[SK_INVOCATIONS] * 3, MONS_REAPER, true); + exercise(SK_INVOCATIONS, 10 + random2(14)); + break; - gotoxy(60, wherey()); + case ABIL_YRED_ANIMATE_CORPSE: + mpr("You call on the dead to walk for you..."); - cprintf( "%s", failure_rate_to_string(Curr_abil[loopy].fail)); + animate_a_corpse( you.x_pos, you.y_pos, BEH_FRIENDLY, + you.pet_target, CORPSE_BODY ); - gotoxy(70, wherey()); - } // end if conditional - } // end "for loopy" - } + exercise(SK_INVOCATIONS, 2 + random2(4)); + break; - if (anything > 0) - { - ki = getch(); + case ABIL_YRED_RECALL_UNDEAD: + recall(1); + exercise(SK_INVOCATIONS, 2 + random2(4)); + break; - if (ki >= 'A' && ki <= 'z') - { - return (ki); - } + case ABIL_YRED_ANIMATE_DEAD: + mpr("You call on the dead to walk for you..."); - if (ki == 0) - ki = getch(); + animate_dead( 1 + you.skills[SK_INVOCATIONS], BEH_FRIENDLY, + you.pet_target, 1 ); - return (ki); - } + exercise(SK_INVOCATIONS, 2 + random2(4)); + break; - ki = getch(); + case ABIL_YRED_DRAIN_LIFE: + drain_life( you.skills[SK_INVOCATIONS] ); + exercise(SK_INVOCATIONS, 2 + random2(4)); + break; - return (ki); -} // end show_abilities() + case ABIL_YRED_CONTROL_UNDEAD: + mass_enchantment( ENCH_CHARM, you.skills[SK_INVOCATIONS] * 8, MHITYOU ); + exercise(SK_INVOCATIONS, 3 + random2(4)); + break; + case ABIL_SIF_MUNA_CHANNEL_ENERGY: + mpr("You channel some magical energy."); -bool generate_abilities( bool check_confused ) -/*****************************/ -{ - int loopy; + inc_mp(1 + random2(you.skills[SK_INVOCATIONS] / 4 + 2), false); + exercise(SK_INVOCATIONS, 1 + random2(3)); + break; - // fill array of structs with "empty" values {dlb}: - for (loopy = 0; loopy < 52; loopy++) - { - Curr_abil[loopy].which = ABIL_NON_ABILITY; - Curr_abil[loopy].fail = 100; - Curr_abil[loopy].is_invocation = false; - } + case ABIL_OKAWARU_MIGHT: + potion_effect( POT_MIGHT, you.skills[SK_INVOCATIONS] * 8 ); + exercise(SK_INVOCATIONS, 1 + random2(3)); + break; - // first we do the racial abilities: - - // Mummies get the ability to restore HPs and stats, but it - // costs permanent MP (and those can never be recovered). -- bwr - if (you.species == SP_MUMMY && you.experience_level >= 13) - { - insert_ability( ABIL_MUMMY_RESTORATION, check_confused ); - } + case ABIL_OKAWARU_HEALING: + if (!cast_healing( 3 + (you.skills[SK_INVOCATIONS] / 6) )) + break; - // checking for species-related abilities and mutagenic counterparts {dlb}: - if (you.attribute[ATTR_TRANSFORMATION] == TRAN_NONE - && ((you.species == SP_GREY_ELF && you.experience_level >= 5) - || (you.species == SP_HIGH_ELF && you.experience_level >= 15))) - { - insert_ability( ABIL_GLAMOUR, check_confused ); - } + exercise(SK_INVOCATIONS, 2 + random2(5)); + break; - if (you.species == SP_NAGA) - { - if (you.mutation[MUT_BREATHE_POISON]) - insert_ability( ABIL_BREATHE_POISON, check_confused ); - else - insert_ability( ABIL_SPIT_POISON, check_confused ); - } - else if (you.mutation[MUT_SPIT_POISON]) - { - insert_ability( ABIL_SPIT_POISON, check_confused ); - } + case ABIL_OKAWARU_HASTE: + potion_effect( POT_SPEED, you.skills[SK_INVOCATIONS] * 8 ); + exercise(SK_INVOCATIONS, 3 + random2(7)); + break; - if (player_genus(GENPC_DRACONIAN)) - { - if (you.experience_level >= 7) + case ABIL_MAKHLEB_MINOR_DESTRUCTION: + if (spell_direction(spd, beam) == -1) { - const int ability = ( - (you.species == SP_GREEN_DRACONIAN) ? ABIL_BREATHE_POISON : - (you.species == SP_RED_DRACONIAN) ? ABIL_BREATHE_FIRE : - (you.species == SP_WHITE_DRACONIAN) ? ABIL_BREATHE_FROST : - (you.species == SP_GOLDEN_DRACONIAN) ? ABIL_SPIT_ACID : - (you.species == SP_BLACK_DRACONIAN) ? ABIL_BREATHE_LIGHTNING : - (you.species == SP_PURPLE_DRACONIAN) ? ABIL_BREATHE_POWER : - (you.species == SP_PALE_DRACONIAN) ? ABIL_BREATHE_STEAM : - (you.species == SP_MOTTLED_DRACONIAN)? ABIL_BREATHE_STICKY_FLAME: - -1); - if (ability != -1) - insert_ability( ability, check_confused ); + return (false); } - } - - //jmf: alternately put check elsewhere - if ((you.level_type == LEVEL_DUNGEON && you.mutation[MUT_MAPPING]) || - (you.level_type == LEVEL_PANDEMONIUM && you.mutation[MUT_MAPPING]==3)) - { - insert_ability( ABIL_MAPPING, check_confused ); - } - if (!you.duration[DUR_CONTROLLED_FLIGHT] && !player_is_levitating()) - { - // 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) - insert_ability( ABIL_FLY, check_confused ); - else if (player_genus(GENPC_DRACONIAN) && you.mutation[MUT_BIG_WINGS]) - insert_ability( ABIL_FLY_II, check_confused ); - } + power = you.skills[SK_INVOCATIONS] + + random2( 1 + you.skills[SK_INVOCATIONS] ) + + random2( 1 + you.skills[SK_INVOCATIONS] ); - // demonic powers {dlb}: - if (you.mutation[MUT_SUMMON_MINOR_DEMONS]) - insert_ability( ABIL_SUMMON_MINOR_DEMON, check_confused ); + switch (random2(5)) + { + case 0: zapping( ZAP_FLAME, power, beam ); break; + case 1: zapping( ZAP_PAIN, power, beam ); break; + case 2: zapping( ZAP_STONE_ARROW, power, beam ); break; + case 3: zapping( ZAP_ELECTRICITY, power, beam ); break; + case 4: zapping( ZAP_BREATHE_ACID, power / 2, beam ); break; + } - if (you.mutation[MUT_SUMMON_DEMONS]) - insert_ability( ABIL_SUMMON_DEMONS, check_confused ); + exercise(SK_INVOCATIONS, 1); + break; - if (you.mutation[MUT_HURL_HELLFIRE]) - insert_ability( ABIL_HELLFIRE, check_confused ); + case ABIL_MAKHLEB_LESSER_SERVANT_OF_MAKHLEB: + summon_ice_beast_etc( 20 + you.skills[SK_INVOCATIONS] * 3, + MONS_NEQOXEC + random2(5), true ); - if (you.mutation[MUT_CALL_TORMENT]) - insert_ability( ABIL_TORMENT, check_confused ); + exercise(SK_INVOCATIONS, 2 + random2(3)); + break; - if (you.mutation[MUT_RAISE_DEAD]) - insert_ability( ABIL_RAISE_DEAD, check_confused ); + case ABIL_MAKHLEB_MAJOR_DESTRUCTION: + if (spell_direction(spd, beam) == -1) + { + return (false); + } - if (you.mutation[MUT_CONTROL_DEMONS]) - insert_ability( ABIL_CONTROL_DEMON, check_confused ); + power = you.skills[SK_INVOCATIONS] * 3 + + random2( 1 + you.skills[SK_INVOCATIONS] ) + + random2( 1 + you.skills[SK_INVOCATIONS] ); - if (you.mutation[MUT_PANDEMONIUM]) - insert_ability( ABIL_TO_PANDEMONIUM, check_confused ); + switch (random2(8)) + { + case 0: zapping( ZAP_FIRE, power, beam ); break; + case 1: zapping( ZAP_FIREBALL, power, beam ); break; + case 2: zapping( ZAP_LIGHTNING, power, beam ); break; + case 3: zapping( ZAP_NEGATIVE_ENERGY, power, beam ); break; + case 4: zapping( ZAP_STICKY_FLAME, power, beam ); break; + case 5: zapping( ZAP_IRON_BOLT, power, beam ); break; + case 6: zapping( ZAP_ORB_OF_ELECTRICITY, power, beam ); break; - if (you.mutation[MUT_CHANNEL_HELL]) - insert_ability( ABIL_CHANNELING, check_confused ); + case 7: + you.attribute[ATTR_DIVINE_LIGHTNING_PROTECTION] = 1; + mpr("Makhleb hurls a blast of lightning!"); - if (you.mutation[MUT_THROW_FLAMES]) - insert_ability( ABIL_THROW_FLAME, check_confused ); + // make a divine lightning bolt... + beam.beam_source = NON_MONSTER; + beam.type = SYM_BURST; + beam.damage = dice_def( 3, 30 ); + beam.flavour = BEAM_ELECTRICITY; + beam.target_x = you.x_pos; + beam.target_y = you.y_pos; + beam.name = "blast of lightning"; + beam.colour = LIGHTCYAN; + beam.thrower = KILL_YOU; + beam.aux_source = "Makhleb's lightning strike"; + beam.ex_size = 1 + you.skills[SK_INVOCATIONS] / 8; + beam.is_tracer = false; - if (you.mutation[MUT_THROW_FROST]) - insert_ability( ABIL_THROW_FROST, check_confused ); + // ... and fire! + explosion(beam); - if (you.mutation[MUT_SMITE]) - insert_ability( ABIL_BOLT_OF_DRAINING, check_confused ); + // protection down + mpr("Your divine protection wanes."); + you.attribute[ATTR_DIVINE_LIGHTNING_PROTECTION] = 0; + break; + } - if (you.duration[DUR_TRANSFORMATION]) - insert_ability( ABIL_END_TRANSFORMATION, check_confused ); + exercise(SK_INVOCATIONS, 3 + random2(5)); + break; - if (you.mutation[MUT_BLINK]) - insert_ability( ABIL_BLINK, check_confused ); + case ABIL_MAKHLEB_GREATER_SERVANT_OF_MAKHLEB: + summon_ice_beast_etc( 20 + you.skills[SK_INVOCATIONS] * 3, + MONS_EXECUTIONER + random2(5), + true ); - if (you.mutation[MUT_TELEPORT_AT_WILL]) - insert_ability( ABIL_TELEPORTATION, check_confused ); + exercise(SK_INVOCATIONS, 6 + random2(6)); + break; - // gods take abilities away until penance completed -- bwr - if (!player_under_penance() && !silenced( you.x_pos, you.y_pos )) - { - for ( int i = 0; i < MAX_GOD_ABILITIES; ++i ) + case ABIL_TROG_BERSERK: + // Trog abilities don't use or train invocations. + if (you.hunger_state < HS_SATIATED) { - if ( you.piety >= piety_breakpoint(i) ) - { - ability_type abil = god_abilities[you.religion][i]; - if ( abil != ABIL_NON_ABILITY ) - insert_ability(abil, check_confused); - } + mpr("You're too hungry to berserk."); + return (false); } - } - // and finally, the ability to opt-out of your faith {dlb}: - if (you.religion != GOD_NO_GOD && !silenced( you.x_pos, you.y_pos )) - insert_ability( ABIL_RENOUNCE_RELIGION, check_confused ); + go_berserk(true); + break; - //jmf: check for breath weapons -- they're exclusive of each other I hope! - // better make better ones first. - if (you.attribute[ATTR_TRANSFORMATION] == TRAN_SERPENT_OF_HELL) - { - insert_ability( ABIL_BREATHE_HELLFIRE, check_confused ); - } - else if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON - || you.mutation[MUT_BREATHE_FLAMES]) - { - insert_ability( ABIL_BREATHE_FIRE, check_confused ); - } + case ABIL_TROG_MIGHT: + // Trog abilities don't use or train invocations. + potion_effect( POT_MIGHT, 150 ); + break; - // checking for unreleased delayed fireball - if (you.attribute[ ATTR_DELAYED_FIREBALL ]) - { - insert_ability( ABIL_DELAYED_FIREBALL, check_confused ); - } + case ABIL_TROG_HASTE_SELF: + // Trog abilities don't use or train invocations. + potion_effect( POT_SPEED, 150 ); + break; - // evocations from items: - if (scan_randarts(RAP_BLINK)) - insert_ability( ABIL_EVOKE_BLINK, check_confused ); + case ABIL_SIF_MUNA_FORGET_SPELL: + cast_selective_amnesia(true); + break; - if (wearing_amulet(AMU_RAGE) || scan_randarts(RAP_BERSERK)) - insert_ability( ABIL_EVOKE_BERSERK, check_confused ); + case ABIL_ELYVILON_LESSER_HEALING: + if (!cast_healing( 3 + (you.skills[SK_INVOCATIONS] / 6) )) + break; - if (scan_randarts( RAP_MAPPING )) - insert_ability( ABIL_EVOKE_MAPPING, check_confused ); + exercise( SK_INVOCATIONS, 1 ); + break; - if (player_equip( EQ_RINGS, RING_INVISIBILITY ) - || player_equip_ego_type( EQ_ALL_ARMOUR, SPARM_DARKNESS ) - || scan_randarts( RAP_INVISIBLE )) - { - // Now you can only turn invisibility off if you have an - // activatable item. Wands and potions allow will have - // to time out. -- bwr - if (you.invis) - insert_ability( ABIL_EVOKE_TURN_VISIBLE, check_confused ); - else - insert_ability( ABIL_EVOKE_TURN_INVISIBLE, check_confused ); - } + case ABIL_ELYVILON_PURIFICATION: + purification(); + exercise( SK_INVOCATIONS, 2 + random2(3) ); + break; - //jmf: "upgrade" for draconians -- expensive flight - // note: this ability only applies to this counter - if (player_equip( EQ_RINGS, RING_LEVITATION ) - || player_equip_ego_type( EQ_BOOTS, SPARM_LEVITATION ) - || scan_randarts( RAP_LEVITATE )) - { - // 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 - if (you.levitation) - insert_ability( ABIL_EVOKE_STOP_LEVITATING, check_confused ); - else - insert_ability( ABIL_EVOKE_LEVITATE, check_confused ); - } - - if (player_equip( EQ_RINGS, RING_TELEPORTATION ) - || scan_randarts( RAP_CAN_TELEPORT )) - { - insert_ability( ABIL_EVOKE_TELEPORTATION, check_confused ); - } + case ABIL_ELYVILON_HEALING: + if (!cast_healing( 10 + (you.skills[SK_INVOCATIONS] / 3) )) + break; - // this is a shameless kludge for the time being {dlb}: - // still shameless. -- bwr - for (loopy = 0; loopy < 52; loopy++) - { - if (Curr_abil[loopy].which != ABIL_NON_ABILITY) - return (true); - } + exercise( SK_INVOCATIONS, 3 + random2(5) ); + break; - return (false); -} // end generate_abilities() + case ABIL_ELYVILON_RESTORATION: + restore_stat( STAT_ALL, false ); + unrot_hp( 100 ); -// 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( int abil, char letter ) -{ - int i; - const int index = letter_to_index( letter ); + exercise( SK_INVOCATIONS, 4 + random2(6) ); + break; - for (i = 0; i < 52; i++) - { - if (you.ability_letter_table[i] == abil) + case ABIL_ELYVILON_GREATER_HEALING: + if (!cast_healing( 20 + you.skills[SK_INVOCATIONS] * 2 )) 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; - } -} + exercise( SK_INVOCATIONS, 6 + random2(10) ); + break; -// 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; -} + case ABIL_LUGONU_ABYSS_EXIT: + if ( you.level_type != LEVEL_ABYSS ) + { + mpr("You aren't in the Abyss!"); + return false; // don't incur costs + } + banished(DNGN_EXIT_ABYSS); + exercise(SK_INVOCATIONS, 8 + random2(10)); -void set_god_ability_slots( void ) -{ - ASSERT( you.religion != GOD_NO_GOD ); + // Lose 1d2 permanent HP + you.hp_max -= (coinflip() ? 2 : 1); + // Deflate HP + set_hp( 1 + random2(you.hp), false ); - int i; + // Lose 1d2 permanent MP + rot_mp(coinflip() ? 2 : 1); + // Deflate MP + if (you.magic_points) + set_mp(random2(you.magic_points), false); + break; - set_god_ability_helper( ABIL_RENOUNCE_RELIGION, 'X' ); + case ABIL_LUGONU_BEND_SPACE: + lugonu_bends_space(); + exercise(SK_INVOCATIONS, 2 + random2(3)); + break; - // clear out other god invocations - for (i = 0; i < 52; i++) + case ABIL_LUGONU_SUMMON_DEMONS: { - 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; + int ndemons = 1 + you.skills[SK_INVOCATIONS] / 4; + if (ndemons > 5) + ndemons = 5; + + for ( int i = 0; i < ndemons; ++i ) + summon_ice_beast_etc( 20 + you.skills[SK_INVOCATIONS] * 3, + summon_any_demon(DEMON_COMMON), true); + + exercise(SK_INVOCATIONS, 6 + random2(6)); + break; } - // finally, add in current god's invocations in traditional slots: - int num = 0; - for ( i = 0; i < MAX_GOD_ABILITIES; ++i ) - { - if ( god_abilities[you.religion][i] != ABIL_NON_ABILITY ) + case ABIL_LUGONU_ABYSS_ENTER: + if (you.level_type == LEVEL_ABYSS) { - set_god_ability_helper(god_abilities[you.religion][i], - (Options.lowercase_invocations ? 'a' : 'A') + num); - ++num; + mpr("You're already here."); + return false; } - } -} + else if (you.level_type == LEVEL_PANDEMONIUM) + { + mpr("That doesn't work from Pandemonium."); + return false; + } + + activate_notes(false); // this banishment shouldn't be noted + banished(DNGN_ENTER_ABYSS); + activate_notes(true); + break; + //jmf: intended as invocations from evil god(s): + case ABIL_CHARM_SNAKE: + cast_snake_charm( you.experience_level * 2 + + you.skills[SK_INVOCATIONS] * 3 ); -// returns index to Curr_abil, -1 on failure -static int find_ability_slot( int which_ability ) -/***********************************************/ -{ - int slot; - for (slot = 0; slot < 52; slot++) - { - if (you.ability_letter_table[slot] == which_ability) - break; - } + exercise(SK_INVOCATIONS, 2 + random2(4)); + break; - // no requested slot, find new one and make it preferred. - if (slot == 52) - { - // skip over a-e if player prefers them for invocations - for (slot = (Options.lowercase_invocations ? 5 : 0); slot < 52; slot++) + case ABIL_TRAN_SERPENT_OF_HELL: + transform(10 + (you.experience_level * 2) + + (you.skills[SK_INVOCATIONS] * 3), TRAN_SERPENT_OF_HELL); + + exercise(SK_INVOCATIONS, 6 + random2(9)); + break; + + case ABIL_BREATHE_HELLFIRE: + if (you.duration[DUR_BREATH_WEAPON]) { - if (you.ability_letter_table[slot] == ABIL_NON_ABILITY) - break; + canned_msg(MSG_CANNOT_DO_YET); + return (false); } - // if we skipped over a-e to reserve them, try them now - if (Options.lowercase_invocations && slot == 52) + if (your_spells( SPELL_HELLFIRE, + 20 + you.experience_level, false ) == SPRET_ABORT) + return (false); + + you.duration[DUR_BREATH_WEAPON] += + 3 + random2(5) + random2(30 - you.experience_level); + break; + + case ABIL_ROTTING: + cast_rotting(you.experience_level * 2 + you.skills[SK_INVOCATIONS] * 3); + exercise(SK_INVOCATIONS, 2 + random2(4)); + break; + + case ABIL_TORMENT_II: + if (you.is_undead) { - for (slot = 5; slot >= 0; slot--) - { - if (you.ability_letter_table[slot] == ABIL_NON_ABILITY) - break; - } + mpr("The unliving cannot use this ability."); + return (false); } - // All letters are assigned, check Curr_abil and try to steal a letter - if (slot == 52) - { - // backwards, to protect the low lettered slots from replacement - for (slot = 51; slot >= 0; slot--) - { - if (Curr_abil[slot].which == ABIL_NON_ABILITY) - break; - } + torment(TORMENT_GENERIC, you.x_pos, you.y_pos); + exercise(SK_INVOCATIONS, 2 + random2(4)); + break; - // no slots at all == no hope of adding - if (slot < 0) - return (-1); + case ABIL_RENOUNCE_RELIGION: + if (yesno("Really renounce your faith, foregoing its fabulous benefits?") + && yesno( "Are you sure you won't change your mind later?" )) + { + excommunication(); + } + else + { + canned_msg(MSG_OK); } + break; - // this ability now takes over this slot - you.ability_letter_table[slot] = which_ability; + case ABIL_NON_ABILITY: + mpr("Sorry, you can't do that."); + break; } - - return (slot); + return true; } -static bool insert_ability( int which_ability, bool check_conf ) -/**********************************************/ +static void pay_ability_costs(const ability_def& abil) { - ASSERT( which_ability != ABIL_NON_ABILITY ); + // currently only delayed fireball is instantaneous -- bwr + you.turn_is_over = !(abil.flags & ABFLAG_INSTANT); - int failure = 0; - bool perfect = false; // is perfect - bool invoc = false; + const int food_cost = abil.food_cost + random2avg(abil.food_cost, 2); + const int piety_cost = abil.piety_cost + + random2((abil.piety_cost + 1) / 2 + 1); - // Look through the table to see if there's a preference, else - // find a new empty slot for this ability. -- bwr - const int slot = find_ability_slot( which_ability ); - if (slot == -1) - return (false); +#if DEBUG_DIAGNOSTICS + mprf(MSGCH_DIAGNOSTICS, "Cost: mp=%d; hp=%d; food=%d; piety=%d", + abil.mp_cost, abil.hp_cost, food_cost, piety_cost ); +#endif - if (check_conf) + if (abil.mp_cost) { - const ability_def &abil = get_ability_def(which_ability); - if (you.conf && !testbits(abil.flags, ABFLAG_CONF_OK)) - return (false); + dec_mp( abil.mp_cost ); + if (abil.flags & ABFLAG_PERMANENT_MP) + rot_mp(1); } - Curr_abil[slot].which = which_ability; - - switch (which_ability) + if (abil.hp_cost) { - // 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_GLAMOUR: - failure = 50 - (you.experience_level * 2); - break; + dec_hp( abil.hp_cost, false ); + if (abil.flags & ABFLAG_PERMANENT_HP) + rot_hp(1); + } - case ABIL_SPIT_POISON: - failure = ((you.species == SP_NAGA) ? 20 : 40) - - 10 * you.mutation[MUT_SPIT_POISON] - - you.experience_level; - break; + if (food_cost) + make_hungry( food_cost, false ); - case ABIL_EVOKE_MAPPING: - failure = 30 - you.skills[SK_EVOCATIONS]; - break; + if (piety_cost) + lose_piety( piety_cost ); +} - case ABIL_MAPPING: - failure = 40 - 10 * you.mutation[MUT_MAPPING] - you.experience_level; - break; +int choose_ability_menu(const std::vector& talents) +{ + Menu abil_menu(MF_SINGLESELECT | MF_ANYPRINTABLE); + abil_menu.set_highlighter(NULL); + abil_menu.set_title(new MenuEntry(" Ability Cost Success")); - case ABIL_BREATHE_FIRE: - failure = ((you.species == SP_RED_DRACONIAN) ? 30 : 50) - - 10 * you.mutation[MUT_BREATHE_FLAMES] - - you.experience_level; + int numbers[52]; + for ( int i = 0; i < 52; ++i ) + numbers[i] = i; - if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON) - failure -= 20; - break; + bool found_invocations = false; - 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; + // 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 = reinterpret_cast(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 = reinterpret_cast(numbers+i); + abil_menu.add_entry(me); + } + } + } - if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON) - failure -= 20; - break; + std::vector sel = abil_menu.show(); + redraw_screen(); + if ( sel.empty() ) + { + return -1; + } + else + { + ASSERT(sel.size() == 1); + ASSERT(sel[0]->hotkeys.size() == 1); + return (*(reinterpret_cast(sel[0]->data))); + } +} - case ABIL_BREATHE_STEAM: - failure = 20 - you.experience_level; +const char* ability_name(ability_type ability) +{ + return get_ability_def(ability).name; +} - if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON) - failure -= 20; - break; +static std::string describe_talent(const talent& tal) +{ + ASSERT( tal.which != ABIL_NON_ABILITY ); - case ABIL_FLY: // this is for kenku {dlb} - failure = 45 - (3 * you.experience_level); - break; + const ability_def& abil = get_ability_def( tal.which ); - case ABIL_FLY_II: // this is for draconians {dlb} - failure = 45 - (you.experience_level + you.strength); - break; - // end species abilties (some mutagenic) + std::ostringstream desc; + desc << std::left + << std::setw(30) << ability_name(tal.which) + << std::setw(24) << make_cost_description(tal.which) + << std::setw(10) << failure_rate_to_string(tal.fail); + return desc.str(); +} - // begin demonic powers {dlb} - case ABIL_THROW_FLAME: - case ABIL_THROW_FROST: - failure = 10 - you.experience_level; - break; +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); +} - case ABIL_SUMMON_MINOR_DEMON: - failure = 27 - you.experience_level; - break; +std::vector your_talents( bool check_confused ) +{ + std::vector talents; - case ABIL_CHANNELING: - case ABIL_BOLT_OF_DRAINING: - failure = 30 - you.experience_level; - break; + // first we do the racial abilities: + if (you.species == SP_MUMMY && you.experience_level >= 13) + add_talent(talents, ABIL_MUMMY_RESTORATION, check_confused); - case ABIL_CONTROL_DEMON: - failure = 35 - you.experience_level; - break; + // checking for species-related abilities and mutagenic counterparts {dlb}: + if (you.attribute[ATTR_TRANSFORMATION] == TRAN_NONE + && ((you.species == SP_GREY_ELF && you.experience_level >= 5) + || (you.species == SP_HIGH_ELF && you.experience_level >= 15))) + { + add_talent(talents, ABIL_GLAMOUR, check_confused); + } - case ABIL_SUMMON_DEMONS: - failure = 40 - you.experience_level; - break; + if (you.species == SP_NAGA) + { + if (you.mutation[MUT_BREATHE_POISON]) + add_talent(talents, ABIL_BREATHE_POISON, check_confused ); + else + add_talent(talents, ABIL_SPIT_POISON, check_confused ); + } + else if (you.mutation[MUT_SPIT_POISON]) + { + add_talent(talents, ABIL_SPIT_POISON, check_confused ); + } - case ABIL_TO_PANDEMONIUM: - failure = 57 - (you.experience_level * 2); - break; + if (player_genus(GENPC_DRACONIAN)) + { + if (you.experience_level >= 7) + { + const ability_type ability = ( + (you.species == SP_GREEN_DRACONIAN) ? ABIL_BREATHE_POISON : + (you.species == SP_RED_DRACONIAN) ? ABIL_BREATHE_FIRE : + (you.species == SP_WHITE_DRACONIAN) ? ABIL_BREATHE_FROST : + (you.species == SP_GOLDEN_DRACONIAN) ? ABIL_SPIT_ACID : + (you.species == SP_BLACK_DRACONIAN) ? ABIL_BREATHE_LIGHTNING : + (you.species == SP_PURPLE_DRACONIAN) ? ABIL_BREATHE_POWER : + (you.species == SP_PALE_DRACONIAN) ? ABIL_BREATHE_STEAM : + (you.species == SP_MOTTLED_DRACONIAN)? ABIL_BREATHE_STICKY_FLAME: + ABIL_NON_ABILITY); + if (ability != ABIL_NON_ABILITY) + add_talent(talents, ability, check_confused ); + } + } - case ABIL_HELLFIRE: - case ABIL_RAISE_DEAD: - failure = 50 - you.experience_level; - break; + //jmf: alternately put check elsewhere + if ((you.level_type == LEVEL_DUNGEON && you.mutation[MUT_MAPPING]) || + (you.level_type == LEVEL_PANDEMONIUM && you.mutation[MUT_MAPPING]==3)) + { + add_talent(talents, ABIL_MAPPING, check_confused ); + } - case ABIL_TORMENT: - failure = 60 - you.experience_level; - break; + if (!you.duration[DUR_CONTROLLED_FLIGHT] && !player_is_levitating()) + { + // 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) && you.mutation[MUT_BIG_WINGS]) + add_talent(talents, ABIL_FLY_II, check_confused ); + } - case ABIL_BLINK: - failure = 30 - (10 * you.mutation[MUT_BLINK]) - you.experience_level; - break; + // demonic powers {dlb}: + if (you.mutation[MUT_SUMMON_MINOR_DEMONS]) + add_talent(talents, ABIL_SUMMON_MINOR_DEMON, check_confused ); - case ABIL_TELEPORTATION: - failure = ((you.mutation[MUT_TELEPORT_AT_WILL] > 1) ? 30 : 50) - - you.experience_level; - break; - // end demonic powers {dlb} + if (you.mutation[MUT_SUMMON_DEMONS]) + add_talent(talents, ABIL_SUMMON_DEMONS, check_confused ); - // begin transformation abilities {dlb} - case ABIL_END_TRANSFORMATION: - perfect = true; - failure = 0; - break; + if (you.mutation[MUT_HURL_HELLFIRE]) + add_talent(talents, ABIL_HELLFIRE, check_confused ); - case ABIL_BREATHE_HELLFIRE: - failure = 32 - you.experience_level; - 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; + if (you.mutation[MUT_CALL_TORMENT]) + add_talent(talents, ABIL_TORMENT, check_confused ); - case ABIL_EVOKE_TURN_VISIBLE: - case ABIL_EVOKE_STOP_LEVITATING: - perfect = true; - failure = 0; - break; + if (you.mutation[MUT_RAISE_DEAD]) + add_talent(talents, ABIL_RAISE_DEAD, check_confused ); - case ABIL_EVOKE_LEVITATE: - case ABIL_EVOKE_BLINK: - failure = 40 - 2 * you.skills[SK_EVOCATIONS]; - break; + if (you.mutation[MUT_CONTROL_DEMONS]) + add_talent(talents, ABIL_CONTROL_DEMON, check_confused ); - case ABIL_EVOKE_BERSERK: - failure = 50 - 2 * you.skills[SK_EVOCATIONS]; + if (you.mutation[MUT_PANDEMONIUM]) + add_talent(talents, ABIL_TO_PANDEMONIUM, check_confused ); - if (you.species == SP_TROLL) - failure -= 30; - else if (player_genus(GENPC_DWARVEN) || you.species == SP_HILL_ORC - || you.species == SP_OGRE) + if (you.mutation[MUT_CHANNEL_HELL]) + add_talent(talents, ABIL_CHANNELING, check_confused ); + + if (you.mutation[MUT_THROW_FLAMES]) + add_talent(talents, ABIL_THROW_FLAME, check_confused ); + + if (you.mutation[MUT_THROW_FROST]) + add_talent(talents, ABIL_THROW_FROST, check_confused ); + + if (you.mutation[MUT_SMITE]) + add_talent(talents, ABIL_BOLT_OF_DRAINING, check_confused ); + + if (you.duration[DUR_TRANSFORMATION]) + add_talent(talents, ABIL_END_TRANSFORMATION, check_confused ); + + if (you.mutation[MUT_BLINK]) + add_talent(talents, ABIL_BLINK, check_confused ); + + if (you.mutation[MUT_TELEPORT_AT_WILL]) + add_talent(talents, ABIL_TELEPORTATION, check_confused ); + + // gods take abilities away until penance completed -- bwr + if (!player_under_penance() && !silenced( you.x_pos, you.y_pos )) + { + for ( int i = 0; i < MAX_GOD_ABILITIES; ++i ) { - failure -= 10; + if ( you.piety >= piety_breakpoint(i) ) + { + ability_type abil = god_abilities[you.religion][i]; + if ( abil != ABIL_NON_ABILITY ) + add_talent(talents,abil, check_confused); + } } - 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; + // and finally, the ability to opt-out of your faith {dlb}: + if (you.religion != GOD_NO_GOD && !silenced( you.x_pos, you.y_pos )) + add_talent(talents, ABIL_RENOUNCE_RELIGION, check_confused ); - case ABIL_ZIN_REPEL_UNDEAD: - case ABIL_TSO_REPEL_UNDEAD: - case ABIL_KIKU_RECALL_UNDEAD_SLAVES: - case ABIL_OKAWARU_MIGHT: - case ABIL_ELYVILON_LESSER_HEALING: - invoc = true; - failure = 30 - (you.piety / 20) - (6 * you.skills[SK_INVOCATIONS]); - break; + //jmf: check for breath weapons -- they're exclusive of each other I hope! + // better make better ones first. + if (you.attribute[ATTR_TRANSFORMATION] == TRAN_SERPENT_OF_HELL) + { + add_talent(talents, ABIL_BREATHE_HELLFIRE, check_confused ); + } + else if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON + || you.mutation[MUT_BREATHE_FLAMES]) + { + add_talent(talents, ABIL_BREATHE_FIRE, check_confused ); + } - // 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; + // checking for unreleased delayed fireball + if (you.attribute[ ATTR_DELAYED_FIREBALL ]) + { + add_talent(talents, ABIL_DELAYED_FIREBALL, check_confused ); + } - case ABIL_TROG_MIGHT: // piety >= 50 - invoc = true; - failure = 80 - you.piety; // starts at 30% - break; + // evocations from items: + if (scan_randarts(RAP_BLINK)) + add_talent(talents, ABIL_EVOKE_BLINK, check_confused ); - case ABIL_TROG_HASTE_SELF: // piety >= 100 - invoc = true; - failure = 160 - you.piety; // starts at 60% - break; + if (wearing_amulet(AMU_RAGE) || scan_randarts(RAP_BERSERK)) + add_talent(talents, ABIL_EVOKE_BERSERK, check_confused ); - case ABIL_YRED_ANIMATE_CORPSE: - invoc = true; - failure = 40 - (you.piety / 20) - (3 * you.skills[SK_INVOCATIONS]); - break; + if (scan_randarts( RAP_MAPPING )) + add_talent(talents, ABIL_EVOKE_MAPPING, check_confused ); - case ABIL_ZIN_HEALING: - case ABIL_TSO_SMITING: - case ABIL_OKAWARU_HEALING: - case ABIL_MAKHLEB_MINOR_DESTRUCTION: - case ABIL_SIF_MUNA_FORGET_SPELL: - case ABIL_KIKU_ENSLAVE_UNDEAD: - case ABIL_YRED_ANIMATE_DEAD: - case ABIL_MAKHLEB_LESSER_SERVANT_OF_MAKHLEB: - case ABIL_ELYVILON_HEALING: - invoc = true; - failure = 40 - (you.piety / 20) - (5 * you.skills[SK_INVOCATIONS]); - break; + if (player_equip( EQ_RINGS, RING_INVISIBILITY ) + || player_equip_ego_type( EQ_ALL_ARMOUR, SPARM_DARKNESS ) + || scan_randarts( RAP_INVISIBLE )) + { + // Now you can only turn invisibility off if you have an + // activatable item. Wands and potions allow will have + // to time out. -- bwr + if (you.invis) + add_talent(talents, ABIL_EVOKE_TURN_VISIBLE, check_confused ); + else + add_talent(talents, ABIL_EVOKE_TURN_INVISIBLE, check_confused ); + } - case ABIL_SIF_MUNA_CHANNEL_ENERGY: - invoc = true; - failure = 40 - you.intel - you.skills[SK_INVOCATIONS]; - break; + //jmf: "upgrade" for draconians -- expensive flight + // note: this ability only applies to this counter + if (player_equip( EQ_RINGS, RING_LEVITATION ) + || player_equip_ego_type( EQ_BOOTS, SPARM_LEVITATION ) + || scan_randarts( RAP_LEVITATE )) + { + // 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 + if (you.levitation) + add_talent(talents, ABIL_EVOKE_STOP_LEVITATING, check_confused ); + else + add_talent(talents, ABIL_EVOKE_LEVITATE, check_confused ); + } - case ABIL_YRED_RECALL_UNDEAD: - invoc = true; - failure = 50 - (you.piety / 20) - (you.skills[SK_INVOCATIONS] * 4); - break; + if (player_equip( EQ_RINGS, RING_TELEPORTATION ) + || scan_randarts( RAP_CAN_TELEPORT )) + { + add_talent(talents, ABIL_EVOKE_TELEPORTATION, check_confused ); + } - case ABIL_ZIN_PESTILENCE: - case ABIL_TSO_ANNIHILATE_UNDEAD: - invoc = true; - failure = 60 - (you.piety / 20) - (5 * you.skills[SK_INVOCATIONS]); - break; + // 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; - case ABIL_MAKHLEB_MAJOR_DESTRUCTION: - case ABIL_YRED_DRAIN_LIFE: - invoc = true; - failure = 60 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); - break; + // 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; - case ABIL_ZIN_HOLY_WORD: - case ABIL_TSO_CLEANSING_FLAME: - case ABIL_ELYVILON_RESTORATION: - case ABIL_YRED_CONTROL_UNDEAD: - case ABIL_OKAWARU_HASTE: - case ABIL_MAKHLEB_GREATER_SERVANT_OF_MAKHLEB: - invoc = true; - failure = 70 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); - break; + // 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; + } + } - case ABIL_ZIN_SUMMON_GUARDIAN: - case ABIL_TSO_SUMMON_DAEVA: - case ABIL_KIKU_INVOKE_DEATH: - case ABIL_ELYVILON_GREATER_HEALING: - invoc = true; - failure = 80 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); - break; + if ( good_key ) + { + talents[i].hotkey = k; + break; + } + } + // In theory, we could be left with an unreachable ability + // here (if you have 53 or more abilities simultaneously.) + } + + return talents; +} - //jmf: following for to-be-created gods - case ABIL_CHARM_SNAKE: - invoc = true; - failure = 40 - (you.piety / 20) - (3 * you.skills[SK_INVOCATIONS]); - break; +// 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 ); - case ABIL_TRAN_SERPENT_OF_HELL: - invoc = true; - failure = 80 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); - break; + for (i = 0; i < 52; i++) + { + if (you.ability_letter_table[i] == abil) + break; + } - case ABIL_ROTTING: - invoc = true; - failure = 60 - (you.piety / 20) - (5 * you.skills[SK_INVOCATIONS]); - 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; + } +} - case ABIL_TORMENT_II: - invoc = true; - failure = 70 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); - break; +// 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; +} - case ABIL_RENOUNCE_RELIGION: - invoc = true; - perfect = true; - failure = 0; - break; +void set_god_ability_slots( void ) +{ + ASSERT( you.religion != GOD_NO_GOD ); - // end invocations {dlb} - default: - failure = -1; - break; + int i; + + set_god_ability_helper( ABIL_RENOUNCE_RELIGION, 'X' ); + + // clear out other god invocations + for (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; } - // Perfect abilities are things like "renounce religion", which - // shouldn't have a failure rate ever. -- bwr - if (failure <= 0 && !perfect) - failure = 1; + // finally, add in current god's invocations in traditional slots: + int num = 0; + for ( 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], + (Options.lowercase_invocations ? 'a' : 'A') + num); + ++num; + } + } +} - if (failure > 100) - failure = 100; - Curr_abil[slot].fail = failure; - Curr_abil[slot].is_invocation = invoc; +// 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 if player prefers them for invocations + const int startpoint = (Options.lowercase_invocations ? 5 : 0); - return (true); -} // end insert_ability() + for (int slot = startpoint; slot < 52; slot++) + { + if (you.ability_letter_table[slot] == ABIL_NON_ABILITY) + return slot; + } + + // if we skipped over a-e to reserve them, try them now + for (int slot = startpoint - 1; slot >= 0; slot--) + { + if (you.ability_letter_table[slot] == ABIL_NON_ABILITY) + return slot; + } + + // All letters are assigned. + return -1; +} //////////////////////////////////////////////////////////////////////////// -- cgit v1.2.3-54-g00ecf