diff options
-rw-r--r-- | crawl-ref/source/command.cc | 100 | ||||
-rw-r--r-- | crawl-ref/source/mon-util.cc | 14 | ||||
-rw-r--r-- | crawl-ref/source/monplace.cc | 18 | ||||
-rw-r--r-- | crawl-ref/source/monstuff.cc | 137 | ||||
-rw-r--r-- | crawl-ref/source/view.cc | 23 | ||||
-rw-r--r-- | crawl-ref/source/view.h | 2 |
6 files changed, 180 insertions, 114 deletions
diff --git a/crawl-ref/source/command.cc b/crawl-ref/source/command.cc index 52f8ec9226..5959825431 100644 --- a/crawl-ref/source/command.cc +++ b/crawl-ref/source/command.cc @@ -1832,59 +1832,59 @@ static void _list_wizard_commands() cols.set_pagesize(get_number_of_lines()); cols.add_formatted(0, - "a : acquirement\n" - "A : set all skills to level\n" - "Ctrl-A : generate new Abyss area\n" - "b : controlled blink\n" - "B : banish yourself to the Abyss\n" - "c : card effect\n" - "C : (un)curse item\n" - "g : add a skill\n" - "G : banish all monsters\n" - "Ctrl-G : save ghost (bones file)\n" - "f : player combat damage stats\n" - "F : combat stats with fsim_kit\n" - "Ctrl-F : combat stats (monster vs PC)\n" - "h/H : heal yourself (super-Heal)\n" - "i/I : identify/unidentify inventory\n" - "Ctrl-I : item generation stats\n" - "l : make entrance to labyrinth\n" - "L : place a vault by name\n" - "m/M : create monster by number/name\n" - "o/% : create an object\n" - "p : make entrance to pandemonium\n" - "P : make a portal (i.e., bazaars)\n" - "r : change character's species\n" - "s : gain 20000 skill points\n" - "S : set skill to level\n", + "<w>a</w> : acquirement\n" + "<w>A</w> : set all skills to level\n" + "<w>Ctrl-A</w> : generate new Abyss area\n" + "<w>b</w> : controlled blink\n" + "<w>B</w> : banish yourself to the Abyss\n" + "<w>c</w> : card effect\n" + "<w>C</w> : (un)curse item\n" + "<w>g</w> : add a skill\n" + "<w>G</w> : banish all monsters\n" + "<w>Ctrl-G</w> : save ghost (bones file)\n" + "<w>f</w> : player combat damage stats\n" + "<w>F</w> : combat stats with fsim_kit\n" + "<w>Ctrl-F</w> : combat stats (monster vs PC)\n" + "<w>h</w>/<w>H</w> : heal yourself (super-Heal)\n" + "<w>i</w>/<w>I</w> : identify/unidentify inventory\n" + "<w>Ctrl-I</w> : item generation stats\n" + "<w>l</w> : make entrance to labyrinth\n" + "<w>L</w> : place a vault by name\n" + "<w>m</w>/<w>M</w> : create monster by number/name\n" + "<w>o</w>/<w>%</w> : create an object\n" + "<w>p</w> : make entrance to pandemonium\n" + "<w>P</w> : make a portal (i.e., bazaars)\n" + "<w>r</w> : change character's species\n" + "<w>s</w> : gain 20000 skill points\n" + "<w>S</w> : set skill to level\n", true, true); cols.add_formatted(1, - "t : tweak object properties\n" - "T : make a trap\n" - "v : show gold value of an item\n" - "x : gain an experience level\n" - "Ctrl-X : change experience level\n" - "X : make Xom do something now\n" - "z/Z : cast spell by number/name\n" - "$ : get 1000 gold\n" - "</> : create up/down staircase\n" - "u/d : shift up/down one level\n" - "~ : go to a specific level\n" - "(/) : make feature by number/name\n" - "] : get a mutation\n" - "[ : get a demonspawn mutation\n" - ": : find branches in the dungeon\n" - "{ : magic mapping\n" - "^ : gain piety\n" - "_ : gain religion\n" - "' : list items\n" - "\" : list monsters\n" - "? : list wizard commands\n" - "| : make unrand/fixed artefacts\n" - "+ : make randart from item\n" - "@ : set Str Int Dex\n" - "\\ : make a shop\n", + "<w>t</w> : tweak object properties\n" + "<w>T</w> : make a trap\n" + "<w>v</w> : show gold value of an item\n" + "<w>x</w> : gain an experience level\n" + "<w>Ctrl-X</w> : change experience level\n" + "<w>X</w> : make Xom do something now\n" + "<w>z</w>/<w>Z</w> : cast spell by number/name\n" + "<w>$</w> : get 1000 gold\n" + "<w><<</w>/<w>></w> : create up/down staircase\n" + "<w>u</w>/<w>d</w> : shift up/down one level\n" + "<w>~</w> : go to a specific level\n" + "<w>(</w>/<w>)</w> : make feature by number/name\n" + "<w>]</w> : get a mutation\n" + "<w>[</w> : get a demonspawn mutation\n" + "<w>:</w> : find branches in the dungeon\n" + "<w>{</w> : magic mapping\n" + "<w>^</w> : gain piety\n" + "<w>_</w> : gain religion\n" + "<w>'</w> : list items\n" + "<w>\"</w> : list monsters\n" + "<w>?</w> : list wizard commands\n" + "<w>|</w> : create all unrand/fixed artefacts\n" + "<w>+</w> : make randart from item\n" + "<w>@</w> : set Str Int Dex\n" + "<w>\\</w> : make a shop\n", true, true); _show_keyhelp_menu(cols.formatted_lines(), false, true); diff --git a/crawl-ref/source/mon-util.cc b/crawl-ref/source/mon-util.cc index 1c1d7fb53b..64ebfc3324 100644 --- a/crawl-ref/source/mon-util.cc +++ b/crawl-ref/source/mon-util.cc @@ -4739,6 +4739,13 @@ void monsters::add_enchantment_effect(const mon_enchant &ench, bool quiet) target_x = you.x_pos; target_y = you.y_pos; foe = MHITYOU; + + if (is_patrolling()) + { + // Enslaved monsters stop patrolling and forget their patrol point, + // they're supposed to follow you now. + patrol_point = coord_def(0, 0); + } break; default: @@ -4838,6 +4845,13 @@ void monsters::remove_enchantment_effect(const mon_enchant &me, bool quiet) activity_interrupt_data(this, "uncharm")); } + if (is_patrolling()) + { + // Enslaved monsters stop patrolling and forget their patrol point, + // in case they were on order to wait. + patrol_point = coord_def(0, 0); + } + // reevaluate behaviour behaviour_event(this, ME_EVAL); break; diff --git a/crawl-ref/source/monplace.cc b/crawl-ref/source/monplace.cc index 2eb7eb29ae..4c9af2a0ff 100644 --- a/crawl-ref/source/monplace.cc +++ b/crawl-ref/source/monplace.cc @@ -188,8 +188,8 @@ static void hell_spawn_random_monsters() if (one_chance_in(genodds)) { mgen_data mg(WANDERING_MONSTER); - mg.proximity = - (one_chance_in(10) ? PROX_NEAR_STAIRS : PROX_AWAY_FROM_PLAYER); + mg.proximity = (one_chance_in(10) ? PROX_NEAR_STAIRS + : PROX_AWAY_FROM_PLAYER); mons_place(mg); viewwindow(true, false); } @@ -1737,12 +1737,12 @@ int mons_place( mgen_data mg ) mg.cls = RANDOM_MONSTER; } - // all monsters have been assigned? {dlb} + // All monsters have been assigned? {dlb} if (mon_count >= MAX_MONSTERS - 1) return (-1); - // this gives a slight challenge to the player as they ascend the - // dungeon with the Orb + // This gives a slight challenge to the player as they ascend the + // dungeon with the Orb. if (you.char_direction == GDT_ASCENDING && mg.cls == RANDOM_MONSTER && you.level_type == LEVEL_DUNGEON && !mg.summoned()) { @@ -1776,8 +1776,8 @@ int mons_place( mgen_data mg ) monsters *creation = &menv[mid]; - // look at special cases: CHARMED, FRIENDLY, HOSTILE, GOD_GIFT - // alert summoned being to player's presence + // Look at special cases: CHARMED, FRIENDLY, HOSTILE, GOD_GIFT. + // Alert summoned being to player's presence. if (mg.behaviour > NUM_BEHAVIOURS) { if (mg.behaviour == BEH_FRIENDLY || mg.behaviour == BEH_GOD_GIFT) @@ -1801,6 +1801,10 @@ int mons_place( mgen_data mg ) if (creation->type == MONS_RAKSHASA_FAKE && !one_chance_in(3)) creation->add_ench(ENCH_INVIS); } + + if (mg.needs_patrol_point()) + creation->patrol_point = coord_def(creation->x, creation->y); + return (mid); } diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc index ab4a48d441..7f586238b1 100644 --- a/crawl-ref/source/monstuff.cc +++ b/crawl-ref/source/monstuff.cc @@ -2149,26 +2149,54 @@ void behaviour_event( monsters *mon, int event, int src, _handle_behaviour( mon ); } -static bool _choose_random_target_grid_in_los(monsters *mon) +static bool _choose_random_patrol_target_grid(monsters *mon) { + int patrol_x = mon->patrol_point.x; + int patrol_y = mon->patrol_point.y; + dungeon_feature_type habitat = habitat2grid( + mons_habitat_by_type(mon->type) ); int pos_x, pos_y; int count_grids = 0; for (int j = -LOS_RADIUS; j < LOS_RADIUS; j++) for (int k = -LOS_RADIUS; k < LOS_RADIUS; k++) { - if (j == 0 && k == 0) - continue; - - pos_x = mon->x + j; - pos_y = mon->y + k; + pos_x = patrol_x + j; + pos_y = patrol_y + k; if (!in_bounds(pos_x, pos_y)) continue; - if (!mon->mon_see_grid(pos_x, pos_y)) + + if (pos_x == mon->x && pos_y == mon->y) continue; + if (!mon->can_pass_through_feat(grd[pos_x][pos_y])) continue; + if (grid_see_grid(mon->x, mon->y, patrol_x, patrol_y, habitat)) + { + // If the patrol point can be easily (within LOS) reached + // from the current position, it suffices if the target is + // within reach of the patrol point OR the current position: + // we can easily get there. + if (!grid_see_grid(patrol_x, patrol_y, pos_x, pos_y, habitat) + && !grid_see_grid(mon->x, mon->y, pos_x, pos_y, habitat)) + { + continue; + } + } + else + { + // If, however, the patrol point is out of reach, we have to + // make sure the new target brings us into reach of it. + // This means that the target must be reachable BOTH from + // the patrol point AND the current position. + if (!grid_see_grid(patrol_x, patrol_y, pos_x, pos_y, habitat) + || !grid_see_grid(mon->x, mon->y, pos_x, pos_y, habitat)) + { + continue; + } + } + if (one_chance_in(++count_grids)) { mon->target_x = pos_x; @@ -2508,9 +2536,11 @@ static void _handle_behaviour(monsters *mon) { if (patrolling) { - if (mon->patrol_point != coord_def(mon->x, mon->y) - || !_choose_random_target_grid_in_los(mon)) + if (!_choose_random_patrol_target_grid(mon)) { + // If we couldn't find a target that is within easy + // reach of the monster and close to the patrol point, + // it's time to head back. mon->target_x = mon->patrol_point.x; mon->target_y = mon->patrol_point.y; } @@ -2727,7 +2757,7 @@ monsters *choose_random_monster_on_level(int weight, return chosen; } -// note that this function *completely* blocks messaging for monsters +// Note that this function *completely* blocks messaging for monsters // distant or invisible to the player ... look elsewhere for a function // permitting output of "It" messages for the invisible {dlb} // Intentionally avoids info and str_pass now. -- bwr @@ -3685,7 +3715,7 @@ static bool _handle_scroll(monsters *monster) bool was_visible = mons_near(monster) && player_monster_visible(monster); - // notice how few cases are actually accounted for here {dlb}: + // Notice how few cases are actually accounted for here {dlb}: const int scroll_type = mitm[monster->inv[MSLOT_SCROLL]].sub_type; switch (scroll_type) { @@ -3880,13 +3910,13 @@ static bool _handle_wand(monsters *monster, bolt &beem) niceWand = true; break; } - // this break causes the wand to be tried on the player: + // This break causes the wand to be tried on the player. break; } return (false); } - // fire tracer, if necessary + // Fire tracer, if necessary. if (!niceWand) { fire_tracer( monster, beem ); @@ -4623,16 +4653,16 @@ static bool _handle_throw(monsters *monster, bolt & beem) return (false); } - // ok, we'll try it. + // Ok, we'll try it. setup_generic_throw( monster, beem ); - // set fake damage for the tracer. + // Set fake damage for the tracer. beem.damage = dice_def(10, 10); // fire tracer fire_tracer( monster, beem ); - // clear fake damage (will be set correctly in mons_throw). + // Clear fake damage (will be set correctly in mons_throw). beem.damage = 0; // good idea? @@ -4770,7 +4800,7 @@ static void _handle_monster_move(int i, monsters *monster) if (monster->hit_points > monster->max_hit_points) monster->hit_points = monster->max_hit_points; - // monster just summoned (or just took stairs), skip this action + // Monster just summoned (or just took stairs), skip this action. if (testbits( monster->flags, MF_JUST_SUMMONED )) { monster->flags &= ~MF_JUST_SUMMONED; @@ -4779,7 +4809,7 @@ static void _handle_monster_move(int i, monsters *monster) _monster_add_energy(monster); - // Handle clouds on nonmoving monsters: + // Handle clouds on nonmoving monsters. if (monster->speed == 0 && env.cgrid[monster->x][monster->y] != EMPTY_CLOUD && !monster->has_ench(ENCH_SUBMERGED)) @@ -5238,7 +5268,7 @@ void handle_monsters(void) // If the player got banished, discard pending monster actions. if (you.banished) { - // clear list of beholding monsters + // Clear list of beholding monsters. if (you.duration[DUR_BEHELD]) { you.beheld_by.clear(); @@ -5284,7 +5314,7 @@ static bool _is_item_jelly_edible(const item_def &item) // // handle_pickup // -// Returns false if monster doesn't spend any time pickup up +// Returns false if monster doesn't spend any time picking something up. // //--------------------------------------------------------------- static bool _handle_pickup(monsters *monster) @@ -5455,7 +5485,7 @@ static bool _mons_can_displace(const monsters *mpusher, const monsters *mpushee) return (false); } - // Batty monsters are unpushable + // Batty monsters are unpushable. if (mons_is_batty(mpusher) || mons_is_batty(mpushee)) return (false); @@ -5630,9 +5660,9 @@ void mons_check_pool(monsters *mons, killer_type killer, int killnum) } } -// returns true for monsters that obviously (to the player) feel -// "thematically at home" in a branch -// currently used for native monsters recognizing traps +// Returns true for monsters that obviously (to the player) +// feel "thematically at home" in a branch. +// Currently used for native monsters recognizing traps. static bool _is_native_in_branch(const monsters *monster, const branch_type branch) { @@ -5777,7 +5807,7 @@ static bool _is_trap_safe(const monsters *monster, const int trap_x, } } - // friendlies will try not to be parted from you + // Friendlies will try not to be parted from you. if (intelligent_ally(monster) && trap.type == TRAP_TELEPORT && player_knows_trap && mons_near(monster)) { @@ -5991,21 +6021,19 @@ bool _mon_can_move_to_pos(const monsters *monster, const int count_x, // Friendlies shouldn't try to move onto the player's // location, if they are aiming for some other target. if (mons_wont_attack(monster) - && monster->foe != MHITNOT && monster->foe != MHITYOU + && (monster->foe != MHITNOT || monster->is_patrolling()) && targ_x == you.x_pos && targ_y == you.y_pos) { return false; } - // wandering through a trap is OK if we're pretty healthy, - // really stupid, or immune to the trap + // Wandering through a trap is OK if we're pretty healthy, + // really stupid, or immune to the trap. const int which_trap = trap_at_xy(targ_x,targ_y); if (which_trap >= 0 && !_is_trap_safe(monster, targ_x, targ_y, just_check)) - { return false; - } if (targ_cloud_num != EMPTY_CLOUD) { @@ -6054,8 +6082,8 @@ bool _mon_can_move_to_pos(const monsters *monster, const int count_x, return true; break; - // this isn't harmful, but dumb critters might think so. case CLOUD_GREY_SMOKE: + // This isn't harmful, but dumb critters might think so. if (mons_intel(monster->type) > I_ANIMAL || coinflip()) return true; @@ -6070,13 +6098,13 @@ bool _mon_can_move_to_pos(const monsters *monster, const int count_x, return true; // harmless clouds } - // if we get here, the cloud is potentially harmful. - // exceedingly dumb creatures will still wander in. + // If we get here, the cloud is potentially harmful. + // Exceedingly dumb creatures will still wander in. if (mons_intel(monster->type) != I_PLANT) return false; } - // if we end up here the monster can safely move + // If we end up here the monster can safely move. return true; } @@ -6089,7 +6117,7 @@ static bool _monster_move(monsters *monster) const habitat_type habitat = mons_habitat(monster); bool deep_water_available = false; - // Berserking monsters make a lot of racket + // Berserking monsters make a lot of racket. if (monster->has_ench(ENCH_BERSERK)) { int noise_level = get_shout_noise_level(mons_shouts(monster->type)); @@ -6128,7 +6156,7 @@ static bool _monster_move(monsters *monster) return false; } - // let's not even bother with this if mmov_x and mmov_y are zero. + // Let's not even bother with this if mmov_x and mmov_y are zero. if (mmov_x == 0 && mmov_y == 0) return false; @@ -6145,10 +6173,7 @@ static bool _monster_move(monsters *monster) const int targ_x = monster->x + count_x - 1; const int targ_y = monster->y + count_y - 1; - // [ds] Bounds check was after grd[targ_x][targ_y] which would - // trigger an ASSERT. Moved it up. - - // bounds check - don't consider moving out of grid! + // Bounds check - don't consider moving out of grid! if (targ_x < 0 || targ_x >= GXM || targ_y < 0 || targ_y >= GYM) { good_move[count_x][count_y] = false; @@ -6162,11 +6187,13 @@ static bool _monster_move(monsters *monster) const monsters* mons = dynamic_cast<const monsters*>(monster); good_move[count_x][count_y] = _mon_can_move_to_pos(mons, count_x-1, count_y-1); - } // now we know where we _can_ move. + } + + // Now we know where we _can_ move. const coord_def newpos = monster->pos() + coord_def(mmov_x, mmov_y); - // normal/smart monsters know about secret doors (they _live_ in the - // dungeon!) + // Normal/smart monsters know about secret doors + // (they _live_ in the dungeon!) if (grd(newpos) == DNGN_CLOSED_DOOR || (grd(newpos) == DNGN_SECRET_DOOR && (mons_intel(monster_index(monster)) == I_HIGH @@ -6174,7 +6201,7 @@ static bool _monster_move(monsters *monster) { if (mons_is_zombified(monster)) { - // for zombies, monster type is kept in mon->base_monster + // For zombies, monster type is kept in mon->base_monster. if (mons_itemuse(monster->base_monster) >= MONUSE_OPEN_DOORS) { _mons_open_door(monster, newpos); @@ -6210,7 +6237,7 @@ static bool _monster_move(monsters *monster) } // done door-eating jellies - // water creatures have a preference for water they can hide in -- bwr + // Water creatures have a preference for water they can hide in -- bwr // [ds] Weakened the powerful attraction to deep water if the monster // is in good health. if (habitat == HT_WATER @@ -6309,7 +6336,7 @@ static bool _monster_move(monsters *monster) if (dist[0] == dist[1] && abs(dist[0]) == FAR_AWAY) continue; - // which one was better? -- depends on FLEEING or not + // Which one was better? -- depends on FLEEING or not. if (monster->behaviour == BEH_FLEE) { if (dist[0] >= dist[1] && dist[0] >= current_distance) @@ -6346,10 +6373,10 @@ static bool _monster_move(monsters *monster) forget_it: // ------------------------------------------------------------------ - // if we haven't found a good move by this point, we're not going to. + // If we haven't found a good move by this point, we're not going to. // ------------------------------------------------------------------ - // take care of beetle burrowing + // Take care of beetle burrowing. if (monster->type == MONS_BORING_BEETLE) { dungeon_feature_type feat = @@ -6368,7 +6395,7 @@ forget_it: bool ret = false; if (good_move[mmov_x + 1][mmov_y + 1] && !(mmov_x == 0 && mmov_y == 0)) { - // check for attacking player + // Check for attacking player. if (monster->x + mmov_x == you.x_pos && monster->y + mmov_y == you.y_pos) { @@ -6407,7 +6434,7 @@ forget_it: } } - // check for attacking another monster + // Check for attacking another monster. int targmon = mgrd[monster->x + mmov_x][monster->y + mmov_y]; if (targmon != NON_MONSTER) { @@ -6442,8 +6469,8 @@ forget_it: { mmov_x = mmov_y = 0; - // fleeing monsters that can't move will panic and possibly - // turn to face their attacker + // Fleeing monsters that can't move will panic and possibly + // turn to face their attacker. if (monster->behaviour == BEH_FLEE) behaviour_event(monster, ME_CORNERED); } @@ -6844,7 +6871,7 @@ void seen_monster(monsters *monster) if (monster->flags & MF_SEEN) return; - // First time we've seen this particular monster + // First time we've seen this particular monster. monster->flags |= MF_SEEN; if (!mons_is_mimic(monster->type) @@ -6878,7 +6905,7 @@ bool shift_monster( monsters *mon, int x, int y ) if (x == 0 && y == 0) { - // try and find a random floor space some distance away + // Try and find a random floor space some distance away. for (i = 0; i < 50; i++) { tx = 5 + random2( GXM - 10 ); @@ -6902,7 +6929,7 @@ bool shift_monster( monsters *mon, int x, int y ) if (!inside_level_bounds(tx, ty)) continue; - // don't drop on anything but vanilla floor right now + // Don't drop on anything but vanilla floor right now. if (grd[tx][ty] != DNGN_FLOOR) continue; diff --git a/crawl-ref/source/view.cc b/crawl-ref/source/view.cc index 6daeea3893..019ea1605f 100644 --- a/crawl-ref/source/view.cc +++ b/crawl-ref/source/view.cc @@ -2438,7 +2438,7 @@ bool find_ray( int sourcex, int sourcey, int targetx, int targety, // Count the number of matching features between two points along // a beam-like path; the path will pass through solid features. -// By default, it excludes enpoints from the count. +// By default, it excludes end points from the count. int num_feats_between(int sourcex, int sourcey, int targetx, int targety, dungeon_feature_type min_feat, dungeon_feature_type max_feat, @@ -3632,7 +3632,7 @@ bool see_grid( const coord_def &p ) return see_grid(env.show, you.pos(), p); } -// answers the question: "Would a grid be within character's line of sight, +// Answers the question: "Would a grid be within character's line of sight, // even if all translucent/clear walls were made opaque?" bool see_grid_no_trans( const coord_def &p ) { @@ -3645,6 +3645,25 @@ bool trans_wall_blocking( const coord_def &p ) return see_grid(p) && !see_grid_no_trans(p); } +// Usually calculates whether from one grid someone could see the other. +// Depending on the viewer's habitat, 'allowed' can be set to DNGN_FLOOR, +// DNGN_SHALLOW_WATER or DNGN_DEEP_WATER. +// Yes, this ignores lava-loving monsters. +bool grid_see_grid(int posx_1, int posy_1, int posx_2, int posy_2, + dungeon_feature_type allowed) +{ + if (distance(posx_1, posy_1, posx_2, posy_2) > LOS_RADIUS * LOS_RADIUS) + return (false); + + dungeon_feature_type max_disallowed = DNGN_MAXOPAQUE; + if (allowed != DNGN_UNSEEN) + max_disallowed = static_cast<dungeon_feature_type>(allowed - 1); + + // XXX: Ignoring clouds for now. + return (num_feats_between(posx_1, posy_1, posx_2, posy_2, DNGN_UNSEEN, + max_disallowed) == 0); +} + static const unsigned dchar_table[ NUM_CSET ][ NUM_DCHAR_TYPES ] = { // CSET_ASCII diff --git a/crawl-ref/source/view.h b/crawl-ref/source/view.h index 40e45becb6..135314223f 100644 --- a/crawl-ref/source/view.h +++ b/crawl-ref/source/view.h @@ -194,6 +194,8 @@ bool see_grid( const env_show_grid &show, bool see_grid(const coord_def &p); bool see_grid_no_trans( const coord_def &p ); bool trans_wall_blocking( const coord_def &p ); +bool grid_see_grid(int posx_1, int posy_1, int posx_2, int posy_2, + dungeon_feature_type allowed = DNGN_UNSEEN); inline bool see_grid( int grx, int gry ) { |