/* * File: spells1.cc * Summary: Implementations of some additional spells. * Mostly Translocations. * Written by: Linley Henzell */ #include "AppHdr.h" #include "spells1.h" #include #include #include #include #include "externs.h" #include "abyss.h" #include "artefact.h" #include "attitude-change.h" #include "beam.h" #include "cloud.h" #include "coord.h" #include "coordit.h" #include "describe.h" #include "directn.h" #include "effects.h" #include "env.h" #include "invent.h" #include "it_use2.h" #include "itemname.h" #include "itemprop.h" #include "item_use.h" #include "los.h" #include "message.h" #include "misc.h" #include "mon-iter.h" #include "mon-stuff.h" #include "mon-util.h" #include "options.h" #include "player.h" #include "religion.h" #include "skills2.h" #include "spells2.h" #include "spells3.h" #include "spells4.h" #include "spl-util.h" #include "state.h" #include "stuff.h" #include "teleport.h" #include "terrain.h" #include "transform.h" #include "traps.h" #include "view.h" #include "shout.h" #include "viewchar.h" static bool _abyss_blocks_teleport(bool cblink) { // Lugonu worshippers get their perks. if (you.religion == GOD_LUGONU) return (false); // Controlled Blink (the spell) works quite reliably in the Abyss. return (cblink ? one_chance_in(3) : !one_chance_in(3)); } // If wizard_blink is set, all restriction are ignored (except for // a monster being at the target spot), and the player gains no // contamination. int blink(int pow, bool high_level_controlled_blink, bool wizard_blink) { ASSERT(!crawl_state.arena); dist beam; if (crawl_state.is_repeating_cmd()) { crawl_state.cant_cmd_repeat("You can't repeat controlled blinks."); crawl_state.cancel_cmd_again(); crawl_state.cancel_cmd_repeat(); return (1); } // yes, there is a logic to this ordering {dlb}: if (item_blocks_teleport(true) && !wizard_blink) mpr("You feel a weird sense of stasis."); else if (you.level_type == LEVEL_ABYSS && _abyss_blocks_teleport(high_level_controlled_blink) && !wizard_blink) { mpr("The power of the Abyss keeps you in your place!"); } else if (you.confused() && !wizard_blink) random_blink(false); else if (!allow_control_teleport(true) && !wizard_blink) { mpr("A powerful magic interferes with your control of the blink."); if (high_level_controlled_blink) return (cast_semi_controlled_blink(pow)); random_blink(false); } else { // query for location {dlb}: while (true) { direction(beam, DIR_TARGET, TARG_ANY, -1, false, false, false, false, "Blink to where?"); if (!beam.isValid || beam.target == you.pos()) { if (!wizard_blink && !yesno("Are you sure you want to cancel this blink?", false, 'n')) { mesclr(); continue; } canned_msg(MSG_OK); return (-1); // early return {dlb} } monsters* beholder = you.get_beholder(beam.target); if (!wizard_blink && beholder) { mprf("You cannot blink away from %s!", beholder->name(DESC_NOCAP_THE, true).c_str()); continue; } if (grd(beam.target) == DNGN_OPEN_SEA) { mesclr(); mpr("You can't blink into the sea!"); } else if (you.see_cell_no_trans(beam.target)) { // Grid in los, no problem. break; } else if (you.trans_wall_blocking( beam.target )) { // Wizard blink can move past translucent walls. if (wizard_blink) break; mesclr(); mpr("You can't blink through translucent walls."); } else { mesclr(); mpr("You can only blink to visible locations."); } } // Allow wizard blink to send player into walls, in case the // user wants to alter that grid to something else. if (wizard_blink && feat_is_solid(grd(beam.target))) grd(beam.target) = DNGN_FLOOR; if (feat_is_solid(grd(beam.target)) || monster_at(beam.target)) { mpr("Oops! Maybe something was there already."); random_blink(false); } else if (you.level_type == LEVEL_ABYSS && !wizard_blink) { abyss_teleport( false ); if (you.pet_target != MHITYOU) you.pet_target = MHITNOT; } else { // Leave a purple cloud. place_cloud(CLOUD_TLOC_ENERGY, you.pos(), 1 + random2(3), KC_YOU); move_player_to_grid(beam.target, false, true, true); // Controlling teleport contaminates the player. -- bwr if (!wizard_blink) contaminate_player( 1, true ); } if (!wizard_blink && you.duration[DUR_CONDENSATION_SHIELD] > 0) remove_condensation_shield(); } crawl_state.cancel_cmd_again(); crawl_state.cancel_cmd_repeat(); return (1); } void random_blink(bool allow_partial_control, bool override_abyss) { ASSERT(!crawl_state.arena); bool success = false; coord_def target; if (item_blocks_teleport(true)) mpr("You feel a weird sense of stasis."); else if (you.level_type == LEVEL_ABYSS && !override_abyss && !one_chance_in(3)) { mpr("The power of the Abyss keeps you in your place!"); } // First try to find a random square not adjacent to the player, // then one adjacent if that fails. else if (!random_near_space(you.pos(), target) && !random_near_space(you.pos(), target, true)) { mpr("You feel jittery for a moment."); } #ifdef USE_SEMI_CONTROLLED_BLINK //jmf: Add back control, but effect is cast_semi_controlled_blink(pow). else if (player_control_teleport() && !you.confused() && allow_partial_control && allow_control_teleport()) { mpr("You may select the general direction of your translocation."); cast_semi_controlled_blink(100); maybe_id_ring_TC(); success = true; } #endif else { // Going to assume that move_player_to_grid() works. (It should // because terrain type, etc. was already checked.) This could // result in awkward messaging if it cancels for some reason, // but it's probably better than getting the blink message after // any Mf transform messages all the time. -cao mpr("You blink."); coord_def origin = you.pos(); success = move_player_to_grid(target, false, true, true); if (success) { // Leave a purple cloud. place_cloud(CLOUD_TLOC_ENERGY, origin, 1 + random2(3), KC_YOU); if (you.level_type == LEVEL_ABYSS) { abyss_teleport(false); if (you.pet_target != MHITYOU) you.pet_target = MHITNOT; } } } if (success && you.duration[DUR_CONDENSATION_SHIELD] > 0) remove_condensation_shield(); } bool fireball(int pow, bolt &beam) { return (zapping(ZAP_FIREBALL, pow, beam, true)); } void setup_fire_storm(const actor *source, int pow, bolt &beam) { beam.name = "great blast of fire"; beam.ex_size = 2 + (random2(pow) > 75); beam.flavour = BEAM_LAVA; beam.real_flavour = beam.flavour; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.colour = RED; beam.beam_source = source->mindex(); // XXX: Should this be KILL_MON_MISSILE? beam.thrower = source->atype() == ACT_PLAYER ? KILL_YOU_MISSILE : KILL_MON; beam.aux_source.clear(); beam.obvious_effect = false; beam.is_beam = false; beam.is_tracer = false; beam.is_explosion = true; beam.ench_power = pow; // used for radius beam.hit = 20 + pow / 10; beam.damage = calc_dice(8, 5 + pow); } void cast_fire_storm(int pow, bolt &beam) { setup_fire_storm(&you, pow, beam); if (beam.explode(false)) mpr("A raging storm of fire appears!"); viewwindow(false); } // No setup/cast split here as monster hellfire is completely different. // Sad, but needed to maintain balance - monster hellfirers get asymmetric // torment too. bool cast_hellfire_burst(int pow, bolt &beam) { beam.name = "burst of hellfire"; beam.aux_source = "burst of hellfire"; beam.ex_size = 1; beam.flavour = BEAM_HELLFIRE; beam.real_flavour = beam.flavour; beam.type = dchar_glyph(DCHAR_FIRED_BURST); beam.colour = RED; beam.beam_source = MHITYOU; beam.thrower = KILL_YOU; beam.obvious_effect = false; beam.is_beam = false; beam.is_explosion = true; beam.ench_power = pow; // used for radius beam.hit = 20 + pow / 10; beam.damage = calc_dice(6, 30 + pow); beam.can_see_invis = you.can_see_invisible(); beam.smart_monster = true; beam.attitude = ATT_FRIENDLY; beam.friend_info.count = 0; beam.is_tracer = true; beam.explode(false); if (beam.beam_cancelled) { canned_msg(MSG_OK); return (false); } mpr("You call forth a pillar of hellfire!"); beam.is_tracer = false; beam.in_explosion_phase = false; beam.explode(true); return (true); } bool _lightning_los(const coord_def& source, const coord_def& target) { // XXX: currently bounded by circular LOS radius; // XXX: adapt opacity -- allow passing clouds. return (exists_ray(source, target, opc_solid, circle_def(LOS_MAX_RADIUS, C_ROUND))); } void cast_chain_lightning(int pow, const actor *caster) { bolt beam; // initialise beam structure beam.name = "lightning arc"; beam.aux_source = "chain lightning"; beam.beam_source = caster->mindex(); beam.thrower = (caster == &you) ? KILL_YOU_MISSILE : KILL_MON_MISSILE; beam.range = 8; beam.hit = AUTOMATIC_HIT; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.flavour = BEAM_ELECTRICITY; beam.obvious_effect = true; beam.is_beam = false; // since we want to stop at our target beam.is_explosion = false; beam.is_tracer = false; coord_def source, target; for (source = caster->pos(); pow > 0; pow -= 8 + random2(13), source = target) { // infinity as far as this spell is concerned // (Range - 1) is used because the distance is randomised and // may be shifted by one. int min_dist = MONSTER_LOS_RANGE - 1; int dist; int count = 0; target.x = -1; target.y = -1; for (monster_iterator mi; mi; ++mi) { if (invalid_monster(*mi)) continue; dist = grid_distance(source, mi->pos()); // check for the source of this arc if (!dist) continue; // randomise distance (arcs don't care about a couple of feet) dist += (random2(3) - 1); // always ignore targets further than current one if (dist > min_dist) continue; if (!_lightning_los(source, mi->pos())) continue; count++; if (dist < min_dist) { // switch to looking for closer targets (but not always) if (!one_chance_in(10)) { min_dist = dist; target = mi->pos(); count = 0; } } else if (target.x == -1 || one_chance_in(count)) { // either first target, or new selected target at min_dist target = mi->pos(); // need to set min_dist for first target case dist = std::max(dist, min_dist); } } // now check if the player is a target dist = grid_distance(source, you.pos()); if (dist) // i.e., player was not the source { // distance randomised (as above) dist += (random2(3) - 1); // select player if only, closest, or randomly selected if ((target.x == -1 || dist < min_dist || (dist == min_dist && one_chance_in(count + 1))) && _lightning_los(source, you.pos())) { target = you.pos(); } } const bool see_source = you.see_cell( source ); const bool see_targ = you.see_cell( target ); if (target.x == -1) { if (see_source) mpr("The lightning grounds out."); break; } // Trying to limit message spamming here so we'll only mention // the thunder when it's out of LoS. if (!see_source) noisy(25, source, "You hear a mighty clap of thunder!"); if (see_source && !see_targ) mpr("The lightning arcs out of your line of sight!"); else if (!see_source && see_targ) mpr("The lightning arc suddenly appears!"); if (!you.see_cell_no_trans( target )) { // It's no longer in the caster's LOS and influence. pow = pow / 2 + 1; } beam.source = source; beam.target = target; beam.colour = LIGHTBLUE; beam.damage = calc_dice(5, 12 + pow * 2 / 3); // Be kinder to the caster. if (target == caster->pos()) { if (!(beam.damage.num /= 2)) beam.damage.num = 1; if ((beam.damage.size /= 2) < 3) beam.damage.size = 3; } beam.fire(); } more(); } void identify(int power, int item_slot) { int id_used = 1; // Scrolls of identify *may* produce "extra" identifications. if (power == -1 && one_chance_in(5)) id_used += (coinflip()? 1 : 2); do { if (item_slot == -1) { item_slot = prompt_invent_item("Identify which item?", MT_INVLIST, OSEL_UNIDENT, true, true, false); } if (prompt_failed(item_slot)) return; item_def& item(you.inv[item_slot]); if (fully_identified(item)) { mpr("Choose an unidentified item, or Esc to abort."); if (Options.auto_list) more(); item_slot = -1; continue; } set_ident_type(item, ID_KNOWN_TYPE); set_ident_flags(item, ISFLAG_IDENT_MASK); if (Options.autoinscribe_artefacts && is_artefact(item)) add_autoinscription( item, artefact_auto_inscription(item)); // For scrolls, now id the scroll, unless already known. if (power == -1 && get_ident_type(OBJ_SCROLLS, SCR_IDENTIFY) != ID_KNOWN_TYPE) { set_ident_type(OBJ_SCROLLS, SCR_IDENTIFY, ID_KNOWN_TYPE); const int wpn = you.equip[EQ_WEAPON]; if (wpn != -1 && you.inv[wpn].base_type == OBJ_SCROLLS && you.inv[wpn].sub_type == SCR_IDENTIFY) { you.wield_change = true; } } // Output identified item. mpr(item.name(DESC_INVENTORY_EQUIP).c_str()); if (item_slot == you.equip[EQ_WEAPON]) you.wield_change = true; id_used--; if (Options.auto_list && id_used > 0) more(); // In case we get to try again. item_slot = -1; } while (id_used > 0); } // Returns whether the spell was actually cast. bool conjure_flame(int pow, const coord_def& where) { // FIXME: This would be better handled by a flag to enforce max range. if (grid_distance(where, you.pos()) > spell_range(SPELL_CONJURE_FLAME, pow, true) || !in_bounds(where)) { mpr("That's too far away."); return (false); } if (you.trans_wall_blocking(where)) { mpr("A translucent wall is in the way."); return (false); } if (cell_is_solid(where)) { if (grd(where) == DNGN_WAX_WALL) mpr("The flames aren't hot enough to melt wax walls!"); else mpr("You can't ignite solid rock!"); return (false); } const int cloud = env.cgrid(where); if (cloud != EMPTY_CLOUD && env.cloud[cloud].type != CLOUD_FIRE) { mpr("There's already a cloud there!"); return (false); } // Note that self-targetting is handled by SPFLAG_NOT_SELF. monsters *monster = monster_at(where); if (monster) { if (you.can_see(monster)) { mpr("You can't place the cloud on a creature."); return (false); } else { // FIXME: maybe should do _paranoid_option_disable() here? mpr("You see a ghostly outline there, and the spell fizzles."); return (true); // Don't give free detection! } } if (cloud != EMPTY_CLOUD) { // Reinforce the cloud - but not too much. // It must be a fire cloud from a previous test. mpr("The fire roars with new energy!"); const int extra_dur = 2 + std::min(random2(pow) / 2, 20); env.cloud[cloud].decay += extra_dur * 5; env.cloud[cloud].set_whose(KC_YOU); } else { const int durat = std::min(5 + (random2(pow)/2) + (random2(pow)/2), 23); place_cloud(CLOUD_FIRE, where, durat, KC_YOU); } return (true); } bool stinking_cloud( int pow, bolt &beem ) { beem.name = "stinking cloud"; beem.colour = GREEN; beem.range = 6; beem.damage = dice_def( 1, 0 ); beem.hit = 20; beem.type = dchar_glyph(DCHAR_FIRED_ZAP); beem.flavour = BEAM_POTION_STINKING_CLOUD; beem.ench_power = pow; beem.beam_source = MHITYOU; beem.thrower = KILL_YOU; beem.is_beam = false; beem.is_explosion = true; beem.aux_source.clear(); // Don't bother tracing if you're targetting yourself. if (beem.target != you.pos()) { // Fire tracer. beem.source = you.pos(); beem.can_see_invis = you.can_see_invisible(); beem.smart_monster = true; beem.attitude = ATT_FRIENDLY; beem.friend_info.count = 0; beem.is_tracer = true; beem.fire(); if (beem.beam_cancelled) { // We don't want to fire through friendlies. canned_msg(MSG_OK); return (false); } } // Really fire. beem.is_tracer = false; beem.fire(); return (true); } int cast_big_c(int pow, cloud_type cty, kill_category whose, bolt &beam) { big_cloud( cty, whose, beam.target, pow, 8 + random2(3), -1 ); return (1); } void big_cloud(cloud_type cl_type, kill_category whose, const coord_def& where, int pow, int size, int spread_rate, int colour, std::string name, std::string tile) { big_cloud(cl_type, whose, cloud_struct::whose_to_killer(whose), where, pow, size, spread_rate, colour, name, tile); } void big_cloud(cloud_type cl_type, killer_type killer, const coord_def& where, int pow, int size, int spread_rate, int colour, std::string name, std::string tile) { big_cloud(cl_type, cloud_struct::killer_to_whose(killer), killer, where, pow, size, spread_rate, colour, name, tile); } void big_cloud(cloud_type cl_type, kill_category whose, killer_type killer, const coord_def& where, int pow, int size, int spread_rate, int colour, std::string name, std::string tile) { apply_area_cloud(make_a_normal_cloud, where, pow, size, cl_type, whose, killer, spread_rate, colour, name, tile); } static bool _mons_hostile(const monsters *mon) { // Needs to be done this way because of friendly/neutral enchantments. return (!mon->wont_attack() && !mon->neutral()); } static bool _can_pacify_monster(const monsters *mon, const int healed) { if (you.religion != GOD_ELYVILON) return (false); if (healed < 1) return (false); // I was thinking of jellies when I wrote this, but maybe we shouldn't // exclude zombies and such... (jpeg) if (mons_intel(mon) <= I_PLANT) // no self-awareness return (false); const mon_holy_type holiness = mon->holiness(); if (!mon->is_holy() && holiness != MH_UNDEAD && holiness != MH_DEMONIC && holiness != MH_NATURAL) { return (false); } if (mons_is_stationary(mon)) // not able to leave the level return (false); if (mon->asleep()) // not aware of what is happening return (false); const int factor = (mons_intel(mon) <= I_ANIMAL) ? 3 : // animals (is_player_same_species(mon->type)) ? 2 // same species : 1; // other int divisor = 3; if (mon->is_holy()) divisor--; else if (holiness == MH_UNDEAD) divisor++; else if (holiness == MH_DEMONIC) divisor += 2; const int random_factor = random2((you.skills[SK_INVOCATIONS] + 1) * healed / divisor); dprf("pacifying %s? max hp: %d, factor: %d, Inv: %d, healed: %d, rnd: %d", mon->name(DESC_PLAIN).c_str(), mon->max_hit_points, factor, you.skills[SK_INVOCATIONS], healed, random_factor); if (mon->max_hit_points < factor * random_factor) return (true); return (false); } // Returns: 1 -- success, 0 -- failure, -1 -- cancel static int _healing_spell(int healed, bool divine_ability, const coord_def& where, bool not_self, targ_mode_type mode) { ASSERT(healed >= 1); bolt beam; dist spd; if (where.origin()) { spd.isValid = spell_direction(spd, beam, DIR_TARGET, mode != TARG_NUM_MODES ? mode : you.religion == GOD_ELYVILON ? TARG_ANY : TARG_FRIEND, LOS_RADIUS, false, true, true, "Heal whom?"); } else { spd.target = where; spd.isValid = in_bounds(spd.target); } if (!spd.isValid) return (-1); if (spd.target == you.pos()) { if (not_self) { mpr("You can only heal others!"); return (-1); } mpr("You are healed."); inc_hp(healed, false); return (1); } monsters* monster = monster_at(spd.target); if (!monster) { mpr("There isn't anything there!"); // This isn't a cancel, to avoid leaking invisible monster // locations. return (0); } const bool can_pacify = _can_pacify_monster(monster, healed); const bool is_hostile = _mons_hostile(monster); // Don't divinely heal a monster you can't pacify. if (divine_ability && you.religion == GOD_ELYVILON && !can_pacify) { canned_msg(MSG_NOTHING_HAPPENS); return (0); } bool did_something = false; if (you.religion == GOD_ELYVILON && can_pacify && is_hostile) { did_something = true; const bool is_holy = monster->is_holy(); const bool is_summoned = monster->is_summoned(); int pgain = 0; if (!is_holy && !is_summoned && you.piety < MAX_PIETY) { pgain = random2(1 + random2(monster->max_hit_points / (2 + you.piety / 20))); } if (pgain > 0) simple_god_message(" approves of your offer of peace."); else mpr("Elyvilon supports your offer of peace."); if (is_holy) good_god_holy_attitude_change(monster); else { simple_monster_message(monster, " turns neutral."); mons_pacify(monster, ATT_NEUTRAL); // Give a small piety return. if (pgain > 0) gain_piety(pgain); } } if (monster->heal(healed)) { did_something = true; mprf("You heal %s.", monster->name(DESC_NOCAP_THE).c_str()); if (monster->hit_points == monster->max_hit_points) simple_monster_message(monster, " is completely healed."); else print_wounds(monster); if (you.religion == GOD_ELYVILON && !is_hostile) { int pgain = 0; if (one_chance_in(8) && you.piety < MAX_PIETY) pgain = 1; if (pgain > 0) { simple_god_message(" approves of your healing of a fellow " "creature."); } else { mpr("Elyvilon appreciates your healing of a fellow " "creature."); } // Give a small piety return. if (pgain > 0) gain_piety(pgain); } } if (!did_something) { canned_msg(MSG_NOTHING_HAPPENS); return (0); } return (1); } // Returns: 1 -- success, 0 -- failure, -1 -- cancel int cast_healing(int pow, bool divine_ability, const coord_def& where, bool not_self, targ_mode_type mode) { pow = std::min(50, pow); return (_healing_spell(pow + roll_dice(2, pow) - 2, divine_ability, where, not_self, mode)); } void remove_divine_vigour() { mpr("Your divine vigour fades away.", MSGCH_DURATION); you.duration[DUR_DIVINE_VIGOUR] = 0; you.attribute[ATTR_DIVINE_VIGOUR] = 0; calc_hp(); calc_mp(); } bool cast_divine_vigour() { bool success = false; if (!you.duration[DUR_DIVINE_VIGOUR]) { mprf("%s grants you divine vigour.", god_name(you.religion).c_str()); const int vigour_amt = 1 + (you.skills[SK_INVOCATIONS]/6); const int old_hp_max = you.hp_max; const int old_mp_max = you.max_magic_points; you.attribute[ATTR_DIVINE_VIGOUR] = vigour_amt; you.set_duration(DUR_DIVINE_VIGOUR, 40 + (you.skills[SK_INVOCATIONS*5])/2); calc_hp(); inc_hp(you.hp_max - old_hp_max, false); calc_mp(); inc_mp(you.max_magic_points - old_mp_max, false); success = true; } else canned_msg(MSG_NOTHING_HAPPENS); return (success); } void remove_divine_stamina() { mpr("Your divine stamina fades away.", MSGCH_DURATION); modify_stat(STAT_STRENGTH, -you.attribute[ATTR_DIVINE_STAMINA], true, "Zin's divine stamina running out"); modify_stat(STAT_INTELLIGENCE, -you.attribute[ATTR_DIVINE_STAMINA], true, "Zin's divine stamina running out"); modify_stat(STAT_DEXTERITY, -you.attribute[ATTR_DIVINE_STAMINA], true, "Zin's divine stamina running out"); you.duration[DUR_DIVINE_STAMINA] = 0; you.attribute[ATTR_DIVINE_STAMINA] = 0; } static bool _kill_duration(duration_type dur) { const bool rc = (you.duration[dur] > 0); you.duration[dur] = 0; return (rc); } bool cast_vitalisation() { bool success = false; int type = 0; // Remove negative afflictions. if (you.disease || you.rotting || you.confused() || you.duration[DUR_PARALYSIS] || you.duration[DUR_POISONING] || you.petrified()) { do { switch (random2(6)) { case 0: if (you.disease) { success = true; you.disease = 0; } break; case 1: if (you.rotting) { success = true; you.rotting = 0; } break; case 2: success = _kill_duration(DUR_CONF); break; case 3: success = _kill_duration(DUR_PARALYSIS); break; case 4: success = _kill_duration(DUR_POISONING); break; case 5: success = _kill_duration(DUR_PETRIFIED); break; } } while (!success); } // Restore stats. else if (you.strength < you.max_strength || you.intel < you.max_intel || you.dex < you.max_dex) { type = 1; while (!restore_stat(STAT_RANDOM, 0, true)) ; success = true; } else { // Add divine stamina. if (!you.duration[DUR_DIVINE_STAMINA]) { success = true; type = 2; mprf("%s grants you divine stamina.", god_name(you.religion).c_str()); const int stamina_amt = 3; you.attribute[ATTR_DIVINE_STAMINA] = stamina_amt; you.set_duration(DUR_DIVINE_STAMINA, 40 + (you.skills[SK_INVOCATIONS]*5)/2); modify_stat(STAT_STRENGTH, stamina_amt, true, ""); modify_stat(STAT_INTELLIGENCE, stamina_amt, true, ""); modify_stat(STAT_DEXTERITY, stamina_amt, true, ""); } } // If vitalisation has succeeded, display an appropriate message. if (success) { mprf("You feel %s.", (type == 0) ? "better" : (type == 1) ? "renewed" : "powerful"); } else canned_msg(MSG_NOTHING_HAPPENS); return (success); } bool cast_revivification(int pow) { bool success = false; if (you.hp == you.hp_max) canned_msg(MSG_NOTHING_HAPPENS); else if (you.hp_max < 21) mpr("You lack the resilience to cast this spell."); else { mpr("Your body is healed in an amazingly painful way."); int loss = 2; for (int i = 0; i < 9; ++i) if (x_chance_in_y(8, pow)) loss++; dec_max_hp(loss); set_hp(you.hp_max, false); success = true; } return (success); } void cast_cure_poison(int pow) { if (you.duration[DUR_POISONING] > 0) reduce_poison_player(2 + random2(pow) + random2(3)); else canned_msg(MSG_NOTHING_HAPPENS); } void purification(void) { mpr("You feel purified!"); you.disease = 0; you.rotting = 0; you.duration[DUR_POISONING] = 0; you.duration[DUR_CONF] = 0; you.duration[DUR_SLOW] = 0; you.duration[DUR_PARALYSIS] = 0; // can't currently happen -- bwr you.duration[DUR_PETRIFIED] = 0; } int allowed_deaths_door_hp(void) { int hp = you.skills[SK_NECROMANCY] / 2; if (you.religion == GOD_KIKUBAAQUDGHA && !player_under_penance()) hp += you.piety / 15; return (hp); } void cast_deaths_door(int pow) { if (you.is_undead) mpr("You're already dead!"); else if (you.duration[DUR_DEATHS_DOOR]) mpr("Your appeal for an extension has been denied."); else { mpr("You feel invincible!"); mpr("You seem to hear sand running through an hourglass...", MSGCH_SOUND); set_hp( allowed_deaths_door_hp(), false ); deflate_hp( you.hp_max, false ); you.set_duration(DUR_DEATHS_DOOR, 10 + random2avg(13, 3) + (random2(pow) / 10)); if (you.duration[DUR_DEATHS_DOOR] > 25 * BASELINE_DELAY) you.duration[DUR_DEATHS_DOOR] = (23 + random2(5)) * BASELINE_DELAY; } return; } void abjuration(int pow) { mpr("Send 'em back where they came from!"); // Scale power into something comparable to summon lifetime. const int abjdur = pow * 12; for (monster_iterator mon(&you.get_los()); mon; ++mon) { if (mon->wont_attack()) continue; int duration; if (mon->is_summoned(&duration)) { int sockage = std::max(fuzz_value(abjdur, 60, 30), 40); dprf("%s abj: dur: %d, abj: %d", mon->name(DESC_PLAIN).c_str(), duration, sockage); bool shielded = false; // TSO and Trog's abjuration protection. if (mons_is_god_gift(*mon, GOD_SHINING_ONE)) { sockage = sockage * (30 - mon->hit_dice) / 45; if (sockage < duration) { simple_god_message(" protects a fellow warrior from your evil magic!", GOD_SHINING_ONE); shielded = true; } } else if (mons_is_god_gift(*mon, GOD_TROG)) { sockage = sockage * 8 / 15; if (sockage < duration) { simple_god_message(" shields an ally from your puny magic!", GOD_TROG); shielded = true; } } mon_enchant abj = mon->get_ench(ENCH_ABJ); if (!mon->lose_ench_duration(abj, sockage) && !shielded) simple_monster_message(*mon, " shudders."); } } } // Antimagic is sort of an anti-extension... it sets a lot of magical // durations to 1 so it's very nasty at times (and potentially lethal, // that's why we reduce levitation to 2, so that the player has a chance // to stop insta-death... sure the others could lead to death, but that's // not as direct as falling into deep water) -- bwr void antimagic() { duration_type dur_list[] = { DUR_INVIS, DUR_CONF, DUR_PARALYSIS, DUR_SLOW, DUR_HASTE, DUR_MIGHT, DUR_AGILITY, DUR_BRILLIANCE, DUR_FIRE_SHIELD, DUR_ICY_ARMOUR, DUR_REPEL_MISSILES, DUR_REGENERATION, DUR_SWIFTNESS, DUR_STONEMAIL, DUR_CONTROL_TELEPORT, DUR_TRANSFORMATION, DUR_DEATH_CHANNEL, DUR_DEFLECT_MISSILES, DUR_PHASE_SHIFT, DUR_SEE_INVISIBLE, DUR_WEAPON_BRAND, DUR_SILENCE, DUR_CONDENSATION_SHIELD, DUR_STONESKIN, DUR_BARGAIN, DUR_INSULATION, DUR_RESIST_POISON, DUR_RESIST_FIRE, DUR_RESIST_COLD, DUR_SLAYING, DUR_STEALTH, DUR_MAGIC_SHIELD, DUR_SAGE, DUR_PETRIFIED }; if (!you.permanent_levitation() && !you.permanent_flight() && you.duration[DUR_LEVITATION] > 2) { you.duration[DUR_LEVITATION] = 2; } if (!you.permanent_flight() && you.duration[DUR_CONTROLLED_FLIGHT] > 1) you.duration[DUR_CONTROLLED_FLIGHT] = 1; for (unsigned int i = 0; i < ARRAYSZ(dur_list); ++i) if (you.duration[dur_list[i]] > 1) you.duration[dur_list[i]] = 1; contaminate_player(-1 * (1 + random2(5))); } void extension(int pow) { int contamination = random2(2); if (you.duration[DUR_HASTE]) { potion_effect(POT_SPEED, pow); contamination++; } if (you.duration[DUR_SLOW]) potion_effect(POT_SLOWING, pow); if (you.duration[DUR_MIGHT]) { potion_effect(POT_MIGHT, pow); contamination++; } if (you.duration[DUR_BRILLIANCE]) { potion_effect(POT_BRILLIANCE, pow); contamination++; } if (you.duration[DUR_AGILITY]) { potion_effect(POT_AGILITY, pow); contamination++; } if (you.duration[DUR_LEVITATION] && !you.duration[DUR_CONTROLLED_FLIGHT]) potion_effect(POT_LEVITATION, pow); if (you.duration[DUR_INVIS]) { potion_effect(POT_INVISIBILITY, pow); contamination++; } if (you.duration[DUR_ICY_ARMOUR]) ice_armour(pow, true); if (you.duration[DUR_REPEL_MISSILES]) missile_prot(pow); if (you.duration[DUR_REGENERATION]) cast_regen(pow); if (you.duration[DUR_DEFLECT_MISSILES]) deflection(pow); if (you.duration[DUR_FIRE_SHIELD]) { you.increase_duration(DUR_FIRE_SHIELD, random2(pow / 20), 50); mpr("Your ring of flames roars with new vigour!"); } if ( !you.duration[DUR_WEAPON_BRAND] < 1) { you.increase_duration(DUR_WEAPON_BRAND, 5 + random2(8), 80); } if (you.duration[DUR_SWIFTNESS]) cast_swiftness(pow); if (you.duration[DUR_INSULATION]) cast_insulation(pow); if (you.duration[DUR_STONEMAIL]) stone_scales(pow); if (you.duration[DUR_CONTROLLED_FLIGHT]) cast_fly(pow); if (you.duration[DUR_CONTROL_TELEPORT]) cast_teleport_control(pow); if (you.duration[DUR_RESIST_POISON]) cast_resist_poison(pow); if (you.duration[DUR_TRANSFORMATION] && (you.species != SP_VAMPIRE || you.attribute[ATTR_TRANSFORMATION] != TRAN_BAT)) { mpr("Your transformation has been extended."); you.increase_duration(DUR_TRANSFORMATION, random2(pow), 100, "Your transformation has been extended."); // Give a warning if it won't last long enough for the // timeout messages. transformation_expiration_warning(); } //jmf: added following if (you.duration[DUR_STONESKIN]) cast_stoneskin(pow); if (you.duration[DUR_PHASE_SHIFT]) cast_phase_shift(pow); if (you.duration[DUR_SEE_INVISIBLE]) cast_see_invisible(pow); if (you.duration[DUR_SILENCE]) //how precisely did you cast extension? cast_silence(pow); if (you.duration[DUR_CONDENSATION_SHIELD]) cast_condensation_shield(pow); if (contamination) contaminate_player( contamination, true ); } void ice_armour(int pow, bool extending) { if (!player_light_armour()) { if (!extending) mpr("You are wearing too much armour."); return; } if (you.duration[DUR_STONEMAIL] || you.duration[DUR_STONESKIN]) { if (!extending) mpr("The spell conflicts with another spell still in effect."); return; } if (you.duration[DUR_ICY_ARMOUR]) mpr( "Your icy armour thickens." ); else { if (you.attribute[ATTR_TRANSFORMATION] == TRAN_ICE_BEAST) mpr( "Your icy body feels more resilient." ); else mpr( "A film of ice covers your body!" ); you.redraw_armour_class = true; } you.increase_duration(DUR_ICY_ARMOUR, 20 + random2(pow) + random2(pow), 50, NULL); } void stone_scales(int pow) { if (you.duration[DUR_ICY_ARMOUR] || you.duration[DUR_STONESKIN]) { mpr("The spell conflicts with another spell still in effect."); return; } if (you.duration[DUR_STONEMAIL]) mpr("Your scaly armour looks firmer."); else { if (you.attribute[ATTR_TRANSFORMATION] == TRAN_STATUE) mpr( "Your stone body feels more resilient." ); else mpr( "A set of stone scales covers your body!" ); you.redraw_evasion = true; you.redraw_armour_class = true; } you.increase_duration(DUR_STONEMAIL, 20 + random2(pow) + random2(pow), 100, NULL); burden_change(); } void missile_prot(int pow) { you.increase_duration(DUR_REPEL_MISSILES, 8 + roll_dice( 2, pow ), 100, "You feel protected from missiles."); } void deflection(int pow) { you.increase_duration(DUR_DEFLECT_MISSILES, 15 + random2(pow), 100, "You feel very safe from missiles."); } void remove_regen(bool divine_ability) { mpr("Your skin stops crawling.", MSGCH_DURATION); you.duration[DUR_REGENERATION] = 0; if (divine_ability) { mpr("You feel less resistant to magic.", MSGCH_DURATION); you.attribute[ATTR_DIVINE_REGENERATION] = 0; } } void cast_regen(int pow, bool divine_ability) { you.increase_duration(DUR_REGENERATION, 5 + roll_dice(2, pow / 3 + 1), 100, "Your skin crawls."); if (divine_ability) { mpr("You feel resistant to magic."); you.attribute[ATTR_DIVINE_REGENERATION] = 1; } } void cast_berserk(void) { go_berserk(true); } void cast_swiftness(int power) { if (you.in_water()) { mpr("The water foams!"); return; } if (!you.duration[DUR_SWIFTNESS] && player_movement_speed() <= 6) { mpr( "You can't move any more quickly." ); return; } // [dshaligram] Removed the on-your-feet bit. Sounds odd when // you're levitating, for instance. you.increase_duration(DUR_SWIFTNESS, 20 + random2(power), 100, "You feel quick."); did_god_conduct(DID_HASTY, 8, true); } void cast_fly(int power) { const int dur_change = 25 + random2(power) + random2(power); const bool was_levitating = you.airborne(); you.increase_duration(DUR_LEVITATION, dur_change, 100); you.increase_duration(DUR_CONTROLLED_FLIGHT, dur_change, 100); burden_change(); if (!was_levitating) { if (you.light_flight()) mpr("You swoop lightly up into the air."); else mpr("You fly up into the air."); // Merfolk boots unmeld if flight takes us out of water. if (you.species == SP_MERFOLK && feat_is_water(grd(you.pos()))) unmeld_one_equip(EQ_BOOTS); } else mpr("You feel more buoyant."); } void cast_insulation(int power) { you.increase_duration(DUR_INSULATION, 10 + random2(power), 100, "You feel insulated."); } void cast_resist_poison(int power) { you.increase_duration(DUR_RESIST_POISON, 10 + random2(power), 100, "You feel resistant to poison."); } void cast_teleport_control(int power) { you.increase_duration(DUR_CONTROL_TELEPORT, 10 + random2(power), 50, "You feel in control."); } void cast_ring_of_flames(int power) { // You shouldn't be able to cast this in the rain. {due} if (in_what_cloud(CLOUD_RAIN)) { mpr("Your spell sizzles in the rain."); return; } you.increase_duration(DUR_FIRE_SHIELD, 5 + (power / 10) + (random2(power) / 5), 50, "The air around you leaps into flame!"); manage_fire_shield(1); } void cast_confusing_touch(int power) { msg::stream << "Your " << your_hand(true) << " begin to glow " << (you.duration[DUR_CONFUSING_TOUCH] ? "brighter" : "red") << "." << std::endl; you.increase_duration(DUR_CONFUSING_TOUCH, 5 + (random2(power) / 5), 50, NULL); } bool cast_sure_blade(int power) { bool success = false; if (!you.weapon()) mpr("You aren't wielding a weapon!"); else if (weapon_skill(you.weapon()->base_type, you.weapon()->sub_type) != SK_SHORT_BLADES) { mpr("You cannot bond with this weapon."); } else { if (!you.duration[DUR_SURE_BLADE]) mpr("You become one with your weapon."); else if (you.duration[DUR_SURE_BLADE] < 25 * BASELINE_DELAY) mpr("Your bond becomes stronger."); you.increase_duration(DUR_SURE_BLADE, 8 + (random2(power) / 10), 25, NULL); success = true; } return (success); } void manage_fire_shield(int delay) { ASSERT(you.duration[DUR_FIRE_SHIELD]); int old_dur = you.duration[DUR_FIRE_SHIELD]; you.duration[DUR_FIRE_SHIELD]-= delay; if(you.duration[DUR_FIRE_SHIELD] < 0) you.duration[DUR_FIRE_SHIELD] = 0; if (!you.duration[DUR_FIRE_SHIELD]) { mpr("Your ring of flames gutters out.", MSGCH_DURATION); return; } int threshold = get_expiration_threshold(DUR_FIRE_SHIELD); if (old_dur > threshold && you.duration[DUR_FIRE_SHIELD] < threshold) mpr("Your ring of flames is guttering out.", MSGCH_WARN); // Place fire clouds all around you for ( adjacent_iterator ai(you.pos()); ai; ++ai ) if (!feat_is_solid(grd(*ai)) && env.cgrid(*ai) == EMPTY_CLOUD) place_cloud( CLOUD_FIRE, *ai, 1 + random2(6), KC_YOU ); }