summaryrefslogtreecommitdiffstats
path: root/crawl-ref/source/beam.cc
diff options
context:
space:
mode:
authorzelgadis <zelgadis@c06c8d41-db1a-0410-9941-cceddc491573>2008-12-01 12:29:07 +0000
committerzelgadis <zelgadis@c06c8d41-db1a-0410-9941-cceddc491573>2008-12-01 12:29:07 +0000
commitf5e97453a4ac3af452849dd8b3b429ecc8c7389d (patch)
treeae4ebfa3f583fde4b04738b120e0575f65b45353 /crawl-ref/source/beam.cc
parent04f5058cac8e12d3b85834bda4589239932f371a (diff)
downloadcrawl-ref-f5e97453a4ac3af452849dd8b3b429ecc8c7389d.tar.gz
crawl-ref-f5e97453a4ac3af452849dd8b3b429ecc8c7389d.zip
Added shields of reflection, though they're not randomly generated and have no
valuation is shopping.cc. It currently only reflects missiles that are blocked with the shield. They could also reflect the brand effects of blocked melee weapons, but I'm not sure if that's a good idea. drop_item and the item to be dropped have been moved from fire_beam()'s parameters into the bolt structure, along with the beam's remaining range. Seems to work fine, but it might still lead to some buggyness. The game now distinguishes between killing yourself by hitting yourself with a beam bounced off a wall versus other forms of self targeting. git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@7705 c06c8d41-db1a-0410-9941-cceddc491573
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)
{
}