/* File: mon-cast.cc * Summary: Monster spell casting. * Written by: Linley Henzell */ #include "AppHdr.h" #include "mon-cast.h" #include "beam.h" #include "cloud.h" #include "colour.h" #include "coordit.h" #include "database.h" #include "effects.h" #include "env.h" #include "fprop.h" #include "fight.h" #include "ghost.h" #include "items.h" #include "misc.h" #include "message.h" #include "mon-behv.h" #include "mon-iter.h" #include "mon-place.h" #include "mon-project.h" #include "terrain.h" #include "tutorial.h" #include "mislead.h" #include "mgen_data.h" #include "coord.h" #include "mon-speak.h" #include "mon-stuff.h" #include "mon-util.h" #include "random.h" #include "religion.h" #include "shout.h" #include "spl-util.h" #include "spl-cast.h" #include "spells1.h" #include "spells3.h" #include "state.h" #include "stuff.h" #include "areas.h" #include "teleport.h" #include "view.h" #include "viewchar.h" #include "xom.h" static bool _valid_mon_spells[NUM_SPELLS]; void init_mons_spells() { monsters fake_mon; fake_mon.type = MONS_BLACK_DRACONIAN; fake_mon.hit_points = 1; bolt pbolt; for (int i = 0; i < NUM_SPELLS; i++) { spell_type spell = (spell_type) i; _valid_mon_spells[i] = false; if (!is_valid_spell(spell)) continue; if (setup_mons_cast(&fake_mon, pbolt, spell, true)) _valid_mon_spells[i] = true; } } bool is_valid_mon_spell(spell_type spell) { if (spell < 0 || spell >= NUM_SPELLS) return (false); return (_valid_mon_spells[spell]); } static void _scale_draconian_breath(bolt& beam, int drac_type) { int scaling = 100; switch (drac_type) { case MONS_RED_DRACONIAN: beam.name = "searing blast"; beam.aux_source = "blast of searing breath"; scaling = 65; break; case MONS_WHITE_DRACONIAN: beam.name = "chilling blast"; beam.aux_source = "blast of chilling breath"; beam.short_name = "frost"; scaling = 65; break; case MONS_PLAYER_GHOST: // draconians only beam.name = "blast of negative energy"; beam.aux_source = "blast of draining breath"; beam.flavour = BEAM_NEG; beam.colour = DARKGREY; scaling = 65; break; } beam.damage.size = scaling * beam.damage.size / 100; } static spell_type _draco_type_to_breath(int drac_type) { switch (drac_type) { case MONS_BLACK_DRACONIAN: return SPELL_LIGHTNING_BOLT; case MONS_MOTTLED_DRACONIAN: return SPELL_STICKY_FLAME_SPLASH; case MONS_YELLOW_DRACONIAN: return SPELL_ACID_SPLASH; case MONS_GREEN_DRACONIAN: return SPELL_POISONOUS_CLOUD; case MONS_PURPLE_DRACONIAN: return SPELL_ISKENDERUNS_MYSTIC_BLAST; case MONS_RED_DRACONIAN: return SPELL_FIRE_BREATH; case MONS_WHITE_DRACONIAN: return SPELL_COLD_BREATH; case MONS_PALE_DRACONIAN: return SPELL_STEAM_BALL; // Handled later. case MONS_PLAYER_GHOST: return SPELL_DRACONIAN_BREATH; default: DEBUGSTR("Invalid monster using draconian breath spell"); break; } return (SPELL_DRACONIAN_BREATH); } static bool _flavour_benefits_monster(beam_type flavour, monsters & monster) { switch(flavour) { case BEAM_HASTE: return (!monster.has_ench(ENCH_HASTE)); case BEAM_INVISIBILITY: return (!monster.has_ench(ENCH_INVIS)); case BEAM_HEALING: return (monster.hit_points != monster.max_hit_points); default: return false; } } // Find an allied monster to cast a beneficial beam spell at. // Only used for haste other at the moment. static bool _set_allied_target(monsters * caster, bolt & pbolt) { monsters * selected_target = NULL; int min_distance = INT_MAX; monster_type caster_genus = mons_genus(caster->type); for (monster_iterator targ(caster); targ; ++targ) { if (*targ != caster && mons_genus(targ->type) == caster_genus && mons_atts_aligned(targ->attitude, caster->attitude) && !targ->has_ench(ENCH_CHARM) && _flavour_benefits_monster(pbolt.flavour, **targ)) { int targ_distance = grid_distance(targ->pos(), caster->pos()); if (targ_distance < min_distance && targ_distance < pbolt.range) { min_distance = targ_distance; selected_target = *targ; } } } if (selected_target) { pbolt.target = selected_target->pos(); return (true); } // Didn't find a target return (false); } bolt mons_spells( monsters *mons, spell_type spell_cast, int power, bool check_validity ) { ASSERT(power > 0); bolt beam; // Initialise to some bogus values so we can catch problems. beam.name = "****"; beam.colour = 1000; beam.hit = -1; beam.damage = dice_def( 1, 0 ); beam.ench_power = -1; beam.type = 0; beam.flavour = BEAM_NONE; beam.thrower = KILL_MISC; beam.is_beam = false; beam.is_explosion = false; // Sandblast is different, and gets range updated later if (spell_cast != SPELL_SANDBLAST) beam.range = spell_range(spell_cast, power, true, false); const int drac_type = (mons_genus(mons->type) == MONS_DRACONIAN) ? draco_subspecies(mons) : mons->type; spell_type real_spell = spell_cast; if (spell_cast == SPELL_DRACONIAN_BREATH) real_spell = _draco_type_to_breath(drac_type); beam.type = dchar_glyph(DCHAR_FIRED_ZAP); // default beam.thrower = KILL_MON_MISSILE; beam.origin_spell = real_spell; // FIXME: this should use the zap_data[] struct from beam.cc! switch (real_spell) { case SPELL_MAGIC_DART: beam.colour = LIGHTMAGENTA; beam.name = "magic dart"; beam.damage = dice_def( 3, 4 + (power / 100) ); beam.hit = AUTOMATIC_HIT; beam.flavour = BEAM_MMISSILE; break; case SPELL_THROW_FLAME: beam.colour = RED; beam.name = "puff of flame"; beam.damage = dice_def( 3, 5 + (power / 40) ); beam.hit = 25 + power / 40; beam.flavour = BEAM_FIRE; break; case SPELL_THROW_FROST: beam.colour = WHITE; beam.name = "puff of frost"; beam.damage = dice_def( 3, 5 + (power / 40) ); beam.hit = 25 + power / 40; beam.flavour = BEAM_COLD; break; case SPELL_SANDBLAST: beam.colour = BROWN; beam.name = "rocky blast"; beam.damage = dice_def( 3, 5 + (power / 40) ); beam.hit = 20 + power / 40; beam.flavour = BEAM_FRAG; beam.range = 2; // spell_range() is wrong here break; case SPELL_DISPEL_UNDEAD: beam.flavour = BEAM_DISPEL_UNDEAD; beam.damage = dice_def( 3, std::min(6 + power / 10, 40) ); beam.is_beam = true; break; case SPELL_PARALYSE: beam.flavour = BEAM_PARALYSIS; beam.is_beam = true; break; case SPELL_SLOW: beam.flavour = BEAM_SLOW; beam.is_beam = true; break; case SPELL_HASTE_OTHER: beam.flavour = BEAM_HASTE; beam.is_beam = true; break; case SPELL_HASTE: // (self) beam.flavour = BEAM_HASTE; break; case SPELL_CORONA: beam.flavour = BEAM_CORONA; beam.is_beam = true; break; case SPELL_CONFUSE: beam.flavour = BEAM_CONFUSION; beam.is_beam = true; break; case SPELL_HIBERNATION: beam.flavour = BEAM_HIBERNATION; beam.is_beam = true; break; case SPELL_SLEEP: beam.flavour = BEAM_SLEEP; beam.is_beam = true; break; case SPELL_POLYMORPH_OTHER: beam.flavour = BEAM_POLYMORPH; beam.is_beam = true; // Be careful with this one. // Having allies mutate you is infuriating. beam.foe_ratio = 1000; break; case SPELL_VENOM_BOLT: beam.name = "bolt of poison"; beam.damage = dice_def( 3, 6 + power / 13 ); beam.colour = LIGHTGREEN; beam.flavour = BEAM_POISON; beam.hit = 19 + power / 20; beam.is_beam = true; break; case SPELL_POISON_ARROW: beam.name = "poison arrow"; beam.damage = dice_def( 3, 7 + power / 12 ); beam.colour = LIGHTGREEN; beam.type = dchar_glyph(DCHAR_FIRED_MISSILE); beam.flavour = BEAM_POISON_ARROW; beam.hit = 20 + power / 25; break; case SPELL_BOLT_OF_MAGMA: beam.name = "bolt of magma"; beam.damage = dice_def( 3, 8 + power / 11 ); beam.colour = RED; beam.flavour = BEAM_LAVA; beam.hit = 17 + power / 25; beam.is_beam = true; break; case SPELL_BOLT_OF_FIRE: beam.name = "bolt of fire"; beam.damage = dice_def( 3, 8 + power / 11 ); beam.colour = RED; beam.flavour = BEAM_FIRE; beam.hit = 17 + power / 25; beam.is_beam = true; break; case SPELL_THROW_ICICLE: beam.name = "shard of ice"; beam.damage = dice_def( 3, 8 + power / 11 ); beam.colour = WHITE; beam.flavour = BEAM_ICE; beam.hit = 17 + power / 25; break; case SPELL_BOLT_OF_COLD: beam.name = "bolt of cold"; beam.damage = dice_def( 3, 8 + power / 11 ); beam.colour = WHITE; beam.flavour = BEAM_COLD; beam.hit = 17 + power / 25; beam.is_beam = true; break; case SPELL_PRIMAL_WAVE: beam.name = "great wave of water"; // Water attack is weaker than the pure elemental damage // attacks, but also less resistible. beam.damage = dice_def( 3, 6 + power / 12 ); beam.colour = LIGHTBLUE; beam.flavour = BEAM_WATER; // Huge wave of water is hard to dodge. beam.hit = 20 + power / 20; beam.is_beam = false; beam.type = dchar_glyph(DCHAR_WAVY); break; case SPELL_FREEZING_CLOUD: beam.name = "freezing blast"; beam.damage = dice_def( 2, 9 + power / 11 ); beam.colour = WHITE; beam.flavour = BEAM_COLD; beam.hit = 17 + power / 25; beam.is_beam = true; beam.is_big_cloud = true; break; case SPELL_SHOCK: beam.name = "zap"; beam.damage = dice_def( 1, 8 + (power / 20) ); beam.colour = LIGHTCYAN; beam.flavour = BEAM_ELECTRICITY; beam.hit = 17 + power / 20; beam.is_beam = true; break; case SPELL_LIGHTNING_BOLT: beam.name = "bolt of lightning"; beam.damage = dice_def( 3, 10 + power / 17 ); beam.colour = LIGHTCYAN; beam.flavour = BEAM_ELECTRICITY; beam.hit = 16 + power / 40; beam.is_beam = true; break; case SPELL_INVISIBILITY: beam.flavour = BEAM_INVISIBILITY; break; case SPELL_FIREBALL: beam.colour = RED; beam.name = "fireball"; beam.damage = dice_def( 3, 7 + power / 10 ); beam.hit = 40; beam.flavour = BEAM_FIRE; beam.foe_ratio = 80; beam.is_explosion = true; break; case SPELL_FIRE_STORM: setup_fire_storm(mons, power / 2, beam); beam.foe_ratio = random_range(40, 55); break; case SPELL_ICE_STORM: beam.name = "great blast of cold"; beam.colour = BLUE; beam.damage = calc_dice( 10, 18 + power / 2 ); beam.hit = 20 + power / 10; // 50: 25 100: 30 beam.ench_power = power; // used for radius beam.flavour = BEAM_ICE; // half resisted beam.is_explosion = true; beam.foe_ratio = random_range(40, 55); break; case SPELL_HELLFIRE_BURST: beam.aux_source = "burst of hellfire"; beam.name = "burst of hellfire"; beam.ex_size = 1; beam.flavour = BEAM_HELLFIRE; beam.is_explosion = true; beam.colour = RED; beam.aux_source.clear(); beam.is_tracer = false; beam.hit = 20; beam.damage = mons_foe_is_mons(mons) ? dice_def(5, 7) : dice_def(3, 20); break; case SPELL_MINOR_HEALING: beam.flavour = BEAM_HEALING; beam.hit = 25 + (power / 5); break; case SPELL_TELEPORT_SELF: beam.flavour = BEAM_TELEPORT; break; case SPELL_TELEPORT_OTHER: beam.flavour = BEAM_TELEPORT; beam.is_beam = true; break; case SPELL_LEHUDIBS_CRYSTAL_SPEAR: // was splinters beam.name = "crystal spear"; beam.damage = dice_def( 3, 16 + power / 10 ); beam.colour = WHITE; beam.type = dchar_glyph(DCHAR_FIRED_MISSILE); beam.flavour = BEAM_MMISSILE; beam.hit = 22 + power / 20; break; case SPELL_DIG: beam.flavour = BEAM_DIGGING; beam.is_beam = true; break; case SPELL_BOLT_OF_DRAINING: // negative energy beam.name = "bolt of negative energy"; beam.damage = dice_def( 3, 6 + power / 13 ); beam.colour = DARKGREY; beam.flavour = BEAM_NEG; beam.hit = 16 + power / 35; beam.is_beam = true; break; case SPELL_ISKENDERUNS_MYSTIC_BLAST: // mystic blast beam.colour = LIGHTMAGENTA; beam.name = "orb of energy"; beam.short_name = "energy"; beam.damage = dice_def( 3, 7 + (power / 14) ); beam.hit = 20 + (power / 20); beam.flavour = BEAM_MMISSILE; break; case SPELL_STEAM_BALL: beam.colour = LIGHTGREY; beam.name = "ball of steam"; beam.damage = dice_def( 3, 7 + (power / 15) ); beam.hit = 20 + power / 20; beam.flavour = BEAM_STEAM; break; case SPELL_PAIN: beam.flavour = BEAM_PAIN; beam.damage = dice_def( 1, 7 + (power / 20) ); beam.ench_power = std::max(50, 8 * mons->hit_dice); beam.is_beam = true; break; case SPELL_STICKY_FLAME_SPLASH: case SPELL_STICKY_FLAME: beam.colour = RED; beam.name = "sticky flame"; beam.damage = dice_def( 3, 3 + power / 50 ); beam.hit = 18 + power / 15; beam.flavour = BEAM_FIRE; break; case SPELL_POISONOUS_CLOUD: beam.name = "blast of poison"; beam.damage = dice_def( 3, 3 + power / 25 ); beam.colour = LIGHTGREEN; beam.flavour = BEAM_POISON; beam.hit = 18 + power / 25; beam.is_beam = true; beam.is_big_cloud = true; break; case SPELL_ENERGY_BOLT: // eye of devastation beam.colour = YELLOW; beam.name = "bolt of energy"; beam.short_name = "energy"; beam.damage = dice_def( 3, 20 ); beam.hit = 15 + power / 30; beam.flavour = BEAM_NUKE; // a magical missile which destroys walls beam.is_beam = true; break; case SPELL_STING: // sting beam.colour = GREEN; beam.name = "sting"; beam.damage = dice_def( 1, 6 + power / 25 ); beam.hit = 60; beam.flavour = BEAM_POISON; break; case SPELL_IRON_SHOT: beam.colour = LIGHTCYAN; beam.name = "iron shot"; beam.damage = dice_def( 3, 8 + (power / 9) ); beam.hit = 20 + (power / 25); beam.type = dchar_glyph(DCHAR_FIRED_MISSILE); beam.flavour = BEAM_MMISSILE; // similarly unresisted thing break; case SPELL_STONE_ARROW: beam.colour = LIGHTGREY; beam.name = "stone arrow"; beam.damage = dice_def( 3, 5 + (power / 10) ); beam.hit = 14 + power / 35; beam.type = dchar_glyph(DCHAR_FIRED_MISSILE); beam.flavour = BEAM_MMISSILE; // similarly unresisted thing break; case SPELL_POISON_SPLASH: beam.colour = GREEN; beam.name = "splash of poison"; beam.damage = dice_def( 1, 4 + power / 10 ); beam.hit = 16 + power / 20; beam.flavour = BEAM_POISON; break; case SPELL_ACID_SPLASH: beam.colour = YELLOW; beam.name = "splash of acid"; beam.damage = dice_def( 3, 7 ); beam.hit = 20 + (3 * mons->hit_dice); beam.flavour = BEAM_ACID; break; case SPELL_DISINTEGRATE: beam.flavour = BEAM_DISINTEGRATION; beam.ench_power = 50; beam.damage = dice_def( 1, 30 + (power / 10) ); beam.is_beam = true; break; case SPELL_MEPHITIC_CLOUD: // swamp drake, player ghost beam.name = "foul vapour"; beam.damage = dice_def(1,0); beam.colour = GREEN; // Well, it works, even if the name isn't quite intuitive. beam.flavour = BEAM_POTION_STINKING_CLOUD; beam.hit = 14 + power / 30; beam.ench_power = power; // probably meaningless beam.is_explosion = true; beam.is_big_cloud = true; break; case SPELL_MIASMA: // death drake beam.name = "foul vapour"; beam.damage = dice_def( 3, 5 + power / 24 ); beam.colour = DARKGREY; beam.flavour = BEAM_MIASMA; beam.hit = 17 + power / 20; beam.is_beam = true; beam.is_big_cloud = true; break; case SPELL_QUICKSILVER_BOLT: // Quicksilver dragon beam.colour = random_colour(); beam.name = "bolt of energy"; beam.short_name = "energy"; beam.damage = dice_def( 3, 25 ); beam.hit = 16 + power / 25; beam.flavour = BEAM_MMISSILE; break; case SPELL_HELLFIRE: // fiend's hellfire beam.name = "blast of hellfire"; beam.aux_source = "blast of hellfire"; beam.colour = RED; beam.damage = dice_def( 3, 25 ); beam.hit = 24; beam.flavour = BEAM_HELLFIRE; beam.is_beam = true; beam.is_explosion = true; break; case SPELL_METAL_SPLINTERS: beam.name = "spray of metal splinters"; beam.short_name = "metal splinters"; beam.damage = dice_def( 3, 20 + power / 20 ); beam.colour = CYAN; beam.flavour = BEAM_FRAG; beam.hit = 19 + power / 30; beam.is_beam = true; break; case SPELL_BANISHMENT: beam.flavour = BEAM_BANISH; beam.is_beam = true; break; case SPELL_BLINK_OTHER: beam.flavour = BEAM_BLINK; beam.is_beam = true; break; case SPELL_BLINK_OTHER_CLOSE: beam.flavour = BEAM_BLINK_CLOSE; beam.is_beam = true; break; case SPELL_FIRE_BREATH: beam.name = "blast of flame"; beam.aux_source = "blast of fiery breath"; beam.damage = dice_def( 3, (mons->hit_dice * 2) ); beam.colour = RED; beam.hit = 30; beam.flavour = BEAM_FIRE; beam.is_beam = true; break; case SPELL_COLD_BREATH: beam.name = "blast of cold"; beam.aux_source = "blast of icy breath"; beam.short_name = "frost"; beam.damage = dice_def( 3, (mons->hit_dice * 2) ); beam.colour = WHITE; beam.hit = 30; beam.flavour = BEAM_COLD; beam.is_beam = true; break; case SPELL_DRACONIAN_BREATH: beam.damage = dice_def( 3, (mons->hit_dice * 2) ); beam.hit = 30; beam.is_beam = true; break; case SPELL_PORKALATOR: beam.name = "porkalator"; beam.type = 0; beam.flavour = BEAM_PORKALATOR; beam.thrower = KILL_MON_MISSILE; beam.is_beam = true; break; case SPELL_IOOD: // tracer only beam.flavour = BEAM_NUKE; beam.is_beam = true; break; default: if (check_validity) { beam.flavour = NUM_BEAMS; return (beam); } if (!is_valid_spell(real_spell)) DEBUGSTR("Invalid spell #%d cast by %s", (int) real_spell, mons->name(DESC_PLAIN, true).c_str()); DEBUGSTR("Unknown monster spell '%s' cast by %s", spell_title(real_spell), mons->name(DESC_PLAIN, true).c_str()); return (beam); } if (beam.is_enchantment()) { beam.type = dchar_glyph(DCHAR_SPACE); beam.name = "0"; } if (spell_cast == SPELL_DRACONIAN_BREATH) _scale_draconian_breath(beam, drac_type); // Accuracy is lowered by one quarter if the dragon is attacking // a target that is wielding a weapon of dragon slaying (which // makes the dragon/draconian avoid looking at the foe). // FIXME: This effect is not yet implemented for player draconians // or characters in dragon form breathing at monsters wielding a // weapon with this brand. if (is_dragonkind(mons)) { if (actor *foe = mons->get_foe()) { if (const item_def *weapon = foe->weapon()) { if (get_weapon_brand(*weapon) == SPWPN_DRAGON_SLAYING) { beam.hit *= 3; beam.hit /= 4; } } } } return (beam); } static bool _los_free_spell(spell_type spell_cast) { return (spell_cast == SPELL_HELLFIRE_BURST || spell_cast == SPELL_BRAIN_FEED || spell_cast == SPELL_SMITING || spell_cast == SPELL_HAUNT || spell_cast == SPELL_FIRE_STORM || spell_cast == SPELL_AIRSTRIKE || spell_cast == SPELL_MISLEAD); } // Set up bolt structure for monster spell casting. bool setup_mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast, bool check_validity) { // always set these -- used by things other than fire_beam() // [ds] Used to be 12 * MHD and later buggily forced to -1 downstairs. // Setting this to a more realistic number now that that bug is // squashed. pbolt.ench_power = 4 * monster->hit_dice; if (spell_cast == SPELL_TELEPORT_SELF) pbolt.ench_power = 2000; else if (spell_cast == SPELL_SLEEP) pbolt.ench_power = 6 * monster->hit_dice; pbolt.beam_source = monster->mindex(); // Convenience for the hapless innocent who assumes that this // damn function does all possible setup. [ds] if (pbolt.target.origin()) pbolt.target = monster->target; // Set bolt type and range. if (_los_free_spell(spell_cast)) { pbolt.range = 0; switch (spell_cast) { case SPELL_BRAIN_FEED: pbolt.type = DMNBM_BRAIN_FEED; return (true); case SPELL_MISLEAD: case SPELL_SMITING: case SPELL_AIRSTRIKE: pbolt.type = DMNBM_SMITING; return (true); default: // Other spells get normal setup: break; } } // The below are no-ops since they don't involve direct_effect, // fire_tracer, or beam. switch (spell_cast) { case SPELL_SUMMON_SMALL_MAMMALS: case SPELL_MAJOR_HEALING: case SPELL_VAMPIRE_SUMMON: case SPELL_SHADOW_CREATURES: // summon anything appropriate for level case SPELL_FAKE_RAKSHASA_SUMMON: case SPELL_FAKE_MARA_SUMMON: case SPELL_SUMMON_PLAYER_GHOST: case SPELL_SUMMON_RAKSHASA: case SPELL_SUMMON_DEMON: case SPELL_SUMMON_UGLY_THING: case SPELL_ANIMATE_DEAD: case SPELL_CALL_IMP: case SPELL_SUMMON_SCORPIONS: case SPELL_SUMMON_UFETUBUS: case SPELL_SUMMON_BEAST: // Geryon case SPELL_SUMMON_UNDEAD: // summon undead around player case SPELL_SUMMON_ICE_BEAST: case SPELL_SUMMON_MUSHROOMS: case SPELL_CONJURE_BALL_LIGHTNING: case SPELL_SUMMON_DRAKES: case SPELL_SUMMON_HORRIBLE_THINGS: case SPELL_HAUNT: case SPELL_SYMBOL_OF_TORMENT: case SPELL_SUMMON_GREATER_DEMON: case SPELL_CANTRIP: case SPELL_BERSERKER_RAGE: case SPELL_SWIFTNESS: case SPELL_WATER_ELEMENTALS: case SPELL_FIRE_ELEMENTALS: case SPELL_AIR_ELEMENTALS: case SPELL_EARTH_ELEMENTALS: case SPELL_KRAKEN_TENTACLES: case SPELL_BLINK: case SPELL_CONTROLLED_BLINK: case SPELL_BLINK_RANGE: case SPELL_BLINK_AWAY: case SPELL_BLINK_CLOSE: case SPELL_TOMB_OF_DOROKLOHE: case SPELL_CHAIN_LIGHTNING: // the only user is reckless case SPELL_SUMMON_EYEBALLS: case SPELL_SUMMON_BUTTERFLIES: case SPELL_MISLEAD: case SPELL_CALL_TIDE: return (true); default: if (check_validity) { bolt beam = mons_spells(monster, spell_cast, 1, true); return (beam.flavour != NUM_BEAMS); } break; } // Need to correct this for power of spellcaster int power = 12 * monster->hit_dice; bolt theBeam = mons_spells(monster, spell_cast, power); // [ds] remind me again why we're doing this piecemeal copying? pbolt.origin_spell = theBeam.origin_spell; pbolt.colour = theBeam.colour; pbolt.range = theBeam.range; pbolt.hit = theBeam.hit; pbolt.damage = theBeam.damage; if (theBeam.ench_power != -1) pbolt.ench_power = theBeam.ench_power; pbolt.type = theBeam.type; pbolt.flavour = theBeam.flavour; pbolt.thrower = theBeam.thrower; pbolt.name = theBeam.name; pbolt.short_name = theBeam.short_name; pbolt.is_beam = theBeam.is_beam; pbolt.source = monster->pos(); pbolt.is_tracer = false; pbolt.is_explosion = theBeam.is_explosion; pbolt.ex_size = theBeam.ex_size; pbolt.foe_ratio = theBeam.foe_ratio; if (!pbolt.is_enchantment()) pbolt.aux_source = pbolt.name; else pbolt.aux_source.clear(); if (spell_cast == SPELL_HASTE || spell_cast == SPELL_INVISIBILITY || spell_cast == SPELL_MINOR_HEALING || spell_cast == SPELL_TELEPORT_SELF) { pbolt.target = monster->pos(); } else if (spell_cast == SPELL_PORKALATOR && one_chance_in(3)) { monsters* targ = NULL; int count = 0; monster_type hog_type = MONS_HOG; for (monster_iterator mi(monster); mi; ++mi) { hog_type = MONS_HOG; if (mi->holiness() == MH_DEMONIC) hog_type = MONS_HELL_HOG; else if (mi->holiness() != MH_NATURAL) continue; if (mi->type != hog_type && mons_atts_aligned(monster->attitude, mi->attitude) && mons_power(hog_type) + random2(4) >= mons_power(mi->type) && (!mi->can_use_spells() || coinflip()) && one_chance_in(++count)) { targ = *mi; } } if (targ) { pbolt.target = targ->pos(); #if DEBUG_DIAGNOSTICS mprf("Porkalator: targetting %s instead", targ->name(DESC_PLAIN).c_str()); #endif monster_polymorph(targ, hog_type); } // else target remains as specified } return (true); } // Returns a suitable breath weapon for the draconian; does not handle all // draconians, does fire a tracer. static spell_type _get_draconian_breath_spell( monsters *monster ) { spell_type draco_breath = SPELL_NO_SPELL; if (mons_genus( monster->type ) == MONS_DRACONIAN) { switch (draco_subspecies( monster )) { case MONS_DRACONIAN: case MONS_YELLOW_DRACONIAN: // already handled as ability break; default: draco_breath = SPELL_DRACONIAN_BREATH; break; } } if (draco_breath != SPELL_NO_SPELL) { // [ds] Check line-of-fire here. It won't happen elsewhere. bolt beem; setup_mons_cast(monster, beem, draco_breath); fire_tracer(monster, beem); if (!mons_should_fire(beem)) draco_breath = SPELL_NO_SPELL; } return (draco_breath); } static bool _is_emergency_spell(const monster_spells &msp, int spell) { // If the emergency spell appears early, it's probably not a dedicated // escape spell. for (int i = 0; i < 5; ++i) if (msp[i] == spell) return (false); return (msp[5] == spell); } //--------------------------------------------------------------- // // handle_spell // // Give the monster a chance to cast a spell. Returns true if // a spell was cast. // //--------------------------------------------------------------- bool handle_mon_spell(monsters *monster, bolt &beem) { bool monsterNearby = mons_near(monster); bool finalAnswer = false; // as in: "Is that your...?" {dlb} const spell_type draco_breath = _get_draconian_breath_spell(monster); // A polymorphed unique will retain his or her spells even in another // form. If the new form has the SPELLCASTER flag, casting happens as // normally, otherwise we need to enforce it, but it only happens with // a 50% chance. const bool spellcasting_poly( !monster->can_use_spells() && mons_class_flag(monster->type, M_SPEAKS) && monster->has_spells()); if (is_sanctuary(monster->pos()) && !monster->wont_attack()) return (false); // Yes, there is a logic to this ordering {dlb}: if (monster->asleep() || monster->submerged() || (!monster->can_use_spells() && !spellcasting_poly && draco_breath == SPELL_NO_SPELL)) { return (false); } // If the monster's a priest, assume summons come from priestly // abilities, in which case they'll have the same god. If the // monster is neither a priest nor a wizard, assume summons come // from intrinsic abilities, in which case they'll also have the // same god. const bool priest = monster->is_priest(); const bool wizard = monster->is_actual_spellcaster(); god_type god = (priest || !(priest || wizard)) ? monster->god : GOD_NO_GOD; if (silenced(monster->pos()) && (priest || wizard || spellcasting_poly || mons_class_flag(monster->type, M_SPELL_NO_SILENT))) { return (false); } // Shapeshifters don't get spells. if (monster->is_shapeshifter() && (priest || wizard)) return (false); else if (monster->has_ench(ENCH_CONFUSION) && !mons_class_flag(monster->type, M_CONFUSED)) { return (false); } else if (monster->type == MONS_PANDEMONIUM_DEMON && !monster->ghost->spellcaster) { return (false); } else if (random2(200) > monster->hit_dice + 50 || monster->type == MONS_BALL_LIGHTNING && coinflip()) { return (false); } else if (spellcasting_poly && coinflip()) // 50% chance of not casting return (false); else { spell_type spell_cast = SPELL_NO_SPELL; monster_spells hspell_pass(monster->spells); // 1KB: the following code is never used for unfriendlies! if (!mon_enemies_around(monster)) { // Force the casting of dig when the player is not visible - // this is EVIL! if (monster->has_spell(SPELL_DIG) && mons_is_seeking(monster)) { spell_cast = SPELL_DIG; finalAnswer = true; } else if ((monster->has_spell(SPELL_MINOR_HEALING) || monster->has_spell(SPELL_MAJOR_HEALING)) && monster->hit_points < monster->max_hit_points) { // The player's out of sight! // Quick, let's take a turn to heal ourselves. -- bwr spell_cast = monster->has_spell(SPELL_MAJOR_HEALING) ? SPELL_MAJOR_HEALING : SPELL_MINOR_HEALING; finalAnswer = true; } else if (mons_is_fleeing(monster) || monster->pacified()) { // Since the player isn't around, we'll extend the monster's // normal choices to include the self-enchant slot. int foundcount = 0; for (int i = NUM_MONSTER_SPELL_SLOTS - 1; i >= 0; --i) { if (ms_useful_fleeing_out_of_sight(monster, hspell_pass[i]) && one_chance_in(++foundcount)) { spell_cast = hspell_pass[i]; finalAnswer = true; } } } else if (monster->foe == MHITYOU && !monsterNearby) return (false); } // Monsters caught in a net try to get away. // This is only urgent if enemies are around. if (!finalAnswer && mon_enemies_around(monster) && monster->caught() && one_chance_in(4)) { for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i) { if (ms_quick_get_away(monster, hspell_pass[i])) { spell_cast = hspell_pass[i]; finalAnswer = true; break; } } } // Promote the casting of useful spells for low-HP monsters. if (!finalAnswer && monster->hit_points < monster->max_hit_points / 4 && !one_chance_in(4)) { // Note: There should always be at least some chance we don't // get here... even if the monster is on its last HP. That // way we don't have to worry about monsters infinitely casting // Healing on themselves (e.g. orc high priests). if ((mons_is_fleeing(monster) || monster->pacified()) && ms_low_hitpoint_cast(monster, hspell_pass[5])) { spell_cast = hspell_pass[5]; finalAnswer = true; } if (!finalAnswer) { int found_spell = 0; for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i) { if (ms_low_hitpoint_cast(monster, hspell_pass[i]) && one_chance_in(++found_spell)) { spell_cast = hspell_pass[i]; finalAnswer = true; } } } } if (!finalAnswer) { // If nothing found by now, safe friendlies and good // neutrals will rarely cast. if (monster->wont_attack() && !mon_enemies_around(monster) && !one_chance_in(10)) { return (false); } // Remove healing/invis/haste if we don't need them. int num_no_spell = 0; for (int i = 0; i < NUM_MONSTER_SPELL_SLOTS; ++i) { if (hspell_pass[i] == SPELL_NO_SPELL) num_no_spell++; else if (ms_waste_of_time(monster, hspell_pass[i]) || hspell_pass[i] == SPELL_DIG) { // Should monster not have selected dig by now, // it never will. hspell_pass[i] = SPELL_NO_SPELL; num_no_spell++; } } // If no useful spells... cast no spell. if (num_no_spell == NUM_MONSTER_SPELL_SLOTS && draco_breath == SPELL_NO_SPELL) { return (false); } const bolt orig_beem = beem; // Up to four tries to pick a spell. for (int loopy = 0; loopy < 4; ++loopy) { beem = orig_beem; bool spellOK = false; // Setup spell - monsters that are fleeing or pacified // and leaving the level will always try to choose their // emergency spell. if (mons_is_fleeing(monster) || monster->pacified()) { spell_cast = (one_chance_in(5) ? SPELL_NO_SPELL : hspell_pass[5]); // Pacified monsters leaving the level won't choose // emergency spells harmful to the area. if (spell_cast != SPELL_NO_SPELL && monster->pacified() && spell_harms_area(spell_cast)) { spell_cast = SPELL_NO_SPELL; } } else { // Randomly picking one of the non-emergency spells: spell_cast = hspell_pass[random2(5)]; } if (spell_cast == SPELL_NO_SPELL) continue; // Setup the spell. setup_mons_cast(monster, beem, spell_cast); // Try to find a nearby ally to haste if (spell_cast == SPELL_HASTE_OTHER && !_set_allied_target(monster, beem)) { spell_cast = SPELL_NO_SPELL; continue; } // beam-type spells requiring tracers if (spell_needs_tracer(spell_cast)) { const bool explode = spell_is_direct_explosion(spell_cast); fire_tracer(monster, beem, explode); // Good idea? if (mons_should_fire(beem)) spellOK = true; } else { // All direct-effect/summoning/self-enchantments/etc. spellOK = true; if (ms_direct_nasty(spell_cast) && mons_aligned(monster->mindex(), monster->foe)) { spellOK = false; } else if (monster->foe == MHITYOU || monster->foe == MHITNOT) { // XXX: Note the crude hack so that monsters can // use ME_ALERT to target (we should really have // a measure of time instead of peeking to see // if the player is still there). -- bwr if (!you.visible_to(monster) && (monster->target != you.pos() || coinflip())) { spellOK = false; } } else if (!monster->can_see(&menv[monster->foe])) { spellOK = false; } else if (monster->type == MONS_DAEVA && monster->god == GOD_SHINING_ONE) { const monsters *mon = &menv[monster->foe]; // Don't allow TSO-worshipping daevas to make // unchivalric magic attacks, except against // appropriate monsters. if (is_unchivalric_attack(monster, mon) && !tso_unchivalric_attack_safe_monster(mon)) { spellOK = false; } } } // If not okay, then maybe we'll cast a defensive spell. if (!spellOK) { spell_cast = (coinflip() ? hspell_pass[2] : SPELL_NO_SPELL); } if (spell_cast != SPELL_NO_SPELL) break; } } // If there's otherwise no ranged attack use the breath weapon. // The breath weapon is also occasionally used. if (draco_breath != SPELL_NO_SPELL && (spell_cast == SPELL_NO_SPELL || !_is_emergency_spell(hspell_pass, spell_cast) && one_chance_in(4)) && !player_or_mon_in_sanct(monster)) { spell_cast = draco_breath; finalAnswer = true; } // Should the monster *still* not have a spell, well, too bad {dlb}: if (spell_cast == SPELL_NO_SPELL) return (false); // Friendly monsters don't use polymorph other, for fear of harming // the player. if (spell_cast == SPELL_POLYMORPH_OTHER && monster->friendly()) return (false); // Try to animate dead: if nothing rises, pretend we didn't cast it. if (spell_cast == SPELL_ANIMATE_DEAD && !animate_dead(monster, 100, SAME_ATTITUDE(monster), monster->foe, monster, "", god, false)) { return (false); } if (monster->type == MONS_BALL_LIGHTNING) monster->hit_points = -1; // FINALLY! determine primary spell effects {dlb}: if (spell_cast == SPELL_BLINK || spell_cast == SPELL_CONTROLLED_BLINK) { // Why only cast blink if nearby? {dlb} if (monsterNearby) { mons_cast_noise(monster, beem, spell_cast); monster_blink(monster); monster->lose_energy(EUT_SPELL); } else return (false); } else if (spell_cast == SPELL_BLINK_RANGE) blink_range(monster); else if (spell_cast == SPELL_BLINK_AWAY) blink_away(monster); else if (spell_cast == SPELL_BLINK_CLOSE) blink_close(monster); else { if (spell_needs_foe(spell_cast)) make_mons_stop_fleeing(monster); mons_cast(monster, beem, spell_cast); monster->lose_energy(EUT_SPELL); } } // end "if mons_class_flag(monster->type, M_SPELLCASTER) ... return (true); } static int _monster_abjure_square(const coord_def &pos, int pow, int actual, int wont_attack) { monsters *target = monster_at(pos); if (target == NULL) return (0); if (!target->alive() || ((bool)wont_attack == target->wont_attack())) { return (0); } int duration; if (!target->is_summoned(&duration)) return (0); pow = std::max(20, fuzz_value(pow, 40, 25)); if (!actual) return (pow > 40 || pow >= duration); // TSO and Trog's abjuration protection. bool shielded = false; if (you.religion == GOD_SHINING_ONE) { pow = pow * (30 - target->hit_dice) / 30; if (pow < duration) { simple_god_message(" protects your fellow warrior from evil " "magic!"); shielded = true; } } else if (you.religion == GOD_TROG) { pow = pow * 4 / 5; if (pow < duration) { simple_god_message(" shields your ally from puny magic!"); shielded = true; } } else if (is_sanctuary(target->pos())) { pow = 0; mpr("Zin's power protects your fellow warrior from evil magic!", MSGCH_GOD); shielded = true; } dprf("Abj: dur: %d, pow: %d, ndur: %d", duration, pow, duration - pow); mon_enchant abj = target->get_ench(ENCH_ABJ); if (!target->lose_ench_duration(abj, pow)) { if (!shielded) simple_monster_message(target, " shudders."); return (1); } return (0); } static int _apply_radius_around_square( const coord_def &c, int radius, int (*fn)(const coord_def &, int, int, int), int pow, int par1, int par2) { int res = 0; for (int yi = -radius; yi <= radius; ++yi) { const coord_def c1(c.x - radius, c.y + yi); const coord_def c2(c.x + radius, c.y + yi); if (in_bounds(c1)) res += fn(c1, pow, par1, par2); if (in_bounds(c2)) res += fn(c2, pow, par1, par2); } for (int xi = -radius + 1; xi < radius; ++xi) { const coord_def c1(c.x + xi, c.y - radius); const coord_def c2(c.x + xi, c.y + radius); if (in_bounds(c1)) res += fn(c1, pow, par1, par2); if (in_bounds(c2)) res += fn(c2, pow, par1, par2); } return (res); } static int _monster_abjuration(const monsters *caster, bool actual) { const bool wont_attack = caster->wont_attack(); int maffected = 0; if (actual) mpr("Send 'em back where they came from!"); int pow = std::min(caster->hit_dice * 90, 2500); // Abjure radius. for (int rad = 1; rad < 5 && pow >= 30; ++rad) { int number_hit = _apply_radius_around_square(caster->pos(), rad, _monster_abjure_square, pow, actual, wont_attack); maffected += number_hit; // Each affected monster drops power. // // We could further tune this by the actual amount of abjuration // damage done to each summon, but the player will probably never // notice. :-) while (number_hit-- > 0) pow = pow * 90 / 100; pow /= 2; } return (maffected); } static bool _mons_abjured(monsters *monster, bool nearby) { if (nearby && _monster_abjuration(monster, false) > 0 && coinflip()) { _monster_abjuration(monster, true); return (true); } return (false); } static monster_type _pick_random_wraith() { static monster_type wraiths[] = { MONS_WRAITH, MONS_SHADOW_WRAITH, MONS_FREEZING_WRAITH, MONS_SPECTRAL_WARRIOR, MONS_PHANTOM, MONS_HUNGRY_GHOST, MONS_FLAYED_GHOST }; return (RANDOM_ELEMENT(wraiths)); } static monster_type _pick_horrible_thing() { return (one_chance_in(4) ? MONS_TENTACLED_MONSTROSITY : MONS_ABOMINATION_LARGE); } static monster_type _pick_undead_summon() { static monster_type undead[] = { MONS_NECROPHAGE, MONS_GHOUL, MONS_HUNGRY_GHOST, MONS_FLAYED_GHOST, MONS_ZOMBIE_SMALL, MONS_SKELETON_SMALL, MONS_SIMULACRUM_SMALL, MONS_FLYING_SKULL, MONS_FLAMING_CORPSE, MONS_MUMMY, MONS_VAMPIRE, MONS_WIGHT, MONS_WRAITH, MONS_SHADOW_WRAITH, MONS_FREEZING_WRAITH, MONS_SPECTRAL_WARRIOR, MONS_ZOMBIE_LARGE, MONS_SKELETON_LARGE, MONS_SIMULACRUM_LARGE, MONS_SHADOW }; return (RANDOM_ELEMENT(undead)); } static void _do_high_level_summon(monsters *monster, bool monsterNearby, spell_type spell_cast, monster_type (*mpicker)(), int nsummons, god_type god, coord_def *target = NULL) { if (_mons_abjured(monster, monsterNearby)) return; const int duration = std::min(2 + monster->hit_dice / 5, 6); for (int i = 0; i < nsummons; ++i) { monster_type which_mons = mpicker(); if (which_mons == MONS_NO_MONSTER) continue; create_monster( mgen_data(which_mons, SAME_ATTITUDE(monster), monster, duration, spell_cast, target ? *target : monster->pos(), monster->foe, 0, god)); } } // Returns true if a message referring to the player's legs makes sense. static bool _legs_msg_applicable() { return (you.species != SP_NAGA && (you.species != SP_MERFOLK || !you.swimming())); } void mons_cast_haunt(monsters *monster) { coord_def fpos; switch (monster->foe) { case MHITNOT: return; case MHITYOU: fpos = you.pos(); break; default: fpos = menv[monster->foe].pos(); } _do_high_level_summon(monster, mons_near(monster), SPELL_HAUNT, _pick_random_wraith, random_range(3, 6), GOD_NO_GOD, &fpos); } void mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast, bool do_noise, bool special_ability) { // Always do setup. It might be done already, but it doesn't hurt // to do it again (cheap). setup_mons_cast(monster, pbolt, spell_cast); // single calculation permissible {dlb} bool monsterNearby = mons_near(monster); int sumcount = 0; int sumcount2; int duration = 0; #if DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Mon #%d casts %s (#%d)", monster->mindex(), spell_title(spell_cast), spell_cast); #endif if (spell_cast == SPELL_CANTRIP) do_noise = false; // Spell itself does the messaging. if (_los_free_spell(spell_cast) && !spell_is_direct_explosion(spell_cast)) { if (monster->foe == MHITYOU || monster->foe == MHITNOT) { if (monsterNearby) { if (do_noise) mons_cast_noise(monster, pbolt, spell_cast, special_ability); direct_effect(monster, spell_cast, pbolt, &you); } return; } if (do_noise) mons_cast_noise(monster, pbolt, spell_cast, special_ability); direct_effect(monster, spell_cast, pbolt, monster->get_foe()); return; } #ifdef DEBUG const unsigned int flags = get_spell_flags(spell_cast); ASSERT(!(flags & (SPFLAG_TESTING | SPFLAG_MAPPING))); // Targeted spells need a valid target. ASSERT(!(flags & SPFLAG_TARGETTING_MASK) || in_bounds(pbolt.target)); #endif if (do_noise) mons_cast_noise(monster, pbolt, spell_cast, special_ability); // If the monster's a priest, assume summons come from priestly // abilities, in which case they'll have the same god. If the // monster is neither a priest nor a wizard, assume summons come // from intrinsic abilities, in which case they'll also have the // same god. const bool priest = monster->is_priest(); const bool wizard = monster->is_actual_spellcaster(); god_type god = (priest || !(priest || wizard)) ? monster->god : GOD_NO_GOD; // Used for summon X elemental and nothing else. {bookofjude} monster_type summon_type = MONS_NO_MONSTER; switch (spell_cast) { default: break; case SPELL_MAJOR_HEALING: if (monster->heal(50 + random2avg(monster->hit_dice * 10, 2))) { simple_monster_message(monster, " is healed."); } return; case SPELL_BERSERKER_RAGE: monster->go_berserk(true); return; case SPELL_SWIFTNESS: monster->add_ench(ENCH_SWIFT); simple_monster_message(monster, " seems to move somewhat quicker."); return; case SPELL_CALL_TIDE: if (player_in_branch(BRANCH_SHOALS)) { const int tide_duration = random_range(18, 50, 2); monster->add_ench(mon_enchant(ENCH_TIDE, 0, KC_OTHER, tide_duration * 10)); monster->props[TIDE_CALL_TURN] = you.num_turns; if (simple_monster_message( monster, " sings a water chant to call the tide!")) { flash_view_delay(ETC_WATER, 300); } } return; case SPELL_SUMMON_SMALL_MAMMALS: case SPELL_VAMPIRE_SUMMON: if (spell_cast == SPELL_SUMMON_SMALL_MAMMALS) sumcount2 = 1 + random2(4); else sumcount2 = 3 + random2(3) + monster->hit_dice / 5; for (sumcount = 0; sumcount < sumcount2; ++sumcount) { monster_type rats[] = { MONS_ORANGE_RAT, MONS_GREEN_RAT, MONS_GREY_RAT, MONS_RAT }; if (spell_cast == SPELL_SUMMON_SMALL_MAMMALS) rats[0] = MONS_QUOKKA; const monster_type mon = (one_chance_in(3) ? MONS_GIANT_BAT : RANDOM_ELEMENT(rats)); create_monster( mgen_data(mon, SAME_ATTITUDE(monster), monster, 5, spell_cast, monster->pos(), monster->foe, 0, god)); } return; case SPELL_SHADOW_CREATURES: // summon anything appropriate for level if (_mons_abjured(monster, monsterNearby)) return; sumcount2 = 1 + random2(4) + random2(monster->hit_dice / 7 + 1); for (sumcount = 0; sumcount < sumcount2; ++sumcount) { create_monster( mgen_data(RANDOM_MONSTER, SAME_ATTITUDE(monster), monster, 5, spell_cast, monster->pos(), monster->foe, 0, god)); } return; case SPELL_WATER_ELEMENTALS: if (summon_type == MONS_NO_MONSTER) summon_type = MONS_WATER_ELEMENTAL; // Deliberate fall through case SPELL_EARTH_ELEMENTALS: if (summon_type == MONS_NO_MONSTER) summon_type = MONS_EARTH_ELEMENTAL; // Deliberate fall through case SPELL_AIR_ELEMENTALS: if (summon_type == MONS_NO_MONSTER) summon_type = MONS_AIR_ELEMENTAL; // Deliberate fall through case SPELL_FIRE_ELEMENTALS: if (summon_type == MONS_NO_MONSTER) summon_type = MONS_FIRE_ELEMENTAL; if (_mons_abjured(monster, monsterNearby)) return; sumcount2 = 1 + random2(4) + random2(monster->hit_dice / 7 + 1); for (sumcount = 0; sumcount < sumcount2; sumcount++) { create_monster( mgen_data(summon_type, SAME_ATTITUDE(monster), monster, 3, spell_cast, monster->pos(), monster->foe, 0, god)); } return; case SPELL_SUMMON_RAKSHASA: sumcount2 = 1 + random2(4) + random2(monster->hit_dice / 7 + 1); for (sumcount = 0; sumcount < sumcount2; sumcount++) { create_monster( mgen_data(MONS_RAKSHASA, SAME_ATTITUDE(monster), monster, 3, spell_cast, monster->pos(), monster->foe, 0, god)); } return; case SPELL_SUMMON_PLAYER_GHOST: // Do nothing in the arena; this could instead create a ghost of an // existant monster, but that would require the spell being dealth with // as a bolt instead. if (crawl_state.arena) return; mpr("There is a horrible, sudden wrenching feeling in your soul!", MSGCH_WARN); create_monster( mgen_data(MONS_PLAYER_GHOST, SAME_ATTITUDE(monster), monster, 6, spell_cast, monster->pos(), monster->foe, 0, god)); return; case SPELL_KRAKEN_TENTACLES: { int kraken_index = monster->mindex(); if (invalid_monster_index(duration)) { mpr("Error! Kraken is not a part of the current environment!", MSGCH_ERROR); return; } sumcount2 = std::max(random2(9), random2(9)); // up to eight tentacles if (sumcount2 == 0) return; for (sumcount = 0; sumcount < MAX_MONSTERS; ++sumcount) if (menv[sumcount].type == MONS_KRAKEN_TENTACLE && (int)menv[sumcount].number == kraken_index) { // Reduce by tentacles already placed. sumcount2--; } for (sumcount = sumcount2; sumcount > 0; --sumcount) { // Tentacles aren't really summoned (controlled by spell_cast // being passed to summon_type), so I'm not sure what the // abjuration value (3) is doing there. (jpeg) int tentacle = create_monster( mgen_data(MONS_KRAKEN_TENTACLE, SAME_ATTITUDE(monster), monster, 3, spell_cast, monster->pos(), monster->foe, 0, god, MONS_NO_MONSTER, kraken_index, monster->colour, you.your_level, PROX_CLOSE_TO_PLAYER, you.level_type)); if (tentacle < 0) { sumcount2--; } else if (monster->holiness() == MH_UNDEAD) { menv[tentacle].flags |= MF_HONORARY_UNDEAD; } } if (sumcount2 == 1) mpr("A tentacle rises from the water!"); else if (sumcount2 > 1) mpr("Tentacles burst out of the water!"); return; } case SPELL_FAKE_MARA_SUMMON: // We only want there to be two fakes, which, plus Mara, means // a total of three Maras; if we already have two, give up, otherwise // we want to summon either one more or two more. sumcount2 = 2 - count_mara_fakes(); for (sumcount = 0; sumcount < sumcount2; sumcount++) { mgen_data summ_mon = mgen_data(MONS_MARA_FAKE, SAME_ATTITUDE(monster), monster, 3, spell_cast, monster->pos(), monster->foe, 0, god); // This is somewhat hacky, to prevent "A Mara", and such, as MONS_FAKE_MARA // is not M_UNIQUE. summ_mon.mname = "Mara"; summ_mon.extra_flags |= MF_NAME_REPLACE; int created = create_monster(summ_mon); if (created == -1) continue; // Mara's clones are special; they have the same stats as him, and // are exact clones, so they are created damaged if necessary, with // identical enchants and with the same items. monsters *new_fake = &menv[created]; new_fake->hit_points = monster->hit_points; new_fake->max_hit_points = monster->max_hit_points; mon_enchant_list::iterator ei; for (ei = monster->enchantments.begin(); ei != monster->enchantments.end(); ++ei) { new_fake->enchantments.insert(*ei); } // Code basically lifted from clone_monster. In theory, it only needs // to copy weapon and armour slots; instead, copy the whole inventory. for (int i = 0; i < NUM_MONSTER_SLOTS; i++) { const int old_index = monster->inv[i]; if (old_index == NON_ITEM) continue; const int new_index = get_item_slot(0); if (new_index == NON_ITEM) { new_fake->unequip(mitm[old_index], i, 0, true); new_fake->inv[i] = NON_ITEM; continue; } new_fake->inv[i] = new_index; mitm[new_index] = mitm[old_index]; mitm[new_index].set_holding_monster(new_fake->mindex()); // Mark items as summoned, so there's no way to get three nice // weapons or such out of him. mitm[new_index].flags |= ISFLAG_SUMMONED; } } return; case SPELL_FAKE_RAKSHASA_SUMMON: sumcount2 = (coinflip() ? 2 : 3); for (sumcount = 0; sumcount < sumcount2; sumcount++) { create_monster( mgen_data(MONS_RAKSHASA_FAKE, SAME_ATTITUDE(monster), monster, 3, spell_cast, monster->pos(), monster->foe, 0, god)); } return; case SPELL_SUMMON_DEMON: // class 2-4 demons if (_mons_abjured(monster, monsterNearby)) return; sumcount2 = 1 + random2(2) + random2(monster->hit_dice / 10 + 1); duration = std::min(2 + monster->hit_dice / 10, 6); for (sumcount = 0; sumcount < sumcount2; sumcount++) { create_monster( mgen_data(summon_any_demon(DEMON_COMMON), SAME_ATTITUDE(monster), monster, duration, spell_cast, monster->pos(), monster->foe, 0, god)); } return; case SPELL_SUMMON_UGLY_THING: if (_mons_abjured(monster, monsterNearby)) return; sumcount2 = 1 + random2(2) + random2(monster->hit_dice / 10 + 1); duration = std::min(2 + monster->hit_dice / 10, 6); for (sumcount = 0; sumcount < sumcount2; ++sumcount) { const int chance = std::max(6 - (monster->hit_dice / 6), 1); monster_type mon = (one_chance_in(chance) ? MONS_VERY_UGLY_THING : MONS_UGLY_THING); create_monster( mgen_data(mon, SAME_ATTITUDE(monster), monster, duration, spell_cast, monster->pos(), monster->foe, 0, god)); } return; case SPELL_ANIMATE_DEAD: // see special handling in mon-stuff::handle_spell() {dlb} animate_dead(monster, 5 + random2(5), SAME_ATTITUDE(monster), monster->foe, monster, "", god); return; case SPELL_CALL_IMP: // class 5 demons sumcount2 = 1 + random2(3) + random2(monster->hit_dice / 5 + 1); duration = std::min(2 + monster->hit_dice / 5, 6); for (sumcount = 0; sumcount < sumcount2; ++sumcount) { create_monster( mgen_data(summon_any_demon(DEMON_LESSER), SAME_ATTITUDE(monster), monster, duration, spell_cast, monster->pos(), monster->foe, 0, god)); } return; case SPELL_SUMMON_SCORPIONS: if (_mons_abjured(monster, monsterNearby)) return; sumcount2 = 1 + random2(3) + random2(monster->hit_dice / 5 + 1); duration = std::min(2 + monster->hit_dice / 5, 6); for (sumcount = 0; sumcount < sumcount2; ++sumcount) { create_monster( mgen_data(MONS_SCORPION, SAME_ATTITUDE(monster), monster, duration, spell_cast, monster->pos(), monster->foe, 0, god)); } return; case SPELL_SUMMON_UFETUBUS: sumcount2 = 2 + random2(2) + random2(monster->hit_dice / 5 + 1); duration = std::min(2 + monster->hit_dice / 5, 6); for (sumcount = 0; sumcount < sumcount2; ++sumcount) { create_monster( mgen_data(MONS_UFETUBUS, SAME_ATTITUDE(monster), monster, duration, spell_cast, monster->pos(), monster->foe, 0, god)); } return; case SPELL_SUMMON_BEAST: // Geryon create_monster( mgen_data(MONS_BEAST, SAME_ATTITUDE(monster), monster, 4, spell_cast, monster->pos(), monster->foe, 0, god)); return; case SPELL_SUMMON_ICE_BEAST: create_monster( mgen_data(MONS_ICE_BEAST, SAME_ATTITUDE(monster), monster, 5, spell_cast, monster->pos(), monster->foe, 0, god)); return; case SPELL_SUMMON_MUSHROOMS: // Summon swarms of icky crawling fungi. if (_mons_abjured(monster, monsterNearby)) return; sumcount2 = 1 + random2(2) + random2(monster->hit_dice / 4 + 1); duration = std::min(2 + monster->hit_dice / 5, 6); for (int i = 0; i < sumcount2; ++i) { create_monster( mgen_data(MONS_WANDERING_MUSHROOM, SAME_ATTITUDE(monster), monster, duration, spell_cast, monster->pos(), monster->foe, 0, god)); } return; case SPELL_SUMMON_HORRIBLE_THINGS: _do_high_level_summon(monster, monsterNearby, spell_cast, _pick_horrible_thing, random_range(3, 5), god); return; case SPELL_CONJURE_BALL_LIGHTNING: { const int n = 2 + random2(monster->hit_dice / 4); for (int i = 0; i < n; ++i) { create_monster( mgen_data(MONS_BALL_LIGHTNING, SAME_ATTITUDE(monster), monster, 2, spell_cast, monster->pos(), monster->foe, 0, god)); } return; } case SPELL_SUMMON_UNDEAD: // Summon undead around player. _do_high_level_summon(monster, monsterNearby, spell_cast, _pick_undead_summon, 2 + random2(2) + random2(monster->hit_dice / 4 + 1), god); return; case SPELL_SYMBOL_OF_TORMENT: if (!monsterNearby || monster->friendly()) return; torment(monster->mindex(), monster->pos()); return; case SPELL_SUMMON_GREATER_DEMON: if (_mons_abjured(monster, monsterNearby)) return; sumcount2 = 1 + random2(monster->hit_dice / 10 + 1); duration = std::min(2 + monster->hit_dice / 10, 6); for (sumcount = 0; sumcount < sumcount2; ++sumcount) { create_monster( mgen_data(summon_any_demon(DEMON_GREATER), SAME_ATTITUDE(monster), monster, duration, spell_cast, monster->pos(), monster->foe, 0, god)); } return; // Journey -- Added in Summon Lizards and Draconians case SPELL_SUMMON_DRAKES: if (_mons_abjured(monster, monsterNearby)) return; sumcount2 = 1 + random2(3) + random2(monster->hit_dice / 5 + 1); duration = std::min(2 + monster->hit_dice / 10, 6); { std::vector monsters; for (sumcount = 0; sumcount < sumcount2; ++sumcount) { bool drag = false; monster_type mon = summon_any_dragon(DRAGON_LIZARD); if (mon == MONS_DRAGON) { drag = true; mon = summon_any_dragon(DRAGON_DRAGON); } monsters.push_back(mon); if (drag) break; } for (int i = 0, size = monsters.size(); i < size; ++i) { create_monster( mgen_data(monsters[i], SAME_ATTITUDE(monster), monster, duration, spell_cast, monster->pos(), monster->foe, 0, god)); } } return; // TODO: Outsource the cantrip messages and allow specification of // special cantrip spells per monster, like for speech, both as // "self buffs" and "player enchantments". case SPELL_CANTRIP: { // Monster spell of uselessness, just prints a message. // This spell exists so that some monsters with really strong // spells (ie orc priest) can be toned down a bit. -- bwr // // XXX: Needs expansion, and perhaps different priest/mage flavours. // Don't give any message if the monster isn't nearby. // (Otherwise you could get them from halfway across the level.) if (!mons_near(monster)) return; const bool friendly = monster->friendly(); const bool buff_only = !friendly && is_sanctuary(you.pos()); const msg_channel_type channel = (friendly) ? MSGCH_FRIEND_ENCHANT : MSGCH_MONSTER_ENCHANT; if (monster->type == MONS_GASTRONOK) { bool has_mon_foe = !invalid_monster_index(monster->foe); std::string slugform = ""; if (buff_only || crawl_state.arena && !has_mon_foe || friendly && !has_mon_foe || coinflip()) { slugform = getSpeakString("gastronok_self_buff"); if (!slugform.empty()) { slugform = replace_all(slugform, "@The_monster@", monster->name(DESC_CAP_THE)); mpr(slugform.c_str(), channel); } } else if (!friendly && !has_mon_foe) { mons_cast_noise(monster, pbolt, spell_cast); // "Enchant" the player. slugform = getSpeakString("gastronok_debuff"); if (!slugform.empty() && (slugform.find("legs") == std::string::npos || _legs_msg_applicable())) { mpr(slugform.c_str()); } } else { // "Enchant" another monster. const monsters* foe = dynamic_cast(monster->get_foe()); slugform = getSpeakString("gastronok_other_buff"); if (!slugform.empty()) { slugform = replace_all(slugform, "@The_monster@", foe->name(DESC_CAP_THE)); mpr(slugform.c_str(), MSGCH_MONSTER_ENCHANT); } } } else { // Messages about the monster influencing itself. const char* buff_msgs[] = { " glows brightly for a moment.", " looks stronger.", " becomes somewhat translucent.", "'s eyes start to glow." }; // Messages about the monster influencing you. const char* other_msgs[] = { "You feel troubled.", "You feel a wave of unholy energy pass over you." }; if (buff_only || crawl_state.arena || x_chance_in_y(2,3)) { simple_monster_message(monster, RANDOM_ELEMENT(buff_msgs), channel); } else if (friendly) { simple_monster_message(monster, " shimmers for a moment.", channel); } else // "Enchant" the player. { mons_cast_noise(monster, pbolt, spell_cast); mpr(RANDOM_ELEMENT(other_msgs)); } } return; } case SPELL_BLINK_OTHER: { // Allow the caster to comment on moving the foe. std::string msg = getSpeakString(monster->name(DESC_PLAIN) + " blink_other"); if (!msg.empty() && msg != "__NONE") { mons_speaks_msg(monster, msg, MSGCH_TALK, silenced(you.pos()) || silenced(monster->pos())); } break; } case SPELL_BLINK_OTHER_CLOSE: { // Allow the caster to comment on moving the foe. std::string msg = getSpeakString(monster->name(DESC_PLAIN) + " blink_other_close"); if (!msg.empty() && msg != "__NONE") { mons_speaks_msg(monster, msg, MSGCH_TALK, silenced(you.pos()) || silenced(monster->pos())); } break; } case SPELL_TOMB_OF_DOROKLOHE: { sumcount = 0; for (adjacent_iterator ai(monster->pos()); ai; ++ai) { // we can blink away the crowd, but only our allies if (monster_at(*ai) && monster_at(*ai)->attitude != monster->attitude) { sumcount++; } if (grd(*ai) != DNGN_FLOOR && grd(*ai) > DNGN_MAX_NONREACH && !feat_is_trap(grd(*ai))) { sumcount++; } } if (abs(you.pos().x - monster->pos().x) <= 1 && abs(you.pos().y - monster->pos().y) <= 1) { sumcount++; } if (sumcount) { monster->blink(); return; } sumcount = 0; for (adjacent_iterator ai(monster->pos()); ai; ++ai) { if (monster_at(*ai) && monster_at(*ai) != monster) { monster_at(*ai)->blink(); if (monster_at(*ai)) { monster_at(*ai)->teleport(true); if (monster_at(*ai)) continue; } } if (grd(*ai) == DNGN_FLOOR || feat_is_trap(grd(*ai))) { grd(*ai) = DNGN_ROCK_WALL; if (env.cgrid(*ai) != EMPTY_CLOUD) delete_cloud(env.cgrid(*ai)); sumcount++; } } if (sumcount) mpr("Walls emerge from the floor!"); monster->number = 1; // mark Khufu as entombed return; } case SPELL_CHAIN_LIGHTNING: if (!monsterNearby || monster->friendly()) return; cast_chain_lightning(4 * monster->hit_dice, monster); return; case SPELL_SUMMON_EYEBALLS: if (_mons_abjured(monster, monsterNearby)) return; sumcount2 = 1 + random2(2) + random2(monster->hit_dice / 7 + 1); duration = std::min(2 + monster->hit_dice / 10, 6); for (sumcount = 0; sumcount < sumcount2; sumcount++) { const monster_type mon = static_cast( random_choose_weighted(100, MONS_GIANT_EYEBALL, 80, MONS_EYE_OF_DRAINING, 60, MONS_GOLDEN_EYE, 40, MONS_SHINING_EYE, 20, MONS_GREAT_ORB_OF_EYES, 10, MONS_EYE_OF_DEVASTATION, 0)); create_monster( mgen_data(mon, SAME_ATTITUDE(monster), monster, duration, spell_cast, monster->pos(), monster->foe, 0, god)); } return; case SPELL_SUMMON_BUTTERFLIES: if (_mons_abjured(monster, monsterNearby)) return; duration = std::min(2 + monster->hit_dice / 5, 6); for (int i = 0; i < 15; ++i) { create_monster( mgen_data(MONS_BUTTERFLY, SAME_ATTITUDE(monster), monster, duration, spell_cast, monster->pos(), monster->foe, 0, god)); } return; case SPELL_IOOD: cast_iood(monster, 6 * monster->hit_dice, &pbolt); return; } // If a monster just came into view and immediately cast a spell, // we need to refresh the screen before drawing the beam. viewwindow(false); if (spell_is_direct_explosion(spell_cast)) { const actor *foe = monster->get_foe(); const bool need_more = foe && (foe == &you || you.see_cell(foe->pos())); pbolt.in_explosion_phase = false; pbolt.explode(need_more); } else pbolt.fire(); } void mons_cast_noise(monsters *monster, bolt &pbolt, spell_type spell_cast, bool special_ability) { bool force_silent = false; spell_type actual_spell = spell_cast; if (spell_cast == SPELL_DRACONIAN_BREATH) { int type = monster->type; if (mons_genus(type) == MONS_DRACONIAN) type = draco_subspecies(monster); switch (type) { case MONS_MOTTLED_DRACONIAN: actual_spell = SPELL_STICKY_FLAME_SPLASH; break; case MONS_YELLOW_DRACONIAN: actual_spell = SPELL_ACID_SPLASH; break; case MONS_PLAYER_GHOST: // Draining breath is silent. force_silent = true; break; default: break; } } else if (monster->type == MONS_SHADOW_DRAGON) // Draining breath is silent. force_silent = true; const bool unseen = !you.can_see(monster); const bool silent = silenced(monster->pos()) || force_silent; const bool no_silent = mons_class_flag(monster->type, M_SPELL_NO_SILENT); if (unseen && silent) return; const unsigned int flags = get_spell_flags(actual_spell); const bool priest = monster->is_priest(); const bool wizard = monster->is_actual_spellcaster(); const bool innate = !(priest || wizard || no_silent) || (flags & SPFLAG_INNATE) || special_ability; int noise; if (silent || (innate && !mons_class_flag(monster->type, M_NOISY_SPELLS) && !(flags & SPFLAG_NOISY) && mons_genus(monster->type) != MONS_DRAGON)) { noise = 0; } else { if (mons_genus(monster->type) == MONS_DRAGON) noise = get_shout_noise_level(S_ROAR); else { // Noise for targeted spells happens at where the spell hits, // rather than where the spell is cast. zappy() sets up the // noise for beams. noise = (flags & SPFLAG_TARGETTING_MASK) ? 1 : spell_noise(actual_spell); } } const std::string cast_str = " cast"; const std::string spell_name = spell_title(actual_spell); const mon_body_shape shape = get_mon_shape(monster); const bool real_spell = !innate && (priest || wizard); const bool visible_beam = pbolt.type != 0 && pbolt.type != ' ' && pbolt.name[0] != '0' && !pbolt.is_enchantment(); const bool targeted = (flags & SPFLAG_TARGETTING_MASK) && (pbolt.target != monster->pos() || visible_beam); std::vector key_list; // First try the spells name. if (shape <= MON_SHAPE_NAGA) { if (real_spell) key_list.push_back(spell_name + cast_str + " real"); if (mons_intel(monster) >= I_NORMAL) key_list.push_back(spell_name + cast_str + " gestures"); } else if (real_spell) { // A real spell being cast by something with no hands? Maybe // it's a polymorphed spellcaster which kept its original spells. // If so, the cast message for it's new type/species/genus probably // won't look right. if (!mons_class_flag(monster->type, M_ACTUAL_SPELLS | M_PRIEST)) { // XXX: We should probably include the monster's shape, // to get a variety of messages. if (wizard) { std::string key = "polymorphed wizard" + cast_str; if (targeted) key_list.push_back(key + " targeted"); key_list.push_back(key); } else if (priest) { std::string key = "polymorphed priest" + cast_str; if (targeted) key_list.push_back(key + " targeted"); key_list.push_back(key); } } } key_list.push_back(spell_name + cast_str); const unsigned int num_spell_keys = key_list.size(); // Next the monster type name, then species name, then genus name. key_list.push_back(mons_type_name(monster->type, DESC_PLAIN) + cast_str); key_list.push_back(mons_type_name(mons_species(monster->type), DESC_PLAIN) + cast_str); key_list.push_back(mons_type_name(mons_genus(monster->type), DESC_PLAIN) + cast_str); // Last, generic wizard, priest or demon. if (wizard) key_list.push_back((std::string)((shape <= MON_SHAPE_NAGA) ? "" : "non-humanoid ") + "wizard" + cast_str); else if (priest) key_list.push_back("priest" + cast_str); else if (mons_is_demon(monster->type)) key_list.push_back("demon" + cast_str); if (targeted) { // For targeted spells, try with the targeted suffix first. for (unsigned int i = key_list.size() - 1; i >= num_spell_keys; i--) { std::string str = key_list[i] + " targeted"; key_list.insert(key_list.begin() + i, str); } // Generic beam messages. if (visible_beam) { key_list.push_back(pbolt.get_short_name() + " beam " + cast_str); key_list.push_back("beam catchall cast"); } } std::string prefix; if (silent) prefix = "silent "; else if (unseen) prefix = "unseen "; std::string msg; for (unsigned int i = 0; i < key_list.size(); i++) { const std::string key = key_list[i]; msg = getSpeakString(prefix + key); if (msg == "__NONE") { msg = ""; break; } else if (msg == "__NEXT") { msg = ""; if (i < num_spell_keys) i = num_spell_keys - 1; else if (ends_with(key, " targeted")) i++; continue; } else if (!msg.empty()) break; // If we got no message and we're using the silent prefix, then // try again without the prefix. if (prefix != "silent") continue; msg = getSpeakString(key); if (msg == "__NONE") { msg = ""; break; } else if (msg == "__NEXT") { msg = ""; if (i < num_spell_keys) i = num_spell_keys - 1; else if (ends_with(key, " targeted")) i++; continue; } else if (!msg.empty()) break; } if (msg.empty()) { if (silent) return; noisy(noise, monster->pos(), monster->mindex()); return; } ///////////////////// // We have a message. ///////////////////// const bool gestured = msg.find("Gesture") != std::string::npos || msg.find(" gesture") != std::string::npos || msg.find("Point") != std::string::npos || msg.find(" point") != std::string::npos; bolt tracer = pbolt; if (targeted) { // For a targeted but rangeless spell make the range positive so that // fire_tracer() will fill out path_taken. if (pbolt.range == 0 && pbolt.target != monster->pos()) tracer.range = ENV_SHOW_DIAMETER; fire_tracer(monster, tracer); } std::string targ_prep = "at"; std::string target = "nothing"; if (!targeted) target = "NO TARGET"; else if (pbolt.target == you.pos()) target = "you"; else if (pbolt.target == monster->pos()) target = monster->pronoun(PRONOUN_REFLEXIVE); // Monsters should only use targeted spells while foe == MHITNOT // if they're targetting themselves. else if (monster->foe == MHITNOT && !monster->confused()) target = "NONEXISTENT FOE"; else if (!invalid_monster_index(monster->foe) && menv[monster->foe].type == MONS_NO_MONSTER) { target = "DEAD FOE"; } else if (in_bounds(pbolt.target) && you.see_cell(pbolt.target)) { if (const monsters* mtarg = monster_at(pbolt.target)) { if (you.can_see(mtarg)) target = mtarg->name(DESC_NOCAP_THE); } } // Monster might be aiming past the real target, or maybe some fuzz has // been applied because the target is invisible. if (target == "nothing" && targeted) { if (pbolt.aimed_at_spot) { int count = 0; for (adjacent_iterator ai(pbolt.target); ai; ++ai) { const actor* act = actor_at(*ai); if (act && act != monster && you.can_see(act)) { targ_prep = "next to"; if (act->atype() == ACT_PLAYER || one_chance_in(++count)) target = act->name(DESC_NOCAP_THE); if (act->atype() == ACT_PLAYER) break; } } } const bool visible_path = visible_beam || gestured; bool mons_targ_aligned = false; const std::vector &path = tracer.path_taken; for (unsigned int i = 0; i < path.size(); i++) { const coord_def pos = path[i]; if (pos == monster->pos()) continue; const monsters *m = monster_at(pos); if (pos == you.pos()) { // Be egotistical and assume that the monster is aiming at // the player, rather than the player being in the path of // a beam aimed at an ally. if (!monster->wont_attack()) { targ_prep = "at"; target = "you"; break; } // If the ally is confused or aiming at an invisible enemy, // with the player in the path, act like it's targeted at // the player if there isn't any visible target earlier // in the path. else if (target == "nothing") { targ_prep = "at"; target = "you"; mons_targ_aligned = true; } } else if (visible_path && m && you.can_see(m)) { bool is_aligned = mons_aligned(m->mindex(), monster->mindex()); std::string name = m->name(DESC_NOCAP_THE); if (target == "nothing") { mons_targ_aligned = is_aligned; target = name; } // If the first target was aligned with the beam source then // the first subsequent non-aligned monster in the path will // take it's place. else if (mons_targ_aligned && !is_aligned) { mons_targ_aligned = false; target = name; } targ_prep = "at"; } else if (visible_path && target == "nothing") { int count = 0; for (adjacent_iterator ai(pbolt.target); ai; ++ai) { const actor* act = monster_at(*ai); if (act && act != monster && you.can_see(act)) { targ_prep = "past"; if (act->atype() == ACT_PLAYER || one_chance_in(++count)) { target = act->name(DESC_NOCAP_THE); } if (act->atype() == ACT_PLAYER) break; } } } } // for (unsigned int i = 0; i < path.size(); i++) } // if (target == "nothing" && targeted) const actor* foe = monster->get_foe(); // If we still can't find what appears to be the target, and the // monster isn't just throwing the spell in a random direction, // we should be able to tell what the monster was aiming for if // we can see the monster's foe and the beam (or the beam path // implied by gesturing). But only if the beam didn't actually hit // anything (but if it did hit something, why didn't that monster // show up in the beam's path?) if (targeted && target == "nothing" && (tracer.foe_info.count + tracer.friend_info.count) == 0 && foe != NULL && you.can_see(foe) && !monster->confused() && (visible_beam || gestured)) { target = foe->name(DESC_NOCAP_THE); targ_prep = (pbolt.aimed_at_spot ? "next to" : "past"); } // If the monster gestures to create an invisible beam then // assume that anything close to the beam is the intended target. // Also, if the monster gestures to create a visible beam but it // misses still say that the monster gestured "at" the target, // rather than "past". if (gestured || target == "nothing") targ_prep = "at"; msg = replace_all(msg, "@at@", targ_prep); msg = replace_all(msg, "@target@", target); std::string beam_name; if (!targeted) beam_name = "NON TARGETED BEAM"; else if (pbolt.name.empty()) beam_name = "INVALID BEAM"; else if (!tracer.seen) beam_name = "UNSEEN BEAM"; else beam_name = pbolt.get_short_name(); msg = replace_all(msg, "@beam@", beam_name); const msg_channel_type chan = (unseen ? MSGCH_SOUND : monster->friendly() ? MSGCH_FRIEND_SPELL : MSGCH_MONSTER_SPELL); if (silent) mons_speaks_msg(monster, msg, chan, true); else if (noisy(noise, monster->pos(), monster->mindex()) || !unseen) { // noisy() returns true if the player heard the noise. mons_speaks_msg(monster, msg, chan); } }