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 }