From 673bdae75485d14f759af597c3c62b99601f9a43 Mon Sep 17 00:00:00 2001 From: peterb12 Date: Thu, 21 Jul 2005 02:34:44 +0000 Subject: Initial revision git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@3 c06c8d41-db1a-0410-9941-cceddc491573 --- trunk/source/abl-show.cc | 2168 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 2168 insertions(+) create mode 100644 trunk/source/abl-show.cc (limited to 'trunk/source/abl-show.cc') diff --git a/trunk/source/abl-show.cc b/trunk/source/abl-show.cc new file mode 100644 index 0000000000..867919159e --- /dev/null +++ b/trunk/source/abl-show.cc @@ -0,0 +1,2168 @@ +/* + * File: abl-show.cc + * Summary: Functions related to special abilities. + * Written by: Linley Henzell + * + * Change History (most recent first): + * + * <6> 19mar2000 jmf added elvish Glamour + * <5> 11/06/99 cdl reduced power of minor destruction + * + * <4> 9/25/99 cdl linuxlib -> liblinux + * + * <3> 5/20/99 BWR Now use scan_randarts to + * check for flags, rather than + * only checking the weapon. + * + * <2> 5/20/99 BWR Extended screen line support + * + * <1> -/--/-- LRH Created + */ + +#include "AppHdr.h" +#include "abl-show.h" + +#include +#include +#include + +#ifdef DOS +#include +#endif + +#include "externs.h" + +#include "beam.h" +#include "effects.h" +#include "food.h" +#include "it_use2.h" +#include "macro.h" +#include "message.h" +#include "misc.h" +#include "monplace.h" +#include "player.h" +#include "religion.h" +#include "skills.h" +#include "skills2.h" +#include "spl-cast.h" +#include "spl-util.h" +#include "spells1.h" +#include "spells2.h" +#include "spells3.h" +#include "spells4.h" +#include "stuff.h" +#include "transfor.h" +#include "view.h" + + +#ifdef LINUX +#include "liblinux.h" +#endif + +// 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} +struct talent +{ + int which; + int fail; + bool is_invocation; +}; + +static FixedVector< talent, 52 > Curr_abil; + +static bool insert_ability( int which_ability ); + +// 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 struct 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_GLAMOUR, "Glamour", 5, 0, 40, 0, ABFLAG_DELAY }, + + { ABIL_MAPPING, "Sense Surroundings", 0, 0, 30, 0, ABFLAG_NONE }, + { ABIL_TELEPORTATION, "Teleportation", 3, 0, 200, 0, ABFLAG_NONE }, + { ABIL_BLINK, "Blink", 1, 0, 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 }, + + // Handled with breath weapons, but doesn't cause a breathing delay + { 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", 3, 3, 75, 0, ABFLAG_NONE }, + { ABIL_SUMMON_DEMON, "Summon Demon", 5, 5, 150, 0, ABFLAG_NONE }, + { ABIL_HELLFIRE, "Hellfire", 8, 8, 200, 0, ABFLAG_NONE }, + { ABIL_TORMENT, "Torment", 9, 0, 250, 0, ABFLAG_PAIN }, + { ABIL_RAISE_DEAD, "Raise Dead", 5, 5, 150, 0, ABFLAG_NONE }, + { ABIL_CONTROL_DEMON, "Control Demon", 4, 4, 100, 0, ABFLAG_NONE }, + { ABIL_TO_PANDEMONIUM, "Gate Yourself to Pandemonium", 7, 0, 200, 0, ABFLAG_NONE }, + { ABIL_CHANNELING, "Channeling", 1, 0, 30, 0, ABFLAG_NONE }, + { ABIL_THROW_FLAME, "Throw Flame", 1, 1, 50, 0, ABFLAG_NONE }, + { ABIL_THROW_FROST, "Throw Frost", 1, 1, 50, 0, ABFLAG_NONE }, + { ABIL_BOLT_OF_DRAINING, "Bolt of Draining", 4, 4, 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, "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_MAPPING, "Evoke Sense Surroundings", 0, 0, 30, 0, ABFLAG_NONE }, + { ABIL_EVOKE_TELEPORTATION, "Evoke Teleportation", 3, 0, 200, 0, ABFLAG_NONE }, + { ABIL_EVOKE_BLINK, "Evoke Blink", 1, 0, 50, 0, ABFLAG_NONE }, + + { 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_REPEL_UNDEAD, "Repel Undead", 1, 0, 100, 0, ABFLAG_NONE }, + { ABIL_ZIN_HEALING, "Minor Healing", 2, 0, 50, 1, ABFLAG_NONE }, + { ABIL_ZIN_PESTILENCE, "Pestilence", 3, 0, 100, 2, ABFLAG_NONE }, + { ABIL_ZIN_HOLY_WORD, "Holy Word", 6, 0, 150, 3, ABFLAG_NONE }, + { ABIL_ZIN_SUMMON_GUARDIAN, "Summon Guardian", 7, 0, 150, 4, ABFLAG_NONE }, + + // The Shining One + { ABIL_TSO_REPEL_UNDEAD, "Repel Undead", 1, 0, 100, 0, ABFLAG_NONE }, + { ABIL_TSO_SMITING, "Smiting", 3, 0, 50, 2, ABFLAG_NONE }, + { ABIL_TSO_ANNIHILATE_UNDEAD, "Annihilate Undead", 3, 0, 50, 2, ABFLAG_NONE }, + { ABIL_TSO_THUNDERBOLT, "Thunderbolt", 5, 0, 100, 2, ABFLAG_NONE }, + { ABIL_TSO_SUMMON_DAEVA, "Summon Daeva", 8, 0, 150, 4, ABFLAG_NONE }, + + // Kikubaaqudgha + { ABIL_KIKU_RECALL_UNDEAD_SLAVES, "Recall Undead Slaves", 2, 0, 50, 0, ABFLAG_NONE }, + { ABIL_KIKU_ENSLAVE_UNDEAD, "Enslave Undead", 4, 0, 150, 3, ABFLAG_NONE }, + { ABIL_KIKU_INVOKE_DEATH, "Invoke Death", 4, 0, 250, 3, ABFLAG_NONE }, + + // Yredelemnul + { ABIL_YRED_ANIMATE_CORPSE, "Animate Corpse", 1, 0, 50, 0, ABFLAG_NONE }, + { ABIL_YRED_RECALL_UNDEAD, "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_CONTROL_UNDEAD, "Control Undead", 5, 0, 150, 2, ABFLAG_NONE }, + + // Vehumet + { ABIL_VEHUMET_CHANNEL_ENERGY, "Channel Energy", 0, 0, 50, 0, ABFLAG_NONE }, + + // Okawaru + { ABIL_OKAWARU_MIGHT, "Might", 2, 0, 50, 1, ABFLAG_NONE }, + { ABIL_OKAWARU_HEALING, "Healing", 2, 0, 75, 1, ABFLAG_NONE }, + { ABIL_OKAWARU_HASTE, "Haste", 5, 0, 100, 3, 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_FORGET_SPELL, "Forget Spell", 5, 0, 0, 8, ABFLAG_NONE }, + + // Trog + { ABIL_TROG_BERSERK, "Berserk", 0, 0, 200, 0, ABFLAG_NONE }, + { ABIL_TROG_MIGHT, "Might", 0, 0, 200, 1, ABFLAG_NONE }, + { ABIL_TROG_HASTE_SELF, "Haste Self", 0, 0, 250, 3, ABFLAG_NONE }, + + // Elyvilon + { ABIL_ELYVILON_LESSER_HEALING, "Lesser Healing", 1, 0, 100, 0, ABFLAG_NONE }, + { ABIL_ELYVILON_PURIFICATION, "Purification", 2, 0, 150, 1, ABFLAG_NONE }, + { ABIL_ELYVILON_HEALING, "Healing", 2, 0, 250, 2, ABFLAG_NONE }, + { ABIL_ELYVILON_RESTORATION, "Restoration", 3, 0, 400, 3, ABFLAG_NONE }, + { ABIL_ELYVILON_GREATER_HEALING, "Greater Healing", 6, 0, 600, 4, ABFLAG_NONE }, + + // These six are unused "evil" god abilities: + { ABIL_CHARM_SNAKE, "Charm Snake", 6, 0, 200, 5, ABFLAG_NONE }, + { ABIL_TRAN_SERPENT_OF_HELL, "Turn into Demonic Serpent", 16, 0, 600, 8, ABFLAG_NONE }, + { ABIL_BREATHE_HELLFIRE, "Breathe Hellfire", 0, 8, 200, 0, ABFLAG_BREATH }, + + { ABIL_ROTTING, "Rotting", 4, 4, 0, 2, ABFLAG_NONE }, + { ABIL_TORMENT_II, "Call Torment", 9, 0, 0, 3, ABFLAG_PAIN }, + { ABIL_SHUGGOTH_SEED, "Sow Shuggoth Seed", 12, 8, 0, 6, ABFLAG_NONE }, + + { ABIL_RENOUNCE_RELIGION, "Renounce Religion", 0, 0, 0, 0, ABFLAG_NONE }, +}; + + +const struct ability_def & get_ability_def( int abil ) +/****************************************************/ +{ + for (unsigned int i = 0; i < sizeof( Ability_List ); i++) + { + if (Ability_List[i].ability == abil) + return (Ability_List[i]); + } + + 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); +} + + +const std::string make_cost_description( const struct ability_def &abil ) +/***********************************************************************/ +{ + char tmp_buff[80]; // avoiding string steams for portability + std::string ret = ""; + + if (abil.mp_cost) + { + snprintf( tmp_buff, sizeof(tmp_buff), "%d%s MP", + abil.mp_cost, + (abil.flags & ABFLAG_PERMANENT_MP) ? " Permanent" : "" ); + + ret += tmp_buff; + } + + if (abil.hp_cost) + { + if (ret.length()) + ret += ", "; + + snprintf( tmp_buff, sizeof(tmp_buff), "%d%s HP", + abil.hp_cost, + (abil.flags & ABFLAG_PERMANENT_HP) ? " Permanent" : "" ); + + ret += tmp_buff; + } + + if (abil.food_cost) + { + if (ret.length()) + ret += ", "; + + ret += "Food"; // randomized and amount hidden from player + } + + if (abil.piety_cost) + { + if (ret.length()) + ret += ", "; + + ret += "Piety"; // randomized and amount hidden from player + } + + if (abil.flags & ABFLAG_BREATH) + { + if (ret.length()) + ret += ", "; + + ret += "Breath"; + } + + if (abil.flags & ABFLAG_DELAY) + { + if (ret.length()) + ret += ", "; + + ret += "Delay"; + } + + if (abil.flags & ABFLAG_PAIN) + { + if (ret.length()) + ret += ", "; + + ret += "Pain"; + } + + if (abil.flags & ABFLAG_EXHAUSTION) + { + if (ret.length()) + ret += ", "; + + ret += "Exhaustion"; + } + + if (abil.flags & ABFLAG_INSTANT) + { + if (ret.length()) + 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.length()) + ret += "None"; + + return (ret); +} + + +/* + Activates a menu which gives player access to all of their non-spell + special abilities - Eg naga's 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) +/*************************/ +{ + unsigned char keyin = 0; + unsigned char spc, spc2; + + int power; + struct dist abild; + struct bolt beam; + struct dist spd; + + unsigned char abil_used; + + // early returns prior to generation of ability list {dlb}: + if (you.conf) + { + mpr("You're too confused!"); + return (false); + } + + if (you.berserker) + { + canned_msg(MSG_TOO_BERSERK); + return (false); + } + + // populate the array of structs {dlb}: + if (!generate_abilities()) + { + mpr("Sorry, you're not good enough to have a special ability."); + return (false); + } + + bool need_redraw = false; + bool need_prompt = true; + bool need_getch = true; + + for (;;) + { + 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') + { + canned_msg( MSG_OK ); + return (false); + } + } + + spc = (int) keyin; + + if (!isalpha( spc )) + { + mpr("You can't do that."); + return (false); + } + + spc2 = letter_to_index(spc); + + if (Curr_abil[spc2].which == -1) + { + mpr("You can't do that."); + return (false); + } + + abil_used = spc2; + + // 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; + } + + if (hungerCheck && you.hunger_state < HS_HUNGRY) + { + mpr("You're too hungry."); + return (false); + } + + // no turning back now... {dlb} + const struct ability_def abil = get_ability_def(Curr_abil[abil_used].which); + + // currently only delayed fireball is instantaneous -- bwr + you.turn_is_over = ((abil.flags & ABFLAG_INSTANT) ? 0 : 1); + + if (random2avg(100, 3) < Curr_abil[abil_used].fail) + { + mpr("You fail to use your ability."); + return (false); + } + + if (!enough_mp( abil.mp_cost, false )) + return (false); + + if (!enough_hp( abil.hp_cost, false )) + return (false); + + // 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." ); + restore_stat( STAT_ALL, false ); + unrot_hp( 100 ); + break; + + case ABIL_DELAYED_FIREBALL: + // Note: power level of ball calculated at release -- bwr + fireball( calc_spell_power( SPELL_DELAYED_FIREBALL, true ) ); + + // only one allowed since this is instantaneous -- bwr + you.attribute[ ATTR_DELAYED_FIREBALL ] = 0; + break; + + case ABIL_GLAMOUR: + if (you.duration[DUR_GLAMOUR]) + { + canned_msg(MSG_CANNOT_DO_YET); + return (false); + } + + mpr("You use your Elvish wiles."); + + cast_glamour( 10 + random2(you.experience_level) + + random2(you.experience_level) ); + + you.duration[DUR_GLAMOUR] = 20 + random2avg(13, 3); + 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) + { + canned_msg(MSG_OK); + return (false); + } + else + { + mpr("You spit poison."); + + zapping( ZAP_SPIT_POISON, + you.experience_level + + you.mutation[MUT_SPIT_POISON] * 5 + + (you.species == SP_NAGA) * 10, + beam ); + + you.duration[DUR_BREATH_WEAPON] = 3 + random2(5); + } + break; + + case ABIL_EVOKE_MAPPING: // randarts + mpr("You sense your surroundings."); + + magic_mapping( 3 + roll_dice( 2, you.skills[SK_EVOCATIONS] ), + 40 + roll_dice( 2, you.skills[SK_EVOCATIONS] ) ); + + exercise( SK_EVOCATIONS, 1 ); + break; + + case ABIL_MAPPING: // Gnome + sense surrounds mut + mpr("You sense your surroundings."); + + magic_mapping( 3 + roll_dice( 2, you.experience_level ) + + you.mutation[MUT_MAPPING] * 10, + 40 + roll_dice( 2, you.experience_level ) ); + 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 ); + 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] + && Curr_abil[abil_used].which != ABIL_SPIT_ACID) + { + canned_msg(MSG_CANNOT_DO_YET); + return (false); + } + else if (spell_direction( abild, beam ) == -1) + { + canned_msg(MSG_OK); + 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) + + snprintf( info, INFO_SIZE, "You breathe fire%c", (power < 15)?'.':'!'); + mpr(info); + + 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_BREATHE_STEAM: + mpr("You exhale a blast of scalding steam."); + zapping(ZAP_BREATHE_STEAM, you.experience_level, beam); + break; + + } + + if (Curr_abil[abil_used].which != ABIL_SPIT_ACID) + { + you.duration[DUR_BREATH_WEAPON] = 3 + random2(5) + + random2(30 - you.experience_level); + } + + if (Curr_abil[abil_used].which == 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 + if (you.hunger_state < HS_SATIATED) + { + mpr("You're too hungry to berserk."); + return (false); + } + + 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."); + you.levitation = 100; + you.duration[DUR_CONTROLLED_FLIGHT] = 100; + } + 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 + } + break; + + // DEMONIC POWERS: + case ABIL_SUMMON_MINOR_DEMON: + summon_ice_beast_etc( you.experience_level * 4, + summon_any_demon(DEMON_LESSER) ); + break; + + case ABIL_SUMMON_DEMON: + summon_ice_beast_etc( you.experience_level * 4, + summon_any_demon(DEMON_COMMON) ); + break; + + case ABIL_HELLFIRE: + your_spells(SPELL_HELLFIRE, 20 + you.experience_level, false); + break; + + case ABIL_TORMENT: + if (you.is_undead) + { + mpr("The unliving cannot use this ability."); + return (false); + } + + torment(you.x_pos, you.y_pos); + break; + + case ABIL_RAISE_DEAD: + your_spells(SPELL_ANIMATE_DEAD, you.experience_level * 5, false); + break; + + case ABIL_CONTROL_DEMON: + if (spell_direction(abild, beam) == -1) + { + canned_msg(MSG_OK); + return (false); + } + + zapping(ZAP_CONTROL_DEMON, you.experience_level * 5, beam); + break; + + case ABIL_TO_PANDEMONIUM: + if (you.level_type == LEVEL_PANDEMONIUM) + { + mpr("You're already here."); + return (false); + } + + banished(DNGN_ENTER_PANDEMONIUM); + 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: + if (spell_direction(abild, beam) == -1) + { + canned_msg(MSG_OK); + return (false); + } + + zapping( (Curr_abil[abil_used].which == ABIL_THROW_FLAME ? ZAP_FLAME + : ZAP_FROST), + you.experience_level * 3, + beam ); + break; + + case ABIL_BOLT_OF_DRAINING: + if (spell_direction(abild, beam) == -1) + { + canned_msg(MSG_OK); + return (false); + } + + zapping(ZAP_NEGATIVE_ENERGY, you.experience_level * 6, beam); + 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); + } + + potion_effect( POT_INVISIBILITY, 2 * you.skills[SK_EVOCATIONS] + 5 ); + contaminate_player( 1 + random2(3) ); + exercise( SK_EVOCATIONS, 1 ); + break; + + case ABIL_EVOKE_TURN_VISIBLE: + mpr("You feel less transparent."); + you.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.levitation = 1; + break; + + case ABIL_END_TRANSFORMATION: + mpr("You feel almost normal."); + you.duration[DUR_TRANSFORMATION] = 2; + break; + + // INVOCATIONS: + case ABIL_ZIN_REPEL_UNDEAD: + case ABIL_TSO_REPEL_UNDEAD: + turn_undead(you.piety); + + if (!you.duration[DUR_REPEL_UNDEAD]) + mpr( "You feel a holy aura protecting you." ); + + you.duration[DUR_REPEL_UNDEAD] += 8 + + roll_dice(2, 2 * you.skills[SK_INVOCATIONS]); + + if (you.duration[ DUR_REPEL_UNDEAD ] > 50) + you.duration[ DUR_REPEL_UNDEAD ] = 50; + + exercise(SK_INVOCATIONS, 1); + break; + + case ABIL_ZIN_HEALING: + if (!cast_healing( 3 + (you.skills[SK_INVOCATIONS] / 6) )) + break; + + exercise(SK_INVOCATIONS, 1 + random2(3)); + break; + + case ABIL_ZIN_PESTILENCE: + mpr( "You call forth a swarm of pestilential beasts!" ); + + if (!summon_swarm( you.skills[SK_INVOCATIONS] * 8, false, true )) + mpr( "Nothing seems to have answered your call." ); + + exercise( SK_INVOCATIONS, 2 + random2(4) ); + 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); + exercise(SK_INVOCATIONS, 8 + random2(10)); + break; + + case ABIL_TSO_SMITING: + cast_smiting( you.skills[SK_INVOCATIONS] * 6 ); + exercise( SK_INVOCATIONS, (coinflip()? 3 : 2) ); + break; + + case ABIL_TSO_ANNIHILATE_UNDEAD: + if (spell_direction(spd, beam) == -1) + { + canned_msg(MSG_OK); + return (false); + } + + zapping(ZAP_DISPEL_UNDEAD, you.skills[SK_INVOCATIONS] * 6, beam); + exercise(SK_INVOCATIONS, 2 + random2(4)); + break; + + case ABIL_TSO_THUNDERBOLT: + if (spell_direction(spd, beam) == -1) + { + canned_msg(MSG_OK); + return (false); + } + + zapping(ZAP_LIGHTNING, you.skills[SK_INVOCATIONS] * 6, beam); + exercise(SK_INVOCATIONS, 3 + random2(6)); + break; + + case ABIL_TSO_SUMMON_DAEVA: + summon_ice_beast_etc(you.skills[SK_INVOCATIONS] * 4, MONS_DAEVA); + exercise(SK_INVOCATIONS, 8 + random2(10)); + break; + + case ABIL_KIKU_RECALL_UNDEAD_SLAVES: + recall(1); + exercise(SK_INVOCATIONS, 1); + break; + + case ABIL_KIKU_ENSLAVE_UNDEAD: + if (spell_direction(spd, beam) == -1) + { + canned_msg(MSG_OK); + return (false); + } + + zapping( ZAP_ENSLAVE_UNDEAD, you.skills[SK_INVOCATIONS] * 8, beam ); + exercise(SK_INVOCATIONS, 5 + random2(5)); + break; + + case ABIL_KIKU_INVOKE_DEATH: + summon_ice_beast_etc(20 + you.skills[SK_INVOCATIONS] * 3, MONS_REAPER); + exercise(SK_INVOCATIONS, 10 + random2(14)); + break; + + case ABIL_YRED_ANIMATE_CORPSE: + mpr("You call on the dead to walk for you..."); + + 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; + + case ABIL_YRED_CONTROL_UNDEAD: + mass_enchantment( ENCH_CHARM, you.skills[SK_INVOCATIONS] * 8, MHITYOU ); + exercise(SK_INVOCATIONS, 3 + random2(4)); + break; + + case ABIL_VEHUMET_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_HEALING: + if (!cast_healing( 3 + (you.skills[SK_INVOCATIONS] / 6) )) + break; + + exercise(SK_INVOCATIONS, 2 + random2(5)); + 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) == -1) + { + canned_msg(MSG_OK); + return (false); + } + + power = you.skills[SK_INVOCATIONS] + + random2( 1 + you.skills[SK_INVOCATIONS] ) + + random2( 1 + you.skills[SK_INVOCATIONS] ); + + 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; + } + + exercise(SK_INVOCATIONS, 1); + break; + + case ABIL_MAKHLEB_LESSER_SERVANT_OF_MAKHLEB: + summon_ice_beast_etc( 20 + you.skills[SK_INVOCATIONS] * 3, + MONS_NEQOXEC + random2(5) ); + + exercise(SK_INVOCATIONS, 2 + random2(3)); + break; + + case ABIL_MAKHLEB_MAJOR_DESTRUCTION: + if (spell_direction(spd, beam) == -1) + { + canned_msg(MSG_OK); + return (false); + } + + power = you.skills[SK_INVOCATIONS] * 3 + + random2( 1 + you.skills[SK_INVOCATIONS] ) + + random2( 1 + you.skills[SK_INVOCATIONS] ); + + 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 7: + you.attribute[ATTR_DIVINE_LIGHTNING_PROTECTION] = 1; + mpr("Makhleb hurls a blast of lightning!"); + + // 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; + strcpy(beam.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.isTracer = false; + + // ... and fire! + explosion(beam); + + // protection down + mpr("Your divine protection wanes."); + you.attribute[ATTR_DIVINE_LIGHTNING_PROTECTION] = 0; + break; + } + + exercise(SK_INVOCATIONS, 3 + random2(5)); + break; + + case ABIL_MAKHLEB_GREATER_SERVANT_OF_MAKHLEB: + summon_ice_beast_etc( 20 + you.skills[SK_INVOCATIONS] * 3, + MONS_EXECUTIONER + random2(5) ); + + exercise(SK_INVOCATIONS, 6 + random2(6)); + break; + + case ABIL_TROG_BERSERK: + // Trog abilities don't use or train invocations. + if (you.hunger_state < HS_SATIATED) + { + mpr("You're too hungry to berserk."); + return (false); + } + + go_berserk(true); + break; + + case ABIL_TROG_MIGHT: + // Trog abilities don't use or train invocations. + potion_effect( POT_MIGHT, 150 ); + break; + + case ABIL_TROG_HASTE_SELF: + // Trog abilities don't use or train invocations. + potion_effect( POT_SPEED, 150 ); + break; + + case ABIL_SIF_MUNA_FORGET_SPELL: + cast_selective_amnesia(true); + break; + + case ABIL_ELYVILON_LESSER_HEALING: + if (!cast_healing( 3 + (you.skills[SK_INVOCATIONS] / 6) )) + break; + + exercise( SK_INVOCATIONS, 1 ); + break; + + case ABIL_ELYVILON_PURIFICATION: + purification(); + exercise( SK_INVOCATIONS, 2 + random2(3) ); + break; + + case ABIL_ELYVILON_HEALING: + if (!cast_healing( 10 + (you.skills[SK_INVOCATIONS] / 3) )) + break; + + exercise( SK_INVOCATIONS, 3 + random2(5) ); + break; + + case ABIL_ELYVILON_RESTORATION: + restore_stat( STAT_ALL, false ); + unrot_hp( 100 ); + + exercise( SK_INVOCATIONS, 4 + random2(6) ); + break; + + case ABIL_ELYVILON_GREATER_HEALING: + if (!cast_healing( 20 + you.skills[SK_INVOCATIONS] * 2 )) + break; + + exercise( SK_INVOCATIONS, 6 + random2(10) ); + 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)); + break; + + 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]) + { + canned_msg(MSG_CANNOT_DO_YET); + return (false); + } + + your_spells( SPELL_HELLFIRE, 20 + you.experience_level, 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) + { + mpr("The unliving cannot use this ability."); + return (false); + } + + torment(you.x_pos, you.y_pos); + exercise(SK_INVOCATIONS, 2 + random2(4)); + break; + + case ABIL_SHUGGOTH_SEED: + if (you.duration[DUR_SHUGGOTH_SEED_RELOAD]) + { + canned_msg(MSG_CANNOT_DO_YET); + return (false); + } + + cast_shuggoth_seed( you.experience_level * 2 + + you.skills[SK_INVOCATIONS] * 3 ); + + you.duration[DUR_SHUGGOTH_SEED_RELOAD] = 10 + random2avg(39, 2); + exercise(SK_INVOCATIONS, 2 + random2(4)); + 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); + } + break; + + default: + mpr("Sorry, you can't do that."); + 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); + +#if DEBUG_DIAGNOSTICS + snprintf( info, INFO_SIZE, "Cost: mp=%d; hp=%d; food=%d; piety=%d", + abil.mp_cost, abil.hp_cost, food_cost, piety_cost ); + + mpr( info, MSGCH_DIAGNOSTICS ); +#endif + + if (abil.mp_cost) + { + dec_mp( abil.mp_cost ); + if (abil.flags & ABFLAG_PERMANENT_MP) + rot_mp(1); + } + + if (abil.hp_cost) + { + dec_hp( abil.hp_cost, false ); + if (abil.flags & ABFLAG_PERMANENT_HP) + rot_hp(1); + } + + if (food_cost) + make_hungry( food_cost, false ); + + if (piety_cost) + lose_piety( piety_cost ); + + return (true); +} // end activate_ability() + + +// 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; + + const int num_lines = get_number_of_lines(); + + for (loopy = 0; loopy < 52; loopy++) + { + if (Curr_abil[loopy].is_invocation) + { + can_invoke = true; + break; + } + } + + +#ifdef DOS_TERM + char buffer[4800]; + + gettext(1, 1, 80, 25, buffer); + window(1, 1, 80, 25); +#endif + + clrscr(); + cprintf(" Ability Cost Success"); + lines++; + + for (int do_invoke = 0; do_invoke < (can_invoke ? 2 : 1); do_invoke++) + { + if (do_invoke) + { + anything++; + textcolor(BLUE); + cprintf(EOL " Invocations - "); + textcolor(LIGHTGREY); + lines++; + } + + for (loopy = 0; loopy < 52; loopy++) + { + if (lines > num_lines - 2) + { + gotoxy(1, num_lines); + cprintf("-more-"); + + ki = getch(); + + if (ki == ESCAPE) + { +#ifdef DOS_TERM + puttext(1, 1, 80, 25, buffer); +#endif + return (ESCAPE); + } + + if (ki >= 'A' && ki <= 'z') + { +#ifdef DOS_TERM + puttext(1, 1, 80, 25, buffer); +#endif + return (ki); + } + + if (ki == 0) + ki = getch(); + + lines = 0; + clrscr(); + gotoxy(1, 1); + anything = 0; + } + + if (Curr_abil[loopy].which != ABIL_NON_ABILITY + && (do_invoke == Curr_abil[loopy].is_invocation)) + { + anything++; + + if (lines > 0) + cprintf(EOL); + + lines++; + + const struct ability_def abil = get_ability_def( Curr_abil[loopy].which ); + + 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( cost_str.c_str() ); + + gotoxy(60, wherey()); + + int spell_f = Curr_abil[loopy].fail; + + cprintf( (spell_f >= 100) ? "Useless" : + (spell_f > 90) ? "Terrible" : + (spell_f > 80) ? "Cruddy" : + (spell_f > 70) ? "Bad" : + (spell_f > 60) ? "Very Poor" : + (spell_f > 50) ? "Poor" : + (spell_f > 40) ? "Fair" : + (spell_f > 30) ? "Good" : + (spell_f > 20) ? "Very Good" : + (spell_f > 10) ? "Great" : + (spell_f > 0) ? "Excellent" : + "Perfect" ); + + gotoxy(70, wherey()); + } // end if conditional + } // end "for loopy" + } + + if (anything > 0) + { + ki = getch(); + + if (ki >= 'A' && ki <= 'z') + { +#ifdef DOS_TERM + puttext(1, 1, 80, 25, buffer); +#endif + return (ki); + } + + if (ki == 0) + ki = getch(); + +#ifdef DOS_TERM + puttext(1, 1, 80, 25, buffer); +#endif + + return (ki); + } + +#ifdef DOS_TERM + puttext(1, 1, 80, 25, buffer); +#endif + + ki = getch(); + + return (ki); +} // end show_abilities() + + +bool generate_abilities( void ) +/*****************************/ +{ + int loopy; + int ability = -1; // used with draconian checks {dlb} + + // 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; + } + + // 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 ); + } + + // 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 ); + } + + if (you.species == SP_NAGA) + { + if (you.mutation[MUT_BREATHE_POISON]) + insert_ability( ABIL_BREATHE_POISON ); + else + insert_ability( ABIL_SPIT_POISON ); + } + else if (you.mutation[MUT_SPIT_POISON]) + { + insert_ability( ABIL_SPIT_POISON ); + } + + if (player_genus(GENPC_DRACONIAN)) + { + if (you.experience_level >= 7) + { + 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 ); + } + } + + //jmf: alternately put check elsewhere + if ((you.level_type == LEVEL_DUNGEON + && (you.species == SP_GNOME || you.mutation[MUT_MAPPING])) + || (you.level_type == LEVEL_PANDEMONIUM + && you.mutation[MUT_MAPPING] == 3)) + { + insert_ability( ABIL_MAPPING ); + } + + if (!you.duration[DUR_CONTROLLED_FLIGHT] && !player_is_levitating()) + { + // kenku can fly, but only from the ground + // (until levitation 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 ); + else if (player_genus(GENPC_DRACONIAN) && you.mutation[MUT_BIG_WINGS]) + insert_ability( ABIL_FLY_II ); + } + + // demonic powers {dlb}: + if (you.mutation[MUT_SUMMON_MINOR_DEMONS]) + insert_ability( ABIL_SUMMON_MINOR_DEMON ); + + if (you.mutation[MUT_SUMMON_DEMONS]) + insert_ability( ABIL_SUMMON_DEMON ); + + if (you.mutation[MUT_HURL_HELLFIRE]) + insert_ability( ABIL_HELLFIRE ); + + if (you.mutation[MUT_CALL_TORMENT]) + insert_ability( ABIL_TORMENT ); + + if (you.mutation[MUT_RAISE_DEAD]) + insert_ability( ABIL_RAISE_DEAD ); + + if (you.mutation[MUT_CONTROL_DEMONS]) + insert_ability( ABIL_CONTROL_DEMON ); + + if (you.mutation[MUT_PANDEMONIUM]) + insert_ability( ABIL_TO_PANDEMONIUM ); + + if (you.mutation[MUT_CHANNEL_HELL]) + insert_ability( ABIL_CHANNELING ); + + if (you.mutation[MUT_THROW_FLAMES]) + insert_ability( ABIL_THROW_FLAME ); + + if (you.mutation[MUT_THROW_FROST]) + insert_ability( ABIL_THROW_FROST ); + + if (you.mutation[MUT_SMITE]) + insert_ability( ABIL_BOLT_OF_DRAINING ); + + if (you.duration[DUR_TRANSFORMATION]) + insert_ability( ABIL_END_TRANSFORMATION ); + + if (you.mutation[MUT_BLINK]) + insert_ability( ABIL_BLINK ); + + if (you.mutation[MUT_TELEPORT_AT_WILL]) + insert_ability( ABIL_TELEPORTATION ); + + // gods take abilities away until penance completed -- bwr + if (!player_under_penance() && !silenced( you.x_pos, you.y_pos )) + { + switch (you.religion) + { + case GOD_ZIN: + if (you.piety >= 30) + insert_ability( ABIL_ZIN_REPEL_UNDEAD ); + if (you.piety >= 50) + insert_ability( ABIL_ZIN_HEALING ); + if (you.piety >= 75) + insert_ability( ABIL_ZIN_PESTILENCE ); + if (you.piety >= 100) + insert_ability( ABIL_ZIN_HOLY_WORD ); + if (you.piety >= 120) + insert_ability( ABIL_ZIN_SUMMON_GUARDIAN ); + break; + + case GOD_SHINING_ONE: + if (you.piety >= 30) + insert_ability( ABIL_TSO_REPEL_UNDEAD ); + if (you.piety >= 50) + insert_ability( ABIL_TSO_SMITING ); + if (you.piety >= 75) + insert_ability( ABIL_TSO_ANNIHILATE_UNDEAD ); + if (you.piety >= 100) + insert_ability( ABIL_TSO_THUNDERBOLT ); + if (you.piety >= 120) + insert_ability( ABIL_TSO_SUMMON_DAEVA ); + break; + + case GOD_YREDELEMNUL: + if (you.piety >= 30) + insert_ability( ABIL_YRED_ANIMATE_CORPSE ); + if (you.piety >= 50) + insert_ability( ABIL_YRED_RECALL_UNDEAD ); + if (you.piety >= 75) + insert_ability( ABIL_YRED_ANIMATE_DEAD ); + if (you.piety >= 100) + insert_ability( ABIL_YRED_DRAIN_LIFE ); + if (you.piety >= 120) + insert_ability( ABIL_YRED_CONTROL_UNDEAD ); + break; + + case GOD_ELYVILON: + if (you.piety >= 30) + insert_ability( ABIL_ELYVILON_LESSER_HEALING ); + if (you.piety >= 50) + insert_ability( ABIL_ELYVILON_PURIFICATION ); + if (you.piety >= 75) + insert_ability( ABIL_ELYVILON_HEALING ); + if (you.piety >= 100) + insert_ability( ABIL_ELYVILON_RESTORATION ); + if (you.piety >= 120) + insert_ability( ABIL_ELYVILON_GREATER_HEALING ); + break; + + case GOD_MAKHLEB: + if (you.piety >= 50) + insert_ability( ABIL_MAKHLEB_MINOR_DESTRUCTION ); + if (you.piety >= 75) + insert_ability( ABIL_MAKHLEB_LESSER_SERVANT_OF_MAKHLEB ); + if (you.piety >= 100) + insert_ability( ABIL_MAKHLEB_MAJOR_DESTRUCTION ); + if (you.piety >= 120) + insert_ability( ABIL_MAKHLEB_GREATER_SERVANT_OF_MAKHLEB ); + break; + + case GOD_KIKUBAAQUDGHA: + if (you.piety >= 30) + insert_ability( ABIL_KIKU_RECALL_UNDEAD_SLAVES ); + if (you.piety >= 75) + insert_ability( ABIL_KIKU_ENSLAVE_UNDEAD ); + if (you.piety >= 120) + insert_ability( ABIL_KIKU_INVOKE_DEATH ); + break; + + case GOD_OKAWARU: + if (you.piety >= 30) + insert_ability( ABIL_OKAWARU_MIGHT ); + if (you.piety >= 50) + insert_ability( ABIL_OKAWARU_HEALING ); + if (you.piety >= 120) + insert_ability( ABIL_OKAWARU_HASTE ); + break; + + case GOD_TROG: + if (you.piety >= 30) + insert_ability( ABIL_TROG_BERSERK ); + if (you.piety >= 50) + insert_ability( ABIL_TROG_MIGHT ); + if (you.piety >= 100) + insert_ability( ABIL_TROG_HASTE_SELF ); + break; + + case GOD_SIF_MUNA: + if (you.piety >= 50) + insert_ability( ABIL_SIF_MUNA_FORGET_SPELL ); + break; + + case GOD_VEHUMET: + if (you.piety >= 100) + insert_ability( ABIL_VEHUMET_CHANNEL_ENERGY ); + break; + + default: + 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 )) + insert_ability( ABIL_RENOUNCE_RELIGION ); + + //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 ); + } + else if (you.attribute[ATTR_TRANSFORMATION] == TRAN_DRAGON + || you.mutation[MUT_BREATHE_FLAMES]) + { + insert_ability( ABIL_BREATHE_FIRE ); + } + + // checking for unreleased delayed fireball + if (you.attribute[ ATTR_DELAYED_FIREBALL ]) + { + insert_ability( ABIL_DELAYED_FIREBALL ); + } + + // evocations from items: + if (scan_randarts(RAP_BLINK)) + insert_ability( ABIL_EVOKE_BLINK ); + + if (wearing_amulet(AMU_RAGE) || scan_randarts(RAP_BERSERK)) + insert_ability( ABIL_EVOKE_BERSERK ); + + if (scan_randarts( RAP_MAPPING )) + insert_ability( ABIL_EVOKE_MAPPING ); + + 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 ); + else + insert_ability( ABIL_EVOKE_TURN_INVISIBLE ); + } + + //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 ); + else + insert_ability( ABIL_EVOKE_LEVITATE ); + } + + if (player_equip( EQ_RINGS, RING_TELEPORTATION ) + || scan_randarts( RAP_CAN_TELEPORT )) + { + insert_ability( ABIL_EVOKE_TELEPORTATION ); + } + + // 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); + } + + return (false); +} // end generate_abilities() + +// 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 +void set_god_ability_helper( int 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; + } +} + +void set_god_ability_slots( void ) +/********************************/ +{ + ASSERT( you.religion != GOD_NO_GOD ); + + int i; + + set_god_ability_helper( ABIL_RENOUNCE_RELIGION, 'X' ); + + int num_abil = 0; + int abil_start = ABIL_NON_ABILITY; + + switch (you.religion) + { + case GOD_ZIN: + abil_start = ABIL_ZIN_REPEL_UNDEAD; + num_abil = 5; + break; + + case GOD_SHINING_ONE: + abil_start = ABIL_TSO_REPEL_UNDEAD; + num_abil = 5; + break; + + case GOD_KIKUBAAQUDGHA: + abil_start = ABIL_KIKU_RECALL_UNDEAD_SLAVES; + num_abil = 3; + break; + + case GOD_YREDELEMNUL: + abil_start = ABIL_YRED_ANIMATE_CORPSE; + num_abil = 5; + break; + + case GOD_VEHUMET: + abil_start = ABIL_VEHUMET_CHANNEL_ENERGY; + num_abil = 1; + break; + + case GOD_OKAWARU: + abil_start = ABIL_OKAWARU_MIGHT; + num_abil = 3; + break; + + case GOD_MAKHLEB: + abil_start = ABIL_MAKHLEB_MINOR_DESTRUCTION; + num_abil = 4; + break; + + case GOD_SIF_MUNA: + abil_start = ABIL_SIF_MUNA_FORGET_SPELL; + num_abil = 1; + break; + + case GOD_TROG: + abil_start = ABIL_TROG_BERSERK; + num_abil = 3; + break; + + case GOD_ELYVILON: + abil_start = ABIL_ELYVILON_LESSER_HEALING; + num_abil = 5; + break; + + case GOD_NEMELEX_XOBEH: + case GOD_XOM: + default: + break; + } + + // clear out other god invocations: + for (i = 0; i < 52; i++) + { + const int abil = you.ability_letter_table[i]; + + if ((abil >= ABIL_ZIN_REPEL_UNDEAD // is a god ability + && abil <= ABIL_ELYVILON_GREATER_HEALING) + && (num_abil == 0 // current god does have abilities + || abil < abil_start // not one of current god's abilities + || abil >= abil_start + num_abil)) + { + you.ability_letter_table[i] = ABIL_NON_ABILITY; + } + } + + // finally, add in current god's invocaions in traditional slots: + if (num_abil) + { + for (i = 0; i < num_abil; i++) + { + set_god_ability_helper( abil_start + i, + (Options.lowercase_invocations ? 'a' : 'A') + i ); + } + } +} + + +// 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; + } + + // no requested slot, find new one and make it prefered. + if (slot == 52) + { + // skip over a-e if player prefers them for invocations + for (slot = (Options.lowercase_invocations ? 5 : 0); slot < 52; slot++) + { + if (you.ability_letter_table[slot] == ABIL_NON_ABILITY) + break; + } + + // if we skipped over a-e to reserve them, try them now + if (Options.lowercase_invocations && slot == 52) + { + for (slot = 5; slot >= 0; slot--) + { + if (you.ability_letter_table[slot] == ABIL_NON_ABILITY) + break; + } + } + + // 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; + } + + // no slots at all == no hope of adding + if (slot < 0) + return (-1); + } + + // this ability now takes over this slot + you.ability_letter_table[slot] = which_ability; + } + + return (slot); +} + +static bool insert_ability( int which_ability ) +/**********************************************/ +{ + ASSERT( which_ability != ABIL_NON_ABILITY ); + + int failure = 0; + bool perfect = false; // is perfect + bool invoc = false; + + // 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); + + Curr_abil[slot].which = which_ability; + + switch (which_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_GLAMOUR: + failure = 50 - (you.experience_level * 2); + break; + + case ABIL_SPIT_POISON: + failure = ((you.species == SP_NAGA) ? 20 : 40) + - 10 * you.mutation[MUT_SPIT_POISON] + - you.experience_level; + break; + + case ABIL_EVOKE_MAPPING: + failure = 30 - you.skills[SK_EVOCATIONS]; + break; + + case ABIL_MAPPING: + failure = ((you.species == SP_GNOME) ? 20 : 40) + - 10 * you.mutation[MUT_MAPPING] + - you.experience_level; + break; + + case ABIL_BREATHE_FIRE: + failure = ((you.species == SP_RED_DRACONIAN) ? 30 : 50) + - 10 * you.mutation[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; + // end species abilties (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_TO_PANDEMONIUM: + failure = 57 - (you.experience_level * 2); + 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: + failure = 30 - (10 * you.mutation[MUT_BLINK]) - you.experience_level; + break; + + case ABIL_TELEPORTATION: + failure = ((you.mutation[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; + + 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_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 + || you.species == SP_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_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; + + // 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_MIGHT: // piety >= 50 + invoc = true; + failure = 80 - you.piety; // starts at 30% + break; + + case ABIL_TROG_HASTE_SELF: // piety >= 100 + invoc = true; + failure = 160 - you.piety; // starts at 60% + break; + + case ABIL_YRED_ANIMATE_CORPSE: + invoc = true; + failure = 40 - (you.piety / 20) - (3 * you.skills[SK_INVOCATIONS]); + break; + + 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_VEHUMET_CHANNEL_ENERGY: + invoc = true; + failure = 40 - you.intel - you.skills[SK_INVOCATIONS]; + break; + + case ABIL_YRED_RECALL_UNDEAD: + invoc = true; + failure = 50 - (you.piety / 20) - (you.skills[SK_INVOCATIONS] * 4); + break; + + case ABIL_ZIN_PESTILENCE: + case ABIL_TSO_ANNIHILATE_UNDEAD: + invoc = true; + failure = 60 - (you.piety / 20) - (5 * you.skills[SK_INVOCATIONS]); + break; + + case ABIL_MAKHLEB_MAJOR_DESTRUCTION: + case ABIL_YRED_DRAIN_LIFE: + invoc = true; + failure = 60 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); + break; + + case ABIL_ZIN_HOLY_WORD: + case ABIL_TSO_THUNDERBOLT: + 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_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; + + //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_TRAN_SERPENT_OF_HELL: + invoc = true; + failure = 80 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); + break; + + case ABIL_ROTTING: + invoc = true; + failure = 60 - (you.piety / 20) - (5 * you.skills[SK_INVOCATIONS]); + break; + + case ABIL_TORMENT_II: + invoc = true; + failure = 70 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 4); + break; + + case ABIL_SHUGGOTH_SEED: + invoc = true; + failure = 85 - (you.piety / 25) - (you.skills[SK_INVOCATIONS] * 3); + break; + + case ABIL_RENOUNCE_RELIGION: + invoc = true; + perfect = true; + failure = 0; + break; + + // end invocations {dlb} + default: + failure = -1; + break; + } + + // Perfect abilities are things like "renounce religion", which + // shouldn't have a failure rate ever. -- bwr + if (failure <= 0 && !perfect) + failure = 1; + + if (failure > 100) + failure = 100; + + Curr_abil[slot].fail = failure; + Curr_abil[slot].is_invocation = invoc; + + return (true); +} // end insert_ability() -- cgit v1.2.3-54-g00ecf