summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/beam.cc
diff options
context:
space:
mode:
Diffstat (limited to 'crawl-ref/source/beam.cc')
-rw-r--r--crawl-ref/source/beam.cc323
1 files changed, 260 insertions, 63 deletions
diff --git a/crawl-ref/source/beam.cc b/crawl-ref/source/beam.cc
index b6195fb054..90f636d0d0 100644
--- a/crawl-ref/source/beam.cc
+++ b/crawl-ref/source/beam.cc
@@ -206,6 +206,9 @@ static void _beam_set_default_values(bolt &beam, int power)
beam.is_beam = false; // default for all beams.
beam.is_tracer = false; // default for all player beams
beam.thrower = KILL_YOU_MISSILE; // missile from player
+ beam.dropped_item = false; // no item droped yet
+ beam.reflections = 0; // no reflections yet
+ beam.bounces = 0; // no bounces yet
beam.aux_source.clear(); // additional source info, unused
}
@@ -278,6 +281,11 @@ bool player_tracer( zap_type ztype, int power, bolt &pbolt, int range)
pbolt.beam_cancelled = false;
pbolt.dont_stop_foe = pbolt.dont_stop_fr = pbolt.dont_stop_player = false;
+ // Clear misc
+ pbolt.dropped_item = false;
+ pbolt.reflections = 0;
+ pbolt.bounces = 0;
+
fire_beam(pbolt);
// Should only happen if the player answered 'n' to one of those
@@ -292,6 +300,13 @@ bool player_tracer( zap_type ztype, int power, bolt &pbolt, int range)
return (false);
}
+ // Tracers shouldn't drop items.
+ ASSERT(!pbolt.dropped_item);
+
+ // Reset, since these are cumulative over recursive calls to fire_beam.
+ pbolt.reflections = 0;
+ pbolt.bounces = 0;
+
// Set to non-tracing for actual firing.
pbolt.is_tracer = false;
return (true);
@@ -1404,17 +1419,55 @@ static bool _affect_mon_in_wall(bolt &pbolt, item_def *item,
* 3d. If no valid move or bounce is found, break
* 4. Check for beam termination on target
* 5. Affect the cell which the beam just moved into -> affect()
- * 6. Decrease remaining range appropriately
- * 7. Check for early out due to aimed_at_feet
- * 8. Draw the beam
- * 9. Drop an object where the beam 'landed'
- *10. Beams explode where the beam 'landed'
- *11. If no message generated yet, send "nothing happens" (enchantments only)
+ * 6. If the beam was reflected during affect() then return, since
+ * a recursive call to fire_beam() took care of the rest.
+ * 7. Decrease remaining range appropriately
+ * 8. Check for early out due to aimed_at_feet
+ * 9. Draw the beam
+ *10. Drop an object where the beam 'landed'
+ *11. Beams explode where the beam 'landed'
+ *12. If no message generated yet, send "nothing happens" (enchantments only)
*
*/
-void fire_beam(bolt &pbolt, item_def *item, bool drop_item)
+void fire_beam(bolt &pbolt)
{
+ const int reflections = pbolt.reflections;
+ if (reflections == 0)
+ {
+ // We aren't being recursively called.
+ beam_message_cache.clear();
+ pbolt.range_used = 0;
+ }
+
+ ASSERT(pbolt.range >= 0 && pbolt.range_used >=0);
+ ASSERT(pbolt.range_used <= pbolt.range);
+ ASSERT(!pbolt.drop_item || pbolt.item);
+ ASSERT(!pbolt.dropped_item);
+
+ if (pbolt.range == pbolt.range_used && pbolt.range > 0)
+ {
+#ifdef DEBUG
+ mprf(MSGCH_DIAGNOSTICS, "fire_beam() called on already done beam "
+ "'%s' (item = '%s')", pbolt.name.c_str(),
+ pbolt.item ? pbolt.item->name(DESC_PLAIN).c_str() : "none");
+#endif
+ return;
+ }
+
+ if (!pbolt.is_tracer && reflections == 0 && YOU_KILL(pbolt.thrower))
+ {
+ switch(pbolt.flavour)
+ {
+ case BEAM_HELLFIRE:
+ case BEAM_HELLFROST:
+ did_god_conduct(DID_UNHOLY, 2 + random2(3), pbolt.effect_known);
+ break;
+ default:
+ break;
+ }
+ }
+
bool beamTerminate; // Has beam been 'stopped' by something?
coord_def &testpos(pbolt.pos);
bool did_bounce = false;
@@ -1425,15 +1478,13 @@ void fire_beam(bolt &pbolt, item_def *item, bool drop_item)
// This fixes beams being in explosion after use as a tracer.
pbolt.in_explosion_phase = false;
- beam_message_cache.clear();
-
#ifdef USE_TILE
int tile_beam = -1;
- if (item && !pbolt.is_tracer && pbolt.flavour == BEAM_MISSILE)
+ if (pbolt.item && !pbolt.is_tracer && pbolt.flavour == BEAM_MISSILE)
{
const coord_def diff = pbolt.target - pbolt.source;
- tile_beam = tileidx_item_throw(*item, diff.x, diff.y);
+ tile_beam = tileidx_item_throw(*pbolt.item, diff.x, diff.y);
}
#endif
@@ -1475,9 +1526,6 @@ void fire_beam(bolt &pbolt, item_def *item, bool drop_item)
// Give chance for beam to affect one cell even if aimed_at_feet.
beamTerminate = false;
- // Setup range.
- int rangeRemaining = pbolt.range;
-
// Before we start drawing the beam, turn buffering off.
#ifdef WIN32CONSOLE
bool oldValue = true;
@@ -1501,7 +1549,11 @@ void fire_beam(bolt &pbolt, item_def *item, bool drop_item)
// Should we ever get a tracer with a wall-affecting
// beam (possible I suppose), we'll quit tracing now.
if (!pbolt.is_tracer)
- rangeRemaining -= affect(pbolt, testpos, item);
+ {
+ (void) affect(pbolt, testpos);
+ if (pbolt.reflections > reflections)
+ return;
+ }
// If it's still a wall, quit.
if (grid_is_solid(grd(testpos)))
@@ -1514,11 +1566,14 @@ void fire_beam(bolt &pbolt, item_def *item, bool drop_item)
if (!_isBouncy(pbolt, grd(testpos)))
{
// Affect any monster that might be in the wall.
- rangeRemaining -= affect(pbolt, testpos, item);
+ (void) affect(pbolt, testpos);
+ if (pbolt.reflections > reflections)
+ return;
do
{
ray.regress();
+ pbolt.bounce_pos = ray.pos();
}
while (grid_is_solid(grd(ray.pos())));
@@ -1527,20 +1582,25 @@ void fire_beam(bolt &pbolt, item_def *item, bool drop_item)
}
did_bounce = true;
+ pbolt.bounces++;
// bounce
do
{
do
+ {
ray.regress();
+ pbolt.bounce_pos = ray.pos();
+ }
while (grid_is_solid(grd(ray.pos())));
ray.advance_and_bounce();
- rangeRemaining -= 2;
+ pbolt.range_used += 2;
}
- while (rangeRemaining > 0 && grid_is_solid(grd(ray.pos())));
+ while (pbolt.range_used < pbolt.range
+ && grid_is_solid(grd(ray.pos())));
- if (rangeRemaining < 1)
+ if (pbolt.range_used >= pbolt.range)
break;
testpos = ray.pos();
@@ -1578,7 +1638,11 @@ void fire_beam(bolt &pbolt, item_def *item, bool drop_item)
}
if (!pbolt.affects_nothing)
- rangeRemaining -= affect(pbolt, testpos, item);
+ {
+ (void) affect(pbolt, testpos);
+ if (pbolt.reflections > reflections)
+ return;
+ }
if (random_beam)
{
@@ -1587,14 +1651,16 @@ void fire_beam(bolt &pbolt, item_def *item, bool drop_item)
}
}
+ ASSERT(pbolt.reflections == reflections);
+
if (pbolt.beam_cancelled)
return;
// Always decrease range by 1.
- rangeRemaining--;
+ pbolt.range_used++;
// Check for range termination.
- if (rangeRemaining <= 0)
+ if (pbolt.range_used >= pbolt.range)
beamTerminate = true;
// Special case - beam was aimed at feet.
@@ -1656,10 +1722,8 @@ void fire_beam(bolt &pbolt, item_def *item, bool drop_item)
// The beam has finished, and terminated at tx, ty.
// Leave an object, if applicable.
- if (drop_item && item)
- beam_drop_object(pbolt, item, testpos);
-
- ASSERT(!drop_item || item);
+ if (pbolt.drop_item && pbolt.item && !pbolt.dropped_item)
+ beam_drop_object(pbolt);
// Check for explosion. NOTE that for tracers, we have to make a copy
// of target co-ords and then reset after calling this -- tracers should
@@ -1707,23 +1771,6 @@ void fire_beam(bolt &pbolt, item_def *item, bool drop_item)
if (!pbolt.is_tracer)
set_buffering(oldValue);
#endif
-
- if (!pbolt.is_tracer)
- {
- switch(pbolt.flavour)
- {
- case BEAM_HELLFIRE:
- case BEAM_HELLFROST:
- if (YOU_KILL(pbolt.thrower))
- {
- did_god_conduct(DID_UNHOLY, 2 + random2(3),
- pbolt.effect_known);
- }
- break;
- default:
- break;
- }
- }
}
@@ -2533,6 +2580,11 @@ void fire_tracer(const monsters *monster, bolt &pbolt, bool explode_only)
pbolt.fr_helped = pbolt.fr_hurt = 0;
pbolt.foe_helped = pbolt.foe_hurt = 0;
+ // Clear misc
+ pbolt.dropped_item = false;
+ pbolt.reflections = 0;
+ pbolt.bounces = 0;
+
// If there's a specifically requested foe_ratio, honour it.
if (!pbolt.foe_ratio)
{
@@ -2554,8 +2606,15 @@ void fire_tracer(const monsters *monster, bolt &pbolt, bool explode_only)
else
fire_beam(pbolt);
+ // Tracers shouldn't drop items.
+ ASSERT(!pbolt.dropped_item);
+
+ // Reset, since these are cumulative over recursive calls to fire_beam.
+ pbolt.reflections = 0;
+ pbolt.bounces = 0;
+
// Unset tracer flag (convenience).
- pbolt.is_tracer = false;
+ pbolt.is_tracer = false;
}
bool check_line_of_sight( const coord_def& source, const coord_def& target )
@@ -2765,14 +2824,39 @@ static bool _beam_term_on_target(bolt &beam, const coord_def& p)
return (false);
}
-void beam_drop_object( bolt &beam, item_def *item, const coord_def& p )
+void beam_drop_object( bolt &beam, item_def *item, const coord_def& _p )
{
+ if (!item)
+ item = beam.item;
ASSERT( item != NULL );
+ ASSERT( is_valid_item(*item) );
+ ASSERT( item->quantity > 0);
+
+#ifdef DEBUG
+ if (!beam.drop_item)
+ mprf(MSGCH_DIAGNOSTICS, "beam_drop_object() called when beam.drop_item "
+ "is false (beam = %s, item = %s)", beam.name.c_str(),
+ item->name(DESC_PLAIN).c_str());
+#endif
- // Conditions: beam is missile and not tracer.
if (beam.is_tracer || beam.flavour != BEAM_MISSILE)
return;
+ if (beam.dropped_item)
+ {
+#ifdef DEBUG
+ mprf(MSGCH_DIAGNOSTICS, "beam_drop_object() called after object "
+ "already dropped (beam = %s, item = %s)", beam.name.c_str(),
+ item->name(DESC_PLAIN).c_str());
+#endif
+ return;
+ }
+
+ coord_def p = _p;
+ if (!in_bounds(p))
+ p = beam.pos;
+
+ // Conditions: beam is missile and not tracer.
if (YOU_KILL(beam.thrower)
&& !thrown_object_destroyed(item, p, false)
|| MON_KILL(beam.thrower)
@@ -2791,7 +2875,16 @@ void beam_drop_object( bolt &beam, item_def *item, const coord_def& p )
}
}
- copy_item_to_grid( *item, p, 1 );
+ if (copy_item_to_grid( *item, p, 1 ))
+ beam.dropped_item = true;
+ else
+ {
+#ifdef DEBUG
+ mprf(MSGCH_DIAGNOSTICS, "beam_drop_object() unable to drop "
+ "object (beam = %s, item = %s)", beam.name.c_str(),
+ item->name(DESC_PLAIN).c_str());
+#endif
+ }
}
}
@@ -2806,7 +2899,7 @@ static bool _found_player(const bolt &beam, const coord_def& p)
return (grid_distance(p, you.pos()) <= dist);
}
-int affect(bolt &beam, const coord_def& p, item_def *item, bool affect_items)
+int affect(bolt &beam, const coord_def& _p, item_def *item, bool affect_items)
{
// Extra range used by hitting something.
int rangeUsed = 0;
@@ -2815,10 +2908,20 @@ int affect(bolt &beam, const coord_def& p, item_def *item, bool affect_items)
if (beam.flavour == BEAM_LINE_OF_SIGHT)
return (0);
+ coord_def p = _p;
+ if (!in_bounds(_p))
+ p = beam.pos;
+
+ if (!item)
+ item = beam.item;
+
if (grid_is_solid(grd(p)))
{
if (beam.is_tracer) // Tracers always stop on walls.
+ {
+ beam.range_used += BEAM_STOP;
return (BEAM_STOP);
+ }
if (_affects_wall(beam, grd(p)))
rangeUsed += _affect_wall(beam, p);
@@ -2843,6 +2946,7 @@ int affect(bolt &beam, const coord_def& p, item_def *item, bool affect_items)
}
}
+ beam.range_used += rangeUsed;
return (rangeUsed);
}
}
@@ -2876,7 +2980,10 @@ int affect(bolt &beam, const coord_def& p, item_def *item, bool affect_items)
}
if (_beam_term_on_target(beam, p))
+ {
+ beam.range_used += BEAM_STOP;
return (BEAM_STOP);
+ }
}
// If there is a monster at this location, affect it.
@@ -2903,10 +3010,14 @@ int affect(bolt &beam, const coord_def& p, item_def *item, bool affect_items)
}
if (_beam_term_on_target(beam, p))
+ {
+ beam.range_used += BEAM_STOP;
return (BEAM_STOP);
+ }
}
}
+ beam.range_used += rangeUsed;
return (rangeUsed);
}
@@ -3277,7 +3388,16 @@ static void _beam_ouch(int dam, bolt &beam)
beam.aux_source.c_str());
}
else if (YOU_KILL(beam.thrower) && beam.aux_source.empty())
- ouch(dam, NON_MONSTER, KILLED_BY_TARGETTING);
+ {
+ if (beam.reflections > 0)
+ ouch(dam, beam.reflector, KILLED_BY_REFLECTION,
+ beam.name.c_str());
+ else if (beam.bounces > 0)
+ ouch(dam, NON_MONSTER, KILLED_BY_BOUNCE,
+ beam.name.c_str());
+ else
+ ouch(dam, NON_MONSTER, KILLED_BY_TARGETTING);
+ }
else if (MON_KILL(beam.thrower))
{
ouch(dam, beam.beam_source, KILLED_BY_BEAM,
@@ -3437,6 +3557,54 @@ static bool _beam_is_harmless_player(bolt &beam)
}
}
+static bool _beam_is_reflectable( const bolt &beam, const item_def *item )
+{
+ if (beam.range_used >= beam.range)
+ return (false);
+
+ return (item && is_shield(*item) && shield_reflects(*item));
+}
+
+static void _ident_reflector(item_def *item)
+{
+ if (!is_artefact(*item))
+ set_ident_flags(*item, ISFLAG_KNOW_TYPE);
+}
+
+static void _reflect_beam(bolt &beam)
+{
+ beam.reflections++;
+
+ // If it bounced off a wall before being refleced then head back towards
+ // the wall.
+ if (beam.bounces > 0 && in_bounds(beam.bounce_pos))
+ beam.target = beam.bounce_pos;
+ else
+ beam.target = beam.source;
+
+ beam.source = beam.pos;
+
+ // Reset bounce_pos, so that if we somehow reflect again before reaching
+ // the wall that we won't keep heading towards the wall.
+ beam.bounce_pos.set(0, 0);
+
+ if (beam.pos == you.pos())
+ beam.reflector = NON_MONSTER;
+ else if (mgrd(beam.pos) != NON_MONSTER)
+ beam.reflector = mgrd(beam.pos);
+ else
+ {
+ beam.reflector = -1;
+#ifdef DEBUG
+ mprf(MSGCH_DIAGNOSTICS, "Bolt reflected by neither player nor "
+ "monster (bolt = %s, item = %s)", beam.name.c_str(),
+ beam.item ? beam.item->name(DESC_PLAIN).c_str() : "none");
+#endif
+ }
+
+ fire_beam(beam);
+}
+
// Returns amount of extra range used up by affectation of the player.
static int _affect_player( bolt &beam, item_def *item, bool affect_items )
{
@@ -3549,7 +3717,16 @@ static int _affect_player( bolt &beam, item_def *item, bool affect_items )
#endif
if (hit < block)
{
- mprf( "You block the %s.", beam.name.c_str() );
+ if (_beam_is_reflectable(beam, you.shield()))
+ {
+ mprf( "Your %s reflects the %s!",
+ you.shield()->name(DESC_PLAIN).c_str(),
+ beam.name.c_str() );
+ _ident_reflector(you.shield());
+ _reflect_beam(beam);
+ }
+ else
+ mprf( "You block the %s.", beam.name.c_str() );
you.shield_block_succeeded();
return (BEAM_STOP);
}
@@ -4421,12 +4598,30 @@ static int _affect_monster(bolt &beam, monsters *mon, item_def *item)
{
const int hit = random2( beam.hit * 130 / 100
+ mon->shield_block_penalty() );
- if (hit < shield_block && mons_near(mon)
- && player_monster_visible(mon))
+ if (hit < shield_block)
{
- mprf("%s blocks the %s.",
- mon->name(DESC_CAP_THE).c_str(),
- beam.name.c_str());
+ item_def *shield = mon->mslot_item(MSLOT_SHIELD);
+ if (_beam_is_reflectable(beam, shield))
+ {
+ if (you.can_see(mon))
+ {
+ mprf("%s reflects the %s off %s %s!",
+ mon->name(DESC_CAP_THE).c_str(),
+ beam.name.c_str(),
+ mon->pronoun(PRONOUN_NOCAP_POSSESSIVE).c_str(),
+ shield->name(DESC_PLAIN).c_str());
+ _ident_reflector(shield);
+ }
+ else if (see_grid(beam.pos))
+ mprf("The %s bounces off of thin air!",
+ beam.name.c_str());
+
+ _reflect_beam(beam);
+ }
+ else
+ mprf("%s blocks the %s.",
+ mon->name(DESC_CAP_THE).c_str(),
+ beam.name.c_str());
mon->shield_block_succeeded();
return (BEAM_STOP);
@@ -5371,20 +5566,22 @@ static bool _nice_beam(monsters *mon, const bolt &beam)
// (extended from setup_mons_cast() and zapping() which act as limited ones).
bolt::bolt() : range(0), type('*'),
colour(BLACK),
- flavour(BEAM_MAGIC), source(), target(), pos(), damage(0,0),
- ench_power(0), hit(0),
+ flavour(BEAM_MAGIC), drop_item(false), item(NULL), source(),
+ target(), pos(), damage(0,0), ench_power(0), hit(0),
thrower(KILL_MISC), ex_size(0), beam_source(MHITNOT), name(),
is_beam(false), is_explosion(false), is_big_cloud(false),
- aimed_at_spot(false),
- aux_source(), affects_nothing(false), obvious_effect(false),
- effect_known(true), fr_count(0), foe_count(0), fr_power(0),
- foe_power(0), fr_hurt(0), foe_hurt(0), fr_helped(0),
- foe_helped(0), is_tracer(false), aimed_at_feet(false),
+ aimed_at_spot(false), aux_source(), affects_nothing(false),
+ effect_known(true), obvious_effect(false), fr_count(0),
+ foe_count(0), fr_power(0), foe_power(0), fr_hurt(0),
+ foe_hurt(0), fr_helped(0), foe_helped(0),
+ dropped_item(false), item_pos(), item_index(NON_ITEM),
+ range_used(0), is_tracer(false), aimed_at_feet(false),
msg_generated(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_foe(false),
- dont_stop_fr(false), dont_stop_player(false)
+ dont_stop_fr(false), dont_stop_player(false),
+ bounces(false), bounce_pos(), reflections(false), reflector(-1)
{
}