diff options
author | peterb12 <peterb12@c06c8d41-db1a-0410-9941-cceddc491573> | 2005-07-21 02:34:44 +0000 |
---|---|---|
committer | peterb12 <peterb12@c06c8d41-db1a-0410-9941-cceddc491573> | 2005-07-21 02:34:44 +0000 |
commit | 673bdae75485d14f759af597c3c62b99601f9a43 (patch) | |
tree | 368103f29fe0ce5dcf98060d9b5faa04590085fb /trunk/source/spells4.cc | |
parent | 7e900be770db24b0405fd2162491c405a425873e (diff) | |
download | crawl-ref-673bdae75485d14f759af597c3c62b99601f9a43.tar.gz crawl-ref-673bdae75485d14f759af597c3c62b99601f9a43.zip |
Initial revision
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@3 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'trunk/source/spells4.cc')
-rw-r--r-- | trunk/source/spells4.cc | 3211 |
1 files changed, 3211 insertions, 0 deletions
diff --git a/trunk/source/spells4.cc b/trunk/source/spells4.cc new file mode 100644 index 0000000000..46ac36ebd0 --- /dev/null +++ b/trunk/source/spells4.cc @@ -0,0 +1,3211 @@ +/* + * File: spells4.cc + * Summary: new spells, focusing on transmigration, divination and + * other neglected areas of Crawl magic ;^) + * Written by: Copyleft Josh Fishman 1999-2000, All Rights Preserved + * + * Change History (most recent first): + * + * <2> 29jul2000 jdj Made a zillion functions static. + * <1> 06jan2000 jmf Created + */ + +#include "AppHdr.h" + +#include <string> +#include <stdio.h> + +#include "externs.h" + +#include "abyss.h" +#include "beam.h" +#include "cloud.h" +#include "debug.h" +#include "delay.h" +#include "describe.h" +#include "direct.h" +#include "dungeon.h" +#include "effects.h" +#include "it_use2.h" +#include "itemname.h" +#include "items.h" +#include "invent.h" +#include "misc.h" +#include "monplace.h" +#include "monstuff.h" +#include "mon-util.h" +#include "mstuff2.h" +#include "ouch.h" +#include "player.h" +#include "randart.h" +#include "religion.h" +#include "skills.h" +#include "spells1.h" +#include "spells4.h" +#include "spl-cast.h" +#include "spl-util.h" +#include "stuff.h" +#include "view.h" + +enum DEBRIS // jmf: add for shatter, dig, and Giants to throw +{ + DEBRIS_METAL, // 0 + DEBRIS_ROCK, + DEBRIS_STONE, + DEBRIS_WOOD, + DEBRIS_CRYSTAL, + NUM_DEBRIS +}; // jmf: ...and I'll actually implement the items Real Soon Now... + +static bool mons_can_host_shuggoth(int type); +// static int make_a_random_cloud(int x, int y, int pow, int ctype); +static int make_a_rot_cloud(int x, int y, int pow, int ctype); +static int quadrant_blink(int x, int y, int pow, int garbage); + +//void cast_animate_golem(int pow); // see actual function for reasoning {dlb} +//void cast_detect_magic(int pow); //jmf: as above... +//void cast_eringyas_surprising_bouquet(int powc); +void do_monster_rot(int mon); + +//jmf: FIXME: put somewhere else (misc.cc?) +// A feeble attempt at Nethack-like completeness for cute messages. +const char *your_hand( bool plural ) +{ + static char hand_buff[80]; + + switch (you.attribute[ATTR_TRANSFORMATION]) + { + default: + mpr("ERROR: unknown transformation in your_hand() (spells4.cc)"); + case TRAN_NONE: + case TRAN_STATUE: + if (you.species == SP_TROLL || you.species == SP_GHOUL) + { + strcpy(hand_buff, "claw"); + break; + } + // or fall-through + case TRAN_ICE_BEAST: + case TRAN_LICH: + strcpy(hand_buff, "hand"); + break; + case TRAN_SPIDER: + strcpy(hand_buff, "front leg"); + break; + case TRAN_SERPENT_OF_HELL: + case TRAN_DRAGON: + strcpy(hand_buff, "foreclaw"); + break; + case TRAN_BLADE_HANDS: + strcpy(hand_buff, "scythe-like blade"); + break; + case TRAN_AIR: + strcpy(hand_buff, "misty tendril"); + break; + } + + if (plural) + strcat(hand_buff, "s"); + + return (hand_buff); +} + +// I need to make some debris for metal, crystal and stone. +// They could go in OBJ_MISSILES, but I think I'd rather move +// MI_LARGE_ROCK into OBJ_DEBRIS and code giants to throw any +// OBJ_DEBRIS they get their meaty mits on. +static void place_debris(int x, int y, int debris_type) +{ +#ifdef USE_DEBRIS_CODE + switch (debris_type) + { + // hate to say this, but the first parameter only allows specific quantity + // for *food* and nothing else -- and I would hate to see that parameter + // (force_unique) abused any more than it already has been ... {dlb}: + case DEBRIS_STONE: + large = items( random2(3), OBJ_MISSILES, MI_LARGE_ROCK, true, 1, 250 ); + small = items( 3 + random2(6) + random2(6) + random2(6), + OBJ_MISSILES, MI_STONE, true, 1, 250 ); + break; + case DEBRIS_METAL: + case DEBRIS_WOOD: + case DEBRIS_CRYSTAL: + break; + } + + if (small != NON_ITEM) + move_item_to_grid( &small, x, y ); + + if (large != NON_ITEM) + move_item_to_grid( &large, x, y ); + +#else + UNUSED( x ); + UNUSED( y ); + UNUSED( debris_type ); + return; +#endif +} // end place_debris() + +// just to avoid typing this over and over +// now returns true if monster died -- bwr +inline bool player_hurt_monster(int monster, int damage) +{ + ASSERT( monster != NON_MONSTER ); + + if (damage > 0) + { + hurt_monster( &menv[monster], damage ); + + if (menv[monster].hit_points > 0) + print_wounds( &menv[monster] ); + else + { + monster_die( &menv[monster], KILL_YOU, 0 ); + return (true); + } + } + + return (false); +} // end player_hurt_monster() + + +// Here begin the actual spells: +static int shatter_monsters(int x, int y, int pow, int garbage) +{ + UNUSED( garbage ); + + dice_def dam_dice( 0, 5 + pow / 4 ); // number of dice set below + const int monster = mgrd[x][y]; + + if (monster == NON_MONSTER) + return (0); + + // Removed a lot of silly monsters down here... people, just because + // it says ice, rock, or iron in the name doesn't mean it's actually + // made out of the substance. -- bwr + switch (menv[monster].type) + { + case MONS_ICE_BEAST: // 3/2 damage + case MONS_SIMULACRUM_SMALL: + case MONS_SIMULACRUM_LARGE: + dam_dice.num = 4; + break; + + case MONS_SKELETON_SMALL: // double damage + case MONS_SKELETON_LARGE: + case MONS_CURSE_SKULL: + case MONS_CLAY_GOLEM: + case MONS_STONE_GOLEM: + case MONS_IRON_GOLEM: + case MONS_CRYSTAL_GOLEM: + case MONS_EARTH_ELEMENTAL: + case MONS_GARGOYLE: + case MONS_SKELETAL_DRAGON: + case MONS_SKELETAL_WARRIOR: + dam_dice.num = 6; + break; + + case MONS_VAPOUR: + case MONS_INSUBSTANTIAL_WISP: + case MONS_AIR_ELEMENTAL: + case MONS_FIRE_ELEMENTAL: + case MONS_WATER_ELEMENTAL: + case MONS_SPECTRAL_WARRIOR: + case MONS_FREEZING_WRAITH: + case MONS_WRAITH: + case MONS_PHANTOM: + case MONS_PLAYER_GHOST: + case MONS_SHADOW: + case MONS_HUNGRY_GHOST: + case MONS_FLAYED_GHOST: + case MONS_SMOKE_DEMON: //jmf: I hate these bastards... + dam_dice.num = 0; + break; + + case MONS_PULSATING_LUMP: + case MONS_JELLY: + case MONS_SLIME_CREATURE: + case MONS_BROWN_OOZE: + case MONS_AZURE_JELLY: + case MONS_DEATH_OOZE: + case MONS_ACID_BLOB: + case MONS_ROYAL_JELLY: + case MONS_OOZE: + case MONS_SPECTRAL_THING: + case MONS_JELLYFISH: + dam_dice.num = 1; + dam_dice.size /= 2; + break; + + case MONS_DANCING_WEAPON: // flies, but earth based + case MONS_MOLTEN_GARGOYLE: + case MONS_QUICKSILVER_DRAGON: + // Soft, earth creatures... would normally resist to 1 die, but + // are sensitive to this spell. -- bwr + dam_dice.num = 2; + break; + + default: // normal damage + if (mons_flies( &menv[monster] )) + dam_dice.num = 1; + else + dam_dice.num = 3; + break; + } + + int damage = roll_dice( dam_dice ) - random2( menv[monster].armour_class ); + + if (damage > 0) + player_hurt_monster( monster, damage ); + else + damage = 0; + + return (damage); +} // end shatter_monsters() + +static int shatter_items(int x, int y, int pow, int garbage) +{ + UNUSED( pow ); + UNUSED( garbage ); + + int broke_stuff = 0, next, obj = igrd[x][y]; + + if (obj == NON_ITEM) + return 0; + + while (obj != NON_ITEM) + { + next = mitm[obj].link; + + switch (mitm[obj].base_type) + { + case OBJ_POTIONS: + if (!one_chance_in(10)) + { + broke_stuff++; + destroy_item(obj); + } + break; + + default: + break; + } + + obj = next; + } + + if (broke_stuff) + { + if (!silenced(x, y) && !silenced(you.x_pos, you.y_pos)) + mpr("You hear glass break."); + + return 1; + } + + return 0; +} // end shatter_items() + +static int shatter_walls(int x, int y, int pow, int garbage) +{ + UNUSED( garbage ); + + int chance = 0; + int stuff = 0; + + // if not in-bounds then we can't really shatter it -- bwr + if (x <= 5 || x >= GXM - 5 || y <= 5 || y >= GYM - 5) + return (0); + + switch (grd[x][y]) + { + case DNGN_SECRET_DOOR: + if (see_grid(x, y)) + mpr("A secret door shatters!"); + grd[x][y] = DNGN_FLOOR; + stuff = DEBRIS_WOOD; + chance = 100; + break; + + case DNGN_CLOSED_DOOR: + case DNGN_OPEN_DOOR: + if (see_grid(x, y)) + mpr("A door shatters!"); + grd[x][y] = DNGN_FLOOR; + stuff = DEBRIS_WOOD; + chance = 100; + break; + + case DNGN_METAL_WALL: + case DNGN_SILVER_STATUE: + stuff = DEBRIS_METAL; + chance = pow / 10; + break; + + case DNGN_ORCISH_IDOL: + case DNGN_GRANITE_STATUE: + chance = 50; + stuff = DEBRIS_STONE; + break; + + case DNGN_STONE_WALL: + chance = pow / 6; + stuff = DEBRIS_STONE; + break; + + case DNGN_ROCK_WALL: + chance = pow / 4; + stuff = DEBRIS_ROCK; + break; + + case DNGN_ORANGE_CRYSTAL_STATUE: + chance = pow / 6; + stuff = DEBRIS_CRYSTAL; + break; + + case DNGN_GREEN_CRYSTAL_WALL: + chance = 50; + stuff = DEBRIS_CRYSTAL; + break; + + default: + break; + } + + if (stuff && random2(100) < chance) + { + if (!silenced( x, y )) + noisy( 30, x, y ); + + grd[x][y] = DNGN_FLOOR; + place_debris(x, y, stuff); + return (1); + } + + return (0); +} // end shatter_walls() + +void cast_shatter(int pow) +{ + int damage = 0; + const bool sil = silenced( you.x_pos, you.y_pos ); + + if (!sil) + noisy( 30, you.x_pos, you.y_pos ); + + snprintf(info, INFO_SIZE, "The dungeon %s!", (sil ? "shakes" : "rumbles")); + mpr(info); + + switch (you.attribute[ATTR_TRANSFORMATION]) + { + case TRAN_NONE: + case TRAN_SPIDER: + case TRAN_LICH: + case TRAN_DRAGON: + case TRAN_AIR: + case TRAN_SERPENT_OF_HELL: + break; + + case TRAN_STATUE: // full damage + damage = 15 + random2avg( (pow / 5), 4 ); + break; + + case TRAN_ICE_BEAST: // 1/2 damage + damage = 10 + random2avg( (pow / 5), 4 ) / 2; + break; + + case TRAN_BLADE_HANDS: // 2d3 damage + mpr("Your scythe-like blades vibrate painfully!"); + damage = 2 + random2avg(5, 2); + break; + + default: + mpr("cast_shatter(): unknown transformation in spells4.cc"); + } + + if (damage) + ouch(damage, 0, KILLED_BY_TARGETTING); + + int rad = 3 + (you.skills[SK_EARTH_MAGIC] / 5); + + apply_area_within_radius(shatter_items, you.x_pos, you.y_pos, pow, rad, 0); + apply_area_within_radius(shatter_monsters, you.x_pos, you.y_pos, pow, rad, 0); + int dest = apply_area_within_radius( shatter_walls, you.x_pos, you.y_pos, + pow, rad, 0 ); + + if (dest && !sil) + mpr("Ka-crash!"); +} // end cast_shatter() + +// cast_forescry: raises evasion (by 8 currently) via divination +void cast_forescry(int pow) +{ + if (!you.duration[DUR_FORESCRY]) + mpr("You begin to receive glimpses of the immediate future..."); + + you.duration[DUR_FORESCRY] += 5 + random2(pow); + + if (you.duration[DUR_FORESCRY] > 30) + you.duration[DUR_FORESCRY] = 30; + + you.redraw_evasion = 1; +} // end cast_forescry() + +void cast_see_invisible(int pow) +{ + if (player_see_invis()) + mpr("Nothing seems to happen."); + else + mpr("Your vision seems to sharpen."); + + // no message if you already are under the spell + you.duration[DUR_SEE_INVISIBLE] += 10 + random2(2 + (pow / 2)); + + if (you.duration[DUR_SEE_INVISIBLE] > 100) + you.duration[DUR_SEE_INVISIBLE] = 100; +} // end cast_see_invisible() + +#if 0 +// FIXME: This would be kinda cool if implemented right. +// The idea is that, like detect_secret_doors, the spell gathers all +// sorts of information about a thing and then tells the caster a few +// cryptic hints. So for a (+3,+5) Mace of Flaming, one might detect +// "enchantment and heat", but for a cursed ring of hunger, one might +// detect "enchantment and ice" (since it gives you a 'deathly cold' +// feeling when you put it on) or "necromancy" (since it's evil). +// A weapon of Divine Wrath and a randart that makes you angry might +// both give similar messages. The key would be to not tell more than +// hints about whether an item is benign or cursed, but give info +// on how strong its enchantment is (and therefore how valuable it +// probably is). +static void cast_detect_magic(int pow) +{ + struct dist bmove; + int x, y; + int monster = 0, item = 0, next; //int max; + FixedVector < int, NUM_SPELL_TYPES > found; + int strong = 0; // int curse = 0; + + for (next = 0; next < NUM_SPELL_TYPES; next++) + { + found[next] = 0; + } + + mpr("Which direction?", MSGCH_PROMPT); + direction( bmove, DIR_DIR ); + + if (!bmove.isValid) + { + canned_msg(MSG_SPELL_FIZZLES); + return; + } + + if (bmove.dx == 0 && bmove.dy == 0) + { + mpr("You detect a divination in progress."); + return; + } + + x = you.x_pos + bmove.dx; + y = you.y_pos + bmove.dy; + + monster = mgrd[x][y]; + if (monster == NON_MONSTER) + goto do_items; + else + goto all_done; + + do_items: + item = igrd[x][y]; + + if (item == NON_ITEM) + goto all_done; + + while (item != NON_ITEM) + { + next = mitm[item].link; + if (is_dumpable_artifact + (mitm[item].base_type, mitm[item].sub_type, mitm[item].plus, + mitm[item].plus2, mitm[item].special, 0, 0)) + { + strong++; + //FIXME: do checks for randart properties + } + else + { + switch (mitm[item].base_type) + { + case OBJ_WEAPONS: + found[SPTYP_ENCHANTMENT] += (mitm[item].plus > 50); + found[SPTYP_ENCHANTMENT] += (mitm[item].plus2 > 50); + break; + + case OBJ_MISSILES: + found[SPTYP_ENCHANTMENT] += (mitm[item].plus > 50); + found[SPTYP_ENCHANTMENT] += (mitm[item].plus2 > 50); + break; + + case OBJ_ARMOUR: + found[SPTYP_ENCHANTMENT] += mitm[item].plus; + } + } + } + + all_done: + if (monster) + { + mpr("You detect a morphogenic field, such as a monster might have."); + } + if (strong) + { + mpr("You detect very strong enchantments."); + return; + } + else + { + //FIXME: + } + return; +} +#endif + +// The description idea was okay, but this spell just isn't that exciting. +// So I'm converting it to the more practical expose secret doors. -- bwr +void cast_detect_secret_doors(int pow) +{ + int found = 0; + + for (int x = you.x_pos - 8; x <= you.x_pos + 8; x++) + { + for (int y = you.y_pos - 8; y <= you.y_pos + 8; y++) + { + if (x < 5 || x > GXM - 5 || y < 5 || y > GYM - 5) + continue; + + if (!see_grid(x, y)) + continue; + + if (grd[x][y] == DNGN_SECRET_DOOR && random2(pow) > random2(15)) + { + grd[x][y] = DNGN_CLOSED_DOOR; + found++; + } + } + } + + if (found) + { + redraw_screen(); + + snprintf( info, INFO_SIZE, "You detect %s secret door%s.", + (found > 1) ? "some" : "a", (found > 1) ? "s" : "" ); + mpr( info ); + } +} // end cast_detect_secret_doors() + +void cast_summon_butterflies(int pow) +{ + // explicitly limiting the number + int num = 4 + random2(3) + random2( pow ) / 10; + if (num > 16) + num = 16; + + for (int scount = 1; scount < num; scount++) + { + create_monster( MONS_BUTTERFLY, ENCH_ABJ_III, BEH_FRIENDLY, + you.x_pos, you.y_pos, MHITYOU, 250 ); + } +} + +void cast_summon_large_mammal(int pow) +{ + int mon; + int temp_rand = random2(pow); + + if (temp_rand < 10) + mon = MONS_JACKAL; + else if (temp_rand < 15) + mon = MONS_HOUND; + else + { + switch (temp_rand % 7) + { + case 0: + if (you.species == SP_HILL_ORC && one_chance_in(3)) + mon = MONS_WARG; + else + mon = MONS_WOLF; + break; + case 1: + case 2: + mon = MONS_WAR_DOG; + break; + case 3: + case 4: + mon = MONS_HOUND; + break; + default: + mon = MONS_JACKAL; + break; + } + } + + create_monster( mon, ENCH_ABJ_III, BEH_FRIENDLY, you.x_pos, you.y_pos, + you.pet_target, 250 ); +} + +void cast_sticks_to_snakes(int pow) +{ + int mon, i, behaviour; + + int how_many = 0; + + int max = 1 + random2( 1 + you.skills[SK_TRANSMIGRATION] ) / 4; + + int dur = ENCH_ABJ_III + random2(pow) / 20; + if (dur > ENCH_ABJ_V) + dur = ENCH_ABJ_V; + + const int weapon = you.equip[EQ_WEAPON]; + + if (weapon == -1) + { + snprintf( info, INFO_SIZE, "Your %s feel slithery!", your_hand(true)); + mpr(info); + return; + } + + behaviour = item_cursed( you.inv[ weapon ] ) ? BEH_HOSTILE + : BEH_FRIENDLY; + + if ((you.inv[ weapon ].base_type == OBJ_MISSILES + && (you.inv[ weapon ].sub_type == MI_ARROW))) + { + if (you.inv[ weapon ].quantity < max) + max = you.inv[ weapon ].quantity; + + for (i = 0; i <= max; i++) + { + //jmf: perhaps also check for poison ammo? + if (pow > 50 || (pow > 25 && one_chance_in(3))) + mon = MONS_SNAKE; + else + mon = MONS_SMALL_SNAKE; + + if (create_monster( mon, dur, behaviour, you.x_pos, you.y_pos, + MHITYOU, 250 ) != -1) + { + how_many++; + } + } + } + + if (you.inv[ weapon ].base_type == OBJ_WEAPONS + && (you.inv[ weapon ].sub_type == WPN_CLUB + || you.inv[ weapon ].sub_type == WPN_SPEAR + || you.inv[ weapon ].sub_type == WPN_QUARTERSTAFF + || you.inv[ weapon ].sub_type == WPN_SCYTHE + || you.inv[ weapon ].sub_type == WPN_GIANT_CLUB + || you.inv[ weapon ].sub_type == WPN_GIANT_SPIKED_CLUB + || you.inv[ weapon ].sub_type == WPN_BOW + || you.inv[ weapon ].sub_type == WPN_ANCUS + || you.inv[ weapon ].sub_type == WPN_HALBERD + || you.inv[ weapon ].sub_type == WPN_GLAIVE + || you.inv[ weapon ].sub_type == WPN_BLOWGUN)) + { + how_many = 1; + + // Upsizing Snakes to Brown Snakes as the base class for using + // the really big sticks (so bonus applies really only to trolls, + // ogres, and most importantly ogre magi). Still it's unlikely + // any character is strong enough to bother lugging a few of + // these around. -- bwr + if (mass_item( you.inv[ weapon ] ) < 500) + mon = MONS_SNAKE; + else + mon = MONS_BROWN_SNAKE; + + if (pow > 90 && one_chance_in(3)) + mon = MONS_GREY_SNAKE; + + if (pow > 70 && one_chance_in(3)) + mon = MONS_BLACK_SNAKE; + + if (pow > 40 && one_chance_in(3)) + mon = MONS_YELLOW_SNAKE; + + if (pow > 20 && one_chance_in(3)) + mon = MONS_BROWN_SNAKE; + + create_monster(mon, dur, behaviour, you.x_pos, you.y_pos, MHITYOU, 250); + } + +#ifdef USE_DEBRIS_CODE + if (you.inv[ weapon ].base_type == OBJ_DEBRIS + && (you.inv[ weapon ].sub_type == DEBRIS_WOOD)) + { + // this is how you get multiple big snakes + how_many = 1; + mpr("FIXME: implement OBJ_DEBRIS conversion! (spells4.cc)"); + } +#endif // USE_DEBRIS_CODE + + if (how_many > you.inv[you.equip[EQ_WEAPON]].quantity) + how_many = you.inv[you.equip[EQ_WEAPON]].quantity; + + if (how_many) + { + dec_inv_item_quantity( you.equip[EQ_WEAPON], how_many ); + + snprintf( info, INFO_SIZE, "You create %s snake%s!", + how_many > 1 ? "some" : "a", how_many > 1 ? "s" : ""); + } + else + { + snprintf( info, INFO_SIZE, "Your %s feel slithery!", your_hand(true)); + } + + mpr(info); + return; +} // end cast_sticks_to_snakes() + +void cast_summon_dragon(int pow) +{ + int happy; + + // Removed the chance of multiple dragons... one should be more + // than enough, and if it isn't, the player can cast again... + // especially since these aren't on the Abjuration plan... they'll + // last until they die (maybe that should be changed, but this is + // a very high level spell so it might be okay). -- bwr + happy = (random2(pow) > 5); + + if (create_monster( MONS_DRAGON, ENCH_ABJ_III, + (happy ? BEH_FRIENDLY : BEH_HOSTILE), + you.x_pos, you.y_pos, MHITYOU, 250 ) != -1) + { + strcpy(info, "A dragon appears."); + + if (!happy) + strcat(info, " It doesn't look very happy."); + } + else + strcpy(info, "Nothing happens."); + + mpr(info); +} // end cast_summon_dragon() + +void cast_conjure_ball_lightning( int pow ) +{ + int num = 3 + random2( 2 + pow / 50 ); + + // but restricted so that the situation doesn't get too gross. + // Each of these will explode for 3d20 damage. -- bwr + if (num > 8) + num = 8; + + bool summoned = false; + + for (int i = 0; i < num; i++) + { + int tx = -1, ty = -1; + + for (int j = 0; j < 10; j++) + { + if (!random_near_space( you.x_pos, you.y_pos, tx, ty, true, true) + && distance( you.x_pos, you.y_pos, tx, ty ) <= 5) + { + break; + } + } + + // if we fail, we'll try the ol' summon next to player trick. + if (tx == -1 || ty == -1) + { + tx = you.x_pos; + ty = you.y_pos; + } + + int mon = mons_place( MONS_BALL_LIGHTNING, BEH_FRIENDLY, MHITNOT, + true, tx, ty ); + + // int mon = create_monster( MONS_BALL_LIGHTNING, 0, BEH_FRIENDLY, + // tx, ty, MHITNOT, 250 ); + + if (mon != -1) + { + mons_add_ench( &menv[mon], ENCH_SHORT_LIVED ); + summoned = true; + } + } + + if (summoned) + mpr( "You create some ball lightning!" ); + else + canned_msg( MSG_NOTHING_HAPPENS ); +} + +static int sleep_monsters(int x, int y, int pow, int garbage) +{ + UNUSED( garbage ); + int mnstr = mgrd[x][y]; + + if (mnstr == NON_MONSTER) return 0; + if (mons_holiness( menv[mnstr].type ) != MH_NATURAL) return 0; + if (check_mons_resist_magic( &menv[mnstr], pow )) return 0; + + // Why shouldn't we be able to sleep friendly monsters? -- bwr + // if (mons_friendly( &menv[mnstr] )) return 0; + + //jmf: now that sleep == hibernation: + if (mons_res_cold( &menv[mnstr] ) > 0 && coinflip()) return 0; + if (mons_has_ench( &menv[mnstr], ENCH_SLEEP_WARY )) return 0; + + menv[mnstr].behaviour = BEH_SLEEP; + mons_add_ench( &menv[mnstr], ENCH_SLEEP_WARY ); + + if (mons_flag( menv[mnstr].type, M_COLD_BLOOD ) && coinflip()) + mons_add_ench( &menv[mnstr], ENCH_SLOW ); + + return 1; +} // end sleep_monsters() + +void cast_mass_sleep(int pow) +{ + apply_area_visible(sleep_monsters, pow); +} // end cast_mass_sleep() + +static int tame_beast_monsters(int x, int y, int pow, int garbage) +{ + UNUSED( garbage ); + int which_mons = mgrd[x][y]; + + if (which_mons == NON_MONSTER) return 0; + + struct monsters *monster = &menv[which_mons]; + + if (mons_holiness(monster->type) != MH_NATURAL) return 0; + if (mons_intel_type(monster->type) != I_ANIMAL) return 0; + if (mons_friendly(monster)) return 0; + + // 50% bonus for dogs, add cats if they get implemented + if (monster->type == MONS_HOUND || monster->type == MONS_WAR_DOG + || monster->type == MONS_BLACK_BEAR) + { + pow += (pow / 2); + } + + if (you.species == SP_HILL_ORC && monster->type == MONS_WARG) + pow += (pow / 2); + + if (check_mons_resist_magic(monster, pow)) + return 0; + + // I'd like to make the monsters affected permanently, but that's + // pretty powerful. Maybe a small (pow/10) chance of being permanently + // tamed, large chance of just being enslaved. + simple_monster_message(monster, " is tamed!"); + + if (random2(100) < random2(pow / 10)) + monster->attitude = ATT_FRIENDLY; // permanent, right? + else + mons_add_ench(monster, ENCH_CHARM); + + return 1; +} // end tame_beast_monsters() + +void cast_tame_beasts(int pow) +{ + apply_area_visible(tame_beast_monsters, pow); +} // end cast_tame_beasts() + +static int ignite_poison_objects(int x, int y, int pow, int garbage) +{ + UNUSED( pow ); + UNUSED( garbage ); + + int obj = igrd[x][y], next, strength = 0; + + if (obj == NON_ITEM) + return (0); + + while (obj != NON_ITEM) + { + next = mitm[obj].link; + if (mitm[obj].base_type == OBJ_POTIONS) + { + switch (mitm[obj].sub_type) + { + // intentional fall-through all the way down + case POT_STRONG_POISON: + strength += 20; + case POT_DEGENERATION: + strength += 10; + case POT_POISON: + strength += 10; + destroy_item(obj); + default: + break; + } + } + + // FIXME: impliment burning poisoned ammo + // else if ( it's ammo that's poisoned) { + // strength += number_of_ammo; + // destroy_item(ammo); + // } + obj = next; + } + + if (strength > 0) + place_cloud(CLOUD_FIRE, x, y, strength + roll_dice(3, strength / 4) ); + + return (strength); +} // end ignite_poison_objects() + +static int ignite_poison_clouds( int x, int y, int pow, int garbage ) +{ + UNUSED( pow ); + UNUSED( garbage ); + + bool did_anything = false; + + const int cloud = env.cgrid[x][y]; + + if (cloud != EMPTY_CLOUD) + { + if (env.cloud[ cloud ].type == CLOUD_STINK + || env.cloud[ cloud ].type == CLOUD_STINK_MON) + { + did_anything = true; + env.cloud[ cloud ].type = CLOUD_FIRE; + + env.cloud[ cloud ].decay /= 2; + + if (env.cloud[ cloud ].decay < 1) + env.cloud[ cloud ].decay = 1; + } + else if (env.cloud[ cloud ].type == CLOUD_POISON + || env.cloud[ cloud ].type == CLOUD_POISON_MON) + { + did_anything = true; + env.cloud[ cloud ].type = CLOUD_FIRE; + } + } + + return ((int) did_anything); +} // end ignite_poison_clouds() + +static int ignite_poison_monsters(int x, int y, int pow, int garbage) +{ + UNUSED( garbage ); + + struct bolt beam; + beam.flavour = BEAM_FIRE; // this is dumb, only used for adjust! + + dice_def dam_dice( 0, 5 + pow / 7 ); // dice added below if applicable + + const int mon_index = mgrd[x][y]; + if (mon_index == NON_MONSTER) + return (0); + + struct monsters *const mon = &menv[ mon_index ]; + + // Monsters which have poison corpses or poisonous attacks: + if (mons_corpse_thingy( mon->type ) == CE_POISONOUS + || mon->type == MONS_GIANT_ANT + || mon->type == MONS_SMALL_SNAKE + || mon->type == MONS_SNAKE + || mon->type == MONS_JELLYFISH + || mons_is_mimic( mon->type )) + { + dam_dice.num = 3; + } + + // Monsters which are poisoned: + int strength = 0; + + // first check for player poison: + int ench = mons_has_ench( mon, ENCH_YOUR_POISON_I, ENCH_YOUR_POISON_IV ); + if (ench != ENCH_NONE) + strength += ench - ENCH_YOUR_POISON_I + 1; + + // ... now monster poison: + ench = mons_has_ench( mon, ENCH_POISON_I, ENCH_POISON_IV ); + if (ench != ENCH_NONE) + strength += ench - ENCH_POISON_I + 1; + + // strength is now the sum of both poison types (although only + // one should actually be present at a given time): + dam_dice.num += strength; + + int damage = roll_dice( dam_dice ); + if (damage > 0) + { + damage = mons_adjust_flavoured( mon, beam, damage ); + +#if DEBUG_DIAGNOSTICS + snprintf( info, INFO_SIZE, "Dice: %dd%d; Damage: %d", + dam_dice.num, dam_dice.size, damage ); + + mpr( info, MSGCH_DIAGNOSTICS ); +#endif + + if (!player_hurt_monster( mon_index, damage )) + { + // Monster survived, remove any poison. + mons_del_ench( mon, ENCH_POISON_I, ENCH_POISON_IV ); + mons_del_ench( mon, ENCH_YOUR_POISON_I, ENCH_YOUR_POISON_IV ); + } + + return (1); + } + + return (0); +} + +void cast_ignite_poison(int pow) +{ + int damage = 0, strength = 0, pcount = 0, acount = 0, totalstrength = 0; + char item; + bool wasWielding = false; + char str_pass[ ITEMNAME_SIZE ]; + + // temp weapon of venom => temp fire brand + const int wpn = you.equip[EQ_WEAPON]; + + if (wpn != -1 + && you.duration[DUR_WEAPON_BRAND] + && get_weapon_brand( you.inv[wpn] ) == SPWPN_VENOM) + { + if (set_item_ego_type( you.inv[wpn], OBJ_WEAPONS, SPWPN_FLAMING )) + { + in_name( wpn, DESC_CAP_YOUR, str_pass ); + strcpy( info, str_pass ); + strcat( info, " bursts into flame!" ); + mpr(info); + + you.wield_change = true; + you.duration[DUR_WEAPON_BRAND] += 1 + you.duration[DUR_WEAPON_BRAND] / 2; + if (you.duration[DUR_WEAPON_BRAND] > 80) + you.duration[DUR_WEAPON_BRAND] = 80; + } + } + + totalstrength = 0; + + for (item = 0; item < ENDOFPACK; item++) + { + if (!you.inv[item].quantity) + continue; + + strength = 0; + + if (you.inv[item].base_type == OBJ_MISSILES) + { + if (you.inv[item].special == 3) + { // burn poison ammo + strength = you.inv[item].quantity; + acount += you.inv[item].quantity; + } + } + + if (you.inv[item].base_type == OBJ_POTIONS) + { + switch (you.inv[item].sub_type) + { + case POT_STRONG_POISON: + strength += 20 * you.inv[item].quantity; + break; + case POT_DEGENERATION: + case POT_POISON: + strength += 10 * you.inv[item].quantity; + break; + default: + break; + } // end switch + + if (strength) + pcount += you.inv[item].quantity; + } + + if (strength) + { + you.inv[item].quantity = 0; + if (item == you.equip[EQ_WEAPON]) + { + you.equip[EQ_WEAPON] = -1; + wasWielding = true; + } + } + + totalstrength += strength; + } + + if (acount > 0) + mpr("Some ammo you are carrying burns!"); + + if (pcount > 0) + { + snprintf( info, INFO_SIZE, "%s potion%s you are carrying explode%s!", + pcount > 1 ? "Some" : "A", + pcount > 1 ? "s" : "", + pcount > 1 ? "" : "s"); + mpr(info); + } + + if (wasWielding == true) + canned_msg( MSG_EMPTY_HANDED ); + + if (totalstrength) + { + place_cloud(CLOUD_FIRE, you.x_pos, you.y_pos, + random2(totalstrength / 4 + 1) + random2(totalstrength / 4 + 1) + + random2(totalstrength / 4 + 1) + random2(totalstrength / 4 + 1) + 1); + } + + // player is poisonous + if (you.mutation[MUT_SPIT_POISON] || you.mutation[MUT_STINGER] + || you.attribute[ATTR_TRANSFORMATION] == TRAN_SPIDER // poison attack + || (!player_is_shapechanged() + && (you.species == SP_GREEN_DRACONIAN // poison breath + || you.species == SP_KOBOLD // poisonous corpse + || you.species == SP_NAGA))) // spit poison + { + damage = roll_dice( 3, 5 + pow / 7 ); + } + + // player is poisoned + damage += roll_dice( you.poison, 6 ); + + if (damage) + { + const int resist = player_res_fire(); + + if (resist > 0) + { + mpr("You feel like your blood is boiling!"); + damage = damage / 3; + } + else if (resist < 0) + { + damage *= 3; + mpr("The poison in your system burns terribly!"); + } + else + { + mpr("The poison in your system burns!"); + } + + ouch( damage, 0, KILLED_BY_TARGETTING ); + + if (you.poison > 0) + { + mpr( "You feel that the poison has left your system." ); + you.poison = 0; + } + } + + apply_area_visible(ignite_poison_clouds, pow); + apply_area_visible(ignite_poison_objects, pow); + apply_area_visible(ignite_poison_monsters, pow); +} // end cast_ignite_poison() + +void cast_silence(int pow) +{ + if (!you.attribute[ATTR_WAS_SILENCED]) + mpr("A profound silence engulfs you."); + + you.attribute[ATTR_WAS_SILENCED] = 1; + + you.duration[DUR_SILENCE] += 10 + random2avg( pow, 2 ); + + if (you.duration[DUR_SILENCE] > 100) + you.duration[DUR_SILENCE] = 100; +} // end cast_silence() + + +/* ****************************************************************** +// no hooks for this anywhere {dlb}: + +void cast_animate_golem(int pow) +{ + // must have more than 20 max_hitpoints + + // must be wielding a Scroll of Paper (for chem) + + // must be standing on a pile of <foo> (for foo in: wood, metal, rock, stone) + + // Will cost you 5-10% of max_hitpoints, or 20 + some, whichever is more + mpr("You imbue the inanimate form with a portion of your life force."); + + naughty(NAUGHTY_CREATED_LIFE, 10); +} + +****************************************************************** */ + +static int discharge_monsters( int x, int y, int pow, int garbage ) +{ + UNUSED( garbage ); + + const int mon = mgrd[x][y]; + int damage = 0; + + struct bolt beam; + beam.flavour = BEAM_ELECTRICITY; // used for mons_adjust_flavoured + + if (x == you.x_pos && y == you.y_pos) + { + mpr( "You are struck by lightning." ); + damage = 3 + random2( 5 + pow / 10 ); + damage = check_your_resists( damage, BEAM_ELECTRICITY ); + ouch( damage, 0, KILLED_BY_WILD_MAGIC ); + } + else if (mon == NON_MONSTER) + return (0); + else if (mons_res_elec(&menv[mon]) > 0 || mons_flies(&menv[mon])) + return (0); + else + { + damage = 3 + random2( 5 + pow / 10 ); + damage = mons_adjust_flavoured( &menv[mon], beam, damage ); + + if (damage) + { + strcpy( info, ptr_monam( &(menv[mon]), DESC_CAP_THE ) ); + strcat( info, " is struck by lightning." ); + mpr( info ); + + player_hurt_monster( mon, damage ); + } + } + + // Recursion to give us chain-lightning -- bwr + // Low power slight chance added for low power characters -- bwr + if ((pow >= 10 && !one_chance_in(3)) || (pow >= 3 && one_chance_in(10))) + { + mpr( "The lightning arcs!" ); + pow /= (coinflip() ? 2 : 3); + damage += apply_random_around_square( discharge_monsters, x, y, + true, pow, 1 ); + } + else if (damage > 0) + { + // Only printed if we did damage, so that the messages in + // cast_discharge() are clean. -- bwr + mpr( "The lightning grounds out." ); + } + + return (damage); +} // end discharge_monsters() + +void cast_discharge( int pow ) +{ + int num_targs = 1 + random2( 1 + pow / 25 ); + int dam; + + dam = apply_random_around_square( discharge_monsters, you.x_pos, you.y_pos, + true, pow, num_targs ); + +#if DEBUG_DIAGNOSTICS + snprintf( info, INFO_SIZE, "Arcs: %d Damage: %d", num_targs, dam ); + mpr( info, MSGCH_DIAGNOSTICS ); +#endif + + if (dam == 0) + { + if (coinflip()) + mpr("The air around you crackles with electrical energy."); + else + { + bool plural = coinflip(); + snprintf( info, INFO_SIZE, "%s blue arc%s ground%s harmlessly %s you.", + plural ? "Some" : "A", + plural ? "s" : "", + plural ? " themselves" : "s itself", + plural ? "around" : (coinflip() ? "beside" : + coinflip() ? "behind" : "before") + ); + + mpr(info); + } + } +} // end cast_discharge() + +// NB: this must be checked against the same effects +// in fight.cc for all forms of attack !!! {dlb} +// This function should be currently unused (the effect is too powerful) +static int distortion_monsters(int x, int y, int pow, int message) +{ + int specdam = 0; + int monster_attacked = mgrd[x][y]; + + if (monster_attacked == NON_MONSTER) + return 0; + + struct monsters *defender = &menv[monster_attacked]; + + if (pow > 100) + pow = 100; + + if (x == you.x_pos && y == you.y_pos) + { + if (you.skills[SK_TRANSLOCATIONS] < random2(8)) + { + miscast_effect( SPTYP_TRANSLOCATION, pow / 9 + 1, pow, 100, + "a distortion effect" ); + } + else + { + miscast_effect( SPTYP_TRANSLOCATION, 1, 1, 100, + "a distortion effect" ); + } + + return 1; + } + + if (defender->type == MONS_BLINK_FROG) // any others resist? + { + int hp = defender->hit_points; + int max_hp = defender->max_hit_points; + + mpr("The blink frog basks in the translocular energy."); + + if (hp < max_hp) + hp += 1 + random2(1 + pow / 4) + random2(1 + pow / 7); + + if (hp > max_hp) + hp = max_hp; + + defender->hit_points = hp; + return 1; + } + else if (coinflip()) + { + strcpy(info, "Space bends around "); + strcat(info, ptr_monam(defender, DESC_NOCAP_THE)); + strcat(info, "."); + mpr(info); + specdam += 1 + random2avg( 7, 2 ) + random2(pow) / 40; + } + else if (coinflip()) + { + strcpy(info, "Space warps horribly around "); + strcat(info, ptr_monam( defender, DESC_NOCAP_THE )); + strcat(info, "!"); + mpr(info); + + specdam += 3 + random2avg( 12, 2 ) + random2(pow) / 25; + } + else if (one_chance_in(3)) + { + monster_blink(defender); + return 1; + } + else if (one_chance_in(3)) + { + monster_teleport(defender, coinflip()); + return 1; + } + else if (one_chance_in(3)) + { + monster_die(defender, KILL_RESET, 0); + return 1; + } + else if (message) + { + mpr("Nothing seems to happen."); + return 1; + } + + player_hurt_monster(monster_attacked, specdam); + + return (specdam); +} // end distortion_monsters() + +void cast_bend(int pow) +{ + apply_one_neighbouring_square( distortion_monsters, pow ); +} // end cast_bend() + +// Really this is just applying the best of Band/Warp weapon/Warp field +// into a spell that gives the "make monsters go away" benefit without +// the insane damage potential. -- bwr +int disperse_monsters(int x, int y, int pow, int message) +{ + UNUSED( message ); + + const int monster_attacked = mgrd[x][y]; + + if (monster_attacked == NON_MONSTER) + return 0; + + struct monsters *defender = &menv[monster_attacked]; + + if (defender->type == MONS_BLINK_FROG) + { + simple_monster_message(defender, " resists."); + return 1; + } + else if (check_mons_resist_magic(defender, pow)) + { + if (coinflip()) + { + simple_monster_message(defender, " partially resists."); + monster_blink(defender); + } + else + simple_monster_message(defender, " resists."); + + return 1; + } + else + { + monster_teleport( defender, true ); + return 1; + } + + return 0; +} + +void cast_dispersal(int pow) +{ + if (apply_area_around_square( disperse_monsters, + you.x_pos, you.y_pos, pow ) == 0) + { + mpr( "There is a brief shimmering in the air around you." ); + } +} + +static int spell_swap_func(int x, int y, int pow, int message) +{ + UNUSED( message ); + + int monster_attacked = mgrd[x][y]; + + if (monster_attacked == NON_MONSTER) + return 0; + + struct monsters *defender = &menv[monster_attacked]; + + if (defender->type == MONS_BLINK_FROG + || check_mons_resist_magic( defender, pow )) + { + simple_monster_message( defender, " resists." ); + } + else + { + // Swap doesn't seem to actually swap, but just sets the + // monster's location equal to the players... this being because + // the acr.cc call is going to move the player afterwards (for + // the regular friendly monster swap). So we'll go through + // standard swap procedure here... since we really want to apply + // the same swap_places function as with friendly monsters... + // see note over there. -- bwr + int old_x = defender->x; + int old_y = defender->y; + + if (swap_places( defender )) + { + you.x_pos = old_x; + you.y_pos = old_y; + } + } + + return 1; +} + +void cast_swap(int pow) +{ + apply_one_neighbouring_square( spell_swap_func, pow ); +} + +static int make_a_rot_cloud(int x, int y, int pow, int ctype) +{ + int next = 0, obj = mgrd[x][y]; + + if (obj == NON_MONSTER) + return 0; + + while (obj != NON_ITEM) + { + next = mitm[obj].link; + + if (mitm[obj].base_type == OBJ_CORPSES + && mitm[obj].sub_type == CORPSE_BODY) + { + if (!mons_skeleton(mitm[obj].plus)) + destroy_item(obj); + else + { + mitm[obj].sub_type = CORPSE_SKELETON; + mitm[obj].special = 200; + mitm[obj].colour = LIGHTGREY; + } + + place_cloud(ctype, x, y, + (3 + random2(pow / 4) + random2(pow / 4) + + random2(pow / 4))); + return 1; + } + + obj = next; + } + + return 0; +} // end make_a_rot_cloud() + +int make_a_normal_cloud(int x, int y, int pow, int ctype) +{ + place_cloud( ctype, x, y, + (3 + random2(pow / 4) + random2(pow / 4) + random2(pow / 4)) ); + + return 1; +} // end make_a_normal_cloud() + +#if 0 + +static int make_a_random_cloud(int x, int y, int pow, int ctype) +{ + if (ctype == CLOUD_NONE) + ctype = CLOUD_BLACK_SMOKE; + + unsigned char cloud_material; + + switch (random2(9)) + { + case 0: + cloud_material = CLOUD_FIRE; + break; + case 1: + cloud_material = CLOUD_STINK; + break; + case 2: + cloud_material = CLOUD_COLD; + break; + case 3: + cloud_material = CLOUD_POISON; + break; + case 4: + cloud_material = CLOUD_BLUE_SMOKE; + break; + case 5: + cloud_material = CLOUD_STEAM; + break; + case 6: + cloud_material = CLOUD_PURP_SMOKE; + break; + default: + cloud_material = ctype; + break; + } + + // that last bit is equivalent to "random2(pow/4) + random2(pow/4) + // + random2(pow/4)" {dlb} + // can you see the pattern? {dlb} + place_cloud(cloud_material, x, y, 3 + random2avg(3 * (pow / 4) - 2, 3)); + + return 1; +} // end make_a_random_cloud() + +#endif + +static int passwall(int x, int y, int pow, int garbage) +{ + UNUSED( garbage ); + + char dx, dy, nx = x, ny = y; + int howdeep = 0; + bool done = false; + int shallow = 1 + (you.skills[SK_EARTH_MAGIC] / 8); + + // allow statues as entry points? + if (grd[x][y] != DNGN_ROCK_WALL) + // Irony: you can start on a secret door but not a door. + // Worked stone walls are out, they're not diggable and + // are used for impassable walls... I'm not sure we should + // even allow statues (should be contiguous rock) -- bwr + { + mpr("That's not a passable wall."); + return 0; + } + + dx = x - you.x_pos; + dy = y - you.y_pos; + + while (!done) + { + // I'm trying to figure proper borders out {dlb} + // FIXME: dungeon border? + if (nx > (GXM - 1) || ny > (GYM - 1) || nx < 2 || ny < 2) + { + mpr("You sense an overwhelming volume of rock."); + return 0; + } + + switch (grd[nx][ny]) + { + default: + done = true; + break; + case DNGN_ROCK_WALL: + case DNGN_ORCISH_IDOL: + case DNGN_GRANITE_STATUE: + case DNGN_SECRET_DOOR: + nx += dx; + ny += dy; + howdeep++; + break; + } + } + + int range = shallow + random2(pow) / 25; + + if (howdeep > shallow) + { + mpr("This rock feels deep."); + + if (yesno("Try anyway?")) + { + if (howdeep > range) + { + ouch(1 + you.hp, 0, KILLED_BY_PETRIFICATION); + //jmf: not return; if wizard, successful transport is option + } + } + else + { + if (one_chance_in(30)) + mpr("Wuss."); + else + canned_msg(MSG_OK); + return 1; + } + } + + // Note that the delay was (1 + howdeep * 2), but now that the + // delay is stopped when the player is attacked it can be much + // shorter since its harder to use for quick escapes. -- bwr + start_delay( DELAY_PASSWALL, 2 + howdeep, nx, ny ); + + return 1; +} // end passwall() + +void cast_passwall(int pow) +{ + apply_one_neighbouring_square(passwall, pow); +} // end cast_passwall() + +static int intoxicate_monsters(int x, int y, int pow, int garbage) +{ + UNUSED( pow ); + UNUSED( garbage ); + + int mon = mgrd[x][y]; + + if (mon == NON_MONSTER) + return 0; + if (mons_intel(menv[mon].type) < I_NORMAL) + return 0; + if (mons_holiness(menv[mon].type) != MH_NATURAL) + return 0; + if (mons_res_poison(&menv[mon]) > 0) + return 0; + + mons_add_ench(&menv[mon], ENCH_CONFUSION); + return 1; +} // end intoxicate_monsters() + +void cast_intoxicate(int pow) +{ + potion_effect( POT_CONFUSION, 10 + (100 - pow) / 10); + + if (one_chance_in(20) && lose_stat( STAT_INTELLIGENCE, 1 + random2(3) )) + mpr("Your head spins!"); + + apply_area_visible(intoxicate_monsters, pow); +} // end cast_intoxicate() + +// intended as a high-level Elven (a)bility +static int glamour_monsters(int x, int y, int pow, int garbage) +{ + UNUSED( garbage ); + + int mon = mgrd[x][y]; + + // Power in this function is already limited by a function of + // experience level (10 + level / 2) since it's only an ability, + // never an actual spell. -- bwr + + if (mon == NON_MONSTER) + return (0); + + if (one_chance_in(5)) + return (0); + + if (mons_intel(menv[mon].type) < I_NORMAL) + return (0); + + if (mons_holiness(mon) != MH_NATURAL) + return (0); + + if (!mons_is_humanoid( menv[mon].type )) + return (0); + + const char show_char = mons_char( menv[mon].type ); + + // gargoyles are immune. + if (menv[mon].type == MONS_GARGOYLE + || menv[mon].type == MONS_METAL_GARGOYLE + || menv[mon].type == MONS_MOLTEN_GARGOYLE) + { + return (0); + } + + // orcs resist thru hatred of elves + // elves resist cause they're elves + // boggarts are malevolent highly magical wee-folk + if (show_char == 'o' || show_char == 'e' || menv[mon].type == MONS_BOGGART) + pow = (pow / 2) + 1; + + if (check_mons_resist_magic(&menv[mon], pow)) + return (0); + + switch (random2(6)) + { + case 0: + mons_add_ench(&menv[mon], ENCH_FEAR); + break; + case 1: + case 4: + mons_add_ench(&menv[mon], ENCH_CONFUSION); + break; + case 2: + case 5: + mons_add_ench(&menv[mon], ENCH_CHARM); + break; + case 3: + menv[mon].behaviour = BEH_SLEEP; + break; + } + + // why no, there's no message as to which effect happened >:^) + if (!one_chance_in(4)) + { + strcpy(info, ptr_monam( &(menv[mon]), DESC_CAP_THE)); + + switch (random2(4)) + { + case 0: + strcat(info, " looks dazed."); + break; + case 1: + strcat(info, " blinks several times."); + break; + case 2: + strcat(info, " rubs its eye"); + if (menv[mon].type != MONS_CYCLOPS) + strcat(info, "s"); + strcat(info, "."); + break; + case 4: + strcat(info, " tilts its head."); + break; + } + + mpr(info); + } + + return (1); +} // end glamour_monsters() + +void cast_glamour(int pow) +{ + apply_area_visible(glamour_monsters, pow); +} // end cast_glamour() + +bool backlight_monsters(int x, int y, int pow, int garbage) +{ + UNUSED( pow ); + UNUSED( garbage ); + + int mon = mgrd[x][y]; + + if (mon == NON_MONSTER) + return (false); + + switch (menv[mon].type) + { + //case MONS_INSUBSTANTIAL_WISP: //jmf: I'm not sure if these glow or not + //case MONS_VAPOUR: + case MONS_UNSEEN_HORROR: // consider making this visible? probably not. + return (false); + + case MONS_FIRE_VORTEX: + case MONS_ANGEL: + case MONS_FIEND: + case MONS_SHADOW: + case MONS_EFREET: + case MONS_HELLION: + case MONS_GLOWING_SHAPESHIFTER: + case MONS_FIRE_ELEMENTAL: + case MONS_AIR_ELEMENTAL: + case MONS_SHADOW_FIEND: + case MONS_SPECTRAL_WARRIOR: + case MONS_ORANGE_RAT: + case MONS_BALRUG: + case MONS_SPATIAL_VORTEX: + case MONS_PIT_FIEND: + case MONS_SHINING_EYE: + case MONS_DAEVA: + case MONS_SPECTRAL_THING: + case MONS_ORB_OF_FIRE: + case MONS_EYE_OF_DEVASTATION: + return (false); // already glowing or invisible + default: + break; + } + + int lvl = mons_has_ench( &menv[mon], ENCH_BACKLIGHT_I, ENCH_BACKLIGHT_IV ); + + if (lvl == ENCH_NONE) + simple_monster_message( &menv[mon], " is outlined in light." ); + else if (lvl == ENCH_BACKLIGHT_IV) + simple_monster_message( &menv[mon], " glows brighter for a moment." ); + else + { + // remove old level + mons_del_ench( &menv[mon], ENCH_BACKLIGHT_I, ENCH_BACKLIGHT_III, true ); + simple_monster_message( &menv[mon], " glows brighter." ); + } + + // this enchantment wipes out invisibility (neat) + mons_del_ench( &menv[mon], ENCH_INVIS ); + mons_add_ench( &menv[mon], ENCH_BACKLIGHT_IV ); + + return (true); +} // end backlight_monsters() + +void cast_evaporate(int pow) +{ + // experimenting with allowing the potion to be thrown... we're + // still making it have to be "in hands" at this point. -- bwr + struct dist spelld; + struct bolt beem; + + const int potion = prompt_invent_item( "Throw which potion?", OBJ_POTIONS ); + + if (potion == -1) + { + snprintf( info, INFO_SIZE, "Wisps of steam play over your %s!", + your_hand(true) ); + + mpr(info); + return; + } + else if (you.inv[potion].base_type != OBJ_POTIONS) + { + mpr( "This spell works only on potions!" ); + canned_msg(MSG_SPELL_FIZZLES); + return; + } + + mpr( STD_DIRECTION_PROMPT, MSGCH_PROMPT ); + + message_current_target(); + + direction( spelld, DIR_NONE, TARG_ENEMY ); + + if (!spelld.isValid) + { + canned_msg(MSG_SPELL_FIZZLES); + return; + } + + beem.target_x = spelld.tx; + beem.target_y = spelld.ty; + + beem.source_x = you.x_pos; + beem.source_y = you.y_pos; + + strcpy( beem.beam_name, "potion" ); + beem.colour = you.inv[potion].colour; + beem.range = 9; + beem.rangeMax = 9; + beem.type = SYM_FLASK; + beem.beam_source = MHITYOU; + beem.thrower = KILL_YOU_MISSILE; + beem.aux_source = NULL; + beem.isBeam = false; + beem.isTracer = false; + + beem.hit = you.dex / 2 + roll_dice( 2, you.skills[SK_THROWING] / 2 + 1 ); + beem.damage = dice_def( 1, 0 ); // no damage, just producing clouds + beem.ench_power = pow; // used for duration only? + + beem.flavour = BEAM_POTION_STINKING_CLOUD; + + switch (you.inv[potion].sub_type) + { + case POT_STRONG_POISON: + beem.flavour = BEAM_POTION_POISON; + beem.ench_power *= 2; + break; + + case POT_DEGENERATION: + beem.flavour = (coinflip() ? BEAM_POTION_POISON : BEAM_POTION_MIASMA); + beem.ench_power *= 2; + break; + + case POT_POISON: + beem.flavour = BEAM_POTION_POISON; + break; + + case POT_DECAY: + beem.flavour = BEAM_POTION_MIASMA; + beem.ench_power *= 2; + break; + + case POT_PARALYSIS: + beem.ench_power *= 2; + // fall through + case POT_CONFUSION: + case POT_SLOWING: + beem.flavour = BEAM_POTION_STINKING_CLOUD; + break; + + case POT_WATER: + case POT_PORRIDGE: + beem.flavour = BEAM_POTION_STEAM; + break; + + case POT_BERSERK_RAGE: + beem.flavour = (coinflip() ? BEAM_POTION_FIRE : BEAM_POTION_STEAM); + break; + + case POT_MUTATION: + case POT_GAIN_STRENGTH: + case POT_GAIN_DEXTERITY: + case POT_GAIN_INTELLIGENCE: + case POT_EXPERIENCE: + case POT_MAGIC: + switch (random2(5)) + { + case 0: beem.flavour = BEAM_POTION_FIRE; break; + case 1: beem.flavour = BEAM_POTION_COLD; break; + case 2: beem.flavour = BEAM_POTION_POISON; break; + case 3: beem.flavour = BEAM_POTION_MIASMA; break; + default: beem.flavour = BEAM_POTION_RANDOM; break; + } + break; + + default: + switch (random2(12)) + { + case 0: beem.flavour = BEAM_POTION_FIRE; break; + case 1: beem.flavour = BEAM_POTION_STINKING_CLOUD; break; + case 2: beem.flavour = BEAM_POTION_COLD; break; + case 3: beem.flavour = BEAM_POTION_POISON; break; + case 4: beem.flavour = BEAM_POTION_RANDOM; break; + case 5: beem.flavour = BEAM_POTION_BLUE_SMOKE; break; + case 6: beem.flavour = BEAM_POTION_BLACK_SMOKE; break; + case 7: beem.flavour = BEAM_POTION_PURP_SMOKE; break; + default: beem.flavour = BEAM_POTION_STEAM; break; + } + break; + } + + if (coinflip()) + exercise( SK_THROWING, 1 ); + + fire_beam(beem); + + // both old and new code use up a potion: + dec_inv_item_quantity( potion, 1 ); + + return; +} // end cast_evaporate() + +// The intent of this spell isn't to produce helpful potions +// for drinking, but rather to provide ammo for the Evaporate +// spell out of corpses, thus potentially making it useful. +// Producing helpful potions would break game balance here... +// and producing more than one potion from a corpse, or not +// using up the corpse might also lead to game balance problems. -- bwr +void cast_fulsome_distillation( int powc ) +{ + char str_pass[ ITEMNAME_SIZE ]; + + if (powc > 50) + powc = 50; + + int corpse = -1; + + // Search items at the players location for corpses. + // XXX: Turn this into a separate function and merge with + // the messes over in butchery, animating, and maybe even + // item pickup from stacks (which would make it easier to + // create a floor stack menu system later) -- bwr + for (int curr_item = igrd[you.x_pos][you.y_pos]; + curr_item != NON_ITEM; + curr_item = mitm[curr_item].link) + { + if (mitm[curr_item].base_type == OBJ_CORPSES + && mitm[curr_item].sub_type == CORPSE_BODY) + { + it_name( curr_item, DESC_NOCAP_THE, str_pass ); + snprintf( info, INFO_SIZE, "Distill a potion from %s?", str_pass ); + + if (yesno( info, true, false )) + { + corpse = curr_item; + break; + } + } + } + + if (corpse == -1) + { + canned_msg(MSG_SPELL_FIZZLES); + return; + } + + const bool rotten = (mitm[corpse].special < 100); + const bool big_monster = (mons_type_hit_dice( mitm[corpse].plus ) >= 5); + const bool power_up = (rotten && big_monster); + + int potion_type = POT_WATER; + + switch (mitm[corpse].plus) + { + case MONS_GIANT_BAT: // extracting batty behaviour : 1 + case MONS_UNSEEN_HORROR: // extracting batty behaviour : 7 + case MONS_GIANT_BLOWFLY: // extracting batty behaviour : 5 + potion_type = POT_CONFUSION; + break; + + case MONS_RED_WASP: // paralysis attack : 8 + case MONS_YELLOW_WASP: // paralysis attack : 4 + potion_type = POT_PARALYSIS; + break; + + case MONS_SNAKE: // clean meat, but poisonous attack : 2 + case MONS_GIANT_ANT: // clean meat, but poisonous attack : 3 + potion_type = (power_up ? POT_POISON : POT_CONFUSION); + break; + + case MONS_ORANGE_RAT: // poisonous meat, but draining attack : 3 + potion_type = (power_up ? POT_DECAY : POT_POISON); + break; + + case MONS_SPINY_WORM: // 12 + potion_type = (power_up ? POT_DECAY : POT_STRONG_POISON); + break; + + default: + switch (mons_corpse_thingy( mitm[corpse].plus )) + { + case CE_CLEAN: + potion_type = (power_up ? POT_CONFUSION : POT_WATER); + break; + + case CE_CONTAMINATED: + potion_type = (power_up ? POT_DEGENERATION : POT_POISON); + break; + + case CE_POISONOUS: + potion_type = (power_up ? POT_STRONG_POISON : POT_POISON); + break; + + case CE_MUTAGEN_RANDOM: + case CE_MUTAGEN_GOOD: // unused + case CE_RANDOM: // unused + potion_type = POT_MUTATION; + break; + + case CE_MUTAGEN_BAD: // unused + case CE_ROTTEN: // actually this only occurs via mangling + case CE_HCL: // necrophage + potion_type = (power_up ? POT_DECAY : POT_STRONG_POISON); + break; + + case CE_NOCORPSE: // shouldn't occur + default: + break; + } + break; + } + + // If not powerful enough, we downgrade the potion + if (random2(50) > powc + 10 * rotten) + { + switch (potion_type) + { + case POT_DECAY: + case POT_DEGENERATION: + case POT_STRONG_POISON: + potion_type = POT_POISON; + break; + + case POT_MUTATION: + case POT_POISON: + potion_type = POT_CONFUSION; + break; + + case POT_PARALYSIS: + potion_type = POT_SLOWING; + break; + + case POT_CONFUSION: + case POT_SLOWING: + default: + potion_type = POT_WATER; + break; + } + } + + // We borrow the corpse's object to make our potion: + mitm[corpse].base_type = OBJ_POTIONS; + mitm[corpse].sub_type = potion_type; + mitm[corpse].quantity = 1; + mitm[corpse].plus = 0; + mitm[corpse].plus2 = 0; + item_colour( mitm[corpse] ); // sets special as well + + it_name( corpse, DESC_NOCAP_A, str_pass ); + snprintf( info, INFO_SIZE, "You extract %s from the corpse.", + str_pass ); + mpr( info ); + + // try to move the potion to the player (for convenience) + if (move_item_to_player( corpse, 1 ) != 1) + { + mpr( "Unfortunately, you can't carry it right now!" ); + } +} + +void make_shuggoth(int x, int y, int hp) +{ + int mon = create_monster( MONS_SHUGGOTH, 100 + random2avg(58, 3), + BEH_HOSTILE, x, y, MHITNOT, 250 ); + + if (mon != -1) + { + menv[mon].hit_points = hp; + menv[mon].max_hit_points = hp; + } + + return; +} // end make_shuggoth() + +static int rot_living(int x, int y, int pow, int message) +{ + UNUSED( message ); + + int mon = mgrd[x][y]; + int ench; + + if (mon == NON_MONSTER) + return 0; + + if (mons_holiness(menv[mon].type) != MH_NATURAL) + return 0; + + if (check_mons_resist_magic(&menv[mon], pow)) + return 0; + + ench = ((random2(pow) + random2(pow) + random2(pow) + random2(pow)) / 4); + + if (ench >= 50) + ench = ENCH_YOUR_ROT_IV; + else if (ench >= 35) + ench = ENCH_YOUR_ROT_III; + else if (ench >= 20) + ench = ENCH_YOUR_ROT_II; + else + ench = ENCH_YOUR_ROT_I; + + mons_add_ench(&menv[mon], ench); + + return 1; +} // end rot_living() + +static int rot_undead(int x, int y, int pow, int garbage) +{ + UNUSED( garbage ); + + int mon = mgrd[x][y]; + int ench; + + if (mon == NON_MONSTER) + return 0; + + if (mons_holiness(menv[mon].type) != MH_UNDEAD) + return 0; + + if (check_mons_resist_magic(&menv[mon], pow)) + return 0; + + // this does not make sense -- player mummies are + // immune to rotting (or have been) -- so what is + // the schema in use here to determine rotting??? {dlb} + + //jmf: up for discussion. it is clearly unfair to + // rot player mummies. + // the `shcema' here is: corporeal non-player undead + // rot, discorporeal undead don't rot. if you wanna + // insist that monsters get the same treatment as + // players, I demand my player mummies get to worship + // the evil mummy & orc god. + switch (menv[mon].type) + { + case MONS_NECROPHAGE: + case MONS_ZOMBIE_SMALL: + case MONS_LICH: + case MONS_MUMMY: + case MONS_VAMPIRE: + case MONS_ZOMBIE_LARGE: + case MONS_WIGHT: + case MONS_GHOUL: + case MONS_BORIS: + case MONS_ANCIENT_LICH: + case MONS_VAMPIRE_KNIGHT: + case MONS_VAMPIRE_MAGE: + case MONS_GUARDIAN_MUMMY: + case MONS_GREATER_MUMMY: + case MONS_MUMMY_PRIEST: + break; + case MONS_ROTTING_HULK: + default: + return 0; // immune (no flesh) or already rotting + } + + ench = ((random2(pow) + random2(pow) + random2(pow) + random2(pow)) / 4); + + if (ench >= 50) + ench = ENCH_YOUR_ROT_IV; + else if (ench >= 35) + ench = ENCH_YOUR_ROT_III; + else if (ench >= 20) + ench = ENCH_YOUR_ROT_II; + else + ench = ENCH_YOUR_ROT_I; + + mons_add_ench(&menv[mon], ench); + + return 1; +} // end rot_undead() + +static int rot_corpses(int x, int y, int pow, int garbage) +{ + UNUSED( garbage ); + + return make_a_rot_cloud(x, y, pow, CLOUD_MIASMA); +} // end rot_corpses() + +void cast_rotting(int pow) +{ + apply_area_visible(rot_living, pow); + apply_area_visible(rot_undead, pow); + apply_area_visible(rot_corpses, pow); + return; +} // end cast_rotting() + +void do_monster_rot(int mon) +{ + int damage = 1 + random2(3); + + if (mons_holiness(menv[mon].type) == MH_UNDEAD && random2(5)) + { + apply_area_cloud(make_a_normal_cloud, menv[mon].x, menv[mon].y, + 10, 1, CLOUD_MIASMA); + } + + player_hurt_monster( mon, damage ); + return; +} // end do_monster_rot() + +static int snake_charm_monsters(int x, int y, int pow, int message) +{ + UNUSED( message ); + + int mon = mgrd[x][y]; + + if (mon == NON_MONSTER) return 0; + if (mons_friendly(&menv[mon])) return 0; + if (one_chance_in(4)) return 0; + if (mons_char(menv[mon].type) != 'S') return 0; + if (check_mons_resist_magic(&menv[mon], pow)) return 0; + + menv[mon].attitude = ATT_FRIENDLY; + snprintf( info, INFO_SIZE, "%s sways back and forth.", ptr_monam( &(menv[mon]), DESC_CAP_THE )); + mpr(info); + + return 1; +} + +void cast_snake_charm(int pow) +{ + // powc = (you.experience_level * 2) + (you.skills[SK_INVOCATIONS] * 3); + apply_one_neighbouring_square(snake_charm_monsters, pow); +} + +void cast_fragmentation(int pow) // jmf: ripped idea from airstrike +{ + struct dist beam; + struct bolt blast; + int debris = 0; + int trap; + bool explode = false; + bool hole = true; + const char *what = NULL; + + mpr("Fragment what (e.g. a wall)?", MSGCH_PROMPT); + direction( beam, DIR_TARGET, TARG_ENEMY ); + + if (!beam.isValid) + { + canned_msg(MSG_SPELL_FIZZLES); + return; + } + + //FIXME: if (player typed '>' to attack floor) goto do_terrain; + blast.beam_source = MHITYOU; + blast.thrower = KILL_YOU; + blast.aux_source = NULL; + blast.ex_size = 1; // default + blast.type = '#'; + blast.colour = 0; + blast.target_x = beam.tx; + blast.target_y = beam.ty; + blast.isTracer = false; + blast.flavour = BEAM_FRAG; + + // Number of dice vary... 3 is easy/common, but it can get as high as 6. + blast.damage = dice_def( 0, 5 + pow / 10 ); + + const int grid = grd[beam.tx][beam.ty]; + const int mon = mgrd[beam.tx][beam.ty]; + + const bool okay_to_dest = ((beam.tx > 5 && beam.tx < GXM - 5) + && (beam.ty > 5 && beam.ty < GYM - 5)); + + if (mon != NON_MONSTER) + { + // This needs its own hand_buff... we also need to do it first + // in case the target dies. -- bwr + char explode_msg[80]; + + snprintf( explode_msg, sizeof( explode_msg ), "%s explodes!", + ptr_monam( &(menv[mon]), DESC_CAP_THE ) ); + + switch (menv[mon].type) + { + case MONS_ICE_BEAST: // blast of ice fragments + case MONS_SIMULACRUM_SMALL: + case MONS_SIMULACRUM_LARGE: + explode = true; + strcpy(blast.beam_name, "icy blast"); + blast.colour = WHITE; + blast.damage.num = 2; + blast.flavour = BEAM_ICE; + if (player_hurt_monster(mon, roll_dice( blast.damage ))) + blast.damage.num += 1; + break; + + case MONS_FLYING_SKULL: + case MONS_SKELETON_SMALL: + case MONS_SKELETON_LARGE: // blast of bone + explode = true; + + snprintf( info, INFO_SIZE, "The sk%s explodes into sharp fragments of bone!", + (menv[mon].type == MONS_FLYING_SKULL) ? "ull" : "eleton"); + + strcpy(blast.beam_name, "blast of bone shards"); + + blast.colour = LIGHTGREY; + + if (random2(50) < (pow / 5)) // potential insta-kill + { + monster_die(&menv[mon], KILL_YOU, 0); + blast.damage.num = 4; + } + else + { + blast.damage.num = 2; + if (player_hurt_monster(mon, roll_dice( blast.damage ))) + blast.damage.num = 4; + } + goto all_done; // i.e. no "Foo Explodes!" + + case MONS_WOOD_GOLEM: + explode = false; + simple_monster_message(&menv[mon], " shudders violently!"); + + // We use blast.damage not only for inflicting damage here, + // but so that later on we'll know that the spell didn't + // fizzle (since we don't actually explode wood golems). -- bwr + blast.damage.num = 2; + player_hurt_monster( mon, roll_dice( blast.damage ) ); + break; + + case MONS_IRON_GOLEM: + case MONS_METAL_GARGOYLE: + explode = true; + strcpy( blast.beam_name, "blast of metal fragments" ); + blast.colour = CYAN; + blast.damage.num = 4; + if (player_hurt_monster(mon, roll_dice( blast.damage ))) + blast.damage.num += 2; + break; + + case MONS_CLAY_GOLEM: // assume baked clay and not wet loam + case MONS_STONE_GOLEM: + case MONS_EARTH_ELEMENTAL: + case MONS_GARGOYLE: + explode = true; + blast.ex_size = 2; + strcpy(blast.beam_name, "blast of rock fragments"); + blast.colour = BROWN; + blast.damage.num = 3; + if (player_hurt_monster(mon, roll_dice( blast.damage ))) + blast.damage.num += 1; + break; + + case MONS_CRYSTAL_GOLEM: + explode = true; + blast.ex_size = 2; + strcpy(blast.beam_name, "blast of crystal shards"); + blast.colour = WHITE; + blast.damage.num = 4; + if (player_hurt_monster(mon, roll_dice( blast.damage ))) + blast.damage.num += 2; + break; + + default: + blast.damage.num = 1; // to mark that a monster was targetted + + // Yes, this spell does lousy damage if the + // monster isn't susceptable. -- bwr + player_hurt_monster( mon, roll_dice( 1, 5 + pow / 25 ) ); + goto do_terrain; + } + + mpr( explode_msg ); + goto all_done; + } + + do_terrain: + // FIXME: do nothing in Abyss & Pandemonium? + + switch (grid) + { + // + // Stone and rock terrain + // + case DNGN_ROCK_WALL: + case DNGN_SECRET_DOOR: + blast.colour = env.rock_colour; + // fall-through + case DNGN_STONE_WALL: + what = "wall"; + if (player_in_branch( BRANCH_HALL_OF_ZOT )) + blast.colour = env.rock_colour; + // fall-through + case DNGN_ORCISH_IDOL: + if (what == NULL) + what = "stone idol"; + if (blast.colour == 0) + blast.colour = DARKGREY; + // fall-through + case DNGN_GRANITE_STATUE: // normal rock -- big explosion + if (what == NULL) + what = "statue"; + + explode = true; + + strcpy(blast.beam_name, "blast of rock fragments"); + blast.damage.num = 3; + if (blast.colour == 0) + blast.colour = LIGHTGREY; + + if (okay_to_dest + && (grid == DNGN_ORCISH_IDOL + || grid == DNGN_GRANITE_STATUE + || (pow >= 40 && grid == DNGN_ROCK_WALL && one_chance_in(3)) + || (pow >= 60 && grid == DNGN_STONE_WALL && one_chance_in(10)))) + { + // terrain blew up real good: + blast.ex_size = 2; + grd[beam.tx][beam.ty] = DNGN_FLOOR; + debris = DEBRIS_ROCK; + } + break; + + // + // Metal -- small but nasty explosion + // + + case DNGN_METAL_WALL: + what = "metal wall"; + blast.colour = CYAN; + // fallthru + case DNGN_SILVER_STATUE: + if (what == NULL) + { + what = "silver statue"; + blast.colour = WHITE; + } + + explode = true; + strcpy( blast.beam_name, "blast of metal fragments" ); + blast.damage.num = 4; + + if (okay_to_dest && pow >= 80 && random2(500) < pow / 5) + { + blast.damage.num += 2; + grd[beam.tx][beam.ty] = DNGN_FLOOR; + debris = DEBRIS_METAL; + } + break; + + // + // Crystal + // + + case DNGN_GREEN_CRYSTAL_WALL: // crystal -- large & nasty explosion + what = "crystal wall"; + blast.colour = GREEN; + // fallthru + case DNGN_ORANGE_CRYSTAL_STATUE: + if (what == NULL) + { + what = "crystal statue"; + blast.colour = LIGHTRED; //jmf: == orange, right? + } + + explode = true; + blast.ex_size = 2; + strcpy(blast.beam_name, "blast of crystal shards"); + blast.damage.num = 5; + + if (okay_to_dest + && ((grid == DNGN_GREEN_CRYSTAL_WALL && coinflip()) + || (grid == DNGN_ORANGE_CRYSTAL_STATUE + && pow >= 50 && one_chance_in(10)))) + { + blast.ex_size = coinflip() ? 3 : 2; + grd[beam.tx][beam.ty] = DNGN_FLOOR; + debris = DEBRIS_CRYSTAL; + } + break; + + // + // Traps + // + + case DNGN_UNDISCOVERED_TRAP: + case DNGN_TRAP_MECHANICAL: + trap = trap_at_xy( beam.tx, beam.ty ); + if (trap != -1 + && trap_category( env.trap[trap].type ) != DNGN_TRAP_MECHANICAL) + { + // non-mechanical traps don't explode with this spell -- bwr + break; + } + + // undiscovered traps appear as exploding from the floor -- bwr + what = ((grid == DNGN_UNDISCOVERED_TRAP) ? "floor" : "trap"); + + explode = true; + hole = false; // to hit monsters standing on traps + strcpy( blast.beam_name, "blast of fragments" ); + blast.colour = env.floor_colour; // in order to blend in + blast.damage.num = 2; + + // Exploded traps are nonfunctional, ammo is also ruined -- bwr + if (okay_to_dest) + { + grd[beam.tx][beam.ty] = DNGN_FLOOR; + env.trap[trap].type = TRAP_UNASSIGNED; + } + break; + + // + // Stone doors and arches + // + + case DNGN_OPEN_DOOR: + case DNGN_CLOSED_DOOR: + // Doors always blow up, stone arches never do (would cause problems) + if (okay_to_dest) + grd[beam.tx][beam.ty] = DNGN_FLOOR; + + // fall-through + case DNGN_STONE_ARCH: // floor -- small explosion + explode = true; + hole = false; // to hit monsters standing on doors + strcpy( blast.beam_name, "blast of rock fragments" ); + blast.colour = LIGHTGREY; + blast.damage.num = 2; + break; + + // + // Permarock and floor are unaffected -- bwr + // + case DNGN_PERMAROCK_WALL: + case DNGN_FLOOR: + explode = false; + snprintf( info, INFO_SIZE, "%s seems to be unnaturally hard.", + (grid == DNGN_PERMAROCK_WALL) ? "That wall" + : "The dungeon floor" ); + explode = false; + break; + + case DNGN_TRAP_III: // What are these? Should they explode? -- bwr + default: + // FIXME: cute message for water? + break; + } + + all_done: + if (explode && blast.damage.num > 0) + { + if (what != NULL) + { + snprintf( info, INFO_SIZE, "The %s explodes!", what); + mpr(info); + } + + explosion( blast, hole ); + } + else if (blast.damage.num == 0) + { + // if damage dice are zero we assume that nothing happened at all. + canned_msg(MSG_SPELL_FIZZLES); + } + + if (debris) + place_debris(beam.tx, beam.ty, debris); +} // end cast_fragmentation() + +void cast_twist(int pow) +{ + struct dist targ; + struct bolt tmp; // used, but ignored + + // level one power cap -- bwr + if (pow > 25) + pow = 25; + + // Get target, using DIR_TARGET for targetting only, + // since we don't use fire_beam() for this spell. + if (spell_direction(targ, tmp, DIR_TARGET) == -1) + return; + + const int mons = mgrd[ targ.tx ][ targ.ty ]; + + // anything there? + if (mons == NON_MONSTER || targ.isMe) + { + mpr("There is no monster there!"); + return; + } + + // Monster can magically save vs attack. + if (check_mons_resist_magic( &menv[ mons ], pow * 2 )) + { + simple_monster_message( &menv[ mons ], " resists." ); + return; + } + + // Roll the damage... this spell is pretty low on damage, because + // it can target any monster in LOS (high utility). This is + // similar to the damage done by Magic Dart (although, the + // distribution is much more uniform). -- bwr + int damage = 1 + random2( 3 + pow / 5 ); + + // Inflict the damage + player_hurt_monster( mons, damage ); + return; +} // end cast_twist() + +// +// This version of far strike is a bit too creative for level one, in +// order to make it work we needed to put a lot of restrictions on it +// (like the damage limitation), which wouldn't be necessary if it were +// a higher level spell. This code might come back as a high level +// translocation spell later (maybe even with special effects if it's +// using some of Josh's ideas about occasionally losing the weapon). +// Would potentially make a good high-level, second book Warper spell +// (since Translocations is a utility school, it should be higher level +// that usual... especially if it turns into a flavoured smiting spell). +// This can all wait until after the next release (as it would be better +// to have a proper way to do a single weapon strike here (you_attack +// does far more than we need or want here)). --bwr +// + +void cast_far_strike(int pow) +{ + struct dist targ; + struct bolt tmp; // used, but ignored + + // Get target, using DIR_TARGET for targetting only, + // since we don't use fire_beam() for this spell. + if (spell_direction(targ, tmp, DIR_TARGET) == -1) + return; + + // Get the target monster... + if (mgrd[targ.tx][targ.ty] == NON_MONSTER + || targ.isMe) + { + mpr("There is no monster there!"); + return; + } + + // Start with weapon base damage... + const int weapon = you.equip[ EQ_WEAPON ]; + + int damage = 3; // default unarmed damage + int speed = 10; // default unarmed time + + if (weapon != -1) // if not unarmed + { + // look up the damage base + if (you.inv[ weapon ].base_type == OBJ_WEAPONS) + { + damage = property( you.inv[ weapon ], PWPN_DAMAGE ); + speed = property( you.inv[ weapon ], PWPN_SPEED ); + + if (get_weapon_brand( you.inv[ weapon ] ) == SPWPN_SPEED) + { + speed *= 5; + speed /= 10; + } + } + else if (item_is_staff( you.inv[ weapon ] )) + { + damage = property( you.inv[ weapon ], PWPN_DAMAGE ); + speed = property( you.inv[ weapon ], PWPN_SPEED ); + } + } + + // Because we're casting a spell (and don't want to make this level + // one spell too good), we're not applying skill speed bonuses and at + // the very least guaranteeing one full turn (speed == 10) like the + // other spells (if any thing else related to speed is changed, at + // least leave this right before the application to you.time_taken). + // Leaving skill out of the speed bonus is an important part of + // keeping this spell from becoming a "better than actual melee" + // spell... although, it's fine if that's the case for early Warpers, + // Fighter types, and such that pick up this trivial first level spell, + // shouldn't be using it instead of melee (but rather as an accessory + // long range plinker). Therefore, we tone things down to try and + // guarantee that the spell is never begins to approach real combat + // (although the magic resistance check might end up with a higher + // hit rate than attacking against EV for high level Warpers). -- bwr + if (speed < 10) + speed = 10; + + you.time_taken *= speed; + you.time_taken /= 10; + + // Apply strength only to damage (since we're only interested in + // force here, not finesse... the dex/to-hit part of combat is + // instead handled via magical ability). This part could probably + // just be removed, as it's unlikely to make any real difference... + // if it is, the Warper stats in newgame.cc should be changed back + // to the standard 6 int-4 dex of spellcasters. -- bwr + int dammod = 78; + const int dam_stat_val = you.strength; + + if (dam_stat_val > 11) + dammod += (random2(dam_stat_val - 11) * 2); + else if (dam_stat_val < 9) + dammod -= (random2(9 - dam_stat_val) * 3); + + damage *= dammod; + damage /= 78; + + struct monsters *monster = &menv[ mgrd[targ.tx][targ.ty] ]; + + // apply monster's AC + if (monster->armour_class > 0) + damage -= random2( 1 + monster->armour_class ); + +#if 0 + // Removing damage limiter since it's categorized at level 4 right now. + + // Force transmitted is limited by skill... + const int limit = (you.skills[SK_TRANSLOCATIONS] + 1) / 2 + 3; + if (damage > limit) + damage = limit; +#endif + + // Roll the damage... + damage = 1 + random2( damage ); + + // Monster can magically save vs attack (this could be replaced or + // augmented with an EV check). + if (check_mons_resist_magic( monster, pow * 2 )) + { + simple_monster_message( monster, " resists." ); + return; + } + + // Inflict the damage + hurt_monster( monster, damage ); + if (monster->hit_points < 1) + monster_die( monster, KILL_YOU, 0 ); + else + print_wounds( monster ); + + return; +} // end cast_far_strike() + +void cast_apportation(int pow) +{ + struct dist beam; + + mpr("Pull items from where?"); + + direction( beam, DIR_TARGET ); + + if (!beam.isValid) + { + canned_msg(MSG_SPELL_FIZZLES); + return; + } + + // it's already here! + if (beam.isMe) + { + mpr( "That's just silly." ); + return; + } + + // Protect the player from destroying the item + const int grid = grd[ you.x_pos ][ you.y_pos ]; + + if (grid == DNGN_LAVA || grid == DNGN_DEEP_WATER) + { + mpr( "That would be silly while over this terrain!" ); + return; + } + + // If this is ever changed to allow moving objects that can't + // be seen, it should at least only allow moving from squares + // that have been phyisically (and maybe magically) seen and + // should probably have a range check as well. In these cases + // the spell should probably be upped to at least two, or three + // if magic mapped squares are allowed. Right now it's okay + // at one... it has a few uses, but you still have to get line + // of sight to the object first so it will only help a little + // with snatching runes or the orb (although it can be quite + // useful for getting items out of statue rooms or the abyss). -- bwr + if (!see_grid( beam.tx, beam.ty )) + { + mpr( "You cannot see there!" ); + return; + } + + // Let's look at the top item in that square... + const int item = igrd[ beam.tx ][ beam.ty ]; + if (item == NON_ITEM) + { + const int mon = mgrd[ beam.tx ][ beam.ty ]; + + if (mon == NON_MONSTER) + mpr( "There are no items there." ); + else if (mons_is_mimic( menv[ mon ].type )) + { + snprintf( info, INFO_SIZE, "%s twitches.", + ptr_monam( &(menv[ mon ]), DESC_CAP_THE ) ); + mpr( info ); + } + else + mpr( "This spell does not work on creatures." ); + + return; + } + + // mass of one unit + const int unit_mass = mass_item( mitm[ item ] ); + // assume we can pull everything + int max_units = mitm[ item ].quantity; + + // item has mass: might not move all of them + if (unit_mass > 0) + { + const int max_mass = pow * 30 + random2( pow * 20 ); + + // most units our power level will allow + max_units = max_mass / unit_mass; + } + + if (max_units <= 0) + { + mpr( "The mass is resisting your pull." ); + return; + } + + // Failure should never really happen after all the above checking, + // but we'll handle it anyways... + if (move_top_item( beam.tx, beam.ty, you.x_pos, you.y_pos )) + { + if (max_units < mitm[ item ].quantity) + { + mitm[ item ].quantity = max_units; + mpr( "You feel that some mass got lost in the cosmic void." ); + } + else + { + mpr( "Yoink!" ); + snprintf( info, INFO_SIZE, "You pull the item%s to yourself.", + (mitm[ item ].quantity > 1) ? "s" : "" ); + mpr( info ); + } + } + else + mpr( "The spell fails." ); +} + +void cast_sandblast(int pow) +{ + bool big = true; + struct dist spd; + struct bolt beam; + + // this type of power manipulation should be done with the others, + // currently over in it_use2.cc (ack) -- bwr + // int hurt = 2 + random2(5) + random2(4) + random2(pow) / 20; + + big = false; + + if (you.equip[EQ_WEAPON] != -1) + { + int wep = you.equip[EQ_WEAPON]; + if (you.inv[wep].base_type == OBJ_MISSILES + && (you.inv[wep].sub_type == MI_STONE || you.inv[wep].sub_type == MI_LARGE_ROCK)) + big = true; + } + + if (spell_direction(spd, beam) == -1) + return; + + if (spd.isMe) + { + canned_msg(MSG_UNTHINKING_ACT); + return; + } + + if (big) + { + dec_inv_item_quantity( you.equip[EQ_WEAPON], 1 ); + zapping(ZAP_SANDBLAST, pow, beam); + } + else + { + zapping(ZAP_SMALL_SANDBLAST, pow, beam); + } +} // end cast_sandblast() + +static bool mons_can_host_shuggoth(int type) //jmf: simplified +{ + if (mons_holiness(type) != MH_NATURAL) + return false; + if (mons_flag(type, M_WARM_BLOOD)) + return true; + + return false; +} + +void cast_shuggoth_seed(int powc) +{ + struct dist beam; + int i; + + mpr("Sow seed in whom?", MSGCH_PROMPT); + + direction( beam, DIR_TARGET, TARG_ENEMY ); + + if (!beam.isValid) + { + mpr("You feel a distant frustration."); + return; + } + + if (beam.isMe) + { + if (!you.is_undead) + { + you.duration[DUR_INFECTED_SHUGGOTH_SEED] = 10; + mpr("A deathly dread twitches in your chest."); + } + else + mpr("You feel a distant frustration."); + } + + i = mgrd[beam.tx][beam.ty]; + + if (i == NON_MONSTER) + { + mpr("You feel a distant frustration."); + return; + } + + if (mons_can_host_shuggoth(menv[i].type)) + { + if (random2(powc) > 100) + mons_add_ench(&menv[i], ENCH_YOUR_SHUGGOTH_III); + else + mons_add_ench(&menv[i], ENCH_YOUR_SHUGGOTH_IV); + + simple_monster_message(&menv[i], " twitches."); + } + + return; +} + +void cast_condensation_shield(int pow) +{ + if (you.equip[EQ_SHIELD] != -1 || you.fire_shield) + canned_msg(MSG_SPELL_FIZZLES); + else + { + if (you.duration[DUR_CONDENSATION_SHIELD] > 0) + you.duration[DUR_CONDENSATION_SHIELD] += 5 + roll_dice(2, 3); + else + { + mpr("A crackling disc of dense vapour forms in the air!"); + you.redraw_armour_class = 1; + + you.duration[DUR_CONDENSATION_SHIELD] = 10 + roll_dice(2, pow / 5); + } + + if (you.duration[DUR_CONDENSATION_SHIELD] > 30) + you.duration[DUR_CONDENSATION_SHIELD] = 30; + } + + return; +} // end cast_condensation_shield() + +static int quadrant_blink(int x, int y, int pow, int garbage) +{ + UNUSED( garbage ); + + if (x == you.x_pos && y == you.y_pos) + return (0); + + if (you.level_type == LEVEL_ABYSS) + { + abyss_teleport( false ); + you.pet_target = MHITNOT; + return (1); + } + + if (pow > 100) + pow = 100; + + // setup: Brent's new algorithm + // we are interested in two things: distance of a test point from + // the ideal 'line', and the distance of a test point from two + // actual points, one in the 'correct' direction and one in the + // 'incorrect' direction. + + // scale distance by 10 for more interesting numbers. + int l,m; // for line equation lx + my = 0 + l = (x - you.x_pos); + m = (you.y_pos - y); + + int tx, ty; // test x,y + int rx, ry; // x,y relative to you. + int sx, sy; // test point in the correct direction + int bx = x; // best x + int by = y; // best y + + int best_dist = 10000; + + sx = l; + sy = -m; + + // for each point (a,b), distance from the line is | la + mb | + + for(int tries = pow * pow / 500 + 1; tries > 0; tries--) + { + if (!random_near_space(you.x_pos, you.y_pos, tx, ty)) + return 0; + + rx = tx - you.x_pos; + ry = ty - you.y_pos; + + int dist = l * rx + m * ry; + dist *= 10 * dist; // square and multiply by 10 + + // check distance to test points + int dist1 = distance(rx, ry, sx, sy) * 10; + int dist2 = distance(rx, ry, -sx, -sy) * 10; + + // 'good' points will always be closer to test point 1 + if (dist2 < dist1) + dist += 80; // make the point less attractive + + if (dist < best_dist) + { + best_dist = dist; + bx = tx; + by = ty; + } + } + + you.x_pos = bx; + you.y_pos = by; + + return (1); +} + +void cast_semi_controlled_blink(int pow) +{ + apply_one_neighbouring_square(quadrant_blink, pow); + return; +} + +void cast_stoneskin(int pow) +{ + if (you.is_undead) + { + mpr("This spell does not affect your undead flesh."); + return; + } + + if (you.attribute[ATTR_TRANSFORMATION] != TRAN_NONE + && you.attribute[ATTR_TRANSFORMATION] != TRAN_STATUE + && you.attribute[ATTR_TRANSFORMATION] != TRAN_BLADE_HANDS) + { + mpr("This spell does not affect your current form."); + return; + } + + if (you.duration[DUR_STONEMAIL] || you.duration[DUR_ICY_ARMOUR]) + { + mpr("This spell conflicts with another spell still in effect."); + return; + } + + if (you.duration[DUR_STONESKIN]) + mpr( "Your skin feels harder." ); + else + { + if (you.attribute[ATTR_TRANSFORMATION] == TRAN_STATUE) + mpr( "Your stone body feels more resilient." ); + else + mpr( "Your skin hardens." ); + + you.redraw_armour_class = 1; + } + + you.duration[DUR_STONESKIN] += 10 + random2(pow) + random2(pow); + + if (you.duration[DUR_STONESKIN] > 50) + you.duration[DUR_STONESKIN] = 50; +} |