From b1c2d6e096eebdbae0c2f5a8dd48a5f67a1eb678 Mon Sep 17 00:00:00 2001 From: haranp Date: Thu, 25 Dec 2008 17:04:20 +0000 Subject: Complete rewrite of the beam code, making it considerably saner. However, there might be quite a few bugs lurking in this rewrite. Sorry. git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@7975 c06c8d41-db1a-0410-9941-cceddc491573 --- crawl-ref/source/beam.cc | 4173 +++++++++++++++++++++------------------------- 1 file changed, 1876 insertions(+), 2297 deletions(-) (limited to 'crawl-ref/source/beam.cc') diff --git a/crawl-ref/source/beam.cc b/crawl-ref/source/beam.cc index 1cd9769bb4..2f0bda82d7 100644 --- a/crawl-ref/source/beam.cc +++ b/crawl-ref/source/beam.cc @@ -62,79 +62,43 @@ #define BEAM_STOP 1000 // all beams stopped by subtracting this // from remaining range -static FixedArray < int, 19, 19 > explode_map; - // Helper functions (some of these should probably be public). -static bool _affects_wall(const bolt &beam, int wall_feature); -static bool _isBouncy(bolt &beam, unsigned char gridtype); -static int _beam_source(const bolt &beam); -static std::string _beam_zapper(const bolt &beam); -static bool _beam_term_on_target(bolt &beam, const coord_def& p); -static void _beam_explodes(bolt &beam, const coord_def& p); -static int _affect_wall(bolt &beam, const coord_def& p); -static int _affect_place_clouds(bolt &beam, const coord_def& p); -static void _affect_place_explosion_clouds(bolt &beam, const coord_def& p); -static int _affect_player(bolt &beam, item_def *item = NULL, - bool affect_items = true); -static int _affect_monster(bolt &beam, monsters *mon, item_def *item = NULL); -static mon_resist_type _affect_monster_enchantment(bolt &beam, monsters *mon); -static void _beam_paralyses_monster( bolt &pbolt, monsters *monster ); -static void _beam_petrifies_monster( bolt &pbolt, monsters *monster ); -static int _range_used_on_hit(bolt &beam); -static void _explosion1(bolt &pbolt); -static void _explosion_map(bolt &beam, const coord_def& p, - int count, int r); -static void _explosion_cell(bolt &beam, const coord_def& p, bool drawOnly, - bool affect_items = true); - 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 bool _nasty_beam(monsters *mon, const bolt &beam); -static bool _nice_beam(monsters *mon, const bolt &beam); static beam_type _chaos_beam_flavour(); static std::set beam_message_cache; -static bool _beam_is_blockable(bolt &pbolt) +bool bolt::is_blockable() const { // BEAM_ELECTRICITY is added here because chain lighting is not // a true beam (stops at the first target it gets to and redirects // from there)... but we don't want it shield blockable. - return (!pbolt.is_beam && !pbolt.is_explosion - && pbolt.flavour != BEAM_ELECTRICITY); + return (!is_beam && !is_explosion && flavour != BEAM_ELECTRICITY); } -// Kludge to suppress multiple redundant messages for a single beam. -static void _beam_mpr(msg_channel_type channel, const char *s, ...) +void bolt::emit_message(msg_channel_type chan, const char* m) { - va_list args; - va_start(args, s); - - char buf[500]; - vsnprintf(buf, sizeof buf, s, args); - - va_end(args); + const std::string message = m; + if (message_cache.find(message) == message_cache.end()) + mpr(m, chan); - std::string message = buf; - if (beam_message_cache.find(message) == beam_message_cache.end()) - mpr(message.c_str(), channel); - - beam_message_cache.insert(message); + message_cache.insert(message); } -static kill_category _whose_kill(const bolt &beam) +kill_category bolt::whose_kill() const { - if (YOU_KILL(beam.thrower)) + if (YOU_KILL(thrower)) return (KC_YOU); - else if (MON_KILL(beam.thrower)) + else if (MON_KILL(thrower)) { - if (beam.beam_source == ANON_FRIENDLY_MONSTER) + if (beam_source == ANON_FRIENDLY_MONSTER) return (KC_FRIENDLY); - if (!invalid_monster_index(beam.beam_source)) + if (!invalid_monster_index(beam_source)) { - const monsters *mon = &menv[beam.beam_source]; + const monsters *mon = &menv[beam_source]; if (mons_friendly(mon)) return (KC_FRIENDLY); } @@ -211,7 +175,6 @@ 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.seen = false; // not seen yet @@ -254,7 +217,7 @@ bool zapping(zap_type ztype, int power, bolt &pbolt, bool needs_tracer, if (ztype == ZAP_DIGGING) pbolt.aimed_at_spot = false; - fire_beam(pbolt); + pbolt.fire(); return (true); } @@ -269,7 +232,9 @@ bool player_tracer( zap_type ztype, int power, bolt &pbolt, int range) if (you.confused()) return (true); + // FIXME: can this be removed? _beam_set_default_values(pbolt, power); + _zappy(ztype, power, pbolt); pbolt.name = "unimportant"; pbolt.is_tracer = true; @@ -288,12 +253,11 @@ bool player_tracer( zap_type ztype, int power, bolt &pbolt, int range) pbolt.dont_stop_foe = pbolt.dont_stop_fr = pbolt.dont_stop_player = false; // Clear misc - pbolt.dropped_item = false; pbolt.seen = false; pbolt.reflections = 0; pbolt.bounces = 0; - fire_beam(pbolt); + pbolt.fire(); // Should only happen if the player answered 'n' to one of those // "Fire through friendly?" prompts. @@ -307,9 +271,6 @@ 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; @@ -798,7 +759,7 @@ const zap_info zap_data[] = { { ZAP_BONE_SHARDS, "spray of bone shards", - // Incoming power is highly dependant on mass (see spells3.cc). + // Incoming power is highly dependent on mass (see spells3.cc). // Basic function is power * 15 + mass... with the largest // available mass (3000) we get a power of 4500 at a power // level of 100 (for 3d20). @@ -1394,24 +1355,18 @@ static void _zappy( zap_type z_type, int power, bolt &pbolt ) // The wall will always shield the monster if the beam bounces off the // wall, and a monster can't use a metal wall to shield itself from // electricity. -static bool _affect_mon_in_wall(bolt &pbolt, item_def *item, - const coord_def& where) +bool bolt::can_affect_wall_monster(const monsters* mon) const { - UNUSED(item); - - int mid = mgrd(where); + const bool superconductor = (grd(mon->pos()) == DNGN_METAL_WALL + && flavour == BEAM_ELECTRICITY); + if (mons_wall_shielded(mon) && !superconductor) + return (false); - if (mid == NON_MONSTER) + if (is_bouncy(grd(mon->pos()))) return (false); - if (pbolt.is_enchantment() - || (!pbolt.is_explosion && !pbolt.is_big_cloud - && (grd(where) == DNGN_METAL_WALL - || pbolt.flavour != BEAM_ELECTRICITY))) - { - if (!mons_wall_shielded(&menv[mid])) - return (true); - } + if (is_enchantment() || (!is_explosion && !is_big_cloud)) + return (true); return (false); } @@ -1504,419 +1459,522 @@ static void _munge_bounced_bolt(bolt &old_bolt, bolt &new_bolt, new_ray.regress(); } -/* - * Beam pseudo code: - * - * 1. Calculate stepx and stepy - algorithms depend on finding a step axis - * which results in a line of rise 1 or less (ie 45 degrees or less) - * 2. Calculate range. Tracers always have max range, otherwise the beam - * will have somewhere between range and rangeMax - * 3. Loop tracing out the line: - * 3a. Check for walls and wall affecting beams - * 3b. If no valid move is found, try a fuzzy move - * 3c. If no valid move is yet found, try bouncing - * 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. 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) - * - */ +bool bolt::invisible() const +{ + return (type == 0 || is_enchantment()); +} -void fire_beam(bolt &pbolt) +void bolt::initialize_fire() { - ASSERT(pbolt.flavour > BEAM_NONE && pbolt.flavour < BEAM_FIRST_PSEUDO); - ASSERT(!pbolt.drop_item || pbolt.item); - ASSERT(!pbolt.dropped_item); + ASSERT(flavour > BEAM_NONE && flavour < BEAM_FIRST_PSEUDO); + ASSERT(!drop_item || item); + ASSERT(range >= 0); - pbolt.real_flavour = pbolt.flavour; + real_flavour = flavour; - const bool beam_invisible = - pbolt.type == 0 || (!pbolt.name.empty() && pbolt.name[0] == '0'); + // Fix some things which the tracer might have set. + range_used = 0; + in_explosion_phase = false; + use_target_as_pos = false; - ASSERT(pbolt.flavour != BEAM_VISUAL - || (!beam_invisible && pbolt.colour != BLACK)); + message_cache.clear(); - const int reflections = pbolt.reflections; - if (reflections == 0) + // seen might be set by caller to supress this. + if (!seen && see_grid(source) && range > 0 && !invisible() ) { - // We aren't being recursively called, so initialize some stuff. - beam_message_cache.clear(); - pbolt.range_used = 0; + seen = true; + const int midx = mgrd(source); - // pbolt.seen might be set by caller to supress this. - if (!pbolt.seen && see_grid(pbolt.source) && pbolt.range > 0 - && !beam_invisible) + if (flavour != BEAM_VISUAL + && !is_tracer + && !YOU_KILL(thrower) + && !crawl_state.is_god_acting() + && (midx == NON_MONSTER || !you.can_see(&menv[midx]))) { - pbolt.seen = true; - int midx = mgrd(pbolt.source); - - if (pbolt.flavour != BEAM_VISUAL && !pbolt.is_tracer - && !YOU_KILL(pbolt.thrower) && !crawl_state.is_god_acting() - && (midx == NON_MONSTER || !you.can_see(&menv[midx]))) - { - mprf("%s appears from out of thin air!", - article_a(pbolt.name, false).c_str()); - } + mprf("%s appears from out of thin air!", + article_a(name, false).c_str()); } } - ASSERT(pbolt.range >= 0 && pbolt.range_used >=0); - ASSERT(pbolt.range_used <= pbolt.range); - - 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"); +#if 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 - return; - } +} - if (!pbolt.is_tracer && reflections == 0 && YOU_KILL(pbolt.thrower)) +void bolt::apply_beam_conducts() +{ + if (!is_tracer && YOU_KILL(thrower)) { - switch(pbolt.flavour) + switch (flavour) { case BEAM_HELLFIRE: case BEAM_HELLFROST: - did_god_conduct(DID_UNHOLY, 2 + random2(3), pbolt.effect_known); + did_god_conduct(DID_UNHOLY, 2 + random2(3), effect_known); break; default: break; } } +} + +void bolt::choose_ray() +{ + if (!chose_ray || reflections > 0) + { + ray.fullray_idx = -1; // to quiet valgrind + find_ray( source, target, true, ray, 0, true ); + } +} - bool beamTerminate; // Has beam been 'stopped' by something? - coord_def &testpos(pbolt.pos); - bool did_bounce = false; - cursor_control coff(false); - // [ds] Forcing the beam out of explosion phase here - currently - // no caller relies on the beam already being in_explosion_phase. - // This fixes beams being in explosion after use as a tracer. - pbolt.in_explosion_phase = false; +// Draw the bolt at p if needed. +void bolt::draw(const coord_def& p) +{ + if (is_tracer || is_enchantment() || !see_grid(p)) + return; + + // We don't clean up the old position. + // First, most people like to see the full path, + // and second, it is hard to do it right with + // respect to killed monsters, cloud trails, etc. + + const coord_def drawpos = grid2view(p); #ifdef USE_TILE - int tile_beam = -1; + if (tile_beam == -1) + tile_beam = tileidx_bolt(*this); - if (pbolt.item && !pbolt.is_tracer && pbolt.flavour == BEAM_MISSILE) + if (tile_beam != -1 && in_los_bounds(drawpos)) { - const coord_def diff = pbolt.target - pbolt.source; - tile_beam = tileidx_item_throw(*pbolt.item, diff.x, diff.y); + tiles.add_overlay(p, tile_beam); + delay(draw_delay); } + else #endif - -#if 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", - (pbolt.is_beam) ? "beam" : "missile", - (pbolt.is_explosion) ? "*" : - (pbolt.is_big_cloud) ? "+" : "", - (pbolt.is_tracer) ? " tracer" : "", - pbolt.name.c_str(), - pbolt.source.x, pbolt.source.y, - pbolt.target.x, pbolt.target.y, - pbolt.type, pbolt.colour, pbolt.flavour, - pbolt.hit, pbolt.damage.num, pbolt.damage.size, - pbolt.range); + { + // bounds check + if (in_los_bounds(drawpos)) + { +#ifndef USE_TILE + cgotoxy(drawpos.x, drawpos.y); + put_colour_ch( + colour == BLACK ? random_colour() : element_colour(colour), + type); #endif + // Get curses to update the screen so we can see the beam. + update_screen(); + delay(draw_delay); + } + } +} - // init - pbolt.aimed_at_feet = (pbolt.target == pbolt.source); - pbolt.msg_generated = false; +void bolt::bounce() +{ + ray_def old_ray = ray; + bolt old_bolt = *this; + do + { + do { + ray.regress(); + } while (grid_is_solid(grd(ray.pos()))); + bounce_pos = ray.pos(); + ray.advance_and_bounce(); + range_used += 2; + } while (range_used < range && grid_is_solid(grd(ray.pos()))); + + if (!grid_is_solid(grd(ray.pos()))) + _munge_bounced_bolt(old_bolt, *this, old_ray, ray); +} - ray_def ray; +void bolt::fake_flavour() +{ + if (real_flavour == BEAM_RANDOM) + flavour = static_cast(random_range(BEAM_FIRE, BEAM_ACID)); + else if (real_flavour == BEAM_CHAOS) + flavour = _chaos_beam_flavour(); +} - if (pbolt.chose_ray) - ray = pbolt.ray; - else +void bolt::digging_wall_effect() +{ + const dungeon_feature_type feat = grd(pos()); + if (feat == DNGN_ROCK_WALL || feat == DNGN_CLEAR_ROCK_WALL) { - ray.fullray_idx = -1; // to quiet valgrind - find_ray( pbolt.source, pbolt.target, true, ray, 0, true ); - } + grd(pos()) = DNGN_FLOOR; + // Mark terrain as changed so travel excludes can be updated + // as necessary. + // XXX: This doesn't work for some reason: after digging + // the wrong grids are marked excluded. + set_terrain_changed(pos()); - if (!pbolt.aimed_at_feet) - ray.advance_through(pbolt.target); + // Blood does not transfer onto floor. + if (is_bloodcovered(pos())) + env.map(pos()).property &= ~(FPROP_BLOODY); - // Give chance for beam to affect one cell even if aimed_at_feet. - beamTerminate = false; - - // Before we start drawing the beam, turn buffering off. -#ifdef WIN32CONSOLE - bool oldValue = true; - if (!pbolt.is_tracer) - oldValue = set_buffering(false); -#endif - while (!beamTerminate) + if (!msg_generated) + { + if (!silenced(you.pos())) + { + mpr("You hear a grinding noise.", MSGCH_SOUND); + obvious_effect = true; + } + + msg_generated = true; + } + } + else if (grid_is_wall(feat)) { - testpos = ray.pos(); - - // Random beams: randomize before affect(). - if (pbolt.real_flavour == BEAM_RANDOM) - pbolt.flavour = static_cast( - random_range(BEAM_FIRE, BEAM_ACID)); - else if (pbolt.real_flavour == BEAM_CHAOS) - pbolt.flavour = _chaos_beam_flavour(); + finish_beam(); + } +} - // Shooting through clouds affects accuracy. - if (env.cgrid(testpos) != EMPTY_CLOUD) - pbolt.hit = std::max(pbolt.hit - 2, 0); +void bolt::fire_wall_effect() +{ + // Fire only affects wax walls. + if (grd(pos()) != DNGN_WAX_WALL) + { + finish_beam(); + return; + } - // See if tx, ty is blocked by something. - if (grid_is_solid(grd(testpos))) + if (!is_superhot()) + { + // No actual effect. + if (flavour != BEAM_HELLFIRE) { - // First, check to see if this beam affects walls. - if (_affects_wall(pbolt, grd(testpos))) + if (see_grid(pos())) { - // Should we ever get a tracer with a wall-affecting - // beam (possible I suppose), we'll quit tracing now. - if (!pbolt.is_tracer) - { - (void) affect(pbolt, testpos); - if (pbolt.reflections > reflections) - return; - } - - // If it's still a wall, quit. - if (grid_is_solid(grd(testpos))) - break; // breaks from line tracing + emit_message(MSGCH_PLAIN, + "The wax appears to soften slightly."); } - else - { - // BEGIN bounce case. Bouncing protects any monster - // in the wall. - if (!_isBouncy(pbolt, grd(testpos))) - { - // Affect any monster that might be in the wall. - (void) affect(pbolt, testpos); - if (pbolt.reflections > reflections) - return; - - do - { - ray.regress(); - pbolt.bounce_pos = ray.pos(); - } - while (grid_is_solid(grd(ray.pos()))); + else if (player_can_smell()) + emit_message(MSGCH_PLAIN, "You smell warm wax."); + } + } + else + { + // Destroy the wall. + grd(pos()) = DNGN_FLOOR; + if (see_grid(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; + } + finish_beam(); +} - testpos = ray.pos(); - break; // breaks from line tracing - } +void bolt::nuke_wall_effect() +{ + const dungeon_feature_type feat = grd(pos()); - did_bounce = true; - pbolt.bounces++; + if (feat == DNGN_ROCK_WALL + || feat == DNGN_WAX_WALL + || feat == DNGN_CLEAR_ROCK_WALL) + { + // FIXME: Blood *does* transfer to the floor here? + 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 || feat == DNGN_GRANITE_STATUE) + { + grd(pos()) = DNGN_FLOOR; - // bounce - ray_def old_ray = ray; - bolt old_bolt = pbolt; - do - { - do - { - ray.regress(); - pbolt.bounce_pos = ray.pos(); - } - while (grid_is_solid(grd(ray.pos()))); + // Blood does not transfer onto floor. + if (is_bloodcovered(pos())) + env.map(pos()).property &= ~(FPROP_BLOODY); + + if (player_can_hear(pos())) + { + if (!see_grid(pos())) + mpr("You hear a hideous screaming!", MSGCH_SOUND); + else + { + mpr("The statue screams as its substance crumbles away!", + MSGCH_SOUND); + } + } + else if (see_grid(pos())) + mpr("The statue twists and shakes as its substance crumbles away!"); - ray.advance_and_bounce(); - pbolt.range_used += 2; - } - while (pbolt.range_used < pbolt.range - && grid_is_solid(grd(ray.pos()))); + if (feat == DNGN_ORCISH_IDOL && beam_source == NON_MONSTER) + did_god_conduct(DID_DESTROY_ORCISH_IDOL, 8); + + obvious_effect = true; + } + finish_beam(); +} - if (!grid_is_solid(grd(ray.pos()))) - _munge_bounced_bolt(old_bolt, pbolt, old_ray, ray); +void bolt::finish_beam() +{ + range_used = range; +} - if (pbolt.range_used >= pbolt.range) - break; +void bolt::affect_wall() +{ + 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 (grid_is_solid(pos())) + finish_beam(); +} - testpos = ray.pos(); - } // end else - beam doesn't affect walls - } // endif - are we in a wall wall? +coord_def bolt::pos() const +{ + if (in_explosion_phase || use_target_as_pos) + return target; + else + return ray.pos(); +} - // At this point, if grd(testpos) is still a wall, we - // couldn't find any path: bouncy, fuzzy, or not - so break. - if (grid_is_solid(grd(testpos))) - break; +void bolt::hit_wall() +{ + const dungeon_feature_type feat = grd(pos()); + ASSERT( grid_is_solid(feat) ); + + if (affects_wall(feat)) + affect_wall(); + else if (is_bouncy(feat) && !in_explosion_phase) + bounce(); + else + { + // Affect a creature in the wall, if any. + if (!invalid_monster_index(mgrd(pos()))) + affect_monster( &menv[mgrd(pos())] ); - const bool was_seen = pbolt.seen; - if (!was_seen && pbolt.range > 0 && !beam_invisible - && see_grid(testpos)) + // Regress for explosions: blow up one step earlier. + if (is_explosion && !in_explosion_phase) { - pbolt.seen = true; + do { + ray.regress(); + } while (grid_is_solid(ray.pos())); + finish_beam(); } + } +} - if (pbolt.flavour != BEAM_VISUAL && !was_seen && pbolt.seen - && !pbolt.is_tracer) - { - mprf("%s appears from out of your range of vision.", - article_a(pbolt.name, false).c_str()); - } +void bolt::affect_cell() +{ + // Shooting through clouds affects accuracy. + if (env.cgrid(pos()) != EMPTY_CLOUD) + hit = std::max(hit - 2, 0); + + fake_flavour(); - // Check for "target termination" - // occurs when beam can be targetted at empty - // cell (e.g. a mage wants an explosion to happen - // between two monsters). - - // In this case, don't affect the cell - players and - // monsters have no chance to dodge or block such - // a beam, and we want to avoid silly messages. - if (testpos == pbolt.target) - beamTerminate = _beam_term_on_target(pbolt, testpos); - - // Affect the cell, except in the special case noted - // above -- affect() will early out if something gets - // hit and the beam is type 'term on target'. - if (!beamTerminate || !pbolt.is_explosion) - { + const coord_def old_pos = pos(); + const bool was_solid = grid_is_solid(grd(pos())); + if (was_solid) + { + const int mid = mgrd(pos()); - if (!pbolt.affects_nothing) + // Some special casing. + if (!invalid_monster_index(mid)) + { + monsters* const mon = &menv[mid]; + if (can_affect_wall_monster(mon)) + affect_monster(mon); + else { - (void) affect(pbolt, testpos); - if (pbolt.reflections > reflections) - return; + mprf("The %s protects %s from harm.", + raw_feature_description(grd(mon->pos())).c_str(), + mon->name(DESC_NOCAP_THE).c_str()); } } + + // Note that this can change the ray position + // and the solidity of the wall. + hit_wall(); + } - ASSERT(pbolt.reflections == reflections); - - if (pbolt.beam_cancelled) - return; - - // Always decrease range by 1. - pbolt.range_used++; + // We don't want to hit a monster in a wall square twice. + const bool still_wall = (was_solid && old_pos == pos()); + if (!still_wall && !invalid_monster_index(mgrd(pos()))) + affect_monster( &menv[mgrd(pos())] ); - // Check for range termination. - if (pbolt.range_used >= pbolt.range) - beamTerminate = true; + // If the player can ever walk through walls, this will + // need special-casing too. + if (found_player()) + affect_player(); - // Special case - beam was aimed at feet. - if (pbolt.aimed_at_feet) - beamTerminate = true; + if (!grid_is_solid(grd(pos()))) + affect_ground(); +} - // Reset chaos beams so that it won't be considered an invisible - // enchantment beam for the purposes of animation. - if (pbolt.real_flavour == BEAM_CHAOS) - pbolt.flavour = pbolt.real_flavour; +// This saves some important things before calling fire(). +void bolt::fire() +{ + if (is_tracer) + { + bolt boltcopy = *this; + do_fire(); + + target = boltcopy.target; + source = boltcopy.source; + aimed_at_spot = boltcopy.aimed_at_spot; + range_used = boltcopy.range_used; + bounces = boltcopy.bounces; + bounce_pos = boltcopy.bounce_pos; + reflections = boltcopy.reflections; + reflector = boltcopy.reflector; + auto_hit = boltcopy.auto_hit; + ray = boltcopy.ray; + } + else + do_fire(); +} - // Actually draw the beam/missile/whatever, if the player can see - // the cell. - if (!pbolt.is_tracer && !pbolt.is_enchantment() && see_grid(testpos)) - { - // We don't clean up the old position. - // First, most people like to see the full path, - // and second, it is hard to do it right with - // respect to killed monsters, cloud trails, etc. +void bolt::do_fire() +{ + initialize_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; + } - // Draw new position. - coord_def drawpos = grid2view(testpos); + apply_beam_conducts(); + cursor_control coff(false); #ifdef USE_TILE - if (tile_beam == -1) - tile_beam = tileidx_bolt(pbolt); + tile_beam = -1; - if (tile_beam != -1 && in_los_bounds(drawpos)) - { - tiles.add_overlay(testpos, tile_beam); - delay(pbolt.delay); - } - else -#endif - // bounds check - if (in_los_bounds(drawpos)) - { -#ifndef USE_TILE - cgotoxy(drawpos.x, drawpos.y); - put_colour_ch( - pbolt.colour == BLACK ? random_colour() - : element_colour(pbolt.colour), - pbolt.type ); + if (item && !is_tracer && flavour == BEAM_MISSILE) + { + const coord_def diff = target - source; + tile_beam = tileidx_item_throw(*item, diff.x, diff.y); + } #endif - // Get curses to update the screen so we can see the beam. - update_screen(); - delay(pbolt.delay); + msg_generated = false; + if (target == source) + { + auto_hit = true; + aimed_at_spot = true; + use_target_as_pos = true; + } + else + { + choose_ray(); + // Take *one* step, so as not to hurt the source. + ray.advance_through(target); + } -#ifdef MISSILE_TRAILS_OFF - // mv: It's not optimal but is usually enough. - if (!pbolt.is_beam) - viewwindow(true, false); +#ifdef WIN32CONSOLE + // Before we start drawing the beam, turn buffering off. + bool oldValue = true; + if (!is_tracer) + oldValue = set_buffering(false); #endif - } - } + while (true) + { + affect_cell(); - if (!did_bounce) - ray.advance_through(pbolt.target); - else - ray.advance(true); - } // end- while !beamTerminate + range_used++; + if (range_used >= range) + break; - // The beam has finished, and terminated at tx, ty. + if (beam_cancelled) + return; - // Leave an object, if applicable. - if (pbolt.drop_item && pbolt.item && !pbolt.dropped_item) - beam_drop_object(pbolt); + if (stop_at_target() && pos() == target) + break; - // 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 - // never change any non-tracers fields in the beam structure. -- GDL - coord_def targetcopy = pbolt.target; + const bool was_seen = seen; + if (!was_seen && range > 0 && !invisible() && see_grid(pos())) + seen = true; - _beam_explodes(pbolt, testpos); + 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 (pbolt.is_tracer) - { - pbolt.target = targetcopy; + // Reset chaos beams so that it won't be considered an invisible + // enchantment beam for the purposes of animation. + if (real_flavour == BEAM_CHAOS) + flavour = real_flavour; + + // Actually draw the beam/missile/whatever, if the player can see + // the cell. + draw(pos()); + + // A bounce takes away the meaning from the target. + if (bounces == 0) + ray.advance_through(target); + else + ray.advance(true); } + // The beam has terminated. + affect_endpoint(); + + // Tracers need nothing further. + if (is_tracer) + return; + // Canned msg for enchantments that affected no-one, but only if the // enchantment is yours (and it wasn't a chaos beam, since with chaos // enchantments are entirely random, and if it randomly attempts // something which ends up having no obvious effect then the player // isn't going to realize it). - if (pbolt.is_enchantment() && pbolt.real_flavour != BEAM_CHAOS) + if (!msg_generated && !obvious_effect && is_enchantment() + && real_flavour != BEAM_CHAOS && YOU_KILL(thrower)) { - if (!pbolt.is_tracer && !pbolt.msg_generated && !pbolt.obvious_effect - && YOU_KILL(pbolt.thrower)) - { - canned_msg(MSG_NOTHING_HAPPENS); - } + canned_msg(MSG_NOTHING_HAPPENS); } - if (!pbolt.is_tracer && !invalid_monster_index(pbolt.beam_source)) + // Reactions if a monster zapped the beam. + if (!invalid_monster_index(beam_source)) { - if (pbolt.foe_hurt == 0 && pbolt.fr_hurt > 0) + if (foe_hurt == 0 && fr_hurt > 0) xom_is_stimulated(128); - else if (pbolt.foe_helped > 0 && pbolt.fr_helped == 0) + else if (foe_helped > 0 && fr_helped == 0) xom_is_stimulated(128); // Allow friendlies to react to projectiles, except when in // sanctuary when pet_target can only be explicitly changed by // the player. - const monsters *mon = &menv[pbolt.beam_source]; - if (pbolt.foe_hurt > 0 && !mons_wont_attack(mon) + const monsters *mon = &menv[beam_source]; + if (foe_hurt > 0 && !mons_wont_attack(mon) && you.pet_target == MHITNOT && env.sanctuary_time <= 0) { - you.pet_target = pbolt.beam_source; + you.pet_target = beam_source; } } // That's it! #ifdef WIN32CONSOLE - if (!pbolt.is_tracer) - set_buffering(oldValue); + set_buffering(oldValue); #endif } - // Returns damage taken by a monster from a "flavoured" (fire, ice, etc.) // attack -- damage from clouds and branded weapons handled elsewhere. int mons_adjust_flavoured(monsters *monster, bolt &pbolt, int hurted, @@ -2025,7 +2083,7 @@ int mons_adjust_flavoured(monsters *monster, bolt &pbolt, int hurted, simple_monster_message(monster, " appears unharmed."); } else if (res <= 0 && doFlavouredEffects && !one_chance_in(3)) - poison_monster(monster, _whose_kill(pbolt)); + poison_monster(monster, pbolt.whose_kill()); break; } @@ -2043,11 +2101,11 @@ int mons_adjust_flavoured(monsters *monster, bolt &pbolt, int hurted, // Poison arrow can poison any living thing regardless of // poison resistance. -- bwr if (mons_has_lifeforce(monster)) - poison_monster(monster, _whose_kill(pbolt), 2, true); + poison_monster(monster, pbolt.whose_kill(), 2, true); } } else if (doFlavouredEffects) - poison_monster(monster, _whose_kill(pbolt), 4); + poison_monster(monster, pbolt.whose_kill(), 4); break; @@ -2106,13 +2164,13 @@ int mons_adjust_flavoured(monsters *monster, bolt &pbolt, int hurted, return (hurted); if (mons_res_poison(monster) <= 0) - poison_monster(monster, _whose_kill(pbolt)); + poison_monster(monster, pbolt.whose_kill()); if (one_chance_in(3 + 2 * mons_res_negative_energy(monster))) { bolt beam; beam.flavour = BEAM_SLOW; - mons_ench_f2(monster, beam); + beam.apply_enchantment_to_monster(monster); } } break; @@ -2378,227 +2436,53 @@ bool mass_enchantment( enchant_type wh_enchant, int pow, int origin, return (msg_generated); } -// Monster has probably failed save, now it gets enchanted somehow. -// * Returns MON_RESIST if monster is unaffected due to magic resist. -// * Returns MON_UNAFFECTED if monster is immune to enchantment. -// * Returns MON_AFFECTED in all other cases (already enchanted, etc). -mon_resist_type mons_ench_f2(monsters *monster, bolt &pbolt) +void bolt::apply_bolt_paralysis(monsters *monster) { - switch (pbolt.flavour) // put in magic resistance + if (!monster->has_ench(ENCH_PARALYSIS) + && monster->add_ench(ENCH_PARALYSIS) + && (!monster->has_ench(ENCH_PETRIFIED) + || monster->has_ench(ENCH_PETRIFYING))) { - case BEAM_SLOW: - // try to remove haste, if monster is hasted - if (monster->del_ench(ENCH_HASTE)) - { - if (simple_monster_message(monster, - " is no longer moving quickly.")) - { - pbolt.obvious_effect = true; - } + if (simple_monster_message(monster, " suddenly stops moving!")) + obvious_effect = true; - return (MON_AFFECTED); - } + mons_check_pool(monster, monster->pos(), killer(), beam_source); + } +} - // not hasted, slow it - if (!monster->has_ench(ENCH_SLOW) - && !mons_is_stationary(monster) - && monster->add_ench(mon_enchant(ENCH_SLOW, 0, _whose_kill(pbolt)))) +// Petrification works in two stages. First the monster is slowed down in +// all of its actions and cannot move away (petrifying), and when that times +// out it remains properly petrified (no movement or actions). The second +// part is similar to paralysis, except that insubstantial monsters can't be +// affected and that stabbing damage is drastically reduced. +void bolt::apply_bolt_petrify(monsters *monster) +{ + int petrifying = monster->has_ench(ENCH_PETRIFYING); + if (monster->has_ench(ENCH_PETRIFIED)) + { + // If the petrifying is not yet finished, we can force it to happen + // right away by casting again. Otherwise, the spell has no further + // effect. + if (petrifying > 0) { - if (!mons_is_paralysed(monster) && !mons_is_petrified(monster) - && simple_monster_message(monster, " seems to slow down.")) + monster->del_ench(ENCH_PETRIFYING, true); + if (!monster->has_ench(ENCH_PARALYSIS) + && simple_monster_message(monster, " stops moving altogether!")) { - pbolt.obvious_effect = true; + obvious_effect = true; } } - return (MON_AFFECTED); + } + else if (monster->add_ench(ENCH_PETRIFIED) + && !monster->has_ench(ENCH_PARALYSIS)) + { + // Add both the petrifying and the petrified enchantment. The former + // will run out sooner and result in plain petrification behaviour. + monster->add_ench(ENCH_PETRIFYING); + if (simple_monster_message(monster, " is moving more slowly.")) + obvious_effect = true; - case BEAM_HASTE: - if (monster->del_ench(ENCH_SLOW)) - { - if (simple_monster_message(monster, " is no longer moving slowly.")) - pbolt.obvious_effect = true; - - return (MON_AFFECTED); - } - - // Not slowed, haste it. - if (!monster->has_ench(ENCH_HASTE) - && !mons_is_stationary(monster) - && monster->add_ench(ENCH_HASTE)) - { - if (!mons_is_paralysed(monster) && !mons_is_petrified(monster) - && simple_monster_message(monster, " seems to speed up.")) - { - pbolt.obvious_effect = true; - } - } - return (MON_AFFECTED); - - case BEAM_HEALING: - if (YOU_KILL(pbolt.thrower)) - { - if (cast_healing(5 + pbolt.damage.roll(), monster->pos()) > 0) - pbolt.obvious_effect = true; - pbolt.msg_generated = true; // to avoid duplicate "nothing happens" - } - else if (heal_monster( monster, 5 + pbolt.damage.roll(), false )) - { - if (monster->hit_points == monster->max_hit_points) - { - if (simple_monster_message(monster, - "'s wounds heal themselves!")) - { - pbolt.obvious_effect = true; - } - } - else if (simple_monster_message(monster, " is healed somewhat.")) - pbolt.obvious_effect = true; - } - return (MON_AFFECTED); - - case BEAM_PARALYSIS: - _beam_paralyses_monster(pbolt, monster); - return (MON_AFFECTED); - - case BEAM_PETRIFY: - _beam_petrifies_monster(pbolt, monster); - return (MON_AFFECTED); - - case BEAM_CONFUSION: - if (!mons_class_is_confusable(monster->type)) - return (MON_UNAFFECTED); - - if (monster->add_ench( mon_enchant(ENCH_CONFUSION, 0, - _whose_kill(pbolt)) )) - { - // Put in an exception for things you won't notice becoming - // confused. - if (simple_monster_message(monster, " appears confused.")) - pbolt.obvious_effect = true; - } - return (MON_AFFECTED); - - case BEAM_INVISIBILITY: - { - // Store the monster name before it becomes an "it" -- bwr - const std::string monster_name = monster->name(DESC_CAP_THE); - - if (!monster->has_ench(ENCH_INVIS) - && monster->add_ench(ENCH_INVIS)) - { - // A casting of invisibility erases backlight. - monster->del_ench(ENCH_BACKLIGHT); - - // Can't use simple_monster_message() here, since it checks - // for visibility of the monster (and it's now invisible). - // -- bwr - if (mons_near( monster )) - { - mprf("%s flickers %s", - monster_name.c_str(), - player_monster_visible(monster) ? "for a moment." - : "and vanishes!" ); - - if (!player_monster_visible(monster)) - { - // Also turn off autopickup. - Options.autopickup_on = false; - mpr("Deactivating autopickup; reactivate with Ctrl-A.", - MSGCH_WARN); - - if (Options.tutorial_left) - { - learned_something_new(TUT_INVISIBLE_DANGER); - Options.tut_seen_invisible = you.num_turns; - } - } - } - - pbolt.obvious_effect = true; - } - return (MON_AFFECTED); - } - case BEAM_CHARM: - if (player_will_anger_monster(monster)) - { - simple_monster_message(monster, " is repulsed!"); - return (MON_OTHER); - } - - if (monster->add_ench(ENCH_CHARM)) - { - // Put in an exception for fungi, plants and other things - // you won't notice becoming charmed. - if (simple_monster_message(monster, " is charmed.")) - pbolt.obvious_effect = true; - } - return (MON_AFFECTED); - - default: - break; - } - - return (MON_AFFECTED); -} - -// degree is ignored. -static void _slow_monster(monsters *mon, int /* degree */) -{ - bolt beam; - beam.flavour = BEAM_SLOW; - mons_ench_f2(mon, beam); -} - -static void _beam_paralyses_monster(bolt &pbolt, 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!")) - pbolt.obvious_effect = true; - - mons_check_pool(monster, monster->pos(), pbolt.killer(), - pbolt.beam_source); - } -} - - -// Petrification works in two stages. First the monster is slowed down in -// all of its actions and cannot move away (petrifying), and when that times -// out it remains properly petrified (no movement or actions). The second -// part is similar to paralysis, except that insubstantial monsters can't be -// affected and that stabbing damage is drastically reduced. -static void _beam_petrifies_monster(bolt &pbolt, monsters *monster) -{ - int petrifying = monster->has_ench(ENCH_PETRIFYING); - if (monster->has_ench(ENCH_PETRIFIED)) - { - // If the petrifying is not yet finished, we can force it to happen - // right away by casting again. Otherwise, the spell has no further - // effect. - if (petrifying > 0) - { - monster->del_ench(ENCH_PETRIFYING, true); - if (!monster->has_ench(ENCH_PARALYSIS) - && simple_monster_message(monster, " stops moving altogether!")) - { - pbolt.obvious_effect = true; - } - } - } - else if (monster->add_ench(ENCH_PETRIFIED) - && !monster->has_ench(ENCH_PARALYSIS)) - { - // Add both the petrifying and the petrified enchantment. The former - // will run out sooner and result in plain petrification behaviour. - monster->add_ench(ENCH_PETRIFYING); - if (simple_monster_message(monster, " is moving more slowly.")) - pbolt.obvious_effect = true; - - mons_check_pool(monster, monster->pos(), pbolt.killer(), - pbolt.beam_source); + mons_check_pool(monster, monster->pos(), killer(), beam_source); } } @@ -2626,7 +2510,7 @@ bool curare_hits_monster(actor *agent, monsters *monster, kill_category who, } if (monster->alive()) - _slow_monster(monster, levels); + enchant_monster_with_flavour(monster, BEAM_SLOW); } // Deities take notice. @@ -2721,7 +2605,6 @@ void fire_tracer(const monsters *monster, bolt &pbolt, bool explode_only) pbolt.foe_helped = pbolt.foe_hurt = 0; // Clear misc - pbolt.dropped_item = false; pbolt.reflections = 0; pbolt.bounces = 0; @@ -2742,12 +2625,9 @@ void fire_tracer(const monsters *monster, bolt &pbolt, bool explode_only) // Fire! if (explode_only) - explosion(pbolt, false, false, true, true, false); + pbolt.explode(false); else - fire_beam(pbolt); - - // Tracers shouldn't drop items. - ASSERT(!pbolt.dropped_item); + pbolt.fire(); // Reset, since these are cumulative over recursive calls to fire_beam. pbolt.reflections = 0; @@ -2807,676 +2687,319 @@ void mimic_alert(monsters *mimic) mimic->flags |= MF_KNOWN_MIMIC; } -static bool _isBouncy(bolt &beam, unsigned char gridtype) +bool bolt::is_bouncy(dungeon_feature_type feat) const { - if ( beam.real_flavour == BEAM_CHAOS - && grid_is_solid(static_cast(gridtype)) ) - { + if (real_flavour == BEAM_CHAOS && grid_is_solid(feat)) return (true); - } - if (beam.is_enchantment()) + if (is_enchantment()) return (false); - if (beam.flavour == BEAM_ELECTRICITY && gridtype != DNGN_METAL_WALL) + if (flavour == BEAM_ELECTRICITY && feat != DNGN_METAL_WALL) return (true); - if ((beam.flavour == BEAM_FIRE || beam.flavour == BEAM_COLD) - && gridtype == DNGN_GREEN_CRYSTAL_WALL ) + if ((flavour == BEAM_FIRE || flavour == BEAM_COLD) + && feat == DNGN_GREEN_CRYSTAL_WALL ) { return (true); } + return (false); } -static void _beam_explodes(bolt &beam, const coord_def& p) +static int _potion_beam_flavour_to_colour(beam_type flavour) { - cloud_type cl_type; - - // This will be the last thing this beam does. Set target_x - // and target_y to hold explosion co'ords. - - beam.target = p; - - // Generic explosion. - if (beam.is_explosion) - { - _explosion1(beam); - return; - } - - if (beam.flavour >= BEAM_POTION_STINKING_CLOUD - && beam.flavour <= BEAM_POTION_RANDOM) + switch (flavour) { - switch (beam.flavour) - { - case BEAM_POTION_STINKING_CLOUD: - beam.colour = GREEN; - break; - - case BEAM_POTION_POISON: - beam.colour = (coinflip() ? GREEN : LIGHTGREEN); - break; + case BEAM_POTION_STINKING_CLOUD: + return (GREEN); - case BEAM_POTION_MIASMA: - case BEAM_POTION_BLACK_SMOKE: - beam.colour = DARKGREY; - break; + case BEAM_POTION_POISON: + return (coinflip() ? GREEN : LIGHTGREEN); - case BEAM_POTION_STEAM: - case BEAM_POTION_GREY_SMOKE: - beam.colour = LIGHTGREY; - break; + case BEAM_POTION_MIASMA: + case BEAM_POTION_BLACK_SMOKE: + return (DARKGREY); - case BEAM_POTION_FIRE: - beam.colour = (coinflip() ? RED : LIGHTRED); - break; + case BEAM_POTION_STEAM: + case BEAM_POTION_GREY_SMOKE: + return (LIGHTGREY); - case BEAM_POTION_COLD: - beam.colour = (coinflip() ? BLUE : LIGHTBLUE); - break; + case BEAM_POTION_FIRE: + return (coinflip() ? RED : LIGHTRED); - case BEAM_POTION_BLUE_SMOKE: - beam.colour = LIGHTBLUE; - break; + case BEAM_POTION_COLD: + return (coinflip() ? BLUE : LIGHTBLUE); - case BEAM_POTION_PURP_SMOKE: - beam.colour = MAGENTA; - break; + case BEAM_POTION_BLUE_SMOKE: + return (LIGHTBLUE); - case BEAM_POTION_RANDOM: - default: - // Leave it the colour of the potion, the clouds will colour - // themselves on the next refresh. -- bwr - break; - } + case BEAM_POTION_PURP_SMOKE: + return (MAGENTA); - _explosion1(beam); - return; + case BEAM_POTION_RANDOM: + default: + // Leave it the colour of the potion, the clouds will colour + // themselves on the next refresh. -- bwr + return (-1); } + return (-1); +} - if (beam.is_tracer) - return; +void bolt::affect_endpoint() +{ + // Leave an object, if applicable. + if (drop_item && item) + drop_object(); - // cloud producer -- POISON BLAST - if (beam.name == "blast of poison") + if (is_explosion) { - big_cloud(CLOUD_POISON, _whose_kill(beam), beam.killer(), p, - 0, 7 + random2(5)); + refine_for_explosion(); + explode(); return; } - // cloud producer -- FOUL VAPOR (SWAMP DRAKE?) - if (beam.name == "foul vapour") + if (flavour >= BEAM_POTION_STINKING_CLOUD && flavour <= BEAM_POTION_RANDOM) { - cl_type = (beam.flavour == BEAM_MIASMA) ? CLOUD_MIASMA : CLOUD_STINK; - big_cloud( cl_type, _whose_kill(beam), beam.killer(), p, 0, 9 ); + int newcolour = _potion_beam_flavour_to_colour(flavour); + if (newcolour >= 0) + colour = newcolour; + explode(); return; } - if (beam.name == "freezing blast") - { - big_cloud( CLOUD_COLD, _whose_kill(beam), beam.killer(), p, - random_range(10, 15), 9 ); + if (is_tracer) return; + + // FIXME: why don't these just have is_explosion set? + // They don't explode in tracers: why not? + if (name == "orb of electricity" + || name == "metal orb" + || name == "great blast of cold" + || name == "ball of vapour") + { + explode(); } - // special cases - orbs & blasts of cold - if (beam.name == "orb of electricity" - || beam.name == "metal orb" - || beam.name == "great blast of cold") + if (name == "blast of poison") + big_cloud(CLOUD_POISON, whose_kill(), killer(), pos(), 0, 7+random2(5)); + + if (name == "foul vapour") { - _explosion1( beam ); - return; + const cloud_type cl_type = (flavour == BEAM_MIASMA) ? CLOUD_MIASMA + : CLOUD_STINK; + big_cloud(cl_type, whose_kill(), killer(), pos(), 0, 9); } - // cloud producer only -- stinking cloud - if (beam.name == "ball of vapour") + if (name == "freezing blast") { - _explosion1( beam ); - return; + big_cloud(CLOUD_COLD, whose_kill(), killer(), pos(), + random_range(10, 15), 9); } } -static bool _beam_term_on_target(bolt &beam, const coord_def& p) +bool bolt::stop_at_target() const { - // Generic - all explosion-type beams can be targeted at empty space, - // and will explode there. This semantic also means that a creature - // in the target cell will have no chance to dodge or block, so we - // DON'T affect() the cell if this function returns true! - - if (beam.is_explosion || beam.is_big_cloud) - return (true); - - // POISON BLAST - if (beam.name == "blast of poison") - return (true); - - // FOUL VAPOR (SWAMP DRAKE) - if (beam.name == "foul vapour") - return (true); - - // STINKING CLOUD - if (beam.name == "ball of vapour") - return (true); - - if (beam.aimed_at_spot && p == beam.target) + if (is_explosion + || is_big_cloud + || aimed_at_spot + || name == "blast of poison" + || name == "foul vapour" + || name == "ball of vapour") + { return (true); + } return (false); } -void beam_drop_object( bolt &beam, item_def *item, const coord_def& _p ) +void bolt::drop_object() { - if (!item) - item = beam.item; - ASSERT( item != NULL ); - ASSERT( is_valid_item(*item) ); - -#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 - - if (beam.is_tracer || beam.flavour != BEAM_MISSILE) - return; + ASSERT( item != NULL && is_valid_item(*item) ); - 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 + // Conditions: beam is missile and not tracer. + if (is_tracer || flavour != BEAM_MISSILE) return; - } - - coord_def p = _p; - if (!in_bounds(p)) - p = beam.pos; + // Summoned creatures' thrown items disappear. if (item->flags & ISFLAG_SUMMONED) { - if (see_grid(p)) + if (see_grid(pos())) { mprf("%s %s!", item->name(DESC_CAP_THE).c_str(), - summoned_poof_msg(beam.beam_source, *item).c_str()); + summoned_poof_msg(beam_source, *item).c_str()); } - item_was_destroyed(*item, beam.beam_source); - - beam.dropped_item = true; - beam.item_pos = p; - beam.item_index = NON_ITEM; + item_was_destroyed(*item, beam_source); return; } - // Conditions: beam is missile and not tracer. - if (YOU_KILL(beam.thrower) - && !thrown_object_destroyed(item, p, false) - || MON_KILL(beam.thrower) - && !mons_thrown_object_destroyed(item, p, false, beam.beam_source)) + if (YOU_KILL(thrower) && !thrown_object_destroyed(item, pos(), false) + || MON_KILL(thrower) + && !mons_thrown_object_destroyed(item, pos(), false, beam_source)) { if (item->sub_type == MI_THROWING_NET) { // Player or monster on position is caught in net. - if (you.pos() == p && you.attribute[ATTR_HELD] - || mgrd(p) != NON_MONSTER && - mons_is_caught(&menv[mgrd(p)])) + if (you.pos() == pos() && you.attribute[ATTR_HELD] + || mgrd(pos()) != NON_MONSTER && + mons_is_caught(&menv[mgrd(pos())])) { // If no trapping net found mark this one. - if (get_trapping_net(p, true) == NON_ITEM) + if (get_trapping_net(pos(), true) == NON_ITEM) set_item_stationary(*item); } } - if (copy_item_to_grid( *item, p, 1 )) - { - beam.dropped_item = true; - beam.item_pos = p; - - // Dropped item might have merged into stack - if (is_stackable_item(*item)) - { - for (stack_iterator si(p); si; ++si) - { - if (items_stack(*item, *si)) - { - beam.item_index = si->index(); - break; - } - } - } - else - beam.item_index = igrd(p); - } - 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 - } + copy_item_to_grid(*item, pos(), 1); } } // Returns true if the beam hits the player, fuzzing the beam if necessary // for monsters without see invis firing tracers at the player. -static bool _found_player(const bolt &beam, const coord_def& p) +bool bolt::found_player() const { - const bool needs_fuzz = (beam.is_tracer && !beam.can_see_invis - && you.invisible() && !YOU_KILL(beam.thrower)); + const bool needs_fuzz = (is_tracer && !can_see_invis + && you.invisible() && !YOU_KILL(thrower)); const int dist = needs_fuzz? 2 : 0; - return (grid_distance(p, you.pos()) <= dist); + return (grid_distance(pos(), you.pos()) <= dist); } -int affect(bolt &beam, const coord_def& _p, item_def *item, bool affect_items) +void bolt::affect_ground() { - // Extra range used by hitting something. - int rangeUsed = 0; - - coord_def p = _p; - if (!in_bounds(_p)) - p = beam.pos; - - if (!item) - item = beam.item; - - if (beam.flavour == BEAM_VISUAL) - { - if (mgrd(p) != NON_MONSTER) - behaviour_event( &menv[mgrd(p)], ME_DISTURB ); - - return (0); - } - - 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); - - // If it's still a wall, quit - we can't do anything else to a - // wall (but we still might be able to do something to any - // monster inside the wall). Otherwise effects (like clouds, - // etc.) are still possible. - if (grid_is_solid(grd(p))) - { - int mid = mgrd(p); - if (mid != NON_MONSTER) - { - monsters *mon = &menv[mid]; - if (_affect_mon_in_wall(beam, NULL, p)) - rangeUsed += _affect_monster( beam, mon, item ); - else if (you.can_see(mon)) - { - mprf("The %s protects %s from harm.", - raw_feature_description(grd(mon->pos())).c_str(), - mon->name(DESC_NOCAP_THE).c_str()); - } - } - - beam.range_used += rangeUsed; - return (rangeUsed); - } - } - - // grd(p) will NOT be a wall for the remainder of this function. - - // If not a tracer, affect items and place clouds. - if (!beam.is_tracer) - { - if (affect_items) - { - const int burn_power = (beam.is_explosion) ? 5 : - (beam.is_beam) ? 3 : 2; - - expose_items_to_element(beam.flavour, p, burn_power); - } - rangeUsed += _affect_place_clouds(beam, p); - } + if (is_tracer) + return; - // If player is at this location, try to affect unless term_on_target. - if (_found_player(beam, p)) + if (affects_items) { - // Done this way so that poison blasts affect the target once (via - // place_cloud) and explosion spells only affect the target once - // (during the explosion phase, not an initial hit during the - // beam phase). - if (!beam.is_big_cloud - && (!beam.is_explosion || beam.in_explosion_phase)) - { - rangeUsed += _affect_player( beam, item, affect_items ); - } - - if (_beam_term_on_target(beam, p)) - { - beam.range_used += BEAM_STOP; - return (BEAM_STOP); - } + const int burn_power = (is_explosion) ? 5 : (is_beam) ? 3 : 2; + expose_items_to_element(flavour, pos(), burn_power); + affect_place_clouds(); } - - // If there is a monster at this location, affect it. - // Submerged monsters aren't really there. -- bwr - int mid = mgrd(p); - if (mid != NON_MONSTER) - { - monsters *mon = &menv[mid]; - const bool invisible = YOU_KILL(beam.thrower) && !you.can_see(mon); - - // Monsters submerged in shallow water can be targeted by beams - // aimed at that spot. - if (mon->alive() - // Don't stop tracers on invisible monsters. - && (!invisible || !beam.is_tracer) - && (!mon->submerged() - || beam.aimed_at_spot && beam.target == mon->pos() - && grd(mon->pos()) == DNGN_SHALLOW_WATER)) - { - if (!beam.is_big_cloud - && (!beam.is_explosion || beam.in_explosion_phase)) - { - rangeUsed += _affect_monster( beam, &menv[mid], item ); - } - - if (_beam_term_on_target(beam, p)) - { - beam.range_used += BEAM_STOP; - return (BEAM_STOP); - } - } - } - - beam.range_used += rangeUsed; - return (rangeUsed); } -static bool _is_fiery(const bolt &beam) +bool bolt::is_fiery() const { - return (beam.flavour == BEAM_FIRE || beam.flavour == BEAM_HELLFIRE - || beam.flavour == BEAM_LAVA); + return (flavour == BEAM_FIRE + || flavour == BEAM_HELLFIRE + || flavour == BEAM_LAVA); } -static bool _is_superhot(const bolt &beam) +bool bolt::is_superhot() const { - if (!_is_fiery(beam)) + if (!is_fiery()) return (false); - return (beam.name == "bolt of fire" - || beam.name == "bolt of magma" - || beam.name.find("hellfire") != std::string::npos - && beam.in_explosion_phase); + return (name == "bolt of fire" + || name == "bolt of magma" + || name.find("hellfire") != std::string::npos + && in_explosion_phase); } -static bool _affects_wall(const bolt &beam, int wall) +bool bolt::affects_wall(dungeon_feature_type wall) const { // digging - if (beam.flavour == BEAM_DIGGING) + if (flavour == BEAM_DIGGING) return (true); // FIXME: There should be a better way to test for ZAP_DISRUPTION // vs. ZAP_DISINTEGRATION. - if (beam.flavour == BEAM_DISINTEGRATION && beam.damage.num >= 3) + if (flavour == BEAM_DISINTEGRATION && damage.num >= 3) return (true); - if (_is_fiery(beam) && wall == DNGN_WAX_WALL) + if (is_fiery() && wall == DNGN_WAX_WALL) return (true); // eye of devastation? - if (beam.flavour == BEAM_NUKE) + if (flavour == BEAM_NUKE) return (true); return (false); } -// Returns amount of extra range used up by affectation of this wall. -static int _affect_wall(bolt &beam, const coord_def& p) +void bolt::affect_place_clouds() { - int rangeUsed = 0; - - // DIGGING - if (beam.flavour == BEAM_DIGGING) - { - if (grd(p) == DNGN_STONE_WALL - || grd(p) == DNGN_METAL_WALL - || grd(p) == DNGN_PERMAROCK_WALL - || grd(p) == DNGN_CLEAR_STONE_WALL - || grd(p) == DNGN_CLEAR_PERMAROCK_WALL - || !in_bounds(p)) - { - return (0); - } - - if (grd(p) == DNGN_ROCK_WALL || grd(p) == DNGN_CLEAR_ROCK_WALL) - { - grd(p) = DNGN_FLOOR; - // Mark terrain as changed so travel excludes can be updated - // as necessary. - // XXX: This doesn't work for some reason: after digging - // the wrong grids are marked excluded. - set_terrain_changed(p); - - // Blood does not transfer onto floor. - if (is_bloodcovered(p)) - env.map(p).property &= ~(FPROP_BLOODY); - - if (!beam.msg_generated) - { - if (!silenced(you.pos())) - { - mpr("You hear a grinding noise.", MSGCH_SOUND); - beam.obvious_effect = true; - } - - beam.msg_generated = true; - } - } - - return (rangeUsed); - } - // END DIGGING EFFECT - - // FIRE effect - if (_is_fiery(beam)) - { - const int wgrd = grd(p); - if (wgrd != DNGN_WAX_WALL) - return (0); - - if (!_is_superhot(beam)) - { - if (beam.flavour != BEAM_HELLFIRE) - { - if (see_grid(p)) - { - _beam_mpr(MSGCH_PLAIN, - "The wax appears to soften slightly."); - } - else if (player_can_smell()) - _beam_mpr(MSGCH_PLAIN, "You smell warm wax."); - } - - return (BEAM_STOP); - } - - grd(p) = DNGN_FLOOR; - if (see_grid(p)) - _beam_mpr(MSGCH_PLAIN, "The wax bubbles and burns!"); - else if (player_can_smell()) - _beam_mpr(MSGCH_PLAIN, "You smell burning wax."); - - place_cloud(CLOUD_FIRE, p, random2(10) + 15, _whose_kill(beam), - beam.killer()); - - beam.obvious_effect = true; - - return (BEAM_STOP); - } - - // NUKE / DISRUPT - if (beam.flavour == BEAM_DISINTEGRATION || beam.flavour == BEAM_NUKE) - { - int targ_grid = grd(p); - - if ((targ_grid == DNGN_ROCK_WALL || targ_grid == DNGN_WAX_WALL - || targ_grid == DNGN_CLEAR_ROCK_WALL) - && in_bounds(p)) - { - grd(p) = DNGN_FLOOR; - if (!silenced(you.pos())) - { - mpr("You hear a grinding noise.", MSGCH_SOUND); - beam.obvious_effect = true; - } - } - - if (targ_grid == DNGN_ORCISH_IDOL - || targ_grid == DNGN_GRANITE_STATUE) - { - grd(p) = DNGN_FLOOR; - - // Blood does not transfer onto floor. - if (is_bloodcovered(p)) - env.map(p).property &= ~(FPROP_BLOODY); - - if (!silenced(you.pos())) - { - if (!see_grid( p )) - mpr("You hear a hideous screaming!", MSGCH_SOUND); - else - { - mpr("The statue screams as its substance crumbles away!", - MSGCH_SOUND); - } - } - else if (see_grid(p)) - mpr("The statue twists and shakes as its substance crumbles away!"); + if (in_explosion_phase) + affect_place_explosion_clouds(); - if (targ_grid == DNGN_ORCISH_IDOL - && beam.beam_source == NON_MONSTER) - { - did_god_conduct(DID_DESTROY_ORCISH_IDOL, 8); - } - - beam.obvious_effect = true; - } + const coord_def p = pos(); - return (BEAM_STOP); - } - - return (rangeUsed); -} - -static int _affect_place_clouds(bolt &beam, const coord_def& p) -{ - if (beam.in_explosion_phase) + // Is there already a cloud here? + const int cloudidx = env.cgrid(p); + if (cloudidx != EMPTY_CLOUD) { - _affect_place_explosion_clouds( beam, p ); - return (0); // return value irrelevant for explosions - } - - // check for CLOUD HITS - if (env.cgrid(p) != EMPTY_CLOUD) // hit a cloud - { - // polymorph randomly changes clouds in its path - if (beam.flavour == BEAM_POLYMORPH) - { - env.cloud[ env.cgrid(p) ].type = - static_cast(1 + random2(8)); - } - - // now exit (all enchantments) - if (beam.is_enchantment()) - return (0); - - int clouty = env.cgrid(p); + cloud_type& ctype = env.cloud[cloudidx].type; + // Polymorph randomly changes clouds in its path + if (flavour == BEAM_POLYMORPH) + ctype = static_cast(1 + random2(8)); // fire cancelling cold & vice versa - if ((env.cloud[clouty].type == CLOUD_COLD - && (beam.flavour == BEAM_FIRE - || beam.flavour == BEAM_LAVA)) - || (env.cloud[clouty].type == CLOUD_FIRE - && beam.flavour == BEAM_COLD)) + 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( clouty ); - return (5); + delete_cloud(cloudidx); + range_used += 5; } + return; } - // POISON BLAST - if (beam.name == "blast of poison") - place_cloud( CLOUD_POISON, p, random2(4) + 2, _whose_kill(beam), - beam.killer() ); + // No clouds here, free to make new ones. + const dungeon_feature_type feat = grd(p); + + if (name == "blast of poison") + place_cloud(CLOUD_POISON, p, random2(4) + 2, whose_kill(), killer()); - // FIRE/COLD over water/lava - if (grd(p) == DNGN_LAVA && beam.flavour == BEAM_COLD - || grid_is_watery(grd(p)) && _is_fiery(beam)) + // Fire/cold over water/lava + if (feat == DNGN_LAVA && flavour == BEAM_COLD + || grid_is_watery(feat) && is_fiery()) { - place_cloud( CLOUD_STEAM, p, 2 + random2(5), _whose_kill(beam), - beam.killer() ); + place_cloud(CLOUD_STEAM, p, 2 + random2(5), whose_kill(), killer()); } - if (grid_is_watery(grd(p)) && beam.flavour == BEAM_COLD - && beam.damage.num * beam.damage.size > 35) + if (grid_is_watery(feat) && flavour == BEAM_COLD + && damage.num * damage.size > 35) { - place_cloud( CLOUD_COLD, p, beam.damage.num * beam.damage.size / 30 + 1, - _whose_kill(beam), beam.killer() ); + place_cloud(CLOUD_COLD, p, damage.num * damage.size / 30 + 1, + whose_kill(), killer()); } - // GREAT BLAST OF COLD - if (beam.name == "great blast of cold") - place_cloud( CLOUD_COLD, p, random2(5) + 3, _whose_kill(beam), - beam.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()); - // BALL OF STEAM - if (beam.name == "ball of steam") - place_cloud( CLOUD_STEAM, p, random2(5) + 2, _whose_kill(beam), - beam.killer() ); + if (flavour == BEAM_MIASMA) + place_cloud(CLOUD_MIASMA, p, random2(5) + 2, whose_kill(), killer()); - if (beam.flavour == BEAM_MIASMA) - place_cloud( CLOUD_MIASMA, p, random2(5) + 2, _whose_kill(beam), - beam.killer() ); + if (name == "poison gas") + place_cloud(CLOUD_POISON, p, random2(4) + 3, whose_kill(), killer()); - // POISON GAS - if (beam.name == "poison gas") - place_cloud( CLOUD_POISON, p, random2(4) + 3, _whose_kill(beam), - beam.killer() ); - - return (0); } -static void _affect_place_explosion_clouds(bolt &beam, const coord_def& p) +void bolt::affect_place_explosion_clouds() { - cloud_type cl_type; - int duration; + const coord_def p = pos(); - // First check: FIRE/COLD over water/lava. - if (grd(p) == DNGN_LAVA && beam.flavour == BEAM_COLD - || grid_is_watery(grd(p)) && _is_fiery(beam)) + // First check: fire/cold over water/lava. + if (grd(p) == DNGN_LAVA && flavour == BEAM_COLD + || grid_is_watery(grd(p)) && is_fiery()) { - place_cloud( CLOUD_STEAM, p, 2 + random2(5), _whose_kill(beam) ); + place_cloud(CLOUD_STEAM, p, 2 + random2(5), whose_kill(), killer()); return; } - if (beam.flavour >= BEAM_POTION_STINKING_CLOUD - && beam.flavour <= BEAM_POTION_RANDOM) + if (flavour >= BEAM_POTION_STINKING_CLOUD && flavour <= BEAM_POTION_RANDOM) { - duration = roll_dice( 2, 3 + beam.ench_power / 20 ); + const int duration = roll_dice(2, 3 + ench_power / 20); + cloud_type cl_type; - switch (beam.flavour) + switch (flavour) { case BEAM_POTION_STINKING_CLOUD: case BEAM_POTION_POISON: @@ -3488,7 +3011,7 @@ static void _affect_place_explosion_clouds(bolt &beam, const coord_def& p) case BEAM_POTION_GREY_SMOKE: case BEAM_POTION_BLUE_SMOKE: case BEAM_POTION_PURP_SMOKE: - cl_type = beam2cloud(beam.flavour); + cl_type = beam2cloud(flavour); break; case BEAM_POTION_RANDOM: @@ -3511,27 +3034,27 @@ static void _affect_place_explosion_clouds(bolt &beam, const coord_def& p) break; } - place_cloud( cl_type, p, duration, _whose_kill(beam) ); + place_cloud(cl_type, p, duration, whose_kill(), killer()); } // then check for more specific explosion cloud types. - if (beam.name == "ice storm") - place_cloud( CLOUD_COLD, p, 2 + random2avg(5, 2), _whose_kill(beam) ); + if (name == "ice storm") + place_cloud(CLOUD_COLD, p, 2 + random2avg(5,2), whose_kill(), killer()); - if (beam.name == "stinking cloud") + if (name == "stinking cloud") { - duration = 1 + random2(4) + random2( (beam.ench_power / 50) + 1 ); - place_cloud( CLOUD_STINK, p, duration, _whose_kill(beam) ); + const int duration = 1 + random2(4) + random2((ench_power / 50) + 1); + place_cloud( CLOUD_STINK, p, duration, whose_kill(), killer() ); } - if (beam.name == "great blast of fire") + if (name == "great blast of fire") { - duration = 1 + random2(5) + roll_dice( 2, beam.ench_power / 5 ); + 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(beam) ); + place_cloud( CLOUD_FIRE, p, duration, whose_kill(), killer() ); if (grd(p) == DNGN_FLOOR && mgrd(p) == NON_MONSTER && one_chance_in(4)) @@ -3540,7 +3063,7 @@ static void _affect_place_explosion_clouds(bolt &beam, const coord_def& p) (crawl_state.is_god_acting()) ? crawl_state.which_god_acting() : GOD_NO_GOD; const beh_type att = - _whose_kill(beam) == KC_OTHER ? BEH_HOSTILE : BEH_FRIENDLY; + whose_kill() == KC_OTHER ? BEH_HOSTILE : BEH_FRIENDLY; mons_place( mgen_data(MONS_FIRE_VORTEX, att, 2, SPELL_FIRE_STORM, p, @@ -3550,71 +3073,59 @@ static void _affect_place_explosion_clouds(bolt &beam, const coord_def& p) } // A little helper function to handle the calling of ouch()... -static void _beam_ouch(int dam, bolt &beam) +void bolt::internal_ouch(int dam) { monsters *monst = NULL; - if (!invalid_monster_index(beam.beam_source) - && menv[beam.beam_source].type != -1) - { - monst = &menv[beam.beam_source]; - } + if (!invalid_monster_index(beam_source) && menv[beam_source].type != -1) + monst = &menv[beam_source]; // The order of this is important. if (monst && (monst->type == MONS_GIANT_SPORE || monst->type == MONS_BALL_LIGHTNING)) { - ouch(dam, beam.beam_source, KILLED_BY_SPORE, - beam.aux_source.c_str()); + ouch(dam, beam_source, KILLED_BY_SPORE, aux_source.c_str()); } - else if (YOU_KILL(beam.thrower) && beam.aux_source.empty()) + else if (YOU_KILL(thrower) && aux_source.empty()) { - 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()); + 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 ouch(dam, NON_MONSTER, KILLED_BY_TARGETTING); } - else if (MON_KILL(beam.thrower)) - { - ouch(dam, beam.beam_source, KILLED_BY_BEAM, - beam.aux_source.c_str()); - } + else if (MON_KILL(thrower)) + ouch(dam, beam_source, KILLED_BY_BEAM, aux_source.c_str()); else // KILL_MISC || (YOU_KILL && aux_source) - { - ouch(dam, beam.beam_source, KILLED_BY_WILD_MAGIC, - beam.aux_source.c_str()); - } + ouch(dam, beam_source, KILLED_BY_WILD_MAGIC, aux_source.c_str()); } // [ds] Apply a fuzz if the monster lacks see invisible and is trying to target // an invisible player. This makes invisibility slightly more powerful. -static bool _fuzz_invis_tracer(bolt &beem) +bool bolt::fuzz_invis_tracer() { // Did the monster have a rough idea of where you are? - int dist = grid_distance(beem.target, you.pos()); + int dist = grid_distance(target, you.pos()); // No, ditch this. if (dist > 2) return (false); - const int beam_src = _beam_source(beem); + const int beam_src = beam_source_as_target(); if (beam_src != MHITNOT && beam_src != MHITYOU) { // Monsters that can sense invisible const monsters *mon = &menv[beam_src]; if (mons_sense_invis(mon)) - return (!dist); + return (dist == 0); } // Apply fuzz now. coord_def fuzz( random_range(-2, 2), random_range(-2, 2) ); - coord_def newtarget = beem.target + fuzz; + coord_def newtarget = target + fuzz; if (in_bounds(newtarget)) - beem.target = newtarget; + target = newtarget; // Fire away! return (true); @@ -3632,9 +3143,9 @@ bool test_beam_hit(int attack, int defence) || random2(attack) >= random2avg(defence, 2)); } -static std::string _beam_zapper(const bolt &beam) +std::string bolt::zapper() const { - const int beam_src = _beam_source(beam); + const int beam_src = beam_source_as_target(); if (beam_src == MHITYOU) return ("self"); else if (beam_src == MHITNOT) @@ -3643,14 +3154,14 @@ static std::string _beam_zapper(const bolt &beam) return menv[beam_src].name(DESC_PLAIN); } -static bool _beam_is_harmless(bolt &beam, monsters *mon) +bool bolt::is_harmless(const monsters *mon) const { - // For enchantments, this is already handled in _nasty_beam(). - if (beam.is_enchantment()) - return (!_nasty_beam(mon, beam)); + // For enchantments, this is already handled in nasty_to(). + if (is_enchantment()) + return (!nasty_to(mon)); // The others are handled here. - switch (beam.flavour) + switch (flavour) { case BEAM_VISUAL: case BEAM_DIGGING: @@ -3689,19 +3200,19 @@ static bool _beam_is_harmless(bolt &beam, monsters *mon) } } -static bool _beam_is_harmless_player(bolt &beam) +bool bolt::harmless_to_player() const { #ifdef DEBUG_DIAGNOSTICS - mprf(MSGCH_DIAGNOSTICS, "beam flavour: %d", beam.flavour); + mprf(MSGCH_DIAGNOSTICS, "beam flavour: %d", flavour); #endif // Shouldn't happen anyway since enchantments are either aimed at self // (not prompted) or cast at monsters and don't explode or bounce. - if (beam.is_enchantment()) + if (is_enchantment()) return (false); // The others are handled here. - switch (beam.flavour) + switch (flavour) { case BEAM_VISUAL: case BEAM_DIGGING: @@ -3738,12 +3249,12 @@ static bool _beam_is_harmless_player(bolt &beam) } } -static bool _beam_is_reflectable( const bolt &beam, const item_def *item ) +bool bolt::is_reflectable(const item_def *it) const { - if (beam.range_used >= beam.range) + if (range_used >= range) return (false); - return (item && is_shield(*item) && shield_reflects(*item)); + return (it && is_shield(*it) && shield_reflects(*it)); } static void _ident_reflector(item_def *item) @@ -3752,488 +3263,487 @@ static void _ident_reflector(item_def *item) set_ident_flags(*item, ISFLAG_KNOW_TYPE); } -static void _reflect_beam(bolt &beam) +void bolt::reflect() { - beam.reflections++; + reflections++; - // If it bounced off a wall before being refleced then head back towards + // If it bounced off a wall before being reflected then head back towards // the wall. - if (beam.bounces > 0 && in_bounds(beam.bounce_pos)) - beam.target = beam.bounce_pos; + if (bounces > 0 && in_bounds(bounce_pos)) + target = bounce_pos; else - beam.target = beam.source; + target = source; - beam.source = beam.pos; + source = pos(); // Reset bounce_pos, so that if we somehow reflect again before reaching // the wall that we won't keep heading towards the wall. - beam.bounce_pos.set(0, 0); + bounce_pos.reset(); - if (beam.pos == you.pos()) - beam.reflector = NON_MONSTER; - else if (mgrd(beam.pos) != NON_MONSTER) - beam.reflector = mgrd(beam.pos); + if (pos() == you.pos()) + reflector = NON_MONSTER; + else if (mgrd(pos()) != NON_MONSTER) + reflector = mgrd(source); else { - beam.reflector = -1; + 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"); + "monster (bolt = %s, item = %s)", name.c_str(), + item ? item->name(DESC_PLAIN).c_str() : "none"); #endif } - beam.flavour = beam.real_flavour; - - fire_beam(beam); + flavour = real_flavour; + choose_ray(); } -// Returns amount of extra range used up by affectation of the player. -static int _affect_player( bolt &beam, item_def *item, bool affect_items ) +void bolt::tracer_affect_player() { - // Digging -- don't care. - if (beam.flavour == BEAM_DIGGING) - return (0); - - // Check for tracer. - if (beam.is_tracer) + // Check whether thrower can see player, unless thrower == player. + if (YOU_KILL(thrower)) { - // Check whether thrower can see player, unless thrower == player. - if (YOU_KILL(beam.thrower)) + // Don't ask if we're aiming at ourselves. + if (!aimed_at_feet && !dont_stop_player && !harmless_to_player()) { - // Don't ask if we're aiming at ourselves. - if (!beam.aimed_at_feet && !beam.dont_stop_player - && !_beam_is_harmless_player(beam)) + if (yesno("That beam is likely to hit you. Continue anyway?", + false, 'n')) { - if (yesno("That beam is likely to hit you. Continue anyway?", - false, 'n')) - { - beam.fr_count += 1; - beam.fr_power += you.experience_level; - beam.dont_stop_player = true; - } - else - { - beam.beam_cancelled = true; - return (BEAM_STOP); - } - } - } - else if (beam.can_see_invis || !you.invisible() - || _fuzz_invis_tracer(beam)) - { - if (mons_att_wont_attack(beam.attitude)) - { - beam.fr_count += 1; - beam.fr_power += you.experience_level; + fr_count++; + fr_power += you.experience_level; + dont_stop_player = true; } else { - beam.foe_count++; - beam.foe_power += you.experience_level; + beam_cancelled = true; + finish_beam(); } } - return (_range_used_on_hit(beam)); } + else if (can_see_invis || !you.invisible() || fuzz_invis_tracer()) + { + if (mons_att_wont_attack(attitude)) + { + fr_count++; + fr_power += you.experience_level; + } + else + { + foe_count++; + foe_power += you.experience_level; + } + } + range_used += range_used_on_hit(); +} - // Trigger an interrupt, so travel will stop on misses - // which generate smoke. - if (!YOU_KILL(beam.thrower)) - interrupt_activity(AI_MONSTER_ATTACKS); +bool bolt::misses_player() +{ + if (is_explosion || aimed_at_feet || auto_hit || is_enchantment()) + return (false); - // BEGIN real beam code - beam.msg_generated = true; + const int dodge = player_evasion(); + int real_tohit = hit; + + // Monsters shooting at an invisible player are very inaccurate. + if (you.invisible() && !can_see_invis) + real_tohit /= 2; - // Use beamHit, NOT beam.hit, for modification of tohit.. geez! - int beamHit = beam.hit; + if (is_beam) + { + // Beams can be dodged. + if (player_light_armour(true) && !aimed_at_feet && coinflip()) + exercise(SK_DODGING, 1); - // Monsters shooting at an invisible player are very inaccurate. - if (you.invisible() && !beam.can_see_invis) - beamHit /= 2; + 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 (!beam.is_enchantment()) + if (!test_beam_hit(real_tohit, dodge)) + { + mprf("The %s misses you.", name.c_str()); + return (true); + } + } + else if (is_blockable()) { - if (!beam.is_explosion && !beam.aimed_at_feet) + // Non-beams can be blocked or dodged. + if (you.shield() + && !aimed_at_feet + && player_shield_class() > 0) { - // BEGIN BEAM/MISSILE - int dodge = player_evasion(); + // We use the original to-hit here. + const int testhit = random2(hit * 130 / 100 + + you.shield_block_penalty()); - if (beam.is_beam) - { - // Beams can be dodged. - if (player_light_armour(true) - && !beam.aimed_at_feet && coinflip()) - { - exercise(SK_DODGING, 1); - } + const int block = you.shield_bonus(); - if (you.duration[DUR_DEFLECT_MISSILES]) - beamHit = random2(beamHit * 2) / 3; - else if (you.duration[DUR_REPEL_MISSILES] - || player_mutation_level(MUT_REPULSION_FIELD) == 3) +#ifdef DEBUG_DIAGNOSTICS + mprf(MSGCH_DIAGNOSTICS, "Beamshield: hit: %d, block %d", + testhit, block); +#endif + if (testhit < block) + { + if (is_reflectable(you.shield())) { - beamHit -= random2(beamHit / 2); + mprf( "Your %s reflects the %s!", + you.shield()->name(DESC_PLAIN).c_str(), + name.c_str() ); + _ident_reflector(you.shield()); + reflect(); } - - if (!test_beam_hit(beamHit, dodge)) + else { - mprf("The %s misses you.", beam.name.c_str()); - return (0); // no extra used by miss! + mprf( "You block the %s.", name.c_str() ); + finish_beam(); } + you.shield_block_succeeded(); + return (true); } - else if (_beam_is_blockable(beam)) - { - // Non-beams can be blocked or dodged. - if (you.shield() - && !beam.aimed_at_feet - && player_shield_class() > 0) - { - int exer = one_chance_in(3) ? 1 : 0; - const int hit = random2( beam.hit * 130 / 100 - + you.shield_block_penalty() ); - - const int block = you.shield_bonus(); -#ifdef DEBUG_DIAGNOSTICS - mprf(MSGCH_DIAGNOSTICS, "Beamshield: hit: %d, block %d", - hit, block); -#endif - if (hit < block) - { - 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); - } - - // Some training just for the "attempt". - if (coinflip()) - exercise( SK_SHIELDS, exer ); - } + // Some training just for the "attempt". + if (coinflip()) + exercise(SK_SHIELDS, one_chance_in(3) ? 1 : 0); + } - if (player_light_armour(true) && !beam.aimed_at_feet - && coinflip()) - { - exercise(SK_DODGING, 1); - } + if (player_light_armour(true) && !aimed_at_feet && coinflip()) + exercise(SK_DODGING, 1); - if (you.duration[DUR_DEFLECT_MISSILES]) - beamHit = random2(beamHit / 2); - else if (you.duration[DUR_REPEL_MISSILES] - || player_mutation_level(MUT_REPULSION_FIELD) == 3) - { - beamHit = random2(beamHit); - } + 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); + } - // miss message - if (!test_beam_hit(beamHit, dodge)) - { - mprf("The %s misses you.", beam.name.c_str()); - return (0); - } - } + // miss message + if (!test_beam_hit(real_tohit, dodge)) + { + mprf("The %s misses you.", name.c_str()); + return (true); } } - else + return (false); +} + +void bolt::affect_player_enchantment() +{ + if ((has_saving_throw() || flavour == BEAM_POLYMORPH) + && you_resist_magic(ench_power)) { - bool nasty = true, nice = false; - - // BEGIN enchantment beam - if (beam.flavour != BEAM_HASTE - && beam.flavour != BEAM_INVISIBILITY - && beam.flavour != BEAM_HEALING - && beam.flavour != BEAM_POLYMORPH - && beam.flavour != BEAM_DISPEL_UNDEAD - && (beam.flavour != BEAM_TELEPORT && beam.flavour != BEAM_BANISH - || !beam.aimed_at_feet) - && you_resist_magic( beam.ench_power )) + // You resisted it. + + // Give a message. + bool need_msg = true; + if (thrower != KILL_YOU_MISSILE && !invalid_monster_index(beam_source)) { - bool need_msg = true; - if (beam.thrower != KILL_YOU_MISSILE - && !invalid_monster_index(beam.beam_source)) + monsters *mon = &menv[beam_source]; + if (!player_monster_visible(mon)) { - monsters *mon = &menv[beam.beam_source]; - if (!player_monster_visible(mon)) - { - mpr("Something tries to affect you, but you resist."); - need_msg = false; - } + mpr("Something tries to affect you, but you resist."); + need_msg = false; } - if (need_msg) - canned_msg(MSG_YOU_RESIST); + } + if (need_msg) + canned_msg(MSG_YOU_RESIST); - // You *could* have gotten a free teleportation in the Abyss, - // but no, you resisted. - if (beam.flavour != BEAM_TELEPORT && you.level_type == LEVEL_ABYSS) - xom_is_stimulated(255); + // You *could* have gotten a free teleportation in the Abyss, + // but no, you resisted. + if (flavour == BEAM_TELEPORT && you.level_type == LEVEL_ABYSS) + xom_is_stimulated(255); - return (_range_used_on_hit(beam)); - } + range_used += range_used_on_hit(); + return; + } + + // You didn't resist it. + _ench_animation( real_flavour ); - _ench_animation( beam.real_flavour ); + bool nasty = true, nice = false; - // these colors are misapplied - see mons_ench_f2() {dlb} - switch (beam.flavour) + switch (flavour) + { + case BEAM_SLEEP: + you.put_to_sleep(ench_power); + break; + + case BEAM_BACKLIGHT: + if (!you.duration[DUR_INVIS]) { - case BEAM_SLEEP: - you.put_to_sleep(beam.ench_power); - break; + if (you.duration[DUR_BACKLIGHT]) + mpr("You glow brighter."); + else + mpr("You are outlined in light."); - case BEAM_BACKLIGHT: - if (!you.duration[DUR_INVIS]) - { - if (you.duration[DUR_BACKLIGHT]) - mpr("You glow brighter."); - else - mpr("You are outlined in light."); + you.duration[DUR_BACKLIGHT] += random_range(15, 35); + if (you.duration[DUR_BACKLIGHT] > 250) + you.duration[DUR_BACKLIGHT] = 250; - you.duration[DUR_BACKLIGHT] += random_range(15, 35); - if (you.duration[DUR_BACKLIGHT] > 250) - you.duration[DUR_BACKLIGHT] = 250; + obvious_effect = true; + } + else + { + mpr("You feel strangely conspicuous."); - beam.obvious_effect = true; - } - else - { - mpr("You feel strangely conspicuous."); + you.duration[DUR_BACKLIGHT] += random_range(3, 5); + if (you.duration[DUR_BACKLIGHT] > 250) + you.duration[DUR_BACKLIGHT] = 250; - you.duration[DUR_BACKLIGHT] += random_range(3, 5); - if (you.duration[DUR_BACKLIGHT] > 250) - you.duration[DUR_BACKLIGHT] = 250; + obvious_effect = true; + } + break; - beam.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 ); - case BEAM_POLYMORPH: - if (MON_KILL(beam.thrower)) - { - mpr("Strange energies course through your body."); - you.mutate(); - beam.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; - break; + case BEAM_SLOW: + potion_effect( POT_SLOWING, ench_power ); + obvious_effect = true; + break; - case BEAM_SLOW: - potion_effect( POT_SLOWING, beam.ench_power ); - beam.obvious_effect = true; - break; + case BEAM_HASTE: + potion_effect( POT_SPEED, ench_power ); + contaminate_player( 1, effect_known ); + obvious_effect = true; + nasty = false; + nice = true; + break; - case BEAM_HASTE: - potion_effect( POT_SPEED, beam.ench_power ); - contaminate_player( 1, beam.effect_known ); - beam.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_HEALING: - potion_effect( POT_HEAL_WOUNDS, beam.ench_power ); - beam.obvious_effect = true; - nasty = false; - nice = true; - break; + case BEAM_PARALYSIS: + potion_effect( POT_PARALYSIS, ench_power ); + obvious_effect = true; + break; - case BEAM_PARALYSIS: - potion_effect( POT_PARALYSIS, beam.ench_power ); - beam.obvious_effect = true; - break; + case BEAM_PETRIFY: + you.petrify( ench_power ); + obvious_effect = true; + break; - case BEAM_PETRIFY: - you.petrify( beam.ench_power ); - beam.obvious_effect = true; - break; + case BEAM_CONFUSION: + potion_effect( POT_CONFUSION, ench_power ); + obvious_effect = true; + break; - case BEAM_CONFUSION: - potion_effect( POT_CONFUSION, beam.ench_power ); - beam.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_INVISIBILITY: - potion_effect( POT_INVISIBILITY, beam.ench_power ); - contaminate_player( 1 + random2(2), beam.effect_known ); - beam.obvious_effect = true; - nasty = false; - nice = true; - break; + case BEAM_TELEPORT: + you_teleport(); - case BEAM_TELEPORT: - you_teleport(); + // An enemy helping you escape while in the Abyss, or an + // enemy stabilizing a teleport that was about to happen. + if (!mons_att_wont_attack(attitude) + && you.level_type == LEVEL_ABYSS) + { + xom_is_stimulated(255); + } - // An enemy helping you escape while in the Abyss, or an - // enemy stabilizing a teleport that was about to happen. - if (!mons_att_wont_attack(beam.attitude) - && you.level_type == LEVEL_ABYSS) - { - xom_is_stimulated(255); - } + obvious_effect = true; + break; - beam.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; // enslavement - confusion? - case BEAM_BLINK: - random_blink(false); - beam.obvious_effect = true; + 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_CHARM: - potion_effect( POT_CONFUSION, beam.ench_power ); - beam.obvious_effect = true; - break; // enslavement - confusion? - - case BEAM_BANISH: - if (YOU_KILL(beam.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 = _beam_zapper(beam); - beam.obvious_effect = true; + case BEAM_PAIN: + if (player_res_torment()) + { + mpr("You are unaffected."); break; + } - case BEAM_PAIN: - if (player_res_torment()) - { - mpr("You are unaffected."); - break; - } + mpr("Pain shoots through your body!"); - mpr("Pain shoots through your body!"); + if (aux_source.empty()) + aux_source = "by nerve-wracking pain"; - if (beam.aux_source.empty()) - beam.aux_source = "by nerve-wracking pain"; + internal_ouch(damage.roll()); + obvious_effect = true; + break; - _beam_ouch(beam.damage.roll(), beam); - beam.obvious_effect = true; + case BEAM_DISPEL_UNDEAD: + if (!you.is_undead) + { + mpr("You are unaffected."); break; + } - case BEAM_DISPEL_UNDEAD: - if (!you.is_undead) - { - mpr("You are unaffected."); - break; - } - - mpr( "You convulse!" ); + mpr( "You convulse!" ); - if (beam.aux_source.empty()) - beam.aux_source = "by dispel undead"; + if (aux_source.empty()) + aux_source = "by dispel undead"; - if (you.is_undead == US_SEMI_UNDEAD) + if (you.is_undead == US_SEMI_UNDEAD) + { + if (you.hunger_state == HS_ENGORGED) + damage.size /= 2; + else if (you.hunger_state > HS_SATIATED) { - if (you.hunger_state == HS_ENGORGED) - beam.damage.size /= 2; - else if (you.hunger_state > HS_SATIATED) - { - beam.damage.size *= 2; - beam.damage.size /= 3; - } + damage.size *= 2; + damage.size /= 3; } - _beam_ouch(beam.damage.roll(), beam); - beam.obvious_effect = true; - break; + } + internal_ouch(damage.roll()); + obvious_effect = true; + break; - case BEAM_DISINTEGRATION: - mpr("You are blasted!"); + case BEAM_DISINTEGRATION: + mpr("You are blasted!"); - if (beam.aux_source.empty()) - beam.aux_source = "a disintegration bolt"; + if (aux_source.empty()) + aux_source = "a disintegration bolt"; - _beam_ouch(beam.damage.roll(), beam); - beam.obvious_effect = true; - break; + internal_ouch(damage.roll()); + obvious_effect = true; + break; - default: - // _All_ enchantments should be enumerated here! - mpr("Software bugs nibble your toes!"); - break; - } + default: + // _All_ enchantments should be enumerated here! + mpr("Software bugs nibble your toes!"); + break; + } - if (nasty) + if (nasty) + { + if (mons_att_wont_attack(attitude)) { - if (mons_att_wont_attack(beam.attitude)) + fr_hurt++; + if (beam_source == NON_MONSTER) { - beam.fr_hurt++; - if (beam.beam_source == NON_MONSTER) - { - // Beam from player rebounded and hit player. - if (!beam.aimed_at_feet) - xom_is_stimulated(255); - } - else - { - // Beam from an ally or neutral. - xom_is_stimulated(128); - } + // Beam from player rebounded and hit player. + if (!aimed_at_feet) + xom_is_stimulated(255); } - else - beam.foe_hurt++; - } - - if (nice) - { - if (mons_att_wont_attack(beam.attitude)) - beam.fr_helped++; else { - beam.foe_helped++; + // Beam from an ally or neutral. xom_is_stimulated(128); } } + else + foe_hurt++; + } + + if (nice) + { + if (mons_att_wont_attack(attitude)) + fr_helped++; + else + { + foe_helped++; + xom_is_stimulated(128); + } + } + + // Regardless of effect, we need to know if this is a stopper + // or not - it seems all of the above are. + range_used += range_used_on_hit(); +} + + +void bolt::affect_player() +{ + // Digging -- don't care. + if (flavour == BEAM_DIGGING) + return; + + if (is_tracer) + { + tracer_affect_player(); + return; + } - // Regardless of affect, we need to know if this is a stopper - // or not - it seems all of the above are. - return (_range_used_on_hit(beam)); + // Trigger an interrupt, so travel will stop on misses + // which generate smoke. + if (!YOU_KILL(thrower)) + interrupt_activity(AI_MONSTER_ATTACKS); - // END enchantment beam + if (is_enchantment()) + { + affect_player_enchantment(); + return; } - // THE BEAM IS NOW GUARANTEED TO BE A NON-ENCHANTMENT WHICH HIT + msg_generated = true; + + if (misses_player()) + return; - const bool engulfs = (beam.is_explosion || beam.is_big_cloud); + const bool engulfs = (is_explosion || is_big_cloud); mprf( "The %s %s you!", - beam.name.c_str(), (engulfs) ? "engulfs" : "hits" ); + name.c_str(), (engulfs) ? "engulfs" : "hits" ); + // FIXME: Lots of duplicated code here (compare handling of + // monsters) int hurted = 0; - int burn_power = (beam.is_explosion) ? 5 : - (beam.is_beam) ? 3 : 2; + int burn_power = (is_explosion) ? 5 : (is_beam) ? 3 : 2; // Roll the damage. - hurted += beam.damage.roll(); + hurted += damage.roll(); #if DEBUG_DIAGNOSTICS int roll = hurted; #endif int armour_damage_reduction = random2( 1 + player_AC() ); - if (beam.flavour == BEAM_ELECTRICITY) + if (flavour == BEAM_ELECTRICITY) armour_damage_reduction /= 2; hurted -= armour_damage_reduction; - // shrapnel - if (beam.flavour == BEAM_FRAG && !player_light_armour()) + // shrapnel has triple AC reduction + if (flavour == BEAM_FRAG && !player_light_armour()) { hurted -= random2( 1 + player_AC() ); hurted -= random2( 1 + player_AC() ); @@ -4257,13 +3767,12 @@ static int _affect_player( bolt &beam, item_def *item, bool affect_items ) bool was_affected = false; int old_hp = you.hp; - if (hurted < 0) - hurted = 0; + hurted = std::max(hurted, 0); // If the beam is an actual missile or of the MMISSILE type (Earth magic) // we might bleed on the floor. if (!engulfs - && (beam.flavour == BEAM_MISSILE || beam.flavour == BEAM_MMISSILE)) + && (flavour == BEAM_MISSILE || flavour == BEAM_MMISSILE)) { int blood = hurted/2; // assumes DVORP_PIERCING, factor: 0.5 if (blood > you.hp) @@ -4272,9 +3781,9 @@ static int _affect_player( bolt &beam, item_def *item, bool affect_items ) bleed_onto_floor(you.pos(), -1, blood, true); } - hurted = check_your_resists( hurted, beam.flavour ); + hurted = check_your_resists( hurted, flavour ); - if (beam.flavour == BEAM_MIASMA && hurted > 0) + if (flavour == BEAM_MIASMA && hurted > 0) { if (player_res_poison() <= 0) { @@ -4300,7 +3809,7 @@ static int _affect_player( bolt &beam, item_def *item, bool affect_items ) else if (item->special == SPMSL_POISONED) { if (!player_res_poison() - && (hurted || beam.ench_power == AUTOMATIC_HIT + && (hurted || ench_power == AUTOMATIC_HIT && x_chance_in_y(90 - 3 * player_AC(), 100))) { poison_player(1 + random2(3)); @@ -4311,7 +3820,7 @@ static int _affect_player( bolt &beam, item_def *item, bool affect_items ) { if (x_chance_in_y(90 - 3 * player_AC(), 100)) { - curare_hits_player(actor_to_death_source(beam.agent()), + curare_hits_player(actor_to_death_source(agent()), 1 + random2(3)); was_affected = true; } @@ -4319,7 +3828,7 @@ static int _affect_player( bolt &beam, item_def *item, bool affect_items ) } // Sticky flame. - if (beam.name == "sticky flame") + if (name == "sticky flame") { if (!player_res_sticky_flame()) { @@ -4328,25 +3837,25 @@ static int _affect_player( bolt &beam, item_def *item, bool affect_items ) } } - if (affect_items) + if (affects_items) { // Simple cases for scroll burns. - if (beam.flavour == BEAM_LAVA || beam.name == "hellfire") + if (flavour == BEAM_LAVA || name == "hellfire") expose_player_to_element(BEAM_LAVA, burn_power); // More complex (geez..) - if (beam.flavour == BEAM_FIRE && beam.name != "ball of steam") + if (flavour == BEAM_FIRE && name != "ball of steam") expose_player_to_element(BEAM_FIRE, burn_power); // Potions exploding. - if (beam.flavour == BEAM_COLD) + if (flavour == BEAM_COLD) expose_player_to_element(BEAM_COLD, burn_power); - if (beam.flavour == BEAM_ACID) + if (flavour == BEAM_ACID) splash_with_acid(5); // Spore pops. - if (beam.in_explosion_phase && beam.flavour == BEAM_SPORE) + if (in_explosion_phase && flavour == BEAM_SPORE) expose_player_to_element(BEAM_SPORE, burn_power); } @@ -4356,35 +3865,35 @@ static int _affect_player( bolt &beam, item_def *item, bool affect_items ) if (hurted > 0 || old_hp < you.hp || was_affected) { - if (mons_att_wont_attack(beam.attitude)) + if (mons_att_wont_attack(attitude)) { - beam.fr_hurt++; + fr_hurt++; // Beam from player rebounded and hit player. // Xom's amusement at the player's being damaged is handled // elsewhere. - if (beam.beam_source == NON_MONSTER) + if (beam_source == NON_MONSTER) { - if (!beam.aimed_at_feet) + if (!aimed_at_feet) xom_is_stimulated(255); } else if (was_affected) xom_is_stimulated(128); } else - beam.foe_hurt++; + foe_hurt++; } - _beam_ouch(hurted, beam); + internal_ouch(hurted); - return (_range_used_on_hit( beam )); + range_used += range_used_on_hit(); } -static int _beam_source(const bolt &beam) +int bolt::beam_source_as_target() const { - return (MON_KILL(beam.thrower) ? beam.beam_source : - beam.thrower == KILL_MISC ? MHITNOT - : MHITYOU); + return (MON_KILL(thrower) ? beam_source : + thrower == KILL_MISC ? MHITNOT + : MHITYOU); } static int _name_to_skill_level(const std::string& name) @@ -4408,314 +3917,450 @@ static int _name_to_skill_level(const std::string& name) return (2 * you.skills[type]); } -static void _update_hurt_or_helped(bolt &beam, monsters *mon) +void bolt::update_hurt_or_helped(monsters *mon) { - if (!mons_atts_aligned(beam.attitude, mons_attitude(mon))) + if (!mons_atts_aligned(attitude, mons_attitude(mon))) { - if (_nasty_beam(mon, beam)) - beam.foe_hurt++; - else if (_nice_beam(mon, beam)) - beam.foe_helped++; + if (nasty_to(mon)) + foe_hurt++; + else if (nice_to(mon)) + foe_helped++; } else { - if (_nasty_beam(mon, beam)) + if (nasty_to(mon)) { - beam.fr_hurt++; + fr_hurt++; // Harmful beam from this monster rebounded and hit the monster. - int midx = monster_index(mon); - if (midx == beam.beam_source) + if (!is_tracer + && static_cast(monster_index(mon)) == beam_source) + { xom_is_stimulated(128); + } } - else if (_nice_beam(mon, beam)) - beam.fr_helped++; + else if (nice_to(mon)) + fr_helped++; } } -// Returns amount of range used up by affectation of this monster. -static int _affect_monster(bolt &beam, monsters *mon, item_def *item) +void bolt::tracer_enchantment_affect_monster(monsters* mon) { - const int tid = mgrd(mon->pos()); - const int mons_type = menv[tid].type; - const int thrower = YOU_KILL(beam.thrower) ? KILL_YOU_MISSILE - : KILL_MON_MISSILE; - const bool submerged = mon->submerged(); - - int hurt; - int hurt_final; - - // digging -- don't care. - if (beam.flavour == BEAM_DIGGING) - return (0); + handle_stop_attack_prompt(mon); + if (!beam_cancelled) + range_used += range_used_on_hit(); +} - // fire storm creates these, so we'll avoid affecting them - if (beam.name == "great blast of fire" - && mon->type == MONS_FIRE_VORTEX) - { - return (0); - } +// Return false if we should skip handling this monster. +bool bolt::determine_damage(monsters* mon, int& preac, int& postac, int& final) +{ + // preac: damage before AC modifier + // postac: damage after AC modifier + // final: damage after AC and resists + // All these are invalid if we return false. + + // Tracers get the mean. + if (is_tracer) + preac = (damage.num * (damage.size + 1)) / 2; + else + preac = damage.roll(); - // check for tracer - if (beam.is_tracer) + // Submerged monsters get some perks. + if (mon->submerged()) { - // Can we see this monster? - if (!beam.can_see_invis && menv[tid].invisible() - || (thrower == KILL_YOU_MISSILE && !see_grid(mon->pos()))) + // The beam will pass overhead unless it's aimed at them. + if (!aimed_at_spot) + return false; + + // Electricity is ineffective. + if (flavour == BEAM_ELECTRICITY) { - // Can't see this monster, ignore it. - return 0; + if (!is_tracer && see_grid(mon->pos())) + mprf("The %s arcs harmlessly into the water.", name.c_str()); + finish_beam(); + return false; } - // Is this a self-detonating monster? Don't consider it either way - // if it is. - if (mons_self_destructs(mon)) - return (BEAM_STOP); - - if (!mons_atts_aligned(beam.attitude, mons_attitude(mon))) - { - beam.foe_count += 1; - beam.foe_power += mons_power(mons_type); - } - else - { - beam.fr_count += 1; - beam.fr_power += mons_power(mons_type); - } + // Otherwise, 1/3 damage reduction. + preac = (preac * 2) / 3; } - else if ((beam.flavour == BEAM_DISINTEGRATION || beam.flavour == BEAM_NUKE) - && mons_is_statue(mons_type) && !mons_is_icy(mons_type)) + + postac = preac - maybe_random2(1 + mon->ac, !is_tracer); + + // Fragmentation has triple AC reduction. + if (flavour == BEAM_FRAG) { - if (!silenced(you.pos())) - { - if (!see_grid( 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_grid( mon->pos() )) - { - mpr("The statue twists and shakes as its substance " - "crumbles away!"); - } - beam.obvious_effect = true; - _update_hurt_or_helped(beam, mon); - mon->hurt(beam.agent(), INSTANT_DEATH); - return (BEAM_STOP); + postac -= maybe_random2(1 + mon->ac, !is_tracer); + postac -= maybe_random2(1 + mon->ac, !is_tracer); } - bool hit_woke_orc = false; - if (beam.is_enchantment()) // enchantments: no to-hit check + postac = std::max(postac, 0); + + // Don't do side effects (beam might miss or be a tracer.) + final = mons_adjust_flavoured(mon, *this, preac, false); + + return true; +} + +void bolt::handle_stop_attack_prompt(monsters* mon) +{ + if ((thrower == KILL_YOU_MISSILE || thrower == KILL_YOU) + && !is_harmless(mon)) { - if (beam.is_tracer) + if ((fr_count == 1 && !dont_stop_fr) + || (foe_count == 1 && !dont_stop_foe)) { - if (beam.thrower == KILL_YOU_MISSILE || beam.thrower == KILL_YOU) + const bool on_target = (target == mon->pos()); + if (stop_attack_prompt(mon, true, on_target)) { - if (!_beam_is_harmless(beam, mon) - && (beam.fr_count == 1 && !beam.dont_stop_fr - || beam.foe_count == 1 && !beam.dont_stop_foe)) - { - const bool target = (beam.target == mon->pos()); - - if (stop_attack_prompt(mon, true, target)) - { - beam.beam_cancelled = true; - return (BEAM_STOP); - } - if (beam.fr_count == 1 && !beam.dont_stop_fr) - beam.dont_stop_fr = true; - else - beam.dont_stop_foe = true; - } + beam_cancelled = true; + finish_beam(); + } + else + { + if (fr_count == 1) + dont_stop_fr = true; + else if (foe_count == 1) + dont_stop_foe = true; } - - return (_range_used_on_hit(beam)); } + } +} - // BEGIN non-tracer enchantment - // Submerged monsters are unaffected by enchantments. - if (submerged) - return (0); +void bolt::tracer_nonenchantment_affect_monster(monsters* mon) +{ + int preac, post, final; + if ( !determine_damage(mon, preac, post, final) ) + return; - god_conduct_trigger conducts[3]; - disable_attack_conducts(conducts); + // Maybe the user wants to cancel at this point. + handle_stop_attack_prompt(mon); + if (beam_cancelled) + return; + + // Check only if actual damage. + if (final > 0) + { + // Monster could be hurt somewhat, but only apply the + // monster's power based on how badly it is affected. + // For example, if a fire giant (power 16) threw a + // fireball at another fire giant, and it only took + // 1/3 damage, then power of 5 would be applied to + // foe_power or fr_power. + + // Counting foes is only important for monster tracers. + if (!mons_atts_aligned(attitude, mons_attitude(mon))) + foe_power += 2 * final * mons_power(mon->type) / preac; + else + fr_power += 2 * final * mons_power(mon->type) / preac; + } + + // Either way, we could hit this monster, so update range used. + range_used += range_used_on_hit(); +} - // Nasty enchantments will annoy the monster, and are considered - // naughty (even if a monster might resist). - if (_nasty_beam(mon, beam)) - { - if (YOU_KILL(beam.thrower)) - { - if (is_sanctuary(mon->pos()) || is_sanctuary(you.pos())) - remove_sanctuary(true); +void bolt::tracer_affect_monster(monsters* mon) +{ + // Ignore unseen monsters. + if (!can_see_invis && mon->invisible() + || (YOU_KILL(thrower) && !see_grid(mon->pos()))) + { + return; + } - set_attack_conducts(conducts, mon, player_monster_visible(mon)); + // Ignore self-detonating monsters. + if (mons_self_destructs(mon)) + return; - if (you.religion == GOD_BEOGH - && mons_species(mon->type) == MONS_ORC - && mons_is_sleeping(mon) && !player_under_penance() - && you.piety >= piety_breakpoint(2) && mons_near(mon)) - { - hit_woke_orc = true; - } - } + // Update friend or foe encountered. + if (!mons_atts_aligned(attitude, mons_attitude(mon))) + { + foe_count++; + foe_power += mons_power(mon->type); + } + else + { + fr_count++; + fr_power += mons_power(mon->type); + } - behaviour_event(mon, ME_ANNOY, _beam_source(beam)); - } - else - behaviour_event(mon, ME_ALERT, _beam_source(beam)); + if (is_enchantment()) + tracer_enchantment_affect_monster(mon); + else + tracer_nonenchantment_affect_monster(mon); +} - enable_attack_conducts(conducts); +void bolt::enchantment_affect_monster(monsters* mon) +{ + // Submerged monsters are unaffected by enchantments. + if (mon->submerged()) + return; - // Doing this here so that the player gets to see monsters - // "flicker and vanish" when turning invisible.... - _ench_animation( beam.real_flavour, mon ); + god_conduct_trigger conducts[3]; + disable_attack_conducts(conducts); + + bool hit_woke_orc = false; - // now do enchantment affect - mon_resist_type ench_result = _affect_monster_enchantment(beam, mon); - if (mon->alive()) + // Nasty enchantments will annoy the monster, and are considered + // naughty (even if a monster might resist). + if (nasty_to(mon)) + { + if (YOU_KILL(thrower)) { - if (mons_is_mimic(mon->type)) - mimic_alert(mon); + if (is_sanctuary(mon->pos()) || is_sanctuary(you.pos())) + remove_sanctuary(true); + + set_attack_conducts(conducts, mon, player_monster_visible(mon)); - switch (ench_result) + if (you.religion == GOD_BEOGH + && mons_species(mon->type) == MONS_ORC + && mons_is_sleeping(mon) && !player_under_penance() + && you.piety >= piety_breakpoint(2) && mons_near(mon)) { - case MON_RESIST: - if (simple_monster_message(mon, " resists.")) - beam.msg_generated = true; - break; - case MON_UNAFFECTED: - if (simple_monster_message(mon, " is unaffected.")) - beam.msg_generated = true; - break; - default: - _update_hurt_or_helped(beam, mon); - break; + hit_woke_orc = true; } } - if (hit_woke_orc) - beogh_follower_convert(mon, true); - - return (_range_used_on_hit(beam)); - - // END non-tracer enchantment + behaviour_event(mon, ME_ANNOY, beam_source_as_target()); } + else + behaviour_event(mon, ME_ALERT, beam_source_as_target()); + enable_attack_conducts(conducts); - // BEGIN non-enchantment (could still be tracer) - if (submerged && !beam.aimed_at_spot) - return (0); // missed me! - - // We need to know how much the monster _would_ be hurt by this, before - // we decide if it actually hits. + // Doing this here so that the player gets to see monsters + // "flicker and vanish" when turning invisible.... + _ench_animation( real_flavour, mon ); - // Roll the damage: - hurt = beam.damage.roll(); + // Try to hit the monster with the enchantment. + const mon_resist_type ench_result = try_enchant_monster(mon); - // Water absorbs some of the damage for submerged monsters. - if (submerged) + if (mon->alive()) // Aftereffects. { - // Can't hurt submerged water creatures with electricity. - if (beam.flavour == BEAM_ELECTRICITY) + // Mimics become known. + if (mons_is_mimic(mon->type)) + mimic_alert(mon); + + // Message or record the success/failure. + switch (ench_result) { - if (see_grid(mon->pos()) && !beam.is_tracer) - { - mprf("The %s arcs harmlessly into the water.", - beam.name.c_str()); - } - return (BEAM_STOP); + case MON_RESIST: + if (simple_monster_message(mon, " resists.")) + msg_generated = true; + break; + case MON_UNAFFECTED: + if (simple_monster_message(mon, " is unaffected.")) + msg_generated = true; + break; + case MON_AFFECTED: + case MON_OTHER: // Should this really be here? + update_hurt_or_helped(mon); + break; } - hurt = hurt * 2 / 3; + if (hit_woke_orc) + beogh_follower_convert(mon, true); } - hurt_final = hurt; + range_used += range_used_on_hit(); +} - if (beam.is_tracer) - hurt_final -= std::max(mon->ac / 2, 0); - else - hurt_final -= random2(1 + mon->ac); +void bolt::monster_post_hit(monsters* mon, int dmg) +{ + if (YOU_KILL(thrower) && mons_near(mon)) + print_wounds(mon); - if (beam.flavour == BEAM_FRAG) + // Don't annoy friendlies or good neutrals if the player's beam + // did no damage. Hostiles will still take umbrage. + if (dmg > 0 || !mons_wont_attack(mon) || !YOU_KILL(thrower)) + behaviour_event(mon, ME_ANNOY, beam_source_as_target()); + + // Sticky flame. + if (name == "sticky flame") { - hurt_final -= random2(1 + mon->ac); - hurt_final -= random2(1 + mon->ac); + const int levels = std::min(4, 1 + random2(mon->hit_dice) / 2); + napalm_monster(mon, whose_kill(), levels); } - if (hurt_final < 0) - hurt_final = 0; + bool wake_mimic = true; - const int raw_damage = hurt_final; + // Handle missile effects. + if (item && item->base_type == OBJ_MISSILES) + { + if (item->special == SPMSL_POISONED) + { + int num_levels = 0; + // ench_power == AUTOMATIC_HIT if this is a poisoned needle. + if (ench_power == AUTOMATIC_HIT + && x_chance_in_y(90 - 3 * mon->ac, 100)) + { + num_levels = 2; + } + else if (random2(dmg) - random2(mon->ac) > 0) + num_levels = 1; - // Check monster resists, _without_ side effects (since the - // beam/missile might yet miss!) - hurt_final = mons_adjust_flavoured( mon, beam, raw_damage, false ); + int num_success = 0; + if (YOU_KILL(thrower)) + { + const int skill_level = _name_to_skill_level(name); + if (x_chance_in_y(skill_level + 25, 50)) + num_success++; + if (x_chance_in_y(skill_level, 50)) + num_success++; + } + else + num_success = 1; -#if DEBUG_DIAGNOSTICS - if (!beam.is_tracer) - { - mprf(MSGCH_DIAGNOSTICS, - "Monster: %s; Damage: pre-AC: %d; post-AC: %d; post-resist: %d", - mon->name(DESC_PLAIN).c_str(), hurt, raw_damage, hurt_final); + if (num_success) + { + if (num_success == 2) + num_levels++; + poison_monster(mon, whose_kill(), num_levels); + } + } + else if (item->special == SPMSL_CURARE) + { + if (ench_power == AUTOMATIC_HIT + && curare_hits_monster(agent(), mon, whose_kill(), 2) + && !mon->alive()) + { + wake_mimic = false; + } + } } -#endif - // Now, we know how much this monster would (probably) be - // hurt by this beam. - if (beam.is_tracer) + if (wake_mimic && mons_is_mimic(mon->type)) + mimic_alert(mon); + else if (dmg) + beogh_follower_convert(mon, true); +} + +// Return true if the block succeeded (including reflections.) +bool bolt::attempt_block(monsters* mon) +{ + const int shield_block = mon->shield_bonus(); + bool rc = false; + if (shield_block > 0) { - if (beam.thrower == KILL_YOU_MISSILE || beam.thrower == KILL_YOU) + const int ht = random2(hit * 130 / 100 + mon->shield_block_penalty()); + if (ht < shield_block) { - if (!_beam_is_harmless(beam, mon) - && (beam.fr_count == 1 && !beam.dont_stop_fr - || beam.foe_count == 1 && !beam.dont_stop_foe)) + rc = true; + item_def *shield = mon->mslot_item(MSLOT_SHIELD); + if (this->is_reflectable(shield)) { - const bool target = (beam.target == mon->pos()); - - if (stop_attack_prompt(mon, true, target)) + if (you.can_see(mon)) { - beam.beam_cancelled = true; - return (BEAM_STOP); + 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); } - if (beam.fr_count == 1 && !beam.dont_stop_fr) - beam.dont_stop_fr = true; - else - beam.dont_stop_foe = true; + else if (see_grid(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()); + + mon->shield_block_succeeded(); + finish_beam(); } + } - // Check only if actual damage. - if (hurt_final > 0 && hurt > 0) + return (rc); +} + +bool bolt::handle_statue_disintegration(monsters* mon) +{ + bool rc = false; + if ((flavour == BEAM_DISINTEGRATION || flavour == BEAM_NUKE) + && mons_is_statue(mon->type) && !mons_is_icy(mon->type)) + { + rc = true; + // Disintegrate the statue. + if (!silenced(you.pos())) { - // Monster could be hurt somewhat, but only apply the - // monster's power based on how badly it is affected. - // For example, if a fire giant (power 16) threw a - // fireball at another fire giant, and it only took - // 1/3 damage, then power of 5 would be applied to - // foe_power or fr_power. - if (!mons_atts_aligned(beam.attitude, mons_attitude(mon))) - { - // Counting foes is only important for monster tracers. - beam.foe_power += 2 * hurt_final * mons_power(mons_type) - / hurt; - } + if (!see_grid(mon->pos())) + mpr("You hear a hideous screaming!", MSGCH_SOUND); else { - beam.fr_power += 2 * hurt_final * mons_power(mons_type) - / hurt; + mpr("The statue screams as its substance crumbles away!", + MSGCH_SOUND); } } + else if (see_grid(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); + // Stop here. + finish_beam(); + } + return (rc); +} + +void bolt::affect_monster(monsters* mon) +{ + // First some special cases. - // Either way, we could hit this monster, so return range used. - return (_range_used_on_hit(beam)); + // Digging doesn't affect monsters (should it harm earth elementals?) + if (flavour == BEAM_DIGGING) + return; + + // Fire storm creates these, so we'll avoid affecting them + if (name == "great blast of fire" && mon->type == MONS_FIRE_VORTEX) + return; + + // Handle tracers separately. + if (is_tracer) + { + tracer_affect_monster(mon); + return; + } + + // Visual - wake monsters. + if (flavour == BEAM_VISUAL) + { + behaviour_event( mon, ME_DISTURB ); + return; + } + + // Special case: disintegrate (or Shatter) a statue. + // Since disintegration is an enchantment, it has to be handled + // here. + if (handle_statue_disintegration(mon)) + return; + + if (is_enchantment()) + { + // no to-hit check + enchantment_affect_monster(mon); + return; } - // END non-enchantment (could still be tracer) - // BEGIN real non-enchantment beam + if (mon->submerged() && !aimed_at_spot) + return; // passes overhead + + // We need to know how much the monster _would_ be hurt by this, + // before we decide if it actually hits. + int preac, postac, final; + if ( !determine_damage(mon, preac, postac, final) ) + return; + +#if DEBUG_DIAGNOSTICS + mprf(MSGCH_DIAGNOSTICS, + "Monster: %s; Damage: pre-AC: %d; post-AC: %d; post-resist: %d", + mon->name(DESC_PLAIN).c_str(), preac, postac, final); +#endif // Player beams which hit friendlies or good neutrals will annoy // them and be considered naughty if they do damage (this is so as @@ -4726,16 +4371,17 @@ static int _affect_monster(bolt &beam, monsters *mon, item_def *item) god_conduct_trigger conducts[3]; disable_attack_conducts(conducts); - if (_nasty_beam(mon, beam)) + bool hit_woke_orc = false; + if (nasty_to(mon)) { - if (YOU_KILL(beam.thrower) && hurt_final > 0) + if (YOU_KILL(thrower) && final > 0) { // It's not the player's fault if he didn't see the monster or // the monster was caught in an unexpected blast of ?immolation. const bool okay = (!player_monster_visible(mon) - || beam.aux_source == "reading a scroll of immolation" - && !beam.effect_known); + || aux_source == "reading a scroll of immolation" + && !effect_known); if (is_sanctuary(mon->pos()) || is_sanctuary(you.pos())) remove_sanctuary(true); @@ -4744,7 +4390,7 @@ static int _affect_monster(bolt &beam, monsters *mon, item_def *item) } if (you.religion == GOD_BEOGH && mons_species(mon->type) == MONS_ORC - && mons_is_sleeping(mon) && YOU_KILL(beam.thrower) + && mons_is_sleeping(mon) && YOU_KILL(thrower) && !player_under_penance() && you.piety >= piety_breakpoint(2) && mons_near(mon)) { @@ -4753,10 +4399,11 @@ static int _affect_monster(bolt &beam, monsters *mon, item_def *item) } // Explosions always 'hit'. - const bool engulfs = (beam.is_explosion || beam.is_big_cloud); + const bool engulfs = (is_explosion || is_big_cloud); - int beam_hit = beam.hit; - if (menv[tid].invisible() && !beam.can_see_invis) + // Make a copy of the to-hit before we modify it. + int beam_hit = hit; + if (mon->invisible() && !this->can_see_invis) beam_hit /= 2; // FIXME We're randomising mon->evasion, which is further @@ -4764,192 +4411,85 @@ static int _affect_monster(bolt &beam, monsters *mon, item_def *item) // to-hit system (which had very little love for monsters). if (!engulfs && !test_beam_hit(beam_hit, random2(mon->ev))) { - // If the PLAYER cannot see the monster, don't tell them anything! - if (player_monster_visible( &menv[tid] ) && mons_near(mon)) - { - msg::stream << "The " << beam.name << " misses " - << mon->name(DESC_NOCAP_THE) << '.' << std::endl; - } - return (0); - } - - // The monster may block the beam. - if (!engulfs && _beam_is_blockable(beam)) - { - const int shield_block = mon->shield_bonus(); - if (shield_block > 0) + // If the PLAYER cannot see the monster, don't tell them anything! + if (player_monster_visible(mon) && mons_near(mon)) { - const int hit = random2( beam.hit * 130 / 100 - + mon->shield_block_penalty() ); - if (hit < shield_block) - { - 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); - } + msg::stream << "The " << name << " misses " + << mon->name(DESC_NOCAP_THE) << '.' << std::endl; } + return; } - _update_hurt_or_helped(beam, mon); + // The monster may block the beam. + if (!engulfs && is_blockable() && attempt_block(mon)) + return; + update_hurt_or_helped(mon); enable_attack_conducts(conducts); // The beam hit. if (mons_near(mon)) { mprf("The %s %s %s.", - beam.name.c_str(), + name.c_str(), engulfs? "engulfs" : "hits", - player_monster_visible(&menv[tid]) ? + player_monster_visible(mon) ? mon->name(DESC_NOCAP_THE).c_str() : "something"); } else { // The player might hear something, if _they_ fired a missile - // (not beam). - if (!silenced(you.pos()) && beam.flavour == BEAM_MISSILE - && YOU_KILL(beam.thrower)) + // (not magic beam). + if (!silenced(you.pos()) && flavour == BEAM_MISSILE + && YOU_KILL(thrower)) { - mprf(MSGCH_SOUND, "The %s hits something.", beam.name.c_str()); + mprf(MSGCH_SOUND, "The %s hits something.", name.c_str()); } } // handling of missiles - if (item && item->base_type == OBJ_MISSILES + if (item + && item->base_type == OBJ_MISSILES && item->sub_type == MI_THROWING_NET) { - monster_caught_in_net(mon, beam); + monster_caught_in_net(mon, *this); } - // Note that hurt_final was calculated above, so we don't need it again. - // just need to apply flavoured specials (since we called with - // doFlavouredEffects = false above). - hurt_final = mons_adjust_flavoured(mon, beam, raw_damage); + // Apply flavoured specials. + mons_adjust_flavoured(mon, *this, postac, true); // If the beam is an actual missile or of the MMISSILE type (Earth magic) // we might bleed on the floor. if (!engulfs - && (beam.flavour == BEAM_MISSILE || beam.flavour == BEAM_MMISSILE) - && !mons_is_summoned(mon) && !mons_is_submerged(mon)) + && (flavour == BEAM_MISSILE || flavour == BEAM_MMISSILE) + && !mons_is_summoned(mon) && !mon->submerged()) { // Using raw_damage instead of the flavoured one! - int blood = raw_damage/2; // assumes DVORP_PIERCING, factor: 0.5 - if (blood > mon->hit_points) - blood = mon->hit_points; - + // assumes DVORP_PIERCING, factor: 0.5 + const int blood = std::min(postac/2, mon->hit_points); bleed_onto_floor(mon->pos(), mon->type, blood, true); } // Now hurt monster. - mon->hurt(beam.agent(), hurt_final, beam.flavour, false); + mon->hurt(agent(), final, flavour, false); if (mon->alive()) - { - if (thrower == KILL_YOU_MISSILE && mons_near(mon)) - print_wounds(mon); - - // Don't annoy friendlies or good neutrals if the player's beam - // did no damage. Hostiles will still take umbrage. - if (hurt_final > 0 || !mons_wont_attack(mon) || !YOU_KILL(beam.thrower)) - behaviour_event(mon, ME_ANNOY, _beam_source(beam)); - - // Sticky flame. - if (beam.name == "sticky flame") - { - int levels = std::min(4, 1 + random2(mon->hit_dice) / 2); - napalm_monster(mon, _whose_kill(beam), levels); - } - - bool wake_mimic = true; - - // Handle missile effects. - if (item && item->base_type == OBJ_MISSILES) - { - if (item->special == SPMSL_POISONED) - { - int num_levels = 0; - // ench_power == AUTOMATIC_HIT if this is a poisoned needle. - if (beam.ench_power == AUTOMATIC_HIT - && x_chance_in_y(90 - 3 * mon->ac, 100)) - { - num_levels = 2; - } - else if (random2(hurt_final) - random2(mon->ac) > 0) - num_levels = 1; - - int num_success = 0; - if (YOU_KILL(beam.thrower)) - { - const int skill_level = _name_to_skill_level(beam.name); - if (x_chance_in_y(skill_level + 25, 50)) - num_success++; - if (x_chance_in_y(skill_level, 50)) - num_success++; - } - else - num_success = 1; - - if (num_success) - { - if (num_success == 2) - num_levels++; - poison_monster(mon, _whose_kill(beam), num_levels); - } - } - else if (item->special == SPMSL_CURARE) - { - if (beam.ench_power == AUTOMATIC_HIT - && curare_hits_monster(beam.agent(), - mon, _whose_kill(beam), 2) - && !mon->alive()) - { - wake_mimic = false; - } - } - } - - if (wake_mimic && mons_is_mimic(mon->type)) - mimic_alert(mon); - else if (hit_woke_orc) - beogh_follower_convert(mon, true); - } + monster_post_hit(mon, final); else - monster_die(mon, beam.thrower, _beam_source(beam)); + monster_die(mon, thrower, beam_source_as_target()); - return (_range_used_on_hit(beam)); + range_used += range_used_on_hit(); } -bool _beam_has_saving_throw(const bolt& beam) +bool bolt::has_saving_throw() const { - if (beam.aimed_at_feet) + if (aimed_at_feet) return (false); bool rc = true; - switch (beam.flavour) + switch (flavour) { case BEAM_HASTE: case BEAM_HEALING: @@ -4985,7 +4525,7 @@ bool _ench_flavour_affects_monster(beam_type flavour, const monsters* mon) case BEAM_ENSLAVE_SOUL: rc = (mons_holiness(mon) == MH_NATURAL - && mon->attitude != ATT_FRIENDLY); + && mon->attitude != ATT_FRIENDLY); break; case BEAM_DISPEL_UNDEAD: @@ -5013,42 +4553,57 @@ bool _ench_flavour_affects_monster(beam_type flavour, const monsters* mon) return rc; } -static mon_resist_type _affect_monster_enchantment(bolt &beam, monsters *mon) +bool enchant_monster_with_flavour(monsters* mon, beam_type flavour, int powc) +{ + bolt dummy; + dummy.flavour = flavour; + dummy.ench_power = powc; + dummy.apply_enchantment_to_monster(mon); + return dummy.obvious_effect; +} + +mon_resist_type bolt::try_enchant_monster(monsters *mon) { // Early out if the enchantment is meaningless. - if (!_ench_flavour_affects_monster(beam.flavour, mon)) + if (!_ench_flavour_affects_monster(flavour, mon)) return (MON_UNAFFECTED); // Check magic resistance. - if (_beam_has_saving_throw(beam)) + if (has_saving_throw()) { if (mons_immune_magic(mon)) return (MON_UNAFFECTED); - if (check_mons_resist_magic(mon, beam.ench_power)) + if (check_mons_resist_magic(mon, ench_power)) return (MON_RESIST); } - switch (beam.flavour) + return (apply_enchantment_to_monster(mon)); +} + +mon_resist_type bolt::apply_enchantment_to_monster(monsters* mon) +{ + // Gigantic-switches-R-Us + switch (flavour) { case BEAM_TELEPORT: if (you.can_see(mon)) - beam.obvious_effect = true; + obvious_effect = true; monster_teleport(mon, false); return (MON_AFFECTED); case BEAM_BLINK: if (you.can_see(mon)) - beam.obvious_effect = true; + obvious_effect = true; monster_blink(mon); return (MON_AFFECTED); case BEAM_POLYMORPH: if (mon->mutate()) - beam.obvious_effect = true; - if (YOU_KILL(beam.thrower)) + obvious_effect = true; + if (YOU_KILL(thrower)) { did_god_conduct(DID_DELIBERATE_MUTATING, 2 + random2(3), - beam.effect_known); + effect_known); } return (MON_AFFECTED); @@ -5057,18 +4612,18 @@ static mon_resist_type _affect_monster_enchantment(bolt &beam, monsters *mon) simple_monster_message(mon, " wobbles for a moment."); else mon->banish(); - beam.obvious_effect = true; + obvious_effect = true; return (MON_AFFECTED); case BEAM_DEGENERATE: if (monster_polymorph(mon, MONS_PULSATING_LUMP)) - beam.obvious_effect = true; + obvious_effect = true; return (MON_AFFECTED); case BEAM_DISPEL_UNDEAD: if (simple_monster_message(mon, " convulses!")) - beam.obvious_effect = true; - mon->hurt(beam.agent(), beam.damage.roll()); + obvious_effect = true; + mon->hurt(agent(), damage.roll()); return (MON_AFFECTED); case BEAM_ENSLAVE_UNDEAD: @@ -5076,13 +4631,12 @@ static mon_resist_type _affect_monster_enchantment(bolt &beam, monsters *mon) 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, beam.ench_power); + "HD: %d; pow: %d", mon->hit_dice, ench_power); #endif - beam.obvious_effect = true; + obvious_effect = true; if (player_will_anger_monster(mon)) { simple_monster_message(mon, " is repulsed!"); @@ -5103,7 +4657,7 @@ static mon_resist_type _affect_monster_enchantment(bolt &beam, monsters *mon) case BEAM_ENSLAVE_SOUL: #if DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, - "HD: %d; pow: %d", mon->hit_dice, beam.ench_power); + "HD: %d; pow: %d", mon->hit_dice, ench_power); #endif if (!mons_can_be_zombified(mon) || mons_intel(mon) < I_NORMAL) @@ -5112,10 +4666,10 @@ static mon_resist_type _affect_monster_enchantment(bolt &beam, monsters *mon) return (MON_OTHER); } - if (mon->hit_dice >= random2(beam.ench_power / 2)) + if (mon->hit_dice >= random2(ench_power / 2)) return (MON_RESIST); - beam.obvious_effect = true; + obvious_effect = true; mon->flags |= MF_ENSLAVED_SOUL; simple_monster_message(mon, "'s soul is now ripe for the taking."); return (MON_AFFECTED); @@ -5123,16 +4677,16 @@ static mon_resist_type _affect_monster_enchantment(bolt &beam, monsters *mon) case BEAM_ENSLAVE_DEMON: #if DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, - "HD: %d; pow: %d", mon->hit_dice, beam.ench_power); + "HD: %d; pow: %d", mon->hit_dice, ench_power); #endif - if (mon->hit_dice * 11 / 2 >= random2(beam.ench_power) + if (mon->hit_dice * 11 / 2 >= random2(ench_power) || mons_is_unique(mon->type)) { return (MON_RESIST); } - beam.obvious_effect = true; + obvious_effect = true; if (player_will_anger_monster(mon)) { simple_monster_message(mon, " is repulsed!"); @@ -5151,171 +4705,310 @@ static mon_resist_type _affect_monster_enchantment(bolt &beam, monsters *mon) case BEAM_PAIN: // pain/agony if (simple_monster_message(mon, " convulses in agony!")) - beam.obvious_effect = true; + obvious_effect = true; - if (beam.name.find("agony") != std::string::npos) // agony + if (name.find("agony") != std::string::npos) // agony mon->hit_points = std::max(mon->hit_points/2, 1); else // pain - mon->hurt(beam.agent(), beam.damage.roll(), beam.flavour); + mon->hurt(agent(), damage.roll(), flavour); return (MON_AFFECTED); case BEAM_DISINTEGRATION: // disrupt/disintegrate if (simple_monster_message(mon, " is blasted.")) - beam.obvious_effect = true; - mon->hurt(beam.agent(), beam.damage.roll(), beam.flavour); + obvious_effect = true; + mon->hurt(agent(), damage.roll(), flavour); return (MON_AFFECTED); case BEAM_SLEEP: if (simple_monster_message(mon, " looks drowsy...")) - beam.obvious_effect = true; + obvious_effect = true; mon->put_to_sleep(); return (MON_AFFECTED); case BEAM_BACKLIGHT: - if (backlight_monsters(mon->pos(), beam.hit, 0)) + if (backlight_monsters(mon->pos(), hit, 0)) { - beam.obvious_effect = true; + obvious_effect = true; return (MON_AFFECTED); } return (MON_UNAFFECTED); + case BEAM_SLOW: + // try to remove haste, if monster is hasted + if (mon->del_ench(ENCH_HASTE)) + { + if (simple_monster_message(mon, " is no longer moving quickly.")) + obvious_effect = true; + return (MON_AFFECTED); + } + + // not hasted, slow it + if (!mon->has_ench(ENCH_SLOW) + && !mons_is_stationary(mon) + && mon->add_ench(mon_enchant(ENCH_SLOW, 0, whose_kill()))) + { + if (!mons_is_paralysed(mon) && !mons_is_petrified(mon) + && simple_monster_message(mon, " seems to slow down.")) + { + obvious_effect = true; + } + } + return (MON_AFFECTED); + + case BEAM_HASTE: + if (mon->del_ench(ENCH_SLOW)) + { + if (simple_monster_message(mon, " is no longer moving slowly.")) + obvious_effect = true; + + return (MON_AFFECTED); + } + + // Not slowed, haste it. + if (!mon->has_ench(ENCH_HASTE) + && !mons_is_stationary(mon) + && mon->add_ench(ENCH_HASTE)) + { + if (!mons_is_paralysed(mon) && !mons_is_petrified(mon) + && simple_monster_message(mon, " seems to speed up.")) + { + obvious_effect = true; + } + } + return (MON_AFFECTED); + + case BEAM_HEALING: + if (YOU_KILL(thrower)) + { + if (cast_healing(5 + damage.roll(), mon->pos()) > 0) + obvious_effect = true; + msg_generated = true; // to avoid duplicate "nothing happens" + } + 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_CONFUSION: + if (!mons_class_is_confusable(mon->type)) + return (MON_UNAFFECTED); + + if (mon->add_ench(mon_enchant(ENCH_CONFUSION, 0, whose_kill()))) + { + // FIXME: Put in an exception for things you won't notice + // becoming confused. + if (simple_monster_message(mon, " appears confused.")) + obvious_effect = true; + } + return (MON_AFFECTED); + + case BEAM_INVISIBILITY: + { + // Store the monster name before it becomes an "it" -- bwr + const std::string monster_name = mon->name(DESC_CAP_THE); + + if (!mon->has_ench(ENCH_INVIS) && mon->add_ench(ENCH_INVIS)) + { + // A casting of invisibility erases backlight. + mon->del_ench(ENCH_BACKLIGHT); + + // Can't use simple_monster_message() here, since it checks + // for visibility of the monster (and it's now invisible). + // -- bwr + if (mons_near(mon)) + { + mprf("%s flickers %s", + monster_name.c_str(), + player_monster_visible(mon) ? "for a moment." + : "and vanishes!" ); + + if (!player_monster_visible(mon)) + { + // Also turn off autopickup. + Options.autopickup_on = false; + mpr("Deactivating autopickup; reactivate with Ctrl-A.", + MSGCH_WARN); + + if (Options.tutorial_left) + { + learned_something_new(TUT_INVISIBLE_DANGER); + Options.tut_seen_invisible = you.num_turns; + } + } + } + + 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->add_ench(ENCH_CHARM)) + { + // FIXME: Put in an exception for fungi, plants and other + // things you won't notice becoming charmed. + if (simple_monster_message(mon, " is charmed.")) + obvious_effect = true; + } + return (MON_AFFECTED); + default: - return (mons_ench_f2(mon, beam)); + break; } + + return (MON_AFFECTED); } // Extra range used on hit. -static int _range_used_on_hit(bolt &beam) +int bolt::range_used_on_hit() const { // Non-beams can only affect one thing (player/monster). - if (!beam.is_beam) + if (!is_beam) return (BEAM_STOP); - if (beam.is_enchantment()) - return (beam.flavour == BEAM_DIGGING ? 0 : BEAM_STOP); + if (is_enchantment()) + return (flavour == BEAM_DIGGING ? 0 : BEAM_STOP); // Hellfire stops for nobody! - if (beam.name == "hellfire") + if (name == "hellfire") return (0); // Generic explosion. - if (beam.is_explosion || beam.is_big_cloud) + if (is_explosion || is_big_cloud) return (BEAM_STOP); // Plant spit. - if (beam.flavour == BEAM_ACID) + if (flavour == BEAM_ACID) return (BEAM_STOP); // Lava doesn't go far, but it goes through most stuff. - if (beam.flavour == BEAM_LAVA) + if (flavour == BEAM_LAVA) return (1); - // If it isn't lightning, reduce range by a lot. - if (beam.flavour != BEAM_ELECTRICITY) - return (2); + // Lightning goes through things. + if (flavour == BEAM_ELECTRICITY) + return (0); - return (0); + return (2); } -// Takes a bolt and refines it for use in the explosion function. Called -// from missile() and beam() in beam.cc. Explosions which do not follow from -// beams (eg scrolls of immolation) bypass this function. -static void _explosion1(bolt &pbolt) +// Takes a bolt and refines it for use in the explosion function. +// Explosions which do not follow from beams (e.g., scrolls of +// immolation) bypass this function. +void bolt::refine_for_explosion() { - int ex_size = 1; - // convenience - coord_def p = pbolt.target; + ex_size = 1; const char *seeMsg = NULL; const char *hearMsg = NULL; // Assume that the player can see/hear the explosion, or // gets burned by it anyway. :) - pbolt.msg_generated = true; + msg_generated = true; - if (pbolt.name == "hellfire") + if (name == "hellfire") { seeMsg = "The hellfire explodes!"; hearMsg = "You hear a strangely unpleasant explosion."; - pbolt.type = dchar_glyph(DCHAR_FIRED_BURST); - pbolt.flavour = BEAM_HELLFIRE; + type = dchar_glyph(DCHAR_FIRED_BURST); + flavour = BEAM_HELLFIRE; } - if (pbolt.name == "golden flame") + if (name == "golden flame") { seeMsg = "The flame explodes!"; hearMsg = "You feel a deep, resonant explosion."; - pbolt.type = dchar_glyph(DCHAR_FIRED_BURST); - pbolt.flavour = BEAM_HOLY; + type = dchar_glyph(DCHAR_FIRED_BURST); + flavour = BEAM_HOLY; ex_size = 2; } - if (pbolt.name == "fireball") + if (name == "fireball") { seeMsg = "The fireball explodes!"; hearMsg = "You hear an explosion."; - pbolt.type = dchar_glyph(DCHAR_FIRED_BURST); - pbolt.flavour = BEAM_FIRE; + type = dchar_glyph(DCHAR_FIRED_BURST); + flavour = BEAM_FIRE; ex_size = 1; } - if (pbolt.name == "orb of electricity") + if (name == "orb of electricity") { seeMsg = "The orb of electricity explodes!"; hearMsg = "You hear a clap of thunder!"; - pbolt.type = dchar_glyph(DCHAR_FIRED_BURST); - pbolt.flavour = BEAM_ELECTRICITY; - pbolt.colour = LIGHTCYAN; - pbolt.damage.num = 1; - ex_size = 2; + type = dchar_glyph(DCHAR_FIRED_BURST); + flavour = BEAM_ELECTRICITY; + colour = LIGHTCYAN; + damage.num = 1; + ex_size = 2; } - if (pbolt.name == "orb of energy") + if (name == "orb of energy") { seeMsg = "The orb of energy explodes."; hearMsg = "You hear an explosion."; } - if (pbolt.name == "metal orb") + if (name == "metal orb") { seeMsg = "The orb explodes into a blast of deadly shrapnel!"; hearMsg = "You hear an explosion!"; - pbolt.name = "blast of shrapnel"; - pbolt.type = dchar_glyph(DCHAR_FIRED_ZAP); - pbolt.flavour = BEAM_FRAG; // Sets it from pure damage to shrapnel - // (which is absorbed extra by armour). + name = "blast of shrapnel"; + type = dchar_glyph(DCHAR_FIRED_ZAP); + flavour = BEAM_FRAG; // Sets it from pure damage to shrapnel + // (which is absorbed extra by armour). } - if (pbolt.name == "great blast of cold") + if (name == "great blast of cold") { seeMsg = "The blast explodes into a great storm of ice!"; hearMsg = "You hear a raging storm!"; - pbolt.name = "ice storm"; - pbolt.type = dchar_glyph(DCHAR_FIRED_ZAP); - pbolt.colour = WHITE; - ex_size = 2 + (random2( pbolt.ench_power ) > 75); + name = "ice storm"; + type = dchar_glyph(DCHAR_FIRED_ZAP); + colour = WHITE; + ex_size = 2 + (random2( ench_power ) > 75); } - if (pbolt.name == "ball of vapour") + if (name == "ball of vapour") { seeMsg = "The ball expands into a vile cloud!"; hearMsg = "You hear a gentle \'poof\'."; - if (!pbolt.is_tracer) - pbolt.name = "stinking cloud"; + if (!is_tracer) + name = "stinking cloud"; } - if (pbolt.name == "potion") + if (name == "potion") { seeMsg = "The potion explodes!"; hearMsg = "You hear an explosion!"; - if (!pbolt.is_tracer) - pbolt.name = "cloud"; + if (!is_tracer) + name = "cloud"; } if (seeMsg == NULL) @@ -5325,316 +5018,199 @@ static void _explosion1(bolt &pbolt) } - if (!pbolt.is_tracer && *seeMsg && *hearMsg) + if (!is_tracer && *seeMsg && *hearMsg) { // Check for see/hear/no msg. - if (see_grid(p) || p == you.pos()) + if (see_grid(target) || target == you.pos()) mpr(seeMsg); else { - if (!player_can_hear(p)) - pbolt.msg_generated = false; + if (!player_can_hear(target)) + msg_generated = false; else mpr(hearMsg, MSGCH_SOUND); } } - - pbolt.ex_size = ex_size; - explosion( pbolt ); } +typedef std::vector< std::vector > sweep_type; -#define MAX_EXPLOSION_RADIUS 9 - -// explosion() is considered to emanate from beam->target_x, target_y -// and has a radius equal to ex_size. The explosion will respect -// boundaries like walls, but go through/around statues/idols/etc. -// -// For each cell affected by the explosion, affect() is called. -int explosion( bolt &beam, bool hole_in_the_middle, - bool explode_in_wall, bool stop_at_statues, - bool stop_at_walls, bool show_more, bool affect_items) +static sweep_type _radial_sweep(int r) { - beam.real_flavour = beam.flavour; + sweep_type result; + sweep_type::value_type work; - if (in_bounds(beam.source) && beam.source != beam.target - && (!explode_in_wall || stop_at_statues || stop_at_walls)) + // Center first. + work.push_back( coord_def(0,0) ); + result.push_back(work); + + for (int rad = 1; rad <= r; ++rad) { - ray_def ray; - int max_dist = grid_distance(beam.source, beam.target); + work.clear(); - ray.fullray_idx = -1; // to quiet valgrind - find_ray( beam.source, beam.target, true, ray, 0, true ); - - // Can cast explosions out from statues or walls. - if (ray.pos() == beam.source) - { - max_dist--; - ray.advance(true); - } - - int dist = 0; - while (dist++ <= max_dist && ray.pos() != beam.target) + for (int d = -rad; d <= rad; ++d) { - if (grid_is_solid(ray.pos())) + // Don't put the corners in twice! + if (d != rad && d != -rad) { - bool is_wall = grid_is_wall(grd(ray.pos())); - if (!stop_at_statues && !is_wall) - { -#if DEBUG_DIAGNOSTICS - mpr("Explosion beam passing over a statue or other " - "non-wall solid feature.", MSGCH_DIAGNOSTICS); -#endif - continue; - } - else if (!stop_at_walls && is_wall) - { -#if DEBUG_DIAGNOSTICS - mpr("Explosion beam passing through a wall.", - MSGCH_DIAGNOSTICS); -#endif - continue; - } - -#if DEBUG_DIAGNOSTICS - if (!is_wall && stop_at_statues) - { - mpr("Explosion beam stopped by a statue or other " - "non-wall solid feature.", MSGCH_DIAGNOSTICS); - } - else if (is_wall && stop_at_walls) - { - mpr("Explosion beam stopped by a by wall.", - MSGCH_DIAGNOSTICS); - } - else - { - mpr("Explosion beam stopped by someting buggy.", - MSGCH_DIAGNOSTICS); - } -#endif - - break; + work.push_back( coord_def(-rad, d) ); + work.push_back( coord_def(+rad, d) ); } - ray.advance(true); - } // while (dist++ <= max_dist) - // Backup so we don't explode inside the wall. - if (!explode_in_wall && grid_is_wall(grd(ray.pos()))) - { -#if DEBUG_DIAGNOSTICS - int old_x = ray.x(); - int old_y = ray.y(); -#endif - ray.regress(); -#if DEBUG_DIAGNOSTICS - mprf(MSGCH_DIAGNOSTICS, - "Can't explode in a solid wall, backing up a step " - "along the beam path from (%d, %d) to (%d, %d)", - old_x, old_y, ray.x(), ray.y()); -#endif + work.push_back( coord_def(d, -rad) ); + work.push_back( coord_def(d, +rad) ); } - beam.target = ray.pos(); - } // if (!explode_in_wall) + result.push_back(work); + } + return result; +} - int r = beam.ex_size; +#define MAX_EXPLOSION_RADIUS 9 - // Beam is now an explosion. - beam.in_explosion_phase = true; +bool bolt::explode(bool show_more, bool hole_in_the_middle) +{ + real_flavour = flavour; + const int r = std::min(ex_size, MAX_EXPLOSION_RADIUS); + in_explosion_phase = true; - if (is_sanctuary(beam.target)) + if (is_sanctuary(pos())) { - if (!beam.is_tracer && see_grid(beam.target) && !beam.name.empty()) + if (!is_tracer && see_grid(pos()) && !name.empty()) { mprf(MSGCH_GOD, "By Zin's power, the %s is contained.", - beam.name.c_str()); + name.c_str()); + return (true); } - return (-1); + return (false); } #if DEBUG_DIAGNOSTICS mprf(MSGCH_DIAGNOSTICS, "explosion at (%d, %d) : t=%d c=%d f=%d hit=%d dam=%dd%d", - beam.target.x, beam.target.y, - beam.type, beam.colour, beam.flavour, - beam.hit, beam.damage.num, beam.damage.size ); + pos().x, pos().y, type, colour, flavour, hit, damage.num, damage.size); #endif - // For now, we don't support explosions greater than 9 radius. - if (r > MAX_EXPLOSION_RADIUS) - r = MAX_EXPLOSION_RADIUS; - // make a noise - noisy(10 + 5 * r, beam.target); - - // set map to INT_MAX - explode_map.init(INT_MAX); + noisy(10 + 5 * r, pos()); - // Discover affected cells - recursion is your friend! - // this is done to model an explosion's behaviour around - // corners where a simple 'line of sight' isn't quite - // enough. This might be slow for really big explosions, - // as the recursion runs approximately as R^2. - _explosion_map(beam, coord_def(0, 0), 0, r); + // Run DFS to determine which cells are influenced + explosion_map exp_map; + exp_map.init(INT_MAX); + determine_affected_cells(exp_map, coord_def(), 0, r, true, true); - // Go through affected cells, drawing effect and - // calling affect() for each. Now, we get a bit - // fancy, drawing all radius 0 effects, then radius 1, - // radius 2, etc. It looks a bit better that way. - - // turn buffering off #ifdef WIN32CONSOLE + // turn buffering off bool oldValue = true; - if (!beam.is_tracer) + if (!is_tracer) oldValue = set_buffering(false); #endif - // --------------------- begin boom --------------- + // We get a bit fancy, drawing all radius 0 effects, then radius + // 1, radius 2, etc. It looks a bit better that way. + const std::vector< std::vector > sweep = _radial_sweep(r); + const coord_def centre(9,9); - bool drawing = true; - for (int i = 0; i < 2; i++) - { - // do center -- but only if its affected - if (!hole_in_the_middle) - _explosion_cell(beam, coord_def(0, 0), drawing, affect_items); + typedef sweep_type::const_iterator siter; + typedef sweep_type::value_type::const_iterator viter; - // do the rest of it - for (int rad = 1; rad <= r; rad ++) + // Draw pass. + if (!is_tracer) + { + for (siter ci = sweep.begin(); ci != sweep.end(); ++ci) { - // do sides - for (int ay = 1 - rad; ay <= rad - 1; ay += 1) + for (viter cci = ci->begin(); cci != ci->end(); ++cci) { - if (explode_map[-rad+9][ay+9] < INT_MAX) - { - _explosion_cell(beam, coord_def(-rad, ay), drawing, - affect_items); - } - if (explode_map[rad+9][ay+9] < INT_MAX) - { - _explosion_cell(beam, coord_def(rad, ay), drawing, - affect_items); - } - } + const coord_def delta = *cci; - // do top & bottom - for (int ax = -rad; ax <= rad; ax += 1) - { - if (explode_map[ax+9][-rad+9] < INT_MAX) - { - _explosion_cell(beam, coord_def(ax, -rad), drawing, - affect_items); - } - if (explode_map[ax+9][rad+9] < INT_MAX) - { - _explosion_cell(beam, coord_def(ax, rad), drawing, - affect_items); - } - } + if (delta.origin() && hole_in_the_middle) + continue; - // new-- delay after every 'ring' {gdl} - // If we don't refresh curses we won't - // guarantee that the explosion is visible. - if (drawing) - update_screen(); - // Only delay on real explosion. - if (!beam.is_tracer && drawing) - delay(50); + if (exp_map(delta + centre) < INT_MAX) + explosion_draw_cell(delta + pos()); + } + update_screen(); + delay(50); } - - drawing = false; } + // Affect pass. int cells_seen = 0; - for ( int i = -9; i <= 9; ++i ) - for ( int j = -9; j <= 9; ++j ) - if ( explode_map[i+9][j+9] - && see_grid(beam.target + coord_def(i,j))) + 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) { - cells_seen++; - } + if (see_grid(delta + pos())) + ++cells_seen; - // ---------------- end boom -------------------------- + explosion_affect_cell(delta + pos()); + } + } + } #ifdef WIN32CONSOLE - if (!beam.is_tracer) + if (!is_tracer) set_buffering(oldValue); #endif - // Duplicate old behaviour - pause after entire explosion - // has been drawn. - if (!beam.is_tracer && cells_seen > 0 && show_more) + // Pause after entire explosion has been drawn. + if (!is_tracer && cells_seen > 0 && show_more) more(); - return (cells_seen); + return (cells_seen > 0); } -static void _explosion_cell(bolt &beam, const coord_def& p, bool drawOnly, - bool affect_items) +void bolt::explosion_draw_cell(const coord_def& p) { - coord_def realpos = beam.target + p; - - if (!drawOnly) - { - // Random/chaos beams: randomize before affect(). - if (beam.real_flavour == BEAM_RANDOM) - beam.flavour = static_cast( - random_range(BEAM_FIRE, BEAM_ACID) ); - else if (beam.real_flavour == BEAM_CHAOS) - beam.flavour = _chaos_beam_flavour(); - - affect(beam, realpos, NULL, affect_items); - - beam.flavour = beam.real_flavour; - } - - // Early out for tracer. - if (beam.is_tracer) - return; - - if (drawOnly) + if (see_grid(p)) { - const coord_def drawpos = grid2view(realpos); - - // XXX Don't you always see your own grid? - if (see_grid(realpos) || realpos == you.pos()) - { + const coord_def drawpos = grid2view(p); #ifdef USE_TILE - if (in_los_bounds(drawpos)) - tiles.add_overlay(realpos, tileidx_bolt(beam)); + if (in_los_bounds(drawpos)) + tiles.add_overlay(p, tileidx_bolt(*this)); #else - // bounds check - if (in_los_bounds(drawpos)) - { - cgotoxy(drawpos.x, drawpos.y, GOTO_DNGN); - put_colour_ch( - beam.colour == BLACK ? random_colour() : beam.colour, - dchar_glyph( DCHAR_EXPLOSION ) ); - } -#endif + // bounds check + if (in_los_bounds(drawpos)) + { + cgotoxy(drawpos.x, drawpos.y, GOTO_DNGN); + put_colour_ch( + colour == BLACK ? random_colour() : colour, + dchar_glyph(DCHAR_EXPLOSION)); } +#endif } } -static void _explosion_map( bolt &beam, const coord_def& p, - int count, int r ) +void bolt::explosion_affect_cell(const coord_def& p) { - // Check to see out of range. - if (p.abs() > r*(r+1)) - return; - - // Check count. - if (count > 10*r) - return; - - const coord_def loc(beam.target + p); + fake_flavour(); + target = p; + affect_cell(); + flavour = real_flavour; +} - // Make sure we haven't run off the map. - if (!map_bounds(loc)) - return; +// Uses DFS +void bolt::determine_affected_cells(explosion_map& m, const coord_def& delta, + int count, int r, + bool stop_at_statues, bool stop_at_walls) +{ + const coord_def centre(9,9); + const coord_def loc = pos() + delta; - // Check sanctuary. - if (is_sanctuary(loc)) + // A bunch of tests for edge cases. + if (delta.rdist() > centre.rdist() + || (delta.abs() > r*(r+1)) + || (count > 10*r) + || !map_bounds(loc) + || is_sanctuary(loc)) + { return; + } const dungeon_feature_type dngn_feat = grd(loc); @@ -5643,31 +5219,39 @@ static void _explosion_map( bolt &beam, const coord_def& p, // Explosion originates from rock/statue (e.g. Lee's rapid // deconstruction) - in this case, ignore solid cells at the // center of the explosion. - if (grid_is_wall(dngn_feat) || dngn_feat == DNGN_SECRET_DOOR || dngn_feat == DNGN_CLOSED_DOOR) { - if (!(_affects_wall(beam, dngn_feat) && p.origin())) + if (stop_at_walls && !(delta.origin() && affects_wall(dngn_feat))) return; } - // Hmm, I think we're ok. - explode_map(p + coord_def(9,9)) = count; + if (grid_is_solid(dngn_feat) && !grid_is_wall(dngn_feat) && stop_at_statues) + return; + + // Hmm, I think we're OK. + m(delta + centre) = std::min(count, m(delta + centre)); // Now recurse in every direction. for (int i = 0; i < 8; i++) { - // Is that cell already covered by a recursion that was closer - // to the center? - if (explode_map(p + coord_def(9,9) + Compass[i]) <= count) + const coord_def new_delta = delta + Compass[i]; + + if (new_delta.rdist() > centre.rdist()) + continue; + + // Is that cell already covered? + if (m(new_delta + centre) <= count) continue; int cadd = 5; - if (p.x * Compass[i].x < 0 || p.y * Compass[i].y < 0) + // Changing direction (e.g. looking around a wall) costs more. + if (delta.x * Compass[i].x < 0 || delta.y * Compass[i].y < 0) cadd = 17; - _explosion_map( beam, p + Compass[i], count + cadd, r); + determine_affected_cells(m, new_delta, count + cadd, r, + stop_at_statues, stop_at_walls); } } @@ -5679,64 +5263,60 @@ static void _explosion_map( bolt &beam, const coord_def& p, // Only enchantments should need the actual monster type // to determine this; non-enchantments are pretty // straightforward. -static bool _nasty_beam(monsters *mon, const bolt &beam) +bool bolt::nasty_to(const monsters *mon) const { // Take care of non-enchantments. - if (!beam.is_enchantment()) + if (!is_enchantment()) return (true); // Now for some non-hurtful enchantments. - if (beam.flavour == BEAM_DIGGING) + if (flavour == BEAM_DIGGING) return (false); - // haste/healing/invisibility - if (_nice_beam(mon, beam)) + // Positive effects + if (nice_to(mon)) return (false); // No charming holy beings! - if (beam.flavour == BEAM_CHARM) + if (flavour == BEAM_CHARM) return (mons_is_holy(mon)); // Friendly and good neutral monsters don't mind being teleported. - if (beam.flavour == BEAM_TELEPORT) + if (flavour == BEAM_TELEPORT) return (!mons_wont_attack(mon)); // degeneration / sleep / enslave soul - if (beam.flavour == BEAM_DEGENERATE || beam.flavour == BEAM_SLEEP - || beam.flavour == BEAM_ENSLAVE_SOUL) + if (flavour == BEAM_DEGENERATE + || flavour == BEAM_SLEEP + || flavour == BEAM_ENSLAVE_SOUL) { return (mons_holiness(mon) == MH_NATURAL); } // dispel undead / control undead - if (beam.flavour == BEAM_DISPEL_UNDEAD - || beam.flavour == BEAM_ENSLAVE_UNDEAD) - { + if (flavour == BEAM_DISPEL_UNDEAD || flavour == BEAM_ENSLAVE_UNDEAD) return (mons_holiness(mon) == MH_UNDEAD); - } // pain/agony - if (beam.flavour == BEAM_PAIN) + if (flavour == BEAM_PAIN) return (!mons_res_negative_energy(mon)); // control demon - if (beam.flavour == BEAM_ENSLAVE_DEMON) + if (flavour == BEAM_ENSLAVE_DEMON) return (mons_holiness(mon) == MH_DEMONIC); // everything else is considered nasty by everyone return (true); } -static bool _nice_beam(monsters *mon, const bolt &beam) +// Return true if the bolt is considered nice by mon. +// This is not the inverse of nasty_to(): the bolt needs to be +// actively positive. +bool bolt::nice_to(const monsters *mon) const { - // haste/healing/invisibility - if (beam.flavour == BEAM_HASTE || beam.flavour == BEAM_HEALING - || beam.flavour == BEAM_INVISIBILITY) - { - return (true); - } - - return (false); + return (flavour == BEAM_HASTE + || flavour == BEAM_HEALING + || flavour == BEAM_INVISIBILITY); } //////////////////////////////////////////////////////////////////////////// @@ -5751,22 +5331,21 @@ static bool _nice_beam(monsters *mon, const bolt &beam) bolt::bolt() : range(0), type('*'), colour(BLACK), flavour(BEAM_MAGIC), real_flavour(BEAM_MAGIC), drop_item(false), - item(NULL), source(), target(), pos(), damage(0,0), + item(NULL), source(), target(), damage(0,0), ench_power(0), hit(0), thrower(KILL_MISC), ex_size(0), beam_source(MHITNOT), name(), short_name(), is_beam(false), is_explosion(false), is_big_cloud(false), aimed_at_spot(false), aux_source(), affects_nothing(false), effect_known(true), - delay(15), obvious_effect(false), + draw_delay(15), 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), seen(false), 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), - bounces(false), bounce_pos(), reflections(false), reflector(-1) + bounces(false), bounce_pos(), reflections(0), reflector(-1) { } @@ -5811,8 +5390,8 @@ void bolt::set_target(const dist &d) void bolt::setup_retrace() { - if (pos.x && pos.y) - target = pos; + if (pos().x && pos().y) + target = pos(); std::swap(source, target); affects_nothing = true; @@ -5823,7 +5402,7 @@ actor* bolt::agent() const { if (YOU_KILL(this->thrower)) return (&you); - else if (this->beam_source < NON_MONSTER && this->beam_source >= 0) + else if (!invalid_monster_index(beam_source)) return (&menv[this->beam_source]); else return (NULL); -- cgit v1.2.3-54-g00ecf