/*
* File: beam.cc
* Summary: Functions related to ranged attacks.
* Written by: Linley Henzell
*/
#include "AppHdr.h"
#include "beam.h"
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cstdarg>
#include <iostream>
#include <set>
#include <algorithm>
#include <cmath>
#include "externs.h"
#include "options.h"
#include "areas.h"
#include "attitude-change.h"
#include "cio.h"
#include "cloud.h"
#include "colour.h"
#include "coord.h"
#include "coordit.h"
#include "delay.h"
#include "dungeon.h"
#include "dgnevent.h"
#include "effects.h"
#include "env.h"
#include "enum.h"
#include "godabil.h"
#include "map_knowledge.h"
#include "fprop.h"
#include "fight.h"
#include "item_use.h"
#include "it_use2.h"
#include "items.h"
#include "itemname.h"
#include "itemprop.h"
#include "los.h"
#include "message.h"
#include "misc.h"
#include "mon-behv.h"
#include "mon-iter.h"
#include "mon-place.h"
#include "mgen_data.h"
#include "mon-stuff.h"
#include "mon-util.h"
#include "mutation.h"
#include "ouch.h"
#include "player.h"
#include "religion.h"
#include "skills.h"
#include "spells1.h"
#include "spells3.h"
#include "spells4.h"
#include "state.h"
#include "stuff.h"
#include "teleport.h"
#include "terrain.h"
#include "transform.h"
#include "traps.h"
#include "view.h"
#include "shout.h"
#include "viewchar.h"
#include "viewgeom.h"
#include "xom.h"
#include "tiles.h"
#define BEAM_STOP 1000 // all beams stopped by subtracting this
// from remaining range
// Helper functions (some of these should probably be public).
static void _ench_animation(int flavour, const monsters *mon = NULL,
bool force = false);
static void _zappy(zap_type z_type, int power, bolt &pbolt);
static beam_type _chaos_beam_flavour();
tracer_info::tracer_info()
{
reset();
}
void tracer_info::reset()
{
count = power = hurt = helped = 0;
dont_stop = false;
}
const tracer_info& tracer_info::operator+=(const tracer_info &other)
{
count += other.count;
power += other.power;
hurt += other.hurt;
helped += other.helped;
dont_stop = dont_stop || other.dont_stop;
return (*this);
}
bool bolt::is_blockable() const
{
// BEAM_ELECTRICITY is added here because chain lightning is not
// a true beam (stops at the first target it gets to and redirects
// from there)... but we don't want it shield blockable.
return (!is_beam && !is_explosion && flavour != BEAM_ELECTRICITY);
}
void bolt::emit_message(msg_channel_type chan, const char* m)
{
const std::string message = m;
if (message_cache.find(message) == message_cache.end())
mpr(m, chan);
message_cache.insert(message);
}
kill_category bolt::whose_kill() const
{
if (YOU_KILL(thrower))
return (KC_YOU);
else if (MON_KILL(thrower))
{
if (beam_source == ANON_FRIENDLY_MONSTER)
return (KC_FRIENDLY);
if (!invalid_monster_index(beam_source))
{
const monsters *mon = &menv[beam_source];
if (mon->friendly())
return (KC_FRIENDLY);
}
}
return (KC_OTHER);
}
// A simple animated flash from Rupert Smith (expanded to be more
// generic).
static void _zap_animation(int colour, const monsters *mon = NULL,
bool force = false)
{
coord_def p = you.pos();
if (mon)
{
if (!force && !mon->visible_to(&you))
return;
p = mon->pos();
}
if (!you.see_cell(p))
return;
const coord_def drawp = grid2view(p);
if (in_los_bounds(drawp))
{
// Default to whatever colour magic is today.
if (colour == -1)
colour = ETC_MAGIC;
#ifdef USE_TILE
tiles.add_overlay(p, tileidx_zap(colour));
#else
view_update();
cgotoxy(drawp.x, drawp.y, GOTO_DNGN);
put_colour_ch(colour, dchar_glyph(DCHAR_FIRED_ZAP));
#endif
update_screen();
int zap_delay = 50;
// Scale delay to match change in arena_delay.
if (crawl_state.arena)
{
zap_delay *= Options.arena_delay;
zap_delay /= 600;
}
delay(zap_delay);
}
}
// Special front function for zap_animation to interpret enchantment flavours.
static void _ench_animation(int flavour, const monsters *mon, bool force)
{
element_type elem;
switch (flavour)
{
case BEAM_HEALING:
elem = ETC_HEAL;
break;
case BEAM_PAIN:
elem = ETC_UNHOLY;
break;
case BEAM_DISPEL_UNDEAD:
elem = ETC_HOLY;
break;
case BEAM_POLYMORPH:
elem = ETC_MUTAGENIC;
break;
case BEAM_CHAOS:
elem = ETC_RANDOM;
break;
case BEAM_TELEPORT:
case BEAM_BANISH:
case BEAM_BLINK:
case BEAM_BLINK_CLOSE:
elem = ETC_WARP;
break;
default:
elem = ETC_ENCHANT;
break;
}
_zap_animation(element_colour(elem), mon, force);
}
// If needs_tracer is true, we need to check the beam path for friendly
// monsters.
bool zapping(zap_type ztype, int power, bolt &pbolt,
bool needs_tracer, const char* msg)
{
dprf("zapping: power=%d", power);
pbolt.thrower = KILL_YOU_MISSILE;
// Check whether tracer goes through friendlies.
// NOTE: Whenever zapping() is called with a randomised value for power
// (or effect), player_tracer should be called directly with the highest
// power possible respecting current skill, experience level, etc.
if (needs_tracer && !player_tracer(ztype, power, pbolt))
return (false);
// Fill in the bolt structure.
_zappy(ztype, power, pbolt);
if (msg)
mpr(msg);
if (ztype == ZAP_LIGHTNING)
{
noisy(25, you.pos(), "You hear a mighty clap of thunder!");
pbolt.heard = true;
}
if (ztype == ZAP_DIGGING)
pbolt.aimed_at_spot = false;
pbolt.fire();
return (true);
}
// Returns true if the path is considered "safe", and false if there are
// monsters in the way the player doesn't want to hit.
// NOTE: Doesn't check for the player being hit by a rebounding lightning bolt.
bool player_tracer( zap_type ztype, int power, bolt &pbolt, int range)
{
// Non-controlleable during confusion.
// (We'll shoot in a different direction anyway.)
if (you.confused())
return (true);
_zappy(ztype, power, pbolt);
pbolt.name = "unimportant";
pbolt.is_tracer = true;
pbolt.source = you.pos();
pbolt.can_see_invis = you.can_see_invisible();
pbolt.smart_monster = true;
pbolt.attitude = ATT_FRIENDLY;
pbolt.thrower = KILL_YOU_MISSILE;
// Init tracer variables.
pbolt.friend_info.reset();
pbolt.foe_info.reset();
pbolt.foe_ratio = 100;
pbolt.beam_cancelled = false;
pbolt.dont_stop_player = false;
// Clear misc
pbolt.seen = false;
pbolt.heard = false;
pbolt.reflections = 0;
pbolt.bounces = 0;
// Save range before overriding it
const int old_range = pbolt.range;
if (range)
pbolt.range = range;
pbolt.fire();
if (range)
pbolt.range = old_range;
// Should only happen if the player answered 'n' to one of those
// "Fire through friendly?" prompts.
if (pbolt.beam_cancelled)
{
dprf("%s", "Beam cancelled.");
canned_msg(MSG_OK);
you.turn_is_over = false;
return (false);
}
// Set to non-tracing for actual firing.
pbolt.is_tracer = false;
return (true);
}
template<typename T>
struct power_deducer
{
virtual T operator()(int pow) const = 0;
virtual ~power_deducer() {}
};
typedef power_deducer<int> tohit_deducer;
template<int adder, int mult_num = 0, int mult_denom = 1>
struct tohit_calculator : public tohit_deducer
{
int operator()(int pow) const
{
return adder + (pow * mult_num) / mult_denom;
}
};
typedef power_deducer<dice_def> dam_deducer;
template<int numdice, int adder, int mult_num, int mult_denom>
struct dicedef_calculator : public dam_deducer
{
dice_def operator()(int pow) const
{
return dice_def(numdice, adder + (pow * mult_num) / mult_denom);
}
};
template<int numdice, int adder, int mult_num, int mult_denom>
struct calcdice_calculator : public dam_deducer
{
dice_def operator()(int pow) const
{
return calc_dice(numdice, adder + (pow * mult_num) / mult_denom);
}
};
struct zap_info
{
zap_type ztype;
const char* name; // NULL means handled specially
int power_cap;
dam_deducer* damage;
tohit_deducer* tohit; // Enchantments have power modifier here
int colour;
bool is_enchantment;
beam_type flavour;
dungeon_char_type glyph;
bool always_obvious;
bool can_beam;
bool is_explosion;
int hit_loudness;
};
const zap_info zap_data[] = {
{
ZAP_FLAME,
"puff of flame",
50,
new dicedef_calculator<2, 4, 1, 10>,
new tohit_calculator<8, 1, 10>,
RED,
false,
BEAM_FIRE,
DCHAR_FIRED_ZAP,
true,
false,
false,
2
},
{
ZAP_FROST,
"puff of frost",
50,
new dicedef_calculator<2, 4, 1, 10>,
new tohit_calculator<8, 1, 10>,
WHITE,
false,
BEAM_COLD,
DCHAR_FIRED_ZAP,
true,
false,
false,
2
},
{
ZAP_SLOWING,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_SLOW,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_HASTING,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_HASTE,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_MAGIC_DARTS,
"magic dart",
25,
new dicedef_calculator<1, 3, 1, 5>,
new tohit_calculator<AUTOMATIC_HIT>,
LIGHTMAGENTA,
false,
BEAM_MMISSILE,
DCHAR_FIRED_ZAP,
true,
false,
false,
1
},
{
ZAP_HEALING,
"0",
100,
new dicedef_calculator<1, 7, 1, 3>,
NULL,
BLACK,
true,
BEAM_HEALING,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_PARALYSIS,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_PARALYSIS,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_FIRE,
"bolt of fire",
200,
new calcdice_calculator<6, 18, 2, 3>,
new tohit_calculator<10, 1, 25>,
RED,
false,
BEAM_FIRE,
DCHAR_FIRED_ZAP,
true,
true,
false,
6
},
{
ZAP_COLD,
"bolt of cold",
200,
new calcdice_calculator<6, 18, 2, 3>,
new tohit_calculator<10, 1, 25>,
WHITE,
false,
BEAM_COLD,
DCHAR_FIRED_ZAP,
true,
true,
false,
6
},
{
ZAP_PRIMAL_WAVE,
"great wave of water",
200,
new calcdice_calculator<4, 14, 3, 5>,
new tohit_calculator<10, 1, 25>,
LIGHTBLUE,
false,
BEAM_WATER,
DCHAR_WAVY,
true,
false,
false,
6
},
{
ZAP_CONFUSION,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_CONFUSION,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_INVISIBILITY,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_INVISIBILITY,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_DIGGING,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_DIGGING,
DCHAR_SPACE,
false,
true,
false,
0
},
{
ZAP_FIREBALL,
"fireball",
200,
new calcdice_calculator<3, 10, 1, 2>,
new tohit_calculator<40>,
RED,
false,
BEAM_FIRE,
DCHAR_FIRED_ZAP,
false,
false,
true,
0 // Noise comes from explosion
},
{
ZAP_TELEPORTATION,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_TELEPORT,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_LIGHTNING,
"bolt of lightning",
200,
new calcdice_calculator<1, 10, 3, 5>,
new tohit_calculator<7, 1, 40>,
LIGHTCYAN,
false,
BEAM_ELECTRICITY,
DCHAR_FIRED_ZAP,
true,
true,
false,
5 // XXX: Maybe louder?
},
{
ZAP_POLYMORPH_OTHER,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_POLYMORPH,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_VENOM_BOLT,
"bolt of poison",
200,
new calcdice_calculator<4, 15, 1, 2>,
new tohit_calculator<8, 1, 20>,
LIGHTGREEN,
false,
BEAM_POISON,
DCHAR_FIRED_ZAP,
true,
true,
false,
5 // XXX: Quieter because it's poison?
},
{
ZAP_NEGATIVE_ENERGY,
"bolt of negative energy",
200,
new calcdice_calculator<4, 15, 3, 5>,
new tohit_calculator<8, 1, 20>,
DARKGREY,
false,
BEAM_NEG,
DCHAR_FIRED_ZAP,
true,
true,
false,
0 // Draining is soundless
},
{
ZAP_CRYSTAL_SPEAR,
"crystal spear",
200,
new calcdice_calculator<10, 23, 1, 1>,
new tohit_calculator<10, 1, 15>,
WHITE,
false,
BEAM_MMISSILE,
DCHAR_FIRED_MISSILE,
true,
false,
false,
8
},
{
ZAP_BEAM_OF_ENERGY,
"narrow beam of energy",
1000,
new calcdice_calculator<12, 40, 3, 2>,
new tohit_calculator<1>,
YELLOW,
false,
BEAM_ENERGY,
DCHAR_FIRED_ZAP,
true,
true,
false,
3
},
{
ZAP_MYSTIC_BLAST,
"orb of energy",
100,
new calcdice_calculator<2, 15, 2, 5>,
new tohit_calculator<10, 1, 7>,
LIGHTMAGENTA,
false,
BEAM_MMISSILE,
DCHAR_FIRED_ZAP,
true,
false,
false,
4
},
{
ZAP_ENSLAVEMENT,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_CHARM,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_PAIN,
"0",
100,
new dicedef_calculator<1, 4, 1,5>,
new tohit_calculator<0, 7, 2>,
BLACK,
true,
BEAM_PAIN,
DCHAR_SPACE,
false,
false,
false,
1 // XXX: Should this be soundless?
},
{
ZAP_STICKY_FLAME,
"sticky flame",
100,
new dicedef_calculator<2, 3, 1, 12>,
new tohit_calculator<11, 1, 10>,
RED,
false,
BEAM_FIRE,
DCHAR_FIRED_ZAP,
true,
false,
false,
4 // XXX: Would sticky flame really be this noisy?
},
{
ZAP_DISPEL_UNDEAD,
"0",
100,
new calcdice_calculator<3, 20, 3, 4>,
new tohit_calculator<0, 3, 2>,
BLACK,
true,
BEAM_DISPEL_UNDEAD,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_BONE_SHARDS,
"spray of bone shards",
// Incoming power is highly dependent on mass (see spells3.cc).
// Basic function is power * 15 + mass... with the largest
// available mass (3000) we get a power of 4500 at a power
// level of 100 (for 3d20).
10000,
new dicedef_calculator<3, 2, 1, 250>,
new tohit_calculator<8, 1, 100>,
LIGHTGREY,
false,
BEAM_MAGIC,
DCHAR_FIRED_ZAP,
true,
true,
false,
3
},
{
ZAP_BANISHMENT,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_BANISH,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_DEGENERATION,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_DEGENERATE,
DCHAR_SPACE,
false,
false,
false,
0 // XXX: How loud should this be?
},
{
ZAP_STING,
"sting",
25,
new dicedef_calculator<1, 3, 1, 5>,
new tohit_calculator<8, 1, 5>,
GREEN,
false,
BEAM_POISON,
DCHAR_FIRED_ZAP,
true,
false,
false,
1 // XXX: Maybe silent because it's poison?
},
{
ZAP_HELLFIRE,
"hellfire",
200,
new calcdice_calculator<3, 10, 3, 4>,
new tohit_calculator<20, 1, 10>,
RED,
false,
BEAM_HELLFIRE,
DCHAR_FIRED_ZAP,
true,
false,
true,
9 // XXX: Even louder because it's hellish?
},
{
ZAP_IRON_SHOT,
"iron shot",
200,
new calcdice_calculator<9, 15, 3, 4>,
new tohit_calculator<7, 1, 15>,
LIGHTCYAN,
false,
BEAM_MMISSILE,
DCHAR_FIRED_MISSILE,
true,
false,
false,
6
},
{
ZAP_STRIKING,
"force bolt",
25,
new dicedef_calculator<1, 5, 0, 1>,
new tohit_calculator<8, 1, 10>,
BLACK,
false,
BEAM_MMISSILE,
DCHAR_SPACE,
true,
false,
false,
4 // XXX: this is just a guess.
},
{
ZAP_STONE_ARROW,
"stone arrow",
50,
new dicedef_calculator<2, 5, 1, 7>,
new tohit_calculator<8, 1, 10>,
LIGHTGREY,
false,
BEAM_MMISSILE,
DCHAR_FIRED_MISSILE,
true,
false,
false,
3
},
{
ZAP_ELECTRICITY,
"zap",
25,
new dicedef_calculator<1, 3, 1, 4>,
new tohit_calculator<8, 1, 7>,
LIGHTCYAN,
false,
BEAM_ELECTRICITY, // beams & reflects
DCHAR_FIRED_ZAP,
true,
true,
false,
1 // XXX: maybe electricity should be louder?
},
{
ZAP_ORB_OF_ELECTRICITY,
"orb of electricity",
200,
new calcdice_calculator<0, 15, 4, 5>,
new tohit_calculator<40>,
LIGHTBLUE,
false,
BEAM_ELECTRICITY,
DCHAR_FIRED_ZAP,
true,
false,
true,
6 // XXX: maybe electricity should be louder?
},
{
ZAP_SPIT_POISON,
"splash of poison",
50,
new dicedef_calculator<1, 4, 1, 2>,
new tohit_calculator<5, 1, 6>,
GREEN,
false,
BEAM_POISON,
DCHAR_FIRED_ZAP,
true,
false,
false,
1
},
{
ZAP_DEBUGGING_RAY,
"debugging ray",
10000,
new dicedef_calculator<1500, 1, 0, 1>,
new tohit_calculator<1500>,
WHITE,
false,
BEAM_MMISSILE,
DCHAR_FIRED_DEBUG,
false,
false,
false,
0
},
// XXX: How loud should breath be?
{
ZAP_BREATHE_FIRE,
"fiery breath",
50,
new dicedef_calculator<3, 4, 1, 3>,
new tohit_calculator<8, 1, 6>,
RED,
false,
BEAM_FIRE,
DCHAR_FIRED_ZAP,
true,
true,
false,
6
},
{
ZAP_BREATHE_FROST,
"freezing breath",
50,
new dicedef_calculator<3, 4, 1, 3>,
new tohit_calculator<8, 1, 6>,
WHITE,
false,
BEAM_COLD,
DCHAR_FIRED_ZAP,
true,
true,
false,
6
},
{
ZAP_BREATHE_ACID,
"acid",
50,
new dicedef_calculator<3, 3, 1, 3>,
new tohit_calculator<5, 1, 6>,
YELLOW,
false,
BEAM_ACID,
DCHAR_FIRED_ZAP,
true,
true,
false,
6
},
{
ZAP_BREATHE_POISON,
"poison gas",
50,
new dicedef_calculator<3, 2, 1, 6>,
new tohit_calculator<6, 1, 6>,
GREEN,
false,
BEAM_POISON,
DCHAR_FIRED_ZAP,
true,
true,
false,
0 // Explosion does the noise.
},
{
ZAP_BREATHE_POWER,
"bolt of energy",
50,
new dicedef_calculator<3, 3, 1, 3>,
new tohit_calculator<5, 1, 6>,
BLUE,
false,
BEAM_MMISSILE,
DCHAR_FIRED_ZAP,
true,
true,
false,
6
},
{
ZAP_ENSLAVE_UNDEAD,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_ENSLAVE_UNDEAD,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_ENSLAVE_SOUL,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_ENSLAVE_SOUL,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_AGONY,
"0agony",
100,
NULL,
new tohit_calculator<0, 5, 1>,
BLACK,
true,
BEAM_PAIN,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_DISINTEGRATION,
"0",
100,
new calcdice_calculator<3, 15, 3, 4>,
new tohit_calculator<0, 5, 2>,
BLACK,
true,
BEAM_DISINTEGRATION,
DCHAR_SPACE,
false,
true,
false,
6
},
{
ZAP_BREATHE_STEAM,
"ball of steam",
50,
new dicedef_calculator<3, 4, 1, 5>,
new tohit_calculator<10, 1, 10>,
LIGHTGREY,
false,
BEAM_STEAM,
DCHAR_FIRED_ZAP,
true,
true,
false,
0 // Explosion does the noise.
},
{
ZAP_CONTROL_DEMON,
"0",
100,
NULL,
new tohit_calculator<0, 3, 2>,
BLACK,
true,
BEAM_ENSLAVE_DEMON,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_ORB_OF_FRAGMENTATION,
"metal orb",
200,
new calcdice_calculator<3, 30, 3, 4>,
new tohit_calculator<20>,
CYAN,
false,
BEAM_FRAG,
DCHAR_FIRED_ZAP,
false,
false,
true,
5 // XXX: Seems like it might be louder than this.
},
{
ZAP_THROW_ICICLE,
"shard of ice",
100,
new calcdice_calculator<3, 10, 1, 2>,
new tohit_calculator<9, 1, 12>,
WHITE,
false,
BEAM_ICE,
DCHAR_FIRED_ZAP,
false,
false,
false,
4
},
{ // ench_power controls radius
ZAP_ICE_STORM,
"great blast of cold",
200,
new calcdice_calculator<7, 22, 1, 1>,
new tohit_calculator<20, 1, 10>,
BLUE,
false,
BEAM_ICE,
DCHAR_FIRED_ZAP,
true,
false,
true,
9 // XXX: Should a storm be louder?
},
{
ZAP_CORONA,
"0",
100,
NULL,
NULL,
BLUE,
true,
BEAM_CORONA,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_HIBERNATION,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_HIBERNATION,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_FLAME_TONGUE,
"flame",
25,
new dicedef_calculator<1, 8, 1, 4>,
new tohit_calculator<7, 1, 6>,
RED,
false,
BEAM_FIRE,
DCHAR_FIRED_BOLT,
true,
false,
false,
1
},
{
ZAP_SANDBLAST,
"rocky blast",
50,
new dicedef_calculator<2, 4, 1, 3>,
new tohit_calculator<13, 1, 10>,
BROWN,
false,
BEAM_FRAG,
DCHAR_FIRED_BOLT,
true,
false,
false,
2 // XXX: Sound 2 for level one spell?
},
{
ZAP_SMALL_SANDBLAST,
"blast of sand",
25,
new dicedef_calculator<1, 8, 1, 4>,
new tohit_calculator<8, 1, 5>,
BROWN,
false,
BEAM_FRAG,
DCHAR_FIRED_BOLT,
true,
false,
false,
1
},
{
ZAP_MAGMA,
"bolt of magma",
200,
new calcdice_calculator<4, 10, 3, 5>,
new tohit_calculator<8, 1, 25>,
RED,
false,
BEAM_LAVA,
DCHAR_FIRED_ZAP,
true,
true,
false,
5
},
{
ZAP_POISON_ARROW,
"poison arrow",
200,
new calcdice_calculator<4, 15, 1, 1>,
new tohit_calculator<5, 1, 10>,
LIGHTGREEN,
false,
BEAM_POISON_ARROW, // extra damage
DCHAR_FIRED_MISSILE,
true,
false,
false,
6 // XXX: Less noise because it's poison?
},
{
ZAP_PETRIFY,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_PETRIFY,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_PORKALATOR,
"porkalator",
100,
NULL,
NULL,
RED,
true,
BEAM_PORKALATOR,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_SLEEP,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_SLEEP,
DCHAR_SPACE,
false,
false,
false,
0
},
{
ZAP_IOOD,
"0",
200,
NULL,
new tohit_calculator<AUTOMATIC_HIT>,
WHITE,
false,
BEAM_NUKE,
DCHAR_FIRED_ZAP,
true,
true,
false,
0
},
};
static void _zappy(zap_type z_type, int power, bolt &pbolt)
{
const zap_info* zinfo = NULL;
// Find the appropriate zap info.
for (unsigned int i = 0; i < ARRAYSZ(zap_data); ++i)
{
if (zap_data[i].ztype == z_type)
{
zinfo = &zap_data[i];
break;
}
}
// None found?
if (zinfo == NULL)
{
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_ERROR, "Couldn't find zap type %d", z_type);
#endif
return;
}
// Fill
pbolt.name = zinfo->name;
pbolt.flavour = zinfo->flavour;
pbolt.real_flavour = zinfo->flavour;
pbolt.colour = zinfo->colour;
pbolt.type = dchar_glyph(zinfo->glyph);
pbolt.obvious_effect = zinfo->always_obvious;
pbolt.is_beam = zinfo->can_beam;
pbolt.is_explosion = zinfo->is_explosion;
if (zinfo->power_cap > 0)
power = std::min(zinfo->power_cap, power);
ASSERT(zinfo->is_enchantment == pbolt.is_enchantment());
if (zinfo->is_enchantment)
{
pbolt.ench_power = (zinfo->tohit ? (*zinfo->tohit)(power) : power);
pbolt.hit = AUTOMATIC_HIT;
}
else
{
pbolt.hit = (*zinfo->tohit)(power);
if (wearing_amulet(AMU_INACCURACY))
pbolt.hit = std::max(0, pbolt.hit - 5);
}
if (zinfo->damage)
pbolt.damage = (*zinfo->damage)(power);
// One special case
if (z_type == ZAP_ICE_STORM)
pbolt.ench_power = power; // used for radius
if (pbolt.loudness == 0)
pbolt.loudness = zinfo->hit_loudness;
}
// Affect monster in wall unless it can shield itself using the wall.
// The wall will always shield the monster if the beam bounces off the
// wall, and a monster can't use a metal wall to shield itself from
// electricity.
bool bolt::can_affect_wall_monster(const monsters* mon) const
{
if (is_enchantment())
return (true);
const bool superconductor = (grd(mon->pos()) == DNGN_METAL_WALL
&& flavour == BEAM_ELECTRICITY);
if (mons_wall_shielded(mon) && !superconductor)
return (false);
if (!is_explosion && !is_big_cloud)
return (true);
if (is_bouncy(grd(mon->pos())))
return (false);
return (false);
}
static beam_type _chaos_beam_flavour()
{
const beam_type flavour = static_cast<beam_type>(
random_choose_weighted(
10, BEAM_FIRE,
10, BEAM_COLD,
10, BEAM_ELECTRICITY,
10, BEAM_POISON,
10, BEAM_NEG,
10, BEAM_ACID,
10, BEAM_HELLFIRE,
10, BEAM_NAPALM,
10, BEAM_SLOW,
10, BEAM_HASTE,
10, BEAM_MIGHT,
10, BEAM_BERSERK,
10, BEAM_HEALING,
10, BEAM_PARALYSIS,
10, BEAM_CONFUSION,
10, BEAM_INVISIBILITY,
10, BEAM_POLYMORPH,
10, BEAM_BANISH,
10, BEAM_DISINTEGRATION,
0));
return (flavour);
}
static void _munge_bounced_bolt(bolt &old_bolt, bolt &new_bolt,
ray_def &old_ray, ray_def &new_ray)
{
if (new_bolt.real_flavour != BEAM_CHAOS)
return;
double old_deg = old_ray.get_degrees();
double new_deg = new_ray.get_degrees();
double angle = fabs(old_deg - new_deg);
if (angle >= 180.0)
angle -= 180.0;
double max = 90.0 + (angle / 2.0);
double min = -90.0 + (angle / 2.0);
double shift;
ray_def temp_ray = new_ray;
for (int tries = 0; tries < 20; tries++)
{
shift = (double) random_range((int)(min * 10000),
(int)(max * 10000)) / 10000.0;
if (new_deg < old_deg)
shift = -shift;
temp_ray.set_degrees(new_deg + shift);
// Don't bounce straight into another wall. Can happen if the beam
// is shot into an inside corner.
ray_def test_ray = temp_ray;
test_ray.advance();
if (in_bounds(test_ray.pos()) && !cell_is_solid(test_ray.pos()))
break;
shift = 0.0;
temp_ray = new_ray;
}
new_ray = temp_ray;
#if DEBUG_DIAGNOSTICS || DEBUG_BEAM || DEBUG_CHAOS_BOUNCE
mprf(MSGCH_DIAGNOSTICS,
"chaos beam: old_deg = %5.2f, new_deg = %5.2f, shift = %5.2f",
(float) old_deg, (float) new_deg, (float) shift);
#endif
// Don't use up range in bouncing off walls, so that chaos beams have
// as many chances as possible to bounce. They're like demented
// ping-pong balls on caffeine.
int range_spent = new_bolt.range_used - old_bolt.range_used;
new_bolt.range += range_spent;
}
bool bolt::invisible() const
{
return (type == 0 || is_enchantment());
}
void bolt::initialise_fire()
{
// Fix some things which the tracer might have set.
range_used = 0;
in_explosion_phase = false;
use_target_as_pos = false;
if (special_explosion != NULL)
{
ASSERT(!is_explosion);
ASSERT(special_explosion->is_explosion);
ASSERT(special_explosion->special_explosion == NULL);
special_explosion->in_explosion_phase = false;
special_explosion->use_target_as_pos = false;
}
if (chose_ray)
{
ASSERT(in_bounds(ray.pos()));
if (source == coord_def())
source = ray.pos();
}
if (target == source)
{
range = 0;
aimed_at_feet = true;
auto_hit = true;
aimed_at_spot = true;
use_target_as_pos = true;
}
if (range == -1)
{
#ifdef DEBUG
if (is_tracer)
{
mpr("Tracer with range == -1, skipping.", MSGCH_ERROR);
return;
}
std::string item_name = item ? item->name(DESC_PLAIN, false, true)
: "none";
std::string dbg_source_name = "unknown";
if (beam_source == NON_MONSTER && source == you.pos())
dbg_source_name = "player";
else if (!invalid_monster_index(beam_source))
dbg_source_name = menv[beam_source].name(DESC_PLAIN, true);
mprf(MSGCH_ERROR, "beam '%s' (source '%s', item '%s') has range -1; "
"setting to LOS_RADIUS",
name.c_str(), dbg_source_name.c_str(), item_name.c_str());
#endif
range = LOS_RADIUS;
}
ASSERT(!name.empty() || is_tracer);
ASSERT(in_bounds(source));
ASSERT(flavour > BEAM_NONE && flavour < BEAM_FIRST_PSEUDO);
ASSERT(!drop_item || item && item->is_valid());
ASSERT(range >= 0);
ASSERT(!aimed_at_feet || source == target);
real_flavour = flavour;
message_cache.clear();
// seen might be set by caller to supress this.
if (!seen && you.see_cell(source) && range > 0 && !invisible() )
{
seen = true;
const monsters* mon = monster_at(source);
if (flavour != BEAM_VISUAL
&& !is_tracer
&& !YOU_KILL(thrower)
&& !crawl_state.is_god_acting()
&& (!mon || !mon->observable()))
{
mprf("%s appears from out of thin air!",
article_a(name, false).c_str());
}
}
// Visible self-targeted beams are always seen, even though they don't
// leave a path.
if (you.see_cell(source) && target == source && !invisible())
seen = true;
// Scale draw_delay to match change in arena_delay.
if (crawl_state.arena && !is_tracer)
{
draw_delay *= Options.arena_delay;
draw_delay /= 600;
}
#ifdef DEBUG_DIAGNOSTICS
mprf( MSGCH_DIAGNOSTICS, "%s%s%s [%s] (%d,%d) to (%d,%d): "
"ty=%d col=%d flav=%d hit=%d dam=%dd%d range=%d",
(is_beam) ? "beam" : "missile",
(is_explosion) ? "*" :
(is_big_cloud) ? "+" : "",
(is_tracer) ? " tracer" : "",
name.c_str(),
source.x, source.y,
target.x, target.y,
type, colour, flavour,
hit, damage.num, damage.size,
range);
#endif
}
void bolt::apply_beam_conducts()
{
if (!is_tracer && YOU_KILL(thrower))
{
switch (flavour)
{
case BEAM_HELLFIRE:
did_god_conduct(DID_UNHOLY, 2 + random2(3), effect_known);
break;
default:
break;
}
}
}
void bolt::choose_ray()
{
if (!chose_ray || reflections > 0)
{
if (!find_ray(source, target, ray))
fallback_ray(source, target, ray);
}
}
// Draw the bolt at p if needed.
void bolt::draw(const coord_def& p)
{
if (is_tracer || is_enchantment() || !you.see_cell(p))
return;
// We don't clean up the old position.
// First, most people like to see the full path,
// and second, it is hard to do it right with
// respect to killed monsters, cloud trails, etc.
const coord_def drawpos = grid2view(p);
#ifdef USE_TILE
if (tile_beam == -1)
{
if (effect_known)
tile_beam = tileidx_bolt(*this);
else
tile_beam = tileidx_zap(ETC_MAGIC);
}
if (tile_beam != -1 && in_los_bounds(drawpos))
{
tiles.add_overlay(p, tile_beam);
delay(draw_delay);
}
else
#endif
{
// bounds check
if (in_los_bounds(drawpos))
{
#ifndef USE_TILE
cgotoxy(drawpos.x, drawpos.y);
put_colour_ch(colour == BLACK ? random_colour()
: element_colour(colour),
type);
#endif
// Get curses to update the screen so we can see the beam.
update_screen();
delay(draw_delay);
}
}
}
// Bounce a bolt off a solid feature.
// The ray is assumed to have just been advanced into
// the feature.
void bolt::bounce()
{
ray_def old_ray = ray;
bolt old_bolt = *this;
do
ray.regress();
while (feat_is_solid(grd(ray.pos())));
bounce_pos = ray.pos();
reflect_grid rg;
for (adjacent_iterator ai(ray.pos(), false); ai; ++ai)
rg(*ai - ray.pos()) = feat_is_solid(grd(*ai));
ray.bounce(rg);
range_used += 2;
ASSERT(!feat_is_solid(grd(ray.pos())));
_munge_bounced_bolt(old_bolt, *this, old_ray, ray);
}
void bolt::fake_flavour()
{
if (real_flavour == BEAM_RANDOM)
flavour = static_cast<beam_type>(random_range(BEAM_FIRE, BEAM_ACID));
else if (real_flavour == BEAM_CHAOS)
flavour = _chaos_beam_flavour();
}
void bolt::digging_wall_effect()
{
const dungeon_feature_type feat = grd(pos());
if (feat == DNGN_ROCK_WALL || feat == DNGN_CLEAR_ROCK_WALL)
{
grd(pos()) = DNGN_FLOOR;
// Mark terrain as changed so travel excludes can be updated
// as necessary.
// XXX: This doesn't work for some reason: after digging
// the wrong grids are marked excluded.
set_terrain_changed(pos());
// Blood does not transfer onto floor.
if (is_bloodcovered(pos()))
env.pgrid(pos()) &= ~(FPROP_BLOODY);
if (!msg_generated)
{
if (!silenced(you.pos()))
{
mpr("You hear a grinding noise.", MSGCH_SOUND);
obvious_effect = true;
}
msg_generated = true;
}
}
else if (feat_is_wall(feat))
finish_beam();
}
void bolt::fire_wall_effect()
{
dungeon_feature_type feat;
// Fire only affects wax walls and trees.
if ((feat = grd(pos())) != DNGN_WAX_WALL && feat != DNGN_TREES)
{
finish_beam();
return;
}
if (feat == DNGN_WAX_WALL)
{
if (!is_superhot())
{
// No actual effect.
if (flavour != BEAM_HELLFIRE && feat == DNGN_WAX_WALL)
{
if (you.see_cell(pos()))
{
emit_message(MSGCH_PLAIN,
"The wax appears to soften slightly.");
}
else if (you.can_smell())
emit_message(MSGCH_PLAIN, "You smell warm wax.");
}
}
else
{
// Destroy the wall.
grd(pos()) = DNGN_FLOOR;
if (you.see_cell(pos()))
emit_message(MSGCH_PLAIN, "The wax bubbles and burns!");
else if (you.can_smell())
emit_message(MSGCH_PLAIN, "You smell burning wax.");
place_cloud(CLOUD_FIRE, pos(), random2(10)+15, whose_kill(), killer());
obvious_effect = true;
}
}
else
{
if (is_superhot())
{
// Destroy the wall.
grd(pos()) = dgn_tree_base_feature_at(pos());
if (you.see_cell(pos()))
emit_message(MSGCH_PLAIN, "The tree burns like a torch!");
else if (you.can_smell())
emit_message(MSGCH_PLAIN, "You smell burning wood.");
if (whose_kill() == KC_YOU)
did_god_conduct(DID_KILL_PLANT, 1, effect_known);
else if (whose_kill() == KC_FRIENDLY)
did_god_conduct(DID_ALLY_KILLED_PLANT, 1, effect_known, 0);
place_cloud(CLOUD_FOREST_FIRE, pos(), random2(30)+25, whose_kill(), killer(), 5);
obvious_effect = true;
}
}
finish_beam();
}
void bolt::elec_wall_effect()
{
const dungeon_feature_type feat = grd(pos());
if (feat == DNGN_TREES)
{
fire_wall_effect();
return;
}
finish_beam();
}
void bolt::nuke_wall_effect()
{
if (env.markers.property_at(pos(), MAT_ANY, "veto_disintegrate") == "veto")
{
finish_beam();
return;
}
const dungeon_feature_type feat = grd(pos());
if (feat == DNGN_ROCK_WALL
|| feat == DNGN_WAX_WALL
|| feat == DNGN_CLEAR_ROCK_WALL
|| feat == DNGN_GRANITE_STATUE)
{
// Blood does not transfer onto floor.
if (is_bloodcovered(pos()))
env.pgrid(pos()) &= ~(FPROP_BLOODY);
grd(pos()) = DNGN_FLOOR;
if (player_can_hear(pos()))
{
mpr("You hear a grinding noise.", MSGCH_SOUND);
obvious_effect = true;
}
}
else if (feat == DNGN_ORCISH_IDOL)
{
grd(pos()) = DNGN_FLOOR;
// Blood does not transfer onto floor.
if (is_bloodcovered(pos()))
env.pgrid(pos()) &= ~(FPROP_BLOODY);
if (player_can_hear(pos()))
{
if (!you.see_cell(pos()))
mpr("You hear a hideous screaming!", MSGCH_SOUND);
else
{
mpr("The idol screams as its substance crumbles away!",
MSGCH_SOUND);
}
}
else if (you.see_cell(pos()))
mpr("The idol twists and shakes as its substance crumbles away!");
if (beam_source == NON_MONSTER)
did_god_conduct(DID_DESTROY_ORCISH_IDOL, 8);
obvious_effect = true;
}
else if (feat == DNGN_TREES)
{
grd(pos()) = DNGN_FLOOR;
// Blood does not transfer onto floor.
if (is_bloodcovered(pos()))
env.pgrid(pos()) &= ~(FPROP_BLOODY);
if (you.see_cell(pos()))
mpr("The tree breaks and falls down!");
else if (player_can_hear(pos()))
mpr("You hear timber falling.", MSGCH_SOUND);
obvious_effect = true;
}
finish_beam();
}
void bolt::finish_beam()
{
range_used = range;
}
void bolt::affect_wall()
{
if (is_tracer)
return;
if (flavour == BEAM_DIGGING)
digging_wall_effect();
else if (is_fiery())
fire_wall_effect();
else if (flavour == BEAM_ELECTRICITY)
elec_wall_effect();
else if (flavour == BEAM_DISINTEGRATION || flavour == BEAM_NUKE)
nuke_wall_effect();
if (cell_is_solid(pos()))
finish_beam();
}
coord_def bolt::pos() const
{
if (in_explosion_phase || use_target_as_pos)
return target;
else
return ray.pos();
}
bool bolt::need_regress() const
{
return ((is_explosion && !in_explosion_phase)
|| drop_item
|| origin_spell == SPELL_PRIMAL_WAVE);
}
// Returns true if the beam ended due to hitting the wall.
bool bolt::hit_wall()
{
const dungeon_feature_type feat = grd(pos());
ASSERT( feat_is_solid(feat) );
if (is_tracer && YOU_KILL(thrower) && in_bounds(target) && !passed_target
&& pos() != target && pos() != source && foe_info.count == 0
&& flavour != BEAM_DIGGING && flavour <= BEAM_LAST_REAL
&& bounces == 0 && reflections == 0 && you.see_cell(target)
&& !feat_is_solid(grd(target)))
{
// Okay, with all those tests passed, this is probably an instance
// of the player manually targetting something whose line of fire
// is blocked, even though its line of sight isn't blocked. Give
// a warning about this fact.
std::string prompt = "Your line of fire to ";
const monsters* mon = monster_at(target);
if (mon && mon->observable())
prompt += mon->name(DESC_NOCAP_THE);
else
{
prompt += "the targeted "
+ feature_description(target, false, DESC_PLAIN, false);
}
prompt += " is blocked by "
+ feature_description(pos(), false, DESC_NOCAP_A, false);
prompt += ". Continue anyway?";
if (!yesno(prompt.c_str(), false, 'n'))
{
beam_cancelled = true;
finish_beam();
return (false);
}
// Well, we warned them.
}
// Press trigger/switch/button in wall if hit by something solid
// or solid-ish.
if (in_bounds(pos()) && !is_explosion && !is_tracer && !monster_at(pos())
&& (flavour == BEAM_MISSILE || flavour == BEAM_MMISSILE))
{
dgn_event event(DET_WALL_HIT, pos());;
event.arg1 = beam_source;
dungeon_events.fire_vetoable_position_event(event, target);
}
if (in_bounds(pos()) && affects_wall(feat))
affect_wall();
else if (is_bouncy(feat) && !in_explosion_phase)
bounce();
else
{
// Regress for explosions: blow up in an open grid (if regressing
// makes any sense). Also regress when dropping items.
if (pos() != source && need_regress())
{
do
ray.regress();
while (ray.pos() != source && cell_is_solid(ray.pos()));
// target is where the explosion is centered, so update it.
if (is_explosion && !is_tracer)
target = ray.pos();
}
finish_beam();
return (true);
}
return (false);
}
void bolt::affect_cell()
{
// Shooting through clouds affects accuracy.
if (env.cgrid(pos()) != EMPTY_CLOUD)
hit = std::max(hit - 2, 0);
fake_flavour();
const coord_def old_pos = pos();
const bool was_solid = feat_is_solid(grd(pos()));
if (was_solid)
{
// Some special casing.
if (monsters* mon = monster_at(pos()))
{
if (can_affect_wall_monster(mon))
affect_monster(mon);
else
{
mprf("The %s protects %s from harm.",
raw_feature_description(grd(mon->pos())).c_str(),
mon->name(DESC_NOCAP_THE).c_str());
}
}
// Note that this can change the ray position and the solidity
// of the wall.
if (hit_wall())
// Beam ended due to hitting wall, so don't hit the player
// or monster with the regressed beam.
return;
}
// If the player can ever walk through walls, this will need
// special-casing too.
bool hit_player = found_player();
if (hit_player)
affect_player();
// We don't want to hit a monster in a wall square twice. Also,
// stop single target beams from affecting a monster if they already
// affected the player on this square. -cao
const bool still_wall = (was_solid && old_pos == pos());
if ((!hit_player || is_beam || is_explosion) && !still_wall)
if (monsters* m = monster_at(pos()))
affect_monster(m);
if (!feat_is_solid(grd(pos())))
affect_ground();
}
bool bolt::apply_hit_funcs(actor* victim, int dmg, int corpse)
{
bool affected = false;
for (unsigned int i = 0; i < hit_funcs.size(); ++i)
affected = (*hit_funcs[i])(*this, victim, dmg, corpse) || affected;
return (affected);
}
bool bolt::apply_dmg_funcs(actor* victim, int &dmg,
std::vector<std::string> &messages)
{
for (unsigned int i = 0; i < damage_funcs.size(); ++i)
{
std::string dmg_msg;
if ( (*damage_funcs[i])(*this, victim, dmg, dmg_msg) )
return (false);
if (!dmg_msg.empty())
messages.push_back(dmg_msg);
}
return (true);
}
static void _undo_tracer(bolt &orig, bolt ©)
{
// FIXME: we should have a better idea of what gets changed!
orig.target = copy.target;
orig.source = copy.source;
orig.aimed_at_spot = copy.aimed_at_spot;
orig.range_used = copy.range_used;
orig.auto_hit = copy.auto_hit;
orig.ray = copy.ray;
orig.colour = copy.colour;
orig.flavour = copy.flavour;
orig.real_flavour = copy.real_flavour;
}
// This saves some important things before calling fire().
void bolt::fire()
{
path_taken.clear();
if (special_explosion)
special_explosion->is_tracer = is_tracer;
if (is_tracer)
{
bolt boltcopy = *this;
if (special_explosion != NULL)
boltcopy.special_explosion = new bolt(*special_explosion);
do_fire();
if (special_explosion != NULL)
{
_undo_tracer(*special_explosion, *boltcopy.special_explosion);
delete boltcopy.special_explosion;
}
_undo_tracer(*this, boltcopy);
}
else
do_fire();
if (special_explosion != NULL)
{
seen = seen || special_explosion->seen;
heard = heard || special_explosion->heard;
beam_cancelled = beam_cancelled || special_explosion->beam_cancelled;
foe_info += special_explosion->foe_info;
friend_info += special_explosion->friend_info;
}
}
void bolt::do_fire()
{
initialise_fire();
if (range <= range_used && range > 0)
{
#ifdef DEBUG
mprf(MSGCH_DIAGNOSTICS, "fire_beam() called on already done beam "
"'%s' (item = '%s')", name.c_str(),
item ? item->name(DESC_PLAIN).c_str() : "none");
#endif
return;
}
apply_beam_conducts();
cursor_control coff(false);
#ifdef USE_TILE
tile_beam = -1;
if (item && !is_tracer && flavour == BEAM_MISSILE)
{
const coord_def diff = target - source;
tile_beam = tileidx_item_throw(*item, diff.x, diff.y);
}
#endif
msg_generated = false;
if (!aimed_at_feet)
{
choose_ray();
// Take *one* step, so as not to hurt the source.
ray.advance();
}
#if defined(TARGET_OS_WINDOWS) && !defined(USE_TILE)
// Before we start drawing the beam, turn buffering off.
bool oldValue = true;
if (!is_tracer)
oldValue = set_buffering(false);
#endif
while (map_bounds(pos()))
{
path_taken.push_back(pos());
if (!affects_nothing)
affect_cell();
range_used++;
if (range_used >= range)
break;
if (beam_cancelled)
return;
if (pos() == target)
{
passed_target = true;
if (stop_at_target())
break;
}
ASSERT(!feat_is_solid(grd(pos()))
|| is_tracer && affects_wall(grd(pos())));
const bool was_seen = seen;
if (!was_seen && range > 0 && !invisible() && you.see_cell(pos()))
seen = true;
if (flavour != BEAM_VISUAL && !was_seen && seen && !is_tracer)
{
mprf("%s appears from out of your range of vision.",
article_a(name, false).c_str());
}
// Reset chaos beams so that it won't be considered an invisible
// enchantment beam for the purposes of animation.
if (real_flavour == BEAM_CHAOS)
flavour = real_flavour;
// Actually draw the beam/missile/whatever, if the player can see
// the cell.
draw(pos());
ray.advance();
}
if (!map_bounds(pos()))
{
ASSERT(!aimed_at_spot);
int tries = std::max(GXM, GYM);
while (!map_bounds(ray.pos()) && tries-- > 0)
ray.regress();
// Something bizarre happening if we can't get back onto the map.
ASSERT(map_bounds(pos()));
}
// The beam has terminated.
if (!affects_nothing)
affect_endpoint();
// Tracers need nothing further.
if (is_tracer || affects_nothing)
return;
// Canned msg for enchantments that affected no-one, but only if the
// enchantment is yours (and it wasn't a chaos beam, since with chaos
// enchantments are entirely random, and if it randomly attempts
// something which ends up having no obvious effect then the player
// isn't going to realise it).
if (!msg_generated && !obvious_effect && is_enchantment()
&& real_flavour != BEAM_CHAOS && YOU_KILL(thrower))
{
canned_msg(MSG_NOTHING_HAPPENS);
}
// Reactions if a monster zapped the beam.
if (!invalid_monster_index(beam_source))
{
if (foe_info.hurt == 0 && friend_info.hurt > 0)
xom_is_stimulated(128);
else if (foe_info.helped > 0 && friend_info.helped == 0)
xom_is_stimulated(128);
// Allow friendlies to react to projectiles, except when in
// sanctuary when pet_target can only be explicitly changed by
// the player.
const monsters *mon = &menv[beam_source];
if (foe_info.hurt > 0 && !mon->wont_attack() && !crawl_state.arena
&& you.pet_target == MHITNOT && env.sanctuary_time <= 0)
{
you.pet_target = beam_source;
}
}
// That's it!
#if defined(TARGET_OS_WINDOWS) && !defined(USE_TILE)
set_buffering(oldValue);
#endif
}
// Returns damage taken by a monster from a "flavoured" (fire, ice, etc.)
// attack -- damage from clouds and branded weapons handled elsewhere.
int mons_adjust_flavoured(monsters *monster, bolt &pbolt, int hurted,
bool doFlavouredEffects)
{
// If we're not doing flavoured effects, must be preliminary
// damage check only.
// Do not print messages or apply any side effects!
int resist = 0;
int original = hurted;
switch (pbolt.flavour)
{
case BEAM_FIRE:
case BEAM_STEAM:
hurted = resist_adjust_damage(
monster,
pbolt.flavour,
(pbolt.flavour == BEAM_FIRE) ? monster->res_fire()
: monster->res_steam(),
hurted, true);
if (!hurted)
{
if (doFlavouredEffects)
{
simple_monster_message(monster,
(original > 0) ? " completely resists."
: " appears unharmed.");
}
}
else if (original > hurted)
{
if (doFlavouredEffects)
simple_monster_message(monster, " resists.");
}
else if (original < hurted && doFlavouredEffects)
{
if (monster->is_icy())
simple_monster_message(monster, " melts!");
else if (monster->type == MONS_BUSH)
simple_monster_message(monster, " is on fire!");
else if (pbolt.flavour == BEAM_FIRE)
simple_monster_message(monster, " is burned terribly!");
else
simple_monster_message(monster, " is scalded terribly!");
}
break;
case BEAM_WATER:
hurted = resist_adjust_damage(monster, pbolt.flavour,
monster->res_asphyx(),
hurted, true);
if (doFlavouredEffects)
{
if (!hurted)
simple_monster_message(monster, " shrugs off the wave.");
}
break;
case BEAM_COLD:
hurted = resist_adjust_damage(monster, pbolt.flavour,
monster->res_cold(),
hurted, true);
if (!hurted)
{
if (doFlavouredEffects)
{
simple_monster_message(monster,
(original > 0) ? " completely resists."
: " appears unharmed.");
}
}
else if (original > hurted)
{
if (doFlavouredEffects)
simple_monster_message(monster, " resists.");
}
else if (original < hurted)
{
if (doFlavouredEffects)
simple_monster_message(monster, " is frozen!");
}
break;
case BEAM_ELECTRICITY:
hurted = resist_adjust_damage(monster, pbolt.flavour,
monster->res_elec(),
hurted, true);
if (!hurted)
{
if (doFlavouredEffects)
{
simple_monster_message(monster,
(original > 0) ? " completely resists."
: " appears unharmed.");
}
}
break;
case BEAM_ACID:
hurted = resist_adjust_damage(monster, pbolt.flavour,
monster->res_acid(),
hurted, true);
if (!hurted)
{
if (doFlavouredEffects)
{
simple_monster_message(monster,
(original > 0) ? " completely resists."
: " appears unharmed.");
}
}
break;
case BEAM_POISON:
{
int res = monster->res_poison();
hurted = resist_adjust_damage(monster, pbolt.flavour, res,
hurted, true);
if (!hurted && res > 0)
{
if (doFlavouredEffects)
{
simple_monster_message(monster,
(original > 0) ? " completely resists."
: " appears unharmed.");
}
}
else if (res <= 0 && doFlavouredEffects && !one_chance_in(3))
poison_monster(monster, pbolt.whose_kill());
break;
}
case BEAM_POISON_ARROW:
hurted = resist_adjust_damage(monster, pbolt.flavour,
monster->res_poison(),
hurted);
if (hurted < original)
{
if (doFlavouredEffects)
{
simple_monster_message(monster, " partially resists.");
// Poison arrow can poison any living thing regardless of
// poison resistance. - bwr
if (mons_has_lifeforce(monster))
poison_monster(monster, pbolt.whose_kill(), 2, true);
}
}
else if (doFlavouredEffects)
poison_monster(monster, pbolt.whose_kill(), 4);
break;
case BEAM_NEG:
if (monster->res_negative_energy() == 3)
{
if (doFlavouredEffects)
simple_monster_message(monster, " completely resists.");
hurted = 0;
}
else
{
// Early out if no side effects.
if (!doFlavouredEffects)
return (hurted);
if (monster->observable())
pbolt.obvious_effect = true;
monster->drain_exp(pbolt.agent());
if (YOU_KILL(pbolt.thrower))
did_god_conduct(DID_NECROMANCY, 2, pbolt.effect_known);
}
break;
case BEAM_MIASMA:
if (monster->res_rotting())
{
if (doFlavouredEffects)
simple_monster_message(monster, " completely resists.");
hurted = 0;
}
else
{
// Early out for tracer/no side effects.
if (!doFlavouredEffects)
return (hurted);
miasma_monster(monster, pbolt.whose_kill());
if (YOU_KILL(pbolt.thrower))
did_god_conduct(DID_UNCLEAN, 2, pbolt.effect_known);
}
break;
case BEAM_HOLY:
{
// Cleansing flame.
const int rhe = monster->res_holy_energy(pbolt.agent());
if (rhe > 0)
hurted = 0;
else if (rhe == 0)
hurted /= 2;
else if (rhe < -1)
hurted = (hurted * 3) / 2;
if (doFlavouredEffects)
{
simple_monster_message(monster,
hurted == 0 ? " appears unharmed."
: " writhes in agony!");
}
break;
}
case BEAM_ICE:
// ice - about 50% of damage is cold, other 50% is impact and
// can't be resisted (except by AC, of course)
hurted = resist_adjust_damage(monster, pbolt.flavour,
monster->res_cold(), hurted,
true);
if (hurted < original)
{
if (doFlavouredEffects)
simple_monster_message(monster, " partially resists.");
}
else if (hurted > original)
{
if (doFlavouredEffects)
simple_monster_message(monster, " is frozen!");
}
break;
case BEAM_LAVA:
hurted = resist_adjust_damage(monster, pbolt.flavour,
monster->res_fire(), hurted, true);
if (hurted < original)
{
if (doFlavouredEffects)
simple_monster_message(monster, " partially resists.");
}
else if (hurted > original)
{
if (monster->is_icy())
{
if (doFlavouredEffects)
simple_monster_message(monster, " melts!");
}
else
{
if (doFlavouredEffects)
simple_monster_message(monster, " is burned terribly!");
}
}
break;
case BEAM_HELLFIRE:
resist = monster->res_fire();
if (resist > 2)
{
if (doFlavouredEffects)
{
simple_monster_message(monster,
(original > 0) ? " completely resists."
: " appears unharmed.");
}
hurted = 0;
}
else if (resist > 0)
{
if (doFlavouredEffects)
simple_monster_message(monster, " partially resists.");
hurted /= 2;
}
else if (resist < 0)
{
if (monster->is_icy())
{
if (doFlavouredEffects)
simple_monster_message(monster, " melts!");
}
else
{
if (doFlavouredEffects)
simple_monster_message(monster, " is burned terribly!");
}
hurted *= 12; // hellfire
hurted /= 10;
}
break;
case BEAM_SPORE:
if (monster->type == MONS_BALLISTOMYCETE)
hurted = 0;
break;
default:
break;
}
return (hurted);
}
static bool _monster_resists_mass_enchantment(monsters *monster,
enchant_type wh_enchant,
int pow)
{
// Assuming that the only mass charm is control undead.
if (wh_enchant == ENCH_CHARM)
{
if (monster->friendly())
return (true);
if (monster->holiness() != MH_UNDEAD)
return (true);
if (monster->check_res_magic(pow))
{
simple_monster_message(monster,
mons_immune_magic(monster)? " is unaffected."
: " resists.");
return (true);
}
}
else if (wh_enchant == ENCH_CONFUSION
|| monster->holiness() == MH_NATURAL)
{
if (wh_enchant == ENCH_CONFUSION
&& !mons_class_is_confusable(monster->type))
{
return (true);
}
if (monster->check_res_magic(pow))
{
simple_monster_message(monster,
mons_immune_magic(monster)? " is unaffected."
: " resists.");
return (true);
}
}
else // trying to enchant an unnatural creature doesn't work
{
simple_monster_message(monster, " is unaffected.");
return (true);
}
return (false);
}
// Enchants all monsters in player's sight.
// If m_succumbed is non-NULL, will be set to the number of monsters that
// were enchanted. If m_attempted is non-NULL, will be set to the number of
// monsters that we tried to enchant.
bool mass_enchantment( enchant_type wh_enchant, int pow, int origin,
int *m_succumbed, int *m_attempted )
{
bool msg_generated = false;
if (m_succumbed)
*m_succumbed = 0;
if (m_attempted)
*m_attempted = 0;
pow = std::min(pow, 200);
const kill_category kc = (origin == MHITYOU ? KC_YOU : KC_OTHER);
for (monster_iterator mi(&you.get_los()); mi; ++mi)
{
if (mi->has_ench(wh_enchant))
continue;
if (m_attempted)
++*m_attempted;
if (_monster_resists_mass_enchantment(*mi, wh_enchant, pow))
continue;
if (mi->add_ench(mon_enchant(wh_enchant, 0, kc)))
{
if (m_succumbed)
++*m_succumbed;
// Do messaging.
const char* msg;
switch (wh_enchant)
{
case ENCH_FEAR: msg = " looks frightened!"; break;
case ENCH_CONFUSION: msg = " looks rather confused."; break;
case ENCH_CHARM: msg = " submits to your will."; break;
default: msg = NULL; break;
}
if (msg)
msg_generated = simple_monster_message(*mi, msg);
// Extra check for fear (monster needs to reevaluate behaviour).
if (wh_enchant == ENCH_FEAR)
behaviour_event(*mi, ME_SCARE, origin);
}
}
if (!msg_generated)
canned_msg(MSG_NOTHING_HAPPENS);
return (msg_generated);
}
void bolt::apply_bolt_paralysis(monsters *monster)
{
if (!monster->paralysed()
&& monster->add_ench(ENCH_PARALYSIS)
&& (!monster->petrified()
|| monster->has_ench(ENCH_PETRIFYING)))
{
if (simple_monster_message(monster, " suddenly stops moving!"))
obvious_effect = true;
mons_check_pool(monster, monster->pos(), killer(), beam_source);
}
}
// Petrification works in two stages. First the monster is slowed down in
// all of its actions and cannot move away (petrifying), and when that times
// out it remains properly petrified (no movement or actions). The second
// part is similar to paralysis, except that insubstantial monsters can't be
// affected and that stabbing damage is drastically reduced.
void bolt::apply_bolt_petrify(monsters *monster)
{
int petrifying = monster->has_ench(ENCH_PETRIFYING);
if (monster->petrified())
{
// If the petrifying is not yet finished, we can force it to happen
// right away by casting again. Otherwise, the spell has no further
// effect.
if (petrifying > 0)
{
monster->del_ench(ENCH_PETRIFYING, true);
if (!monster->has_ench(ENCH_PARALYSIS)
&& simple_monster_message(monster, " stops moving altogether!"))
{
obvious_effect = true;
}
}
}
else if (monster->add_ench(ENCH_PETRIFIED)
&& !monster->has_ench(ENCH_PARALYSIS))
{
// Add both the petrifying and the petrified enchantment. The former
// will run out sooner and result in plain petrification behaviour.
monster->add_ench(ENCH_PETRIFYING);
if (simple_monster_message(monster, " is moving more slowly."))
obvious_effect = true;
mons_check_pool(monster, monster->pos(), killer(), beam_source);
}
}
bool curare_hits_monster(actor *agent, monsters *monster, kill_category who,
int levels)
{
poison_monster(monster, who, levels, false);
int hurted = 0;
if (!monster->res_asphyx())
{
hurted = roll_dice(2, 6);
// Note that the hurtage is halved by poison resistance.
if (monster->res_poison() > 0)
hurted /= 2;
if (hurted)
{
simple_monster_message(monster, " convulses.");
monster->hurt(agent, hurted, BEAM_POISON);
}
if (monster->alive())
enchant_monster_with_flavour(monster, agent, BEAM_SLOW);
}
// Deities take notice.
if (who == KC_YOU)
did_god_conduct(DID_POISON, 5 + random2(3));
return (hurted > 0);
}
// Actually poisons a monster (with message).
bool poison_monster(monsters *monster, kill_category who, int levels,
bool force, bool verbose)
{
if (!monster->alive())
return (false);
if ((!force && monster->res_poison() > 0) || levels <= 0)
return (false);
const mon_enchant old_pois = monster->get_ench(ENCH_POISON);
monster->add_ench(mon_enchant(ENCH_POISON, levels, who));
const mon_enchant new_pois = monster->get_ench(ENCH_POISON);
// Actually do the poisoning. The order is important here.
if (new_pois.degree > old_pois.degree)
{
if (verbose)
{
simple_monster_message(monster,
old_pois.degree > 0 ? " looks even sicker."
: " is poisoned.");
}
behaviour_event(monster, ME_ANNOY, (who == KC_YOU) ? MHITYOU : MHITNOT);
}
// Finally, take care of deity preferences.
if (who == KC_YOU)
did_god_conduct(DID_POISON, 5 + random2(3));
return (new_pois.degree > old_pois.degree);
}
// Actually poisons, rots, and/or slows a monster with miasma (with
// message).
bool miasma_monster(monsters *monster, kill_category who)
{
if (!monster->alive())
return (false);
if (monster->res_rotting())
return (false);
bool success = poison_monster(monster, who);
if (monster->max_hit_points > 4 && coinflip())
{
monster->max_hit_points--;
monster->hit_points = std::min(monster->max_hit_points,
monster->hit_points);
success = true;
}
if (one_chance_in(3))
{
bolt beam;
beam.flavour = BEAM_SLOW;
beam.apply_enchantment_to_monster(monster);
success = true;
}
return (success);
}
// Actually napalms a monster (with message).
bool napalm_monster(monsters *monster, kill_category who, int levels,
bool verbose)
{
if (!monster->alive())
return (false);
if (monster->res_sticky_flame() || levels <= 0)
return (false);
const mon_enchant old_flame = monster->get_ench(ENCH_STICKY_FLAME);
monster->add_ench(mon_enchant(ENCH_STICKY_FLAME, levels, who));
const mon_enchant new_flame = monster->get_ench(ENCH_STICKY_FLAME);
// Actually do the napalming. The order is important here.
if (new_flame.degree > old_flame.degree)
{
if (verbose)
simple_monster_message(monster, " is covered in liquid flames!");
behaviour_event(monster, ME_WHACK, who == KC_YOU ? MHITYOU : MHITNOT);
}
return (new_flame.degree > old_flame.degree);
}
// Used by monsters in "planning" which spell to cast. Fires off a "tracer"
// which tells the monster what it'll hit if it breathes/casts etc.
//
// The output from this tracer function is written into the
// tracer_info variables (friend_info and foe_info.)
//
// Note that beam properties must be set, as the tracer will take them
// into account, as well as the monster's intelligence.
void fire_tracer(const monsters *monster, bolt &pbolt, bool explode_only)
{
// Don't fiddle with any input parameters other than tracer stuff!
pbolt.is_tracer = true;
pbolt.source = monster->pos();
pbolt.beam_source = monster->mindex();
pbolt.can_see_invis = monster->can_see_invisible();
pbolt.smart_monster = (mons_intel(monster) >= I_NORMAL);
pbolt.attitude = mons_attitude(monster);
// Init tracer variables.
pbolt.foe_info.reset();
pbolt.friend_info.reset();
// Clear misc
pbolt.reflections = 0;
pbolt.bounces = 0;
// If there's a specifically requested foe_ratio, honour it.
if (!pbolt.foe_ratio)
{
pbolt.foe_ratio = 80; // default - see mons_should_fire()
// Foe ratio for summoning greater demons & undead -- they may be
// summoned, but they're hostile and would love nothing better
// than to nuke the player and his minions.
if (mons_att_wont_attack(pbolt.attitude)
&& !mons_att_wont_attack(monster->attitude))
{
pbolt.foe_ratio = 25;
}
}
pbolt.in_explosion_phase = false;
// Fire!
if (explode_only)
pbolt.explode(false);
else
pbolt.fire();
// Unset tracer flag (convenience).
pbolt.is_tracer = false;
}
// When a mimic is hit by a ranged attack, it teleports away (the slow
// way) and changes its appearance - the appearance change is in
// monster_teleport() in mon-stuff.cc.
void mimic_alert(monsters *mimic)
{
if (!mimic->alive())
return;
bool should_id = !testbits(mimic->flags, MF_KNOWN_MIMIC)
&& mimic->observable();
// If we got here, we at least got a resists message, if not
// a full wounds printing. Thus, might as well id the mimic.
if (mimic->has_ench(ENCH_TP))
{
if (should_id)
mimic->flags |= MF_KNOWN_MIMIC;
return;
}
const bool instant_tele = !one_chance_in(3);
monster_teleport( mimic, instant_tele );
// At least for this short while, we know it's a mimic.
if (!instant_tele && should_id)
mimic->flags |= MF_KNOWN_MIMIC;
}
void create_feat_at(coord_def center,
dungeon_feature_type overwriteable,
dungeon_feature_type newfeat)
{
if (grd(center) == overwriteable)
dungeon_terrain_changed(center, newfeat, true, false, true);
}
void create_feat_splash(coord_def center,
dungeon_feature_type overwriteable,
dungeon_feature_type newfeat,
int radius,
int nattempts)
{
// Always affect center.
create_feat_at(center, overwriteable, newfeat);
for (int i = 0; i < nattempts; ++i)
{
const coord_def newp(dgn_random_point_visible_from(center, radius));
if (newp.origin() || grd(newp) != overwriteable)
continue;
create_feat_at(newp, overwriteable, newfeat);
}
}
bool bolt::is_bouncy(dungeon_feature_type feat) const
{
if (real_flavour == BEAM_CHAOS && feat_is_solid(feat))
return (true);
if (is_enchantment())
return (false);
if (flavour == BEAM_ELECTRICITY && feat != DNGN_METAL_WALL
&& feat != DNGN_TREES)
{
return (true);
}
if ((flavour == BEAM_FIRE || flavour == BEAM_COLD)
&& feat == DNGN_GREEN_CRYSTAL_WALL )
{
return (true);
}
return (false);
}
static int _potion_beam_flavour_to_colour(beam_type flavour)
{
switch (flavour)
{
case BEAM_POTION_STINKING_CLOUD:
return (GREEN);
case BEAM_POTION_POISON:
return (coinflip() ? GREEN : LIGHTGREEN);
case BEAM_POTION_MIASMA:
case BEAM_POTION_BLACK_SMOKE:
return (DARKGREY);
case BEAM_POTION_STEAM:
case BEAM_POTION_GREY_SMOKE:
return (LIGHTGREY);
case BEAM_POTION_FIRE:
return (coinflip() ? RED : LIGHTRED);
case BEAM_POTION_COLD:
return (coinflip() ? BLUE : LIGHTBLUE);
case BEAM_POTION_BLUE_SMOKE:
return (LIGHTBLUE);
case BEAM_POTION_PURPLE_SMOKE:
return (MAGENTA);
case BEAM_POTION_RANDOM:
default:
// Leave it the colour of the potion, the clouds will colour
// themselves on the next refresh. -- bwr
return (-1);
}
return (-1);
}
void bolt::affect_endpoint()
{
if (special_explosion)
{
special_explosion->refine_for_explosion();
special_explosion->target = pos();
special_explosion->explode();
}
// Leave an object, if applicable.
if (drop_item && item)
drop_object();
if (is_explosion)
{
refine_for_explosion();
target = pos();
explode();
return;
}
if (is_tracer)
return;
if (origin_spell == SPELL_PRIMAL_WAVE) // &&coinflip()
{
if (you.see_cell(pos()))
{
mprf("The wave splashes down.");
noisy(25, pos());
}
else
{
noisy(25, pos(), "You hear a splash.");
}
create_feat_splash(pos(),
DNGN_FLOOR,
DNGN_SHALLOW_WATER,
2,
random_range(1, 9, 2));
}
// FIXME: why don't these just have is_explosion set?
// They don't explode in tracers: why not?
if (name == "orb of electricity"
|| name == "metal orb"
|| name == "great blast of cold")
{
target = pos();
refine_for_explosion();
explode();
}
if (name == "blast of poison")
big_cloud(CLOUD_POISON, whose_kill(), killer(), pos(), 0, 7+random2(5));
if (name == "foul vapour")
{
// death drake; swamp drakes handled earlier
ASSERT(flavour == BEAM_MIASMA);
big_cloud(CLOUD_MIASMA, whose_kill(), killer(), pos(), 0, 9);
}
if (name == "freezing blast")
{
big_cloud(CLOUD_COLD, whose_kill(), killer(), pos(),
random_range(10, 15), 9);
}
}
bool bolt::stop_at_target() const
{
return (is_explosion || is_big_cloud || aimed_at_spot);
}
void bolt::drop_object()
{
ASSERT( item != NULL && item->is_valid() );
// Conditions: beam is missile and not tracer.
if (is_tracer || !was_missile)
return;
// Summoned creatures' thrown items disappear.
if (item->flags & ISFLAG_SUMMONED)
{
if (you.see_cell(pos()))
{
mprf("%s %s!",
item->name(DESC_CAP_THE).c_str(),
summoned_poof_msg(beam_source, *item).c_str());
}
item_was_destroyed(*item, beam_source);
return;
}
if (!thrown_object_destroyed(item, pos()))
{
if (item->sub_type == MI_THROWING_NET)
{
monsters* m = monster_at(pos());
// Player or monster at position is caught in net.
if (you.pos() == pos() && you.attribute[ATTR_HELD]
|| m && m->caught())
{
// If no trapping net found mark this one.
if (get_trapping_net(pos(), true) == NON_ITEM)
set_item_stationary(*item);
}
}
copy_item_to_grid(*item, pos(), 1);
}
else if (item->sub_type == MI_LARGE_ROCK)
{
// Large rocks mulch to stone.
std::string sound_msg = "You hear a cracking sound!";
if (you.see_cell(pos()))
{
mprf("%s shatters into pieces!",
item->name(DESC_CAP_THE).c_str());
sound_msg = "";
}
noisy(12, pos(), sound_msg.c_str());
item->sub_type = MI_STONE;
item->quantity = 10 + random2(41);
// Remove thrown flag: we might not want to pick up the stones.
item->flags &= ~ISFLAG_THROWN;
copy_item_to_grid(*item, pos(), item->quantity);
}
else
{
item_was_destroyed(*item, NON_MONSTER);
}
}
// Returns true if the beam hits the player, fuzzing the beam if necessary
// for monsters without see invis firing tracers at the player.
bool bolt::found_player() const
{
const bool needs_fuzz = (is_tracer && !can_see_invis
&& you.invisible() && !YOU_KILL(thrower));
const int dist = needs_fuzz? 2 : 0;
return (grid_distance(pos(), you.pos()) <= dist);
}
void bolt::affect_ground()
{
// Explosions only have an effect during their explosion phase.
// Special cases can be handled here.
if (is_explosion && !in_explosion_phase)
return;
if (is_tracer)
return;
// Spore explosions might spawn a fungus. The spore explosion
// covers 21 tiles in open space, so the expected number of spores
// produced is the x in x_chance_in_y() in the conditional below.
if (is_explosion && flavour == BEAM_SPORE
&& x_chance_in_y(2, 21)
&& mons_class_can_pass(MONS_BALLISTOMYCETE, env.grid(pos()))
&& !actor_at(pos()))
{
beh_type beh;
// Half the fungi in arena mode are friendly.
if (crawl_state.arena)
{
beh = coinflip() ? BEH_FRIENDLY : BEH_HOSTILE;
}
else
{
switch (this->attitude)
{
case ATT_NEUTRAL:
beh = BEH_NEUTRAL;
break;
case ATT_FRIENDLY:
case ATT_GOOD_NEUTRAL:
beh = BEH_GOOD_NEUTRAL;
break;
default:
beh = BEH_HOSTILE;
break;
}
}
int rc = create_monster(mgen_data(MONS_BALLISTOMYCETE,
beh,
agent(),
0,
0,
pos(),
MHITNOT,
MG_FORCE_PLACE));
if (rc != -1 && you.see_cell(pos()))
mpr("A fungus suddenly grows.");
}
if (affects_items)
{
const int burn_power = is_explosion ? 5 :
is_beam ? 3
: 2;
expose_items_to_element(flavour, pos(), burn_power);
affect_place_clouds();
}
}
bool bolt::is_fiery() const
{
return (flavour == BEAM_FIRE
|| flavour == BEAM_HELLFIRE
|| flavour == BEAM_LAVA);
}
bool bolt::is_superhot() const
{
if (!is_fiery() && flavour != BEAM_ELECTRICITY)
return (false);
return (name == "bolt of fire"
|| name == "bolt of magma"
|| name == "fireball"
|| name == "bolt of lightning"
|| name.find("hellfire") != std::string::npos
&& in_explosion_phase);
}
bool bolt::affects_wall(dungeon_feature_type wall) const
{
// digging
if (flavour == BEAM_DIGGING)
return (true);
if (flavour == BEAM_DISINTEGRATION && damage.num >= 3)
return (true);
if (is_fiery() && (wall == DNGN_WAX_WALL || wall == DNGN_TREES))
return (true);
if (flavour == BEAM_ELECTRICITY && wall == DNGN_TREES)
return (true);
// eye of devastation?
if (flavour == BEAM_NUKE)
return (true);
// Lee's Rapid Deconstruction
if (flavour == BEAM_FRAG)
return (true);
return (false);
}
void bolt::affect_place_clouds()
{
if (in_explosion_phase)
affect_place_explosion_clouds();
const coord_def p = pos();
// Is there already a cloud here?
const int cloudidx = env.cgrid(p);
if (cloudidx != EMPTY_CLOUD)
{
cloud_type& ctype = env.cloud[cloudidx].type;
// Polymorph randomly changes clouds in its path
if (flavour == BEAM_POLYMORPH)
{
cloud_type new_type = static_cast<cloud_type>(1 + random2(8));
if (new_type == ctype)
return;
if (p == you.pos())
{
mprf("The %s you are in turns into %s!",
cloud_name(cloudidx).c_str(), cloud_name(new_type).c_str());
obvious_effect = true;
}
else if (you.see_cell(p))
{
mprf("A cloud of %s turns into %s.",
cloud_name(cloudidx).c_str(), cloud_name(new_type).c_str());
obvious_effect = true;
}
ctype = new_type;
env.cloud[cloudidx].name = "";
return;
}
// fire cancelling cold & vice versa
if ((ctype == CLOUD_COLD
&& (flavour == BEAM_FIRE || flavour == BEAM_LAVA))
|| (ctype == CLOUD_FIRE && flavour == BEAM_COLD))
{
if (player_can_hear(p))
mpr("You hear a sizzling sound!", MSGCH_SOUND);
delete_cloud(cloudidx);
range_used += 5;
}
return;
}
// No clouds here, free to make new ones.
const dungeon_feature_type feat = grd(p);
if (name == "blast of poison")
place_cloud(CLOUD_POISON, p, random2(4) + 2, whose_kill(), killer());
// Fire/cold over water/lava
if (feat == DNGN_LAVA && flavour == BEAM_COLD
|| feat_is_watery(feat) && is_fiery())
{
place_cloud(CLOUD_STEAM, p, 2 + random2(5), whose_kill(), killer());
}
if (feat_is_watery(feat) && flavour == BEAM_COLD
&& damage.num * damage.size > 35)
{
place_cloud(CLOUD_COLD, p, damage.num * damage.size / 30 + 1,
whose_kill(), killer());
}
if (name == "great blast of cold")
place_cloud(CLOUD_COLD, p, random2(5) + 3, whose_kill(), killer());
if (name == "ball of steam")
place_cloud(CLOUD_STEAM, p, random2(5) + 2, whose_kill(), killer());
if (flavour == BEAM_MIASMA)
place_cloud(CLOUD_MIASMA, p, random2(5) + 2, whose_kill(), killer());
if (name == "poison gas")
place_cloud(CLOUD_POISON, p, random2(4) + 3, whose_kill(), killer());
}
void bolt::affect_place_explosion_clouds()
{
const coord_def p = pos();
// First check: fire/cold over water/lava.
if (grd(p) == DNGN_LAVA && flavour == BEAM_COLD
|| feat_is_watery(grd(p)) && is_fiery())
{
place_cloud(CLOUD_STEAM, p, 2 + random2(5), whose_kill(), killer());
return;
}
if (flavour >= BEAM_POTION_STINKING_CLOUD && flavour <= BEAM_POTION_RANDOM)
{
const int duration = roll_dice(2, 3 + ench_power / 20);
cloud_type cl_type;
switch (flavour)
{
case BEAM_POTION_STINKING_CLOUD:
case BEAM_POTION_POISON:
case BEAM_POTION_MIASMA:
case BEAM_POTION_STEAM:
case BEAM_POTION_FIRE:
case BEAM_POTION_COLD:
case BEAM_POTION_BLACK_SMOKE:
case BEAM_POTION_GREY_SMOKE:
case BEAM_POTION_BLUE_SMOKE:
case BEAM_POTION_PURPLE_SMOKE:
case BEAM_POTION_RAIN:
case BEAM_POTION_MUTAGENIC:
cl_type = beam2cloud(flavour);
break;
case BEAM_POTION_RANDOM:
switch (random2(10))
{
case 0: cl_type = CLOUD_FIRE; break;
case 1: cl_type = CLOUD_STINK; break;
case 2: cl_type = CLOUD_COLD; break;
case 3: cl_type = CLOUD_POISON; break;
case 4: cl_type = CLOUD_BLACK_SMOKE; break;
case 5: cl_type = CLOUD_GREY_SMOKE; break;
case 6: cl_type = CLOUD_BLUE_SMOKE; break;
case 7: cl_type = CLOUD_PURPLE_SMOKE; break;
default: cl_type = CLOUD_STEAM; break;
}
break;
default:
cl_type = CLOUD_STEAM;
break;
}
place_cloud(cl_type, p, duration, whose_kill(), killer());
}
// then check for more specific explosion cloud types.
if (name == "ice storm")
place_cloud(CLOUD_COLD, p, 2 + random2avg(5,2), whose_kill(), killer());
if (name == "stinking cloud")
{
const int duration = 1 + random2(4) + random2((ench_power / 50) + 1);
place_cloud( CLOUD_STINK, p, duration, whose_kill(), killer() );
}
if (name == "great blast of fire")
{
int duration = 1 + random2(5) + roll_dice(2, ench_power / 5);
if (duration > 20)
duration = 20 + random2(4);
place_cloud( CLOUD_FIRE, p, duration, whose_kill(), killer() );
if (grd(p) == DNGN_FLOOR && !monster_at(p) && one_chance_in(4))
{
const god_type god =
(crawl_state.is_god_acting()) ? crawl_state.which_god_acting()
: GOD_NO_GOD;
const beh_type att =
(whose_kill() == KC_OTHER ? BEH_HOSTILE : BEH_FRIENDLY);
actor* summ = agent();
mgen_data mg(MONS_FIRE_VORTEX, att, summ, 2, SPELL_FIRE_STORM,
p, MHITNOT, 0, god);
// Spell-summoned monsters need to have a live summoner.
if (summ == NULL || !summ->alive())
{
if (!source_name.empty())
mg.non_actor_summoner = source_name;
else if (god != GOD_NO_GOD)
mg.non_actor_summoner = god_name(god);
}
mons_place(mg);
}
}
}
// A little helper function to handle the calling of ouch()...
void bolt::internal_ouch(int dam)
{
monsters *monst = NULL;
if (!invalid_monster_index(beam_source) && menv[beam_source].type != -1)
monst = &menv[beam_source];
// The order of this is important.
if (monst && (monst->type == MONS_GIANT_SPORE
|| monst->type == MONS_BALL_LIGHTNING))
{
ouch(dam, beam_source, KILLED_BY_SPORE, aux_source.c_str());
}
else if (YOU_KILL(thrower) && aux_source.empty())
{
if (reflections > 0)
ouch(dam, reflector, KILLED_BY_REFLECTION, name.c_str());
else if (bounces > 0)
ouch(dam, NON_MONSTER, KILLED_BY_BOUNCE, name.c_str());
else
{
if (aimed_at_feet && effect_known)
ouch(dam, NON_MONSTER, KILLED_BY_SELF_AIMED, name.c_str());
else
ouch(dam, NON_MONSTER, KILLED_BY_TARGETTING);
}
}
else if (flavour == BEAM_DISINTEGRATION || flavour == BEAM_NUKE)
ouch(dam, beam_source, KILLED_BY_DISINT, aux_source.c_str());
else if (MON_KILL(thrower))
ouch(dam, beam_source, KILLED_BY_BEAM, aux_source.c_str());
else // KILL_MISC || (YOU_KILL && aux_source)
ouch(dam, beam_source, KILLED_BY_WILD_MAGIC, aux_source.c_str());
}
// [ds] Apply a fuzz if the monster lacks see invisible and is trying to target
// an invisible player. This makes invisibility slightly more powerful.
bool bolt::fuzz_invis_tracer()
{
// Did the monster have a rough idea of where you are?
int dist = grid_distance(target, you.pos());
// No, ditch this.
if (dist > 2)
return (false);
const int beam_src = beam_source_as_target();
if (beam_src != MHITNOT && beam_src != MHITYOU)
{
// Monsters that can sense invisible
const monsters *mon = &menv[beam_src];
if (mons_sense_invis(mon))
return (dist == 0);
}
// Apply fuzz now.
coord_def fuzz( random_range(-2, 2), random_range(-2, 2) );
coord_def newtarget = target + fuzz;
if (in_bounds(newtarget))
target = newtarget;
// Fire away!
return (true);
}
// A first step towards to-hit sanity for beams. We're still being
// very kind to the player, but it should be fairer to monsters than
// 4.0.
static bool _test_beam_hit(int attack, int defence, bool is_beam,
bool deflect, bool repel, defer_rand &r)
{
if (is_beam && deflect)
{
attack = r[0].random2(attack * 2) / 3;
}
else if (is_beam && repel)
{
if (attack >= 2)
attack = r[0].random_range((attack + 1) / 2 + 1, attack);
}
else if (deflect)
{
attack = r[0].random2(attack / 2);
}
else if (repel)
{
attack = r[0].random2(attack);
}
dprf("Beam attack: %d, defence: %d", attack, defence);
// Reproducing old behavior here; magic dart is dodgable with DMsl
if (attack == AUTOMATIC_HIT)
return (true);
attack = r[1].random2(attack);
defence = r[2].random2avg(defence, 2);
dprf("Beam new attack: %d, defence: %d", attack, defence);
return (attack >= defence);
}
std::string bolt::zapper() const
{
const int beam_src = beam_source_as_target();
if (beam_src == MHITYOU)
return ("self");
else if (beam_src == MHITNOT)
return ("");
else
return menv[beam_src].name(DESC_PLAIN);
}
bool bolt::is_harmless(const monsters *mon) const
{
// For enchantments, this is already handled in nasty_to().
if (is_enchantment())
return (!nasty_to(mon));
// The others are handled here.
switch (flavour)
{
case BEAM_VISUAL:
case BEAM_DIGGING:
return (true);
case BEAM_HOLY:
return (mon->res_holy_energy(agent()) > 0);
case BEAM_STEAM:
return (mon->res_steam() >= 3);
case BEAM_FIRE:
return (mon->res_fire() >= 3);
case BEAM_COLD:
return (mon->res_cold() >= 3);
case BEAM_MIASMA:
return (mon->res_rotting());
case BEAM_NEG:
return (mon->res_negative_energy() == 3);
case BEAM_ELECTRICITY:
return (mon->res_elec() >= 3);
case BEAM_POISON:
return (mon->res_poison() >= 3);
case BEAM_ACID:
return (mon->res_acid() >= 3);
default:
return (false);
}
}
bool bolt::harmless_to_player() const
{
dprf("beam flavour: %d", flavour);
switch (flavour)
{
case BEAM_VISUAL:
case BEAM_DIGGING:
return (true);
// Positive enchantments.
case BEAM_HASTE:
case BEAM_HEALING:
case BEAM_INVISIBILITY:
return (true);
case BEAM_HOLY:
return (is_good_god(you.religion));
case BEAM_STEAM:
return (player_res_steam(false) >= 3);
case BEAM_MIASMA:
return (you.res_rotting());
case BEAM_NEG:
return (player_prot_life(false) >= 3);
case BEAM_POISON:
return (player_res_poison(false));
case BEAM_POTION_STINKING_CLOUD:
return (player_res_poison(false) || player_mental_clarity(false));
case BEAM_ELECTRICITY:
return (player_res_electricity(false));
case BEAM_FIRE:
case BEAM_COLD:
case BEAM_ACID:
// Fire and ice can destroy inventory items, acid damage equipment.
return (false);
default:
return (false);
}
}
bool bolt::is_reflectable(const item_def *it) const
{
if (range_used >= range)
return (false);
return (it && is_shield(*it) && shield_reflects(*it));
}
// Reflect a beam back the direction it came. This is used
// by shields of reflection.
void bolt::reflect()
{
reflections++;
// If it bounced off a wall before being reflected then head back towards
// the wall.
if (bounces > 0 && map_bounds(bounce_pos))
target = bounce_pos;
else
target = source;
source = pos();
// Reset bounce_pos, so that if we somehow reflect again before reaching
// the wall that we won't keep heading towards the wall.
bounce_pos.reset();
if (pos() == you.pos())
reflector = NON_MONSTER;
else if (monsters* m = monster_at(pos()))
reflector = m->mindex();
else
{
reflector = -1;
#ifdef DEBUG
mprf(MSGCH_DIAGNOSTICS, "Bolt reflected by neither player nor "
"monster (bolt = %s, item = %s)", name.c_str(),
item ? item->name(DESC_PLAIN).c_str() : "none");
#endif
}
flavour = real_flavour;
choose_ray();
}
void bolt::tracer_affect_player()
{
// Check whether thrower can see player, unless thrower == player.
if (YOU_KILL(thrower))
{
// Don't ask if we're aiming at ourselves.
if (!aimed_at_feet && !dont_stop_player && !harmless_to_player())
{
if (yesno("That beam is likely to hit you. Continue anyway?",
false, 'n'))
{
friend_info.count++;
friend_info.power += you.experience_level;
dont_stop_player = true;
}
else
{
beam_cancelled = true;
finish_beam();
}
}
}
else if (can_see_invis || !you.invisible() || fuzz_invis_tracer())
{
if (mons_att_wont_attack(attitude))
{
friend_info.count++;
friend_info.power += you.experience_level;
}
else
{
foe_info.count++;
foe_info.power += you.experience_level;
}
}
std::vector<std::string> messages;
int dummy = 0;
apply_dmg_funcs(&you, dummy, messages);
for (unsigned int i = 0; i < messages.size(); ++i)
mpr(messages[i].c_str(), MSGCH_WARN);
apply_hit_funcs(&you, 0);
range_used += range_used_on_hit(&you);
}
bool bolt::misses_player()
{
if (is_explosion || aimed_at_feet || auto_hit || is_enchantment())
return (false);
const int dodge = player_evasion();
const int dodge_less = player_evasion(EV_IGNORE_PHASESHIFT);
int real_tohit = hit;
// Monsters shooting at an invisible player are very inaccurate.
if (you.invisible() && !can_see_invis)
real_tohit /= 2;
if (you.backlit() && !you.halo_radius())
real_tohit += 2 + random2(8);
// Wow, what a horrid test. These cannot be blocked or dodged
if (!is_beam && !is_blockable())
return (false);
bool train_shields_more = false;
if (is_blockable()
&& you.shield()
&& !aimed_at_feet
&& player_shield_class() > 0)
{
// We use the original to-hit here.
const int testhit = random2(hit * 130 / 100
+ you.shield_block_penalty());
const int block = you.shield_bonus();
dprf("Beamshield: hit: %d, block %d", testhit, block);
if (testhit < block)
{
if (is_reflectable(you.shield()))
{
mprf( "Your %s reflects the %s!",
you.shield()->name(DESC_PLAIN).c_str(),
name.c_str() );
ident_reflector(you.shield());
reflect();
}
else
{
mprf( "You block the %s.", name.c_str() );
finish_beam();
}
you.shield_block_succeeded(agent());
return (true);
}
// Some training just for the "attempt".
train_shields_more = true;
}
if (player_light_armour(true) && !aimed_at_feet && coinflip())
exercise(SK_DODGING, 1);
defer_rand r;
bool miss = true;
bool dmsl = you.duration[DUR_DEFLECT_MISSILES];
bool rmsl = dmsl || you.duration[DUR_REPEL_MISSILES]
|| player_mutation_level(MUT_REPULSION_FIELD) == 3;
if (!_test_beam_hit(real_tohit, dodge_less, is_beam, false, false, r))
{
mprf("The %s misses you.", name.c_str());
}
else if (!_test_beam_hit(real_tohit, dodge_less, is_beam, false, rmsl, r))
{
mprf("The %s is repelled.", name.c_str());
}
else if (!_test_beam_hit(real_tohit, dodge_less, is_beam, dmsl, rmsl, r))
{
// active voice to imply stronger effect
mprf("You deflect the %s!", name.c_str());
}
else if (!_test_beam_hit(real_tohit, dodge, is_beam, dmsl, rmsl, r))
{
mprf("You momentarily phase out as the %s "
"passes through you.", name.c_str());
}
else
{
const bool engulfs = is_explosion || is_big_cloud;
int dodge_more = player_evasion(EV_IGNORE_HELPLESS);
if (hit_verb.empty())
hit_verb = engulfs ? "engulfs" : "hits";
if (_test_beam_hit(real_tohit, dodge_more, is_beam, dmsl, rmsl, r))
{
mprf("The %s %s you!", name.c_str(), hit_verb.c_str());
}
else
{
mprf("Helpless, you fail to dodge the %s.", name.c_str());
}
miss = false;
}
if (coinflip() && train_shields_more)
exercise(SK_SHIELDS, one_chance_in(3) ? 1 : 0);
return (miss);
}
void bolt::affect_player_enchantment()
{
if (flavour != BEAM_POLYMORPH && has_saving_throw()
&& you.check_res_magic(ench_power))
{
// You resisted it.
// Give a message.
bool need_msg = true;
if (thrower != KILL_YOU_MISSILE && !invalid_monster_index(beam_source))
{
monsters *mon = &menv[beam_source];
if (!mon->observable())
{
mpr("Something tries to affect you, but you resist.");
need_msg = false;
}
}
if (need_msg)
canned_msg(MSG_YOU_RESIST);
// You *could* have gotten a free teleportation in the Abyss,
// but no, you resisted.
if (flavour == BEAM_TELEPORT && you.level_type == LEVEL_ABYSS)
xom_is_stimulated(255);
range_used += range_used_on_hit(&you);
return;
}
// You didn't resist it.
if (effect_known)
_ench_animation(real_flavour);
else
_zap_animation(-1);
bool nasty = true, nice = false;
switch (flavour)
{
case BEAM_HIBERNATION:
you.hibernate(ench_power);
break;
case BEAM_SLEEP:
you.put_to_sleep(NULL, ench_power);
break;
case BEAM_CORONA:
you.backlight();
obvious_effect = true;
break;
case BEAM_POLYMORPH:
if (MON_KILL(thrower))
{
mpr("Strange energies course through your body.");
you.mutate();
obvious_effect = true;
}
else if (get_ident_type(OBJ_WANDS, WAND_POLYMORPH_OTHER)
== ID_KNOWN_TYPE)
{
mpr("This is polymorph other only!");
}
else
canned_msg(MSG_NOTHING_HAPPENS);
break;
case BEAM_SLOW:
potion_effect( POT_SLOWING, ench_power );
obvious_effect = true;
break;
case BEAM_HASTE:
potion_effect( POT_SPEED, ench_power, false, thrower == KILL_YOU_MISSILE );
contaminate_player( 1, effect_known );
obvious_effect = true;
nasty = false;
nice = true;
break;
case BEAM_HEALING:
potion_effect( POT_HEAL_WOUNDS, ench_power );
obvious_effect = true;
nasty = false;
nice = true;
break;
case BEAM_PARALYSIS:
potion_effect( POT_PARALYSIS, ench_power );
obvious_effect = true;
break;
case BEAM_PETRIFY:
you.petrify( agent(), ench_power );
obvious_effect = true;
break;
case BEAM_CONFUSION:
potion_effect( POT_CONFUSION, ench_power );
obvious_effect = true;
break;
case BEAM_INVISIBILITY:
potion_effect( POT_INVISIBILITY, ench_power );
contaminate_player( 1 + random2(2), effect_known );
obvious_effect = true;
nasty = false;
nice = true;
break;
case BEAM_TELEPORT:
you_teleport();
// An enemy helping you escape while in the Abyss, or an
// enemy stabilizing a teleport that was about to happen.
if (!mons_att_wont_attack(attitude)
&& you.level_type == LEVEL_ABYSS)
{
xom_is_stimulated(255);
}
obvious_effect = true;
break;
case BEAM_BLINK:
random_blink(false);
obvious_effect = true;
break;
case BEAM_BLINK_CLOSE:
blink_other_close(&you, source);
obvious_effect = true;
break;
case BEAM_CHARM:
potion_effect( POT_CONFUSION, ench_power );
obvious_effect = true;
break; // enslavement - confusion?
case BEAM_BANISH:
if (YOU_KILL(thrower))
{
mpr("This spell isn't strong enough to banish yourself.");
break;
}
if (you.level_type == LEVEL_ABYSS)
{
mpr("You feel trapped.");
break;
}
you.banished = true;
you.banished_by = zapper();
obvious_effect = true;
break;
case BEAM_PAIN:
if (player_res_torment())
{
mpr("You are unaffected.");
break;
}
if (aux_source.empty())
aux_source = "by nerve-wracking pain";
if (name.find("agony") != std::string::npos)
{
if (you.res_negative_energy()) // Agony has no effect with rN.
{
mpr("You are unaffected.");
break;
}
mpr("Your body is wracked with pain!");
// On the player, Agony acts like single-target torment.
internal_ouch(std::max(0, you.hp / 2 - 1));
}
else
{
mpr("Pain shoots through your body!");
internal_ouch(damage.roll());
}
obvious_effect = true;
break;
case BEAM_DISPEL_UNDEAD:
if (!you.is_undead)
{
mpr("You are unaffected.");
break;
}
mpr("You convulse!");
if (aux_source.empty())
aux_source = "by dispel undead";
if (you.is_undead == US_SEMI_UNDEAD)
{
if (you.hunger_state == HS_ENGORGED)
damage.size /= 2;
else if (you.hunger_state > HS_SATIATED)
{
damage.size *= 2;
damage.size /= 3;
}
}
internal_ouch(damage.roll());
obvious_effect = true;
break;
case BEAM_DISINTEGRATION:
mpr("You are blasted!");
if (aux_source.empty())
aux_source = "a disintegration bolt";
{
int amt = damage.roll();
internal_ouch(amt);
if (you.can_bleed())
blood_spray(you.pos(), MONS_PLAYER, amt / 5);
}
obvious_effect = true;
break;
case BEAM_PORKALATOR:
if (!transform(ench_power, TRAN_PIG, true))
{
mpr("You feel like a pig.");
break;
}
obvious_effect = true;
break;
case BEAM_BERSERK:
potion_effect( POT_BERSERK_RAGE, ench_power );
obvious_effect = true;
break;
case BEAM_MIGHT:
potion_effect( POT_MIGHT, ench_power );
obvious_effect = true;
break;
default:
// _All_ enchantments should be enumerated here!
mpr("Software bugs nibble your toes!");
break;
}
if (nasty)
{
if (mons_att_wont_attack(attitude))
{
friend_info.hurt++;
if (beam_source == NON_MONSTER)
{
// Beam from player rebounded and hit player.
if (!aimed_at_feet)
xom_is_stimulated(255);
}
else
{
// Beam from an ally or neutral.
xom_is_stimulated(128);
}
}
else
foe_info.hurt++;
}
else if (nice)
{
if (mons_att_wont_attack(attitude))
friend_info.helped++;
else
{
foe_info.helped++;
xom_is_stimulated(128);
}
}
apply_hit_funcs(&you, 0);
// Regardless of effect, we need to know if this is a stopper
// or not - it seems all of the above are.
range_used += range_used_on_hit(&you);
}
void bolt::affect_player()
{
// Explosions only have an effect during their explosion phase.
// Special cases can be handled here.
if (is_explosion && !in_explosion_phase)
{
// Trigger the explosion.
finish_beam();
return;
}
// Digging -- don't care.
if (flavour == BEAM_DIGGING)
return;
if (is_tracer)
{
tracer_affect_player();
return;
}
// Trigger an interrupt, so travel will stop on misses which
// generate smoke.
if (!YOU_KILL(thrower))
interrupt_activity(AI_MONSTER_ATTACKS);
if (is_enchantment())
{
affect_player_enchantment();
return;
}
msg_generated = true;
if (misses_player())
return;
const bool engulfs = is_explosion || is_big_cloud;
// FIXME: Lots of duplicated code here (compare handling of
// monsters)
int hurted = 0;
int burn_power = (is_explosion) ? 5 : (is_beam) ? 3 : 2;
// Roll the damage.
hurted += damage.roll();
#if DEBUG_DIAGNOSTICS
int roll = hurted;
#endif
std::vector<std::string> messages;
apply_dmg_funcs(&you, hurted, messages);
int armour_damage_reduction = random2( 1 + you.armour_class() );
if (flavour == BEAM_ELECTRICITY)
armour_damage_reduction /= 2;
hurted -= armour_damage_reduction;
// shrapnel has triple AC reduction
if (flavour == BEAM_FRAG && !player_light_armour())
{
hurted -= random2( 1 + you.armour_class() );
hurted -= random2( 1 + you.armour_class() );
}
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS,
"Player damage: rolled=%d; after AC=%d", roll, hurted );
#endif
if (you.equip[EQ_BODY_ARMOUR] != -1)
{
if (!player_light_armour(false) && one_chance_in(4)
&& x_chance_in_y(item_mass(you.inv[you.equip[EQ_BODY_ARMOUR]]) + 1,
1000))
{
exercise( SK_ARMOUR, 1 );
}
}
bool was_affected = false;
int old_hp = you.hp;
hurted = std::max(0, hurted);
// If the beam is an actual missile or of the MMISSILE type (Earth magic)
// we might bleed on the floor.
if (!engulfs
&& (flavour == BEAM_MISSILE || flavour == BEAM_MMISSILE))
{
// assumes DVORP_PIERCING, factor: 0.5
int blood = std::min(you.hp, hurted / 2);
bleed_onto_floor(you.pos(), MONS_PLAYER, blood, true);
}
hurted = check_your_resists(hurted, flavour);
if (flavour == BEAM_MIASMA && hurted > 0)
was_affected = miasma_player();
if (flavour == BEAM_NUKE) // DISINTEGRATION already handled
blood_spray(you.pos(), MONS_PLAYER, hurted / 5);
// Confusion effect for spore explosions
if (flavour == BEAM_SPORE && hurted && you.holiness() != MH_UNDEAD)
potion_effect( POT_CONFUSION, 1);
// handling of missiles
if (item && item->base_type == OBJ_MISSILES)
{
// SPMSL_POISONED is handled via callback _poison_hit_victim()
// in item_use.cc.
if (item->sub_type == MI_THROWING_NET)
{
if (player_caught_in_net())
{
if (beam_source != NON_MONSTER)
xom_is_stimulated(64);
was_affected = true;
}
}
else if (item->special == SPMSL_CURARE)
{
if (x_chance_in_y(90 - 3 * you.armour_class(), 100))
{
curare_hits_player(actor_to_death_source(agent()),
1 + random2(3));
was_affected = true;
}
}
}
// Sticky flame.
if (name == "sticky flame")
{
if (!player_res_sticky_flame())
{
napalm_player(random2avg(7, 3) + 1);
was_affected = true;
}
}
// Acid.
if (flavour == BEAM_ACID)
splash_with_acid(5, affects_items);
if (affects_items)
{
// Simple cases for scroll burns.
if (flavour == BEAM_LAVA || name.find("hellfire") != std::string::npos)
expose_player_to_element(BEAM_LAVA, burn_power);
// More complex (geez..)
if (flavour == BEAM_FIRE && name != "ball of steam")
expose_player_to_element(BEAM_FIRE, burn_power);
// Potions exploding.
if (flavour == BEAM_COLD)
expose_player_to_element(BEAM_COLD, burn_power);
// Spore pops.
if (in_explosion_phase && flavour == BEAM_SPORE)
expose_player_to_element(BEAM_SPORE, burn_power);
}
dprf("Damage: %d", hurted );
was_affected = apply_hit_funcs(&you, hurted) || was_affected;
if (hurted > 0 || old_hp < you.hp || was_affected)
{
if (mons_att_wont_attack(attitude))
{
friend_info.hurt++;
// Beam from player rebounded and hit player.
// Xom's amusement at the player's being damaged is handled
// elsewhere.
if (beam_source == NON_MONSTER)
{
if (!aimed_at_feet)
xom_is_stimulated(255);
}
else if (was_affected)
xom_is_stimulated(128);
}
else
foe_info.hurt++;
}
if (hurted > 0)
{
for (unsigned int i = 0; i < messages.size(); ++i)
mpr(messages[i].c_str(), MSGCH_WARN);
}
internal_ouch(hurted);
range_used += range_used_on_hit(&you);
if (flavour == BEAM_WATER && origin_spell == SPELL_PRIMAL_WAVE)
water_hits_actor(&you);
}
int bolt::beam_source_as_target() const
{
return (MON_KILL(thrower) ? beam_source :
thrower == KILL_MISC ? MHITNOT
: MHITYOU);
}
void bolt::update_hurt_or_helped(monsters *mon)
{
if (!mons_atts_aligned(attitude, mons_attitude(mon)))
{
if (nasty_to(mon))
foe_info.hurt++;
else if (nice_to(mon))
{
foe_info.helped++;
// Accidentally helped a foe.
if (!is_tracer && !effect_known)
{
int interest = 128;
if (flavour == BEAM_INVISIBILITY && can_see_invis)
interest = 32;
xom_is_stimulated(interest);
}
}
}
else
{
if (nasty_to(mon))
{
friend_info.hurt++;
// Harmful beam from this monster rebounded and hit the monster.
if (!is_tracer && mon->mindex() == beam_source)
xom_is_stimulated(128);
}
else if (nice_to(mon))
friend_info.helped++;
}
}
void bolt::tracer_enchantment_affect_monster(monsters* mon)
{
// Update friend or foe encountered.
if (!mons_atts_aligned(attitude, mons_attitude(mon)))
{
foe_info.count++;
foe_info.power += mons_power(mon->type);
}
else
{
friend_info.count++;
friend_info.power += mons_power(mon->type);
}
handle_stop_attack_prompt(mon);
if (!beam_cancelled)
{
apply_hit_funcs(mon, 0);
range_used += range_used_on_hit(mon);
}
}
// Return false if we should skip handling this monster.
bool bolt::determine_damage(monsters* mon, int& preac, int& postac, int& final,
std::vector<std::string>& messages)
{
// preac: damage before AC modifier
// postac: damage after AC modifier
// final: damage after AC and resists
// All these are invalid if we return false.
// Tracers get the mean.
if (is_tracer)
preac = (damage.num * (damage.size + 1)) / 2;
else
preac = damage.roll();
if (!apply_dmg_funcs(mon, preac, messages))
return (false);
// Submerged monsters get some perks.
if (mon->submerged())
{
// The beam will pass overhead unless it's aimed at them.
if (!aimed_at_spot)
return (false);
// Electricity is ineffective.
if (flavour == BEAM_ELECTRICITY)
{
if (!is_tracer && you.see_cell(mon->pos()))
mprf("The %s arcs harmlessly into the water.", name.c_str());
finish_beam();
return (false);
}
// Otherwise, 1/3 damage reduction.
preac = (preac * 2) / 3;
}
postac = preac - maybe_random2(1 + mon->ac, !is_tracer);
// Fragmentation has triple AC reduction.
if (flavour == BEAM_FRAG)
{
postac -= maybe_random2(1 + mon->ac, !is_tracer);
postac -= maybe_random2(1 + mon->ac, !is_tracer);
}
postac = std::max(postac, 0);
// Don't do side effects (beam might miss or be a tracer).
final = mons_adjust_flavoured(mon, *this, postac, false);
return (true);
}
void bolt::handle_stop_attack_prompt(monsters* mon)
{
if ((thrower == KILL_YOU_MISSILE || thrower == KILL_YOU)
&& !is_harmless(mon))
{
if (friend_info.count == 1 && !friend_info.dont_stop
|| foe_info.count == 1 && !foe_info.dont_stop)
{
if (stop_attack_prompt(mon, true, target))
{
beam_cancelled = true;
finish_beam();
}
else
{
if (friend_info.count == 1)
friend_info.dont_stop = true;
else if (foe_info.count == 1)
foe_info.dont_stop = true;
}
}
}
}
void bolt::tracer_nonenchantment_affect_monster(monsters* mon)
{
std::vector<std::string> messages;
int preac, post, final;
if (!determine_damage(mon, preac, post, final, messages))
return;
// Check only if actual damage.
if (final > 0)
{
// Monster could be hurt somewhat, but only apply the
// monster's power based on how badly it is affected.
// For example, if a fire giant (power 16) threw a
// fireball at another fire giant, and it only took
// 1/3 damage, then power of 5 would be applied.
// Counting foes is only important for monster tracers.
if (!mons_atts_aligned(attitude, mons_attitude(mon)))
{
foe_info.power += 2 * final * mons_power(mon->type) / preac;
foe_info.count++;
}
else
{
friend_info.power += 2 * final * mons_power(mon->type) / preac;
friend_info.count++;
}
}
// Maybe the user wants to cancel at this point.
handle_stop_attack_prompt(mon);
if (beam_cancelled)
return;
// Check only if actual damage.
if (!is_tracer && final > 0)
{
for (unsigned int i = 0; i < messages.size(); ++i)
mpr(messages[i].c_str(), MSGCH_MONSTER_DAMAGE);
}
apply_hit_funcs(mon, final);
// Either way, we could hit this monster, so update range used.
range_used += range_used_on_hit(mon);
}
void bolt::tracer_affect_monster(monsters* mon)
{
// Ignore unseen monsters.
if (!mon->visible_to(&you)
|| (YOU_KILL(thrower) && !you.see_cell(mon->pos())))
{
return;
}
// Trigger explosion on exploding beams.
if (is_explosion && !in_explosion_phase)
{
finish_beam();
return;
}
if (is_enchantment())
tracer_enchantment_affect_monster(mon);
else
tracer_nonenchantment_affect_monster(mon);
}
void bolt::enchantment_affect_monster(monsters* mon)
{
// Submerged monsters are unaffected by enchantments.
if (mon->submerged())
return;
god_conduct_trigger conducts[3];
disable_attack_conducts(conducts);
bool hit_woke_orc = false;
// Nasty enchantments will annoy the monster, and are considered
// naughty (even if a monster might resist).
if (nasty_to(mon))
{
if (YOU_KILL(thrower))
{
if (is_sanctuary(mon->pos()) || is_sanctuary(you.pos()))
remove_sanctuary(true);
set_attack_conducts(conducts, mon, you.can_see(mon));
if (you.religion == GOD_BEOGH
&& mons_species(mon->type) == MONS_ORC
&& mon->asleep() && !player_under_penance()
&& you.piety >= piety_breakpoint(2) && mons_near(mon))
{
hit_woke_orc = true;
}
}
behaviour_event(mon, ME_ANNOY, beam_source_as_target());
}
else
behaviour_event(mon, ME_ALERT, beam_source_as_target());
enable_attack_conducts(conducts);
// Doing this here so that the player gets to see monsters
// "flicker and vanish" when turning invisible....
if (effect_known)
_ench_animation( real_flavour, mon );
else
_zap_animation(-1, mon, false);
// Try to hit the monster with the enchantment.
const mon_resist_type ench_result = try_enchant_monster(mon);
if (mon->alive()) // Aftereffects.
{
// Mimics become known.
if (mons_is_mimic(mon->type))
mimic_alert(mon);
// Message or record the success/failure.
switch (ench_result)
{
case MON_RESIST:
if (simple_monster_message(mon, " resists."))
msg_generated = true;
break;
case MON_UNAFFECTED:
if (simple_monster_message(mon, " is unaffected."))
msg_generated = true;
break;
case MON_AFFECTED:
case MON_OTHER: // Should this really be here?
update_hurt_or_helped(mon);
break;
}
if (hit_woke_orc)
beogh_follower_convert(mon, true);
}
apply_hit_funcs(mon, 0);
range_used += range_used_on_hit(mon);
}
void bolt::monster_post_hit(monsters* mon, int dmg)
{
if (YOU_KILL(thrower) && mons_near(mon))
print_wounds(mon);
// Don't annoy friendlies or good neutrals if the player's beam
// did no damage. Hostiles will still take umbrage.
if (dmg > 0 || !mon->wont_attack() || !YOU_KILL(thrower))
behaviour_event(mon, ME_ANNOY, beam_source_as_target());
// Sticky flame.
if (name == "sticky flame")
{
const int levels = std::min(4, 1 + random2(mon->hit_dice) / 2);
napalm_monster(mon, whose_kill(), levels);
}
bool wake_mimic = true;
// Handle missile effects.
if (item && item->base_type == OBJ_MISSILES)
{
// SPMSL_POISONED handled via callback _poison_hit_victim() in
// item_use.cc
if (item->special == SPMSL_CURARE)
{
if (ench_power == AUTOMATIC_HIT
&& curare_hits_monster(agent(), mon, whose_kill(), 2)
&& !mon->alive())
{
wake_mimic = false;
}
}
}
if (wake_mimic && mons_is_mimic(mon->type))
mimic_alert(mon);
else if (dmg)
beogh_follower_convert(mon, true);
if (flavour == BEAM_WATER && origin_spell == SPELL_PRIMAL_WAVE)
water_hits_actor(mon);
}
void bolt::water_hits_actor(actor *act)
{
const coord_def oldpos(act->pos());
if (knockback_actor(act))
{
if (you.can_see(act))
mprf("%s %s knocked back by the %s.",
act->name(DESC_CAP_THE).c_str(),
act->conj_verb("are").c_str(),
this->name.c_str());
act->apply_location_effects(oldpos, killer(), beam_source);
}
}
// Return true if the block succeeded (including reflections.)
bool bolt::attempt_block(monsters* mon)
{
const int shield_block = mon->shield_bonus();
bool rc = false;
if (shield_block > 0)
{
const int ht = random2(hit * 130 / 100 + mon->shield_block_penalty());
if (ht < shield_block)
{
rc = true;
item_def *shield = mon->mslot_item(MSLOT_SHIELD);
if (is_reflectable(shield))
{
if (mon->observable())
{
mprf("%s reflects the %s off %s %s!",
mon->name(DESC_CAP_THE).c_str(),
name.c_str(),
mon->pronoun(PRONOUN_NOCAP_POSSESSIVE).c_str(),
shield->name(DESC_PLAIN).c_str());
ident_reflector(shield);
}
else if (you.see_cell(pos()))
mprf("The %s bounces off of thin air!", name.c_str());
reflect();
}
else
{
mprf("%s blocks the %s.",
mon->name(DESC_CAP_THE).c_str(),
name.c_str());
finish_beam();
}
mon->shield_block_succeeded(agent());
}
}
return (rc);
}
bool bolt::handle_statue_disintegration(monsters* mon)
{
bool rc = false;
if ((flavour == BEAM_DISINTEGRATION || flavour == BEAM_NUKE)
&& mons_is_statue(mon->type, true))
{
rc = true;
// Disintegrate the statue.
if (!silenced(you.pos()))
{
if (!you.see_cell(mon->pos()))
mpr("You hear a hideous screaming!", MSGCH_SOUND);
else
{
mpr("The statue screams as its substance crumbles away!",
MSGCH_SOUND);
}
}
else if (you.see_cell(mon->pos()))
{
mpr("The statue twists and shakes as its substance "
"crumbles away!");
}
obvious_effect = true;
update_hurt_or_helped(mon);
mon->hurt(agent(), INSTANT_DEATH);
apply_hit_funcs(mon, INSTANT_DEATH);
// Stop here.
finish_beam();
}
return (rc);
}
void bolt::affect_monster(monsters* mon)
{
// Don't hit dead monsters.
if (!mon->alive())
{
apply_hit_funcs(mon, 0);
return;
}
// First some special cases.
// Digging doesn't affect monsters (should it harm earth elementals?)
if (flavour == BEAM_DIGGING)
{
apply_hit_funcs(mon, 0);
return;
}
// Missiles go past bushes.
if (mon->type == MONS_BUSH && !is_beam && !is_explosion
&& (flavour == BEAM_MISSILE || flavour == BEAM_MMISSILE))
{
apply_hit_funcs(mon, 0);
return;
}
if (fedhas_shoot_through(*this, mon))
{
apply_hit_funcs(mon, 0);
if (!is_tracer)
{
// FIXME: Could use a better message, something about
// dodging that doesn't sound excessively weird would be
// nice.
mprf(MSGCH_GOD, "Fedhas protects %s plant from harm.",
attitude == ATT_FRIENDLY ? "your" : "a");
}
return;
}
// Fire storm creates these, so we'll avoid affecting them
if (name == "great blast of fire" && mon->type == MONS_FIRE_VORTEX)
{
apply_hit_funcs(mon, 0);
return;
}
// Handle tracers separately.
if (is_tracer)
{
tracer_affect_monster(mon);
return;
}
// Visual - wake monsters.
if (flavour == BEAM_VISUAL)
{
behaviour_event(mon, ME_DISTURB, beam_source, source);
apply_hit_funcs(mon, 0);
return;
}
// Special case: disintegrate (or Shatter) a statue.
// Since disintegration is an enchantment, it has to be handled
// here.
if (handle_statue_disintegration(mon))
return;
if (is_enchantment())
{
// no to-hit check
enchantment_affect_monster(mon);
return;
}
if (mon->submerged() && !aimed_at_spot)
return; // passes overhead
if (is_explosion && !in_explosion_phase)
{
// It hit a monster, so the beam should terminate.
// Don't actually affect the monster; the explosion
// will take care of that.
finish_beam();
return;
}
// We need to know how much the monster _would_ be hurt by this,
// before we decide if it actually hits.
std::vector<std::string> messages;
int preac, postac, final;
if (!determine_damage(mon, preac, postac, final, messages))
return;
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS,
"Monster: %s; Damage: pre-AC: %d; post-AC: %d; post-resist: %d",
mon->name(DESC_PLAIN).c_str(), preac, postac, final);
#endif
// Player beams which hit friendlies or good neutrals will annoy
// them and be considered naughty if they do damage (this is so as
// not to penalise players that fling fireballs into a melee with
// fire elementals on their side - the elementals won't give a sh*t,
// after all).
god_conduct_trigger conducts[3];
disable_attack_conducts(conducts);
bool hit_woke_orc = false;
if (nasty_to(mon))
{
if (YOU_KILL(thrower) && final > 0)
{
// It's not the player's fault if he didn't see the monster
// or the monster was caught in an unexpected blast of
// ?immolation.
const bool okay =
(!you.can_see(mon)
|| aux_source == "scroll of immolation" && !effect_known);
if (is_sanctuary(mon->pos()) || is_sanctuary(you.pos()))
remove_sanctuary(true);
set_attack_conducts(conducts, mon, !okay);
}
if (you.religion == GOD_BEOGH && mons_species(mon->type) == MONS_ORC
&& mon->asleep() && YOU_KILL(thrower)
&& !player_under_penance() && you.piety >= piety_breakpoint(2)
&& mons_near(mon))
{
hit_woke_orc = true;
}
}
// Explosions always 'hit'.
const bool engulfs = (is_explosion || is_big_cloud);
if (engulfs && flavour == BEAM_SPORE
&& mon->holiness() == MH_NATURAL)
{
apply_enchantment_to_monster(mon);
}
// Make a copy of the to-hit before we modify it.
int beam_hit = hit;
if (mon->invisible() && !can_see_invis)
beam_hit /= 2;
if (mon->backlit() && !mon->halo_radius())
beam_hit += 2 + random2(8);
defer_rand r;
int rand_ev = random2(mon->ev);
bool dmsl = mon->type == MONS_KIRKE;
// FIXME: We're randomising mon->evasion, which is further
// randomised inside test_beam_hit. This is so we stay close to the
// 4.0 to-hit system (which had very little love for monsters).
if (!engulfs && !_test_beam_hit(beam_hit, rand_ev, is_beam, dmsl, false, r))
{
// If the PLAYER cannot see the monster, don't tell them anything!
if (mon->observable())
{
// if it would have hit otherwise...
if (_test_beam_hit(beam_hit, rand_ev, is_beam, false, false, r))
{
msg::stream << mon->name(DESC_CAP_THE) << " deflects the "
<< name << '!' << std::endl;
}
else
{
msg::stream << "The " << name << " misses "
<< mon->name(DESC_NOCAP_THE) << '.' << std::endl;
}
}
return;
}
// The monster may block the beam.
if (!engulfs && is_blockable() && attempt_block(mon))
return;
update_hurt_or_helped(mon);
enable_attack_conducts(conducts);
// We'll say giant spore explosions don't trigger the ally attack conduct
// for Fedhas worshipers. Mostly because you can accidentally blow up a
// group of 8 plants and get placed under penance until the end of time
// otherwise. I'd prefer to do this elsewhere but the beam information
// goes out of scope.
//
// Also exempting miscast explosions from this conduct -cao
if (you.religion == GOD_FEDHAS
&& (flavour == BEAM_SPORE
|| beam_source == NON_MONSTER
&& aux_source.find("your miscasting") != std::string::npos))
{
conducts[0].enabled = false;
}
if (!is_explosion)
heard = noisy(loudness, pos(), beam_source) || heard;
// The beam hit.
if (mons_near(mon))
{
// Monsters don't currently use Phase Shift and are never currently
// helpless in ranged combat.
if (hit_verb.empty())
hit_verb = engulfs ? "engulfs" : "hits";
mprf("The %s %s %s.",
name.c_str(),
hit_verb.c_str(),
mon->observable() ?
mon->name(DESC_NOCAP_THE).c_str() : "something");
}
else if (heard && !noise_msg.empty())
mprf(MSGCH_SOUND, "%s", noise_msg.c_str());
// The player might hear something, if _they_ fired a missile
// (not magic beam).
else if (!silenced(you.pos()) && flavour == BEAM_MISSILE
&& YOU_KILL(thrower))
{
mprf(MSGCH_SOUND, "The %s hits something.", name.c_str());
}
// handling of missiles
if (item
&& item->base_type == OBJ_MISSILES
&& item->sub_type == MI_THROWING_NET)
{
monster_caught_in_net(mon, *this);
}
if (final > 0)
{
for (unsigned int i = 0; i < messages.size(); ++i)
mpr(messages[i].c_str(), MSGCH_MONSTER_DAMAGE);
}
// Apply flavoured specials.
mons_adjust_flavoured(mon, *this, postac, true);
// mons_adjust_flavoured may kill the monster directly.
if (mon->alive())
{
// If the beam is an actual missile or of the MMISSILE type
// (Earth magic) we might bleed on the floor.
if (!engulfs
&& (flavour == BEAM_MISSILE || flavour == BEAM_MMISSILE)
&& !mon->is_summoned() && !mon->submerged())
{
// Using raw_damage instead of the flavoured one!
// assumes DVORP_PIERCING, factor: 0.5
const int blood = std::min(postac/2, mon->hit_points);
bleed_onto_floor(mon->pos(), mon->type, blood, true);
}
// Now hurt monster.
mon->hurt(agent(), final, flavour, false);
}
int corpse = -1;
monsters orig = *mon;
if (mon->alive())
monster_post_hit(mon, final);
else
{
// Preserve name of the source monster if it winds up killing
// itself.
if (mon->mindex() == beam_source && source_name.empty())
source_name = orig.name(DESC_NOCAP_A, true);
// Prevent spore explosions killing plants from being registered
// as a Fedhas misconduct. Deaths can trigger the ally dying or
// plant dying conducts, but spore explosions shouldn't count
// for either of those.
//
// FIXME: Should be a better way of doing this. For now, we are
// just falsifying the death report... -cao
if (you.religion == GOD_FEDHAS && flavour == BEAM_SPORE
&& fedhas_protects(mon))
{
if (mon->attitude == ATT_FRIENDLY)
mon->attitude = ATT_HOSTILE;
corpse = monster_die(mon, KILL_MON, beam_source_as_target());
}
else
{
killer_type ref_killer = thrower;
if (!YOU_KILL(thrower) && reflector == NON_MONSTER)
ref_killer = KILL_YOU_MISSILE;
corpse = monster_die(mon, ref_killer, beam_source_as_target());
}
}
// Give the callbacks a dead-but-valid monster object.
if (mon->type == MONS_NO_MONSTER)
{
orig.hit_points = -1;
mon = &orig;
}
apply_hit_funcs(mon, final, corpse);
range_used += range_used_on_hit(mon);
}
bool bolt::has_saving_throw() const
{
if (aimed_at_feet)
return (false);
switch (flavour)
{
case BEAM_HASTE:
case BEAM_MIGHT:
case BEAM_BERSERK:
case BEAM_HEALING:
case BEAM_INVISIBILITY:
case BEAM_DISPEL_UNDEAD:
case BEAM_ENSLAVE_SOUL: // has a different saving throw
case BEAM_ENSLAVE_DEMON: // ditto
return (false);
default:
return (true);
}
}
bool _ench_flavour_affects_monster(beam_type flavour, const monsters* mon)
{
bool rc = true;
switch (flavour)
{
case BEAM_POLYMORPH:
rc = mon->can_mutate();
break;
case BEAM_DEGENERATE:
rc = (mon->holiness() == MH_NATURAL
&& mon->type != MONS_PULSATING_LUMP);
break;
case BEAM_ENSLAVE_UNDEAD:
rc = (mon->holiness() == MH_UNDEAD && mon->attitude != ATT_FRIENDLY);
break;
case BEAM_ENSLAVE_SOUL:
rc = (mon->holiness() == MH_NATURAL && mon->attitude != ATT_FRIENDLY);
break;
case BEAM_DISPEL_UNDEAD:
rc = (mon->holiness() == MH_UNDEAD);
break;
case BEAM_ENSLAVE_DEMON:
rc = (mon->holiness() == MH_DEMONIC && !mon->friendly());
break;
case BEAM_PAIN:
rc = !mon->res_negative_energy();
break;
case BEAM_HIBERNATION:
rc = mon->can_hibernate();
break;
case BEAM_PORKALATOR:
rc = (mon->holiness() == MH_DEMONIC && mon->type != MONS_HELL_HOG)
|| (mon->holiness() == MH_NATURAL && mon->type != MONS_HOG);
break;
default:
break;
}
return rc;
}
bool enchant_monster_with_flavour(monsters* mon, actor *foe,
beam_type flavour, int powc)
{
bolt dummy;
dummy.flavour = flavour;
dummy.ench_power = powc;
dummy.set_agent(foe);
dummy.apply_enchantment_to_monster(mon);
return dummy.obvious_effect;
}
mon_resist_type bolt::try_enchant_monster(monsters *mon)
{
// Early out if the enchantment is meaningless.
if (!_ench_flavour_affects_monster(flavour, mon))
return (MON_UNAFFECTED);
// Check magic resistance.
if (has_saving_throw())
{
if (mons_immune_magic(mon))
return (MON_UNAFFECTED);
// (Very) ugly things and shapeshifters will never resist
// polymorph beams.
if (flavour == BEAM_POLYMORPH
&& (mon->type == MONS_UGLY_THING
|| mon->type == MONS_VERY_UGLY_THING
|| mon->is_shapeshifter()))
{
;
}
else
{
if (mon->check_res_magic(ench_power))
return (MON_RESIST);
}
}
return (apply_enchantment_to_monster(mon));
}
mon_resist_type bolt::apply_enchantment_to_monster(monsters* mon)
{
// Gigantic-switches-R-Us
switch (flavour)
{
case BEAM_TELEPORT:
if (mon->observable())
obvious_effect = true;
monster_teleport(mon, false);
return (MON_AFFECTED);
case BEAM_BLINK:
if (mon->observable())
obvious_effect = true;
monster_blink(mon);
return (MON_AFFECTED);
case BEAM_BLINK_CLOSE:
if (mon->observable())
obvious_effect = true;
blink_other_close(mon, source);
return (MON_AFFECTED);
case BEAM_POLYMORPH:
if (mon->mutate())
obvious_effect = true;
if (YOU_KILL(thrower))
{
did_god_conduct(DID_DELIBERATE_MUTATING, 2 + random2(3),
effect_known);
}
return (MON_AFFECTED);
case BEAM_BANISH:
if (you.level_type == LEVEL_ABYSS)
simple_monster_message(mon, " wobbles for a moment.");
else
mon->banish();
obvious_effect = true;
return (MON_AFFECTED);
case BEAM_DEGENERATE:
if (monster_polymorph(mon, MONS_PULSATING_LUMP))
obvious_effect = true;
return (MON_AFFECTED);
case BEAM_DISPEL_UNDEAD:
if (simple_monster_message(mon, " convulses!"))
obvious_effect = true;
mon->hurt(agent(), damage.roll());
return (MON_AFFECTED);
case BEAM_ENSLAVE_UNDEAD:
{
const god_type god =
(crawl_state.is_god_acting()) ? crawl_state.which_god_acting()
: GOD_NO_GOD;
dprf("HD: %d; pow: %d", mon->hit_dice, ench_power);
obvious_effect = true;
if (player_will_anger_monster(mon))
{
simple_monster_message(mon, " is repulsed!");
return (MON_OTHER);
}
simple_monster_message(mon, " is enslaved.");
// Wow, permanent enslaving!
mon->attitude = ATT_FRIENDLY;
behaviour_event(mon, ME_ALERT, MHITNOT);
mons_make_god_gift(mon, god);
return (MON_AFFECTED);
}
case BEAM_ENSLAVE_SOUL:
{
dprf("HD: %d; pow: %d", mon->hit_dice, ench_power);
if (!mons_can_be_zombified(mon) || mons_intel(mon) < I_NORMAL)
{
simple_monster_message(mon, " is unaffected.");
return (MON_OTHER);
}
// The monster can be no more than lightly wounded/damaged,
// using the formula from mon-stuff.cc:mons_get_damage_level().
if (mon->hit_points <= mon->max_hit_points * 3 / 4)
{
simple_monster_message(mon, "'s soul is too badly injured.");
return (MON_OTHER);
}
obvious_effect = true;
const int duration = you.skills[SK_INVOCATIONS] * 3 / 4 + 2;
mon->add_ench(mon_enchant(ENCH_SOUL_RIPE, 0, KC_YOU, duration * 10));
simple_monster_message(mon, "'s soul is now ripe for the taking.");
return (MON_AFFECTED);
}
case BEAM_ENSLAVE_DEMON:
dprf("HD: %d; pow: %d", mon->hit_dice, ench_power);
if (mon->hit_dice * 11 / 2 >= random2(ench_power)
|| mons_is_unique(mon->type))
{
return (MON_RESIST);
}
obvious_effect = true;
if (player_will_anger_monster(mon))
{
simple_monster_message(mon, " is repulsed!");
return (MON_OTHER);
}
simple_monster_message(mon, " is enslaved.");
// Wow, permanent enslaving! (sometimes)
if (one_chance_in(2 + mon->hit_dice / 4))
mon->attitude = ATT_FRIENDLY;
else
mon->add_ench(ENCH_CHARM);
behaviour_event(mon, ME_ALERT, MHITNOT);
return (MON_AFFECTED);
case BEAM_PAIN: // pain/agony
if (simple_monster_message(mon, " convulses in agony!"))
obvious_effect = true;
if (name.find("agony") != std::string::npos) // agony
mon->hit_points = std::max(mon->hit_points/2, 1);
else // pain
mon->hurt(agent(), damage.roll(), flavour);
return (MON_AFFECTED);
case BEAM_DISINTEGRATION: // disrupt/disintegrate
if (simple_monster_message(mon, " is blasted."))
obvious_effect = true;
mon->hurt(agent(), damage.roll(), flavour);
return (MON_AFFECTED);
case BEAM_HIBERNATION:
if (mon->can_hibernate())
{
if (simple_monster_message(mon, " looks drowsy..."))
obvious_effect = true;
mon->hibernate();
return (MON_AFFECTED);
}
return (MON_UNAFFECTED);
case BEAM_CORONA:
if (backlight_monsters(mon->pos(), hit, 0))
{
obvious_effect = true;
return (MON_AFFECTED);
}
return (MON_UNAFFECTED);
case BEAM_SLOW:
obvious_effect = do_slow_monster(mon, whose_kill());
return (MON_AFFECTED);
case BEAM_HASTE:
if (mon->del_ench(ENCH_SLOW, true))
{
if (simple_monster_message(mon, " is no longer moving slowly."))
obvious_effect = true;
return (MON_AFFECTED);
}
// Not slowed, haste it.
if (!mon->has_ench(ENCH_HASTE)
&& !mons_is_stationary(mon)
&& mon->add_ench(ENCH_HASTE))
{
if (!mon->paralysed() && !mon->petrified()
&& simple_monster_message(mon, " seems to speed up."))
{
obvious_effect = true;
}
}
return (MON_AFFECTED);
case BEAM_MIGHT:
if (!mon->has_ench(ENCH_MIGHT)
&& !mons_is_stationary(mon)
&& mon->add_ench(ENCH_MIGHT))
{
if (simple_monster_message(mon, " seems to grow stronger."))
obvious_effect = true;
}
return (MON_AFFECTED);
case BEAM_BERSERK:
if (!mon->berserk())
{
// currently from potion, hence voluntary
mon->go_berserk(true);
// can't return this from go_berserk, unfortunately
obvious_effect = mons_near(mon);
}
return (MON_AFFECTED);
case BEAM_HEALING:
if (YOU_KILL(thrower))
{
if (cast_healing(5 + damage.roll(), false, mon->pos()) > 0)
obvious_effect = true;
msg_generated = true; // to avoid duplicate "nothing happens"
}
else if (mon->heal(5 + damage.roll()))
{
if (mon->hit_points == mon->max_hit_points)
{
if (simple_monster_message(mon, "'s wounds heal themselves!"))
obvious_effect = true;
}
else if (simple_monster_message(mon, " is healed somewhat."))
obvious_effect = true;
}
return (MON_AFFECTED);
case BEAM_PARALYSIS:
apply_bolt_paralysis(mon);
return (MON_AFFECTED);
case BEAM_PETRIFY:
apply_bolt_petrify(mon);
return (MON_AFFECTED);
case BEAM_SPORE:
case BEAM_CONFUSION:
if (!mons_class_is_confusable(mon->type))
return (MON_UNAFFECTED);
if (mon->add_ench(mon_enchant(ENCH_CONFUSION, 0, whose_kill())))
{
// FIXME: Put in an exception for things you won't notice
// becoming confused.
if (simple_monster_message(mon, " appears confused."))
obvious_effect = true;
}
return (MON_AFFECTED);
case BEAM_SLEEP:
if (mon->has_ench(ENCH_SLEEPY))
return (MON_UNAFFECTED);
mon->put_to_sleep(agent(), 0);
if (simple_monster_message(mon, " falls asleep!"))
obvious_effect = true;
return (MON_AFFECTED);
case BEAM_INVISIBILITY:
{
// Store the monster name before it becomes an "it" -- bwr
const std::string monster_name = mon->name(DESC_CAP_THE);
if (!mon->has_ench(ENCH_INVIS) && mon->add_ench(ENCH_INVIS))
{
// A casting of invisibility erases backlight.
mon->del_ench(ENCH_CORONA);
// Can't use simple_monster_message() here, since it checks
// for visibility of the monster (and it's now invisible).
// -- bwr
if (mons_near(mon))
{
mprf("%s flickers %s",
monster_name.c_str(),
mon->visible_to(&you) ? "for a moment."
: "and vanishes!" );
if (!mon->visible_to(&you))
autotoggle_autopickup(true);
}
obvious_effect = true;
}
return (MON_AFFECTED);
}
case BEAM_CHARM:
if (player_will_anger_monster(mon))
{
simple_monster_message(mon, " is repulsed!");
return (MON_OTHER);
}
if (!mon->has_ench(ENCH_CHARM))
{
// XXX: Another hackish thing for Pikel's band neutrality.
if (mon->type == MONS_PIKEL)
pikel_band_neutralise();
if (simple_monster_message(mon, " is charmed."))
obvious_effect = true;
mon->add_ench(ENCH_CHARM);
}
return (MON_AFFECTED);
case BEAM_PORKALATOR:
{
// Monster's which use the ghost structure can't be properly
// restored from hog form.
if (mons_is_ghost_demon(mon->type))
return (MON_UNAFFECTED);
monsters orig_mon(*mon);
if (monster_polymorph(mon, (mon->holiness() == MH_DEMONIC ?
MONS_HELL_HOG : MONS_HOG)))
{
obvious_effect = true;
// Don't restore items to monster if it reverts.
orig_mon.inv = mon->inv;
// For monster reverting to original form.
mon->props[ORIG_MONSTER_KEY] = orig_mon;
}
return (MON_AFFECTED);
}
default:
break;
}
return (MON_AFFECTED);
}
// Extra range used on hit.
int bolt::range_used_on_hit(const actor* victim) const
{
int used = 0;
// Non-beams can only affect one thing (player/monster).
if (!is_beam)
used = BEAM_STOP;
else if (is_enchantment())
used = (flavour == BEAM_DIGGING ? 0 : BEAM_STOP);
// Hellfire stops for nobody!
else if (name.find("hellfire") != std::string::npos)
used = 0;
// Generic explosion.
else if (is_explosion || is_big_cloud)
used = BEAM_STOP;
// Plant spit.
else if (flavour == BEAM_ACID)
used = BEAM_STOP;
// Lightning goes through things.
else if (flavour == BEAM_ELECTRICITY)
used = 0;
else
used = 1;
// Assume we didn't hit, after all.
if (is_tracer && beam_source == NON_MONSTER && used == BEAM_STOP)
return 1;
if (in_explosion_phase)
return (used);
for (unsigned int i = 0; i < range_funcs.size(); ++i)
if ( (*range_funcs[i])(*this, victim, used) )
break;
return (used);
}
// Checks whether the beam knocks back the supplied actor. The actor
// should have already failed their EV check, so the save is entirely
// body-mass-based.
bool bolt::knockback_actor(actor *act)
{
ASSERT(ray.pos() == act->pos());
const coord_def oldpos(ray.pos());
const ray_def ray_copy(ray);
ray.advance();
const coord_def newpos(ray.pos());
if (newpos == oldpos || actor_at(newpos) || feat_is_solid(grd(newpos))
|| !act->can_pass_through(newpos)
// Save is based on target's body weight.
|| random2(2500) < act->body_weight())
{
ray = ray_copy;
return false;
}
act->move_to_pos(newpos);
// Knockback cannot ever kill the actor directly - caller must do
// apply_location_effects after messaging.
return true;
}
// Takes a bolt and refines it for use in the explosion function.
// Explosions which do not follow from beams (e.g., scrolls of
// immolation) bypass this function.
void bolt::refine_for_explosion()
{
ASSERT(!special_explosion);
const char *seeMsg = NULL;
const char *hearMsg = NULL;
if (ex_size == 0)
ex_size = 1;
// Assume that the player can see/hear the explosion, or
// gets burned by it anyway. :)
msg_generated = true;
// tmp needed so that what c_str() points to doesn't go out of scope
// before the function ends.
std::string tmp;
if (item != NULL)
{
tmp = "The " + item->name(DESC_PLAIN, false, false, false)
+ " explodes!";
seeMsg = tmp.c_str();
hearMsg = "You hear an explosion.";
type = dchar_glyph(DCHAR_FIRED_BURST);
}
if (name.find("hellfire") != std::string::npos)
{
seeMsg = "The hellfire explodes!";
hearMsg = "You hear a strangely unpleasant explosion.";
type = dchar_glyph(DCHAR_FIRED_BURST);
flavour = BEAM_HELLFIRE;
}
if (name == "fireball")
{
seeMsg = "The fireball explodes!";
hearMsg = "You hear an explosion.";
type = dchar_glyph(DCHAR_FIRED_BURST);
flavour = BEAM_FIRE;
ex_size = 1;
}
if (name == "orb of electricity")
{
seeMsg = "The orb of electricity explodes!";
hearMsg = "You hear a clap of thunder!";
type = dchar_glyph(DCHAR_FIRED_BURST);
flavour = BEAM_ELECTRICITY;
colour = LIGHTCYAN;
damage.num = 1;
ex_size = 2;
}
if (name == "orb of energy")
{
seeMsg = "The orb of energy explodes.";
hearMsg = "You hear an explosion.";
}
if (name == "metal orb")
{
seeMsg = "The orb explodes into a blast of deadly shrapnel!";
hearMsg = "You hear an explosion!";
name = "blast of shrapnel";
type = dchar_glyph(DCHAR_FIRED_ZAP);
flavour = BEAM_FRAG; // Sets it from pure damage to shrapnel
// (which is absorbed extra by armour).
}
if (name == "great blast of cold")
{
seeMsg = "The blast explodes into a great storm of ice!";
hearMsg = "You hear a raging storm!";
name = "ice storm";
type = dchar_glyph(DCHAR_FIRED_ZAP);
colour = WHITE;
ex_size = is_tracer ? 3 : (2 + (random2(ench_power) > 75));
}
if (name == "stinking cloud")
{
seeMsg = "The beam expands into a vile cloud!";
hearMsg = "You hear a gentle \'poof\'.";
}
if (name == "foul vapour")
{
seeMsg = "The ball expands into a vile cloud!";
hearMsg = "You hear a gentle \'poof\'.";
if (!is_tracer)
name = "stinking cloud";
}
if (name == "potion")
{
seeMsg = "The potion explodes!";
hearMsg = "You hear an explosion!";
if (!is_tracer)
{
name = "cloud";
ASSERT(flavour >= BEAM_POTION_STINKING_CLOUD
&& flavour <= BEAM_POTION_RANDOM);
const int newcolour = _potion_beam_flavour_to_colour(flavour);
if (newcolour >= 0)
colour = newcolour;
}
}
if (seeMsg == NULL)
{
seeMsg = "The beam explodes into a cloud of software bugs!";
hearMsg = "You hear the sound of one hand clapping!";
}
if (!is_tracer && *seeMsg && *hearMsg)
{
heard = player_can_hear(target);
// Check for see/hear/no msg.
if (you.see_cell(target) || target == you.pos())
mpr(seeMsg);
else
{
if (!heard)
msg_generated = false;
else
mpr(hearMsg, MSGCH_SOUND);
}
}
}
typedef std::vector< std::vector<coord_def> > sweep_type;
static sweep_type _radial_sweep(int r)
{
sweep_type result;
sweep_type::value_type work;
// Center first.
work.push_back( coord_def(0,0) );
result.push_back(work);
for (int rad = 1; rad <= r; ++rad)
{
work.clear();
for (int d = -rad; d <= rad; ++d)
{
// Don't put the corners in twice!
if (d != rad && d != -rad)
{
work.push_back( coord_def(-rad, d) );
work.push_back( coord_def(+rad, d) );
}
work.push_back( coord_def(d, -rad) );
work.push_back( coord_def(d, +rad) );
}
result.push_back(work);
}
return result;
}
#define MAX_EXPLOSION_RADIUS 9
// Returns true if we saw something happening.
bool bolt::explode(bool show_more, bool hole_in_the_middle)
{
ASSERT(!special_explosion);
ASSERT(!in_explosion_phase);
ASSERT(ex_size > 0);
// explode() can be called manually without setting real_flavour.
// FIXME: The entire flavour/real_flavour thing needs some
// rewriting!
if (real_flavour == BEAM_CHAOS || real_flavour == BEAM_RANDOM)
flavour = real_flavour;
else
real_flavour = flavour;
const int r = std::min(ex_size, MAX_EXPLOSION_RADIUS);
in_explosion_phase = true;
if (is_sanctuary(pos()))
{
if (!is_tracer && you.see_cell(pos()) && !name.empty())
{
mprf(MSGCH_GOD, "By Zin's power, the %s is contained.",
name.c_str());
return (true);
}
return (false);
}
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS,
"explosion at (%d, %d) : t=%d c=%d f=%d hit=%d dam=%dd%d r=%d",
pos().x, pos().y, type, colour, flavour, hit, damage.num, damage.size, r);
#endif
if (!is_tracer)
{
loudness = 10 + 5 * r;
bool heard_expl = noisy(loudness, pos(), beam_source);
heard = heard || heard_expl;
if (heard_expl && !noise_msg.empty() && !you.see_cell(pos()))
mprf(MSGCH_SOUND, "%s", noise_msg.c_str());
}
// Run DFS to determine which cells are influenced
explosion_map exp_map;
exp_map.init(INT_MAX);
determine_affected_cells(exp_map, coord_def(), 0, r, true, true);
#if defined(TARGET_OS_WINDOWS) && !defined(USE_TILE)
// turn buffering off
bool oldValue = true;
if (!is_tracer)
oldValue = set_buffering(false);
#endif
// We get a bit fancy, drawing all radius 0 effects, then radius
// 1, radius 2, etc. It looks a bit better that way.
const std::vector< std::vector<coord_def> > sweep = _radial_sweep(r);
const coord_def centre(9,9);
typedef sweep_type::const_iterator siter;
typedef sweep_type::value_type::const_iterator viter;
// Draw pass.
if (!is_tracer)
{
for (siter ci = sweep.begin(); ci != sweep.end(); ++ci)
{
for (viter cci = ci->begin(); cci != ci->end(); ++cci)
{
const coord_def delta = *cci;
if (delta.origin() && hole_in_the_middle)
continue;
if (exp_map(delta + centre) < INT_MAX)
explosion_draw_cell(delta + pos());
}
update_screen();
int explode_delay = 50;
// Scale delay to match change in arena_delay.
if (crawl_state.arena)
{
explode_delay *= Options.arena_delay;
explode_delay /= 600;
}
delay(explode_delay);
}
}
// Affect pass.
int cells_seen = 0;
for (siter ci = sweep.begin(); ci != sweep.end(); ++ci)
{
for (viter cci = ci->begin(); cci != ci->end(); ++cci)
{
const coord_def delta = *cci;
if (delta.origin() && hole_in_the_middle)
continue;
if (exp_map(delta + centre) < INT_MAX)
{
if (you.see_cell(delta + pos()))
++cells_seen;
explosion_affect_cell(delta + pos());
}
}
}
#if defined(TARGET_OS_WINDOWS) && !defined(USE_TILE)
if (!is_tracer)
set_buffering(oldValue);
#endif
// Delay after entire explosion has been drawn.
if (!is_tracer && cells_seen > 0 && show_more)
{
int explode_delay = 150;
// Scale delay to match change in arena_delay.
if (crawl_state.arena)
{
explode_delay *= Options.arena_delay;
explode_delay /= 600;
}
delay(explode_delay);
}
return (cells_seen > 0);
}
void bolt::explosion_draw_cell(const coord_def& p)
{
if (you.see_cell(p))
{
const coord_def drawpos = grid2view(p);
#ifdef USE_TILE
if (in_los_bounds(drawpos))
tiles.add_overlay(p, tileidx_bolt(*this));
#else
// bounds check
if (in_los_bounds(drawpos))
{
cgotoxy(drawpos.x, drawpos.y, GOTO_DNGN);
put_colour_ch(colour == BLACK ? random_colour() : colour,
dchar_glyph(DCHAR_EXPLOSION));
}
#endif
}
}
void bolt::explosion_affect_cell(const coord_def& p)
{
// pos() = target during an explosion, so restore it after affecting
// the cell.
const coord_def orig_pos = target;
fake_flavour();
target = p;
affect_cell();
flavour = real_flavour;
target = orig_pos;
}
// Uses DFS
void bolt::determine_affected_cells(explosion_map& m, const coord_def& delta,
int count, int r,
bool stop_at_statues, bool stop_at_walls)
{
const coord_def centre(9,9);
const coord_def loc = pos() + delta;
// A bunch of tests for edge cases.
if (delta.rdist() > centre.rdist()
|| (delta.abs() > r*(r+1))
|| (count > 10*r)
|| !map_bounds(loc)
|| is_sanctuary(loc))
{
return;
}
const dungeon_feature_type dngn_feat = grd(loc);
// Check to see if we're blocked by a wall.
if (feat_is_wall(dngn_feat)
|| dngn_feat == DNGN_SECRET_DOOR
|| feat_is_closed_door(dngn_feat))
{
// Special case: explosion originates from rock/statue
// (e.g. Lee's Rapid Deconstruction) - in this case, ignore
// solid cells at the center of the explosion.
if (stop_at_walls && !(delta.origin() && affects_wall(dngn_feat)))
return;
}
if (feat_is_solid(dngn_feat) && !feat_is_wall(dngn_feat) && stop_at_statues)
return;
// Check if it passes the callback functions.
bool hits = true;
for (unsigned int i = 0; i < aoe_funcs.size(); ++i)
hits = (*aoe_funcs[i])(*this, loc) && hits;
if (hits)
{
// Hmm, I think we're OK.
m(delta + centre) = std::min(count, m(delta + centre));
}
// Now recurse in every direction.
for (int i = 0; i < 8; ++i)
{
const coord_def new_delta = delta + Compass[i];
if (new_delta.rdist() > centre.rdist())
continue;
// Is that cell already covered?
if (m(new_delta + centre) <= count)
continue;
int cadd = 5;
// Changing direction (e.g. looking around a wall) costs more.
if (delta.x * Compass[i].x < 0 || delta.y * Compass[i].y < 0)
cadd = 17;
determine_affected_cells(m, new_delta, count + cadd, r,
stop_at_statues, stop_at_walls);
}
}
// Returns true if the beam is harmful (ignoring monster
// resists) -- mon is given for 'special' cases where,
// for example, "Heal" might actually hurt undead, or
// "Holy Word" being ignored by holy monsters, etc.
//
// Only enchantments should need the actual monster type
// to determine this; non-enchantments are pretty
// straightforward.
bool bolt::nasty_to(const monsters *mon) const
{
// Cleansing flame.
if (flavour == BEAM_HOLY)
return (mon->res_holy_energy(agent()) <= 0);
// The orbs are made of pure disintegration energy. This also has the side
// effect of not stopping us from firing further orbs when the previous one
// is still flying.
if (flavour == BEAM_DISINTEGRATION || flavour == BEAM_NUKE)
return (mon->type != MONS_ORB_OF_DESTRUCTION);
// Take care of other non-enchantments.
if (!is_enchantment())
return (true);
// Now for some non-hurtful enchantments.
if (flavour == BEAM_DIGGING)
return (false);
// Positive effects.
if (nice_to(mon))
return (false);
// No charming holy beings!
if (flavour == BEAM_CHARM)
return (mon->is_holy());
// Friendly and good neutral monsters don't mind being teleported.
if (flavour == BEAM_TELEPORT)
return (!mon->wont_attack());
// degeneration / sleep / enslave soul
if (flavour == BEAM_DEGENERATE
|| flavour == BEAM_HIBERNATION
|| flavour == BEAM_ENSLAVE_SOUL)
{
return (mon->holiness() == MH_NATURAL);
}
// dispel undead / control undead
if (flavour == BEAM_DISPEL_UNDEAD || flavour == BEAM_ENSLAVE_UNDEAD)
return (mon->holiness() == MH_UNDEAD);
// pain / agony
if (flavour == BEAM_PAIN)
return (!mon->res_negative_energy());
// control demon
if (flavour == BEAM_ENSLAVE_DEMON)
return (mon->holiness() == MH_DEMONIC);
// everything else is considered nasty by everyone
return (true);
}
// Return true if the bolt is considered nice by mon.
// This is not the inverse of nasty_to(): the bolt needs to be
// actively positive.
bool bolt::nice_to(const monsters *mon) const
{
// Polymorphing a (very) ugly thing will mutate it into a different
// (very) ugly thing.
if (flavour == BEAM_POLYMORPH)
{
return (mon->type == MONS_UGLY_THING
|| mon->type == MONS_VERY_UGLY_THING);
}
if (flavour == BEAM_HASTE
|| flavour == BEAM_HEALING
|| flavour == BEAM_INVISIBILITY)
{
return (true);
}
return (false);
}
////////////////////////////////////////////////////////////////////////////
// bolt
// A constructor for bolt to help guarantee that we start clean (this has
// caused way too many bugs). Putting it here since there's no good place to
// put it, and it doesn't do anything other than initialise its members.
//
// TODO: Eventually it'd be nice to have a proper factory for these things
// (extended from setup_mons_cast() and zapping() which act as limited ones).
bolt::bolt() : origin_spell(SPELL_NO_SPELL),
range(-2), type('*'), colour(BLACK), flavour(BEAM_MAGIC),
real_flavour(BEAM_MAGIC), drop_item(false), item(NULL),
source(), target(), damage(0, 0), ench_power(0), hit(0),
thrower(KILL_MISC), ex_size(0), beam_source(MHITNOT),
source_name(), name(), short_name(), hit_verb(),
loudness(0), noise_msg(), is_beam(false), is_explosion(false),
is_big_cloud(false), aimed_at_spot(false), aux_source(),
affects_nothing(false), affects_items(true), effect_known(true),
draw_delay(15), special_explosion(NULL), range_funcs(),
damage_funcs(), hit_funcs(), aoe_funcs(), obvious_effect(false),
seen(false), heard(false), path_taken(), range_used(0),
is_tracer(false), aimed_at_feet(false), msg_generated(false),
passed_target(false), in_explosion_phase(false),
smart_monster(false), can_see_invis(false),
attitude(ATT_HOSTILE), foe_ratio(0), chose_ray(false),
beam_cancelled(false), dont_stop_player(false), bounces(false),
bounce_pos(), reflections(0), reflector(-1), auto_hit(false)
{
}
killer_type bolt::killer() const
{
if (flavour == BEAM_BANISH)
return (KILL_RESET);
switch (thrower)
{
case KILL_YOU:
case KILL_YOU_MISSILE:
return (flavour == BEAM_PARALYSIS
|| flavour == BEAM_PETRIFY) ? KILL_YOU : KILL_YOU_MISSILE;
case KILL_MON:
case KILL_MON_MISSILE:
return (KILL_MON_MISSILE);
case KILL_YOU_CONF:
return (KILL_YOU_CONF);
default:
return (KILL_MON_MISSILE);
}
}
void bolt::set_target(const dist &d)
{
if (!d.isValid)
return;
target = d.target;
chose_ray = d.choseRay;
if (d.choseRay)
ray = d.ray;
if (d.isEndpoint)
aimed_at_spot = true;
}
void bolt::setup_retrace()
{
if (pos().x && pos().y)
target = pos();
std::swap(source, target);
chose_ray = false;
affects_nothing = true;
aimed_at_spot = true;
range_used = 0;
}
void bolt::set_agent(actor *actor)
{
// NULL actor is fine by us.
if (!actor)
return;
if (actor->atype() == ACT_PLAYER)
{
thrower = KILL_YOU_MISSILE;
}
else
{
thrower = KILL_MON_MISSILE;
beam_source = actor->mindex();
}
}
actor* bolt::agent() const
{
if (YOU_KILL(thrower))
return (&you);
else if (!invalid_monster_index(beam_source))
return (&menv[beam_source]);
else
return (NULL);
}
bool bolt::is_enchantment() const
{
return (flavour >= BEAM_FIRST_ENCHANTMENT
&& flavour <= BEAM_LAST_ENCHANTMENT);
}
std::string bolt::get_short_name() const
{
if (!short_name.empty())
return (short_name);
if (item != NULL && item->is_valid())
return item->name(DESC_NOCAP_A, false, false, false, false,
ISFLAG_IDENT_MASK | ISFLAG_COSMETIC_MASK
| ISFLAG_RACIAL_MASK);
if (real_flavour == BEAM_RANDOM || real_flavour == BEAM_CHAOS)
return beam_type_name(real_flavour);
if (flavour == BEAM_FIRE && name == "sticky fire")
return ("sticky fire");
if (flavour == BEAM_ELECTRICITY && is_beam)
return ("lightning");
if (flavour == BEAM_NONE || flavour == BEAM_MISSILE
|| flavour == BEAM_MMISSILE)
{
return (name);
}
return beam_type_name(flavour);
}
std::string beam_type_name(beam_type type)
{
switch (type)
{
case BEAM_NONE: return ("none");
case BEAM_MISSILE: return ("missile");
case BEAM_MMISSILE: return ("magic missile");
case BEAM_POTION_FIRE: // fall through
case BEAM_FIRE: return ("fire");
case BEAM_POTION_COLD: // fall through
case BEAM_COLD: return ("cold");
case BEAM_WATER: return ("water");
case BEAM_MAGIC: return ("magic");
case BEAM_ELECTRICITY: return ("electricity");
case BEAM_POTION_STINKING_CLOUD:
case BEAM_POTION_POISON: // fall through
case BEAM_POISON: return ("poison");
case BEAM_NEG: return ("negative energy");
case BEAM_ACID: return ("acid");
case BEAM_MIASMA: // fall through
case BEAM_POTION_MIASMA: return ("miasma");
case BEAM_SPORE: return ("spores");
case BEAM_POISON_ARROW: return ("poison arrow");
case BEAM_HELLFIRE: return ("hellfire");
case BEAM_NAPALM: return ("sticky fire");
case BEAM_POTION_STEAM: // fall through
case BEAM_STEAM: return ("steam");
case BEAM_ENERGY: return ("energy");
case BEAM_HOLY: return ("holy energy");
case BEAM_FRAG: return ("fragments");
case BEAM_LAVA: return ("magma");
case BEAM_ICE: return ("ice");
case BEAM_NUKE: return ("nuke");
case BEAM_RANDOM: return ("random");
case BEAM_CHAOS: return ("chaos");
case BEAM_SLOW: return ("slow");
case BEAM_HASTE: return ("haste");
case BEAM_MIGHT: return ("might");
case BEAM_HEALING: return ("healing");
case BEAM_PARALYSIS: return ("paralysis");
case BEAM_CONFUSION: return ("confusion");
case BEAM_INVISIBILITY: return ("invisibility");
case BEAM_DIGGING: return ("digging");
case BEAM_TELEPORT: return ("teleportation");
case BEAM_POLYMORPH: return ("polymorph");
case BEAM_CHARM: return ("enslave");
case BEAM_BANISH: return ("banishment");
case BEAM_DEGENERATE: return ("degeneration");
case BEAM_ENSLAVE_UNDEAD: return ("enslave undead");
case BEAM_ENSLAVE_SOUL: return ("enslave soul");
case BEAM_PAIN: return ("pain");
case BEAM_DISPEL_UNDEAD: return ("dispel undead");
case BEAM_DISINTEGRATION: return ("disintegration");
case BEAM_ENSLAVE_DEMON: return ("enslave demon");
case BEAM_BLINK: return ("blink");
case BEAM_BLINK_CLOSE: return ("blink close");
case BEAM_PETRIFY: return ("petrify");
case BEAM_CORONA: return ("backlight");
case BEAM_PORKALATOR: return ("porkalator");
case BEAM_HIBERNATION: return ("hibernation");
case BEAM_SLEEP: return ("sleep");
case BEAM_BERSERK: return ("berserk");
case BEAM_POTION_BLACK_SMOKE: return ("black smoke");
case BEAM_POTION_GREY_SMOKE: return ("grey smoke");
case BEAM_POTION_BLUE_SMOKE: return ("blue smoke");
case BEAM_POTION_PURPLE_SMOKE: return ("purple smoke");
case BEAM_POTION_RAIN: return ("rain");
case BEAM_POTION_RANDOM: return ("random potion");
case BEAM_POTION_MUTAGENIC: return ("mutagenic fog");
case BEAM_VISUAL: return ("visual effects");
case BEAM_TORMENT_DAMAGE: return ("torment damage");
case BEAM_STEAL_FOOD: return ("steal food");
case BEAM_GLOOM: return ("gloom");
case NUM_BEAMS: DEBUGSTR("invalid beam type");
return ("INVALID");
}
DEBUGSTR("unknown beam type");
return("UNKNOWN");
}
void clear_zap_info_on_exit()
{
const unsigned int zap_size = sizeof(zap_data) / sizeof(zap_info);
for (unsigned int i = 0; i < zap_size; ++i)
{
delete zap_data[i].damage;
delete zap_data[i].tohit;
}
}