/* * File: spells4.cc * Summary: new spells, focusing on Transmutations, Divinations, * and other neglected areas of Crawl magic ;^) * Written by: Copyleft Josh Fishman 1999-2000, All Rights Preserved */ #include "AppHdr.h" #include #include #include #include #include "externs.h" #include "abyss.h" #include "artefact.h" #include "beam.h" #include "cloud.h" #include "coordit.h" #include "debug.h" #include "delay.h" #include "directn.h" #include "dungeon.h" #include "effects.h" #include "env.h" #include "invent.h" #include "it_use2.h" #include "item_use.h" #include "itemprop.h" #include "items.h" #include "los.h" #include "makeitem.h" #include "message.h" #include "misc.h" #include "mon-behv.h" #include "mon-place.h" #include "coord.h" #include "mon-stuff.h" #include "mon-util.h" #include "ouch.h" #include "player.h" #include "quiver.h" #include "religion.h" #include "skills.h" #include "spells1.h" #include "spells4.h" #include "spl-mis.h" #include "spl-util.h" #include "stuff.h" #include "areas.h" #include "teleport.h" #include "terrain.h" #include "transform.h" #include "traps.h" #include "view.h" #include "shout.h" #include "viewchar.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... // Just to avoid typing this over and over. // Returns true if monster died. -- bwr static bool _player_hurt_monster(monsters& m, int damage, beam_type flavour = BEAM_MISSILE) { if (damage > 0) { m.hurt(&you, damage, flavour, false); if (m.alive()) { print_wounds(&m); behaviour_event(&m, ME_WHACK, MHITYOU); } else { monster_die(&m, KILL_YOU, NON_MONSTER); return (true); } } return (false); } // Here begin the actual spells: static int _shatter_monsters(coord_def where, int pow, int, actor *) { dice_def dam_dice( 0, 5 + pow / 3 ); // number of dice set below monsters *monster = monster_at(where); if (monster == NULL) 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 (monster->type) { case MONS_ICE_BEAST: // 3/2 damage case MONS_SIMULACRUM_SMALL: case MONS_SIMULACRUM_LARGE: case MONS_SILVER_STATUE: 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_ORANGE_STATUE: case MONS_STATUE: 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(monster)) dam_dice.num = 1; else dam_dice.num = 3; break; } int damage = dam_dice.roll() - random2(monster->ac); if (damage > 0) _player_hurt_monster(*monster, damage); else damage = 0; return (damage); } static int _shatter_items(coord_def where, int pow, int, actor *) { UNUSED( pow ); int broke_stuff = 0; for ( stack_iterator si(where); si; ++si ) { if (si->base_type == OBJ_POTIONS && !one_chance_in(10)) { broke_stuff++; destroy_item(si->index()); } } if (broke_stuff) { if (player_can_hear(where)) mpr("You hear glass break.", MSGCH_SOUND); return 1; } return 0; } static int _shatter_walls(coord_def where, int pow, int, actor *) { int chance = 0; // if not in-bounds then we can't really shatter it -- bwr if (!in_bounds(where)) return 0; if (env.markers.property_at(where, MAT_ANY, "veto_shatter") == "veto") return 0; const dungeon_feature_type grid = grd(where); switch (grid) { case DNGN_SECRET_DOOR: if (you.see_cell(where)) mpr("A secret door shatters!"); chance = 100; break; case DNGN_CLOSED_DOOR: case DNGN_DETECTED_SECRET_DOOR: case DNGN_OPEN_DOOR: if (you.see_cell(where)) mpr("A door shatters!"); chance = 100; break; case DNGN_METAL_WALL: chance = pow / 10; break; case DNGN_ORCISH_IDOL: case DNGN_GRANITE_STATUE: chance = 50; break; case DNGN_CLEAR_STONE_WALL: case DNGN_STONE_WALL: chance = pow / 6; break; case DNGN_CLEAR_ROCK_WALL: case DNGN_ROCK_WALL: chance = pow / 4; break; case DNGN_GREEN_CRYSTAL_WALL: chance = 50; break; default: break; } if (x_chance_in_y(chance, 100)) { noisy(30, where); grd(where) = DNGN_FLOOR; if (grid == DNGN_ORCISH_IDOL) did_god_conduct(DID_DESTROY_ORCISH_IDOL, 8); return (1); } return (0); } void cast_shatter(int pow) { int damage = 0; const bool silence = silenced(you.pos()); if (silence) mpr("The dungeon shakes!"); else { noisy(30, you.pos()); mpr("The dungeon rumbles!", MSGCH_SOUND); } switch (you.attribute[ATTR_TRANSFORMATION]) { case TRAN_NONE: case TRAN_SPIDER: case TRAN_LICH: case TRAN_DRAGON: case TRAN_BAT: 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 > 0) ouch(damage, NON_MONSTER, KILLED_BY_TARGETTING); int rad = 3 + (you.skills[SK_EARTH_MAGIC] / 5); apply_area_within_radius(_shatter_items, you.pos(), pow, rad, 0); apply_area_within_radius(_shatter_monsters, you.pos(), pow, rad, 0); int dest = apply_area_within_radius(_shatter_walls, you.pos(), pow, rad, 0); if (dest && !silence) mpr("Ka-crash!", MSGCH_SOUND); } // Cast_phase_shift: raises evasion (by 8 currently) via Translocations. void cast_phase_shift(int pow) { if (!you.duration[DUR_PHASE_SHIFT]) mpr("You feel the strange sensation of being on two planes at once."); else mpr("Your feel the material plane grow further away."); you.increase_duration(DUR_PHASE_SHIFT, 5 + random2(pow), 30); you.redraw_evasion = true; } void cast_see_invisible(int pow) { if (you.can_see_invisible()) mpr("Nothing seems to happen."); else { mpr("Your vision seems to sharpen."); // We might have to turn autopickup back on again. // TODO: Once the spell times out we might want to check all monsters // in LOS for invisibility and turn autopickup off again, if // needed. autotoggle_autopickup(false); } // No message if you already are under the spell. you.increase_duration(DUR_SEE_INVISIBLE, 10 + random2(2 + pow/2), 100); } // 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 (radius_iterator ri(&you.get_los()); ri; ++ri ) if (grd(*ri) == DNGN_SECRET_DOOR && random2(pow) > random2(15)) { reveal_secret_door(*ri); found++; } if (found) redraw_screen(); mprf("You detect %s", (found > 0) ? "secret doors!" : "nothing."); } static int _sleep_monsters(coord_def where, int pow, int, actor *) { monsters *monster = monster_at(where); if (!monster) return (0); if (!monster->can_hibernate(true)) return (0); if (monster->check_res_magic(pow)) return (0); const int res = monster->res_cold(); if (res > 0 && one_chance_in(std::max(4 - res, 1))) return (0); if (monster->has_ench(ENCH_SLEEP_WARY) && !one_chance_in(3)) return (0); monster->hibernate(); monster->expose_to_element(BEAM_COLD, 2); return (1); } void cast_mass_sleep(int pow) { apply_area_visible(_sleep_monsters, pow); } // This is a hack until we set an is_beast flag in the monster data // (which we might never do, this is sort of minor.) // It's a list of monster types which can be affected by beast taming. static bool _is_domesticated_animal(int type) { const monster_type types[] = { MONS_GIANT_BAT, MONS_HOUND, MONS_JACKAL, MONS_RAT, MONS_YAK, MONS_WYVERN, MONS_HIPPOGRIFF, MONS_GRIFFON, MONS_DEATH_YAK, MONS_WAR_DOG, MONS_GREY_RAT, MONS_GREEN_RAT, MONS_ORANGE_RAT, MONS_SHEEP, MONS_HOG, MONS_GIANT_FROG, MONS_GIANT_TOAD, MONS_SPINY_FROG, MONS_BLINK_FROG, MONS_WOLF, MONS_WARG, MONS_BEAR, MONS_GRIZZLY_BEAR, MONS_POLAR_BEAR, MONS_BLACK_BEAR }; for (unsigned int i = 0; i < ARRAYSZ(types); ++i) if (types[i] == type) return (true); return (false); } static int _tame_beast_monsters(coord_def where, int pow, int, actor *) { monsters *monster = monster_at(where); if (monster == NULL) return 0; if (!_is_domesticated_animal(monster->type) || monster->friendly() || player_will_anger_monster(monster)) { return 0; } // 50% bonus for dogs if (monster->type == MONS_HOUND || monster->type == MONS_WAR_DOG ) pow += (pow / 2); if (you.species == SP_HILL_ORC && monster->type == MONS_WARG) pow += (pow / 2); if (monster->check_res_magic(pow)) return 0; simple_monster_message(monster, " is tamed!"); if (random2(100) < random2(pow / 10)) monster->attitude = ATT_FRIENDLY; // permanent else monster->add_ench(ENCH_CHARM); // temporary return 1; } void cast_tame_beasts(int pow) { apply_area_visible(_tame_beast_monsters, pow); } static int _ignite_poison_objects(coord_def where, int pow, int, actor *) { UNUSED( pow ); int strength = 0; for ( stack_iterator si(where); si; ++si ) { if (si->base_type == OBJ_POTIONS) { switch (si->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(si->index()); default: break; } } // FIXME: implement burning poisoned ammo } if (strength > 0) { place_cloud(CLOUD_FIRE, where, strength + roll_dice(3, strength / 4), KC_YOU); } return (strength); } static int _ignite_poison_clouds( coord_def where, int pow, int, actor *) { UNUSED( pow ); bool did_anything = false; const int i = env.cgrid(where); if (i != EMPTY_CLOUD) { cloud_struct& cloud = env.cloud[i]; if (cloud.type == CLOUD_STINK) { did_anything = true; cloud.type = CLOUD_FIRE; cloud.decay /= 2; if (cloud.decay < 1) cloud.decay = 1; } else if (cloud.type == CLOUD_POISON) { did_anything = true; cloud.type = CLOUD_FIRE; } } return did_anything; } static int _ignite_poison_monsters(coord_def where, int pow, int, actor *) { 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. monsters *mon = monster_at(where); if (mon == NULL) return (0); // Monsters which have poison corpses or poisonous attacks. if (mons_is_poisoner(mon)) dam_dice.num = 3; // Monsters which are poisoned: int strength = 0; // First check for player poison. mon_enchant ench = mon->get_ench(ENCH_POISON); if (ench.ench != ENCH_NONE) strength += ench.degree; // 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 = dam_dice.roll(); if (damage > 0) { damage = mons_adjust_flavoured(mon, beam, damage); simple_monster_message(mon, " seems to burn from within!"); dprf("Dice: %dd%d; Damage: %d", dam_dice.num, dam_dice.size, damage); if (!_player_hurt_monster(*mon, damage)) { // Monster survived, remove any poison. mon->del_ench(ENCH_POISON); behaviour_event(mon, ME_ALERT); } return (1); } return (0); } void cast_ignite_poison(int pow) { flash_view(RED); // Poison branding becomes fire branding. if (you.weapon() && you.duration[DUR_WEAPON_BRAND] && get_weapon_brand(*you.weapon()) == SPWPN_VENOM) { if (set_item_ego_type(*you.weapon(), OBJ_WEAPONS, SPWPN_FLAMING)) { mprf("%s bursts into flame!", you.weapon()->name(DESC_CAP_YOUR).c_str()); you.wield_change = true; int increase = 1 + you.duration[DUR_WEAPON_BRAND] /(2 * BASELINE_DELAY); you.increase_duration(DUR_WEAPON_BRAND, increase, 80); } } int totalstrength = 0; int ammo_burnt = 0; int potions_burnt = 0; bool was_wielding = false; for (int i = 0; i < ENDOFPACK; ++i) { item_def& item = you.inv[i]; if (!item.is_valid()) continue; int strength = 0; if (item.base_type == OBJ_MISSILES && item.special == SPMSL_POISONED) { // Burn poison ammo. strength = item.quantity; ammo_burnt += item.quantity; } else if (item.base_type == OBJ_POTIONS) { // Burn poisonous potions. switch (item.sub_type) { case POT_STRONG_POISON: strength = 20 * item.quantity; break; case POT_DEGENERATION: case POT_POISON: strength = 10 * item.quantity; break; default: break; } if (strength) potions_burnt += item.quantity; } if (strength) { if (i == you.equip[EQ_WEAPON]) { unwield_item(); was_wielding = true; } item_was_destroyed(item); destroy_item(item); } totalstrength += strength; } if (ammo_burnt) mpr("Some ammunition you are carrying burns!"); if (potions_burnt) { mprf("%s potion%s you are carrying explode%s!", potions_burnt > 1 ? "Some" : "A", potions_burnt > 1 ? "s" : "", potions_burnt > 1 ? "" : "s"); } if (was_wielding) canned_msg(MSG_EMPTY_HANDED); if (totalstrength) { place_cloud( CLOUD_FIRE, you.pos(), random2(totalstrength / 4 + 1) + random2(totalstrength / 4 + 1) + random2(totalstrength / 4 + 1) + random2(totalstrength / 4 + 1) + 1, KC_YOU); } int damage = 0; // Player is poisonous. if (player_mutation_level(MUT_SPIT_POISON) || player_mutation_level(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.duration[DUR_POISONING], 6); if (damage) { const int resist = player_res_fire(); if (resist > 0) { mpr("You feel like your blood is boiling!"); damage /= 3; } else if (resist < 0) { mpr("The poison in your system burns terribly!"); damage *= 3; } else { mpr("The poison in your system burns!"); } ouch(damage, NON_MONSTER, KILLED_BY_TARGETTING); if (you.duration[DUR_POISONING] > 0) { mpr( "You feel that the poison has left your system." ); you.duration[DUR_POISONING] = 0; } } apply_area_visible(_ignite_poison_clouds, pow); apply_area_visible(_ignite_poison_objects, pow); apply_area_visible(_ignite_poison_monsters, pow); #ifndef USE_TILE delay(100); // show a brief flash #endif flash_view(0); } void cast_silence(int pow) { if (!you.attribute[ATTR_WAS_SILENCED]) mpr("A profound silence engulfs you."); you.attribute[ATTR_WAS_SILENCED] = 1; you.increase_duration(DUR_SILENCE, 10 + random2avg(pow,2), 100); if (you.beheld()) you.update_beholders(); } static int _discharge_monsters(coord_def where, int pow, int, actor *) { monsters *monster = monster_at(where); int damage = 0; bolt beam; beam.flavour = BEAM_ELECTRICITY; // used for mons_adjust_flavoured if (where == you.pos()) { mpr("You are struck by lightning."); damage = 3 + random2(5 + pow / 10); damage = check_your_resists( damage, BEAM_ELECTRICITY ); if (you.airborne()) damage /= 2; ouch(damage, NON_MONSTER, KILLED_BY_WILD_MAGIC); } else if (monster == NULL) return (0); else if (monster->res_elec() > 0 || mons_flies(monster)) return (0); else { damage = 3 + random2(5 + pow/10); damage = mons_adjust_flavoured(monster, beam, damage ); if (damage) { mprf("%s is struck by lightning.", monster->name(DESC_CAP_THE).c_str()); _player_hurt_monster(*monster, 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, where, 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); } void cast_discharge(int pow) { int num_targs = 1 + random2(1 + pow / 25); int dam; dam = apply_random_around_square(_discharge_monsters, you.pos(), true, pow, num_targs); dprf("Arcs: %d Damage: %d", num_targs, dam); if (dam == 0) { if (coinflip()) mpr("The air around you crackles with electrical energy."); else { const bool plural = coinflip(); mprf("%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")); } } } // 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(coord_def where, int pow, int, actor *) { monsters *mon = monster_at(where); if (!mon) return (0); if (mons_genus(mon->type) == MONS_BLINK_FROG) { simple_monster_message(mon, " resists."); return (1); } else if (mon->check_res_magic(pow)) { // XXX: Note that this might affect magic-immunes! if (coinflip()) { simple_monster_message(mon, " partially resists."); monster_blink(mon); } else simple_monster_message(mon, " resists."); return (1); } else { monster_teleport(mon, true); return (1); } return (0); } void cast_dispersal(int pow) { if (apply_area_around_square(disperse_monsters, you.pos(), pow) == 0) mpr("The air shimmers briefly around you."); } int make_a_normal_cloud(coord_def where, int pow, int spread_rate, cloud_type ctype, kill_category whose, killer_type killer, int colour, std::string name, std::string tile) { if (killer == KILL_NONE) killer = cloud_struct::whose_to_killer(whose); place_cloud( ctype, where, (3 + random2(pow / 4) + random2(pow / 4) + random2(pow / 4)), whose, killer, spread_rate, colour, name, tile ); return 1; } bool _feat_is_passwallable(dungeon_feature_type feat) { // Irony: you can passwall through a secret door but not a door. // Worked stone walls are out, they're not diggable and // are used for impassable walls... switch (feat) { case DNGN_ROCK_WALL: case DNGN_CLEAR_ROCK_WALL: case DNGN_SECRET_DOOR: return (true); default: return (false); } } bool cast_passwall(const coord_def& delta, int pow) { int shallow = 1 + (you.skills[SK_EARTH_MAGIC] / 8); int range = shallow + random2(pow) / 25; int maxrange = shallow + pow / 25; coord_def dest; for (dest = you.pos() + delta; in_bounds(dest) && _feat_is_passwallable(grd(dest)); dest += delta) ; int walls = (dest - you.pos()).rdist() - 1; if (walls == 0) { mpr("That's not a passable wall."); return (false); } // Below here, failing to cast yields information to the // player, so we don't make the spell abort (return true). if (!in_bounds(dest)) mpr("You sense an overwhelming volume of rock."); else if (feat_is_solid(grd(dest))) mpr("Something is blocking your path through the rock."); else if (walls > maxrange) mpr("This rock feels extremely deep."); else if (walls > range) mpr("You fail to penetrate the rock."); else { // Passwall delay is reduced, and the delay cannot be interrupted. start_delay(DELAY_PASSWALL, 1 + walls, dest.x, dest.y); } return (true); } static int _intoxicate_monsters(coord_def where, int pow, int, actor *) { UNUSED( pow ); monsters *monster = monster_at(where); if (monster == NULL || mons_intel(monster) < I_NORMAL || monster->holiness() != MH_NATURAL || monster->res_poison() > 0) { return 0; } monster->add_ench(mon_enchant(ENCH_CONFUSION, 0, KC_YOU)); return 1; } 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), false, "casting intoxication")) { mpr("Your head spins!"); } apply_area_visible(_intoxicate_monsters, pow); } bool backlight_monsters(coord_def where, int pow, int garbage) { UNUSED(pow); UNUSED(garbage); monsters *monster = monster_at(where); if (monster == NULL) return (false); // Already glowing. if (mons_class_flag(monster->type, M_GLOWS)) return (false); mon_enchant bklt = monster->get_ench(ENCH_CORONA); const int lvl = bklt.degree; // This enchantment overrides invisibility (neat). if (monster->has_ench(ENCH_INVIS)) { if (!monster->has_ench(ENCH_CORONA)) { monster->add_ench( mon_enchant(ENCH_CORONA, 1, KC_OTHER, random_range(30, 50))); simple_monster_message(monster, " is lined in light."); } return (true); } monster->add_ench(mon_enchant(ENCH_CORONA, 1)); if (lvl == 0) simple_monster_message(monster, " is outlined in light."); else if (lvl == 4) simple_monster_message(monster, " glows brighter for a moment."); else simple_monster_message(monster, " glows brighter."); return (true); } bool cast_evaporate(int pow, bolt& beem, int pot_idx) { ASSERT(you.inv[pot_idx].base_type == OBJ_POTIONS); item_def& potion = you.inv[pot_idx]; beem.name = "potion"; beem.colour = potion.colour; beem.type = dchar_glyph(DCHAR_FIRED_FLASK); beem.beam_source = MHITYOU; beem.thrower = KILL_YOU_MISSILE; beem.is_beam = false; beem.aux_source.clear(); 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.is_explosion = true; beem.range = 8; beem.flavour = BEAM_POTION_STINKING_CLOUD; beam_type tracer_flavour = BEAM_MMISSILE; switch (potion.sub_type) { case POT_STRONG_POISON: beem.flavour = BEAM_POTION_POISON; tracer_flavour = BEAM_POISON; beem.ench_power *= 2; break; case POT_DEGENERATION: beem.effect_known = false; beem.flavour = (coinflip() ? BEAM_POTION_POISON : BEAM_POTION_MIASMA); tracer_flavour = BEAM_MIASMA; beem.ench_power *= 2; break; case POT_POISON: beem.flavour = BEAM_POTION_POISON; tracer_flavour = BEAM_POISON; break; case POT_DECAY: beem.flavour = BEAM_POTION_MIASMA; tracer_flavour = BEAM_MIASMA; beem.ench_power *= 2; break; case POT_PARALYSIS: beem.ench_power *= 2; // fall through case POT_CONFUSION: case POT_SLOWING: tracer_flavour = beem.flavour = BEAM_POTION_STINKING_CLOUD; break; case POT_WATER: case POT_PORRIDGE: tracer_flavour = beem.flavour = BEAM_POTION_STEAM; break; case POT_BLOOD: case POT_BLOOD_COAGULATED: if (one_chance_in(3)) break; // stinking cloud // deliberate fall through case POT_BERSERK_RAGE: beem.effect_known = false; beem.flavour = (coinflip() ? BEAM_POTION_FIRE : BEAM_POTION_STEAM); if (potion.sub_type == POT_BERSERK_RAGE) tracer_flavour = BEAM_FIRE; else tracer_flavour = BEAM_RANDOM; break; case POT_MUTATION: // Maybe we'll get a mutagenic cloud. if (coinflip()) { beem.effect_known = true; tracer_flavour = beem.flavour = BEAM_POTION_MUTAGENIC; break; } // if not, deliberate fall through for something random case POT_GAIN_STRENGTH: case POT_GAIN_DEXTERITY: case POT_GAIN_INTELLIGENCE: case POT_EXPERIENCE: case POT_MAGIC: beem.effect_known = false; 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; } tracer_flavour = BEAM_RANDOM; break; default: beem.effect_known = false; 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; default: beem.flavour = BEAM_POTION_STEAM; break; } tracer_flavour = BEAM_RANDOM; break; } // Fire tracer. FIXME: use player_tracer() here! beem.source = you.pos(); beem.can_see_invis = you.can_see_invisible(); beem.smart_monster = true; beem.attitude = ATT_FRIENDLY; beem.beam_cancelled = false; beem.is_tracer = true; beem.friend_info.reset(); beam_type real_flavour = beem.flavour; beem.flavour = tracer_flavour; beem.fire(); if (beem.beam_cancelled) { // We don't want to fire through friendlies or at ourselves. canned_msg(MSG_OK); return (false); } if (coinflip()) exercise(SK_THROWING, 1); // Really fire. beem.flavour = real_flavour; beem.is_tracer = false; beem.fire(); // Use up a potion. if (is_blood_potion(potion)) remove_oldest_blood_potion(potion); dec_inv_item_quantity(pot_idx, 1); return (true); } // 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 /*pow*/) { int corpse = -1; // Search items at the player's location for corpses. for (stack_iterator si(you.pos(), true); si; ++si) { if (si->base_type == OBJ_CORPSES && si->sub_type == CORPSE_BODY) { snprintf(info, INFO_SIZE, "Distill a potion from %s?", si->name(DESC_NOCAP_THE).c_str()); if (yesno(info, true, 0, false)) { corpse = si->index(); break; } } } if (corpse == -1) { canned_msg(MSG_SPELL_FIZZLES); return; } potion_type pot_type = POT_WATER; switch (mons_corpse_effect(mitm[corpse].plus)) { case CE_CLEAN: pot_type = POT_WATER; break; case CE_CONTAMINATED: pot_type = (mons_weight(mitm[corpse].plus) >= 900) ? POT_DEGENERATION : POT_CONFUSION; break; case CE_POISONOUS: pot_type = POT_POISON; break; case CE_MUTAGEN_RANDOM: case CE_MUTAGEN_GOOD: // unused case CE_RANDOM: // unused pot_type = POT_MUTATION; break; case CE_MUTAGEN_BAD: // unused case CE_ROTTEN: // actually this only occurs via mangling case CE_HCL: // necrophage pot_type = POT_DECAY; break; case CE_NOCORPSE: // shouldn't occur default: break; } switch (mitm[corpse].plus) { case MONS_RED_WASP: // paralysis attack pot_type = POT_PARALYSIS; break; case MONS_YELLOW_WASP: // slowing attack pot_type = POT_SLOWING; break; default: break; } struct monsterentry* smc = get_monster_data(mitm[corpse].plus); for (int nattk = 0; nattk < 4; ++nattk) { if (smc->attack[nattk].flavour == AF_POISON_MEDIUM || smc->attack[nattk].flavour == AF_POISON_STRONG || smc->attack[nattk].flavour == AF_POISON_STR) { pot_type = POT_STRONG_POISON; } } const bool was_orc = (mons_species(mitm[corpse].plus) == MONS_ORC); // We borrow the corpse's object to make our potion. mitm[corpse].base_type = OBJ_POTIONS; mitm[corpse].sub_type = pot_type; mitm[corpse].quantity = 1; mitm[corpse].plus = 0; mitm[corpse].plus2 = 0; mitm[corpse].flags = 0; mitm[corpse].inscription.clear(); item_colour(mitm[corpse]); // sets special as well set_ident_type(mitm[corpse], ID_KNOWN_TYPE); mprf("You extract %s from the corpse.", mitm[corpse].name(DESC_NOCAP_A).c_str()); // 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!"); if (was_orc) did_god_conduct(DID_DESECRATE_ORCISH_REMAINS, 2); } bool cast_fragmentation(int pow, const dist& spd) { int debris = 0; bool explode = false; bool hole = true; const char *what = NULL; if (!exists_ray(you.pos(), spd.target)) { mpr("There's a wall in the way!"); return (false); } //FIXME: If (player typed '>' to attack floor) goto do_terrain; bolt beam; beam.flavour = BEAM_FRAG; beam.type = dchar_glyph(DCHAR_FIRED_BURST); beam.beam_source = MHITYOU; beam.thrower = KILL_YOU; beam.ex_size = 1; beam.source = you.pos(); beam.hit = AUTOMATIC_HIT; beam.set_target(spd); beam.aux_source.clear(); // Number of dice vary... 3 is easy/common, but it can get as high as 6. beam.damage = dice_def(0, 5 + pow / 10); const dungeon_feature_type grid = grd(spd.target); if (monsters *mon = monster_at(spd.target)) { // Save the monster's name in case it isn't available later. const std::string name_cap_the = mon->name(DESC_CAP_THE); switch (mon->type) { case MONS_WOOD_GOLEM: simple_monster_message(mon, " shudders violently!"); // We use beam.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 explode = false; beam.damage.num = 2; _player_hurt_monster(*mon, beam.damage.roll(), BEAM_DISINTEGRATION); break; case MONS_IRON_GOLEM: case MONS_METAL_GARGOYLE: explode = true; beam.name = "blast of metal fragments"; beam.colour = CYAN; beam.damage.num = 4; if (_player_hurt_monster(*mon, beam.damage.roll(), BEAM_DISINTEGRATION)) beam.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: case MONS_STATUE: explode = true; beam.ex_size = 2; beam.name = "blast of rock fragments"; beam.colour = BROWN; beam.damage.num = 3; if (_player_hurt_monster(*mon, beam.damage.roll(), BEAM_DISINTEGRATION)) beam.damage.num++; break; case MONS_SILVER_STATUE: case MONS_ORANGE_STATUE: explode = true; beam.ex_size = 2; if (mon->type == MONS_SILVER_STATUE) { beam.name = "blast of silver fragments"; beam.colour = WHITE; beam.damage.num = 3; } else { beam.name = "blast of orange crystal shards"; beam.colour = LIGHTRED; beam.damage.num = 6; } { int statue_damage = beam.damage.roll() * 2; if (pow >= 50 && one_chance_in(10)) statue_damage = mon->hit_points; if (_player_hurt_monster(*mon, statue_damage, BEAM_DISINTEGRATION)) beam.damage.num += 2; } break; case MONS_CRYSTAL_GOLEM: explode = true; beam.ex_size = 2; beam.name = "blast of crystal shards"; beam.colour = WHITE; beam.damage.num = 4; if (_player_hurt_monster(*mon, beam.damage.roll(), BEAM_DISINTEGRATION)) beam.damage.num += 2; break; default: if (mon->is_icy()) // blast of ice { explode = true; beam.name = "icy blast"; beam.colour = WHITE; beam.damage.num = 2; beam.flavour = BEAM_ICE; if (_player_hurt_monster(*mon, beam.damage.roll()), BEAM_DISINTEGRATION); beam.damage.num++; break; } else if (mons_is_skeletal(mon->type) || mon->type == MONS_FLYING_SKULL) // blast of bone { mprf("The %s explodes into sharp fragments of bone!", (mon->type == MONS_FLYING_SKULL) ? "skull" : "skeleton"); explode = true; beam.name = "blast of bone shards"; beam.colour = LIGHTGREY; if (x_chance_in_y(pow / 5, 50)) // potential insta-kill { monster_die(mon, KILL_YOU, NON_MONSTER); beam.damage.num = 4; } else { beam.damage.num = 2; if (_player_hurt_monster(*mon, beam.damage.roll(), BEAM_DISINTEGRATION)) beam.damage.num += 2; } goto all_done; // i.e., no "Foo Explodes!" } else { const bool petrifying = mon->petrifying(); const bool petrified = mon->petrified() && !petrifying; // Petrifying or petrified monsters can be exploded. if (petrifying || petrified) { explode = true; beam.ex_size = petrifying ? 1 : 2; beam.name = "blast of petrified fragments"; beam.colour = mons_class_colour(mon->type); beam.damage.num = petrifying ? 2 : 3; if (_player_hurt_monster(*mon, beam.damage.roll(), BEAM_DISINTEGRATION)) beam.damage.num++; break; } } // Mark that a monster was targetted. beam.damage.num = 1; // Yes, this spell does lousy damage if the monster // isn't susceptible. -- bwr _player_hurt_monster(*mon, roll_dice(1, 5 + pow / 25), BEAM_DISINTEGRATION); goto do_terrain; } mprf("%s shatters!", name_cap_the.c_str()); goto all_done; } for (stack_iterator si(spd.target, true); si; ++si) { if (si->base_type == OBJ_CORPSES) { std::string nm = si->name(DESC_CAP_THE); if (si->sub_type == CORPSE_BODY) { if (!explode_corpse(*si, spd.target)) { mprf("%s seems to be exceptionally well connected.", nm.c_str()); goto all_done; } } mprf("%s explodes!", nm.c_str()); destroy_item(si.link()); // si invalid now! goto all_done; } } if (env.markers.property_at(spd.target, MAT_ANY, "veto_fragmentation") == "veto") { mprf("%s seems to be unnaturally hard.", feature_description(spd.target, false, DESC_CAP_THE, false).c_str()); canned_msg(MSG_SPELL_FIZZLES); return (true); } do_terrain: // FIXME: do nothing in Abyss & Pandemonium? switch (grid) { // // Stone and rock terrain // case DNGN_ROCK_WALL: case DNGN_CLEAR_ROCK_WALL: case DNGN_SECRET_DOOR: beam.colour = env.rock_colour; // fall-through case DNGN_CLEAR_STONE_WALL: case DNGN_STONE_WALL: what = "wall"; if (player_in_branch(BRANCH_HALL_OF_ZOT)) beam.colour = env.rock_colour; // fall-through case DNGN_ORCISH_IDOL: if (what == NULL) what = "stone idol"; if (beam.colour == 0) beam.colour = DARKGREY; // fall-through case DNGN_GRANITE_STATUE: // normal rock -- big explosion if (what == NULL) what = "statue"; explode = true; beam.name = "blast of rock fragments"; beam.damage.num = 3; if (beam.colour == 0) beam.colour = LIGHTGREY; if ((grid == DNGN_ORCISH_IDOL || grid == DNGN_GRANITE_STATUE || pow >= 40 && grid == DNGN_ROCK_WALL && one_chance_in(3) || pow >= 40 && grid == DNGN_CLEAR_ROCK_WALL && one_chance_in(3) || pow >= 60 && grid == DNGN_STONE_WALL && one_chance_in(10) || pow >= 60 && grid == DNGN_CLEAR_STONE_WALL && one_chance_in(10))) { // terrain blew up real good: beam.ex_size = 2; grd(spd.target) = DNGN_FLOOR; debris = DEBRIS_ROCK; } break; // // Metal -- small but nasty explosion // case DNGN_METAL_WALL: what = "metal wall"; beam.colour = CYAN; explode = true; beam.name = "blast of metal fragments"; beam.damage.num = 4; if (pow >= 80 && x_chance_in_y(pow / 5, 500)) { beam.damage.num += 2; grd(spd.target) = DNGN_FLOOR; debris = DEBRIS_METAL; } break; // // Crystal // case DNGN_GREEN_CRYSTAL_WALL: // crystal -- large & nasty explosion what = "crystal wall"; beam.colour = GREEN; explode = true; beam.ex_size = 2; beam.name = "blast of crystal shards"; beam.damage.num = 5; if (grid == DNGN_GREEN_CRYSTAL_WALL && coinflip()) { beam.ex_size = coinflip() ? 3 : 2; grd(spd.target) = DNGN_FLOOR; debris = DEBRIS_CRYSTAL; } break; // // Traps // case DNGN_UNDISCOVERED_TRAP: case DNGN_TRAP_MECHANICAL: { trap_def* ptrap = find_trap(spd.target); if (ptrap && ptrap->category() != 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 beam.name = "blast of fragments"; beam.colour = env.floor_colour; // in order to blend in beam.damage.num = 2; // Exploded traps are nonfunctional, ammo is also ruined -- bwr ptrap->destroy(); break; } // // Stone doors and arches // case DNGN_OPEN_DOOR: case DNGN_CLOSED_DOOR: case DNGN_DETECTED_SECRET_DOOR: // Doors always blow up, stone arches never do (would cause problems). grd(spd.target) = DNGN_FLOOR; // fall-through case DNGN_STONE_ARCH: // Floor -- small explosion. explode = true; hole = false; // to hit monsters standing on doors beam.name = "blast of rock fragments"; beam.colour = LIGHTGREY; beam.damage.num = 2; break; // // Permarock and floor are unaffected -- bwr // case DNGN_PERMAROCK_WALL: case DNGN_CLEAR_PERMAROCK_WALL: case DNGN_FLOOR: explode = false; mprf("%s seems to be unnaturally hard.", (grid == DNGN_FLOOR) ? "The dungeon floor" : "That wall"); break; default: // FIXME: cute message for water? break; } all_done: if (explode && beam.damage.num > 0) { if (what != NULL) mprf("The %s shatters!", what); beam.explode(true, hole); if (grid == DNGN_ORCISH_IDOL) did_god_conduct(DID_DESTROY_ORCISH_IDOL, 8); } else if (beam.damage.num == 0) { // If damage dice are zero, assume that nothing happened at all. canned_msg(MSG_SPELL_FIZZLES); } return (true); } bool cast_portal_projectile(int pow) { dist target; int item = get_ammo_to_shoot(-1, target, true); if (item == -1) return (false); if (cell_is_solid(target.target)) { mpr("You can't shoot at gazebos."); return (false); } // Can't use portal through walls. (That'd be just too cheap!) if (you.trans_wall_blocking( target.target )) { mpr("A translucent wall is in the way."); return (false); } if (!check_warning_inscriptions(you.inv[item], OPER_FIRE)) return (false); bolt beam; throw_it( beam, item, true, random2(pow/4), &target ); return (true); } bool cast_apportation(int pow, const coord_def& where) { if (you.trans_wall_blocking(where)) { mpr("A translucent wall is in the way."); return (false); } // Letting mostly-melee characters spam apport after every Shoals // fight seems like it has too much grinding potential. We could // weaken this for high power. if (grd(where) == DNGN_DEEP_WATER || grd(where) == DNGN_LAVA) { mpr("The density of the terrain blocks your spell."); return (false); } // Let's look at the top item in that square... // And don't allow apporting from shop inventories. const int item_idx = igrd(where); if (item_idx == NON_ITEM || !in_bounds(where)) { // Maybe the player *thought* there was something there (a mimic.) if (monsters* m = monster_at(where)) { if (mons_is_mimic(m->type) && you.can_see(m)) { mprf("%s twitches.", m->name(DESC_CAP_THE).c_str()); // Nothing else gives this message, so identify the mimic. m->flags |= MF_KNOWN_MIMIC; return (true); // otherwise you get free mimic ID } } mpr("There are no items there."); return (false); } item_def& item = mitm[item_idx]; // Protect the player from destroying the item. if (feat_destroys_item(grd(you.pos()), item)) { mpr( "That would be silly while over this terrain!" ); return (false); } // Mass of one unit. const int unit_mass = item_mass(item); const int max_mass = pow * 30 + random2(pow * 20); int max_units = item.quantity; if (unit_mass > 0) max_units = max_mass / unit_mass; if (max_units <= 0) { mpr("The mass is resisting your pull."); return (true); } // We need to modify the item *before* we move it, because // move_top_item() might change the location, or merge // with something at our position. mprf("Yoink! You pull the item%s to yourself.", (item.quantity > 1) ? "s" : ""); if (max_units < item.quantity) { item.quantity = max_units; mpr("You feel that some mass got lost in the cosmic void."); } // If we apport a net, free the monster under it. if (item.base_type == OBJ_MISSILES && item.sub_type == MI_THROWING_NET && item_is_stationary(item)) { remove_item_stationary(item); if (monsters *monster = monster_at(where)) monster->del_ench(ENCH_HELD, true); } // Actually move the item. move_top_item(where, you.pos()); return (true); } bool wielding_rocks() { bool rc = false; if (you.weapon()) { const item_def& wpn(*you.weapon()); rc = (wpn.base_type == OBJ_MISSILES && (wpn.sub_type == MI_STONE || wpn.sub_type == MI_LARGE_ROCK)); } return (rc); } bool cast_sandblast(int pow, bolt &beam) { const bool big = wielding_rocks(); const bool success = zapping(big ? ZAP_SANDBLAST : ZAP_SMALL_SANDBLAST, pow, beam, true); if (big && success) dec_inv_item_quantity( you.equip[EQ_WEAPON], 1 ); return (success); } void remove_condensation_shield() { mpr("Your icy shield evaporates.", MSGCH_DURATION); you.duration[DUR_CONDENSATION_SHIELD] = 0; you.redraw_armour_class = true; } void cast_condensation_shield(int pow) { if (you.shield() || you.duration[DUR_FIRE_SHIELD]) canned_msg(MSG_SPELL_FIZZLES); else { if (you.duration[DUR_CONDENSATION_SHIELD] > 0) { mpr("The disc of vapour around you crackles some more."); you.increase_duration(DUR_CONDENSATION_SHIELD, 5 + roll_dice(2,3), 30); } else { mpr("A crackling disc of dense vapour forms in the air!"); you.increase_duration(DUR_CONDENSATION_SHIELD, 10 + roll_dice(2, pow / 5), 30); you.redraw_armour_class = true; } } } void remove_divine_shield() { mpr("Your divine shield disappears!", MSGCH_DURATION); you.duration[DUR_DIVINE_SHIELD] = 0; you.attribute[ATTR_DIVINE_SHIELD] = 0; you.redraw_armour_class = true; } // shield bonus = attribute for duration turns, then decreasing by 1 // every two out of three turns // overall shield duration = duration + attribute // recasting simply resets those two values (to better values, presumably) void cast_divine_shield() { if (!you.duration[DUR_DIVINE_SHIELD]) { if (you.shield() || you.duration[DUR_FIRE_SHIELD] || you.duration[DUR_CONDENSATION_SHIELD]) { mprf("Your shield is strengthened by %s's divine power.", god_name(you.religion).c_str()); } else mpr("A divine shield forms around you!"); } else mpr("Your divine shield is renewed."); you.redraw_armour_class = true; // duration of complete shield bonus from 35 to 80 turns you.set_duration(DUR_DIVINE_SHIELD, 35 + (you.skills[SK_INVOCATIONS] * 4) / 3); // shield bonus up to 8 you.attribute[ATTR_DIVINE_SHIELD] = 3 + you.skills[SK_SHIELDS]/5; you.redraw_armour_class = true; } static int _quadrant_blink(coord_def where, int pow, int, actor *) { if (where == you.pos()) return (0); if (you.level_type == LEVEL_ABYSS) { abyss_teleport( false ); if (you.pet_target != MHITYOU) you.pet_target = MHITNOT; return (1); } if (pow > 100) pow = 100; const int dist = random2(6) + 2; // 2-7 // This is where you would *like* to go. const coord_def base = you.pos() + (where - you.pos()) * dist; // This can take a while if pow is high and there's lots of translucent // walls nearby. coord_def target; bool found = false; for (int i = 0; i < (pow*pow) / 500 + 1; ++i) { // Find a space near our base point... // First try to find a random square not adjacent to the basepoint, // then one adjacent if that fails. if (!random_near_space(base, target) && !random_near_space(base, target, true)) { return 0; } // ... which is close enough, but also far enough from us. if (distance(base, target) > 10 || distance(you.pos(), target) < 8) continue; if (!you.see_cell_no_trans(target)) continue; found = true; break; } if (!found) return(0); coord_def origin = you.pos(); int res = move_player_to_grid(target, false, true, true); if (res) { // Leave a purple cloud. place_cloud(CLOUD_TLOC_ENERGY, origin, 1 + random2(3), KC_YOU); } return res; } int cast_semi_controlled_blink(int pow) { int result = apply_one_neighbouring_square(_quadrant_blink, pow); // Controlled blink causes glowing. if (result) contaminate_player(1, true); return (result); } void cast_stoneskin(int pow) { if (you.is_undead && (you.species != SP_VAMPIRE || you.hunger_state < HS_SATIATED)) { 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 = true; } you.increase_duration(DUR_STONESKIN, 10 + random2(pow) + random2(pow), 50); } bool do_slow_monster(monsters* mon, kill_category whose_kill) { // Try to remove haste, if monster is hasted. if (mon->del_ench(ENCH_HASTE, true)) { if (simple_monster_message(mon, " is no longer moving quickly.")) return (true); } // Not hasted, slow it. if (!mon->has_ench(ENCH_SLOW) && !mons_is_stationary(mon) && mon->add_ench(mon_enchant(ENCH_SLOW, 0, whose_kill))) { if (!mon->paralysed() && !mon->petrified() && simple_monster_message(mon, " seems to slow down.")) { return (true); } } return (false); }