/** * @file * @brief Translocation spells. **/ #include "AppHdr.h" #include #include #include #include "spl-transloc.h" #include "externs.h" #include "abyss.h" #include "act-iter.h" #include "areas.h" #include "cloud.h" #include "coord.h" #include "coordit.h" #include "delay.h" #include "directn.h" #include "dungeon.h" #include "env.h" #include "fprop.h" #include "invent.h" #include "itemprop.h" #include "items.h" #include "libutil.h" #include "losglobal.h" #include "message.h" #include "misc.h" #include "mon-behv.h" #include "mon-util.h" #include "orb.h" #include "output.h" #include "prompt.h" #include "random.h" #include "shout.h" #include "spl-util.h" #include "stash.h" #include "state.h" #include "teleport.h" #include "terrain.h" #include "throw.h" #include "transform.h" #include "traps.h" #include "view.h" #include "viewmap.h" #include "xom.h" // XXX: can miscast before cancelling. spret_type cast_controlled_blink(int pow, bool fail) { fail_check(); if (blink(pow, true) == -1) return SPRET_ABORT; return SPRET_SUCCESS; } spret_type cast_disjunction(int pow, bool fail) { fail_check(); int rand = random_range(35, 45) + random2(pow / 12); you.duration[DUR_DISJUNCTION] = min(90 + pow / 12, max(you.duration[DUR_DISJUNCTION] + rand, 30 + rand)); contaminate_player(1000, true); disjunction(); return SPRET_SUCCESS; } void disjunction() { int steps = you.time_taken; invalidate_agrid(true); for (int step = 0; step < steps; ++step) { vector mvec; for (radius_iterator ri(you.pos(), LOS_RADIUS, C_ROUND); ri; ++ri) { monster* mons = monster_at(*ri); if (!mons || !you.see_cell(*ri)) continue; mvec.push_back(mons); } if (mvec.empty()) return; // blink should be isotropic shuffle_array(mvec); for (vector::iterator mitr = mvec.begin(); mitr != mvec.end(); mitr++) { monster* mons = *mitr; if (!mons->alive() || mons->no_tele()) continue; coord_def p = mons->pos(); if (!disjunction_haloed(p)) continue; int dist = grid_distance(you.pos(), p); int decay = max(1, (dist - 1) * (dist + 1)); int chance = pow(0.8, 1.0 / decay) * 1000; if (!x_chance_in_y(chance, 1000)) blink_away(mons, &you, false); } } } // If wizard_blink is set, all restriction are ignored (except for // a monster being at the target spot), and the player gains no // contamination. int blink(int pow, bool high_level_controlled_blink, bool wizard_blink, string *pre_msg, bool safely_cancellable) { ASSERT(!crawl_state.game_is_arena()); dist beam; if (crawl_state.is_repeating_cmd()) { crawl_state.cant_cmd_repeat("You can't repeat controlled blinks."); crawl_state.cancel_cmd_again(); crawl_state.cancel_cmd_repeat(); return -1; } // yes, there is a logic to this ordering {dlb}: if (you.no_tele(true, true, true) && !wizard_blink) { if (pre_msg) mpr(pre_msg->c_str()); canned_msg(MSG_STRANGE_STASIS); } else if (you.confused() && !wizard_blink) { if (pre_msg) mpr(pre_msg->c_str()); random_blink(false); } // The orb sometimes degrades controlled blinks to completely uncontrolled. else if (orb_haloed(you.pos()) && !wizard_blink) { if (safely_cancellable && !high_level_controlled_blink && !yesno("Your blink will be uncontrolled - continue anyway?", false, 'n')) { canned_msg(MSG_OK); return -1; } if (pre_msg) mpr(pre_msg->c_str()); mprf(MSGCH_ORB, "The orb interferes with your control of the blink!"); // abort still wastes the turn if (high_level_controlled_blink && coinflip()) return cast_semi_controlled_blink(pow, false, false) ? 1 : 0; random_blink(false); } else if (!allow_control_teleport(true) && !wizard_blink) { if (safely_cancellable && !high_level_controlled_blink && !yesno("Your blink will be uncontrolled - continue anyway?", false, 'n')) { canned_msg(MSG_OK); return -1; } if (pre_msg) mpr(pre_msg->c_str()); mpr("A powerful magic interferes with your control of the blink."); // FIXME: cancel shouldn't waste a turn here -- need to rework Abyss handling if (high_level_controlled_blink) return cast_semi_controlled_blink(pow, false/*true*/, false) ? 1 : -1; random_blink(false); } else { // query for location {dlb}: while (1) { direction_chooser_args args; args.restricts = DIR_TARGET; args.needs_path = false; args.may_target_monster = false; args.top_prompt = "Blink to where?"; direction(beam, args); if (crawl_state.seen_hups) { mpr("Cancelling blink due to HUP."); return -1; } if (!beam.isValid || beam.target == you.pos()) { if (!wizard_blink && !safely_cancellable && !yesno("Are you sure you want to cancel this blink?", false, 'n')) { clear_messages(); continue; } canned_msg(MSG_OK); return -1; // early return {dlb} } monster* beholder = you.get_beholder(beam.target); if (!wizard_blink && beholder) { mprf("You cannot blink away from %s!", beholder->name(DESC_THE, true).c_str()); continue; } monster* fearmonger = you.get_fearmonger(beam.target); if (!wizard_blink && fearmonger) { mprf("You cannot blink closer to %s!", fearmonger->name(DESC_THE, true).c_str()); continue; } if (grd(beam.target) == DNGN_OPEN_SEA) { clear_messages(); mpr("You can't blink into the sea!"); } else if (grd(beam.target) == DNGN_LAVA_SEA) { clear_messages(); mpr("You can't blink into the sea of lava!"); } else if (!check_moveto(beam.target, "blink")) { // try again (messages handled by check_moveto) } else if (you.see_cell_no_trans(beam.target)) { // Grid in los, no problem. break; } else if (you.trans_wall_blocking(beam.target)) { // Wizard blink can move past translucent walls. if (wizard_blink) break; clear_messages(); mpr("There's something in the way!"); } else { clear_messages(); mpr("You can only blink to visible locations."); } } if (!you.attempt_escape(2)) return false; if (pre_msg) mpr(pre_msg->c_str()); // Allow wizard blink to send player into walls, in case the // user wants to alter that grid to something else. if (wizard_blink && cell_is_solid(beam.target)) grd(beam.target) = DNGN_FLOOR; if (cell_is_solid(beam.target) || monster_at(beam.target)) { mpr("Oops! Maybe something was there already."); random_blink(false); } else { // Leave a purple cloud. if (!wizard_blink) place_cloud(CLOUD_TLOC_ENERGY, you.pos(), 1 + random2(3), &you); move_player_to_grid(beam.target, false); // Controlling teleport contaminates the player. -- bwr if (!wizard_blink) contaminate_player(1000, true); } } crawl_state.cancel_cmd_again(); crawl_state.cancel_cmd_repeat(); return 1; } spret_type cast_blink(bool allow_partial_control, bool fail) { fail_check(); random_blink(allow_partial_control); return SPRET_SUCCESS; } void random_blink(bool allow_partial_control, bool override_abyss, bool override_stasis) { ASSERT(!crawl_state.game_is_arena()); coord_def target; if (you.no_tele(true, true, true) && !override_stasis) canned_msg(MSG_STRANGE_STASIS); // First try to find a random square not adjacent to the player, // then one adjacent if that fails. else if (!random_near_space(&you, you.pos(), target) && !random_near_space(&you, you.pos(), target, true)) { mpr("You feel jittery for a moment."); } //jmf: Add back control, but effect is cast_semi_controlled_blink(pow). else if (player_control_teleport() && !you.confused() && allow_partial_control && allow_control_teleport()) { mpr("You may select the general direction of your translocation."); // FIXME: handle aborts here, don't waste the turn cast_semi_controlled_blink(100, false, true); } else if (you.attempt_escape(2)) { canned_msg(MSG_YOU_BLINK); coord_def origin = you.pos(); move_player_to_grid(target, false); // Leave a purple cloud. if (!cell_is_solid(origin)) place_cloud(CLOUD_TLOC_ENERGY, origin, 1 + random2(3), &you); } } // This function returns true if the player can use controlled teleport // here. bool allow_control_teleport(bool quiet) { const bool retval = !(testbits(env.level_flags, LFLAG_NO_TELE_CONTROL) || orb_haloed(you.pos()) || you.beheld()); // Tell the player why if they have teleport control. if (!quiet && !retval && player_control_teleport()) { if (orb_haloed(you.pos())) mprf(MSGCH_ORB, "The orb prevents control of your teleportation!"); else if (you.beheld()) mpr("It is impossible to concentrate on your destination whilst mesmerised."); else mpr("A powerful magic prevents control of your teleportation."); } return retval; } spret_type cast_teleport_self(bool fail) { fail_check(); you_teleport(); return SPRET_SUCCESS; } void you_teleport() { // [Cha] here we block teleportation, which will save the player from // death from read-id'ing scrolls (in sprint) if (you.no_tele(true, true)) canned_msg(MSG_STRANGE_STASIS); else if (you.duration[DUR_TELEPORT]) { mpr("You feel strangely stable."); you.duration[DUR_TELEPORT] = 0; } else { mpr("You feel strangely unstable."); int teleport_delay = 3 + random2(3); // Doesn't care whether the cTele will actually work or not. if (player_control_teleport()) { mpr("You feel your translocation being delayed."); teleport_delay += 1 + random2(3); } if (player_in_branch(BRANCH_ABYSS) && !one_chance_in(5)) { mpr("You feel the power of the Abyss delaying your translocation."); teleport_delay += 5 + random2(10); } else if (orb_haloed(you.pos())) { mprf(MSGCH_ORB, "You feel the orb delaying this translocation!"); teleport_delay += 5 + random2(5); } you.set_duration(DUR_TELEPORT, teleport_delay); } } // Should return true if we don't want anyone to teleport here. static bool _cell_vetoes_teleport(const coord_def cell, bool check_monsters = true, bool wizard_tele = false) { // Monsters always veto teleport. if (monster_at(cell) && check_monsters) return true; // As do all clouds; this may change. if (env.cgrid(cell) != EMPTY_CLOUD && !wizard_tele) return true; if (cell_is_solid(cell)) return true; return is_feat_dangerous(grd(cell), true) && !wizard_tele; } static void _handle_teleport_update(bool large_change, const coord_def old_pos) { if (large_change) { viewwindow(); for (monster_iterator mi; mi; ++mi) { const bool see_cell = you.see_cell(mi->pos()); if (mi->foe == MHITYOU && !see_cell && !you.penance[GOD_ASHENZARI]) { mi->foe_memory = 0; behaviour_event(*mi, ME_EVAL); } else if (see_cell) behaviour_event(*mi, ME_EVAL); } } #ifdef USE_TILE if (you.species == SP_MERFOLK) { const dungeon_feature_type new_grid = grd(you.pos()); const dungeon_feature_type old_grid = grd(old_pos); if (feat_is_water(old_grid) && !feat_is_water(new_grid) || !feat_is_water(old_grid) && feat_is_water(new_grid)) { init_player_doll(); } } #endif } static bool _teleport_player(bool allow_control, bool wizard_tele, int range) { bool is_controlled = (allow_control && !you.confused() && player_control_teleport() && allow_control_teleport() && !you.berserk()); // All wizard teleports are automatically controlled. if (wizard_tele) is_controlled = true; if (!wizard_tele && (crawl_state.game_is_sprint() || you.no_tele(true, true)) && !player_in_branch(BRANCH_ABYSS)) { canned_msg(MSG_STRANGE_STASIS); return false; } // After this point, we're guaranteed to teleport. Kill the appropriate // delays. interrupt_activity(AI_TELEPORT); // Update what we can see at the current location as well as its stash, // in case something happened in the exact turn that we teleported // (like picking up/dropping an item). viewwindow(); StashTrack.update_stash(you.pos()); if (player_in_branch(BRANCH_ABYSS) && !wizard_tele) { abyss_teleport(); if (you.pet_target != MHITYOU) you.pet_target = MHITNOT; return true; } coord_def pos(1, 0); const coord_def old_pos = you.pos(); bool large_change = false; if (is_controlled) { // Only have the messages and the more prompt for non-wizard. if (!wizard_tele) { mpr("You may choose your destination (press '.' or delete to select)."); mpr("Expect minor deviation."); more(); } while (true) { level_pos lpos; bool chose = show_map(lpos, false, true, false); pos = lpos.pos; redraw_screen(); // If we've received a HUP signal then the user can't choose a // location, so cancel the teleport. if (crawl_state.seen_hups) { mprf(MSGCH_ERROR, "Controlled teleport interrupted by HUP signal, " "cancelling teleport."); if (!wizard_tele) contaminate_player(1000, true); return false; } dprf("Target square (%d,%d)", pos.x, pos.y); if (!chose || pos == you.pos()) { if (!wizard_tele) { if (!yesno("Are you sure you want to cancel this teleport?", true, 'n')) { continue; } } if (!wizard_tele) contaminate_player(1000, true); return false; } monster* beholder = you.get_beholder(pos); if (beholder && !wizard_tele) { mprf("You cannot teleport away from %s!", beholder->name(DESC_THE, true).c_str()); mpr("Choose another destination (press '.' or delete to select)."); more(); continue; } monster* fearmonger = you.get_fearmonger(pos); if (fearmonger && !wizard_tele) { mprf("You cannot teleport closer to %s!", fearmonger->name(DESC_THE, true).c_str()); mpr("Choose another destination (press '.' or delete to select)."); more(); continue; } break; } // Don't randomly walk wizard teleports. if (!wizard_tele) { pos.x += random2(3) - 1; pos.y += random2(3) - 1; if (one_chance_in(4)) { pos.x += random2(3) - 1; pos.y += random2(3) - 1; } dprf("Scattered target square (%d, %d)", pos.x, pos.y); } if (!in_bounds(pos)) { mpr("Nearby solid objects disrupt your rematerialisation!"); is_controlled = false; } if (is_controlled) { if (!you.see_cell(pos)) large_change = true; // Merfolk should be able to control-tele into deep water. if (_cell_vetoes_teleport(pos, true, wizard_tele)) { if (wizard_tele) { mprf(MSGCH_WARN, "Even you can't go there right now. Sorry!"); return false; } dprf("Target square (%d, %d) vetoed, now random teleport.", pos.x, pos.y); is_controlled = false; large_change = false; } else if (testbits(env.pgrid(pos), FPROP_NO_CTELE_INTO) && !wizard_tele) { is_controlled = false; large_change = false; mprf(MSGCH_WARN, "A strong magical force throws you back!"); } else { // Leave a purple cloud. if (!wizard_tele && !cell_is_solid(old_pos)) place_cloud(CLOUD_TLOC_ENERGY, old_pos, 1 + random2(3), &you); move_player_to_grid(pos, false); // Controlling teleport contaminates the player. - bwr if (!wizard_tele) contaminate_player(1000, true); } // End teleport control. if (you.duration[DUR_CONTROL_TELEPORT]) { mprf(MSGCH_DURATION, "You feel uncertain."); you.duration[DUR_CONTROL_TELEPORT] = 0; } } } if (!is_controlled) { coord_def newpos; // If in a labyrinth, always teleport well away from the centre. // (Check done for the straight line, no pathfinding involved.) bool need_distance_check = false; coord_def centre; if (player_in_branch(BRANCH_LABYRINTH)) { bool success = false; for (int xpos = 0; xpos < GXM; xpos++) { for (int ypos = 0; ypos < GYM; ypos++) { centre = coord_def(xpos, ypos); if (!in_bounds(centre)) continue; if (grd(centre) == DNGN_ESCAPE_HATCH_UP) { success = true; break; } } if (success) break; } need_distance_check = success; } int tries = 500; do newpos = random_in_bounds(); while (--tries > 0 && (_cell_vetoes_teleport(newpos) || (newpos - old_pos).abs() > dist_range(range) || need_distance_check && (newpos - centre).abs() <= dist_range(min(range - 1, 34)) || testbits(env.pgrid(newpos), FPROP_NO_RTELE_INTO))); // Running out of tries should only happen for limited-range teleports, // which are all involuntary; no message. Return false so it doesn't // count as a random teleport for Xom purposes. if (tries == 0) return false; else if (newpos == old_pos) mpr("Your surroundings flicker for a moment."); else if (you.see_cell(newpos)) mpr("Your surroundings seem slightly different."); else { mpr("Your surroundings suddenly seem different."); large_change = true; } // Leave a purple cloud. if (!cell_is_solid(old_pos)) place_cloud(CLOUD_TLOC_ENERGY, old_pos, 1 + random2(3), &you); move_player_to_grid(newpos, false); } _handle_teleport_update(large_change, old_pos); return !is_controlled; } bool you_teleport_to(const coord_def where_to, bool move_monsters) { // Attempts to teleport the player from their current location to 'where'. // Follows this line of reasoning: // 1. Check the location (against _cell_vetoes_teleport), if valid, // teleport the player there. // 2. If not because of a monster, and move_monster, teleport that // monster out of the way, then teleport the player there. // 3. Otherwise, iterate over adjacent squares. If a sutiable position is // found (or a monster can be moved out of the way, with move_monster) // then teleport the player there. // 4. If not, give up and return false. const coord_def old_pos = you.pos(); coord_def where = where_to; coord_def old_where = where_to; // Don't bother to calculate a possible new position if it's out of bounds. if (!in_bounds(where)) return false; if (_cell_vetoes_teleport(where)) { if (monster_at(where) && move_monsters && !_cell_vetoes_teleport(where, false)) { // dlua only, don't heed no_tele monster* mons = monster_at(where); mons->teleport(true); } else { for (adjacent_iterator ai(where); ai; ++ai) { if (!_cell_vetoes_teleport(*ai)) { where = *ai; break; } else { if (monster_at(*ai) && move_monsters && !_cell_vetoes_teleport(*ai, false)) { monster* mons = monster_at(*ai); mons->teleport(true); where = *ai; break; } } } // Give up, we can't find a suitable spot. if (where == old_where) return false; } } // If we got this far, we're teleporting the player. // Leave a purple cloud. if (!cell_is_solid(old_pos)) place_cloud(CLOUD_TLOC_ENERGY, old_pos, 1 + random2(3), &you); bool large_change = you.see_cell(where); move_player_to_grid(where, false); _handle_teleport_update(large_change, old_pos); return true; } void you_teleport_now(bool allow_control, bool wizard_tele, int range) { const bool randtele = _teleport_player(allow_control, wizard_tele, range); // Xom is amused by uncontrolled teleports that land you in a // dangerous place, unless the player is in the Abyss and // teleported to escape from all the monsters chasing him/her, // since in that case the new dangerous area is almost certainly // *less* dangerous than the old dangerous area. // Teleporting in a labyrinth is also funny, more so for non-minotaurs. if (randtele && (player_in_branch(BRANCH_LABYRINTH) || !player_in_branch(BRANCH_ABYSS) && player_in_a_dangerous_place())) { xom_is_stimulated(200); } } spret_type cast_portal_projectile(int pow, bool fail) { fail_check(); if (!you.duration[DUR_PORTAL_PROJECTILE]) mpr("You begin teleporting projectiles to their destination."); else mpr("You renew your portal."); // Calculate the accuracy bonus based on current spellpower. you.attribute[ATTR_PORTAL_PROJECTILE] = pow; you.increase_duration(DUR_PORTAL_PROJECTILE, 3 + random2(pow / 2) + random2(pow / 5), 50); return SPRET_SUCCESS; } spret_type cast_apportation(int pow, bolt& beam, bool fail) { const coord_def where = beam.target; if (!cell_see_cell(you.pos(), where, LOS_SOLID)) { mpr("There's something in the way!"); return SPRET_ABORT; } // Letting mostly-melee characters spam apport after every Shoals // fight seems like it has too much grinding potential. We could // weaken this for high power. if (grd(where) == DNGN_DEEP_WATER || grd(where) == DNGN_LAVA) { mpr("The density of the terrain blocks your spell."); return SPRET_ABORT; } // Let's look at the top item in that square... // And don't allow apporting from shop inventories. const int item_idx = igrd(where); if (item_idx == NON_ITEM || !in_bounds(where)) { mpr("There are no items there."); return SPRET_ABORT; } item_def& item = mitm[item_idx]; // Nets can be apported when they have a victim trapped. if (item_is_stationary(item) && !item_is_stationary_net(item)) { mpr("You cannot apport that!"); return SPRET_ABORT; } // Can't apport the Orb in zotdef or sprint if (item_is_orb(item) && (crawl_state.game_is_zotdef() || crawl_state.game_is_sprint())) { mpr("You cannot apport the Orb!"); return SPRET_ABORT; } fail_check(); // We need to modify the item *before* we move it, because // move_top_item() might change the location, or merge // with something at our position. if (item_is_orb(item)) { fake_noisy(30, where); // There's also a 1-in-3 flat chance of apport failing. if (one_chance_in(3)) { orb_pickup_noise(where, 30, "The orb shrieks and becomes a dead weight against your magic!", "The orb lets out a furious burst of light and becomes " "a dead weight against your magic!"); return SPRET_SUCCESS; } else // Otherwise it's just a noisy little shiny thing { orb_pickup_noise(where, 30, "The orb shrieks as your magic touches it!", "The orb lets out a furious burst of light as your magic touches it!"); } } // If we apport a net, free the monster under it. if (item_is_stationary_net(item)) { free_stationary_net(item_idx); if (monster* mons = monster_at(where)) mons->del_ench(ENCH_HELD, true); } // Heavy items require more power to apport directly to your feet. // They might end up just moving a few squares, depending on spell // power and item mass. beam.is_tracer = true; beam.aimed_at_spot = true; beam.affects_nothing = true; beam.fire(); // Pop the item's location off the end beam.path_taken.pop_back(); // The actual number of squares it needs to traverse to get to you. int dist = beam.path_taken.size(); // The maximum number of squares the item will actually move, always // at least one square. int max_dist = max(pow * 2 / 5, 1); dprf("Apport dist=%d, max_dist=%d", dist, max_dist); int location_on_path = max(-1, dist - max_dist); // Don't move mimics under you. if ((item.flags & ISFLAG_MIMIC) && location_on_path == -1) location_on_path = 0; coord_def new_spot; if (location_on_path == -1) new_spot = you.pos(); else new_spot = beam.path_taken[location_on_path]; // Try to find safe terrain for the item. while (location_on_path < dist) { if (!feat_virtually_destroys_item(grd(new_spot), item)) break; location_on_path++; new_spot = beam.path_taken[location_on_path]; } if (location_on_path == dist) { mpr("Not with that terrain in the way!"); return SPRET_SUCCESS; } dprf("Apport: new spot is %d/%d", new_spot.x, new_spot.y); // Actually move the item. mprf("Yoink! You pull the item%s towards yourself.", (item.quantity > 1) ? "s" : ""); move_top_item(where, new_spot); // Mark the item as found now. origin_set(new_spot); return SPRET_SUCCESS; } static bool _quadrant_blink(coord_def dir, int pow) { if (pow > 100) pow = 100; const int dist = random2(6) + 2; // 2-7 // This is where you would *like* to go. const coord_def base = you.pos() + dir * dist; // This can take a while if pow is high and there's lots of translucent // walls nearby. coord_def target; bool found = false; for (int i = 0; i < pow*pow / 500 + 1; ++i) { // Find a space near our base point... // First try to find a random square not adjacent to the basepoint, // then one adjacent if that fails. if (!random_near_space(&you, base, target) && !random_near_space(&you, base, target, true)) { // Uh oh, WHY should this fail the blink? return false; } // ... which is close enough, but also far enough from us. if (distance2(base, target) > 10 || distance2(you.pos(), target) < 8) continue; if (!you.see_cell_no_trans(target)) continue; found = true; break; } if (!found) { // We've already succeeded at blinking, so the Abyss shouldn't block it. random_blink(false, true); return true; } coord_def origin = you.pos(); move_player_to_grid(target, false); // Leave a purple cloud. if (!cell_is_solid(origin)) place_cloud(CLOUD_TLOC_ENERGY, origin, 1 + random2(3), &you); return true; } spret_type cast_semi_controlled_blink(int pow, bool cheap_cancel, bool end_ctele, bool fail) { dist bmove; direction_chooser_args args; args.restricts = DIR_DIR; args.mode = TARG_ANY; while (1) { mprf(MSGCH_PROMPT, "Which direction? [ESC to cancel]"); direction(bmove, args); if (crawl_state.seen_hups) { mpr("Cancelling blink due to HUP."); return SPRET_ABORT; } if (bmove.isValid && !bmove.delta.origin()) break; if (cheap_cancel || yesno("Are you sure you want to cancel this blink?", false ,'n')) { canned_msg(MSG_OK); return SPRET_ABORT; } } fail_check(); // Note: this can silently fail, eating the blink -- WHY? if (you.attempt_escape(2) && _quadrant_blink(bmove.delta, pow)) { // Controlled blink causes glowing. contaminate_player(1000, true); // End teleport control if this was a random blink upgraded by cTele. if (end_ctele && you.duration[DUR_CONTROL_TELEPORT]) { mprf(MSGCH_DURATION, "You feel uncertain."); you.duration[DUR_CONTROL_TELEPORT] = 0; } } return SPRET_SUCCESS; } spret_type cast_golubrias_passage(const coord_def& where, bool fail) { // randomize position a bit to make it not as useful to use on monsters // chasing you, as well as to not give away hidden trap positions int tries = 0; int tries2 = 0; coord_def randomized_where = where; coord_def randomized_here = you.pos(); do { tries++; randomized_where = where; randomized_where.x += random_range(-2, 2); randomized_where.y += random_range(-2, 2); } while ((!in_bounds(randomized_where) || grd(randomized_where) != DNGN_FLOOR || monster_at(randomized_where) || !you.see_cell(randomized_where) || you.trans_wall_blocking(randomized_where) || randomized_where == you.pos()) && tries < 100); do { tries2++; randomized_here = you.pos(); randomized_here.x += random_range(-2, 2); randomized_here.y += random_range(-2, 2); } while ((!in_bounds(randomized_here) || grd(randomized_here) != DNGN_FLOOR || monster_at(randomized_here) || !you.see_cell(randomized_here) || you.trans_wall_blocking(randomized_here) || randomized_here == you.pos() || randomized_here == randomized_where) && tries2 < 100); if (tries >= 100 || tries2 >= 100) { if (you.trans_wall_blocking(randomized_where)) mpr("You cannot create a passage on the other side of the transparent wall."); else // XXX: bleh, dumb message mpr("Creating passages of Golubria requires sufficient empty space."); return SPRET_ABORT; } if (!allow_control_teleport(true) || testbits(env.pgrid(randomized_where), FPROP_NO_CTELE_INTO) || testbits(env.pgrid(randomized_here), FPROP_NO_CTELE_INTO)) { mpr("A powerful magic interferes with the creation of the passage."); return SPRET_ABORT; } fail_check(); place_specific_trap(randomized_where, TRAP_GOLUBRIA); place_specific_trap(randomized_here, TRAP_GOLUBRIA); env.level_state |= LSTATE_GOLUBRIA; trap_def *trap = find_trap(randomized_where); trap_def *trap2 = find_trap(randomized_here); if (!trap || !trap2) { mpr("Something buggy happened."); return SPRET_ABORT; } trap->reveal(); trap2->reveal(); return SPRET_SUCCESS; }