/* * File: mstuff2.cc * Summary: Misc monster related functions. * Written by: Linley Henzell * * Modified for Crawl Reference by $Author$ on $Date$ */ #include "AppHdr.h" #include "mstuff2.h" #include #include #include #include #include "externs.h" #include "beam.h" #include "debug.h" #include "effects.h" #include "item_use.h" #include "itemname.h" #include "itemprop.h" #include "items.h" #include "message.h" #include "misc.h" #include "monplace.h" #include "monstuff.h" #include "mon-util.h" #include "player.h" #include "randart.h" #include "religion.h" #include "spells1.h" #include "spells3.h" #include "spells4.h" #include "spl-mis.h" #include "spl-util.h" #include "stuff.h" #include "traps.h" #include "view.h" static int _monster_abjuration(const monsters *caster, bool actual); 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 }; return wraiths[random2(sizeof(wraiths) / sizeof(*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 undead[random2(sizeof(undead) / sizeof(*undead))]; } static void _do_high_level_summon(monsters *monster, bool monsterNearby, monster_type (*mpicker)(), int nsummons) { 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_PROGRAM_BUG) continue; create_monster( mgen_data(which_mons, SAME_ATTITUDE(monster), duration, monster->pos(), monster->foe)); } } 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_FIRE_STORM || spell_cast == SPELL_AIRSTRIKE; } void mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast) { // 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_index(monster), spell_title(spell_cast), spell_cast); #endif if (_los_free_spell(spell_cast) && !spell_is_direct_explosion(spell_cast)) { if (monster->foe == MHITYOU || monster->foe == MHITNOT) { if (monsterNearby) direct_effect(monster, spell_cast, pbolt, &you); return; } direct_effect(monster, spell_cast, pbolt, monster->get_foe()); return; } switch (spell_cast) { default: break; case SPELL_GREATER_HEALING: if (heal_monster(monster, 50 + random2avg(monster->hit_dice * 10, 2), false)) { simple_monster_message(monster, " is healed."); } return; case SPELL_BERSERKER_RAGE: monster->go_berserk(true); 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 mon = MONS_GIANT_BAT; if (!one_chance_in(3)) { int temp_rand = random2(4); mon = (temp_rand == 0) ? MONS_ORANGE_RAT : (temp_rand == 1) ? MONS_GREEN_RAT : (temp_rand == 2) ? MONS_GREY_RAT : MONS_RAT; } create_monster( mgen_data(mon, SAME_ATTITUDE(monster), 5, monster->pos(), monster->foe)); } 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), 5, monster->pos(), monster->foe)); } 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), 3, monster->pos(), monster->foe)); } 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), duration, monster->pos(), monster->foe)); } 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), duration, monster->pos(), monster->foe)); } return; case SPELL_ANIMATE_DEAD: // see special handling in monstuff::handle_spell() {dlb} animate_dead(monster, 5 + random2(5), SAME_ATTITUDE(monster), monster->foe); 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), duration, monster->pos(), monster->foe)); } 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), duration, monster->pos(), monster->foe)); } 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), duration, monster->pos(), monster->foe)); } return; case SPELL_SUMMON_BEAST: // Geryon create_monster( mgen_data(MONS_BEAST, SAME_ATTITUDE(monster), 4, monster->pos(), monster->foe)); return; case SPELL_SUMMON_ICE_BEAST: create_monster( mgen_data(MONS_ICE_BEAST, SAME_ATTITUDE(monster), 5, monster->pos(), monster->foe)); 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), duration, monster->pos(), monster->foe)); } return; case SPELL_SUMMON_WRAITHS: _do_high_level_summon(monster, monsterNearby, _pick_random_wraith, random_range(3, 6)); return; case SPELL_SUMMON_HORRIBLE_THINGS: _do_high_level_summon(monster, monsterNearby, _pick_horrible_thing, random_range(3, 5)); 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), 2, monster->pos(), monster->foe)); } return; } case SPELL_SUMMON_UNDEAD: // Summon undead around player. _do_high_level_summon(monster, monsterNearby, _pick_undead_summon, 2 + random2(2) + random2(monster->hit_dice / 4 + 1)); return; case SPELL_SYMBOL_OF_TORMENT: if (!monsterNearby || mons_friendly(monster)) return; simple_monster_message(monster, " calls on the powers of Hell!"); torment(monster_index(monster), 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), duration, monster->pos(), monster->foe)); } return; // Journey -- Added in Summon Lizards and Draconian 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++) { monster_type mon = summon_any_dragon(DRAGON_LIZARD); if (mon == MONS_DRAGON) { monsters.clear(); monsters.push_back(summon_any_dragon(DRAGON_DRAGON)); break; } monsters.push_back(mon); } for (int i = 0, size = monsters.size(); i < size; ++i) { create_monster( mgen_data(monsters[i], SAME_ATTITUDE(monster), duration, monster->pos(), monster->foe)); } } return; case SPELL_CANTRIP: { const bool friendly = mons_friendly(monster); const bool buff_only = !friendly && is_sanctuary(you.pos()); bool need_friendly_stub = false; const msg_channel_type channel = (friendly) ? MSGCH_FRIEND_ENCHANT : MSGCH_MONSTER_ENCHANT; // 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. switch (random2(buff_only ? 4 : 7)) { case 0: simple_monster_message(monster, " glows brightly for a moment.", channel); break; case 1: simple_monster_message(monster, " looks stronger.", channel); break; case 2: simple_monster_message(monster, " becomes somewhat translucent.", channel); break; case 3: simple_monster_message(monster, "'s eyes start to glow.", channel); break; case 4: if (friendly) need_friendly_stub = true; else mpr("You feel troubled."); break; case 5: if (friendly) need_friendly_stub = true; else mpr("You feel a wave of unholy energy pass over you."); break; case 6: default: if (friendly) need_friendly_stub = true; else if (one_chance_in(20)) mpr("You resist (whatever that was supposed to do)."); else mpr("You resist."); break; } if (need_friendly_stub) { simple_monster_message(monster, " shimmers for a moment.", channel); } return; } } if (spell_is_direct_explosion(spell_cast)) { const actor *foe = monster->get_foe(); const bool need_more = foe && (foe == &you || see_grid(foe->pos())); explosion(pbolt, false, false, true, true, need_more); } else fire_beam(pbolt); } // Set up bolt structure for monster spell casting. void setup_mons_cast(monsters *monster, bolt &pbolt, spell_type spell_cast) { // 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; pbolt.beam_source = monster_index(monster); // 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 if (_los_free_spell(spell_cast)) { switch (spell_cast) { case SPELL_BRAIN_FEED: pbolt.type = DMNBM_BRAIN_FEED; return; case SPELL_SMITING: pbolt.type = DMNBM_SMITING; return; 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_GREATER_HEALING: case SPELL_VAMPIRE_SUMMON: case SPELL_SHADOW_CREATURES: // summon anything appropriate for level case SPELL_FAKE_RAKSHASA_SUMMON: 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_SUMMON_WRAITHS: case SPELL_SYMBOL_OF_TORMENT: case SPELL_SUMMON_GREATER_DEMON: case SPELL_CANTRIP: case SPELL_BERSERKER_RAGE: return; default: break; } // Need to correct this for power of spellcaster int power = 12 * monster->hit_dice; bolt theBeam = mons_spells(monster, spell_cast, power); 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.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_LESSER_HEALING || spell_cast == SPELL_TELEPORT_SELF) { pbolt.target = monster->pos(); } } void monster_random_space(const monsters *monster, coord_def& target, bool forbid_sanctuary) { while (true) { target.x = 10 + random2(GXM - 20); target.y = 10 + random2(GYM - 20); // Don't land on top of another monster. if (mgrd(target) != NON_MONSTER || target == you.pos()) continue; if (is_sanctuary(target) && forbid_sanctuary) continue; if (monster_habitable_grid(monster, grd(target))) break; } } void monster_random_space(monster_type mon, coord_def& target, bool forbid_sanctuary) { monsters dummy; dummy.type = mon; return monster_random_space(&dummy, target, forbid_sanctuary); } void monster_teleport(monsters *monster, bool instan, bool silent) { if (!instan) { if (monster->del_ench(ENCH_TP)) { if (!silent) simple_monster_message(monster, " seems more stable."); } else { if (!silent) simple_monster_message(monster, " looks slightly unstable."); monster->add_ench( mon_enchant(ENCH_TP, 0, KC_OTHER, random_range(20, 30)) ); } return; } bool was_seen = player_monster_visible(monster) && mons_near(monster) && !mons_is_lurking(monster); if (!silent) simple_monster_message(monster, " disappears!"); const coord_def oldplace = monster->pos(); // Pick the monster up. mgrd(oldplace) = NON_MONSTER; mons_clear_trapping_net(monster); coord_def newpos; monster_random_space(monster, newpos, !mons_wont_attack(monster)); monster->moveto(newpos); mgrd(monster->pos()) = monster_index(monster); // Mimics change form/colour when teleported. if (mons_is_mimic( monster->type )) { int old_type = monster->type; monster->type = MONS_GOLD_MIMIC + random2(5); monster->colour = get_mimic_colour( monster ); // If it's changed form, you won't recognize it. // This assumes that a non-gold mimic turning into another item of // the same description is really, really unlikely. if (old_type != MONS_GOLD_MIMIC || monster->type != MONS_GOLD_MIMIC) was_seen = false; } const bool now_visible = mons_near(monster); if (!silent && now_visible) { if (was_seen) simple_monster_message(monster, " reappears nearby!"); else simple_monster_message(monster, " appears out of thin air!", MSGCH_PLAIN, 0, DESC_CAP_A); } if (player_monster_visible(monster) && now_visible) seen_monster(monster); monster->check_redraw(oldplace); monster->apply_location_effects(oldplace); // Teleporting mimics change form - if they reappear out of LOS, they are // no longer known. if (mons_is_mimic(monster->type)) { if (now_visible) monster->flags |= MF_KNOWN_MIMIC; else monster->flags &= ~MF_KNOWN_MIMIC; } } // end monster_teleport() void setup_dragon(struct monsters *monster, struct bolt &pbolt) { const int type = (mons_genus( monster->type ) == MONS_DRACONIAN) ? draco_subspecies( monster ) : monster->type; int scaling = 100; pbolt.name.clear(); switch (type) { case MONS_FIREDRAKE: case MONS_HELL_HOUND: case MONS_DRAGON: case MONS_LINDWURM: case MONS_XTAHUA: pbolt.name += "blast of flame"; pbolt.aux_source = "blast of fiery breath"; pbolt.flavour = BEAM_FIRE; pbolt.colour = RED; pbolt.range = 6; break; case MONS_ICE_DRAGON: pbolt.name += "blast of cold"; pbolt.aux_source = "blast of icy breath"; pbolt.flavour = BEAM_COLD; pbolt.colour = WHITE; pbolt.range = 7; break; case MONS_RED_DRACONIAN: pbolt.name += "searing blast"; pbolt.aux_source = "blast of searing breath"; pbolt.flavour = BEAM_FIRE; pbolt.colour = RED; pbolt.range = 6; scaling = 65; break; case MONS_WHITE_DRACONIAN: pbolt.name += "chilling blast"; pbolt.aux_source = "blast of chilling breath"; pbolt.flavour = BEAM_COLD; pbolt.colour = WHITE; pbolt.range = 7; scaling = 65; break; case MONS_PLAYER_GHOST: // draconians only pbolt.name += "blast of negative energy"; pbolt.aux_source = "blast of draining breath"; pbolt.flavour = BEAM_NEG; pbolt.colour = DARKGREY; pbolt.range = LOS_RADIUS; scaling = 65; break; default: DEBUGSTR("Bad monster class in setup_dragon()"); break; } #ifdef DEBUG_DIAGNOSTICS mprf( MSGCH_DIAGNOSTICS, "bolt name: '%s'", pbolt.name.c_str() ); #endif pbolt.damage = dice_def( 3, (monster->hit_dice * 2) ); pbolt.damage.size = scaling * pbolt.damage.size / 100; pbolt.type = dchar_glyph(DCHAR_FIRED_ZAP); pbolt.hit = 30; pbolt.beam_source = monster_index(monster); pbolt.thrower = KILL_MON; pbolt.is_beam = true; // 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 (mons_genus(monster->type) == MONS_DRAGON || mons_genus(monster->type) == MONS_DRACONIAN) { if (actor *foe = monster->get_foe()) { if (const item_def *weapon = foe->weapon()) { if (get_weapon_brand(*weapon) == SPWPN_DRAGON_SLAYING) { pbolt.hit *= 3; pbolt.hit /= 4; } } } } } void setup_generic_throw(struct monsters *monster, struct bolt &pbolt) { // FIXME we should use a sensible range here pbolt.range = LOS_RADIUS; pbolt.beam_source = monster_index(monster); pbolt.type = dchar_glyph(DCHAR_FIRED_MISSILE); pbolt.flavour = BEAM_MISSILE; pbolt.thrower = KILL_MON_MISSILE; pbolt.aux_source.clear(); pbolt.is_beam = false; } bool mons_throw(struct monsters *monster, struct bolt &pbolt, int hand_used) { bool returning = (get_weapon_brand(mitm[hand_used]) == SPWPN_RETURNING || get_ammo_brand(mitm[hand_used]) == SPMSL_RETURNING); int baseHit = 0, baseDam = 0; // from thrown or ammo int ammoHitBonus = 0, ammoDamBonus = 0; // from thrown or ammo int lnchHitBonus = 0, lnchDamBonus = 0; // special add from launcher int exHitBonus = 0, exDamBonus = 0; // 'extra' bonus from skill/dex/str int lnchBaseDam = 0; int hitMult = 0; int damMult = 0; int diceMult = 100; // Some initial convenience & initializations. int wepClass = mitm[hand_used].base_type; int wepType = mitm[hand_used].sub_type; int weapon = monster->inv[MSLOT_WEAPON]; int lnchType = (weapon != NON_ITEM) ? mitm[weapon].sub_type : 0; const bool skilled = mons_class_flag(monster->type, M_FIGHTER); monster->lose_energy(EUT_MISSILE); const int throw_energy = monster->action_energy(EUT_MISSILE); // Dropping item copy, since the launched item might be different. item_def item = mitm[hand_used]; item.quantity = 1; if (mons_friendly(monster)) item.flags |= ISFLAG_DROPPED_BY_ALLY; // FIXME we should actually determine a sensible range here pbolt.range = LOS_RADIUS; pbolt.beam_source = monster_index(monster); pbolt.type = dchar_glyph(DCHAR_FIRED_MISSILE); pbolt.colour = item.colour; pbolt.flavour = BEAM_MISSILE; pbolt.thrower = KILL_MON_MISSILE; pbolt.aux_source.clear(); const launch_retval projected = is_launched(monster, monster->mslot_item(MSLOT_WEAPON), mitm[hand_used]); // extract launcher bonuses due to magic if (projected == LRET_LAUNCHED) { lnchHitBonus = mitm[weapon].plus; lnchDamBonus = mitm[weapon].plus2; lnchBaseDam = property(mitm[weapon], PWPN_DAMAGE); } // extract weapon/ammo bonuses due to magic ammoHitBonus = item.plus; ammoDamBonus = item.plus2; // Archers get a boost from their melee attack. if (mons_class_flag(monster->type, M_ARCHER)) { const mon_attack_def attk = mons_attack_spec(monster, 0); if (attk.type == AT_SHOOT) ammoDamBonus += random2avg(attk.damage, 2); } if (projected == LRET_THROWN) { // Darts are easy. if (wepClass == OBJ_MISSILES && wepType == MI_DART) { baseHit = 11; hitMult = 40; damMult = 25; } else { baseHit = 6; hitMult = 30; damMult = 25; } baseDam = property(item, PWPN_DAMAGE); if (wepClass == OBJ_MISSILES) // throw missile { // ammo damage needs adjusting here - OBJ_MISSILES // don't get separate tohit/damage bonuses! ammoDamBonus = ammoHitBonus; // [dshaligram] Thrown stones/darts do only half the damage of // launched stones/darts. This matches 4.0 behaviour. if (wepType == MI_DART || wepType == MI_STONE || wepType == MI_SLING_BULLET) { baseDam = div_rand_round(baseDam, 2); } } // give monster "skill" bonuses based on HD exHitBonus = (hitMult * monster->hit_dice) / 10 + 1; exDamBonus = (damMult * monster->hit_dice) / 10 + 1; } // Monsters no longer gain unfair advantages with weapons of // fire/ice and incorrect ammo. They now have the same restrictions // as players. int bow_brand = SPWPN_NORMAL; const int ammo_brand = get_ammo_brand(item); const bool poison = (ammo_brand == SPMSL_POISONED); if (projected == LRET_LAUNCHED) { bow_brand = get_weapon_brand(mitm[monster->inv[MSLOT_WEAPON]]); switch (lnchType) { case WPN_BLOWGUN: baseHit = 12; hitMult = 60; damMult = 0; lnchDamBonus = 0; break; case WPN_BOW: case WPN_LONGBOW: baseHit = 0; hitMult = 60; damMult = 35; // monsters get half the launcher damage bonus, // which is about as fair as I can figure it. lnchDamBonus = (lnchDamBonus + 1) / 2; break; case WPN_CROSSBOW: baseHit = 4; hitMult = 70; damMult = 30; break; case WPN_HAND_CROSSBOW: baseHit = 2; hitMult = 50; damMult = 20; break; case WPN_SLING: baseHit = 10; hitMult = 40; damMult = 20; // monsters get half the launcher damage bonus, // which is about as fair as I can figure it. lnchDamBonus /= 2; break; } // Launcher is now more important than ammo for base damage. baseDam = property(item, PWPN_DAMAGE); if (lnchBaseDam) baseDam = lnchBaseDam + random2(1 + baseDam); // missiles don't have pluses2; use hit bonus ammoDamBonus = ammoHitBonus; exHitBonus = (hitMult * monster->hit_dice) / 10 + 1; exDamBonus = (damMult * monster->hit_dice) / 10 + 1; if (!baseDam && elemental_missile_beam(bow_brand, ammo_brand)) baseDam = 4; // [dshaligram] This is a horrible hack - we force beam.cc to // consider this beam "needle-like". if (wepClass == OBJ_MISSILES && wepType == MI_NEEDLE) pbolt.ench_power = AUTOMATIC_HIT; // elven bow w/ elven arrow, also orcish if (get_equip_race(mitm[monster->inv[MSLOT_WEAPON]]) == get_equip_race(mitm[monster->inv[MSLOT_MISSILE]])) { baseHit++; baseDam++; if (get_equip_race(mitm[monster->inv[MSLOT_WEAPON]]) == ISFLAG_ELVEN) pbolt.hit++; } // POISON brand launchers poison ammo if (bow_brand == SPWPN_VENOM && ammo_brand == SPMSL_NORMAL) set_item_ego_type(item, OBJ_MISSILES, SPMSL_POISONED); // Vorpal brand increases damage dice size. if (bow_brand == SPWPN_VORPAL) diceMult = diceMult * 130 / 100; // Note: we already have throw_energy taken off. -- bwr int speed_delta = 0; if (lnchType == WPN_CROSSBOW) { if (bow_brand == SPWPN_SPEED) { // Speed crossbows take 50% less time to use than // ordinary crossbows. speed_delta = div_rand_round(throw_energy * 2, 5); } else { // Ordinary crossbows take 20% more time to use // than ordinary bows. speed_delta = -div_rand_round(throw_energy, 5); } } else if (bow_brand == SPWPN_SPEED) { // Speed bows take 50% less time to use than // ordinary bows. speed_delta = div_rand_round(throw_energy, 2); } monster->speed_increment += speed_delta; } // WEAPON or AMMO of FIRE if (bow_brand == SPWPN_FLAME && ammo_brand != SPMSL_ICE || ammo_brand == SPMSL_FLAME && bow_brand != SPWPN_FROST) { baseHit += 2; exDamBonus += 6; pbolt.flavour = BEAM_FIRE; pbolt.name = "bolt of "; if (poison) pbolt.name += "poison "; pbolt.name += "flame"; pbolt.colour = RED; pbolt.type = dchar_glyph(DCHAR_FIRED_ZAP); } // WEAPON or AMMO of FROST else if (bow_brand == SPWPN_FROST && ammo_brand != SPMSL_FLAME || ammo_brand == SPMSL_ICE && bow_brand != SPWPN_FLAME) { baseHit += 2; exDamBonus += 6; pbolt.flavour = BEAM_COLD; pbolt.name = "bolt of "; if (poison) pbolt.name += "poison "; pbolt.name += "frost"; pbolt.colour = WHITE; pbolt.type = dchar_glyph(DCHAR_FIRED_ZAP); } // monster intelligence bonus if (mons_intel(monster) == I_HIGH) exHitBonus += 10; // Now, if a monster is, for some reason, throwing something really // stupid, it will have baseHit of 0 and damage of 0. Ah well. std::string msg = monster->name(DESC_CAP_THE); msg += ((projected == LRET_LAUNCHED) ? " shoots " : " throws "); if (!pbolt.name.empty() && projected == LRET_LAUNCHED) { msg += "a "; msg += pbolt.name; } else { // build shoot message msg += item.name(DESC_NOCAP_A); // build beam name pbolt.name = item.name(DESC_PLAIN, false, false, false); } msg += "."; if (monster->visible()) { mpr(msg.c_str()); if (projected == LRET_LAUNCHED && item_type_known(mitm[monster->inv[MSLOT_WEAPON]]) || projected == LRET_THROWN && mitm[hand_used].base_type == OBJ_MISSILES) { set_ident_flags(mitm[hand_used], ISFLAG_KNOW_TYPE); } } // [dshaligram] When changing bolt names here, you must edit // hiscores.cc (scorefile_entry::terse_missile_cause()) to match. char throw_buff[ITEMNAME_SIZE]; if (projected == LRET_LAUNCHED) { snprintf(throw_buff, sizeof(throw_buff), "Shot with a%s %s by %s", (is_vowel(pbolt.name[0]) ? "n" : ""), pbolt.name.c_str(), monster->name(DESC_NOCAP_A).c_str()); } else { snprintf(throw_buff, sizeof(throw_buff), "Hit by a%s %s thrown by %s", (is_vowel(pbolt.name[0]) ? "n" : ""), pbolt.name.c_str(), monster->name(DESC_NOCAP_A).c_str()); } pbolt.aux_source = throw_buff; // Add everything up. pbolt.hit = baseHit + random2avg(exHitBonus, 2) + ammoHitBonus; pbolt.damage = dice_def(1, baseDam + random2avg(exDamBonus, 2) + ammoDamBonus); if (projected == LRET_LAUNCHED) { pbolt.damage.size += lnchDamBonus; pbolt.hit += lnchHitBonus; } pbolt.damage.size = diceMult * pbolt.damage.size / 100; if (monster->has_ench(ENCH_BATTLE_FRENZY)) { const mon_enchant ench = monster->get_ench(ENCH_BATTLE_FRENZY); #ifdef DEBUG_DIAGNOSTICS const dice_def orig_damage = pbolt.damage; #endif pbolt.damage.size = pbolt.damage.size * (115 + ench.degree * 15) / 100; #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "%s frenzy damage: %dd%d -> %dd%d", monster->name(DESC_PLAIN).c_str(), orig_damage.num, orig_damage.size, pbolt.damage.num, pbolt.damage.size); #endif } // Skilled archers get better to-hit and damage. if (skilled) { pbolt.hit = pbolt.hit * 120 / 100; pbolt.damage.size = pbolt.damage.size * 120 / 100; } scale_dice(pbolt.damage); // decrease inventory bool really_returns; if (returning && !one_chance_in(mons_power(monster->type) + 3)) really_returns = true; else really_returns = false; fire_beam( pbolt, &item, !really_returns ); // The item can be destroyed before returning. if (really_returns && mons_thrown_object_destroyed(&item, pbolt.target, true, pbolt.beam_source)) { really_returns = false; } if (really_returns) { // Fire beam in reverse. pbolt.setup_retrace(); viewwindow(true, false); fire_beam(pbolt, &item, false); msg::stream << "The weapon returns " << (player_monster_visible(monster)? ("to " + monster->name(DESC_NOCAP_THE)) : "whence it came from") << "!" << std::endl; // Player saw the item return. if (!is_artefact(item)) { // Since this only happens for non-artefacts, also mark properties // as known. set_ident_flags(mitm[hand_used], ISFLAG_KNOW_TYPE | ISFLAG_KNOW_PROPERTIES); } } else if (dec_mitm_item_quantity(hand_used, 1)) monster->inv[returning ? hand_used : MSLOT_MISSILE] = NON_ITEM; return (true); } bool mons_thrown_object_destroyed( item_def *item, const coord_def& where, bool returning, int midx ) { ASSERT( item != NULL ); bool destroyed = (item->base_type == OBJ_MISSILES && item->sub_type != MI_THROWING_NET && coinflip()); bool hostile_grid = grid_destroys_items(grd(where)); // Non-returning items thrown into item-destroying grids are always // destroyed. Returning items are only destroyed if they would have // been randomly destroyed anyway. if (returning && !destroyed) hostile_grid = false; if (hostile_grid) { // No destruction sound here. Too much message spam otherwise. item_was_destroyed(*item, midx); destroyed = true; } return destroyed; } // XXX: Should really do something about mons_hit, but can't be bothered. void spore_goes_pop(monsters *monster) { bolt beam; const int type = monster->type; if (monster == NULL) return; beam.is_tracer = false; beam.is_explosion = true; beam.beam_source = monster_index(monster); beam.type = dchar_glyph(DCHAR_FIRED_BURST); beam.target = monster->pos(); beam.thrower = KILL_MON; // someone else's explosion beam.aux_source.clear(); const char* msg = NULL; const char* sanct_msg = NULL; if (type == MONS_GIANT_SPORE) { beam.flavour = BEAM_SPORE; beam.name = "explosion of spores"; beam.colour = LIGHTGREY; beam.damage = dice_def( 3, 15 ); beam.ex_size = 2; msg = "The giant spore explodes!"; sanct_msg = "By Zin's power, the giant spore's explosion is contained."; } else if (type == MONS_BALL_LIGHTNING) { beam.flavour = BEAM_ELECTRICITY; beam.name = "blast of lightning"; beam.colour = LIGHTCYAN; beam.damage = dice_def( 3, 20 ); beam.ex_size = coinflip() ? 3 : 2; msg = "The ball lightning explodes!"; sanct_msg = "By Zin's power, the ball lightning's explosion " "is contained."; } else { msg::streams(MSGCH_DIAGNOSTICS) << "Unknown spore type: " << static_cast(type) << std::endl; return; } if (you.can_see(monster)) { viewwindow(true, false); if (is_sanctuary(monster->pos())) mpr(sanct_msg, MSGCH_GOD); else mpr(msg); } if (is_sanctuary(monster->pos())) return; explosion(beam, false, false, true, true, mons_near(monster)); } bolt mons_spells( monsters *mons, spell_type spell_cast, int power ) { ASSERT(power > 0); bolt beam; // Initialize 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; beam.range = spell_range(spell_cast, power, true); switch (spell_cast) { case SPELL_MAGIC_DART: beam.colour = LIGHTMAGENTA; // inv_colour [throw_2]; beam.name = "magic dart"; // inv_name [throw_2]); beam.damage = dice_def( 3, 4 + (power / 100) ); beam.hit = AUTOMATIC_HIT; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON_MISSILE; beam.flavour = BEAM_MMISSILE; beam.is_beam = false; break; case SPELL_THROW_FLAME: beam.colour = RED; beam.name = "puff of flame"; // should this be the same as magic missile? // No... magic missile is special in that it has a really // high to-hit value, so these should do more damage -- bwr beam.damage = dice_def( 3, 5 + (power / 40) ); beam.hit = 25 + power / 40; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON_MISSILE; beam.flavour = BEAM_FIRE; beam.is_beam = false; break; case SPELL_THROW_FROST: beam.colour = WHITE; beam.name = "puff of frost"; // should this be the same as magic missile? // see SPELL_FLAME -- bwr beam.damage = dice_def( 3, 5 + (power / 40) ); beam.hit = 25 + power / 40; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON_MISSILE; beam.flavour = BEAM_COLD; beam.is_beam = false; break; case SPELL_DISPEL_UNDEAD: beam.name = "0"; beam.flavour = BEAM_DISPEL_UNDEAD; beam.thrower = KILL_MON_MISSILE; beam.damage = dice_def( 3, std::min(6 + power / 10, 40) ); beam.is_beam = true; break; case SPELL_PARALYSE: beam.name = "0"; beam.type = 0; beam.flavour = BEAM_PARALYSIS; beam.thrower = KILL_MON_MISSILE; beam.is_beam = true; break; case SPELL_SLOW: beam.name = "0"; beam.type = 0; beam.flavour = BEAM_SLOW; beam.thrower = KILL_MON_MISSILE; beam.is_beam = true; break; case SPELL_HASTE: // (self) beam.name = "0"; beam.type = 0; beam.flavour = BEAM_HASTE; beam.thrower = KILL_MON_MISSILE; break; case SPELL_BACKLIGHT: beam.name = "0"; beam.type = 0; beam.flavour = BEAM_BACKLIGHT; beam.thrower = KILL_MON_MISSILE; beam.is_beam = true; break; case SPELL_CONFUSE: beam.name = "0"; beam.type = 0; beam.flavour = BEAM_CONFUSION; beam.thrower = KILL_MON_MISSILE; beam.is_beam = true; break; case SPELL_SLEEP: beam.name = "0"; beam.type = 0; beam.flavour = BEAM_SLEEP; beam.thrower = KILL_MON_MISSILE; beam.is_beam = true; break; case SPELL_POLYMORPH_OTHER: beam.name = "0"; beam.type = 0; beam.flavour = BEAM_POLYMORPH; beam.thrower = KILL_MON_MISSILE; beam.is_beam = true; break; case SPELL_VENOM_BOLT: beam.name = "bolt of poison"; beam.damage = dice_def( 3, 6 + power / 13 ); beam.colour = LIGHTGREEN; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON; 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.thrower = KILL_MON; 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.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON; 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.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON; beam.flavour = BEAM_FIRE; beam.hit = 17 + power / 25; beam.is_beam = true; break; case SPELL_ICE_BOLT: beam.name = "bolt of ice"; beam.damage = dice_def( 3, 8 + power / 11 ); beam.colour = WHITE; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON; beam.flavour = BEAM_ICE; beam.hit = 17 + power / 25; beam.is_beam = true; break; case SPELL_BOLT_OF_COLD: beam.name = "bolt of cold"; beam.damage = dice_def( 3, 8 + power / 11 ); beam.colour = WHITE; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON; beam.flavour = BEAM_COLD; beam.hit = 17 + power / 25; beam.is_beam = true; break; case SPELL_FREEZING_CLOUD: beam.name = "freezing blast"; beam.damage = dice_def( 2, 9 + power / 11 ); beam.colour = WHITE; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON; 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.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON; 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.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON; beam.flavour = BEAM_ELECTRICITY; beam.hit = 16 + power / 40; beam.is_beam = true; break; case SPELL_INVISIBILITY: beam.name = "0"; beam.type = 0; beam.flavour = BEAM_INVISIBILITY; beam.thrower = KILL_MON; break; case SPELL_FIREBALL: beam.colour = RED; beam.name = "fireball"; beam.damage = dice_def( 3, 7 + power / 10 ); beam.hit = 40; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON; beam.flavour = BEAM_FIRE; beam.is_beam = false; 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.type = dchar_glyph(DCHAR_FIRED_ZAP); 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 = "hellfire"; beam.ex_size = 1; beam.flavour = BEAM_HELLFIRE; beam.is_explosion = true; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.colour = RED; beam.thrower = KILL_MON; beam.aux_source.clear(); beam.is_beam = false; 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_LESSER_HEALING: beam.name = "0"; beam.type = 0; beam.flavour = BEAM_HEALING; beam.thrower = KILL_MON; beam.hit = 25 + (power / 5); break; case SPELL_TELEPORT_SELF: beam.name = "0"; beam.type = 0; beam.flavour = BEAM_TELEPORT; // 6 is used by digging beam.thrower = KILL_MON; break; case SPELL_TELEPORT_OTHER: beam.name = "0"; beam.type = 0; beam.flavour = BEAM_TELEPORT; // 6 is used by digging beam.thrower = KILL_MON; beam.is_beam = true; break; case SPELL_BLINK: beam.is_beam = false; 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.thrower = KILL_MON; beam.flavour = BEAM_MMISSILE; beam.hit = 22 + power / 20; beam.is_beam = false; break; case SPELL_DIG: beam.name = "0"; beam.type = 0; beam.flavour = BEAM_DIGGING; beam.thrower = KILL_MON; 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.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON; 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.damage = dice_def( 3, 7 + (power / 14) ); beam.hit = 20 + (power / 20); beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON_MISSILE; beam.flavour = BEAM_MMISSILE; beam.is_beam = false; 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.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON_MISSILE; beam.flavour = BEAM_STEAM; beam.is_beam = false; break; case SPELL_PAIN: beam.name = "0"; beam.type = 0; beam.flavour = BEAM_PAIN; // pain beam.thrower = KILL_MON; 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: beam.colour = RED; beam.name = "sticky flame"; beam.damage = dice_def( 3, 3 + power / 50 ); beam.hit = 18 + power / 15; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON_MISSILE; beam.flavour = BEAM_FIRE; beam.is_beam = false; break; case SPELL_POISONOUS_CLOUD: // demon beam.name = "blast of poison"; beam.damage = dice_def( 3, 3 + power / 25 ); beam.colour = LIGHTGREEN; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON; 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.damage = dice_def( 3, 20 ); beam.hit = 15 + power / 30; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON_MISSILE; 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.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON_MISSILE; beam.flavour = BEAM_POISON; beam.is_beam = false; break; case SPELL_BOLT_OF_IRON: beam.colour = LIGHTCYAN; beam.name = "iron bolt"; beam.damage = dice_def( 3, 8 + (power / 9) ); beam.hit = 20 + (power / 25); beam.type = dchar_glyph(DCHAR_FIRED_MISSILE); beam.thrower = KILL_MON_MISSILE; beam.flavour = BEAM_MMISSILE; // similarly unresisted thing beam.is_beam = false; 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.thrower = KILL_MON_MISSILE; beam.flavour = BEAM_MMISSILE; // similarly unresisted thing beam.is_beam = false; 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.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON_MISSILE; beam.flavour = BEAM_POISON; beam.is_beam = false; break; case SPELL_DISINTEGRATE: beam.name = "0"; beam.type = 0; beam.flavour = BEAM_DISINTEGRATION; beam.thrower = KILL_MON; beam.ench_power = 50; // beam.hit = 30 + (power / 10); beam.damage = dice_def( 1, 30 + (power / 10) ); beam.is_beam = true; break; case SPELL_MEPHITIC_CLOUD: // swamp drake beam.name = "foul vapour"; beam.damage = dice_def( 3, 2 + power / 25 ); beam.colour = GREEN; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON; beam.flavour = BEAM_POISON; beam.hit = 14 + power / 30; beam.is_beam = 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.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON; 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.damage = dice_def( 3, 25 ); beam.hit = 16 + power / 25; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON_MISSILE; beam.flavour = BEAM_MMISSILE; beam.is_beam = false; break; case SPELL_HELLFIRE: // fiend's hellfire beam.name = "hellfire"; beam.aux_source = "blast of hellfire"; beam.colour = RED; beam.damage = dice_def( 3, 25 ); beam.hit = 24; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON; beam.flavour = BEAM_HELLFIRE; beam.is_beam = true; beam.is_explosion = true; break; case SPELL_METAL_SPLINTERS: beam.name = "spray of metal splinters"; beam.damage = dice_def( 3, 20 + power / 20 ); beam.colour = CYAN; beam.type = dchar_glyph(DCHAR_FIRED_ZAP); beam.thrower = KILL_MON; beam.flavour = BEAM_FRAG; beam.hit = 19 + power / 30; beam.is_beam = true; break; case SPELL_BANISHMENT: beam.name = "0"; beam.type = 0; beam.flavour = BEAM_BANISH; beam.thrower = KILL_MON_MISSILE; beam.is_beam = true; break; case SPELL_BLINK_OTHER: beam.name = "0"; beam.type = 0; beam.flavour = BEAM_BLINK; beam.thrower = KILL_MON; beam.is_beam = true; break; default: DEBUGSTR("Unknown spell"); } return (beam); } // end mons_spells() static int _monster_abjure_square(const coord_def &pos, int pow, int actual, int wont_attack) { const int mindex = mgrd(pos); if (mindex == NON_MONSTER) return (0); monsters *target = &menv[mindex]; if (!target->alive() || ((bool)wont_attack == mons_wont_attack(target))) return (0); mon_enchant abj = target->get_ench(ENCH_ABJ); if (abj.ench == ENCH_NONE) return (0); pow = std::max(20, fuzz_value(pow, 40, 25)); if (!actual) return (pow > 40 || pow >= abj.duration); // TSO and Trog's abjuration protection. if (you.religion == GOD_SHINING_ONE) { pow = pow * (30 - target->hit_dice) / 30; if (pow < abj.duration) simple_god_message(" protects your fellow warrior from evil magic!"); } else if (you.religion == GOD_TROG) { pow = pow * 4 / 5; if (pow < abj.duration) simple_god_message(" shields your ally from puny magic!"); } else if (is_sanctuary(target->pos())) { mpr("Zin's power protects your fellow warrior from evil magic!", MSGCH_GOD); return (0); } #ifdef DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "Abj: dur: %d, pow: %d, ndur: %d", abj.duration, pow, abj.duration - pow); #endif if (!target->lose_ench_duration(abj, pow)) simple_monster_message(target, " shudders."); return (1); } 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 = mons_wont_attack(caster); 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); } bool silver_statue_effects(monsters *mons) { if ((mons_player_visible(mons) || one_chance_in(3)) && !one_chance_in(3)) { const std::string msg = "'s eyes glow " + weird_glowing_colour() + '.'; simple_monster_message(mons, msg.c_str(), MSGCH_WARN); create_monster( mgen_data( summon_any_demon((coinflip() ? DEMON_COMMON : DEMON_LESSER)), BEH_HOSTILE, 5, you.pos(), MHITYOU)); return (true); } return (false); } bool orange_statue_effects(monsters *mons) { if ((mons_player_visible(mons) || one_chance_in(3)) && !one_chance_in(3)) { mpr("A hostile presence attacks your mind!", MSGCH_WARN); MiscastEffect( &you, monster_index(mons), SPTYP_DIVINATION, random2(15), random2(150), "an orange crystal statue"); return (true); } return (false); } bool orc_battle_cry(monsters *chief) { const actor *foe = chief->get_foe(); int affected = 0; if (foe && (foe != &you || !mons_friendly(chief)) && !silenced(chief->pos()) && chief->can_see(foe) && coinflip()) { const int boss_index = monster_index(chief); const int level = chief->hit_dice > 12? 2 : 1; std::vector seen_affected; for (int i = 0; i < MAX_MONSTERS; ++i) { monsters *mon = &menv[i]; if (mon != chief && mon->alive() && mons_species(mon->type) == MONS_ORC && mons_aligned(boss_index, i) && mon->hit_dice < chief->hit_dice && !mon->has_ench(ENCH_BERSERK) && !mon->cannot_move() && !mon->confused() && chief->can_see(mon)) { mon_enchant ench = mon->get_ench(ENCH_BATTLE_FRENZY); if (ench.ench == ENCH_NONE || ench.degree < level) { const int dur = random_range(12, 20) * speed_to_duration(mon->speed); if (ench.ench != ENCH_NONE) { ench.degree = level; ench.duration = std::max(ench.duration, dur); mon->update_ench(ench); } else { mon->add_ench(mon_enchant(ENCH_BATTLE_FRENZY, level, KC_OTHER, dur)); } affected++; if (you.can_see(mon)) seen_affected.push_back(mon); if (mon->asleep()) behaviour_event(mon, ME_DISTURB, MHITNOT, chief->pos()); } } } if (affected) { if (you.can_see(chief) && player_can_hear(chief->pos())) { mprf(MSGCH_SOUND, "%s roars a battle-cry!", chief->name(DESC_CAP_THE).c_str()); } // The yell happens whether you happen to see it or not. noisy(15, chief->pos()); // Disabling detailed frenzy announcement because it's so spammy. const msg_channel_type channel = mons_friendly(chief) ? MSGCH_MONSTER_ENCHANT : MSGCH_FRIEND_ENCHANT; if (!seen_affected.empty()) { std::string who; if (seen_affected.size() == 1) { who = seen_affected[0]->name(DESC_CAP_THE); mprf(channel, "%s goes into a battle-frenzy!", who.c_str()); } else { int type = seen_affected[0]->type; for (unsigned int i = 0; i < seen_affected.size(); i++) { if (seen_affected[i]->type != type) { // just mention plain orcs type = MONS_ORC; break; } } who = get_monster_data(type)->name; mprf(channel, "%s %s go into a battle-frenzy!", mons_friendly(chief) ? "Your" : "The", pluralise(who).c_str()); } } } } // Orc battle cry doesn't cost the monster an action. return (false); } static bool _make_monster_angry(const monsters *mon, monsters *targ) { if (mons_friendly(mon) != mons_friendly(targ)) return (false); // targ is guaranteed to have a foe (needs_berserk checks this). // Now targ needs to be closer to *its* foe than mon is (otherwise // mon might be in the way). coord_def victim; if (targ->foe == MHITYOU) victim = you.pos(); else if (targ->foe != MHITNOT) { const monsters *vmons = &menv[targ->foe]; if (!vmons->alive()) return (false); victim = vmons->pos(); } else { // Should be impossible. needs_berserk should find this case. ASSERT(false); return (false); } // If mon may be blocking targ from its victim, don't try. if (victim.distance_from(targ->pos()) > victim.distance_from(mon->pos())) return (false); if (mons_near(mon) && player_monster_visible(mon)) { mprf("%s goads %s on!", mon->name(DESC_CAP_THE).c_str(), targ->name(DESC_NOCAP_THE).c_str()); } targ->go_berserk(false); return (true); } bool moth_incite_monsters(const monsters *mon) { if (is_sanctuary(you.pos()) || is_sanctuary(mon->pos())) return false; int goaded = 0; for (int i = 0; i < MAX_MONSTERS; ++i) { monsters *targ = &menv[i]; if (targ == mon || !targ->alive() || !targ->needs_berserk()) continue; if (mon->pos().distance_from(targ->pos()) > 3) continue; if (is_sanctuary(targ->pos())) continue; // Cannot goad other moths of wrath! if (targ->type == MONS_MOTH_OF_WRATH) continue; if (_make_monster_angry(mon, targ) && !one_chance_in(3 * ++goaded)) return (true); } return (false); } void mons_clear_trapping_net(monsters *mon) { if (!mons_is_caught(mon)) return; const int net = get_trapping_net(mon->pos()); if (net != NON_ITEM) remove_item_stationary(mitm[net]); mon->del_ench(ENCH_HELD, true); }