1 // Hyperbolic Rogue - PC movement
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file pcmove.cpp
5  *  \brief PC movements
6  */
7 
8 #include "hyper.h"
9 
10 namespace hr {
11 
12 EX bool keepLightning = false;
13 
14 EX bool seenSevenMines = false;
15 
16 /** \brief have we been warned about the Haunted Woods? */
17 EX bool hauntedWarning;
18 
19 /** \brief is the Survivalist achievement still valid? have we received it? */
20 EX bool survivalist;
21 
fail_survivalist()22 EX void fail_survivalist() {
23   changes.value_set(survivalist, false);
24   }
25 
26 /** \brief last move was invisible */
27 EX bool invismove = false;
28 /** \brief last move was invisible due to Orb of Fish (thus Fish still see you)*/
29 EX bool invisfish = false;
30 
31 /** \brief if false, make the PC look in direction cwt.spin (after attack); otherwise, make them look the other direction (after move) */
32 EX bool flipplayer = true;
33 
34 /** \brief Cellwalker describing the single player. Also used temporarily in shmup and multiplayer modes. */
35 EX cellwalker cwt;
36 
singlepos()37 EX cell*& singlepos() { return cwt.at; }
singleused()38 EX inline bool singleused() { return !(shmup::on || multi::players > 1); }
39 
40 /** \brief should we center the screen on the PC? */
41 EX bool playermoved = true;
42 
43 /** \brief did the player cheat? how many times? */
44 EX int  cheater = 0;
45 
46 /** \brief lands visited -- unblock some modes */
47 EX bool landvisited[landtypes];
48 
49 EX int noiseuntil; // noise until the given turn
50 
createNoise(int t)51 EX void createNoise(int t) {
52   noiseuntil = max(noiseuntil, turncount+t);
53   invismove = false;
54   if(shmup::on) shmup::visibleFor(100 * t);
55   }
56 
57 #if HDR
58 enum eLastmovetype { lmSkip, lmMove, lmAttack, lmPush, lmTree, lmInstant };
59 extern eLastmovetype lastmovetype, nextmovetype;
60 
61 enum eForcemovetype { fmSkip, fmMove, fmAttack, fmInstant, fmActivate };
62 extern eForcemovetype forcedmovetype;
63 #endif
64 
65 EX namespace orbbull {
66   cell *prev[MAXPLAYER];
67   eLastmovetype prevtype[MAXPLAYER];
68   int count;
69 
is(cell * c1,cell * c2,cell * c3)70   bool is(cell *c1, cell *c2, cell *c3) {
71     int lp = neighborId(c2, c1);
72     int ln = neighborId(c2, c3);
73     return lp >= 0 && ln >= 0 && anglestraight(c2, lp, ln);
74     }
75 
gainBullPowers()76   EX void gainBullPowers() {
77     items[itOrbShield]++; orbused[itOrbShield] = true;
78     items[itOrbThorns]++; orbused[itOrbThorns] = true;
79     items[itOrbHorns]++; orbused[itOrbHorns] = true;
80     }
81 
check()82   EX void check() {
83     int cp = multi::cpid;
84     if(cp < 0 || cp >= MAXPLAYER) cp = 0;
85 
86     if(!items[itOrbBull]) {
87       prev[cp] = NULL;
88       return;
89       }
90 
91     bool seq = false;
92 
93     if(prev[cp] && prevtype[cp] == lmMove && lastmovetype == lmMove)
94       seq = is(prev[cp], lastmove, cwt.at);
95 
96     if(prev[cp] && prevtype[cp] == lmMove && lastmovetype == lmAttack)
97       seq = is(prev[cp], cwt.at, lastmove);
98 
99     if(prev[cp] && prevtype[cp] == lmAttack && lastmovetype == lmAttack && count)
100       seq = lastmove == prev[cp];
101 
102     if(prev[cp] && prevtype[cp] == lmAttack && lastmovetype == lmMove && count)
103       seq = cwt.at == prev[cp];
104 
105     prev[cp] = lastmove; prevtype[cp] = lastmovetype;
106 
107     if(seq) {
108       if(lastmovetype == lmMove) count++;
109       gainBullPowers();
110       }
111     else count = 0;
112     }
113 EX }
114 
checkNeedMove(bool checkonly,bool attacking)115 bool pcmove::checkNeedMove(bool checkonly, bool attacking) {
116   if(items[itOrbDomination] > ORBBASE && cwt.at->monst)
117     return false;
118   int flags = 0;
119   if(cwt.at->monst) {
120     if(vmsg(miRESTRICTED)) {
121       if(isMountable(cwt.at->monst))
122         addMessage(XLAT("You need to dismount %the1!", cwt.at->monst));
123       else
124         addMessage(XLAT("You need to move to give space to %the1!", cwt.at->monst));
125       }
126     }
127   else if(cwt.at->wall == waRoundTable) {
128     if(markOrb2(itOrbAether)) return false;
129     if(vmsg(miRESTRICTED))
130       addMessage(XLAT("It would be impolite to land on the table!"));
131     }
132   else if(cwt.at->wall == waLake) {
133     if(markOrb2(itOrbAether)) return false;
134     if(markOrb2(itOrbFish)) return false;
135     if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false;
136     flags |= AF_FALL;
137     if(vmsg(miWALL)) addMessage(XLAT("Ice below you is melting! RUN!"));
138     }
139   else if(!attacking && cellEdgeUnstable(cwt.at)) {
140     if(markOrb2(itOrbAether)) return false;
141     if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false;
142     if(vmsg(miRESTRICTED)) addMessage(XLAT("Nothing to stand on here!"));
143     return true;
144     }
145   else if(among(cwt.at->wall, waSea, waCamelotMoat, waLake, waDeepWater)) {
146     if(markOrb(itOrbFish)) return false;
147     if(markOrb2(itOrbAether)) return false;
148     if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false;
149     if(vmsg(miWALL)) addMessage(XLAT("You have to run away from the water!"));
150     }
151   else if(cwt.at->wall == waClosedGate) {
152     if(markOrb2(itOrbAether)) return false;
153     if(vmsg(miWALL)) addMessage(XLAT("The gate is closing right on you! RUN!"));
154     }
155   else if(isFire(cwt.at) && !markOrb(itOrbWinter) && !markOrb(itCurseWater) && !markOrb2(itOrbShield)) {
156     if(markOrb2(itOrbAether)) return false;
157     if(vmsg(miWALL)) addMessage(XLAT("This spot will be burning soon! RUN!"));
158     }
159   else if(cwt.at->wall == waMagma && !markOrb(itOrbWinter) && !markOrb(itCurseWater) && !markOrb2(itOrbShield)) {
160     if(markOrb2(itOrbAether)) return false;
161     if(in_gravity_zone(cwt.at) && passable(cwt.at, cwt.at, P_ISPLAYER)) return false;
162     if(vmsg(miWALL)) addMessage(XLAT("Run away from the magma!"));
163     }
164   else if(cwt.at->wall == waChasm) {
165     if(markOrb2(itOrbAether)) return false;
166     if(in_gravity_zone(cwt.at) && passable(cwt.at, cwt.at, P_ISPLAYER)) return false;
167     flags |= AF_FALL;
168     if(vmsg(miWALL)) addMessage(XLAT("The floor has collapsed! RUN!"));
169     }
170   else if(items[itOrbAether] > ORBBASE && !passable(cwt.at, NULL, P_ISPLAYER | P_NOAETHER)) {
171     if(markOrb2(itOrbAether)) return false;
172     vmsg(miWALL);
173     return true;
174     }
175   else if(!passable(cwt.at, NULL, P_ISPLAYER)) {
176     if(isFire(cwt.at)) return false; // already checked: have Shield
177     if(markOrb2(itOrbAether)) return false;
178     if(vmsg(miWALL)) addMessage(XLAT("Your Aether power has expired! RUN!"));
179     }
180   else return false;
181   if(hardcore && !checkonly)
182     killHardcorePlayer(multi::cpid, flags);
183   return true;
184   }
185 
186 EX cell *lastmove;
187 eLastmovetype lastmovetype, nextmovetype;
188 eForcemovetype forcedmovetype;
189 
190 #if HDR
191 struct pcmove {
192   bool switchplaces;
193   bool checkonly;
194   bool errormsgs;
195   int origd;
196   bool fmsMove, fmsAttack, fmsActivate;
197   int d;
198   int subdir;
199   bool boatmove;
200   bool good_tortoise;
201   flagtype attackflags;
202 
203   bool movepcto();
204   bool actual_move();
205   bool stay();
206   bool after_instant(bool kl);
207 
208   bool perform_actual_move();
209   bool after_move();
210   bool perform_move_or_jump();
211   bool swing();
212   bool boat_move();
213   bool after_escape();
214   bool move_if_okay();
215   bool attack();
216 
217   bool checkNeedMove(bool checkonly, bool attacking);
218 
219   void tell_why_cannot_attack();
220   void tell_why_impassable();
221   void handle_friendly_ivy();
222 
223   movei mi, mip;
pcmovehr::pcmove224   pcmove() : mi(nullptr, nullptr, 0), mip(nullptr, nullptr, 0) {}
225 
226   bool vmsg(int code);
227   };
228 #endif
229 
230 EX cell *global_pushto;
231 
vmsg(int code)232 bool pcmove::vmsg(int code) { checked_move_issue = code; changes.rollback(); return errormsgs && !checkonly; }
233 
234 EX bool movepcto(int d, int subdir IS(1), bool checkonly IS(false)) {
235   checked_move_issue = miVALID;
236   pcmove pcm;
237   pcm.checkonly = checkonly;
238   pcm.d = d; pcm.subdir = subdir;
239   auto b = pcm.movepcto();
240   global_pushto = pcm.mip.t;
241   return b;
242   }
243 
movepcto()244 bool pcmove::movepcto() {
245   if(dual::state == 1) return dual::movepc(d, subdir, checkonly);
246   if(d >= 0 && !checkonly && subdir != 1 && subdir != -1) printf("subdir = %d\n", subdir);
247   mip.t = NULL;
248   switchplaces = false;
249 
250   if(d == MD_USE_ORB)
251     return targetRangedOrb(multi::whereto[multi::cpid].tgt, roMultiGo);
252 
253   errormsgs = multi::players == 1 || multi::cpid == multi::players-1;
254   if(hardcore && !canmove) return false;
255   if(!checkonly && d >= 0) {
256     flipplayer = false;
257     if(multi::players > 1) multi::flipped[multi::cpid] = false;
258     }
259   DEBBI(checkonly ? 0 : DF_TURN, ("movepc"));
260   if(!checkonly) invismove = false;
261   boatmove = false;
262 
263   if(multi::players > 1)
264     lastmountpos[multi::cpid] = cwt.at;
265   else
266     lastmountpos[0] = cwt.at;
267 
268   if(againstRose(cwt.at, NULL) && d<0 && !scentResistant()) {
269     if(vmsg(miRESTRICTED))
270       addMessage("You just cannot stand in place, those roses smell too nicely.");
271     return false;
272     }
273 
274   gravity_state = gsNormal;
275 
276   fmsMove     = forcedmovetype == fmSkip || forcedmovetype == fmMove;
277   fmsAttack   = forcedmovetype == fmSkip || forcedmovetype == fmAttack;
278   fmsActivate = forcedmovetype == fmSkip || forcedmovetype == fmActivate;
279 
280   changes.init(checkonly);
281   bool b = (d >= 0) ? actual_move() : stay();
282   if(checkonly || !b) {
283     changes.rollback();
284     if(!checkonly) flipplayer = false;
285     }
286   else if(changes.on) {
287     println(hlog, "error: not commited!");
288     changes.commit();
289     }
290 
291   if(!b) {
292     // bool try_instant = (forcedmovetype == fmInstant) || (forcedmovetype == fmSkip && !passable(c2, cwt.at, P_ISPLAYER | P_MIRROR | P_USEBOAT | P_FRIENDSWAP));
293 
294     if(items[itOrbFlash]) {
295       if(checkonly) { nextmovetype = lmInstant; return true; }
296       if(orbProtection(itOrbFlash)) return true;
297       activateFlash();
298       checkmove();
299       return true;
300       }
301 
302     if(items[itOrbLightning]) {
303       if(checkonly) { nextmovetype = lmInstant; return true; }
304       if(orbProtection(itOrbLightning)) return true;
305       activateLightning();
306       checkmove();
307       return true;
308       }
309 
310     if(who_kills_me == moOutlaw && items[itRevolver] && !checkonly) {
311       cell *c2 = cwt.cpeek();
312       forCellEx(c3, c2) forCellEx(c4, c3) if(c4->monst == moOutlaw) {
313         eItem i = targetRangedOrb(c4, roCheck);
314         if(i == itRevolver) {
315           targetRangedOrb(c4, roKeyboard);
316           return true;
317           }
318         }
319       }
320     }
321 
322   return b;
323   }
324 
after_move()325 bool pcmove::after_move() {
326   if(checkonly) return true;
327 
328   invisfish = false;
329   if(items[itOrbFish]) {
330      invisfish = true;
331      for(cell *pc: player_positions())
332        if(!isWatery(pc))
333          invisfish = false;
334      if(d < 0) invisfish = false; // no invisibility if staying still
335      if(invisfish) invismove = true, markOrb(itOrbFish);
336      }
337 
338   last_gravity_state = gravity_state;
339   if(multi::players == 1) monstersTurn();
340 
341   save_memory();
342 
343   check_total_victory();
344 
345   if(items[itWhirlpool] && cwt.at->land != laWhirlpool)
346     achievement_gain_once("WHIRL1");
347 
348   if(items[itLotus] >= 25 && !isHaunted(cwt.at->land) && survivalist)
349     achievement_gain_once("SURVIVAL");
350 
351   if(seenSevenMines && cwt.at->land != laMinefield) {
352     changes.value_set(seenSevenMines, false);
353     achievement_gain("SEVENMINE");
354     }
355 
356   DEBB(DF_TURN, ("done"));
357   return true;
358   }
359 
swing()360 bool pcmove::swing() {
361   sideAttack(cwt.at, d, moPlayer, 0);
362 
363   mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK);
364 
365   if(monstersnear_add_pmi(movei(cwt.at, STAY))) {
366     if(vmsg(miTHREAT))
367       wouldkill("You would be killed by %the1!");
368     return false;
369     }
370   if(checkonly) return true;
371   if(changes.on) changes.commit();
372 
373   animateAttack(mi, LAYER_SMALL);
374   if(survivalist && isHaunted(mi.t->land))
375     survivalist = false;
376   lastmovetype = lmTree; lastmove = mi.t;
377   swordAttackStatic();
378 
379   return after_move();
380   }
381 
after_instant(bool kl)382 bool pcmove::after_instant(bool kl) {
383   changes.commit();
384   keepLightning = kl;
385   bfs();
386   keepLightning = false;
387   if(multi::players > 1) { multi::whereto[multi::cpid].d = MD_UNDECIDED; return false; }
388   checkmove();
389   return true;
390   }
391 
copy_metadata(cell * x,const gcell * y)392 EX void copy_metadata(cell *x, const gcell *y) {
393   x->wall = y->wall;
394   x->monst = y->monst;
395   x->item = y->item;
396   x->mondir = y->mondir;
397   x->stuntime = y->stuntime;
398   x->hitpoints = y->hitpoints;
399   x->monmirror = y->monmirror;
400   x->LHU = y->LHU;
401   if(isIcyLand(x)) {
402     x->landparam = y->landparam;
403     }
404   x->wparam = y->wparam;
405   }
406 
407 #if HDR
408 
409 extern void playSound(cell *c, const string& fname, int vol);
410 
411 /** \brief A structure to keep track of changes made during the player movement.
412  *
413  *  This is a singleton object, \link hr::changes \endlink.
414  */
415 
416 struct changes_t {
417   vector<reaction_t> rollbacks;
418   vector<reaction_t> commits;
419   bool on;
420   bool checking;
421 
422   /**
423    * \brief Start keeping track of changes, perform changes.
424    *
425    * init(false) if you intend to commit the changes (if successful), or
426    * init(true) if you just want to check whether the move would be successful,
427    * without performing it if it is.
428    */
429 
inithr::changes_t430   void init(bool ch) {
431     on = true;
432     ccell(cwt.at);
433     forCellEx(c1, cwt.at) ccell(c1);
434     value_keep(kills);
435     value_keep(items);
436     value_keep(hrngen);
437     checking = ch;
438     }
439 
440   /** \brief Commit the changes. Should only be called after init(false). */
441 
commithr::changes_t442   void commit() {
443     on = false;
444     for(auto& p: commits) p();
445     rollbacks.clear();
446     commits.clear();
447     }
448 
449   /** \brief Rollback the changes. */
450 
rollbackhr::changes_t451   void rollback(int pos = 0) {
452     on = false;
453     while(!rollbacks.empty()) {
454       rollbacks.back()();
455       rollbacks.pop_back();
456       }
457     rollbacks.clear();
458     commits.clear();
459     }
460 
461   /** \brief The changes to cell c will be rolled back when rollback() is called. */
ccellhr::changes_t462   void ccell(cell *c) {
463     if(!on) return;
464     gcell a = *c;
465     rollbacks.push_back([c, a] { copy_metadata(c, &a); });
466     }
467 
468   /** \brief Set the value of what to value. This change will be rolled back if necessary. */
value_sethr::changes_t469   template<class T> void value_set(T& what, T value) {
470     if(!on) { what = value; return; }
471     if(what == value) return;
472     T old = what;
473     rollbacks.push_back([&what, old] { what = old; });
474     what = value;
475     }
476 
477   /** \brief Add step to the value of what. This change will be rolled back if necessary. */
478 
value_addhr::changes_t479   template<class T> void value_add(T& what, T step) {
480     value_keep(what); what += step;
481     }
482 
value_inchr::changes_t483   template<class T> void value_inc(T& what) { value_add(what, 1); }
484 
485   /** \brief Any change to the value of what will be rolled back if necessary. */
486 
value_keephr::changes_t487   template<class T> void value_keep(T& what) {
488     if(!on) return;
489     T old = what;
490     rollbacks.push_back([&what, old] { what = old; });
491     }
492 
493   /** \brief Like value_keep but for maps. */
494 
map_valuehr::changes_t495   template<class T, class U, class V> void map_value(map<T, U>& vmap, V& key) {
496     if(vmap.count(key)) {
497       auto val = vmap[key];
498       at_rollback([&vmap, key, val] { vmap[key] = val; });
499       }
500     else {
501       at_rollback([&vmap, key] { vmap.erase(key); });
502       }
503     }
504 
505   /** \brief Perform the given action on commit. @see LATE */
506 
at_commithr::changes_t507   void at_commit(reaction_t act) {
508     if(!on) act();
509     else commits.emplace_back(act);
510     }
511 
512   /** \brief Perform the given action on rollback. */
513 
at_rollbackhr::changes_t514   void at_rollback(reaction_t act) {
515     if(on) rollbacks.emplace_back(act);
516     }
517 
push_pushhr::changes_t518   void push_push(cell *tgt) {
519     pushes.push_back(tgt);
520     auto v = [] { pushes.pop_back(); };
521     rollbacks.push_back(v);
522     commits.push_back(v);
523     }
524   };
525 #endif
526 
527 /** \brief The only instance of hr::changes_t */
528 EX changes_t changes;
529 
530 /**
531  * Auxiliary function for hr::apply_chaos(). Returns whether the cell attribute LHU
532  * should be switched.
533  */
switch_lhu_in(eLand l)534 bool switch_lhu_in(eLand l) {
535   return among(l, laBrownian, laMinefield, laTerracotta, laHive);
536   }
537 
538 /** \brief how should be the direction from 'src' be mirrored to 'dst' */
chaos_mirror_dir(int dir,cellwalker src,cellwalker dst)539 EX int chaos_mirror_dir(int dir, cellwalker src, cellwalker dst) {
540   if(dir >= dst.at->type) return dir;
541   return (dst-src.to_spin(dir)).spin;
542   }
543 
544 #if HDR
swap_data(T & data,cell * c1,cell * c2)545 template<class T> void swap_data(T& data, cell *c1, cell *c2) {
546   changes.map_value(data, c1);
547   changes.map_value(data, c2);
548   if(data.count(c1) && data.count(c2))
549     swap(data[c1], data[c2]);
550   else if(data.count(c1))
551     data[c2] = data[c1], data.erase(c1);
552   else if(data.count(c2))
553     data[c1] = data[c2], data.erase(c2);
554   }
555 #endif
556 
557 /** \brief Apply the Orb of Chaos.
558  *
559  * We assume that the player moves from cwt.peek, in
560  * in the direction given by cwt.spin.
561  */
apply_chaos()562 void apply_chaos() {
563   auto wa = cwt+1+wstep;
564   auto wb = cwt-1+wstep;
565   cell *ca = wa.at;
566   cell *cb = wb.at;
567   if(dice::swap_forbidden(ca, cb)) return;
568   if(dice::swap_forbidden(cb, ca)) return;
569   if(!items[itOrbChaos] || chaos_forbidden(ca) || chaos_forbidden(cb)) return;
570   if(ca && is_paired(ca->monst)) killMonster(ca, moPlayer);
571   if(cb && is_paired(cb->monst)) killMonster(cb, moPlayer);
572   destroyTrapsOn(ca);
573   destroyTrapsOn(cb);
574   if (ca->wall == waStone) destroyTrapsAround(ca);
575   if (cb->wall == waStone) destroyTrapsAround(cb);
576   changes.ccell(ca);
577   changes.ccell(cb);
578   gcell coa = *ca;
579   gcell cob = *cb;
580   if(ca->monst != cb->monst)
581     markOrb(itOrbChaos);
582   if(ca->wall != cb->wall)
583     markOrb(itOrbChaos);
584   if(ca->item != cb->item)
585     markOrb(itOrbChaos);
586   copy_metadata(ca, &cob);
587   copy_metadata(cb, &coa);
588   if(!switch_lhu_in(ca->land)) ca->LHU = coa.LHU;
589   if(!switch_lhu_in(cb->land)) cb->LHU = cob.LHU;
590   if(ca->monst && !(isFriendly(ca) && markOrb(itOrbEmpathy)))
591     ca->stuntime = min(ca->stuntime + 3, 15), markOrb(itOrbChaos);
592   if(cb->monst && !(isFriendly(cb) && markOrb(itOrbEmpathy)))
593     cb->stuntime = min(cb->stuntime + 3, 15), markOrb(itOrbChaos);
594   ca->monmirror = !ca->monmirror;
595   cb->monmirror = !cb->monmirror;
596   ca->mondir = chaos_mirror_dir(ca->mondir, wb, wa);
597   cb->mondir = chaos_mirror_dir(cb->mondir, wa, wb);
598   if(isPrincess(ca) && !isPrincess(cb))
599     princess::move(movei{cb, ca, JUMP});
600   if(isPrincess(cb) && !isPrincess(ca))
601     princess::move(movei{ca, cb, JUMP});
602   if(ca->monst == moTortoise || cb->monst == moTortoise) {
603     tortoise::move_adult(ca, cb);
604     }
605   if(dice::on(ca) || dice::on(cb)) {
606     dice::chaos_swap(wa, wb);
607     }
608   if(ca->item == itBabyTortoise || cb->item == itBabyTortoise) {
609     tortoise::move_baby(ca, cb);
610     }
611   }
612 
actual_move()613 bool pcmove::actual_move() {
614 
615   origd = d;
616   if(d >= 0) {
617     cwt += d;
618     dynamicval<bool> b(changes.on, false);
619     mirror::act(d, mirror::SPINSINGLE);
620     d = cwt.spin;
621     }
622   if(d != -1 && !checkonly) playermoved = true;
623 
624   mi = movei(cwt.at, d);
625   cell *& c2 = mi.t;
626   good_tortoise = c2->monst == moTortoise && tortoise::seek() && !tortoise::diff(tortoise::getb(c2)) && !c2->item;
627 
628   if(items[itOrbGravity]) {
629     if(c2->monst && !should_switchplace(cwt.at, c2))
630       gravity_state = get_static_gravity(cwt.at);
631     else
632       gravity_state = get_move_gravity(cwt.at, c2);
633     if(gravity_state) markOrb(itOrbGravity);
634     }
635 
636   if(againstRose(cwt.at, c2) && !scentResistant()) {
637     if(vmsg(miRESTRICTED)) addMessage("Those roses smell too nicely. You have to come towards them.");
638     return false;
639     }
640 
641   if(items[itOrbDomination] > ORBBASE && isMountable(c2->monst) && fmsMove) {
642     if(checkonly) { nextmovetype = lmMove; return true; }
643     if(!isMountable(cwt.at->monst)) dragon::target = NULL;
644     movecost(cwt.at, c2, 3);
645 
646     flipplayer = true; if(multi::players > 1) multi::flipped[multi::cpid] = true;
647     invismove = (turncount >= noiseuntil) && items[itOrbInvis] > 0;
648     killFriendlyIvy();
649     return perform_move_or_jump();
650     }
651 
652   if(isActivable(c2) && fmsActivate) {
653     if(checkonly) { nextmovetype = lmInstant; return true; }
654     activateActiv(c2, true);
655     return after_instant(false);
656     }
657 
658   #if CAP_COMPLEX2
659   if(c2->monst == moAnimatedDie) {
660     mip = determinePush(cwt, subdir, [] (movei mi) { return canPushThumperOn(mi, cwt.at); });
661     if(mip.proper()) {
662       auto tgt = roll_effect(mip, dice::data[c2]);
663       if(tgt.happy() > 0) {
664         changes.ccell(c2);
665         c2->monst = moNone;
666         c2->wall = waRichDie;
667         }
668       }
669     }
670   #endif
671 
672   if(isPushable(c2->wall) && !c2->monst && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) {
673     mip = determinePush(cwt, subdir, [] (movei mi) { return canPushThumperOn(mi, cwt.at); });
674     if(mip.t) changes.ccell(mip.t);
675     if(mip.d == NO_SPACE) {
676       if(vmsg(miWALL)) addMessage(XLAT("No room to push %the1.", c2->wall));
677       return false;
678       }
679     nextmovetype = lmMove;
680     addMessage(XLAT("You push %the1.", c2->wall));
681     lastmovetype = lmPush; lastmove = cwt.at;
682     pushThumper(mip);
683     changes.push_push(mip.t);
684     return perform_actual_move();
685     }
686 
687   if(c2->item == itHolyGrail && roundTableRadius(c2) < newRoundTableRadius()) {
688     if(vmsg(miRESTRICTED)) addMessage(XLAT("That was not a challenge. Find a larger castle!"));
689     return false;
690     }
691 
692   if(c2->item == itOrbYendor && !checkonly && !peace::on && !itemHiddenFromSight(c2) && yendor::check(c2)) {
693     return false;
694     }
695 
696   if(isWatery(c2) && !nonAdjacentPlayer(cwt.at,c2) && !c2->monst && cwt.at->wall == waBoat && fmsMove)
697     return boat_move();
698 
699   if(!c2->monst && cwt.at->wall == waBoat && cwt.at->item != itOrbYendor && boatGoesThrough(c2) && markOrb(itOrbWater) && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) {
700 
701     if(c2->item && !cwt.at->item) moveItem(c2, cwt.at, false), boatmove = true;
702     placeWater(c2, cwt.at);
703     moveBoat(mi);
704     changes.ccell(c2);
705     c2->mondir = revhint(cwt.at, d);
706     if(c2->item) boatmove = !boatmove;
707     return perform_actual_move();
708     }
709 
710   return after_escape();
711   }
712 
blowaway_message(cell * c2)713 void blowaway_message(cell *c2) {
714   addMessage(airdist(c2) < 3 ? XLAT("The Air Elemental blows you away!") : XLAT("You cannot go against the wind!"));
715   }
716 
tortoise_hero_message(cell * c2)717 EX void tortoise_hero_message(cell *c2) {
718   bool fem = playergender() == GEN_F;
719   playSound(c2, fem ? "heal-princess" : "heal-prince");
720   addMessage(fem ? XLAT("You are now a tortoise heroine!") : XLAT("You are now a tortoise hero!"));
721   }
722 
boat_move()723 bool pcmove::boat_move() {
724 
725   cell *& c2 = mi.t;
726 
727   if(againstWind(c2, cwt.at)) {
728     if(vmsg(miRESTRICTED)) blowaway_message(c2);
729     return false;
730     }
731 
732   if(againstCurrent(c2, cwt.at) && !markOrb(itOrbWater)) {
733     if(markOrb(itOrbFish) || markOrb(itOrbAether) || gravity_state)
734       return after_escape();
735     if(vmsg(miRESTRICTED)) addMessage(XLAT("You cannot go against the current!"));
736     return false;
737     }
738 
739   if(cwt.at->item == itOrbYendor) {
740     if(markOrb(itOrbFish) || markOrb(itOrbAether) || gravity_state)
741       return after_escape();
742     if(vmsg(miRESTRICTED)) addMessage(XLAT("The Orb of Yendor is locked in with powerful magic."));
743     return false;
744     }
745 
746   nextmovetype = lmMove;
747   moveBoat(mi);
748   boatmove = true;
749   return perform_actual_move();
750   }
751 
tell_why_cannot_attack()752 void pcmove::tell_why_cannot_attack() {
753   cell *& c2 = mi.t;
754   if(c2->monst == moWorm || c2->monst == moWormtail || c2->monst == moWormwait)
755     addMessage(XLAT("You cannot attack Sandworms directly!"));
756   else if(c2->monst == moHexSnake || c2->monst == moHexSnakeTail)
757     addMessage(XLAT("You cannot attack Rock Snakes directly!"));
758   else if(nonAdjacentPlayer(c2, cwt.at))
759     addMessage(XLAT("You cannot attack diagonally!"));
760   else if(thruVine(c2, cwt.at))
761     addMessage(XLAT("You cannot attack through the Vine!"));
762   else if(c2->monst == moTentacle || c2->monst == moTentacletail || c2->monst == moTentaclewait || c2->monst == moTentacleEscaping)
763     addMessage(XLAT("You cannot attack Tentacles directly!"));
764   else if(c2->monst == moHedge && !markOrb(itOrbThorns)) {
765     addMessage(XLAT("You cannot attack %the1 directly!", c2->monst));
766     addMessage(XLAT("Stab them by walking around them."));
767     }
768   else if(c2->monst == moRoseBeauty || isBull(c2->monst) || c2->monst == moButterfly)
769     addMessage(XLAT("You cannot attack %the1!", c2->monst));
770   else if(c2->monst == moFlailer && !c2->stuntime) {
771     addMessage(XLAT("You cannot attack %the1 directly!", c2->monst));
772     addMessage(XLAT("Make him hit himself by walking away from him."));
773     }
774   else if(c2->monst == moVizier && c2->hitpoints > 1 && !(attackflags & AF_FAST)) {
775     addMessage(XLAT("You cannot attack %the1 directly!", c2->monst));
776     addMessage(XLAT("Hit him by walking away from him."));
777     }
778   else if(c2->monst == moShadow)
779     addMessage(XLAT("You cannot defeat the Shadow!"));
780   else if(c2->monst == moGreater || c2->monst == moGreaterM)
781     addMessage(XLAT("You cannot defeat the Greater Demon yet!"));
782   else if(c2->monst == moDraugr)
783     addMessage(XLAT("Your mundane weapon cannot hurt %the1!", c2->monst));
784   else if(isRaider(c2->monst))
785     addMessage(XLAT("You cannot attack Raiders directly!"));
786   else if(isSwitch(c2->monst))
787     addMessage(XLAT("You cannot attack Jellies in their wall form!"));
788   else if(c2->monst == moAnimatedDie)
789     addMessage(XLAT("You can only push this die if the highest number would be on the top!"));
790   else if(c2->monst == moAngryDie)
791     addMessage(XLAT("This die is really angry at you!"));
792   else if((attackflags & AF_WEAK) && isIvy(c2))
793     addMessage(XLAT("You are too weakened to attack %the1!", c2->monst));
794   else if(isWorm(cwt.at->monst) && isWorm(c2->monst) && wormhead(cwt.at) == wormhead(c2) && cwt.at->monst != moTentacleGhost && c2->monst != moTentacleGhost)
795     addMessage(XLAT("You cannot attack your own mount!"));
796   else if(checkOrb(c2->monst, itOrbShield))
797     addMessage(XLAT("A magical shield protects %the1!", c2->monst));
798   else
799     addMessage(XLAT("For some reason... cannot attack!"));
800   }
801 
after_escape()802 bool pcmove::after_escape() {
803   cell*& c2 = mi.t;
804 
805   bool push_behind = c2->wall == waBigStatue || (among(c2->wall, waCTree, waSmallTree, waBigTree, waShrub, waVinePlant) && markOrb(itOrbWoods));
806 
807   if(thruVine(c2, cwt.at) && markOrb(itOrbWoods)) push_behind = true;
808 
809   if(push_behind && !c2->monst && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) {
810     eWall what = c2->wall;
811     if(!thruVine(c2, cwt.at) && !canPushStatueOn(cwt.at, P_ISPLAYER)) {
812       if(vmsg(miRESTRICTED)) {
813         if(isFire(cwt.at))
814           addMessage(XLAT("You have to escape first!"));
815         else
816           addMessage(XLAT("There is not enough space!"));
817         }
818       return false;
819       }
820 
821     changes.ccell(c2);
822     changes.ccell(cwt.at);
823 
824     c2->wall = cwt.at->wall;
825     c2->wparam = cwt.at->wparam;
826     if(doesnotFall(cwt.at)) {
827       cwt.at->wall = what;
828       if(cellHalfvine(what))
829         c2->wall = waNone, cwt.at->wall = waVinePlant;
830       }
831 
832     nextmovetype = lmMove;
833     addMessage(XLAT("You push %the1 behind you!", what));
834     animateMovement(mi.rev(), LAYER_BOAT);
835     changes.push_push(cwt.at);
836     return perform_actual_move();
837     }
838 
839   bool attackable;
840   attackable =
841     c2->wall == waBigTree ||
842     c2->wall == waSmallTree ||
843     (c2->wall == waShrub && items[itOrbSlaying]) ||
844     c2->wall == waMirrorWall;
845   if(attackable && markOrb(itOrbAether) && c2->wall != waMirrorWall)
846     attackable = false;
847   bool nm; nm = attackable;
848   if(forcedmovetype == fmAttack) attackable = true;
849   attackable = attackable && (!c2->monst || isFriendly(c2));
850   attackable = attackable && !nonAdjacentPlayer(cwt.at,c2);
851 
852   bool dont_attack = items[itOrbFlash] || items[itOrbLightning];
853 
854   if(attackable && fmsAttack && !dont_attack && !items[itCurseWeakness]) {
855     if(checkNeedMove(checkonly, true)) return false;
856     nextmovetype = nm ? lmAttack : lmSkip;
857     if(c2->wall == waSmallTree) {
858       drawParticles(c2, winf[c2->wall].color, 4);
859       addMessage(XLAT("You chop down the tree."));
860       playSound(c2, "hit-axe" + pick123());
861       changes.ccell(c2);
862       c2->wall = waNone;
863       spread_plague(cwt.at, c2, mi.d, moPlayer);
864       return swing();
865       }
866     else if(c2->wall == waShrub && markOrb(itOrbSlaying)) {
867       drawParticles(c2, winf[c2->wall].color, 4);
868       addMessage(XLAT("You chop down the shrub."));
869       playSound(c2, "hit-axe" + pick123());
870       changes.ccell(c2);
871       c2->wall = waNone;
872       spread_plague(cwt.at, c2, mi.d, moPlayer);
873       return swing();
874       }
875     else if(c2->wall == waBigTree) {
876       drawParticles(c2, winf[c2->wall].color, 8);
877       addMessage(XLAT("You start chopping down the tree."));
878       playSound(c2, "hit-axe" + pick123());
879       changes.ccell(c2);
880       c2->wall = waSmallTree;
881       return swing();
882       }
883     if(!peace::on) {
884       if(c2->wall == waMirrorWall)
885         addMessage(XLAT("You swing your sword at the mirror."));
886       else if(c2->wall)
887         addMessage(XLAT("You swing your sword at %the1.", c2->wall));
888       else
889         addMessage(XLAT("You swing your sword."));
890       return swing();
891       }
892     return false;
893     }
894   else if(c2->monst == moKnight) {
895     #if CAP_COMPLEX2
896     if(vmsg(miWALL)) camelot::knightFlavorMessage(c2);
897     #endif
898     return false;
899     }
900   else if(c2->monst && (!isFriendly(c2) || c2->monst == moTameBomberbird || isMountable(c2->monst)) && !(peace::on && !good_tortoise))
901     return attack();
902   else if(!passable(c2, cwt.at, P_USEBOAT | P_ISPLAYER | P_MIRROR | P_MONSTER)) {
903     tell_why_impassable();
904     return false;
905     }
906   else if(items[itFatigue] + fatigue_cost(mi) > 10) {
907     if(vmsg(miRESTRICTED))
908       addMessage(XLAT("You are too fatigued!"));
909     return false;
910     }
911   else if(fmsMove)
912     return move_if_okay();
913 
914   else return false;
915   }
916 
move_if_okay()917 bool pcmove::move_if_okay() {
918   cell*& c2 = mi.t;
919   #if CAP_COMPLEX2
920   if(mine::marked_mine(c2) && !mine::safe() && !checkonly && warningprotection(XLAT("Are you sure you want to step there?")))
921     return false;
922   #endif
923 
924   if(snakelevel(c2) <= snakelevel(cwt.at)-2) {
925     bool can_leave = false;
926     forCellEx(c3, c2) if(passable(c3, c2, P_ISPLAYER | P_MONSTER)) can_leave = true;
927     if(!can_leave && !checkonly && warningprotection(XLAT("Are you sure you want to step there?")))
928       return false;
929     }
930 
931   if(switchplace_prevent(cwt.at, c2, *this))
932     return false;
933   if(!checkonly && warningprotection_hit(do_we_stab_a_friend(mi, moPlayer)))
934     return false;
935 
936   nextmovetype = lmMove;
937   return perform_actual_move();
938   }
939 
tell_why_impassable()940 void pcmove::tell_why_impassable() {
941   cell*& c2 = mi.t;
942   if(nonAdjacentPlayer(cwt.at,c2)) {
943     if(vmsg(miRESTRICTED)) addMessage(geosupport_football() < 2 ?
944       XLAT("You cannot move between the cells without dots here!") :
945       XLAT("You cannot move between the triangular cells here!")
946       );
947     }
948   else if(againstWind(c2, cwt.at)) {
949     if(vmsg(miRESTRICTED))
950       blowaway_message(c2);
951     }
952   else if(anti_alchemy(c2, cwt.at)) {
953     if(vmsg(miRESTRICTED))
954       addMessage(XLAT("Wrong color!"));
955     }
956   else if(c2->wall == waRoundTable) {
957     if(vmsg(miRESTRICTED))
958       addMessage(XLAT("It would be impolite to land on the table!"));
959     }
960   else if(snakelevel(cwt.at) >= 3 && snakelevel(c2) == 0 && !isWall(c2)) {
961     if(vmsg(miRESTRICTED))
962       addMessage(XLAT("You would get hurt!", c2->wall));
963     }
964   else if(cellEdgeUnstable(cwt.at) && cellEdgeUnstable(c2)) {
965     if(vmsg(miRESTRICTED))
966       addMessage(XLAT("Gravity does not allow this!"));
967     }
968   else if(c2->wall == waChasm && c2->land == laDual) {
969     if(vmsg(miRESTRICTED))
970       addMessage(XLAT("You cannot move there!"));
971     }
972   else if(!c2->wall) {
973     if(vmsg(miRESTRICTED))
974       addMessage(XLAT("You cannot move there!"));
975     }
976   else {
977     if(vmsg(miWALL))
978       addMessage(XLAT("You cannot move through %the1!", c2->wall));
979     }
980   }
981 
attack()982 bool pcmove::attack() {
983   auto& c2 = mi.t;
984   if(!fmsAttack) return false;
985 
986   if(items[itOrbFlash] || items[itOrbLightning])
987     return false;
988 
989   attackflags = AF_NORMAL;
990   if(items[itOrbSpeed]&1) attackflags |= AF_FAST;
991   if(items[itOrbSlaying]) attackflags |= AF_CRUSH;
992   if(items[itCurseWeakness]) attackflags |= AF_WEAK;
993 
994   bool ca =canAttack(cwt.at, moPlayer, c2, c2->monst, attackflags);
995 
996   if(!ca) {
997     if(forcedmovetype == fmAttack) {
998       if(monstersnear_add_pmi(movei(cwt.at, STAY))) {
999         if(vmsg(miTHREAT)) wouldkill("%The1 would get you!");
1000         return false;
1001         }
1002       nextmovetype = lmSkip;
1003       addMessage(XLAT("You swing your sword at %the1.", c2->monst));
1004       return swing();
1005       }
1006     if(vmsg(miENTITY)) tell_why_cannot_attack();
1007     return false;
1008     }
1009 
1010   // mip.t=c2 means that the monster is not destroyed and thus
1011   // still counts for lightning in monstersnear
1012 
1013   mip = movei(c2, nullptr, NO_SPACE);
1014 
1015   if(items[itCurseWeakness] || (isStunnable(c2->monst) && c2->hitpoints > 1)) {
1016     if(monsterPushable(c2))
1017       mip = determinePush(cwt, subdir, [] (movei mi) { return passable(mi.t, mi.s, P_BLOW); });
1018     else
1019       mip.t = c2;
1020     if(mip.t) changes.ccell(mip.t);
1021     changes.push_push(mip.t);
1022     }
1023 
1024   if(!(isWatery(cwt.at) && c2->monst == moWaterElemental) && checkNeedMove(checkonly, true))
1025     return false;
1026 
1027   if(c2->monst == moTameBomberbird && warningprotection_hit(moTameBomberbird)) return false;
1028 
1029   nextmovetype = lmAttack;
1030 
1031   mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK);
1032 
1033   int tk = tkills();
1034   plague_kills = 0;
1035 
1036   if(good_tortoise) {
1037     changes.ccell(c2);
1038     c2->stuntime = 2;
1039     changes.at_commit([c2] {
1040       items[itBabyTortoise] += 4;
1041       updateHi(itBabyTortoise, items[itBabyTortoise]);
1042       c2->item = itBabyTortoise;
1043       tortoise::babymap[c2] = tortoise::seekbits;
1044       tortoise_hero_message(c2);
1045       achievement_collection(itBabyTortoise);
1046       });
1047     }
1048   else {
1049     eMonster m = c2->monst;
1050     if(m) {
1051       if((attackflags & AF_CRUSH) && !canAttack(cwt.at, moPlayer, c2, c2->monst, attackflags ^ AF_CRUSH ^ AF_MUSTKILL))
1052         markOrb(itOrbSlaying);
1053       if(c2->monst == moTerraWarrior && hrand(100) > 2 * items[itTerra]) {
1054         if(hrand(2 + jiangshi_on_screen) < 2)
1055           changes.value_add(wandering_jiangshi, 1);
1056         }
1057       attackMonster(c2, attackflags | AF_MSG, moPlayer);
1058       if(m == moRusalka) {
1059         changes.ccell(cwt.at);
1060         if(cwt.at->wall == waNone) cwt.at->wall = waShallow;
1061         else if(cwt.at->wall == waShallow || isAlch(cwt.at->wall)) cwt.at->wall = waDeepWater;
1062         }
1063       changes.ccell(c2);
1064       // salamanders are stunned for longer time when pushed into a wall
1065       if(c2->monst == moSalamander && (mip.t == c2 || !mip.t)) c2->stuntime = 10;
1066       if(!c2->monst || isAnyIvy(m)) {
1067         spread_plague(cwt.at, c2, mi.d, moPlayer);
1068         produceGhost(c2, m, moPlayer);
1069         }
1070       if(mip.proper()) pushMonster(mip);
1071       animateAttack(mi, LAYER_SMALL);
1072       }
1073     }
1074 
1075   sideAttack(cwt.at, d, moPlayer, tkills() - tk - plague_kills);
1076   lastmovetype = lmAttack; lastmove = c2;
1077   swordAttackStatic();
1078 
1079   if(monstersnear_add_pmi(movei(cwt.at, STAY))) {
1080     if(vmsg(miTHREAT)) wouldkill("You would be killed by %the1!");
1081     return false;
1082     }
1083   if(checkonly) return true;
1084   if(changes.on) changes.commit();
1085 
1086   return after_move();
1087   }
1088 
chaos_forbidden(cell * c)1089 EX bool chaos_forbidden(cell *c) {
1090   return do_not_touch_this_wall(c) || isMultitile(c->monst);
1091   }
1092 
fatigue_cost(const movei & mi)1093 EX int fatigue_cost(const movei& mi) {
1094   return
1095     gravityLevelDiff(mi.t, mi.s) +
1096     (snakelevel(mi.t) - snakelevel(mi.s)) +
1097     (againstWind(mi.s, mi.t) ? 0 : 1);
1098   }
1099 
alchMayDuplicate(eWall w)1100 bool alchMayDuplicate(eWall w) {
1101   return !isDie(w) && w != waBoat && w != waArrowTrap;
1102 }
1103 
perform_actual_move()1104 bool pcmove::perform_actual_move() {
1105   cell*& c2 = mi.t;
1106   changes.at_commit([&] {
1107     flipplayer = true; if(multi::players > 1) multi::flipped[multi::cpid] = true;
1108     });
1109   if(c2->item && isAlch(c2)) {
1110     if(alchMayDuplicate(cwt.at->wall)) {
1111       c2->wall = cwt.at->wall;
1112       c2->wparam = cwt.at->wparam;
1113       }
1114     else
1115       c2->wall = waNone;
1116     }
1117   #if CAP_COMPLEX2
1118   if(c2->wall == waRoundTable) {
1119     addMessage(XLAT("You jump over the table!"));
1120     }
1121 
1122   if(cwt.at->wall == waRoundTable)
1123     camelot::roundTableMessage(c2);
1124   #endif
1125 
1126   invismove = (turncount >= noiseuntil) && items[itOrbInvis] > 0;
1127 
1128   if(items[itOrbFire]) {
1129     invismove = false;
1130     if(makeflame(cwt.at, 10, false)) markOrb(itOrbFire);
1131     }
1132 
1133   if(items[itCurseWater]) {
1134     invismove = false;
1135     if(makeshallow(mi.s, 10, false)) markOrb(itCurseWater);
1136     }
1137 
1138   if(markOrb(itCurseFatigue) && !markOrb(itOrbAether))
1139     items[itFatigue] += fatigue_cost(mi);
1140 
1141   handle_friendly_ivy();
1142 
1143   if(items[itOrbDigging]) {
1144     invismove = false;
1145     if(earthMove(mi)) markOrb(itOrbDigging);
1146     }
1147 
1148   movecost(cwt.at, c2, 1);
1149 
1150   if(!boatmove && collectItem(c2)) return true;
1151   if(boatmove && c2->item && cwt.at->item) {
1152      eItem it = c2->item;
1153      c2->item = cwt.at->item;
1154      if(collectItem(c2)) return true;
1155      eItem it2 = c2->item;
1156      c2->item = it;
1157      cwt.at->item = it2;
1158      }
1159   if(doPickupItemsWithMagnetism(c2)) return true;
1160 
1161   if(isIcyLand(cwt.at) && cwt.at->wall == waNone && markOrb(itOrbWinter)) {
1162     invismove = false;
1163     cwt.at->wall = waIcewall;
1164     }
1165 
1166   if(items[itOrbWinter])
1167     forCellEx(c3, c2) if(c3->wall == waIcewall && c3->item) {
1168       changes.ccell(c3);
1169       markOrb(itOrbWinter);
1170       if(collectItem(c3)) return true;
1171       }
1172 
1173   movecost(cwt.at, c2, 2);
1174 
1175   handle_switchplaces(cwt.at, c2, switchplaces);
1176 
1177   return perform_move_or_jump();
1178   }
1179 
handle_friendly_ivy()1180 void pcmove::handle_friendly_ivy() {
1181   cell*& c2 = mi.t;
1182   bool haveIvy = false;
1183   forCellEx(c3, cwt.at) if(c3->monst == moFriendlyIvy) haveIvy = true;
1184 
1185   bool killIvy = haveIvy;
1186 
1187   if(items[itOrbNature]) {
1188     if(c2->monst != moFriendlyIvy && strictlyAgainstGravity(c2, cwt.at, false, MF_IVY)) {
1189       invismove = false;
1190       }
1191     else if(cwt.at->monst) invismove = false;
1192     else if(haveIvy || !cellEdgeUnstable(cwt.at, MF_IVY)) {
1193       cwt.at->monst  = moFriendlyIvy;
1194       cwt.at->mondir = neighborId(cwt.at, c2);
1195       invismove = false;
1196       markOrb(itOrbNature);
1197       killIvy = false;
1198       }
1199     }
1200 
1201   if(killIvy) killFriendlyIvy();
1202   }
1203 
perform_move_or_jump()1204 bool pcmove::perform_move_or_jump() {
1205   lastmovetype = lmMove; lastmove = cwt.at;
1206   apply_chaos();
1207 
1208   stabbingAttack(mi, moPlayer);
1209   changes.value_keep(cwt);
1210   cwt += wstep;
1211 
1212   mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK | mirror::GO);
1213 
1214   auto pmi = player_move_info(mi);
1215   playerMoveEffects(mi);
1216 
1217   if(mi.t->monst == moFriendlyIvy) changes.ccell(mi.t), mi.t->monst = moNone;
1218 
1219   if(monstersnear_add_pmi(pmi)) {
1220     if(vmsg(miTHREAT)) wouldkill("%The1 would kill you there!");
1221     return false;
1222     }
1223 
1224   if(checkonly) return true;
1225   if(changes.on) changes.commit();
1226 
1227   if(switchplaces) {
1228     indAnimateMovement(mi, LAYER_SMALL);
1229     indAnimateMovement(mi.rev(), LAYER_SMALL);
1230     commitAnimations(LAYER_SMALL);
1231     }
1232   else
1233     animateMovement(mi, LAYER_SMALL);
1234   current_display->which_copy = current_display->which_copy * adj(mi);
1235 
1236   countLocalTreasure();
1237   landvisited[cwt.at->land] = true;
1238   afterplayermoved();
1239 
1240   return after_move();
1241   }
1242 
stay()1243 bool pcmove::stay() {
1244   if(items[itOrbGravity]) {
1245     gravity_state = get_static_gravity(cwt.at);
1246     if(gravity_state) markOrb(itOrbGravity);
1247     }
1248   lastmovetype = lmSkip; lastmove = NULL;
1249   if(checkNeedMove(checkonly, false))
1250     return false;
1251   swordAttackStatic();
1252   nextmovetype = lmSkip;
1253 
1254   mi = movei(cwt.at, STAY);
1255   if(last_gravity_state && !gravity_state)
1256     playerMoveEffects(mi);
1257   if(d == -2)
1258     dropGreenStone(cwt.at);
1259 
1260   items[itFatigue] -= 5;
1261   if(items[itFatigue] < 0)
1262     items[itFatigue] = 0;
1263 
1264   if(monstersnear_add_pmi(mi)) {
1265     if(vmsg(miTHREAT)) wouldkill("%The1 would get you!");
1266     return false;
1267     }
1268   if(checkonly) return true;
1269   if(changes.on) changes.commit();
1270   if(cellUnstable(cwt.at) && !markOrb(itOrbAether))
1271     doesFallSound(cwt.at);
1272 
1273   return after_move();
1274   }
1275 
1276 #if HDR
movepcto(const movedir & md)1277 inline bool movepcto(const movedir& md) { return movepcto(md.d, md.subdir); }
1278 #endif
1279 
1280 
warningprotection(const string & s)1281 EX bool warningprotection(const string& s) {
1282   if(hardcore) return false;
1283   if(multi::activePlayers() > 1) return false;
1284   if(items[itWarning]) return false;
1285   pushScreen([s] () {
1286     gamescreen(1);
1287     dialog::addBreak(250);
1288     dialog::init(XLAT("WARNING"), 0xFF0000, 150, 100);
1289     dialog::addBreak(500);
1290     dialog::addInfo(s);
1291     dialog::addBreak(500);
1292     dialog::addItem(XLAT("YES"), 'y');
1293     dialog::lastItem().scale = 200;
1294     auto yes = [] () { items[itWarning] = 1; popScreen(); };
1295     dialog::add_action(yes);
1296     dialog::add_key_action(SDLK_RETURN, yes);
1297     dialog::addItem(XLAT("NO"), 'n');
1298     dialog::lastItem().scale = 200;
1299     dialog::add_action([] () { items[itWarning] = 0; popScreen(); });
1300     dialog::display();
1301     });
1302   return true;
1303   }
1304 
warningprotection_hit(eMonster m)1305 EX bool warningprotection_hit(eMonster m) {
1306   if(m && warningprotection(XLAT("Are you sure you want to hit %the1?", m)))
1307     return true;
1308   return false;
1309   }
1310 
playerInWater()1311 EX bool playerInWater() {
1312   for(int i: player_indices())
1313     if(isWatery(playerpos(i)) && !playerInBoat(i))
1314       return true;
1315   return false;
1316   }
1317 
numplayers()1318 EX int numplayers() {
1319   return multi::players;
1320   }
1321 
player_positions()1322 EX vector<cell*> player_positions() {
1323   vector<cell*> res;
1324   for(int i=0; i<numplayers(); i++)
1325     if(multi::playerActive(i))
1326       res.push_back(playerpos(i));
1327   return res;
1328   }
1329 
player_indices()1330 EX vector<int> player_indices() {
1331   vector<int> res;
1332   for(int i=0; i<numplayers(); i++)
1333     if(multi::playerActive(i))
1334       res.push_back(i);
1335   return res;
1336   }
1337 
playerpos(int i)1338 EX cell *playerpos(int i) {
1339   if(shmup::on) return shmup::playerpos(i);
1340   if(multi::players > 1) return multi::player[i].at;
1341   return singlepos();
1342   }
1343 
allPlayersInBoats()1344 EX bool allPlayersInBoats() {
1345   for(cell *pc: player_positions())
1346     if(pc->wall != waBoat) return true;
1347   return false;
1348   }
1349 
whichPlayerOn(cell * c)1350 EX int whichPlayerOn(cell *c) {
1351   if(singleused()) return c == singlepos() ? 0 : -1;
1352   for(int i: player_indices())
1353     if(playerpos(i) == c) return i;
1354   return -1;
1355   }
1356 
isPlayerOn(cell * c)1357 EX bool isPlayerOn(cell *c) {
1358   return whichPlayerOn(c) >= 0;
1359   }
1360 
isPlayerInBoatOn(cell * c,int i)1361 EX bool isPlayerInBoatOn(cell *c, int i) {
1362   return
1363     (playerpos(i) == c && (
1364       c->wall == waBoat || c->wall == waStrandedBoat || (shmup::on && shmup::playerInBoat(i))
1365       ));
1366   }
1367 
playerInBoat(int i)1368 EX bool playerInBoat(int i) {
1369   return isPlayerInBoatOn(playerpos(i), i);
1370   }
1371 
isPlayerInBoatOn(cell * c)1372 EX bool isPlayerInBoatOn(cell *c) {
1373   for(int i=0; i<numplayers(); i++) if(isPlayerInBoatOn(c, i)) return true;
1374   return false;
1375   }
1376 
playerInPower()1377 EX bool playerInPower() {
1378   if(singleused())
1379     return singlepos()->land == laPower || singlepos()->land == laHalloween;
1380   for(cell *pc: player_positions())
1381     if(pc->land == laPower || pc->land == laHalloween)
1382       return true;
1383   return false;
1384   }
1385 
playerMoveEffects(movei mi)1386 EX void playerMoveEffects(movei mi) {
1387   cell *c1 = mi.s;
1388   cell *c2 = mi.t;
1389 
1390   if(peace::on) items[itOrbSword] = c2->land == laBurial ? 100 : 0;
1391 
1392   changes.value_keep(sword::dir[multi::cpid]);
1393   sword::dir[multi::cpid] = sword::shift(mi, sword::dir[multi::cpid]);
1394 
1395   destroyWeakBranch(c1, c2, moPlayer);
1396 
1397   #if CAP_COMPLEX2
1398   mine::uncover_full(c2);
1399   #endif
1400 
1401   if((c2->wall == waClosePlate || c2->wall == waOpenPlate) && normal_gravity_at(c2) && !markOrb(itOrbAether))
1402     toggleGates(c2, c2->wall);
1403 
1404   if(c2->wall == waArrowTrap && c2->wparam == 0 && normal_gravity_at(c2) && !markOrb(itOrbAether))
1405     activateArrowTrap(c2);
1406 
1407   if(c2->wall == waFireTrap && c2->wparam == 0 && normal_gravity_at(c2) &&!markOrb(itOrbAether)) {
1408     playSound(c2, "click");
1409     changes.ccell(c2);
1410     c2->wparam = 1;
1411     }
1412 
1413   if(c2->wall == waReptile)
1414     c2->wparam = -1;
1415 
1416   princess::playernear(c2);
1417 
1418   if(c2->wall == waGlass && items[itOrbAether] > ORBBASE+1) {
1419     addMessage(XLAT("Your Aether powers are drained by %the1!", c2->wall));
1420     drainOrb(itOrbAether, 2);
1421     }
1422 
1423   if(cellUnstable(c2) && !markOrb(itOrbAether)) {
1424     doesFallSound(c2);
1425     if(c2->land == laMotion && c2->wall == waChasm) c2->mondir = mi.rev_dir_or(NODIR);
1426     }
1427 
1428   if(c2->wall == waStrandedBoat && markOrb(itOrbWater))
1429     c2->wall = waBoat;
1430 
1431   if(c2->land == laOcean && c2->wall == waBoat && c2->landparam < 30 && markOrb(itOrbWater))
1432     c2->landparam = 40;
1433 
1434   if((c2->land == laHauntedWall || c2->land == laHaunted) && !hauntedWarning) {
1435     changes.value_set(hauntedWarning, true);
1436     addMessage(XLAT("You become a bit nervous..."));
1437     addMessage(XLAT("Better not to let your greed make you stray from your path."));
1438     playSound(c2, "nervous");
1439     }
1440   }
1441 
afterplayermoved()1442 EX void afterplayermoved() {
1443   pregen();
1444   if(!racing::on)
1445   setdist(cwt.at, 7 - getDistLimit() - genrange_bonus, NULL);
1446   prairie::treasures();
1447   if(generatingEquidistant) {
1448     printf("Warning: generatingEquidistant set to true\n");
1449     generatingEquidistant = false;
1450     }
1451   }
1452 
produceGhost(cell * c,eMonster victim,eMonster who)1453 EX void produceGhost(cell *c, eMonster victim, eMonster who) {
1454   if(who != moPlayer && !items[itOrbEmpathy]) return;
1455   if(markOrb(itOrbUndeath) && !c->monst && isGhostable(victim)) {
1456     changes.ccell(c);
1457     c->monst = moFriendlyGhost, c->stuntime = 0;
1458     if(who != moPlayer) markOrb(itOrbEmpathy);
1459     }
1460   }
1461 
swordAttack(cell * mt,eMonster who,cell * c,int bb)1462 EX bool swordAttack(cell *mt, eMonster who, cell *c, int bb) {
1463   eMonster m = c->monst;
1464   if(c->wall == waCavewall) markOrb(bb ? itOrbSword2: itOrbSword);
1465   if(among(c->wall, waSmallTree, waBigTree, waRose, waCTree, waVinePlant, waBigBush, waSmallBush, waSolidBranch, waWeakBranch, waShrub)
1466     || thruVine(mt, c)) {
1467     changes.ccell(c);
1468     playSound(NULL, "hit-axe"+pick123());
1469     markOrb(bb ? itOrbSword2: itOrbSword);
1470     drawParticles(c, winf[c->wall].color, 16);
1471     addMessage(XLAT("You chop down %the1.", c->wall));
1472     destroyHalfvine(c);
1473     c->wall = waNone;
1474     }
1475   if(c->wall == waBarrowDig) {
1476     changes.ccell(c);
1477     playSound(NULL, "hit-axe"+pick123());
1478     markOrb(bb ? itOrbSword2: itOrbSword);
1479     drawParticles(c, winf[c->wall].color, 16);
1480     c->wall = waNone;
1481     }
1482   if(c->wall == waBarrowWall && items[itBarrow] >= 25) {
1483     changes.ccell(c);
1484     playSound(NULL, "hit-axe"+pick123());
1485     markOrb(bb ? itOrbSword2: itOrbSword);
1486     drawParticles(c, winf[c->wall].color, 16);
1487     c->wall = waNone;
1488     }
1489   if(c->wall == waExplosiveBarrel)
1490     explodeBarrel(c);
1491   if(!peace::on && canAttack(mt, who, c, m, AF_SWORD)) {
1492     changes.ccell(c);
1493     markOrb(bb ? itOrbSword2: itOrbSword);
1494     int k = tkills();
1495     attackMonster(c, AF_NORMAL | AF_MSG | AF_SWORD, who);
1496     if(c->monst == moShadow) c->monst = moNone;
1497     produceGhost(c, m, who);
1498     if(tkills() > k) return true;
1499     }
1500   return false;
1501   }
1502 
swordAttackStatic(int bb)1503 EX void swordAttackStatic(int bb) {
1504   swordAttack(cwt.at, moPlayer, sword::pos(multi::cpid, bb), bb);
1505   }
1506 
swordAttackStatic()1507 EX void swordAttackStatic() {
1508   for(int bb = 0; bb < 2; bb++)
1509     if(sword::orbcount(bb))
1510       swordAttackStatic(bb);
1511   }
1512 
1513 EX int plague_kills;
1514 
spread_plague(cell * mf,cell * mt,int dir,eMonster who)1515 EX void spread_plague(cell *mf, cell *mt, int dir, eMonster who) {
1516   if(!items[itOrbPlague]) return;
1517   if(who != moPlayer && !items[itOrbEmpathy]) return;
1518   forCellEx(mx, mt) if(celldistance(mx, mf) > celldistance(mx, mf->modmove(dir)) && celldistance(mx, mf) <= 4) {
1519     sideAttackAt(mf, dir, mx, who, itOrbPlague, mt);
1520     }
1521   }
1522 
sideAttackAt(cell * mf,int dir,cell * mt,eMonster who,eItem orb,cell * pf)1523 EX void sideAttackAt(cell *mf, int dir, cell *mt, eMonster who, eItem orb, cell *pf) {
1524   eMonster m = mt->monst;
1525   flagtype f = AF_SIDE;
1526   if(orb == itOrbPlague) f |= AF_PLAGUE;
1527   if(items[itOrbSlaying]) f|= AF_CRUSH;
1528   if(!items[orb]) return;
1529   auto plague_particles = [&] {
1530     if(orb == itOrbPlague) {
1531       for(int i=0; i<16; i++)
1532         drawDirectionalParticle(pf, neighborId(pf, mt), (i&1) ? orb_auxiliary_color(orb) : iinf[orb].color);
1533       }
1534     };
1535   if(canAttack(mf, who, mt, m, f)) {
1536     if((f & AF_CRUSH) && !canAttack(mf, who, mt, m, AF_SIDE | AF_MUSTKILL))
1537       markOrb(itOrbSlaying);
1538     markOrb(orb);
1539     changes.ccell(mt);
1540     plague_particles();
1541     if(who != moPlayer) markOrb(itOrbEmpathy);
1542     int kk = 0;
1543     if(orb == itOrbPlague) kk = tkills();
1544     if(attackMonster(mt, AF_NORMAL | AF_SIDE | AF_MSG, who) || isAnyIvy(m)) {
1545       if(orb == itOrbPlague && kk < tkills())
1546         plague_kills++;
1547       if(mt->monst != m) spread_plague(mf, mt, dir, who);
1548       produceGhost(mt, m, who);
1549       }
1550     }
1551   else if(mt->wall == waSmallTree) {
1552     changes.ccell(mt);
1553     plague_particles();
1554     markOrb(orb);
1555     mt->wall = waNone;
1556     spread_plague(mf, mt, dir, who);
1557     }
1558   else if(mt->wall == waShrub && markEmpathy(itOrbSlaying)) {
1559     changes.ccell(mt);
1560     plague_particles();
1561     markOrb(orb);
1562     mt->wall = waNone;
1563     spread_plague(mf, mt, dir, who);
1564     }
1565   else if(mt->wall == waBigTree) {
1566     changes.ccell(mt);
1567     plague_particles();
1568     markOrb(orb);
1569     mt->wall = waSmallTree;
1570     }
1571   else if(mt->wall == waExplosiveBarrel && orb != itOrbPlague) {
1572     changes.ccell(mt);
1573     explodeBarrel(mt);
1574     }
1575   }
1576 
sideAttack(cell * mf,int dir,eMonster who,int bonus,eItem orb)1577 EX void sideAttack(cell *mf, int dir, eMonster who, int bonus, eItem orb) {
1578   if(!items[orb]) return;
1579   if(who != moPlayer && !items[itOrbEmpathy]) return;
1580   for(int k: {-1, 1}) {
1581     int dir1 = dir + k*bonus;
1582     dir1 = mf->c.fix(dir1);
1583     cell *mt = mf->move(dir1);
1584     sideAttackAt(mf, dir1, mt, who, orb, mf);
1585     }
1586   }
1587 
sideAttack(cell * mf,int dir,eMonster who,int bonuskill)1588 EX void sideAttack(cell *mf, int dir, eMonster who, int bonuskill) {
1589 
1590   int k = tkills();
1591   plague_kills = 0;
1592   sideAttack(mf, dir, who, 1, itOrbSide1);
1593   sideAttack(mf, dir, who, 2, itOrbSide2);
1594   sideAttack(mf, dir, who, 3, itOrbSide3);
1595   k += plague_kills;
1596 
1597   if(who == moPlayer) {
1598     int kills = tkills() - k + bonuskill;
1599     if(kills >= 5) achievement_gain_once("MELEE5");
1600     }
1601   }
1602 
do_we_stab_a_friend(movei mi,eMonster who)1603 EX eMonster do_we_stab_a_friend(movei mi, eMonster who) {
1604   eMonster m = moNone;
1605   do_swords(mi, who, [&] (cell *c, int bb) {
1606     if(!peace::on && canAttack(mi.t, who, c, c->monst, AF_SWORD) && c->monst && isFriendly(c)) m = c->monst;
1607     });
1608 
1609   for(int t=0; t<mi.s->type; t++) {
1610     cell *c = mi.s->move(t);
1611     if(!c) continue;
1612 
1613     bool stabthere = false;
1614     if(logical_adjacent(mi.t, who, c)) stabthere = true;
1615 
1616     if(stabthere && canAttack(mi.t,who,c,c->monst,AF_STAB) && isFriendly(c))
1617       return c->monst;
1618     }
1619 
1620   return m;
1621   }
1622 
wouldkill(const char * msg)1623 EX void wouldkill(const char *msg) {
1624   if(who_kills_me == moWarning)
1625     addMessage(XLAT("This move appears dangerous -- are you sure?"));
1626   else if(who_kills_me == moFireball)
1627     addMessage(XLAT("Cannot move into the current location of another player!"));
1628   else if(who_kills_me == moAirball)
1629     addMessage(XLAT("Players cannot get that far away!"));
1630   else if(who_kills_me == moTongue)
1631     addMessage(XLAT("Cannot push into another player!"));
1632   else if(who_kills_me == moCrushball)
1633     addMessage(XLAT("Cannot push into the same location!"));
1634   else
1635     addMessage(XLAT(msg, who_kills_me));
1636   }
1637 
movecost(cell * from,cell * to,int phase)1638 EX void movecost(cell* from, cell *to, int phase) {
1639   if(from->land == laPower && to->land != laPower && (phase & 1)) {
1640     int n=0;
1641     for(int i=0; i<ittypes; i++)
1642       if(itemclass(eItem(i)) == IC_ORB && items[i] >= 2 && i != itOrbFire)
1643         items[i] = 2, n++;
1644     if(n)
1645       addMessage(XLAT("As you leave, your powers are drained!"));
1646     }
1647 
1648 #if CAP_TOUR
1649   if(from->land != to->land && tour::on && (phase & 2)) {
1650     changes.at_commit([to] { tour::checkGoodLand(to->land); });
1651     }
1652 #endif
1653 
1654   if(to->land == laCrossroads4 && !geometry && (phase & 2) && !cheater) {
1655     achievement_gain_once("CR4");
1656     changes.value_set(chaosUnlocked, true);
1657     }
1658 
1659   if(isHaunted(from->land) && !isHaunted(to->land) && (phase & 2)) {
1660     updateHi(itLotus, truelotus = items[itLotus]);
1661     if(items[itLotus] >= 1) achievement_gain_once("LOTUS1");
1662     if(items[itLotus] >= (big_unlock ? 25 : 10)) achievement_gain_once("LOTUS2");
1663     if(items[itLotus] >= (big_unlock ? 50 : 25)) achievement_gain_once("LOTUS3");
1664     if(items[itLotus] >= 50 && !big_unlock) achievement_gain_once("LOTUS4");
1665     achievement_final(false);
1666     }
1667 
1668   if(geometry == gNormal && celldist(to) == 0 && !usedSafety && gold() >= 100 && (phase & 2))
1669     achievement_gain_once("COMEBACK");
1670 
1671   bool tortoiseOK =
1672     to->land == from->land || to->land == laTortoise ||
1673     (to->land == laDragon && from->land != laTortoise) ||
1674     ls::any_chaos();
1675 
1676   if(tortoise::seek() && !from->item && !tortoiseOK && passable(from, NULL, 0) && (phase & 2)) {
1677     changes.ccell(from);
1678     changes.map_value(tortoise::babymap, from);
1679     from->item = itBabyTortoise;
1680     tortoise::babymap[from] = tortoise::seekbits;
1681     addMessage(XLAT("You leave %the1.", itBabyTortoise));
1682     items[itBabyTortoise]--;
1683     }
1684   }
1685 
1686 }