1 /**
2 * @file
3 * @brief Movement, open-close door commands, movement effects.
4 **/
5
6 #include <algorithm>
7 #include <cstring>
8 #include <string>
9 #include <sstream>
10
11 #include "AppHdr.h"
12
13 #include "movement.h"
14
15 #include "abyss.h"
16 #include "art-enum.h"
17 #include "bloodspatter.h"
18 #include "cloud.h"
19 #include "coord.h"
20 #include "coordit.h"
21 #include "delay.h"
22 #include "directn.h"
23 #include "dungeon.h"
24 #include "english.h" // walk_verb_to_present
25 #include "env.h"
26 #include "fight.h"
27 #include "fprop.h"
28 #include "god-abil.h"
29 #include "god-conduct.h"
30 #include "god-passive.h"
31 #include "items.h"
32 #include "message.h"
33 #include "mon-act.h"
34 #include "mon-behv.h"
35 #include "mon-death.h"
36 #include "mon-place.h"
37 #include "mon-util.h"
38 #include "nearby-danger.h"
39 #include "player.h"
40 #include "player-reacts.h"
41 #include "prompt.h"
42 #include "random.h"
43 #include "religion.h"
44 #include "shout.h"
45 #include "state.h"
46 #include "stringutil.h"
47 #include "spl-damage.h"
48 #include "target-compass.h"
49 #include "terrain.h"
50 #include "traps.h"
51 #include "travel.h"
52 #include "travel-open-doors-type.h"
53 #include "transform.h"
54 #include "xom.h" // XOM_CLOUD_TRAIL_TYPE_KEY
55
56 static void _apply_move_time_taken(int additional_time_taken = 0);
57
58 // Swap monster to this location. Player is swapped elsewhere.
59 // Moves the monster into position, but does not move the player
60 // or apply location effects: the latter should happen after the
61 // player is moved.
_swap_places(monster * mons,const coord_def & loc)62 static void _swap_places(monster* mons, const coord_def &loc)
63 {
64 ASSERT(map_bounds(loc));
65 ASSERT(monster_habitable_grid(mons, env.grid(loc)));
66
67 if (monster_at(loc))
68 {
69 mpr("Something prevents you from swapping places.");
70 return;
71 }
72
73 // Friendly foxfire dissipates instead of damaging the player.
74 if (mons->type == MONS_FOXFIRE)
75 {
76 simple_monster_message(*mons, " dissipates!",
77 MSGCH_MONSTER_DAMAGE, MDAM_DEAD);
78 monster_die(*mons, KILL_DISMISSED, NON_MONSTER, true);
79 return;
80 }
81
82 mpr("You swap places.");
83
84 mons->move_to_pos(loc, true, true);
85
86 return;
87 }
88
89 // Check squares adjacent to player for given feature and return how
90 // many there are. If there's only one, return the dx and dy.
_check_adjacent(dungeon_feature_type feat,coord_def & delta)91 static int _check_adjacent(dungeon_feature_type feat, coord_def& delta)
92 {
93 int num = 0;
94
95 set<coord_def> doors;
96 for (adjacent_iterator ai(you.pos(), true); ai; ++ai)
97 {
98 if (env.grid(*ai) == feat)
99 {
100 // Specialcase doors to take into account gates.
101 if (feat_is_door(feat))
102 {
103 // Already included in a gate, skip this door.
104 if (doors.count(*ai))
105 continue;
106
107 // Check if it's part of a gate. If so, remember all its doors.
108 set<coord_def> all_door;
109 find_connected_identical(*ai, all_door);
110 doors.insert(begin(all_door), end(all_door));
111 }
112
113 num++;
114 delta = *ai - you.pos();
115 }
116 }
117
118 return num;
119 }
120
_entered_malign_portal(actor * act)121 static void _entered_malign_portal(actor* act)
122 {
123 ASSERT(act); // XXX: change to actor &act
124 if (you.can_see(*act))
125 {
126 mprf("%s %s twisted violently and ejected from the portal!",
127 act->name(DESC_THE).c_str(), act->conj_verb("be").c_str());
128 }
129
130 act->blink();
131 act->hurt(nullptr, roll_dice(2, 4), BEAM_MISSILE, KILLED_BY_WILD_MAGIC,
132 "", "entering a malign gateway");
133 }
134
_cancel_barbed_move(bool rampaging)135 static bool _cancel_barbed_move(bool rampaging)
136 {
137 if (you.duration[DUR_BARBS] && !you.props.exists(BARBS_MOVE_KEY))
138 {
139 std::string prompt = "The barbs in your skin will harm you if you move.";
140 prompt += rampaging ? " Rampaging like this could really hurt!" : "";
141 prompt += " Continue?";
142 if (!yesno(prompt.c_str(), false, 'n'))
143 {
144 canned_msg(MSG_OK);
145 return true;
146 }
147
148 you.props[BARBS_MOVE_KEY] = true;
149 }
150
151 return false;
152 }
153
apply_barbs_damage(bool rampaging)154 void apply_barbs_damage(bool rampaging)
155 {
156 if (you.duration[DUR_BARBS])
157 {
158 mprf(MSGCH_WARN, "The barbed spikes dig painfully into your body "
159 "as you move.");
160 ouch(roll_dice(2, you.attribute[ATTR_BARBS_POW]), KILLED_BY_BARBS);
161 bleed_onto_floor(you.pos(), MONS_PLAYER, 2, false);
162
163 // Sometimes decrease duration even when we move.
164 if (one_chance_in(3))
165 extract_manticore_spikes("The barbed spikes snap loose.");
166 // But if that failed to end the effect, duration stays the same.
167 if (you.duration[DUR_BARBS])
168 you.duration[DUR_BARBS] += (rampaging ? 0 : you.time_taken);
169 }
170 }
171
_cancel_ice_move()172 static bool _cancel_ice_move()
173 {
174 vector<string> effects;
175 if (i_feel_safe(false, true, true))
176 return false;
177
178 if (you.duration[DUR_ICY_ARMOUR])
179 effects.push_back("icy armour");
180
181 if (you.duration[DUR_FROZEN_RAMPARTS])
182 effects.push_back("frozen ramparts");
183
184 if (!effects.empty())
185 {
186 string prompt = "Your "
187 + comma_separated_line(effects.begin(), effects.end())
188 + " will break if you move. Continue?";
189
190 if (!yesno(prompt.c_str(), false, 'n'))
191 {
192 canned_msg(MSG_OK);
193 return true;
194 }
195 }
196
197 return false;
198 }
199
cancel_harmful_move(bool physically,bool rampaging)200 bool cancel_harmful_move(bool physically, bool rampaging)
201 {
202 return physically ? (_cancel_barbed_move(rampaging) || _cancel_ice_move())
203 : _cancel_ice_move();
204 }
205
remove_ice_movement()206 void remove_ice_movement()
207 {
208 if (you.duration[DUR_ICY_ARMOUR])
209 {
210 mprf(MSGCH_DURATION, "Your icy armour cracks and falls away as "
211 "you move.");
212 you.duration[DUR_ICY_ARMOUR] = 0;
213 you.redraw_armour_class = true;
214 }
215
216 if (you.duration[DUR_FROZEN_RAMPARTS])
217 {
218 you.duration[DUR_FROZEN_RAMPARTS] = 0;
219 end_frozen_ramparts();
220 mprf(MSGCH_DURATION, "The frozen ramparts melt away as you move.");
221 }
222 }
223
water_hold_substance()224 string water_hold_substance()
225 {
226 return you.props["water_hold_substance"].get_string();
227 }
228
remove_water_hold()229 void remove_water_hold()
230 {
231 if (you.duration[DUR_WATER_HOLD])
232 {
233 mprf("You slip free of the %s engulfing you.",
234 water_hold_substance().c_str());
235 you.clear_far_engulf();
236 }
237 }
238
_clear_constriction_data()239 static void _clear_constriction_data()
240 {
241 you.stop_directly_constricting_all(true);
242 if (you.is_directly_constricted())
243 you.stop_being_constricted();
244 }
245
apply_cloud_trail(const coord_def old_pos)246 bool apply_cloud_trail(const coord_def old_pos)
247 {
248 if (you.duration[DUR_CLOUD_TRAIL])
249 {
250 if (cell_is_solid(old_pos))
251 ASSERT(you.wizmode_teleported_into_rock);
252 else
253 {
254 auto cloud = static_cast<cloud_type>(
255 you.props[XOM_CLOUD_TRAIL_TYPE_KEY].get_int());
256 ASSERT(cloud != CLOUD_NONE);
257 check_place_cloud(cloud, old_pos, random_range(3, 10), &you,
258 0, -1);
259 return true;
260 }
261 }
262 return false;
263 }
264
cancel_confused_move(bool stationary)265 bool cancel_confused_move(bool stationary)
266 {
267 dungeon_feature_type dangerous = DNGN_FLOOR;
268 monster *bad_mons = 0;
269 string bad_suff, bad_adj;
270 bool penance = false;
271 bool flight = false;
272 for (adjacent_iterator ai(you.pos(), false); ai; ++ai)
273 {
274 if (!stationary
275 && is_feat_dangerous(env.grid(*ai), true)
276 && need_expiration_warning(env.grid(*ai))
277 && (dangerous == DNGN_FLOOR || env.grid(*ai) == DNGN_LAVA))
278 {
279 dangerous = env.grid(*ai);
280 if (need_expiration_warning(DUR_FLIGHT, env.grid(*ai)))
281 flight = true;
282 break;
283 }
284 else
285 {
286 string suffix, adj;
287 monster *mons = monster_at(*ai);
288 if (mons
289 && (stationary
290 || !(is_sanctuary(you.pos()) && is_sanctuary(mons->pos()))
291 && !fedhas_passthrough(mons))
292 && bad_attack(mons, adj, suffix, penance)
293 && mons->angered_by_attacks())
294 {
295 bad_mons = mons;
296 bad_suff = suffix;
297 bad_adj = adj;
298 if (penance)
299 break;
300 }
301 }
302 }
303
304 if (dangerous != DNGN_FLOOR || bad_mons)
305 {
306 string prompt = "";
307 prompt += "Are you sure you want to ";
308 prompt += !stationary ? "stumble around" : "swing wildly";
309 prompt += " while confused and next to ";
310
311 if (dangerous != DNGN_FLOOR)
312 {
313 prompt += (dangerous == DNGN_LAVA ? "lava" : "deep water");
314 prompt += flight ? " while you are losing your buoyancy"
315 : " while your transformation is expiring";
316 }
317 else
318 {
319 string name = bad_mons->name(DESC_PLAIN);
320 if (starts_with(name, "the "))
321 name.erase(0, 4);
322 if (!starts_with(bad_adj, "your"))
323 bad_adj = "the " + bad_adj;
324 prompt += bad_adj + name + bad_suff;
325 }
326 prompt += "?";
327
328 if (penance)
329 prompt += " This could place you under penance!";
330
331 if (!crawl_state.disables[DIS_CONFIRMATIONS]
332 && !yesno(prompt.c_str(), false, 'n'))
333 {
334 canned_msg(MSG_OK);
335 return true;
336 }
337 }
338
339 return false;
340 }
341
342 // Opens doors.
343 // If move is !::origin, it carries a specific direction for the
344 // door to be opened (eg if you type ctrl + dir).
open_door_action(coord_def move)345 void open_door_action(coord_def move)
346 {
347 ASSERT(!crawl_state.game_is_arena());
348 ASSERT(!crawl_state.arena_suspended);
349
350 if (you.attribute[ATTR_HELD])
351 {
352 free_self_from_net();
353 you.turn_is_over = true;
354 return;
355 }
356
357 if (you.confused())
358 {
359 canned_msg(MSG_TOO_CONFUSED);
360 return;
361 }
362
363 coord_def delta;
364
365 // The player hasn't picked a direction yet.
366 if (move.origin())
367 {
368 const int num = _check_adjacent(DNGN_CLOSED_DOOR, move)
369 + _check_adjacent(DNGN_CLOSED_CLEAR_DOOR, move)
370 + _check_adjacent(DNGN_RUNED_DOOR, move)
371 + _check_adjacent(DNGN_RUNED_CLEAR_DOOR, move);
372
373 if (num == 0)
374 {
375 mpr("There's nothing to open nearby.");
376 return;
377 }
378
379 // If there's only one door to open, don't ask.
380 if (num == 1 && Options.easy_door)
381 delta = move;
382 else
383 {
384 delta = prompt_compass_direction();
385 if (delta.origin())
386 return;
387 }
388 }
389 else
390 delta = move;
391
392 // We got a valid direction.
393 const coord_def doorpos = you.pos() + delta;
394
395 if (door_vetoed(doorpos))
396 {
397 // Allow doors to be locked.
398 const string door_veto_message = env.markers.property_at(doorpos,
399 MAT_ANY,
400 "veto_reason");
401 if (door_veto_message.empty())
402 mpr("The door is shut tight!");
403 else
404 mpr(door_veto_message);
405 if (you.confused())
406 you.turn_is_over = true;
407
408 return;
409 }
410
411 const dungeon_feature_type feat = (in_bounds(doorpos) ? env.grid(doorpos)
412 : DNGN_UNSEEN);
413 switch (feat)
414 {
415 case DNGN_CLOSED_DOOR:
416 case DNGN_CLOSED_CLEAR_DOOR:
417 case DNGN_RUNED_DOOR:
418 case DNGN_RUNED_CLEAR_DOOR:
419 player_open_door(doorpos);
420 break;
421 case DNGN_OPEN_DOOR:
422 case DNGN_OPEN_CLEAR_DOOR:
423 {
424 string door_already_open = "";
425 if (in_bounds(doorpos))
426 {
427 door_already_open = env.markers.property_at(doorpos, MAT_ANY,
428 "door_verb_already_open");
429 }
430
431 if (!door_already_open.empty())
432 mpr(door_already_open);
433 else
434 mpr("It's already open!");
435 break;
436 }
437 case DNGN_SEALED_DOOR:
438 case DNGN_SEALED_CLEAR_DOOR:
439 mpr("That door is sealed shut!");
440 break;
441 default:
442 mpr("There isn't anything that you can open there!");
443 break;
444 }
445 }
446
close_door_action(coord_def move)447 void close_door_action(coord_def move)
448 {
449 if (you.attribute[ATTR_HELD])
450 {
451 mprf("You can't close doors while %s.", held_status());
452 return;
453 }
454
455 if (you.confused())
456 {
457 canned_msg(MSG_TOO_CONFUSED);
458 return;
459 }
460
461 coord_def delta;
462
463 if (move.origin())
464 {
465 // If there's only one door to close, don't ask.
466 int num = _check_adjacent(DNGN_OPEN_DOOR, move)
467 + _check_adjacent(DNGN_OPEN_CLEAR_DOOR, move);
468 if (num == 0)
469 {
470 mpr("There's nothing to close nearby.");
471 return;
472 }
473 // move got set in _check_adjacent
474 else if (num == 1 && Options.easy_door)
475 delta = move;
476 else
477 {
478 delta = prompt_compass_direction();
479 if (delta.origin())
480 return;
481 }
482 }
483 else
484 delta = move;
485
486 const coord_def doorpos = you.pos() + delta;
487 const dungeon_feature_type feat = (in_bounds(doorpos) ? env.grid(doorpos)
488 : DNGN_UNSEEN);
489
490 switch (feat)
491 {
492 case DNGN_OPEN_DOOR:
493 case DNGN_OPEN_CLEAR_DOOR:
494 player_close_door(doorpos);
495 break;
496 case DNGN_CLOSED_DOOR:
497 case DNGN_CLOSED_CLEAR_DOOR:
498 case DNGN_RUNED_DOOR:
499 case DNGN_RUNED_CLEAR_DOOR:
500 case DNGN_SEALED_DOOR:
501 case DNGN_SEALED_CLEAR_DOOR:
502 mpr("It's already closed!");
503 break;
504 default:
505 mpr("There isn't anything that you can close there!");
506 break;
507 }
508 }
509
510 // Maybe prompt to enter a portal, return true if we should enter the
511 // portal, false if the user said no at the prompt.
prompt_dangerous_portal(dungeon_feature_type ftype)512 bool prompt_dangerous_portal(dungeon_feature_type ftype)
513 {
514 switch (ftype)
515 {
516 case DNGN_ENTER_PANDEMONIUM:
517 case DNGN_ENTER_ABYSS:
518 return yesno("If you enter this portal you might not be able to return "
519 "immediately. Continue?", false, 'n');
520
521 case DNGN_MALIGN_GATEWAY:
522 return yesno("Are you sure you wish to approach this portal? There's no "
523 "telling what its forces would wreak upon your fragile "
524 "self.", false, 'n');
525
526 default:
527 return true;
528 }
529 }
530
531 /**
532 * Rampages the player toward a hostile monster, if one exists in the direction
533 * of the move input. Invalid things along the rampage path cancel the rampage.
534 *
535 * @param move A relative coord_def of the player's CMD_MOVE input,
536 * as called by move_player_action().
537 * @return spret::fail if something invalid prevented the rampage,
538 * spret::abort if a player prompt response should cancel the move
539 * entirely,
540 * spret::success if the rampage occurred.
541 */
_rampage_forward(coord_def move)542 static spret _rampage_forward(coord_def move)
543 {
544 ASSERT(!crawl_state.game_is_arena());
545
546 // Assert if the requested move is not a move delta
547 // this would throw off our tracer_target.
548 ASSERT(abs(move.x) <= 1 && abs(move.y) <= 1);
549
550 if (crawl_state.is_replaying_keys())
551 {
552 crawl_state.cancel_cmd_all("You can't repeat rampage.");
553 return spret::abort;
554 }
555
556 // Don't rampage if the player has status effects that should prevent it:
557 // fungusform + terrified, confusion, immobile (tree)form, or constricted.
558 if (you.is_nervous()
559 || you.confused()
560 || you.is_stationary()
561 || you.is_constricted())
562 {
563 return spret::fail;
564 }
565
566
567 // This logic assumes that the relative coord_def move is from [-1,1].
568 // If the move_player_action() calls are ever rewritten in a way that
569 // breaks this assumption, these targeters will need to be updated.
570 const int tracer_range = you.current_vision;
571 const coord_def tracer_target = you.pos() + (move * tracer_range);
572
573 // Setup the rampage tracer beam.
574 bolt beam;
575 beam.range = LOS_RADIUS;
576 beam.aimed_at_spot = true;
577 beam.target = tracer_target;
578 beam.name = "rampaging";
579 beam.source_name = "you";
580 beam.source = you.pos();
581 beam.source_id = MID_PLAYER;
582 beam.thrower = KILL_YOU;
583 // The rampage reposition is explicitly noiseless for stab synergy.
584 // Its ensuing move or attack action will generate a normal amount of noise.
585 beam.loudness = 0;
586 beam.pierce = true;
587 beam.affects_nothing = true;
588 beam.is_tracer = true;
589 // is_targeting prevents bolt::do_fire() from interrupting with a prompt,
590 // if our tracer crosses something that blocks line of fire.
591 beam.is_targeting = true;
592 beam.fire();
593
594 monster* valid_target = nullptr;
595
596 // Iterate the tracer to see if the first visible target is a hostile mons.
597 for (coord_def p : beam.path_taken)
598 {
599 // Don't rampage without direct visibility to the target tile.
600 if (!you.see_cell_no_trans(p))
601 return spret::fail;
602
603 monster* mon = monster_at(p);
604 // Check for a plausible target at this cell.
605 // If there's no monster, a Fedhas ally, or an invis monster,
606 // perform terrain checks and if they pass keep going.
607 if (!mon
608 || fedhas_passthrough(mon)
609 || !you.can_see(*mon))
610 {
611 // Don't rampage if our tracer path is broken by something we can't
612 // safely pass through before it reaches a monster.
613 if (!you.can_pass_through(p) || is_feat_dangerous(env.grid(p)))
614 return spret::fail;
615 continue;
616 }
617 // Don't rampage if the closest mons is non-hostile or a (non-Fedhas) plant.
618 else if (mon && (mon->friendly()
619 || mon->neutral()
620 || mons_is_firewood(*mon)))
621 {
622 return spret::fail;
623 }
624 // Okay, the first mons along the tracer is a valid target.
625 // Don't need terrain checks because we'll attack the mons.
626 else if (mon)
627 {
628 valid_target = mon;
629 break;
630 }
631 }
632 if (!valid_target)
633 return spret::fail;
634
635 const bool enhanced = player_equip_unrand(UNRAND_SEVEN_LEAGUE_BOOTS);
636 const int rampage_distance = enhanced
637 ? grid_distance(you.pos(), valid_target->pos()) - 1
638 : 1;
639
640 const coord_def rampage_destination = you.pos() + (move * rampage_distance);
641 const coord_def rampage_target = you.pos() + (move * (rampage_distance + 1));
642
643 // Reset the beam target to the actual rampage_destination distance.
644 beam.target = rampage_destination;
645
646 // Will the second move be an attack?
647 const bool attacking = valid_target->pos() == rampage_target;
648
649 // Don't rampage if the player's tile is being targeted, somehow.
650 if (beam.target == you.pos())
651 return spret::fail;
652
653 // Beholder/fearmonger messaging is handled by the movement code,
654 // so we return fail even though the move will ultimately be aborted.
655 // Rampaging within (up to the edge) of the allowed range is permitted.
656 // Don't rampage if it would take us away from a beholder.
657 const monster* beholder = you.get_beholder(beam.target);
658 if (beholder)
659 return spret::fail;
660
661 // Don't rampage if it would take us toward a fearmonger.
662 const monster* fearmonger = you.get_fearmonger(beam.target);
663 if (fearmonger)
664 return spret::fail;
665
666 // Do allow rampaging on top of Fedhas plants,
667 const monster* mons = monster_at(beam.target);
668 bool fedhas_move = false;
669 if (mons && fedhas_passthrough(mons))
670 fedhas_move = true;
671 // but otherwise, don't rampage if it would land us on top of a monster,
672 else if (mons)
673 {
674 if (!you.can_see(*mons))
675 {
676 // .. and if a mons was in the way and invisible, notify the player.
677 clear_messages();
678 mpr("Something unexpectedly blocked you, preventing you from rampaging!");
679 }
680 return spret::fail;
681 }
682
683 // Abort if the player answers no to
684 // * barbs damaging move prompt
685 // * breaking ice spells prompt
686 // * dangerous terrain/trap/cloud/exclusion prompt
687 // * weapon check prompts;
688 // messaging for this is handled by check_moveto().
689 if (!check_moveto(beam.target, "rampage")
690 || attacking && !wielded_weapon_check(you.weapon(), "rampage")
691 || !attacking && !check_moveto(rampage_target, "rampage"))
692 {
693 stop_running();
694 you.turn_is_over = false;
695 return spret::abort;
696 }
697
698 // We've passed the validity checks, go ahead and rampage.
699
700 // First, apply any necessary pre-move effects:
701 remove_water_hold();
702 _clear_constriction_data();
703 const coord_def old_pos = you.pos();
704
705 clear_messages();
706 const monster* current = monster_at(you.pos());
707 if (fedhas_move && (!current || !fedhas_passthrough(current)))
708 {
709 mprf("You %s quickly through the %s towards %s!",
710 enhanced ? "stride" : "rampage",
711 mons_genus(mons->type) == MONS_FUNGUS ? "fungus" : "plants",
712 valid_target->name(DESC_THE, true).c_str());
713 }
714 else
715 {
716 mprf("You %s towards %s!",
717 enhanced ? "stride" : "rampage",
718 valid_target->name(DESC_THE, true).c_str());
719 }
720
721 // stepped = true, we're flavouring this as movement, not a blink.
722 move_player_to_grid(beam.target, true);
723
724 // No full-LOS stabbing.
725 if (enhanced)
726 behaviour_event(valid_target, ME_ALERT, &you, you.pos());
727
728 // Lastly, apply post-move effects unhandled by move_player_to_grid().
729 apply_barbs_damage(true);
730 remove_ice_movement();
731 apply_cloud_trail(old_pos);
732
733 // If there is somehow an active run delay here, update the travel trail.
734 if (you_are_delayed() && current_delay()->is_run())
735 env.travel_trail.push_back(you.pos());
736
737 return spret::success;
738 }
739
_apply_move_time_taken(int additional_time_taken)740 static void _apply_move_time_taken(int additional_time_taken)
741 {
742 you.time_taken *= player_movement_speed();
743 you.time_taken = div_rand_round(you.time_taken, 10);
744 you.time_taken += additional_time_taken;
745
746 if (you.running && you.running.travel_speed)
747 {
748 you.time_taken = max(you.time_taken,
749 div_round_up(100, you.running.travel_speed));
750 }
751
752 if (you.duration[DUR_NO_HOP])
753 you.duration[DUR_NO_HOP] += you.time_taken;
754 }
755
756 // The "first square" of rampaging ordinarily has no time cost, and the "second
757 // square" is where its move delay or attack delay would be applied. If the
758 // player begins a rampage, and then cancels the second move, as through a
759 // prompt, we have to ensure they don't get zero-cost movement out of it. Here
760 // we apply movedelay, end the turn, and call relevant post-move effects.
_finalize_cancelled_rampage_move()761 static void _finalize_cancelled_rampage_move()
762 {
763 _apply_move_time_taken();
764 you.turn_is_over = true;
765
766 if (player_in_branch(BRANCH_ABYSS))
767 maybe_shift_abyss_around_player();
768
769 you.apply_berserk_penalty = true;
770
771 // Rampaging prevents Wu Jian attacks, so we do not process them
772 // here
773 update_acrobat_status();
774 }
775
776 // Called when the player moves by walking/running. Also calls attack
777 // function etc when necessary.
move_player_action(coord_def move)778 void move_player_action(coord_def move)
779 {
780 ASSERT(!crawl_state.game_is_arena() && !crawl_state.arena_suspended);
781
782 bool attacking = false;
783 bool moving = true; // used to prevent eventual movement (swap)
784 bool swap = false;
785
786 int additional_time_taken = 0; // Extra time independent of movement speed
787
788 ASSERT(!in_bounds(you.pos()) || !cell_is_solid(you.pos())
789 || you.wizmode_teleported_into_rock);
790
791 if (you.attribute[ATTR_HELD])
792 {
793 free_self_from_net();
794 you.turn_is_over = true;
795 return;
796 }
797
798 coord_def initial_position = you.pos();
799
800 // When confused, sometimes make a random move.
801 if (you.confused())
802 {
803 if (you.is_stationary())
804 {
805 // Don't choose a random location to try to attack into - allows
806 // abuse, since trying to move (not attack) takes no time, and
807 // shouldn't. Just force confused trees to use ctrl.
808 mpr("You cannot move. (Use ctrl+direction or * direction to "
809 "attack without moving.)");
810 return;
811 }
812
813 if (cancel_confused_move(false))
814 return;
815
816 if (cancel_harmful_move())
817 return;
818
819 if (!one_chance_in(3))
820 {
821 move.x = random2(3) - 1;
822 move.y = random2(3) - 1;
823 if (move.origin())
824 {
825 mpr("You're too confused to move!");
826 you.apply_berserk_penalty = true;
827 you.turn_is_over = true;
828 return;
829 }
830 }
831
832 const coord_def new_targ = you.pos() + move;
833 if (!in_bounds(new_targ) || !you.can_pass_through(new_targ))
834 {
835 you.turn_is_over = true;
836 if (you.digging) // no actual damage
837 {
838 mprf("Your mandibles retract as you bump into %s.",
839 feature_description_at(new_targ, false,
840 DESC_THE).c_str());
841 you.digging = false;
842 }
843 else
844 {
845 mprf("You bump into %s.",
846 feature_description_at(new_targ, false,
847 DESC_THE).c_str());
848 }
849 you.apply_berserk_penalty = true;
850 crawl_state.cancel_cmd_repeat();
851
852 return;
853 }
854 }
855
856 bool rampaged = false;
857
858 // Rampaging takes priority over normal Wu Jian movement, but not over
859 // Serpent's Lash.
860 if (you.rampaging() && !you.attribute[ATTR_SERPENTS_LASH])
861 {
862 switch (_rampage_forward(move))
863 {
864 // Check the player's position again; rampage may have moved us.
865
866 // Cancel the move entirely if rampage was aborted from a prompt.
867 case spret::abort:
868 ASSERT(!in_bounds(you.pos()) || !cell_is_solid(you.pos())
869 || you.wizmode_teleported_into_rock);
870 return;
871
872 case spret::success:
873 rampaged = true;
874 // If we've rampaged, reset initial_position for the second
875 // move.
876 initial_position = you.pos();
877 // intentional fallthrough
878 default:
879 case spret::fail:
880 ASSERT(!in_bounds(you.pos()) || !cell_is_solid(you.pos())
881 || you.wizmode_teleported_into_rock);
882 break;
883 }
884 }
885
886 const coord_def targ = you.pos() + move;
887 // You can't walk out of bounds!
888 if (!in_bounds(targ))
889 {
890 // Why isn't the border permarock?
891 if (you.digging)
892 {
893 mpr("This wall is too hard to dig through.");
894 you.digging = false;
895 }
896 return;
897 }
898
899 // XX generalize?
900 const string walkverb = you.airborne() ? "fly"
901 : you.swimming() ? "swim"
902 : you.form == transformation::spider ? "crawl"
903 : you.form != transformation::none ? "walk" // XX
904 : walk_verb_to_present(lowercase_first(species::walking_verb(you.species)));
905
906 monster* targ_monst = monster_at(targ);
907 if (fedhas_passthrough(targ_monst) && !you.is_stationary())
908 {
909 // Moving on a plant takes 1.5 x normal move delay. We
910 // will print a message about it but only when moving
911 // from open space->plant (hopefully this will cut down
912 // on the message spam).
913 you.time_taken = div_rand_round(you.time_taken * 3, 2);
914
915 monster* current = monster_at(you.pos());
916 if (!current || !fedhas_passthrough(current))
917 {
918 // Probably need a better message. -cao
919 mprf("You %s carefully through the %s.", walkverb.c_str(),
920 mons_genus(targ_monst->type) == MONS_FUNGUS ? "fungus"
921 : "plants");
922 }
923 targ_monst = nullptr;
924 }
925
926 bool targ_pass = you.can_pass_through(targ) && !you.is_stationary();
927
928 if (you.digging)
929 {
930 if (feat_is_diggable(env.grid(targ)))
931 targ_pass = true;
932 else // moving or attacking ends dig
933 {
934 you.digging = false;
935 if (feat_is_solid(env.grid(targ)))
936 mpr("You can't dig through that.");
937 else
938 mpr("You retract your mandibles.");
939 }
940 }
941
942 // You can swap places with a friendly or good neutral monster if
943 // you're not confused, or even with hostiles if both of you are inside
944 // a sanctuary.
945 const bool try_to_swap = targ_monst
946 && (targ_monst->wont_attack()
947 && !you.confused()
948 || is_sanctuary(you.pos())
949 && is_sanctuary(targ));
950
951 // You cannot move away from a siren but you CAN fight monsters on
952 // neighbouring squares.
953 monster* beholder = nullptr;
954 if (!you.confused())
955 beholder = you.get_beholder(targ);
956
957 // You cannot move closer to a fear monger.
958 monster *fmonger = nullptr;
959 if (!you.confused())
960 fmonger = you.get_fearmonger(targ);
961
962 if (!rampaged && you.running.check_stop_running())
963 {
964 // [ds] Do we need this? Shouldn't it be false to start with?
965 you.turn_is_over = false;
966 return;
967 }
968
969 coord_def mon_swap_dest;
970
971 if (targ_monst && !targ_monst->submerged())
972 {
973 if (try_to_swap && !beholder && !fmonger)
974 {
975 if (swap_check(targ_monst, mon_swap_dest))
976 swap = true;
977 else
978 {
979 stop_running();
980 moving = false;
981 }
982 }
983 else if (targ_monst->temp_attitude() == ATT_NEUTRAL && !you.confused()
984 && targ_monst->visible_to(&you))
985 {
986 simple_monster_message(*targ_monst, " refuses to make way for you. "
987 "(Use ctrl+direction or * direction to attack.)");
988 you.turn_is_over = false;
989 return;
990 }
991 else if (!try_to_swap) // attack!
992 {
993 // Don't allow the player to freely locate invisible monsters
994 // with confirmation prompts.
995 if (!you.can_see(*targ_monst) && you.is_stationary())
996 {
997 canned_msg(MSG_CANNOT_MOVE);
998 you.turn_is_over = false;
999 return;
1000 }
1001 // Rampaging forcibly initiates the attack, but the attack
1002 // can still be cancelled.
1003 if (!rampaged && !you.can_see(*targ_monst)
1004 && !you.confused()
1005 && !check_moveto(targ, walkverb)
1006 // Attack cancelled by fight_melee
1007 || !fight_melee(&you, targ_monst))
1008 {
1009 stop_running();
1010 // If we cancel this move after rampaging, we end the turn.
1011 if (rampaged)
1012 {
1013 move.reset();
1014 _finalize_cancelled_rampage_move();
1015 return;
1016 }
1017 you.turn_is_over = false;
1018 return;
1019 }
1020
1021 you.turn_is_over = true;
1022 you.berserk_penalty = 0;
1023 attacking = true;
1024 }
1025 }
1026 else if (you.form == transformation::fungus && moving && !you.confused())
1027 {
1028 if (you.is_nervous())
1029 {
1030 mpr("You're too terrified to move while being watched!");
1031 stop_running();
1032 you.turn_is_over = false;
1033 return;
1034 }
1035 }
1036
1037 const bool running = you_are_delayed() && current_delay()->is_run();
1038 bool dug = false;
1039
1040 if (!attacking && targ_pass && moving && !beholder && !fmonger)
1041 {
1042 if (you.confused() && is_feat_dangerous(env.grid(targ)))
1043 {
1044 mprf("You nearly stumble into %s!",
1045 feature_description_at(targ, false, DESC_THE).c_str());
1046 you.apply_berserk_penalty = true;
1047 you.turn_is_over = true;
1048 return;
1049 }
1050
1051 // If confused, we've already been prompted (in case of stumbling into
1052 // a monster and attacking instead).
1053 // If rampaging we've already been prompted.
1054 if (!you.confused() && !rampaged && !check_moveto(targ, walkverb))
1055 {
1056 stop_running();
1057 you.turn_is_over = false;
1058 return;
1059 }
1060
1061 if (!you.attempt_escape()) // false means constricted and did not escape
1062 return;
1063
1064 if (you.digging)
1065 {
1066 mprf("You dig through %s.", feature_description_at(targ, false,
1067 DESC_THE).c_str());
1068 destroy_wall(targ);
1069 noisy(6, you.pos());
1070 additional_time_taken += BASELINE_DELAY / 5;
1071 dug = true;
1072 }
1073
1074 if (swap)
1075 _swap_places(targ_monst, mon_swap_dest);
1076
1077 if (running && env.travel_trail.empty())
1078 env.travel_trail.push_back(you.pos());
1079 else if (!running)
1080 clear_travel_trail();
1081
1082 coord_def old_pos = you.pos();
1083 // Don't trigger things that require movement
1084 // when confusion causes no move.
1085 if (you.pos() != targ && targ_pass)
1086 {
1087 remove_water_hold();
1088 _clear_constriction_data();
1089 move_player_to_grid(targ, true);
1090 apply_barbs_damage();
1091 remove_ice_movement();
1092 apply_cloud_trail(old_pos);
1093 }
1094
1095 // Now it is safe to apply the swappee's location effects and add
1096 // trailing effects. Doing so earlier would allow e.g. shadow traps to
1097 // put a monster at the player's location.
1098 if (swap)
1099 targ_monst->apply_location_effects(targ);
1100
1101 if (you_are_delayed() && current_delay()->is_run())
1102 env.travel_trail.push_back(you.pos());
1103
1104 _apply_move_time_taken(additional_time_taken);
1105
1106 move.reset();
1107 you.turn_is_over = true;
1108 request_autopickup();
1109 }
1110
1111 // BCR - Easy doors single move
1112 if ((Options.travel_open_doors == travel_open_doors_type::open
1113 || !you.running)
1114 && !attacking && feat_is_closed_door(env.grid(targ)))
1115 {
1116 open_door_action(move);
1117 move.reset();
1118 return;
1119 }
1120 else if (!targ_pass && env.grid(targ) == DNGN_MALIGN_GATEWAY
1121 && !attacking && !you.is_stationary())
1122 {
1123 if (!crawl_state.disables[DIS_CONFIRMATIONS]
1124 && !prompt_dangerous_portal(env.grid(targ)))
1125 {
1126 // No rampage check because the portal blocks the
1127 // rampage tracer
1128 return;
1129 }
1130
1131 move.reset();
1132 you.turn_is_over = true;
1133
1134 _entered_malign_portal(&you);
1135 return;
1136 }
1137 else if (!targ_pass && !attacking)
1138 {
1139 // No rampage check here, since you can't rampage at walls
1140 if (you.is_stationary())
1141 canned_msg(MSG_CANNOT_MOVE);
1142 else if (env.grid(targ) == DNGN_OPEN_SEA)
1143 mpr("The ferocious winds and tides of the open sea thwart your progress.");
1144 else if (env.grid(targ) == DNGN_LAVA_SEA)
1145 mpr("The endless sea of lava is not a nice place.");
1146 else if (feat_is_tree(env.grid(targ)) && you_worship(GOD_FEDHAS))
1147 mpr("You cannot walk through the dense trees.");
1148
1149 stop_running();
1150 move.reset();
1151 you.turn_is_over = false;
1152 crawl_state.cancel_cmd_repeat();
1153 return;
1154 }
1155 else if (beholder && !attacking)
1156 {
1157 mprf("You cannot move away from %s!",
1158 beholder->name(DESC_THE).c_str());
1159 stop_running();
1160 // If this would have been the move after rampaging, we end the turn.
1161 if (rampaged)
1162 {
1163 move.reset();
1164 _finalize_cancelled_rampage_move();
1165 return;
1166 }
1167 you.turn_is_over = false;
1168 return;
1169 }
1170 else if (fmonger && !attacking)
1171 {
1172 mprf("You cannot move closer to %s!",
1173 fmonger->name(DESC_THE).c_str());
1174 stop_running();
1175 // If this would have been the move after rampaging, we end the turn.
1176 if (rampaged)
1177 {
1178 move.reset();
1179 _finalize_cancelled_rampage_move();
1180 return;
1181 }
1182 you.turn_is_over = false;
1183 return;
1184 }
1185
1186 if (you.running == RMODE_START)
1187 you.running = RMODE_CONTINUE;
1188
1189 if (player_in_branch(BRANCH_ABYSS))
1190 maybe_shift_abyss_around_player();
1191
1192 you.apply_berserk_penalty = !attacking;
1193
1194 if (rampaged || player_equip_unrand(UNRAND_LIGHTNING_SCALES))
1195 did_god_conduct(DID_HASTY, 1, true);
1196
1197 bool did_wu_jian_attack = false;
1198 if (you_worship(GOD_WU_JIAN) && !attacking && !dug && !rampaged)
1199 did_wu_jian_attack = wu_jian_post_move_effects(false, initial_position);
1200
1201 // If you actually moved you are eligible for amulet of the acrobat.
1202 if (!attacking && moving && !did_wu_jian_attack)
1203 update_acrobat_status();
1204 }
1205