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