#include "AppHdr.h"
#include "beam.h"
#include <cstdlib>
#include <cstdio>
#include <cstring>
#include <cstdarg>
#include <iostream>
#include <set>
#include <algorithm>
#include <cmath>
#ifdef TARGET_OS_DOS
#include <dos.h>
#include <conio.h>
#endif
#include "externs.h"
#include "cio.h"
#include "cloud.h"
#include "colour.h"
#include "delay.h"
#include "dgnevent.h"
#include "effects.h"
#include "enum.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 "monplace.h"
#include "monstuff.h"
#include "mon-util.h"
#include "mstuff2.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 "terrain.h"
#include "transfor.h"
#include "traps.h"
#include "view.h"
#include "xom.h"
#include "tiles.h"
#define BEAM_STOP 1000
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
{
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 (mons_friendly_real(mon))
return (KC_FRIENDLY);
}
}
return (KC_OTHER);
}
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 (!see_cell(p))
return;
const coord_def drawp = grid2view(p);
if (in_los_bounds(drawp))
{
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;
if (crawl_state.arena)
{
zap_delay *= Options.arena_delay;
zap_delay /= 600;
}
delay(zap_delay);
}
}
static void _ench_animation(int flavour, const monsters *mon, bool force)
{
const int elem = (flavour == BEAM_HEALING) ? ETC_HEAL :
(flavour == BEAM_PAIN) ? ETC_UNHOLY :
(flavour == BEAM_DISPEL_UNDEAD) ? ETC_HOLY :
(flavour == BEAM_POLYMORPH) ? ETC_MUTAGENIC :
(flavour == BEAM_CHAOS
|| flavour == BEAM_RANDOM) ? ETC_RANDOM :
(flavour == BEAM_TELEPORT
|| flavour == BEAM_BANISH
|| flavour == BEAM_BLINK) ? ETC_WARP
: ETC_ENCHANT;
_zap_animation(element_colour(elem), mon, force);
}
bool zapping(zap_type ztype, int power, bolt &pbolt,
bool needs_tracer, const char* msg)
{
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "zapping: power=%d", power);
#endif
pbolt.thrower = KILL_YOU_MISSILE;
if (needs_tracer && !player_tracer(ztype, power, pbolt))
return (false);
_zappy(ztype, power, pbolt);
if (msg)
mpr(msg);
if (ztype == ZAP_LIGHTNING)
noisy(25, you.pos(), "You hear a mighty clap of thunder!");
if (ztype == ZAP_DIGGING)
pbolt.aimed_at_spot = false;
pbolt.fire();
return (true);
}
bool player_tracer( zap_type ztype, int power, bolt &pbolt, int range)
{
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;
pbolt.friend_info.reset();
pbolt.foe_info.reset();
pbolt.foe_ratio = 100;
pbolt.beam_cancelled = false;
pbolt.dont_stop_player = false;
pbolt.seen = false;
pbolt.reflections = 0;
pbolt.bounces = 0;
const int old_range = pbolt.range;
if (range)
pbolt.range = range;
pbolt.fire();
if (range)
pbolt.range = old_range;
if (pbolt.beam_cancelled)
{
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "%s", "Beam cancelled.");
#endif
canned_msg(MSG_OK);
you.turn_is_over = false;
return (false);
}
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; int power_cap;
dam_deducer* damage;
tohit_deducer* tohit; int colour;
bool is_enchantment;
beam_type flavour;
dungeon_char_type glyph;
bool always_obvious;
bool can_beam;
bool is_explosion;
};
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
},
{
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
},
{
ZAP_SLOWING,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_SLOW,
DCHAR_SPACE,
false,
false,
false
},
{
ZAP_HASTING,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_HASTE,
DCHAR_SPACE,
false,
false,
false
},
{
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
},
{
ZAP_HEALING,
"0",
100,
new dicedef_calculator<1, 7, 1, 3>,
NULL,
BLACK,
true,
BEAM_HEALING,
DCHAR_SPACE,
false,
false,
false
},
{
ZAP_PARALYSIS,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_PARALYSIS,
DCHAR_SPACE,
false,
false,
false
},
{
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
},
{
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
},
{
ZAP_CONFUSION,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_CONFUSION,
DCHAR_SPACE,
false,
false,
false
},
{
ZAP_INVISIBILITY,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_INVISIBILITY,
DCHAR_SPACE,
false,
false,
false
},
{
ZAP_DIGGING,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_DIGGING,
DCHAR_SPACE,
false,
true,
false
},
{
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
},
{
ZAP_TELEPORTATION,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_TELEPORT,
DCHAR_SPACE,
false,
false,
false
},
{
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
},
{
ZAP_POLYMORPH_OTHER,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_POLYMORPH,
DCHAR_SPACE,
false,
false,
false
},
{
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
},
{
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
},
{
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
},
{
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
},
{
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
},
{
ZAP_ENSLAVEMENT,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_CHARM,
DCHAR_SPACE,
false,
false,
false
},
{
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
},
{
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
},
{
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
},
{
ZAP_BONE_SHARDS,
"spray of bone shards",
10000,
new dicedef_calculator<3, 2, 1, 250>,
new tohit_calculator<8, 1, 100>,
LIGHTGREY,
false,
BEAM_MAGIC,
DCHAR_FIRED_ZAP,
true,
true,
false
},
{
ZAP_BANISHMENT,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_BANISH,
DCHAR_SPACE,
false,
false,
false
},
{
ZAP_DEGENERATION,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_DEGENERATE,
DCHAR_SPACE,
false,
false,
false
},
{
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
},
{
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
},
{
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
},
{
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
},
{
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
},
{
ZAP_ELECTRICITY,
"zap",
25,
new dicedef_calculator<1, 3, 1, 4>,
new tohit_calculator<8, 1, 7>,
LIGHTCYAN,
false,
BEAM_ELECTRICITY, DCHAR_FIRED_ZAP,
true,
true,
false
},
{
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
},
{
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
},
{
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
},
{
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
},
{
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
},
{
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
},
{
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
},
{
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
},
{
ZAP_ENSLAVE_UNDEAD,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_ENSLAVE_UNDEAD,
DCHAR_SPACE,
false,
false,
false
},
{
ZAP_ENSLAVE_SOUL,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_ENSLAVE_SOUL,
DCHAR_SPACE,
false,
false,
false
},
{
ZAP_AGONY,
"0agony",
100,
NULL,
new tohit_calculator<0, 5, 1>,
BLACK,
true,
BEAM_PAIN,
DCHAR_SPACE,
false,
false,
false
},
{
ZAP_DISRUPTION,
"0",
100,
new dicedef_calculator<1, 4, 1, 5>,
new tohit_calculator<0, 3, 1>,
BLACK,
true,
BEAM_DISINTEGRATION,
DCHAR_SPACE,
false,
false,
false
},
{
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
},
{
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
},
{
ZAP_CONTROL_DEMON,
"0",
100,
NULL,
new tohit_calculator<0, 3, 2>,
BLACK,
true,
BEAM_ENSLAVE_DEMON,
DCHAR_SPACE,
false,
false,
false
},
{
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
},
{
ZAP_FLING_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
},
{ 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
},
{
ZAP_BACKLIGHT,
"0",
100,
NULL,
NULL,
BLUE,
true,
BEAM_BACKLIGHT,
DCHAR_SPACE,
false,
false,
false
},
{
ZAP_SLEEP,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_SLEEP,
DCHAR_SPACE,
false,
false,
false
},
{
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
},
{
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
},
{
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
},
{
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
},
{
ZAP_POISON_ARROW,
"poison arrow",
200,
new calcdice_calculator<4, 15, 1, 1>,
new tohit_calculator<5, 1, 10>,
LIGHTGREEN,
false,
BEAM_POISON_ARROW, DCHAR_FIRED_MISSILE,
true,
false,
false
},
{
ZAP_PETRIFY,
"0",
100,
NULL,
NULL,
BLACK,
true,
BEAM_PETRIFY,
DCHAR_SPACE,
false,
false,
false
},
{
ZAP_PORKALATOR,
"porkalator",
100,
NULL,
NULL,
RED,
true,
BEAM_PORKALATOR,
DCHAR_SPACE,
false,
false,
false
}
};
static void _zappy(zap_type z_type, int power, bolt &pbolt)
{
const zap_info* zinfo = NULL;
for (unsigned int i = 0; i < ARRAYSZ(zap_data); ++i)
{
if (zap_data[i].ztype == z_type)
{
zinfo = &zap_data[i];
break;
}
}
if (zinfo == NULL)
{
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_ERROR, "Couldn't find zap type %d", z_type);
#endif
return;
}
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);
if (z_type == ZAP_ICE_STORM)
pbolt.ench_power = power; }
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);
ray_def test_ray = temp_ray;
test_ray.advance(true);
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
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()
{
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 source_name = "unknown";
if (beam_source == NON_MONSTER && source == you.pos())
source_name = "player";
else if (!invalid_monster_index(beam_source))
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(), 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 && is_valid_item(*item));
ASSERT(range >= 0);
ASSERT(!aimed_at_feet || source == target);
real_flavour = flavour;
message_cache.clear();
if (!seen && 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 || !you.can_see(mon)))
{
mprf("%s appears from out of thin air!",
article_a(name, false).c_str());
}
}
if (see_cell(source) && target == source && !invisible())
seen = true;
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);
}
}
void bolt::draw(const coord_def& p)
{
if (is_tracer || is_enchantment() || !see_cell(p))
return;
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
{
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
update_screen();
delay(draw_delay);
}
}
}
void bolt::bounce()
{
ray_def old_ray = ray;
bolt old_bolt = *this;
do
{
do
ray.regress();
while (feat_is_solid(grd(ray.pos())));
bounce_pos = ray.pos();
ray.advance_and_bounce();
range_used += 2;
}
while (range_used < range && feat_is_solid(grd(ray.pos())));
if (!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;
set_terrain_changed(pos());
if (is_bloodcovered(pos()))
env.map(pos()).property &= ~(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;
if ((feat=grd(pos())) != DNGN_WAX_WALL && (feat != DNGN_TREES))
{
finish_beam();
return;
}
if (feat == DNGN_WAX_WALL)
{
if (!is_superhot())
{
if (flavour != BEAM_HELLFIRE && feat == DNGN_WAX_WALL)
{
if (see_cell(pos()))
{
emit_message(MSGCH_PLAIN,
"The wax appears to soften slightly.");
}
else if (player_can_smell())
emit_message(MSGCH_PLAIN, "You smell warm wax.");
}
}
else
{
grd(pos()) = DNGN_FLOOR;
if (see_cell(pos()))
emit_message(MSGCH_PLAIN, "The wax bubbles and burns!");
else if (player_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())
{
grd(pos()) = DNGN_FLOOR;
if (see_cell(pos()))
emit_message(MSGCH_PLAIN, "The tree burns like a torch!");
else if (player_can_smell())
emit_message(MSGCH_PLAIN, "You smell burning wood.");
if (whose_kill() == KC_YOU)
did_god_conduct(DID_KILL_PLANT, 1, effect_known, 0);
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::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)
{
if (is_bloodcovered(pos()))
env.map(pos()).property &= ~(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;
if (is_bloodcovered(pos()))
env.map(pos()).property &= ~(FPROP_BLOODY);
if (player_can_hear(pos()))
{
if (!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 (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;
}
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_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();
}
void 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 && see_cell(target)
&& !feat_is_solid(grd(target)))
{
std::string prompt = "Your line of fire to ";
const monsters* mon = monster_at(target);
if (mon && you.can_see(mon))
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;
}
}
if (!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 (affects_wall(feat))
affect_wall();
else if (is_bouncy(feat) && !in_explosion_phase)
bounce();
else
{
if (pos() != source
&& ((is_explosion && !in_explosion_phase) || drop_item))
{
do
ray.regress();
while (ray.pos() != source && cell_is_solid(ray.pos()));
if (is_explosion && !is_tracer)
target = ray.pos();
}
finish_beam();
}
}
void bolt::affect_cell(bool avoid_self)
{
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()));
bool avoid_monster = (avoid_self && thrower == KILL_MON_MISSILE);
bool avoid_player = (avoid_self && thrower != KILL_MON_MISSILE);
if (was_solid)
{
if (monsters* mon = monster_at(pos()))
{
if (can_affect_wall_monster(mon) && !avoid_monster)
affect_monster(mon);
else if (!avoid_monster)
{
mprf("The %s protects %s from harm.",
raw_feature_description(grd(mon->pos())).c_str(),
mon->name(DESC_NOCAP_THE).c_str());
}
}
hit_wall();
}
const bool still_wall = (was_solid && old_pos == pos());
bool hit_player = false;
if (found_player() && !avoid_player)
{
affect_player();
hit_player = true;
}
if ((!hit_player || is_beam || is_explosion)
&& !still_wall && !avoid_monster)
{
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 ©)
{
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;
}
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)
{
seen = seen || special_explosion->seen;
foe_info += special_explosion->foe_info;
friend_info += special_explosion->friend_info;
_undo_tracer(*special_explosion, *boltcopy.special_explosion);
delete boltcopy.special_explosion;
}
_undo_tracer(*this, boltcopy);
}
else
do_fire();
}
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
bool avoid_self = (!aimed_at_feet && (!is_explosion || !in_explosion_phase));
msg_generated = false;
if (!aimed_at_feet)
{
choose_ray();
}
#if defined(TARGET_OS_WINDOWS) && !defined(USE_TILE)
bool oldValue = true;
if (!is_tracer)
oldValue = set_buffering(false);
#endif
while (in_bounds(pos()))
{
path_taken.push_back(pos());
if (!affects_nothing)
affect_cell(avoid_self);
if (!avoid_self)
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())) || avoid_self)
|| is_tracer && affects_wall(grd(pos())));
const bool was_seen = seen;
if (!was_seen && range > 0 && !invisible() && 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());
}
if (real_flavour == BEAM_CHAOS)
flavour = real_flavour;
draw(pos());
if (bounces == 0)
ray.advance_through(target);
else
ray.advance(true);
avoid_self = false;
}
if (!in_bounds(pos()))
{
ASSERT(!aimed_at_spot);
int tries = std::max(GXM, GYM);
while (!in_bounds(ray.pos()) && tries-- > 0)
ray.regress();
ASSERT(in_bounds(pos()));
}
if (!affects_nothing)
affect_endpoint();
if (is_tracer || affects_nothing)
return;
if (!msg_generated && !obvious_effect && is_enchantment()
&& real_flavour != BEAM_CHAOS && YOU_KILL(thrower))
{
canned_msg(MSG_NOTHING_HAPPENS);
}
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);
const monsters *mon = &menv[beam_source];
if (foe_info.hurt > 0 && !mons_wont_attack(mon) && !crawl_state.arena
&& you.pet_target == MHITNOT && env.sanctuary_time <= 0)
{
you.pet_target = beam_source;
}
}
#if defined(TARGET_OS_WINDOWS) && !defined(USE_TILE)
set_buffering(oldValue);
#endif
}
int mons_adjust_flavoured(monsters *monster, bolt &pbolt, int hurted,
bool doFlavouredEffects)
{
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 (mons_is_icy(monster->type))
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_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." );
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
{
if (!doFlavouredEffects)
return (hurted);
if (you.can_see(monster))
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
{
if (!doFlavouredEffects)
return (hurted);
miasma_monster(monster, pbolt.whose_kill());
}
break;
case BEAM_HOLY:
{
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:
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 (mons_is_icy(monster->type))
{
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 (mons_is_icy(monster->type))
{
if (doFlavouredEffects)
simple_monster_message(monster, " melts!");
}
else
{
if (doFlavouredEffects)
simple_monster_message(monster, " is burned terribly!");
}
hurted *= 12; hurted /= 10;
}
break;
default:
break;
}
return (hurted);
}
static bool _monster_resists_mass_enchantment(monsters *monster,
enchant_type wh_enchant,
int pow)
{
if (wh_enchant == ENCH_CHARM)
{
if (mons_friendly(monster))
return (true);
if (monster->holiness() != MH_UNDEAD)
return (true);
if (check_mons_resist_magic(monster, 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 (check_mons_resist_magic(monster, pow))
{
simple_monster_message(monster,
mons_immune_magic(monster)? " is unaffected."
: " resists.");
return (true);
}
}
else {
simple_monster_message(monster, " is unaffected.");
return (true);
}
return (false);
}
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;
viewwindow(false, false);
pow = std::min(pow, 200);
const kill_category kc = (origin == MHITYOU ? KC_YOU : KC_OTHER);
for (int i = 0; i < MAX_MONSTERS; ++i)
{
monsters* const monster = &menv[i];
if (!monster->alive())
continue;
if (!mons_near(monster))
continue;
if (monster->has_ench(wh_enchant))
continue;
if (m_attempted)
++*m_attempted;
if (_monster_resists_mass_enchantment(monster, wh_enchant, pow))
continue;
if (monster->add_ench(mon_enchant(wh_enchant, 0, kc)))
{
if (m_succumbed)
++*m_succumbed;
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(monster, msg);
if (wh_enchant == ENCH_FEAR)
behaviour_event(monster, ME_SCARE, origin);
}
}
if (!msg_generated)
canned_msg(MSG_NOTHING_HAPPENS);
return (msg_generated);
}
void bolt::apply_bolt_paralysis(monsters *monster)
{
if (!monster->has_ench(ENCH_PARALYSIS)
&& monster->add_ench(ENCH_PARALYSIS)
&& (!monster->has_ench(ENCH_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);
}
}
void bolt::apply_bolt_petrify(monsters *monster)
{
int petrifying = monster->has_ench(ENCH_PETRIFYING);
if (monster->has_ench(ENCH_PETRIFIED))
{
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))
{
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);
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);
}
if (who == KC_YOU)
did_god_conduct(DID_POISON, 5 + random2(3));
return (hurted > 0);
}
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);
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);
}
if (who == KC_YOU)
did_god_conduct(DID_POISON, 5 + random2(3));
return (new_pois.degree > old_pois.degree);
}
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);
}
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);
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);
}
void fire_tracer(const monsters *monster, bolt &pbolt, bool explode_only)
{
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);
pbolt.foe_info.reset();
pbolt.friend_info.reset();
pbolt.reflections = 0;
pbolt.bounces = 0;
if (!pbolt.foe_ratio)
{
pbolt.foe_ratio = 80;
if (mons_att_wont_attack(pbolt.attitude)
&& !mons_att_wont_attack(monster->attitude))
{
pbolt.foe_ratio = 25;
}
}
pbolt.in_explosion_phase = false;
if (explode_only)
pbolt.explode(false);
else
pbolt.fire();
pbolt.is_tracer = false;
}
void mimic_alert(monsters *mimic)
{
if (!mimic->alive())
return;
bool should_id = !testbits(mimic->flags, MF_KNOWN_MIMIC)
&& you.can_see(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 );
if (!instant_tele && should_id)
mimic->flags |= MF_KNOWN_MIMIC;
}
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)
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_PURP_SMOKE:
return (MAGENTA);
case BEAM_POTION_RANDOM:
default:
return (-1);
}
return (-1);
}
void bolt::affect_endpoint()
{
if (special_explosion)
{
special_explosion->refine_for_explosion();
special_explosion->target = pos();
special_explosion->explode();
}
if (drop_item && item)
drop_object();
if (is_explosion)
{
refine_for_explosion();
target = pos();
explode();
return;
}
if (is_tracer)
return;
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")
{
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 && is_valid_item(*item) );
if (is_tracer || flavour != BEAM_MISSILE)
return;
if (item->flags & ISFLAG_SUMMONED)
{
if (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(), false))
{
if (item->sub_type == MI_THROWING_NET)
{
monsters* m = monster_at(pos());
if (you.pos() == pos() && you.attribute[ATTR_HELD]
|| m && mons_is_caught(m))
{
if (get_trapping_net(pos(), true) == NON_ITEM)
set_item_stationary(*item);
}
}
copy_item_to_grid(*item, pos(), 1);
}
}
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()
{
if (is_explosion && !in_explosion_phase)
return;
if (is_tracer)
return;
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 = (crawl_state.arena && coinflip()) ? BEH_FRIENDLY
: BEH_HOSTILE;
int rc = create_monster(mgen_data(MONS_BALLISTOMYCETE,
beh,
0,
0,
pos(),
MHITNOT,
MG_FORCE_PLACE));
if (rc != -1 && 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())
return (false);
return (name == "bolt of fire"
|| name == "bolt of magma"
|| name == "fireball"
|| name.find("hellfire") != std::string::npos
&& in_explosion_phase);
}
bool bolt::affects_wall(dungeon_feature_type wall) const
{
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_NUKE)
return (true);
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();
const int cloudidx = env.cgrid(p);
if (cloudidx != EMPTY_CLOUD)
{
cloud_type& ctype = env.cloud[cloudidx].type;
if (flavour == BEAM_POLYMORPH)
ctype = static_cast<cloud_type>(1 + random2(8));
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;
}
const dungeon_feature_type feat = grd(p);
if (name == "blast of poison")
place_cloud(CLOUD_POISON, p, random2(4) + 2, whose_kill(), killer());
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();
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_PURP_SMOKE:
case BEAM_POTION_RAIN:
case BEAM_POTION_MUTAGENIC:
cl_type = beam2cloud(flavour);
break;
case BEAM_POTION_RANDOM:
switch (random2(11))
{
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_PURP_SMOKE; break;
default: cl_type = CLOUD_STEAM; break;
}
break;
default:
cl_type = CLOUD_STEAM;
break;
}
place_cloud(cl_type, p, duration, whose_kill(), killer());
}
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);
mons_place(
mgen_data(MONS_FIRE_VORTEX, att, 2, SPELL_FIRE_STORM, p,
MHITNOT, 0, god));
}
}
}
void bolt::internal_ouch(int dam)
{
monsters *monst = NULL;
if (!invalid_monster_index(beam_source) && menv[beam_source].type != -1)
monst = &menv[beam_source];
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 (MON_KILL(thrower))
ouch(dam, beam_source, KILLED_BY_BEAM, aux_source.c_str());
else ouch(dam, beam_source, KILLED_BY_WILD_MAGIC, aux_source.c_str());
}
bool bolt::fuzz_invis_tracer()
{
int dist = grid_distance(target, you.pos());
if (dist > 2)
return (false);
const int beam_src = beam_source_as_target();
if (beam_src != MHITNOT && beam_src != MHITYOU)
{
const monsters *mon = &menv[beam_src];
if (mons_sense_invis(mon))
return (dist == 0);
}
coord_def fuzz( random_range(-2, 2), random_range(-2, 2) );
coord_def newtarget = target + fuzz;
if (in_bounds(newtarget))
target = newtarget;
return (true);
}
bool test_beam_hit(int attack, int defence)
{
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "Beam attack: %d, defence: %d", attack, defence);
#endif
return (attack == AUTOMATIC_HIT
|| random2(attack) >= random2avg(defence, 2));
}
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
{
if (is_enchantment())
return (!nasty_to(mon));
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
{
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "beam flavour: %d", flavour);
#endif
switch (flavour)
{
case BEAM_VISUAL:
case BEAM_DIGGING:
return (true);
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 (player_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:
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));
}
static void _ident_reflector(item_def *item)
{
if (!is_artefact(*item))
set_ident_flags(*item, ISFLAG_KNOW_TYPE);
}
void bolt::reflect()
{
reflections++;
if (bounces > 0 && in_bounds(bounce_pos))
target = bounce_pos;
else
target = source;
source = pos();
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()
{
if (YOU_KILL(thrower))
{
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);
range_used += range_used_on_hit(&you);
apply_hit_funcs(&you, 0);
}
bool bolt::misses_player()
{
if (is_explosion || aimed_at_feet || auto_hit || is_enchantment())
return (false);
const int dodge = player_evasion();
int real_tohit = hit;
if (you.invisible() && !can_see_invis)
real_tohit /= 2;
if (is_beam)
{
if (player_light_armour(true) && !aimed_at_feet && coinflip())
exercise(SK_DODGING, 1);
if (you.duration[DUR_DEFLECT_MISSILES])
real_tohit = random2(real_tohit * 2) / 3;
else if (you.duration[DUR_REPEL_MISSILES]
|| player_mutation_level(MUT_REPULSION_FIELD) == 3)
{
real_tohit -= random2(real_tohit / 2);
}
if (!test_beam_hit(real_tohit, dodge))
{
mprf("The %s misses you.", name.c_str());
return (true);
}
}
else if (is_blockable())
{
if (you.shield()
&& !aimed_at_feet
&& player_shield_class() > 0)
{
const int testhit = random2(hit * 130 / 100
+ you.shield_block_penalty());
const int block = you.shield_bonus();
#ifdef DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "Beamshield: hit: %d, block %d",
testhit, block);
#endif
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();
return (true);
}
if (coinflip())
exercise(SK_SHIELDS, one_chance_in(3) ? 1 : 0);
}
if (player_light_armour(true) && !aimed_at_feet && coinflip())
exercise(SK_DODGING, 1);
if (you.duration[DUR_DEFLECT_MISSILES])
real_tohit = random2(real_tohit / 2);
else if (you.duration[DUR_REPEL_MISSILES]
|| player_mutation_level(MUT_REPULSION_FIELD) == 3)
{
real_tohit = random2(real_tohit);
}
if (!test_beam_hit(real_tohit, dodge))
{
mprf("The %s misses you.", name.c_str());
return (true);
}
}
return (false);
}
void bolt::affect_player_enchantment()
{
if (flavour != BEAM_POLYMORPH && has_saving_throw()
&& you_resist_magic(ench_power))
{
bool need_msg = true;
if (thrower != KILL_YOU_MISSILE && !invalid_monster_index(beam_source))
{
monsters *mon = &menv[beam_source];
if (!you.can_see(mon))
{
mpr("Something tries to affect you, but you resist.");
need_msg = false;
}
}
if (need_msg)
canned_msg(MSG_YOU_RESIST);
if (flavour == BEAM_TELEPORT && you.level_type == LEVEL_ABYSS)
xom_is_stimulated(255);
range_used += range_used_on_hit(&you);
return;
}
if (effect_known)
_ench_animation(real_flavour);
else
_zap_animation(-1);
bool nasty = true, nice = false;
switch (flavour)
{
case BEAM_SLEEP:
you.put_to_sleep(ench_power);
break;
case BEAM_BACKLIGHT:
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();
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_CHARM:
potion_effect( POT_CONFUSION, ench_power );
obvious_effect = true;
break;
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()) {
mpr("You are unaffected.");
break;
}
mpr("Your body is wracked with pain!");
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";
internal_ouch(damage.roll());
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;
default:
mpr("Software bugs nibble your toes!");
break;
}
if (nasty)
{
if (mons_att_wont_attack(attitude))
{
friend_info.hurt++;
if (beam_source == NON_MONSTER)
{
if (!aimed_at_feet)
xom_is_stimulated(255);
}
else
{
xom_is_stimulated(128);
}
}
else
foe_info.hurt++;
}
if (nice)
{
if (mons_att_wont_attack(attitude))
friend_info.helped++;
else
{
foe_info.helped++;
xom_is_stimulated(128);
}
}
range_used += range_used_on_hit(&you);
apply_hit_funcs(&you, 0);
}
void bolt::affect_player()
{
if (is_explosion && !in_explosion_phase)
{
finish_beam();
return;
}
if (flavour == BEAM_DIGGING)
return;
if (is_tracer)
{
tracer_affect_player();
return;
}
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;
if (hit_verb.empty())
hit_verb = engulfs ? "engulfs" : "hits";
mprf("The %s %s you!", name.c_str(), hit_verb.c_str());
int hurted = 0;
int burn_power = (is_explosion) ? 5 : (is_beam) ? 3 : 2;
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 + player_AC() );
if (flavour == BEAM_ELECTRICITY)
armour_damage_reduction /= 2;
hurted -= armour_damage_reduction;
if (flavour == BEAM_FRAG && !player_light_armour())
{
hurted -= random2( 1 + player_AC() );
hurted -= random2( 1 + player_AC() );
}
#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 (!engulfs
&& (flavour == BEAM_MISSILE || flavour == BEAM_MMISSILE))
{
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_SPORE && hurted && you.holiness() != MH_UNDEAD)
potion_effect( POT_CONFUSION, 1);
if (item && item->base_type == OBJ_MISSILES)
{
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 * player_AC(), 100))
{
curare_hits_player(actor_to_death_source(agent()),
1 + random2(3));
was_affected = true;
}
}
}
if (name == "sticky flame")
{
if (!player_res_sticky_flame())
{
napalm_player(random2avg(7, 3) + 1);
was_affected = true;
}
}
if (flavour == BEAM_ACID)
splash_with_acid(5, affects_items);
if (affects_items)
{
if (flavour == BEAM_LAVA || name.find("hellfire") != std::string::npos)
expose_player_to_element(BEAM_LAVA, burn_power);
if (flavour == BEAM_FIRE && name != "ball of steam")
expose_player_to_element(BEAM_FIRE, burn_power);
if (flavour == BEAM_COLD)
expose_player_to_element(BEAM_COLD, burn_power);
if (in_explosion_phase && flavour == BEAM_SPORE)
expose_player_to_element(BEAM_SPORE, burn_power);
}
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS, "Damage: %d", hurted );
#endif
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++;
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);
}
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++;
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++;
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)
{
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)
{
range_used += range_used_on_hit(mon);
apply_hit_funcs(mon, 0);
}
}
bool bolt::determine_damage(monsters* mon, int& preac, int& postac, int& final,
std::vector<std::string>& messages)
{
bool originator_worships_feawn = false;
if (!is_explosion && beam_source == NON_MONSTER)
originator_worships_feawn = (you.religion == GOD_FEAWN);
else if (!is_explosion && beam_source >= 0 && beam_source < MAX_MONSTERS)
originator_worships_feawn = (env.mons[beam_source].god == GOD_FEAWN);
if (!is_enchantment()
&& attitude == mon->attitude
&& originator_worships_feawn
&& feawn_protects(mon))
{
if (!is_tracer)
{
mprf(MSGCH_GOD, "Feawn protects %s plant from harm.",
attitude == ATT_FRIENDLY ? "your" : "a");
}
return (false);
}
if (is_tracer)
preac = (damage.num * (damage.size + 1)) / 2;
else
preac = damage.roll();
if (!apply_dmg_funcs(mon, preac, messages))
return (false);
if (mon->submerged())
{
if (!aimed_at_spot)
return (false);
if (flavour == BEAM_ELECTRICITY)
{
if (!is_tracer && see_cell(mon->pos()))
mprf("The %s arcs harmlessly into the water.", name.c_str());
finish_beam();
return (false);
}
preac = (preac * 2) / 3;
}
postac = preac - maybe_random2(1 + mon->ac, !is_tracer);
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);
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;
if (final > 0)
{
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++;
}
}
handle_stop_attack_prompt(mon);
if (beam_cancelled)
return;
if (!is_tracer && final > 0)
{
for (unsigned int i = 0; i < messages.size(); ++i)
mpr(messages[i].c_str(), MSGCH_MONSTER_DAMAGE);
}
range_used += range_used_on_hit(mon);
apply_hit_funcs(mon, final);
}
void bolt::tracer_affect_monster(monsters* mon)
{
if (!can_see_invis && mon->invisible()
|| (YOU_KILL(thrower) && !see_cell(mon->pos())))
{
return;
}
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)
{
if (mon->submerged())
return;
god_conduct_trigger conducts[3];
disable_attack_conducts(conducts);
bool hit_woke_orc = false;
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);
if (effect_known)
_ench_animation( real_flavour, mon );
else
_zap_animation(-1, mon, false);
const mon_resist_type ench_result = try_enchant_monster(mon);
if (mon->alive()) {
if (mons_is_mimic(mon->type))
mimic_alert(mon);
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: update_hurt_or_helped(mon);
break;
}
if (hit_woke_orc)
beogh_follower_convert(mon, true);
}
range_used += range_used_on_hit(mon);
apply_hit_funcs(mon, 0);
}
void bolt::monster_post_hit(monsters* mon, int dmg)
{
if (YOU_KILL(thrower) && mons_near(mon))
print_wounds(mon);
if (dmg > 0 || !mons_wont_attack(mon) || !YOU_KILL(thrower))
behaviour_event(mon, ME_ANNOY, beam_source_as_target());
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;
if (item && item->base_type == OBJ_MISSILES)
{
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);
}
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 (you.can_see(mon))
{
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 (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();
}
}
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;
if (!silenced(you.pos()))
{
if (!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 (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);
finish_beam();
}
return (rc);
}
void bolt::affect_monster(monsters* mon)
{
if (!mon->alive())
{
apply_hit_funcs(mon, 0);
return;
}
if (flavour == BEAM_DIGGING)
{
apply_hit_funcs(mon, 0);
return;
}
if (!is_beam && mon->type == MONS_BUSH)
{
apply_hit_funcs(mon, 0);
return;
}
if (name == "great blast of fire" && mon->type == MONS_FIRE_VORTEX)
{
apply_hit_funcs(mon, 0);
return;
}
if (is_tracer)
{
tracer_affect_monster(mon);
return;
}
if (flavour == BEAM_VISUAL)
{
behaviour_event(mon, ME_DISTURB, beam_source, source);
apply_hit_funcs(mon, 0);
return;
}
if (handle_statue_disintegration(mon))
return;
if (is_enchantment())
{
enchantment_affect_monster(mon);
return;
}
if (mon->submerged() && !aimed_at_spot)
return;
if (is_explosion && !in_explosion_phase)
{
finish_beam();
return;
}
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
god_conduct_trigger conducts[3];
disable_attack_conducts(conducts);
bool hit_woke_orc = false;
if (nasty_to(mon))
{
if (YOU_KILL(thrower) && final > 0)
{
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;
}
}
const bool engulfs = (is_explosion || is_big_cloud);
if (engulfs && flavour == BEAM_SPORE
&& mon->holiness() == MH_NATURAL)
{
apply_enchantment_to_monster(mon);
}
int beam_hit = hit;
if (mon->invisible() && !can_see_invis)
beam_hit /= 2;
if (mon->type == MONS_KIRKE) beam_hit = random2(beam_hit * 2) / 3;
if (!engulfs && !test_beam_hit(beam_hit, random2(mon->ev)))
{
if (you.can_see(mon))
{
msg::stream << "The " << name << " misses "
<< mon->name(DESC_NOCAP_THE) << '.' << std::endl;
}
return;
}
if (!engulfs && is_blockable() && attempt_block(mon))
return;
update_hurt_or_helped(mon);
enable_attack_conducts(conducts);
if (you.religion == GOD_FEAWN
&& (flavour == BEAM_SPORE
|| beam_source == NON_MONSTER
&& aux_source.find("your miscasting") != std::string::npos))
{
conducts[0].enabled = false;
}
if (mons_near(mon))
{
if (hit_verb.empty())
hit_verb = engulfs ? "engulfs" : "hits";
mprf("The %s %s %s.",
name.c_str(),
hit_verb.c_str(),
you.can_see(mon) ?
mon->name(DESC_NOCAP_THE).c_str() : "something");
}
else
{
if (!silenced(you.pos()) && flavour == BEAM_MISSILE
&& YOU_KILL(thrower))
{
mprf(MSGCH_SOUND, "The %s hits something.", name.c_str());
}
}
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);
}
mons_adjust_flavoured(mon, *this, postac, true);
if (!engulfs
&& (flavour == BEAM_MISSILE || flavour == BEAM_MMISSILE)
&& !mons_is_summoned(mon) && !mon->submerged())
{
const int blood = std::min(postac/2, mon->hit_points);
bleed_onto_floor(mon->pos(), mon->type, blood, true);
}
mon->hurt(agent(), final, flavour, false);
int corpse = -1;
monsters orig = *mon;
if (mon->alive())
monster_post_hit(mon, final);
else
{
if (you.religion == GOD_FEAWN && flavour == BEAM_SPORE
&& feawn_protects(mon))
{
if (mon->attitude == ATT_FRIENDLY)
mon->attitude = ATT_HOSTILE;
corpse = monster_die(mon, KILL_MON, beam_source_as_target());
}
else
corpse = monster_die(mon, thrower, beam_source_as_target());
}
if (mon->type == MONS_NO_MONSTER)
{
orig.hit_points = -1;
mon = &orig;
}
range_used += range_used_on_hit(mon);
apply_hit_funcs(mon, final, corpse);
}
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: case BEAM_ENSLAVE_DEMON: 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 && !mons_friendly(mon));
break;
case BEAM_PAIN:
rc = !mon->res_negative_energy();
break;
case BEAM_SLEEP:
rc = !mon->has_ench(ENCH_SLEEP_WARY) && mon->holiness() == MH_NATURAL && mon->res_cold() <= 0; 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)
{
if (!_ench_flavour_affects_monster(flavour, mon))
return (MON_UNAFFECTED);
if (has_saving_throw())
{
if (mons_immune_magic(mon))
return (MON_UNAFFECTED);
if (flavour == BEAM_POLYMORPH
&& (mon->type == MONS_UGLY_THING
|| mon->type == MONS_VERY_UGLY_THING
|| mons_is_shapeshifter(mon)))
{
;
}
else
{
if (check_mons_resist_magic(mon, ench_power))
return (MON_RESIST);
}
}
return (apply_enchantment_to_monster(mon));
}
mon_resist_type bolt::apply_enchantment_to_monster(monsters* mon)
{
switch (flavour)
{
case BEAM_TELEPORT:
if (you.can_see(mon))
obvious_effect = true;
monster_teleport(mon, false);
return (MON_AFFECTED);
case BEAM_BLINK:
if (you.can_see(mon))
obvious_effect = true;
monster_blink(mon);
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;
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS,
"HD: %d; pow: %d", mon->hit_dice, ench_power);
#endif
obvious_effect = true;
if (player_will_anger_monster(mon))
{
simple_monster_message(mon, " is repulsed!");
return (MON_OTHER);
}
simple_monster_message(mon, " is enslaved.");
mon->attitude = ATT_FRIENDLY;
behaviour_event(mon, ME_ALERT, MHITNOT);
mons_make_god_gift(mon, god);
return (MON_AFFECTED);
}
case BEAM_ENSLAVE_SOUL:
{
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS,
"HD: %d; pow: %d", mon->hit_dice, ench_power);
#endif
if (!mons_can_be_zombified(mon) || mons_intel(mon) < I_NORMAL)
{
simple_monster_message(mon, " is unaffected.");
return (MON_OTHER);
}
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:
#if DEBUG_DIAGNOSTICS
mprf(MSGCH_DIAGNOSTICS,
"HD: %d; pow: %d", mon->hit_dice, ench_power);
#endif
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.");
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: if (simple_monster_message(mon, " convulses in agony!"))
obvious_effect = true;
if (name.find("agony") != std::string::npos) mon->hit_points = std::max(mon->hit_points/2, 1);
else mon->hurt(agent(), damage.roll(), flavour);
return (MON_AFFECTED);
case BEAM_DISINTEGRATION: if (simple_monster_message(mon, " is blasted."))
obvious_effect = true;
mon->hurt(agent(), damage.roll(), flavour);
return (MON_AFFECTED);
case BEAM_SLEEP:
if (simple_monster_message(mon, " looks drowsy..."))
obvious_effect = true;
mon->put_to_sleep();
return (MON_AFFECTED);
case BEAM_BACKLIGHT:
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);
}
if (!mon->has_ench(ENCH_HASTE)
&& !mons_is_stationary(mon)
&& mon->add_ench(ENCH_HASTE))
{
if (!mons_is_paralysed(mon) && !mons_is_petrified(mon)
&& 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->has_ench(ENCH_BERSERK)) {
mon->go_berserk(true);
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; }
else if (heal_monster(mon, 5 + damage.roll(), false))
{
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())))
{
if (simple_monster_message(mon, " appears confused."))
obvious_effect = true;
}
return (MON_AFFECTED);
case BEAM_INVISIBILITY:
{
const std::string monster_name = mon->name(DESC_CAP_THE);
if (!mon->has_ench(ENCH_INVIS) && mon->add_ench(ENCH_INVIS))
{
mon->del_ench(ENCH_BACKLIGHT);
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))
{
if (simple_monster_message(mon, " is charmed."))
obvious_effect = true;
mon->add_ench(ENCH_CHARM);
}
return (MON_AFFECTED);
case BEAM_PORKALATOR:
if (monster_polymorph(mon, (mon->holiness() == MH_DEMONIC ?
MONS_HELL_HOG : MONS_HOG)))
{
obvious_effect = true;
}
return (MON_AFFECTED);
default:
break;
}
return (MON_AFFECTED);
}
int bolt::range_used_on_hit(const actor* victim) const
{
int used = 0;
if (!is_beam)
used = BEAM_STOP;
else if (is_enchantment())
used = (flavour == BEAM_DIGGING ? 0 : BEAM_STOP);
else if (name.find("hellfire") != std::string::npos)
used = 0;
else if (is_explosion || is_big_cloud)
used = BEAM_STOP;
else if (flavour == BEAM_ACID)
used = BEAM_STOP;
else if (flavour == BEAM_ELECTRICITY)
used = 0;
else
used = 1;
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);
}
void bolt::refine_for_explosion()
{
ASSERT(!special_explosion);
const char *seeMsg = NULL;
const char *hearMsg = NULL;
if (ex_size == 0)
ex_size = 1;
msg_generated = true;
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; }
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)
{
if (see_cell(target) || target == you.pos())
mpr(seeMsg);
else
{
if (!player_can_hear(target))
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;
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)
{
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
bool bolt::explode(bool show_more, bool hole_in_the_middle)
{
ASSERT(!special_explosion);
ASSERT(!in_explosion_phase);
ASSERT(ex_size > 0);
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 && 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
noisy(10 + 5 * r, pos(), beam_source);
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)
bool oldValue = true;
if (!is_tracer)
oldValue = set_buffering(false);
#endif
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;
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;
if (crawl_state.arena)
{
explode_delay *= Options.arena_delay;
explode_delay /= 600;
}
delay(explode_delay);
}
}
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 (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
if (!is_tracer && cells_seen > 0 && show_more)
{
int explode_delay = 150;
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 (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
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)
{
const coord_def orig_pos = target;
fake_flavour();
target = p;
affect_cell();
flavour = real_flavour;
target = orig_pos;
}
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;
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);
if (feat_is_wall(dngn_feat)
|| dngn_feat == DNGN_SECRET_DOOR
|| feat_is_closed_door(dngn_feat))
{
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;
bool hits = true;
for (unsigned int i = 0; i < aoe_funcs.size(); ++i)
hits = (*aoe_funcs[i])(*this, loc) && hits;
if (hits) {
m(delta + centre) = std::min(count, m(delta + centre));
}
for (int i = 0; i < 8; ++i)
{
const coord_def new_delta = delta + Compass[i];
if (new_delta.rdist() > centre.rdist())
continue;
if (m(new_delta + centre) <= count)
continue;
int cadd = 5;
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);
}
}
bool bolt::nasty_to(const monsters *mon) const
{
if (flavour == BEAM_HOLY)
return (mon->res_holy_energy(agent()) <= 0);
if (!is_enchantment())
return (true);
if (flavour == BEAM_DIGGING)
return (false);
if (nice_to(mon))
return (false);
if (flavour == BEAM_CHARM)
return (mons_is_holy(mon));
if (flavour == BEAM_TELEPORT)
return (!mons_wont_attack(mon));
if (flavour == BEAM_DEGENERATE
|| flavour == BEAM_SLEEP
|| flavour == BEAM_ENSLAVE_SOUL)
{
return (mon->holiness() == MH_NATURAL);
}
if (flavour == BEAM_DISPEL_UNDEAD || flavour == BEAM_ENSLAVE_UNDEAD)
return (mon->holiness() == MH_UNDEAD);
if (flavour == BEAM_PAIN)
return (!mon->res_negative_energy());
if (flavour == BEAM_ENSLAVE_DEMON)
return (mon->holiness() == MH_DEMONIC);
return (true);
}
bool bolt::nice_to(const monsters *mon) const
{
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::bolt() : 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), name(), short_name(), hit_verb(), 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), 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)
{
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 && is_valid_item(*item))
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: case BEAM_FIRE: return ("fire");
case BEAM_POTION_COLD: case BEAM_COLD: return ("cold");
case BEAM_MAGIC: return ("magic");
case BEAM_ELECTRICITY: return ("electricity");
case BEAM_POTION_STINKING_CLOUD:
case BEAM_POTION_POISON: case BEAM_POISON: return ("poison");
case BEAM_NEG: return ("negative energy");
case BEAM_ACID: return ("acid");
case BEAM_MIASMA: 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: 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_PETRIFY: return ("petrify");
case BEAM_BACKLIGHT: return ("backlight");
case BEAM_PORKALATOR: return ("porkalator");
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_PURP_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 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;
}
}