1 // Hyperbolic Rogue - monster movement
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file monstermove.cpp
5  *  \brief monster movement
6  */
7 
8 #include "hyper.h"
9 
10 namespace hr {
11 
12 EX int turncount;
13 
14 EX int mutantphase;
15 
16 EX int sagephase = 0;
17 
18 /** list of cells that the monsters are targetting (PCs, allies, Thumpers, etc.) */
19 EX vector<cell*> targets;
20 
21 /** monsters to move, ordered by the number of possible good moves */
22 grow_vector<vector<cell*>> movesofgood;
23 
24 EX vector<pair<cell*, int> > butterflies;
25 
addButterfly(cell * c)26 EX void addButterfly(cell *c) {
27   for(int i=0; i<isize(butterflies); i++)
28     if(butterflies[i].first == c) {
29       butterflies[i].second = 0;
30       return;
31       }
32   butterflies.push_back(make_pair(c, 0));
33   }
34 
makeTrollFootprints(cell * c)35 EX void makeTrollFootprints(cell *c) {
36   if(c->land != laTrollheim) return;
37   if(c->item == itTrollEgg && c->landparam) return;
38   c->landparam = turncount + 100;
39   }
40 
hasPrincessWeapon(eMonster m)41 EX bool hasPrincessWeapon(eMonster m) {
42   return m == moPalace || m == moFatGuard;
43   }
44 
sageheat(cell * c,double v)45 EX void sageheat(cell *c, double v) {
46   HEAT(c) += v;
47   if(c->wall == waFrozenLake && HEAT(c) > .6) c->wall = waLake;
48   }
49 
50 bool sagefresh = true;
51 
52 /** effect of moving monster m from cf to ct
53  *  this is called from moveMonster, or separately from moveIvy/moveWorm,
54  *  or when a dead bird falls (then m == moDeadBird)
55  */
56 
moveEffect(const movei & mi,eMonster m)57 EX void moveEffect(const movei& mi, eMonster m) {
58 
59   auto& cf = mi.s;
60   auto& ct = mi.t;
61   if(cf) destroyWeakBranch(cf, ct, m);
62 
63   mayExplodeMine(ct, m);
64 
65   #if CAP_COMPLEX2
66   if(!isNonliving(m)) terracotta::check_around(ct);
67   #endif
68 
69   if(ct->wall == waMineUnknown && !ct->item && !ignoresPlates(m) && normal_gravity_at(ct))
70     ct->landparam |= 2; // mark as safe
71 
72   if((ct->wall == waClosePlate || ct->wall == waOpenPlate) && !ignoresPlates(m) && normal_gravity_at(ct))
73     toggleGates(ct, ct->wall);
74   if(m == moDeadBird && cf == ct && cellUnstable(cf) && normal_gravity_at(ct)) {
75     fallingFloorAnimation(cf);
76     cf->wall = waChasm;
77     }
78 
79   if(ct->wall == waReptile) ct->wparam = -1;
80 
81   if(ct->wall == waArrowTrap && !ignoresPlates(m) && normal_gravity_at(ct))
82     activateArrowTrap(ct);
83 
84   if(ct->wall == waFireTrap && !ignoresPlates(m) && ct->wparam == 0 && normal_gravity_at(ct)) {
85     playSound(ct, "click");
86     ct->wparam = 1;
87     }
88 
89   if(cf && isPrincess(m)) princess::move(mi);
90 
91   #if CAP_COMPLEX2
92   if(cf && m == moKnight) camelot::move_knight(cf, ct);
93   #endif
94 
95   if(cf && m == moTortoise) {
96     tortoise::move_adult(cf, ct);
97     }
98 
99   if(cf && ct->item == itBabyTortoise && !cf->item) {
100     cf->item = itBabyTortoise;
101     ct->item = itNone;
102     animateMovement(mi.rev(), LAYER_BOAT);
103     tortoise::move_baby(cf, ct);
104     }
105 
106   #if CAP_COMPLEX2
107   if(isDie(m) && mi.proper())
108     dice::roll(mi);
109   #endif
110   }
111 
moveMonster(const movei & mi)112 EX void moveMonster(const movei& mi) {
113   auto& cf = mi.s;
114   auto& ct = mi.t;
115   eMonster m = cf->monst;
116   changes.ccell(cf);
117   changes.ccell(ct);
118   bool fri = isFriendly(cf);
119   if(isDragon(m)) {
120     printf("called for Dragon\n");
121     return;
122     }
123   if(m != moMimic) animateMovement(mi, LAYER_SMALL);
124   // the following line is necessary because otherwise plates disappear only inside the sight range
125   if(cellUnstable(cf) && !ignoresPlates(m)) {
126     fallingFloorAnimation(cf);
127     changes.ccell(cf);
128     cf->wall = waChasm;
129     }
130   moveEffect(mi, m);
131   if(ct->wall == waCamelotMoat &&
132     (m == moShark || m == moCShark || m == moGreaterShark))
133       achievement_gain_once("MOATSHARK");
134   if(m == moTentacleGhost) {
135     changes.ccell(cf);
136     cf->monst = moTentacletail;
137     m = moGhost;
138     }
139   else cf->monst = moNone;
140   if(ct->monst == moTentacletail && m == moGhost) {
141     ct->monst = moTentacleGhost;
142     }
143   else {
144     ct->monst = m;
145     if(m == moWolf) ct->monst = moWolfMoved;
146     if(m == moHunterChanging) ct->stuntime = 1;
147     int d =neighborId(ct, cf);
148     if(ct->monst != moTentacleGhost)
149       ct->mondir = d;
150     if(d >= 0)
151       ct->monmirror = cf->monmirror ^ ct->c.mirror(d);
152     }
153   ct->hitpoints = cf->hitpoints;
154   ct->stuntime = cf->stuntime;
155 
156   if(isMagneticPole(m) || m == moPair) {
157     if(cf->mondir == 15) {
158       ct->monst = moPirate;
159       return;
160       }
161     cell *other_pole = cf->move(cf->mondir);
162     if(other_pole) {
163       ct->mondir = neighborId(ct, other_pole),
164       other_pole->mondir = neighborId(other_pole, ct);
165       }
166     }
167 
168   if(fri || isBug(m) || items[itOrbDiscord]) stabbingAttack(mi, m);
169 
170   if(mi.d == JUMP && m == moVaulter) {
171     cell *cm = common_neighbor(cf, ct);
172     changes.ccell(cm);
173     if(cm->wall == waShrub) cm->wall = waNone;
174     if(cm->wall == waSmallTree) cm->wall = waNone;
175     if(cm->wall == waBigTree) cm->wall = waSmallTree;
176     if(cm->wall == waExplosiveBarrel) explodeBarrel(cm);
177     if(cm->monst)
178       attackMonster(cm, AF_NORMAL | AF_MSG | AF_GETPLAYER, m);
179     ct->mondir = JUMP;
180     }
181 
182   if(isLeader(m)) {
183     if(ct->wall == waBigStatue) {
184       ct->wall = cf->wall;
185       ct->wparam = cf->wparam;
186       cf->wall = waBigStatue;
187       animateMovement(mi.rev(), LAYER_BOAT);
188       }
189 
190     moveBoatIfUsingOne(mi);
191     }
192 
193   if(isTroll(m)) { makeTrollFootprints(ct); makeTrollFootprints(cf); }
194 
195   int inc = incline(cf, ct);
196 
197   if(m == moEarthElemental) {
198     if(!passable(ct, cf, 0)) earthFloor(ct);
199     earthMove(mi);
200     }
201 
202   if(m == moWaterElemental) {
203     placeWater(ct, cf);
204     for(int i=0; i<ct->type; i++) {
205       cell *c2 = ct->move(i);
206       if(!c2) continue;
207       if(c2->wall == waBoat && !(isPlayerOn(c2) && markOrb(itOrbWater))) {
208         addMessage(XLAT("%The1 is washed away!", c2->wall, moWaterElemental));
209         placeWater(c2, ct);
210         }
211       else if(c2->wall == waStrandedBoat) {
212         addMessage(XLAT("%The1 is washed away!", c2->wall, moWaterElemental));
213         c2->wall = waNone;
214         }
215       else if(c2->wall == waDeadTroll) {
216         addMessage(XLAT("%The1 is washed away!", c2->wall, moWaterElemental));
217         c2->wall = waCavefloor;
218         }
219       else if(c2->wall == waDeadTroll2) {
220         addMessage(XLAT("%The1 is washed away!", c2->wall, moWaterElemental));
221         c2->wall = waNone;
222         }
223       else if(isFire(c2) && c2->wall != waEternalFire) {
224         addMessage(XLAT("%The1 is extinguished!", c2->wall, moWaterElemental));
225         if(c2->wall == waBurningDock)
226           c2->wall = waDock;
227         else
228           c2->wall = waNone;
229         }
230       if(shmup::on && isWatery(c2)) shmup::destroyBoats(c2);
231       }
232     }
233 
234   if(m == moGreaterShark) for(int i=0; i<ct->type; i++) {
235     cell *c3 = ct->move(i);
236     if(c3 && c3->wall == waBoat)
237       makeflame(c3, 5, false);
238     }
239 
240   // lancers pierce our friends :(
241   if(m == moLancer) {
242     // printf("lancer stab?\n");
243     forCellEx(c3, ct) if(!logical_adjacent(cf, m, c3)) {
244       if(canAttack(ct, moLancer, c3, c3->monst, AF_LANCE | AF_GETPLAYER)) {
245         attackMonster(c3, AF_LANCE | AF_MSG | AF_GETPLAYER, m);
246         }
247       // this looks the same as effect graphically as exploding right away,
248       // except that it does not kill the lancer
249       if(c3->wall == waExplosiveBarrel)
250         c3->wall = waFireTrap, c3->wparam = 2;
251       }
252     }
253 
254   if(m == moWitchFire) makeflame(cf, 10, false);
255   if(m == moFireElemental) { makeflame(cf, 20, false); if(cf->wparam < 20) cf->wparam = 20; }
256 
257   bool adj = false;
258   if(ct->cpdist == 1 && (items[itOrb37] || !nonAdjacent(cf,ct)) && markOrb(itOrbBeauty) && !isFriendly(ct))
259     adj = true;
260 
261   if(!adj && items[itOrbEmpathy] && items[itOrbBeauty] && !isFriendly(ct)) {
262     for(int i=0; i<ct->type; i++) if(ct->move(i) && isFriendly(ct->move(i)))
263       adj = true, markOrb(itOrbEmpathy), markOrb(itOrbBeauty);
264     }
265 
266   if(adj && ct->stuntime == 0 && !isMimic(m)) {
267     ct->stuntime = 2;
268     checkStunKill(ct);
269     }
270 
271   if(!cellEdgeUnstable(ct)) {
272     if(isMetalBeast(m)) ct->stuntime += 2;
273     if(m == moTortoise) ct->stuntime += 3;
274     if(m == moWorldTurtle) ct->stuntime += 3;
275     if(m == moDraugr && ct->land != laBurial && ct->land != laHalloween) ct->stuntime += 2;
276     if(m == moBrownBug && snakelevel(ct) < snakelevel(cf)) ct->stuntime += 2;
277     if(m == moBrownBug && snakelevel(ct) < snakelevel(cf) - 1) ct->stuntime += 2;
278     if(m == moBrownBug && isWatery(ct) && !isWatery(cf)) ct->stuntime += 2;
279     }
280 
281   if(isWitch(m) && ct->item == itOrbLife && passable(cf, NULL, P_MIRROR)) {
282     // note that Fire Witches don't pick up Orbs of Life,
283     addMessage(XLAT("%The1 picks up %the2!", moWitch, ct->item));
284     cf->monst = moEvilGolem; ct->item = itNone;
285     }
286   else if(m == moWitch) {
287     bool pickup = false;
288     if(ct->item == itOrbFlash)
289       pickup = true, m = moWitchFlash;
290     if(ct->item == itOrbWinter)
291       pickup = true, m = moWitchWinter;
292     if(ct->item == itOrbAether)
293       pickup = true, m = moWitchGhost;
294     if(ct->item == itOrbFire)
295       pickup = true, m = moWitchFire;
296     if(ct->item == itOrbSpeed)
297       pickup = true, m = moWitchSpeed;
298     if(ct->item == itOrbLife)
299       pickup = true, cf->monst = moEvilGolem;
300     if(pickup) {
301       addMessage(XLAT("%The1 picks up %the2!", moWitch, ct->item));
302       ct->monst = m; ct->item = itNone;
303       // speedwitches are stunned to prevent them from making a move
304       // immediately
305       if(m == moWitchSpeed) ct->stuntime = 1;
306       }
307     }
308 
309   if(m == moAirElemental) airmap.push_back(make_pair(ct, 0));
310   if(m == moWolf && ct->land == laVolcano) ct->monst = moLavaWolf;
311   if(m == moLavaWolf && isIcyLand(ct)) ct->monst = moWolfMoved;
312 
313   if(m == moPair) ct->stuntime++;
314 
315   if(inc == -3 && ct->monst == moReptile)
316     ct->stuntime =3;
317   else if(inc == 2 && ct->monst == moReptile)
318     ct->stuntime = 2;
319   else if(inc == 3 && ct->monst == moReptile)
320     ct->stuntime = 3;
321   else if(inc == -3 && !survivesFall(ct->monst) && !passable(cf, ct, P_MONSTER)) {
322     addMessage(XLAT("%The1 falls!", ct->monst));
323     fallMonster(ct, AF_FALL);
324     if(isBull(m) && cf->wall == waRed3)
325       ct->wall = waRed1;
326     }
327   if(isThorny(ct->wall) && !survivesThorns(ct->monst)) {
328     addMessage(XLAT("%The1 is killed by thorns!", ct->monst));
329     playSound(ct, "hit-rose");
330     if(isBull(ct->monst)) ct->wall = waNone;
331     fallMonster(ct, AF_CRUSH);
332     }
333   if(sword::at(ct) && canAttack(NULL, moPlayer, ct, m, AF_SWORD_INTO)) {
334     attackMonster(ct, AF_SWORD_INTO | AF_MSG, moPlayer);
335     achievement_gain_once("GOSWORD");
336     }
337 
338   if(ct->mpdist == 7 && cf->mpdist > 7) {
339     playSeenSound(ct);
340     }
341   }
342 
cannotGo(eMonster m,cell * c)343 EX bool cannotGo(eMonster m, cell *c) {
344   if(m == moCrystalSage && (c->land != laCocytus || HEAT(c) > SAGEMELT || allPlayersInBoats()))
345     return true;
346   return false;
347   }
348 
wantsToStay(eMonster m)349 EX bool wantsToStay(eMonster m) {
350   return m == moCrystalSage && allPlayersInBoats();
351   }
352 
batsAfraid(cell * c)353 EX bool batsAfraid(cell *c) {
354   // bats
355   for(int i=0; i<isize(targets); i++)
356     if(c == targets[i] || isNeighbor(c, targets[i])) {
357       if(!targets[i]->monst && invismove) continue;
358       bool enear = false;
359       forCellEx(c, targets[i])
360         forCellEx(c2, c)
361           forCellEx(c3, c2)
362             if(isActiveEnemy(c3, targets[i]->monst) && c3->monst != moBat &&
363               passable_for(c3->monst, c2, c3, 0) &&
364               passable_for(c3->monst, c, c2, 0)
365               )
366             enear = true;
367       if(!enear) return true;
368       }
369   return false;
370   }
371 
angledist(int t,int d1,int d2)372 EX int angledist(int t, int d1, int d2) {
373   int dd = d1 - d2;
374   while(dd<0) dd += t;
375   while(dd>t/2) dd -= t;
376   if(dd<0) dd = -dd;
377   return dd;
378   }
379 
angledistButterfly(int t,int d1,int d2,bool mirrored)380 EX int angledistButterfly(int t, int d1, int d2, bool mirrored) {
381   int dd = d1 - d2;
382   if(mirrored) dd = -dd;
383   while(dd<0) dd += t;
384   return dd;
385   }
386 
angledist(cell * c,int d1,int d2)387 EX int angledist(cell *c, int d1, int d2) {
388   return angledist(c->type, d1, d2);
389   }
390 
anglestraight(cell * c,int d1,int d2)391 EX bool anglestraight(cell *c, int d1, int d2) {
392   return angledist(c->type, d1, d2) >= c->type / 2;
393   }
394 
bulldist(cell * c)395 EX int bulldist(cell *c) {
396   int low = 0;
397   forCellEx(c2, c) if(c2->cpdist < c->cpdist) low++;
398   return 8 * c->cpdist - low;
399   }
400 
bulldistance(cell * c,cell * d)401 EX int bulldistance(cell *c, cell *d) {
402   int low = 0;
403   int cd = celldistance(c, d);
404   forCellEx(c2, c) if(celldistance(c2, d) < cd) low++;
405   return 8 * cd - low;
406   }
407 
landheattype(cell * c)408 EX int landheattype(cell *c) {
409   if(isIcyLand(c)) return 0;
410   if(c->land == laVolcano) return 2;
411   return 1;
412   }
413 
414 /** for the monster at c1, evaluation of the move to c2
415  *  @param mf what moves are allowed
416  */
417 
moveval(cell * c1,cell * c2,int d,flagtype mf)418 EX int moveval(cell *c1, cell *c2, int d, flagtype mf) {
419   if(!c2) return -5000;
420 
421   eMonster m = c1->monst;
422 
423   // Angry Beasts can only go forward
424   if(m == moRagingBull && c1->mondir != NODIR && !anglestraight(c1, c1->mondir, d)) return -1700;
425 
426   // never move against a rose
427   if(againstRose(c1, c2) && !ignoresSmell(m)) return -1600;
428 
429   // worms cannot attack if they cannot move
430   if(isWorm(m) && !passable_for(c1->monst, c2, c1, P_MONSTER)) return -1700;
431 
432   if(canAttack(c1, m, c2, c2->monst, AF_GETPLAYER | mf) && !(mf & MF_NOATTACKS)) {
433     if(m == moRagingBull && c1->mondir != NODIR) return -1700;
434     if(mf & MF_MOUNT) {
435       if(c2 == dragon::target) return 3000;
436       else if(isFriendlyOrBug(c2)) return 500;
437       else return 2000;
438       }
439     if(isPlayerOn(c2)) return peace::on ? -1700 : 2500;
440     else if(isFriendlyOrBug(c2)) return peace::on ? -1600 : 2000;
441     else return 500;
442     }
443 
444   if(!passable_for(c1->monst, c2, c1, 0))
445     return
446       // never move into a wall
447       (passable_for(c1->monst, c2, c1, P_DEADLY)) ? -1300 :
448       -1700; // move impossible
449 
450   if(slowMover(m) && nogoSlow(c2, c1)) return -1300;
451 
452   if(isPlayerOn(c2)) return -1700; // probably shielded
453 
454   if((mf & MF_MOUNT) && c2 == dragon::target) return 3000;
455 
456   // crystal sages would die out of Cocytus
457   if(cannotGo(m, c2)) return -600;
458 
459   // Rose Beauties keep to the Rose Garden
460   if(m == moRoseBeauty && c2->land != laRose) return -600;
461 
462   if(wantsToStay(m)) return 750;
463 
464   if((m == moRatling || m == moRatlingAvenger) && lastmovetype == lmSkip && !items[itFatigue]) return 650;
465 
466   if(m == moLancer) {
467     bool lancerok = true;
468     forCellEx(c3, c2) if(c1 != c3 && !logical_adjacent(c1, m, c3))
469       if(canAttack(c2, moLancer, c3, c3->monst, AF_LANCE | AF_ONLY_ENEMY))
470         lancerok = false;
471     if(!lancerok) return 750;
472     }
473 
474   bool hunt = true;
475 
476   if(m == moLavaWolf) {
477     // prefers to keep to volcano
478     int clht = landheattype(c1);
479     int dlht = landheattype(c2);
480     if(dlht > clht) return 1510;
481     if(dlht < clht) return 700;
482     // will not hunt the player if these rules do not allow it
483     bool onlava = false;
484     for(cell *c: targets) {
485       if(landheattype(c) >= clht) onlava = true;
486       forCellEx(cc, c) if(landheattype(cc) >= clht) onlava = true;
487       }
488     if(!onlava) hunt = false;
489     }
490 
491   if(m == moWolf) {
492     int val = 1500;
493     if(c2->land == laVolcano) return 1510;
494     if(heat::absheat(c2) <= heat::absheat(c1))
495       return 900;
496     for(int i=0; i<c1->type; i++) {
497       cell *c3 = c1->move(i);
498       if(heat::absheat(c3) > heat::absheat(c2))
499         val--;
500       }
501     return val;
502     }
503 
504   if((mf & MF_MOUNT) && dragon::target)
505     return 1500 + celldistance(c1, dragon::target) - celldistance(c2, dragon::target);
506 
507   // Goblins avoid getting near the Sword
508   if(m == moGoblin && sword::isnear(c2)) return 790;
509   if(m == moBat && batsAfraid(c2)) return 790;
510 
511   if(m == moButterfly)
512     return 1500 + angledistButterfly(c1->type, c1->mondir, d, c1->monmirror);
513 
514   if(m == moRagingBull && c1->mondir != NODIR)
515     return 1500 - bulldist(c2);
516 
517   // actually they just run away
518   if(m == moHunterChanging && c2->pathdist > c1->pathdist) return 1600;
519 
520   if((mf & MF_PATHDIST) && !pathlock) printf("using MF_PATHDIST without path\n");
521 
522   int bonus = 0;
523   if(m == moBrownBug && snakelevel(c2) < snakelevel(c1)) bonus = -10;
524 
525   if(hunt && (mf & MF_PATHDIST) && c2->pathdist < c1->pathdist && !peace::on) return 1500 + bonus; // good move
526 
527   // prefer straight direction when wandering
528   int dd = angledist(c1, c1->mondir, d);
529 
530   // goblins blocked by anglophobia prefer to move around than to stay
531   if(m == moGoblin) {
532     bool swn = false;
533     forCellEx(c3, c1) if(sword::isnear(c3)) swn = true;
534     if(swn) dd += 210;
535     }
536 
537   return 800 + dd;
538   }
539 
540 // stay value
stayval(cell * c,flagtype mf)541 EX int stayval(cell *c, flagtype mf) {
542   if(isShark(c->monst) && !isWatery(c))
543     return 525;
544   if(againstRose(c, NULL) && !ignoresSmell(c->monst)) return -1500;
545   if(!passable_for(c->monst, c, NULL, P_MONSTER | P_MIRROR))
546     return 525;
547   if(cellEdgeUnstable(c))
548     return -1500;
549   if(isRatling(c->monst) && lastmovetype != lmSkip)
550     return 700;
551   // Goblins avoid staying near the Sword (if there is no choice, movement is preferred)
552   if(c->monst == moGoblin && sword::isnear(c)) return 780;
553   // Vikings move in a roughly straight line even if they cannot detect you
554   if(c->monst == moViking && c->wall == waBoat)
555     return 750;
556   // in peaceful, all monsters are wandering
557   if(peace::on && c->monst != moTortoise) return 750;
558   if(isWorm(c->monst)) return 550;
559   if(c->monst == moRagingBull) return -1690; // worse than to stay in place
560   if(c->monst == moBat && batsAfraid(c)) return 575;
561   if(c->monst == moHunterGuard) return 1600; // prefers to stay in place
562   // Lava Wolves will wander if not hunting
563   if(c->monst == moLavaWolf) return 750;
564   return 1000;
565   }
566 
totalbulldistance(cell * c,int k)567 EX int totalbulldistance(cell *c, int k) {
568   shpos.resize(SHSIZE);
569   int tbd = 0;
570   for(int p: player_indices()) {
571     cell *c2  = shpos[(cshpos+SHSIZE-k-1)%SHSIZE][p];
572     if(c2) tbd += bulldistance(c, c2);
573     }
574   return tbd;
575   }
576 
determinizeBull(cell * c,vector<int> & posdir)577 EX void determinizeBull(cell *c, vector<int>& posdir) {
578   // determinize the Angry Beast movement:
579   // use the previous PC's positions as the tiebreaker
580   int nc = isize(posdir);
581   for(int k=0; k<SHSIZE && nc>1; k++) {
582     vector<int> pts(nc);
583     for(int d=0; d<nc; d++) pts[d] = totalbulldistance(c->cmove(posdir[d]), k);
584 
585     int bestpts = 1000;
586     for(int d=0; d<nc; d++) if(pts[d] < bestpts) bestpts = pts[d];
587     int nc0 = 0;
588     for(int d=0; d<nc; d++) if(pts[d] == bestpts) posdir[nc0++] = posdir[d];
589     nc = nc0;
590     }
591   posdir.resize(nc);
592   }
593 
determinizeBullPush(cellwalker bull)594 EX int determinizeBullPush(cellwalker bull) {
595   vector<int> dirs(2);
596   int positive;
597   bull += wstep;
598   cell *c2 = bull.at;
599   if(!(c2->type & 1)) return 1; // irrelevant
600   int d = c2->type / 2;
601   bull += d; dirs[0] = positive = bull.spin;
602   bull -= 2*d; dirs[1] = bull.spin;
603   determinizeBull(c2, dirs);
604   if(dirs[0] == positive) return -1;
605   return 1;
606   }
607 
608 vector<int> global_posdir;
609 
pickMoveDirection(cell * c,flagtype mf)610 EX int pickMoveDirection(cell *c, flagtype mf) {
611   int bestval = stayval(c, mf);
612   global_posdir = {-1};
613 
614   // printf("stayval [%p, %s]: %d\n", c, dnameof(c->monst), bestval);
615   for(int d=0; d<c->type; d++) {
616     cell *c2 = c->move(d);
617     int val = moveval(c, c2, d, mf);
618     // printf("[%d] %p: val=%5d pass=%d\n", d, c2, val, passable(c2,c,0));
619     if(val > bestval) global_posdir.clear(), bestval = val;
620     if(val == bestval) global_posdir.push_back(d);
621     }
622 
623   if(c->monst == moRagingBull)
624     determinizeBull(c, global_posdir);
625 
626   return hrand_elt(global_posdir, -1);
627   }
628 
pickDownDirection(cell * c,flagtype mf)629 EX int pickDownDirection(cell *c, flagtype mf) {
630   vector<int> downs;
631   int bestdif = -100;
632   forCellIdEx(c2, i, c) {
633     if(gravityLevelDiff(c2, c) < 0 && passable_for(c->monst, c2, c, P_MIRROR) &&
634       !isPlayerOn(c2)) {
635       int cdif = i-c->mondir;
636       if(cdif < 0) cdif += c->type;
637       if(cdif > c->type/2) cdif = cdif - c->type;
638       if(cdif < 0) cdif = -2*cdif+1; else cdif = 2*cdif;
639       // printf("i=%d md=%d dif=%d\n", i, c->mondir, cdif);
640       if(c2->wall == waClosePlate || c->wall == waClosePlate)
641         cdif += 20;
642       if(cdif > bestdif) bestdif = cdif, downs.clear();
643       if(cdif == bestdif) downs.push_back(i);
644       }
645     }
646   return hrand_elt(downs, -1);
647   }
648 
649 // Angry Beast attack
650 // note: this is done both before and after movement
beastAttack(cell * c,bool player,bool targetdir)651 EX void beastAttack(cell *c, bool player, bool targetdir) {
652   if(c->mondir == NODIR) return;
653   forCellIdEx(c2, d, c) {
654     bool opposite = targetdir ? (d==c->mondir) : anglestraight(c, d, c->mondir);
655     int flags = AF_BULL;
656     if(player) flags |= AF_GETPLAYER;
657     if(!opposite) flags |= AF_ONLY_FBUG;
658     if(canAttack(c, moRagingBull, c2, c2->monst, flags)) {
659       attackMonster(c2, flags | AF_MSG, moRagingBull);
660       if(c2->monst && c2->stuntime) {
661         cellwalker bull (c, d);
662         int subdir = determinizeBullPush(bull);
663         auto mi = determinePush(bull, subdir, [c2] (movei mi) { return passable(mi.t, c2, P_BLOW) && !isPlayerOn(mi.t); });
664         if(mi.proper())
665           pushMonster(mi);
666         }
667       }
668     if(c2->wall == waThumperOff) {
669       playSound(c2, "click");
670       c2->wall = waThumperOn;
671       c2->wparam = 100;
672       }
673     if(c2->wall == waExplosiveBarrel) {
674       playSound(c2, "click");
675       explodeBarrel(c2);
676       }
677     if(c2->wall == waThumperOn) {
678       cellwalker bull (c, d);
679       int subdir = determinizeBullPush(bull);
680       auto mi = determinePush(bull, subdir, [c] (movei mi) { return canPushThumperOn(mi, c); });
681       if(mi.proper())
682         pushThumper(mi);
683       }
684     }
685   }
686 
687 EX bool quantum;
688 
moveNormal(cell * c,flagtype mf)689 EX cell *moveNormal(cell *c, flagtype mf) {
690   eMonster m = c->monst;
691   if(isPowerMonster(m) && !playerInPower()) return NULL;
692 
693   int d;
694 
695   if(c->stuntime) {
696     if(cellEdgeUnstable(c, MF_STUNNED)) d = pickDownDirection(c, mf), global_posdir = {d};
697     else return NULL;
698     }
699   else {
700     // Angry Beasts attack all neighbors first
701     if(m == moRagingBull) beastAttack(c, true, false);
702     d = pickMoveDirection(c, mf);
703     }
704   if(d == -1) {
705     stayEffect(c);
706     return c;
707     }
708 
709   if(!quantum) {
710     movei mi(c, d);
711     auto& c2 = mi.t;
712     if(isPlayerOn(c2)) {
713       if(m == moCrusher) {
714         addMessage(XLAT("%The1 raises his weapon...", m));
715         crush_next.push_back(c2);
716         c->stuntime = 7;
717         return c2;
718         }
719       killThePlayerAt(m, c2, 0);
720       return c2;
721       }
722 
723     eMonster m2 = c2->monst;
724 
725     if(m2 && m == moCrusher) {
726       addMessage(XLAT("%The1 raises his weapon...", m));
727       crush_next.push_back(c2);
728       c->stuntime = 7;
729       return c2;
730       }
731     else if(m2) {
732       attackMonster(c2, AF_NORMAL | AF_MSG, m);
733       animateAttack(mi, LAYER_SMALL);
734       if(m == moFlailer && m2 == moIllusion)
735         attackMonster(c, 0, m2);
736       return c2;
737       }
738 
739     moveMonster(mi);
740     if(m == moRagingBull) beastAttack(c2, false, false);
741     return c2;
742     }
743   else {
744     bool attacking = false;
745     for(int dir: global_posdir) {
746       cell *c2 = c->move(dir);
747 
748       if(isPlayerOn(c2)) {
749         killThePlayerAt(m, c2, 0);
750         attacking = true;
751         }
752 
753       else {
754         eMonster m2 = c2->monst;
755         if(m2) {
756           attackMonster(c2, AF_NORMAL | AF_MSG, m);
757           if(m == moFlailer && m2 == moIllusion)
758             attackMonster(c, 0, m2);
759           attacking = true;
760           }
761         }
762       }
763 
764     if(!attacking) for(int dir: global_posdir) {
765       movei mi(c, dir);
766       if(!c->monst) c->monst = m;
767       moveMonster(mi);
768       if(m == moRagingBull) beastAttack(mi.t, false, false);
769       }
770     return c->move(d);
771     }
772   }
773 
mountmove(const movei & mi,bool fp)774 EX void mountmove(const movei& mi, bool fp) {
775   for(int i=0; i<numplayers(); i++) {
776     if(playerpos(i) == mi.s) {
777       animateMovement(mi, LAYER_SMALL);
778       if(multi::players > 1) {
779         multi::player[i].at = mi.t;
780         multi::player[i].spin = mi.rev_dir_force();
781         multi::flipped[i] = fp;
782         }
783       else {
784         cwt.at = mi.t;
785         cwt.spin = mi.rev_dir_force();
786         flipplayer = fp;
787         }
788       afterplayermoved();
789       }
790     if(lastmountpos[i] == mi.s && mi.s) {
791       lastmountpos[i] = mi.t;
792       }
793     else if(lastmountpos[i] == mi.t)  {
794       lastmountpos[i] = NULL;
795       }
796     }
797   }
798 
moveWorm(cell * c)799 EX void moveWorm(cell *c) {
800 
801   bool mounted = isMounted(c);
802 
803   if(c->monst == moWormwait) { c->monst = moWorm; return; }
804   else if(c->monst == moTentaclewait) { c->monst = moTentacle; return; }
805   else if(c->monst == moTentacleEscaping) {
806     // explodeAround(c);
807     forCellEx(c2, c)
808       if(canAttack(c, c->monst, c2, c2->monst, mounted ? AF_ONLY_ENEMY : (AF_GETPLAYER | AF_ONLY_FBUG))) {
809         attackMonster(c2, AF_NORMAL | AF_MSG | AF_GETPLAYER, c->monst);
810         }
811     cell *c2 = c;
812     vector<cell*> allcells;
813     while(c2->mondir != NODIR) {
814       allcells.push_back(c2);
815       c2 = c2->move(c2->mondir);
816       }
817     allcells.push_back(c2);
818     for(int i=isize(allcells)-2; i>=0; i--) {
819       cell *cmt = allcells[i+1];
820       cell *cft = allcells[i];
821       auto mi = moveimon(cft);
822       if(cft->monst != moTentacleGhost && cmt->monst != moTentacleGhost)
823         mountmove(mi, false);
824       animateMovement(mi, LAYER_BIG);
825       }
826     c->monst = moNone;
827     if(c->mondir != NODIR) c->move(c->mondir)->monst = moTentacleEscaping;
828     return;
829     }
830   else if(c->monst != moWorm && c->monst != moTentacle) return;
831 
832   eMonster m = c->monst;
833   int id = m - moWorm;
834 
835   int mf = MF_PATHDIST | AF_EAT;
836 
837   if(mounted) mf ^= (MF_MOUNT | MF_PATHDIST);
838 
839   // without this, in 3D geometries, Sandworms explode because no land around them is generated yet
840   forCellCM(c2, c) setdist(c2, 8, c);
841 
842   int dir = pickMoveDirection(c, mf);
843 
844   if(c->wall == waRose) {
845     addMessage(XLAT("%The1 eats %the2!", c->monst, c->wall));
846     c->wall = waNone;
847     dir = -1;
848     }
849 
850   if(dir == -1) {
851     int spices = 0;
852     if(id) {
853       addMessage(XLAT("Cthulhu withdraws his tentacle!"));
854       kills[moTentacle]++;
855       c->monst = moTentacleEscaping;
856       moveWorm(c);
857       }
858     else {
859       kills[moWorm]++;
860       spices = 3;
861       }
862     eItem loc = treasureType(c->land);
863     bool spiceSeen = false;
864     while(c->monst == moWorm || c->monst == moWormtail || c->monst == moTentacle || c->monst == moTentacletail) {
865       // if(!id)
866       explodeAround(c);
867       drawParticles(c, minf[c->monst].color, 16);
868       if(spices > 0 && c->land == laDesert) {
869         if(notDippingForExtra(itSpice, loc)) {
870           c->item = itSpice;
871           if(c->cpdist <= 6) spiceSeen = true;
872           }
873         spices--;
874         }
875       c->monst = moNone;
876       if(c->mondir != NODIR) c = c->move(c->mondir);
877       }
878     if(!id) {
879       if(spiceSeen)
880         addMessage(XLAT("The sandworm explodes in a cloud of Spice!"));
881       else
882         addMessage(XLAT("The sandworm explodes!"));
883       playSound(NULL, "explosion");
884       if(geometry == gZebraQuotient)
885         achievement_gain_once("ZEBRAWORM", rg::special_geometry);
886       }
887     return;
888     }
889 
890   movei mi(c, dir);
891   auto& goal = mi.t;
892 
893   if(isPlayerOn(goal) || goal->monst)
894     attackMonster(goal, AF_EAT | AF_MSG | AF_GETPLAYER, c->monst);
895 
896   if(1) {
897     goal->monst = eMonster(moWormwait + id);
898     moveEffect(mi, eMonster(moWormwait + id));
899 
900     animateMovement(mi, LAYER_BIG);
901     c->monst = eMonster(moWormtail + id);
902     goal->mondir = mi.rev_dir_or(NODIR);
903     goal->monmirror = c->monmirror ^ c->c.mirror(dir);
904     setdist(goal, 6, nullptr);
905 
906     mountmove(mi, true);
907 
908     if(id) {
909       cell *c2 = c, *c3 = c2;
910       while(c2->monst == moTentacletail || c2->monst == moTentacleGhost) {
911         auto mim = moveimon(c2).rev();
912         if(!mim.proper()) return;
913         c3 = c2, c2 = mim.s;
914         if(c3->monst != moTentacleGhost && c2->monst != moTentacleGhost)
915           mountmove(mim, true);
916         animateMovement(mim, LAYER_BIG);
917         }
918       }
919 
920     cell *c2 = c, *c3 = c2;
921     for(int a=0; a<WORMLENGTH; a++) {
922       if(c2->monst == moWormtail) {
923         movei mim = moveimon(c2).rev();
924         if(!mim.proper()) {
925           drawParticles(c2, (linf[c2->land].color & 0xF0F0F0), 16);
926           return;
927           }
928         c3 = c2, c2 = mim.s;
929         mountmove(mim, true);
930         animateMovement(mim, LAYER_BIG);
931         }
932       }
933 
934     if(c2->monst == moWormtail) c2->monst = moNone, c3->mondir = NODIR;
935     }
936 
937   }
938 
ivynext(cell * c)939 EX void ivynext(cell *c) {
940   cellwalker cw(c, c->mondir, c->monmirror);
941 
942   // check the mirroring status
943   cell *c2 = c;
944   while(true) {
945     if(c2->monst == moIvyRoot) break;
946     if(!proper(c2, c2->mondir))
947       return; /* incorrect data */
948     if(!isIvy(c2->monst)) break;
949     if(c2->c.mirror(c2->mondir)) cw.mirrored = !cw.mirrored;
950     c2 = c2->move(c2->mondir);
951     }
952 
953   cw.at->monst = moIvyWait;
954   bool findleaf = false;
955   while(true) {
956     cw += 1;
957     if(cw.spin == signed(cw.at->mondir)) {
958       if(findleaf) {
959         cw.at->monst = moIvyHead; break;
960         }
961       cw.at->monst = moIvyWait;
962       cw += wstep;
963       continue;
964       }
965     cw += wstep;
966     if(cw.at->monst == moIvyWait && signed(cw.at->mondir) == cw.spin) {
967       cw.at->monst = moIvyBranch;
968       findleaf = true; continue;
969       }
970     cw += wstep;
971     }
972   }
973 
974 // this removes Ivy, but also potentially causes Vines to grow
removeIvy(cell * c)975 EX void removeIvy(cell *c) {
976   eMonster m = c->monst;
977   c->monst = moNone; // NEWYEARFIX
978   for(int i=0; i<c->type; i++)
979   // note that semi-vines don't count
980     if(c->move(i)->wall == waVinePlant) {
981       destroyHalfvine(c);
982       if (!do_not_touch_this_wall(c))
983         c->wall = waVinePlant;
984       }
985   if(c->wall != waVinePlant) {
986     if(m == moIvyDead)
987       m = moIvyWait;
988     drawParticles(c, minf[m].color, 2);
989     }
990   }
991 
moveivy()992 EX void moveivy() {
993   if(isize(ivies) == 0) return;
994   if(racing::on) return;
995   pathdata pd(moIvyRoot);
996   for(int i=0; i<isize(ivies); i++) {
997     cell *c = ivies[i];
998     cell *co = c;
999     if(c->monst != moIvyHead) continue;
1000     ivynext(c);
1001 
1002     int pd = c->pathdist;
1003 
1004     movei mi(nullptr, nullptr, NODIR);
1005 
1006     while(c->monst != moIvyRoot) {
1007       if(!isIvy(c->monst)) {
1008         raiseBuggyGeneration(c, "that's not an Ivy!");
1009         break;
1010         }
1011       if(c->mondir == NODIR) {
1012         raiseBuggyGeneration(c, "wrong mondir!");
1013         break;
1014         }
1015 
1016       forCellIdEx(c2, j, c) {
1017         if(canAttack(c, c->monst, c2, c2->monst, AF_ONLY_FRIEND | AF_GETPLAYER)) {
1018           if(isPlayerOn(c2))
1019             killThePlayerAt(c->monst, c2, 0);
1020           else {
1021             if(attackJustStuns(c2, 0, c->monst))
1022               addMessage(XLAT("The ivy attacks %the1!", c2->monst));
1023             else if(isNonliving(c2->monst))
1024               addMessage(XLAT("The ivy destroys %the1!", c2->monst));
1025             else
1026               addMessage(XLAT("The ivy kills %the1!", c2->monst));
1027             attackMonster(c2, AF_NORMAL, c->monst);
1028             }
1029           continue;
1030           }
1031         if(c2 && c2->pathdist < pd && passable(c2, c, 0) && !strictlyAgainstGravity(c2, c, false, MF_IVY))
1032           mi = movei(c, j), pd = c2->pathdist;
1033         }
1034       c = c->move(c->mondir);
1035       }
1036 
1037     auto& mto = mi.t;
1038 
1039     if(mto && mto->cpdist) {
1040       animateMovement(mi, LAYER_BIG);
1041       mto->monst = moIvyWait, mto->mondir = mi.rev_dir_or(NODIR);
1042       mto->monmirror = mi.s->monmirror ^ mi.mirror();
1043       moveEffect(mi, moIvyWait);
1044       // if this is the only branch, we want to move the head immediately to mto instead
1045       if(mi.s->monst == moIvyHead) {
1046         mto->monst = moIvyHead; co->monst = moIvyBranch;
1047         }
1048       }
1049     else if(!proper(co, co->mondir) || !co->move(co->mondir)) ; /* should not happen */
1050     else if(co->move(co->mondir)->monst != moIvyRoot) {
1051       // shrink useless branches, but do not remove them completely (at the root)
1052       if(co->monst == moIvyHead) co->move(co->mondir)->monst = moIvyHead;
1053       removeIvy(co);
1054       }
1055     }
1056   }
1057 
1058 vector<cell*> gendfs;
1059 
1060 int targetcount;
1061 
isTargetOrAdjacent(cell * c)1062 EX bool isTargetOrAdjacent(cell *c) {
1063   for(int i=0; i<targetcount; i++)
1064     if(gendfs[i] == c || isNeighbor(gendfs[i], c))
1065       return true;
1066   return false;
1067   }
1068 
groupmove2(const movei & mi,eMonster movtype,flagtype mf)1069 EX void groupmove2(const movei& mi, eMonster movtype, flagtype mf) {
1070   auto& c = mi.s;
1071   auto& from = mi.t; // note: we are moving from 'c' to 'from'!'
1072   if(!c) return;
1073 
1074   if(c->pathdist == 0) return;
1075 
1076   if(movtype == moKrakenH && isTargetOrAdjacent(from)) ;
1077 /*  else if(passable_for(movtype, from, c, P_ONPLAYER | P_CHAIN | P_MONSTER)) ;
1078   else if(canAttack(c, movtype, from, from->monst, AF_GETPLAYER)) ; */
1079   else if(from->wall == waThumperOn) ;
1080   else if(passable_for(movtype, from, c, P_CHAIN | P_MONSTER)) ;
1081   else if(canAttack(c, movtype, from, from->monst, AF_GETPLAYER | AF_NOSHIELD)) ;
1082   else if(isMagneticPole(movtype)) {
1083     // a special case here -- we have to ignore the illegality of
1084     // the 'second' move due to an adjacent opposite pole
1085     forCellIdEx(c2, d, c)
1086       if(c2->monst == movtype) {
1087         cell *c3 = c2->move(c2->mondir);
1088         eMonster m2 = c3->monst;
1089         c3->monst = moNone;
1090         bool ok =
1091            passable_for(movtype, from, c, P_CHAIN | P_MONSTER)
1092         && passable_for(movtype, c, c2, P_CHAIN | P_MONSTER);
1093         c3->monst = m2;
1094         if(ok) groupmove2(movei(c, d).rev(), movtype, mf);
1095         }
1096     }
1097   else return;
1098 
1099   if(from->monst) {
1100     if(mf & MF_MOUNT) {
1101       // don't go through friends
1102       if(isFriendlyOrBug(from)) return;
1103       }
1104     else {
1105       // go through the player (even mounted)
1106       if(isPlayerOn(from)) ;
1107       // go through the mounted dragon
1108       else if(isDragon(from->monst) && isFriendlyOrBug(from)) ;
1109       // but not through other worms
1110       else if(isWorm(from)) return;
1111       // go through other friends
1112       else if(isFriendlyOrBug(from)) ;
1113       else return;
1114       }
1115     }
1116 
1117   // Kraken movement
1118   if(movtype == moKrakenH && c->monst == moKrakenT && c->stuntime == 0)
1119     kraken::trymove(c);
1120 
1121   if(movegroup(c->monst) == movtype) {
1122 
1123     int af = AF_ONLY_FBUG | AF_GETPLAYER;
1124     if(mf & MF_MOUNT) af = 0;
1125 
1126     if(!passable_for(movtype, from, c, P_ONPLAYER | P_MONSTER)) return;
1127     if(!ignoresSmell(c->monst) && againstRose(c, from)) return;
1128     if((mf & MF_ONLYEAGLE) && c->monst != moEagle && c->monst != moBat)
1129       return;
1130     if((mf & MF_ONLYEAGLE) && bird_disruption(c) && markOrb(itOrbGravity)) return;
1131     // in the gravity lands, eagles cannot ascend in their second move
1132     if((mf & MF_ONLYEAGLE) && gravityLevelDiff(c, from) < 0) {
1133       onpath(c, 0);
1134       return;
1135       }
1136     if((mf & MF_NOFRIEND) && isFriendly(c)) return;
1137     if((mf & MF_MOUNT) && !isMounted(c)) return;
1138     if(isRatling(c->monst) && lastmovetype == lmSkip) return;
1139 
1140     if(c->stuntime) return;
1141     if(c->monst == moBat && batsAfraid(from)) return;
1142 
1143     // note: move from 'c' to 'from'!
1144     if(!(mf & MF_NOATTACKS)) for(int j=0; j<c->type; j++)
1145       if(c->move(j) && canAttack(c, c->monst, c->move(j), c->move(j)->monst, af)) {
1146         attackMonster(c->move(j), AF_NORMAL | AF_GETPLAYER | AF_MSG, c->monst);
1147         animateAttack(movei(c, j), LAYER_SMALL);
1148         onpath(c, 0);
1149         // XLATC eagle
1150         return;
1151         }
1152 
1153     if(from->cpdist == 0 || from->monst) { onpath(c, 0); return; }
1154 
1155     if(movtype == moDragonHead) {
1156       dragon::move(mi);
1157       return;
1158       }
1159 
1160     moveMonster(mi);
1161 
1162     onpath(from, 0);
1163 
1164     if(isDie(mi.t->monst)) {
1165       /* other dice will not pathfind through the original cell */
1166       /* this makes it easier for the player to roll dice correctly */
1167       onpath(c, 0);
1168       return;
1169       }
1170     }
1171   onpath(c, 0);
1172   // MAXGCELL
1173   if(isize(gendfs) < 1000 || c->cpdist <= 6) gendfs.push_back(c);
1174   }
1175 
groupmove(eMonster movtype,flagtype mf)1176 EX void groupmove(eMonster movtype, flagtype mf) {
1177   pathdata pd(0);
1178   gendfs.clear();
1179 
1180   if(mf & MF_MOUNT) {
1181     if(dragon::target) gendfs.push_back(dragon::target);
1182     if(movtype == moDragonHead) {
1183       for(int i=0; i<isize(dcal); i++) {
1184         cell *c = (i == 0 && dragon::target) ? dragon::target : dcal[i];
1185         if(!c->monst) continue;
1186         if(isFriendlyOrBug(c)) continue;
1187         forCellIdEx(c2, d, c) if(c2->monst && isMounted(c2)) {
1188           groupmove2(movei(c,d).rev(),movtype,mf);
1189           }
1190         }
1191       }
1192     }
1193   else {
1194     if(!peace::on) for(int i=0; i<isize(targets); i++) gendfs.push_back(targets[i]);
1195 
1196     if(invisfish && (movtype == moSlime || movtype == moShark || movtype == moKrakenH))
1197       for(cell *pc: player_positions())
1198         gendfs.push_back(pc);
1199     }
1200 
1201   targetcount = isize(gendfs);
1202 
1203   for(int i=0; i<isize(gendfs); i++) {
1204     cell *c = gendfs[i];
1205     vector<int> dirtable;
1206 
1207     forCellIdAll(c2,t,c) dirtable.push_back(t);
1208     hrandom_shuffle(dirtable);
1209 
1210     for(auto& t: dirtable) {
1211       groupmove2(movei(c, t).rev(),movtype,mf);
1212       }
1213 
1214     if(movtype == moEagle && c->monst == moNone && !isPlayerOn(c) && !bird_disruption(c)) {
1215       cell *c2 = whirlwind::jumpFromWhereTo(c, false);
1216       groupmove2(movei(c2, c, STRONGWIND), movtype, mf);
1217       }
1218 
1219     if(frog_power(movtype) && c->monst == moNone && !isPlayerOn(c)) {
1220       forCellEx(c2, c) forCellEx(c3, c2)
1221         groupmove2(movei(c3, c, JUMP), movtype, mf);
1222       }
1223     }
1224 
1225   if(movtype != moDragonHead) for(int i=0; i<isize(dcal); i++) {
1226     cell *c = dcal[i];
1227     if((mf & MF_ONLYEAGLE) && c->monst != moEagle && c->monst != moBat) return;
1228     if(movegroup(c->monst) == movtype && c->pathdist != 0) {
1229       cell *c2 = moveNormal(c, mf);
1230       if(c2) onpath(c2, 0);
1231       }
1232     }
1233   }
1234 
1235 // Hex monsters
1236 
1237 vector<cell*> hexdfs;
1238 
moveHexSnake(const movei & mi,bool mounted)1239 EX void moveHexSnake(const movei& mi, bool mounted) {
1240   // note: move from 'c' to 'from'!
1241   auto& from = mi.t;
1242   auto& c = mi.s;
1243   setdist(from, 6, nullptr);
1244   if(from->wall == waBoat) from->wall = waSea;
1245   moveEffect(mi, c->monst);
1246   from->monst = c->monst; from->mondir = mi.rev_dir_or(NODIR); from->hitpoints = c->hitpoints;
1247   c->monst = moHexSnakeTail;
1248   preventbarriers(from);
1249 
1250   animateMovement(mi, LAYER_BIG);
1251   mountmove(mi, true);
1252 
1253   cell *c2 = c, *c3=c2;
1254   for(int a=0;; a++) if(c2->monst == moHexSnakeTail) {
1255     if(a == ROCKSNAKELENGTH) { c2->monst = moNone, c3->mondir = NODIR; break; }
1256     auto mim = moveimon(c2).rev();
1257     if(!mim.proper()) break;
1258     mountmove(mim, true);
1259     animateMovement(mim, LAYER_BIG);
1260     c3 = c2, c2 = mim.s;
1261     }
1262     else break;
1263   }
1264 
snakeAttack(cell * c,bool mounted)1265 EX void snakeAttack(cell *c, bool mounted) {
1266   for(int j=0; j<c->type; j++)
1267     if(c->move(j) && canAttack(c, moHexSnake, c->move(j), c->move(j)->monst,
1268       mounted ? AF_ONLY_ENEMY : (AF_ONLY_FBUG | AF_GETPLAYER))) {
1269         eMonster m2 = c->move(j)->monst;
1270         attackMonster(c->move(j), AF_NORMAL | AF_GETPLAYER | AF_MSG, moHexSnake);
1271         spread_plague(c, c->move(j), j, moHexSnake);
1272         produceGhost(c->move(j), moHexSnake, m2);
1273         }
1274   }
1275 
goodmount(cell * c,bool mounted)1276 EX bool goodmount(cell *c, bool mounted) {
1277   if(mounted) return isMounted(c);
1278   else return !isMounted(c);
1279   }
1280 
inpair(cell * c,int colorpair)1281 EX int inpair(cell *c, int colorpair) {
1282   return (colorpair >> pattern_threecolor(c)) & 1;
1283   }
1284 
snake_pair(cell * c)1285 EX int snake_pair(cell *c) {
1286   if(c->mondir == NODIR)
1287     return (1 << pattern_threecolor(c));
1288   else
1289     return (1 << pattern_threecolor(c)) | (1 << pattern_threecolor(c->move(c->mondir)));
1290   }
1291 
1292 // note: move from 'c' to 'from'!
hexvisit(cell * c,cell * from,int d,bool mounted,int colorpair)1293 EX void hexvisit(cell *c, cell *from, int d, bool mounted, int colorpair) {
1294   if(!c) return;
1295   if(cellUnstable(c) || cellEdgeUnstable(c)) return;
1296   if(c->pathdist == 0) return;
1297 
1298   if(cellUnstableOrChasm(c) || cellUnstableOrChasm(from)) return;
1299 
1300   /* if(c->monst == moHexSnake)
1301     printf("%p:%p %s %d\n", from, c, dnameof(from->monst), passable(from, c, true, false, false)); */
1302 
1303   if(from->cpdist && (!passable(from, c, P_MONSTER|P_WIND|P_FISH))) return;
1304 
1305   if(c->monst == moHexSnake && snake_pair(c) == colorpair) {
1306     // printf("got snake\n");
1307 
1308     if(!inpair(from, colorpair)) return;
1309     if(!goodmount(c, mounted)) return;
1310 
1311     if(canAttack(c, moHexSnake, from, from->monst, AF_EAT | (mounted ? AF_ONLY_ENEMY : AF_ONLY_FBUG | AF_GETPLAYER))) {
1312       attackMonster(from, AF_MSG | AF_EAT | AF_GETPLAYER, c->monst);
1313       }
1314 
1315     if(from->cpdist == 0 || from->monst) return;
1316 
1317     snakeAttack(c, mounted);
1318     moveHexSnake(movei(from, d).rev(), mounted);
1319     }
1320 
1321   onpath(c, 0);
1322 
1323   // MAXGCELL
1324   if(isize(hexdfs) < 2000 || c->cpdist <= 6)
1325     hexdfs.push_back(c);
1326   }
1327 
movehex(bool mounted,int colorpair)1328 EX void movehex(bool mounted, int colorpair) {
1329   pathdata pd(3);
1330   hexdfs.clear();
1331 
1332   if(mounted) {
1333     if(dragon::target && dragon::target->monst != moHexSnake) {
1334       hexdfs.push_back(dragon::target);
1335       onpath(dragon::target, 0);
1336       }
1337     }
1338   else for(cell *c: targets) {
1339     hexdfs.push_back(c);
1340     onpath(c, 0);
1341     }
1342   //hexdfs.push_back(cwt.at);
1343 
1344   for(int i=0; i<isize(hexdfs); i++) {
1345     cell *c = hexdfs[i];
1346     vector<int> dirtable;
1347     for(int t=0; t<c->type; t++) if(c->move(t) && inpair(c->move(t), colorpair))
1348       dirtable.push_back(t);
1349 
1350     hrandom_shuffle(dirtable);
1351     for(auto& t: dirtable) {
1352       hexvisit(c->move(t), c, t, mounted, colorpair);
1353       }
1354     }
1355   }
1356 
movehex_rest(bool mounted)1357 EX void movehex_rest(bool mounted) {
1358   for(int i=0; i<isize(hexsnakes); i++) {
1359     cell *c = hexsnakes[i];
1360     int colorpair;
1361     if(c->monst == moHexSnake) {
1362       colorpair = snake_pair(c);
1363       if(!goodmount(c, mounted)) continue;
1364       vector<int> dirtable = hrandom_permutation(c->type);
1365       for(int u=0; u<c->type; u++) {
1366         createMov(c, dirtable[u]);
1367         if(inpair(c->move(dirtable[u]), colorpair))
1368           hexvisit(c, c->move(dirtable[u]), c->c.spin(dirtable[u]), mounted, colorpair);
1369         }
1370       }
1371     if(c->monst == moHexSnake) {
1372       snakeAttack(c, mounted);
1373       kills[moHexSnake]++;
1374       playSound(c, "die-troll");
1375       cell *c2 = c;
1376       while(c2->monst == moHexSnakeTail || c2->monst == moHexSnake) {
1377         if(c2->monst != moHexSnake && c2->mondir != NODIR)
1378           snakepile(c2, moHexSnake);
1379         snakepile(c2, moHexSnake);
1380         c2->monst = moNone;
1381         if(c2->mondir == NODIR) break;
1382         c2 = c2->move(c2->mondir);
1383         }
1384       }
1385     }
1386   }
1387 
movemutant()1388 EX void movemutant() {
1389   manual_celllister mcells;
1390   for(cell *c: currentmap->allcells()) mcells.add(c);
1391   if(!bounded)
1392     for(int i=0; i<isize(mcells.lst); i++) {
1393       cell *c = mcells.lst[i];
1394       if(c->land == laClearing && c->monst != moMutant && !pseudohept(c))
1395         forCellEx(c2, c) forCellEx(c3, c2) if(celldistAlt(c3) < celldistAlt(c))
1396           mcells.add(c3);
1397       }
1398 
1399   vector<cell*> young;
1400   for(cell *c: mcells.lst)
1401     if(c->monst == moMutant && c->stuntime == mutantphase)
1402       young.push_back(c);
1403 
1404   for(int j=1; j<isize(young); j++)
1405     swap(young[j], young[hrand(j+1)]);
1406 
1407   mutantphase++;
1408   mutantphase &= 15;
1409 
1410   for(int i=0; i<isize(young); i++) {
1411     cell *c = young[i];
1412     for(int j=0; j<c->type; j++) {
1413       movei mi(c, j);
1414       auto& c2 = mi.t;
1415       if(!c2) continue;
1416 
1417       if(c2->monst != moMutant && canAttack(c, moMutant, c2, c2->monst, AF_ONLY_FBUG | AF_GETPLAYER)) {
1418         attackMonster(c2, AF_NORMAL | AF_MSG | AF_GETPLAYER, moMutant);
1419         continue;
1420         }
1421 
1422       if(isPlayerOn(c2)) continue;
1423 
1424       if((c2->land == laOvergrown || !pseudohept(c2)) && passable(c2, c, 0)) {
1425         if(c2->land == laClearing && !bounded && c2->mpdist > 7) continue;
1426         c2->monst = moMutant;
1427         c2->mondir = c->c.spin(j);
1428         c2->stuntime = mutantphase;
1429         animateMovement(mi, LAYER_BIG);
1430         }
1431       }
1432     }
1433   }
1434 
1435 #if HDR
1436 #define SHSIZE 16
1437 #endif
1438 
1439 EX vector<array<cell*, MAXPLAYER>> shpos;
1440 EX int cshpos = 0;
1441 
1442 EX cell *lastmountpos[MAXPLAYER];
1443 
clearshadow()1444 EX void clearshadow() {
1445   shpos.resize(SHSIZE);
1446   for(int i=0; i<SHSIZE; i++) for(int p=0; p<MAXPLAYER; p++)
1447     shpos[i][p] = NULL;
1448   }
1449 
1450 /** \brief kill the shadow by clearing its history -- c is provided for multiplayer */
kill_shadow_at(cell * c)1451 EX void kill_shadow_at(cell *c) {
1452   for(int p=0; p<MAXPLAYER; p++)
1453     if(shpos[cshpos][p] == c)
1454       for(int i=0; i<SHSIZE; i++)
1455         changes.value_set(shpos[i][p], (cell*) nullptr);
1456   }
1457 
moveshadow()1458 EX void moveshadow() {
1459 
1460   cell *shfrom = NULL;
1461 
1462   shpos.resize(SHSIZE);
1463   for(int p: player_indices()) {
1464     cell *c = shpos[cshpos][p];
1465     if(c && c->monst == moShadow) {
1466       for(int j=0; j<c->type; j++)
1467         if(c->move(j) && canAttack(c, moShadow, c->move(j), c->move(j)->monst, AF_ONLY_FBUG | AF_GETPLAYER))
1468           attackMonster(c->move(j), AF_NORMAL | AF_MSG | AF_GETPLAYER, c->monst);
1469       c->monst = moNone;
1470       shfrom = c;
1471       }
1472     shpos[cshpos][p] = playerpos(p);
1473     }
1474   cshpos = (cshpos+1) % SHSIZE;
1475   for(int p: player_indices()) {
1476     cell* where = shpos[cshpos][p];
1477     if(where && where->monst == moNone && where->cpdist && among(where->land, laGraveyard, laCursed)) {
1478       if(sword::at(where)) {
1479         kill_shadow_at(where);
1480         fightmessage(moShadow, moPlayer, false, AF_SWORD_INTO);
1481         continue;
1482         }
1483       if(shfrom) animateMovement(match(shfrom, where), LAYER_SMALL);
1484       where->monst = moShadow;
1485       where->hitpoints = p;
1486       where->stuntime = 0;
1487       // the Shadow sets off the mines and stuff
1488       moveEffect(movei(where, where, NODIR), moShadow);
1489       }
1490     }
1491   }
1492 
moveghosts()1493 EX void moveghosts() {
1494 
1495   if(invismove) return;
1496   movesofgood.clear();
1497 
1498   for(int i=0; i<isize(ghosts); i++) {
1499     cell *c = ghosts[i];
1500 
1501     if(c->stuntime) continue;
1502     if(isPowerMonster(c) && !playerInPower()) continue;
1503 
1504     if(isGhostMover(c->monst)) {
1505       int goodmoves = 0;
1506 
1507       for(int k=0; k<c->type; k++) if(c->move(k) && c->move(k)->cpdist < c->cpdist)
1508         if(ghostmove(c->monst, c->move(k), c, 0) && !isPlayerOn(c->move(k)))
1509           goodmoves++;
1510 
1511       movesofgood.grow(goodmoves).push_back(c);
1512       }
1513     }
1514 
1515   for(auto& v: movesofgood) for(cell *c: v) {
1516 
1517     if(c->stuntime) continue;
1518     if(isPowerMonster(c) && !playerInPower()) continue;
1519 
1520     if(isGhostMover(c->monst) && c->cpdist >= 1) {
1521 
1522       vector<int> mdir;
1523 
1524       for(int j=0; j<c->type; j++)
1525         if(c->move(j) && canAttack(c, c->monst, c->move(j), c->move(j)->monst, AF_GETPLAYER | AF_ONLY_FBUG)) {
1526           // XLATC ghost/greater shark
1527 
1528           attackMonster(c->move(j), AF_NORMAL | AF_MSG | AF_GETPLAYER, c->monst);
1529           goto nextghost;
1530           }
1531 
1532       for(int k=0; k<c->type; k++) if(c->move(k) && c->move(k)->cpdist < c->cpdist)
1533         if(ghostmove(c->monst, c->move(k), c, 0))
1534           mdir.push_back(k);
1535       if(mdir.empty()) continue;
1536       int d = hrand_elt(mdir);
1537       cell *c2 = c->move(d);
1538       if(c2->monst == moTortoise && c2->stuntime > 1) {
1539         addMessage(XLAT("%The1 scares %the2 a bit!", c->monst, c2->monst));
1540         c2->stuntime = 1;
1541         }
1542       else moveMonster(movei(c, d));
1543 
1544       }
1545     nextghost: ;
1546     }
1547   }
1548 
1549 /** for an ally m at c, evaluate staying in place */
stayvalue(eMonster m,cell * c)1550 EX int stayvalue(eMonster m, cell *c) {
1551   if(!passable_for(c->monst, c, NULL, P_MONSTER | P_MIRROR))
1552     return -1501;
1553   if(cellEdgeUnstable(c))
1554     return -1501;
1555   if(againstRose(c, NULL) && !ignoresSmell(c->monst)) return -1500;
1556   return 100;
1557   }
1558 
1559 /** for an ally m at c, evaluate moving to c2 */
movevalue(eMonster m,cell * c,int dir,flagtype flags)1560 EX int movevalue(eMonster m, cell *c, int dir, flagtype flags) {
1561 
1562   auto mi = movei(c, dir);
1563   auto& c2 = mi.t;
1564   int val = 0;
1565 
1566   if(isPlayerOn(c2)) val = -3000;
1567   else if(againstRose(c, c2)  && !ignoresSmell(m)) return -1200;
1568   else if(m == moPrincess && c2->stuntime && hasPrincessWeapon(c2->monst) &&
1569     !cellDangerous(c) && !cellDangerous(c2) && canAttack(c, m, c2, c2->monst, AF_IGNORE_UNARMED | flags)) {
1570     val = 15000;
1571     }
1572   else if(canAttack(c,m,c2,c2->monst,flags))
1573     val =
1574     (!passable_for(c->monst, c, NULL, P_MONSTER | P_MIRROR)) ? 100 :
1575     (m == moPrincessArmed && isPrincess(c2->monst)) ? 14000 : // jealousy!
1576     isActiveEnemy(c2,m) ? 12000 :
1577     among(c2->monst, moSkeleton, moMetalBeast, moReptile, moTortoise, moSalamander, moTerraWarrior, moBrownBug) ? -400 :
1578     isIvy(c2) ? 8000 :
1579     isInactiveEnemy(c2,m) ? 1000 :
1580     -500;
1581 
1582   else if(passable_for(m, c2, c, 0)) {
1583 #if CAP_COMPLEX2
1584     if(mine::marked_mine(c2) && !ignoresPlates(m))
1585       val = 50;
1586     else
1587 #endif
1588       val = 4000;
1589 
1590     int tk = tkills();
1591     changes.init(true);
1592     moveMonster(mi);
1593     int tk2 = tkills();
1594     bool b = monstersnear(mi.t, m);
1595     changes.rollback();
1596     if(b) val = 50;
1597     else if(tk2 > tk) val += 1000 + 200 * (tk2 - tk);
1598     }
1599   else if(passable_for(m, c2, c, P_DEADLY)) return -1100;
1600   else return -1750;
1601 
1602   if(c->monst == moGolem ) {
1603     val -= c2->pathdist;
1604     }
1605   else if(c->monst == moFriendlyGhost )
1606     val += c2->cpdist - 40;
1607   else if(c->monst == moMouse) {
1608     int d;
1609     if(!euclid && (c2->land != laPalace || !c2->master->alt)) d = 200;
1610     else d = celldistAlt(c2);
1611     // first rule: suicide if the Princess is killed,
1612     // by monstersnear or jumping into a chasm
1613     princess::info *i = princess::getPrisonInfo(c);
1614     if(i && !i->princess) {
1615       if(val == 50 || c2->wall == waChasm) val = 20000;
1616       }
1617     // second rule: move randomly if the Princess is saved
1618     if(i && i->bestdist > 6)
1619       ;
1620     // third rule: do not get too far from the Princess
1621     else if(d > 150)
1622       val -= (700+d);
1623     // fourth rule: do not get too far from the Rogue
1624     // NOTE: since Mouse is not a target, we can use
1625     // the full pathfinding here instead of cpdist!
1626     else if(c2->pathdist > 3 && c2->pathdist <= 19)
1627       val -= (500+c2->pathdist * 10);
1628     else if(c2->pathdist > 19)
1629       val -= (700);
1630     // fifth rule: get close to the Princess, to point the way
1631     else
1632       val -= (250+d);
1633     /*
1634     // avoid stepping on trapdoors and plates
1635     // (REMOVED BECAUSE MICE NO LONGER ACTIVATE TRAPDOORS AND PLATES)
1636     // note that the Mouse will still step on the trapdoor
1637     // if it wants to get close to you and there is no other way
1638     if(c2->wall == waTrapdoor)
1639       val -= 5;
1640     */
1641     }
1642   if(isPrincess(c->monst)) {
1643 
1644     int d = c2->pathdist;
1645     if(d <= 3) val -= d;
1646     else val -= 10 * d;
1647 
1648     // the Princess also avoids stepping on pressure plates
1649     if(c2->wall == waClosePlate || c2->wall == waOpenPlate || c2->wall == waTrapdoor)
1650       val -= 5;
1651     }
1652   if(c->monst == moTameBomberbird) {
1653     int d = c2->pathdist;
1654     if(d == 1 && c->pathdist > 1) d = 5;
1655     if(d == 2 && c->pathdist > 2) d = 4;
1656     val -= d;
1657     }
1658   if(c->monst == moKnight && (eubinary || c2->master->alt)) {
1659     val -= celldistAlt(c2);
1660     // don't go to external towers
1661     if(c2->wall == waTower && c2->wparam == 1 && !c2->monst)
1662       return 60;
1663     }
1664   return val;
1665   }
1666 
movegolems(flagtype flags)1667 EX void movegolems(flagtype flags) {
1668   if(items[itOrbEmpathy] && items[itOrbSlaying])
1669     flags |= AF_CRUSH;
1670   int qg = 0;
1671   for(int i=0; i<isize(golems); i++) {
1672     cell *c = golems[i];
1673     eMonster m = c->monst;
1674     pathdata pd(m, false);
1675     if(c->stuntime) continue;
1676     if(m == moGolem || m == moKnight || m == moTameBomberbird || m == moPrincess ||
1677       m == moPrincessArmed || m == moMouse || m == moFriendlyGhost) {
1678       if(m == moGolem) qg++;
1679       if(m == moFriendlyGhost) markOrb(itOrbUndeath);
1680 
1681       bool recorduse[ittypes];
1682       for(int i=0; i<ittypes; i++) recorduse[i] = orbused[i];
1683 
1684       DEBB(DF_TURN, ("stayval"));
1685       int bestv = stayvalue(m, c);
1686       vector<int> bdirs;
1687 
1688       DEBB(DF_TURN, ("moveval"));
1689       for(int k=0; k<c->type; k++) if(c->move(k)) {
1690         int val = movevalue(m, c, k, flags);
1691 
1692         if(val > bestv) bestv = val, bdirs.clear();
1693         if(val == bestv) bdirs.push_back(k);
1694         }
1695 
1696       if(m == moTameBomberbird) {
1697         cell *c2 = whirlwind::jumpDestination(c);
1698         if(c2 && !c2->monst) {
1699           int val = movevalue(m, c, STRONGWIND, flags);
1700           // printf("val = %d bestv = %d\n",
1701           if(val > bestv) bestv = val, bdirs.clear();
1702           if(val == bestv) bdirs.push_back(STRONGWIND);
1703           }
1704         }
1705 
1706       for(int i=0; i<ittypes; i++) orbused[i] = recorduse[i];
1707 
1708 //    printf("stayvalue = %d, result = %d, bq = %d\n", stayvalue(m,c), bestv, bq);
1709 
1710       if(bdirs.empty()) continue;
1711       int dir = hrand_elt(bdirs);
1712       auto mi = movei(c, dir);
1713       auto& c2 = mi.t;
1714       if(c2->monst) {
1715         bool revenge = (m == moPrincess);
1716         bool jealous = (isPrincess(c->monst) && isPrincess(c2->monst));
1717         eMonster m2 = c2->monst;
1718         if(revenge) {
1719           playSound(c2, princessgender() ? "dzia-princess" : "dzia-prince");
1720           addMessage(XLAT("%The1 takes %his1 revenge on %the2!", m, c2->monst));
1721           }
1722         if(revenge || jealous) flags |= AF_CRUSH;
1723         else if((flags & AF_CRUSH) && !canAttack(c, m, c2, c2->monst, flags ^ AF_CRUSH ^ AF_MUSTKILL))
1724           markOrb(itOrbEmpathy), markOrb(itOrbSlaying);
1725         attackMonster(c2, flags | AF_MSG, m);
1726         animateAttack(movei(c, dir), LAYER_SMALL);
1727         spread_plague(c, c2, dir, m);
1728         produceGhost(c2, m2, m);
1729         sideAttack(c, dir, m, 0);
1730         if(revenge) c->monst = m = moPrincessArmed;
1731         if(jealous) {
1732           playSound(c2, princessgender() ? "dzia-princess" : "dzia-prince");
1733           addMessage("\"That should teach you to take me seriously!\"");
1734           }
1735         }
1736       else {
1737         passable_for(m, c2, c, P_DEADLY);
1738         DEBB(DF_TURN, ("move"));
1739         moveMonster(mi);
1740         if(m != moTameBomberbird && m != moFriendlyGhost)
1741           moveBoatIfUsingOne(mi);
1742 
1743         if(c2->monst == m) {
1744           if(m == moGolem) c2->monst = moGolemMoved;
1745           if(m == moMouse) c2->monst = moMouseMoved;
1746           if(m == moPrincess) c2->monst = moPrincessMoved;
1747           if(m == moPrincessArmed) c2->monst = moPrincessArmedMoved;
1748           if(m == moTameBomberbird) c2->monst = moTameBomberbirdMoved;
1749           if(m == moKnight) c2->monst = moKnightMoved;
1750           if(m == moFriendlyGhost) c2->stuntime = 1;
1751           }
1752 
1753         empathyMove(mi);
1754         }
1755       DEBB(DF_TURN, ("other"));
1756       }
1757     }
1758   achievement_count("GOLEM", qg, 0);
1759   }
1760 
1761 /** note: butterflies don't use moveNormal for two reasons:
1762  *  1) to make sure that they move AFTER bulls
1763  *  2) to make sure that they move offscreen
1764  */
moveButterflies()1765 EX void moveButterflies() {
1766   int j = 0;
1767   for(int i=0; i<isize(butterflies); i++) {
1768     cell* c = butterflies[i].first;
1769     if(c->monst == moButterfly) {
1770       /* // don't move if under attack of a bull
1771       bool underattack = false;
1772       forCellEx(c3, c)
1773         if(c3->monst == moRagingBull && c3->mondir != NODIR &&
1774           angledist(c3->type, c3->mondir, neighborId(c3, c)) == 3 &&
1775           canAttack(c3, moRagingBull, c, c->monst, AF_BULL)
1776           )
1777             underattack = true;
1778       if(underattack) continue; */
1779       cell *c2 = moveNormal(c, 0);
1780       if(butterflies[i].second < 50 && c2)
1781         butterflies[j++] = make_pair(c2, butterflies[i].second+1);
1782       }
1783     }
1784   butterflies.resize(j);
1785   }
1786 
1787 // assume pathdist
specialMoves()1788 EX void specialMoves() {
1789   for(int i=0; i<isize(dcal); i++) {
1790     cell *c = dcal[i];
1791 
1792     if(c->stuntime) continue;
1793 
1794     eMonster m = c->monst;
1795 
1796     if(m == moHunterGuard && items[itHunting] >= 10)
1797       c->monst = moHunterChanging;
1798 
1799     if ((havewhat & HF_FAILED_AMBUSH) && hyperbolic && !quotient) {
1800       if(m == moHunterDog)
1801         c->monst = moHunterChanging;
1802       forCellEx(c2, c)
1803         if(c2->monst == moHunterDog)
1804           c2->monst = moHunterChanging;
1805       }
1806 
1807     if(m == moSleepBull && !peace::on) {
1808       bool wakeup = false;
1809       forCellEx(c2, c) if(c2->monst == moGadfly) {
1810         addMessage(XLAT("%The1 wakes up %the2.", c2->monst, m));
1811         wakeup = true;
1812         }
1813       for(int i=0; i<isize(targets); i++) {
1814         cell *t = targets[i];
1815         if(celldistance(c, t) <= 2) wakeup = true;
1816         }
1817       if(wakeup) {
1818         playSound(NULL, "bull");
1819         c->monst = m = moRagingBull;
1820         c->mondir = NODIR;
1821         }
1822       }
1823 
1824     if(m == moNecromancer) {
1825       pathdata pd(moNecromancer);
1826       int gravenum = 0, zombienum = 0;
1827       cell *gtab[8], *ztab[8];
1828       for(int j=0; j<c->type; j++) if(c->move(j)) {
1829         if(c->move(j)->wall == waFreshGrave) gtab[gravenum++] = c->move(j);
1830         if(passable(c->move(j), c, 0) && c->move(j)->pathdist < c->pathdist)
1831           ztab[zombienum++] = c->move(j);
1832         }
1833       if(gravenum && zombienum) {
1834         cell *gr = gtab[hrand(gravenum)];
1835         gr->wall = waAncientGrave;
1836         gr->monst = moGhost;
1837         gr->stuntime = 1;
1838         ztab[hrand(zombienum)]->monst = moZombie;
1839         ztab[hrand(zombienum)]->stuntime = 1;
1840         addMessage(XLAT("%The1 raises some undead!", m));
1841         playSound(c, "necromancy");
1842         }
1843       }
1844 
1845     else if(m == moOutlaw) {
1846       for(cell *c1: gun_targets(c))
1847         if(canAttack(c, moOutlaw, c1, c1->monst, AF_GETPLAYER | AF_ONLY_FBUG | AF_GUN)) {
1848           attackMonster(c1, AF_GETPLAYER | AF_ONLY_FBUG | AF_GUN, moOutlaw);
1849           c->stuntime = 1;
1850           break;
1851           }
1852       }
1853 
1854     else if(m == moWitchFlash && flashWouldKill(c, AF_GETPLAYER | AF_ONLY_FBUG) && !flashWouldKill(c, false)) {
1855       addMessage(XLAT("%The1 activates her Flash spell!", m));
1856       m = moWitch;
1857       activateFlashFrom(c, moWitchFlash, AF_MAGIC | AF_GETPLAYER | AF_MSG);
1858       c->stuntime = 1;
1859       }
1860 
1861     else if(m == moCrystalSage && c->cpdist <= 4 && isIcyLand(cwt.at) && cwt.at->wall != waBoat) {
1862       // only one sage attacks
1863       if(sagefresh) {
1864         sagefresh = false;
1865         if(sagephase == 0) {
1866           addMessage(XLAT("%The1 shows you two fingers.", m));
1867           addMessage(XLAT("You wonder what does it mean?"));
1868           }
1869         else if(sagephase == 1) {
1870           addMessage(XLAT("%The1 shows you a finger.", m));
1871           addMessage(XLAT("You think about possible meanings."));
1872           }
1873         else {
1874           addMessage(XLAT("%The1 moves his finger downwards.", m));
1875           addMessage(XLAT("Your brain is steaming."));
1876           }
1877         sagephase++;
1878         for(int i=0; i<isize(targets); i++) {
1879           cell *t = targets[i];
1880           if(celldistance(c, t) > 4) continue;
1881           sageheat(t, .0);
1882           for(int i=0; i<t->type; i++)
1883             sageheat(t->move(i), .3);
1884           }
1885         }
1886       c->stuntime = 1;
1887       }
1888 
1889     else if(m == moPyroCultist && !peace::on) {
1890       bool shot = false;
1891       bool dont_approach = false;
1892       // smaller range on the sphere
1893       int firerange = (sphere || getDistLimit() < 5) ? 2 : 4;
1894       for(int i=0; i<isize(targets); i++) {
1895         cell *t = targets[i];
1896         if(celldistance(c,t) <= firerange && makeflame(t, 20, true)) {
1897           if(isPlayerOn(t))
1898             addMessage(XLAT("%The1 throws fire at you!", m));
1899           else
1900             addMessage(XLAT("%The1 throws fire at %the2!", m, t->monst));
1901           makeflame(t, 20, false);
1902           playSound(t, "fire");
1903           c->monst = moCultist;
1904           shot = true;
1905           }
1906         if(celldistance(c,t) <= 3 && !sphere) dont_approach = true;
1907         }
1908       if(shot || dont_approach) c->stuntime = 1;
1909       }
1910 
1911     else if(m == moHexer && c->item && (classflag(c->item) & IF_CURSE) && !peace::on) {
1912       // bool dont_approach = false;
1913       // smaller range on the sphere
1914       int firerange = (sphere || getDistLimit() < 5) ? 2 : 4;
1915 
1916       bool dont_approach = false;
1917       for(int i=0; i<isize(targets); i++) {
1918         cell *t = targets[i];
1919 
1920         if(isPlayerOn(t)) {
1921           int d = celldistance(c,t);
1922           if(d <= firerange) {
1923             addMessage(XLAT("%The1 curses you with %the2!", m, c->item));
1924             playSound(t, "fire");
1925             animate_item_throw(c, t, c->item);
1926             items[c->item] += orbcharges(c->item);
1927             c->item = itNone;
1928             c->stuntime = 1;
1929             }
1930           if(d == firerange+1) dont_approach = true;
1931           }
1932         }
1933 
1934       if(dont_approach) c->stuntime = 1;
1935       }
1936 
1937     else if(m == moVampire) {
1938       for(int i=0; i<isize(targets); i++) {
1939         cell *t = targets[i];
1940         if(celldistance(c,t) <= 2) {
1941           bool msg = false;
1942           for(int i=0; i<ittypes; i++)
1943             if(itemclass(eItem(i)) == IC_ORB && items[i] && items[itOrbTime] && !orbused[i]) {
1944               orbused[i] = true;
1945               msg = true;
1946               }
1947           if(msg) addMessage(XLAT("%The1 drains your powers!", m));
1948           c->stuntime = 1;
1949           }
1950         }
1951       }
1952     }
1953   }
1954 
moveworms()1955 EX void moveworms() {
1956   if(!isize(worms)) return;
1957   pathdata pd(moWorm);
1958   int wrm = isize(worms);
1959   for(int i=0; i<wrm; i++) {
1960     moveWorm(worms[i]);
1961     }
1962   }
1963 
refreshFriend(cell * c)1964 EX void refreshFriend(cell *c) {
1965   if(c->monst == moGolemMoved) c->monst = moGolem;
1966   if(c->monst == moMouseMoved) c->monst = moMouse;
1967   if(c->monst == moPrincessMoved) c->monst = moPrincess;
1968   if(c->monst == moPrincessArmedMoved) c->monst = moPrincessArmed;
1969   if(c->monst == moKnightMoved) c->monst = moKnight;
1970   if(c->monst == moTameBomberbirdMoved) c->monst = moTameBomberbird;
1971   }
1972 
consMove(cell * c,eMonster param)1973 EX void consMove(cell *c, eMonster param) {
1974   eMonster m = c->monst;
1975 
1976   if(movegroup(m) != moYeti) return;
1977 
1978   if(m == moWitchSpeed) havewhat |= HF_FAST;
1979   bool slow = slowMover(m);
1980   if(slow) havewhat |= HF_SLOW;
1981 
1982   if(param == moYeti && slow) return;
1983   if(param == moTortoise && !slow) return;
1984   if(param == moWitchSpeed && m != moWitchSpeed) return;
1985 
1986   if(isActiveEnemy(c, moPlayer)) {
1987     int goodmoves = 0;
1988     for(int t=0; t<c->type; t++) {
1989       cell *c2 = c->move(t);
1990       if(c2 && c2->pathdist < c->pathdist)
1991         goodmoves++;
1992       }
1993     movesofgood.grow(goodmoves).push_back(c);
1994     }
1995   else
1996     movesofgood.grow(0).push_back(c);
1997   }
1998 
moveNormals(eMonster param)1999 EX void moveNormals(eMonster param) {
2000   pathdata pd(param);
2001 
2002   movesofgood.clear();
2003 
2004   for(int i=0; i<isize(pathqm); i++)
2005     consMove(pathqm[i], param);
2006 
2007   int dcs = isize(dcal);
2008   for(int i=0; i<dcs; i++) {
2009     cell *c = dcal[i];
2010     if(c->pathdist == PINFD) consMove(c, param);
2011     }
2012 
2013   for(auto& v: movesofgood) for(cell *c: v) {
2014     if(minf[c->monst].mgroup == moYeti) {
2015       moveNormal(c, MF_PATHDIST);
2016       }
2017     }
2018   }
2019 
movehex_all()2020 EX void movehex_all() {
2021   for(int i: snaketypes) {
2022     movehex(false, i);
2023     if(!shmup::on && haveMount()) movehex(true, i);
2024     }
2025   movehex_rest(false);
2026   movehex_rest(true);
2027   }
2028 
movemonsters()2029 EX void movemonsters() {
2030   #if CAP_COMPLEX2
2031   ambush::distance = 0;
2032   #endif
2033 
2034   DEBB(DF_TURN, ("lava1"));
2035   orboflava(1);
2036 
2037   #if CAP_COMPLEX2
2038   ambush::check_state();
2039   #endif
2040 
2041   sagefresh = true;
2042   turncount++;
2043 
2044   specialMoves();
2045 
2046   DEBB(DF_TURN, ("ghosts"));
2047   moveghosts();
2048 
2049   DEBB(DF_TURN, ("butterflies"));
2050   moveButterflies();
2051 
2052   DEBB(DF_TURN, ("normal"));
2053   moveNormals(moYeti);
2054 
2055   DEBB(DF_TURN, ("slow"));
2056   if(havewhat & HF_SLOW) moveNormals(moTortoise);
2057 
2058   if(sagefresh) sagephase = 0;
2059 
2060   DEBB(DF_TURN, ("ivy"));
2061   moveivy();
2062   DEBB(DF_TURN, ("slimes"));
2063   groupmove(moSlime, 0);
2064   DEBB(DF_TURN, ("sharks"));
2065   if(havewhat & HF_SHARK) groupmove(moShark, 0);
2066   DEBB(DF_TURN, ("eagles"));
2067   if(havewhat & HF_BIRD) groupmove(moEagle, 0);
2068   if(havewhat & HF_EAGLES) groupmove(moEagle, MF_NOATTACKS | MF_ONLYEAGLE);
2069   DEBB(DF_TURN, ("eagles"));
2070   if(havewhat & HF_REPTILE) groupmove(moReptile, 0);
2071   DEBB(DF_TURN, ("jumpers"));
2072   if(havewhat & HF_JUMP) {
2073     groupmove(moFrog, 0);
2074     groupmove(moVaulter, 0);
2075     groupmove(moPhaser, 0);
2076     }
2077   DEBB(DF_TURN, ("air"));
2078   if(havewhat & HF_AIR) {
2079     airmap.clear();
2080     groupmove(moAirElemental, 0);
2081     buildAirmap();
2082     }
2083   DEBB(DF_TURN, ("earth"));
2084   if(havewhat & HF_EARTH) groupmove(moEarthElemental, 0);
2085   DEBB(DF_TURN, ("water"));
2086   if(havewhat & HF_WATER) groupmove(moWaterElemental, 0);
2087   DEBB(DF_TURN, ("void"));
2088   if(havewhat & HF_VOID) groupmove(moVoidBeast, 0);
2089   DEBB(DF_TURN, ("leader"));
2090   if(havewhat & HF_LEADER) groupmove(moPirate, 0);
2091   DEBB(DF_TURN, ("mutant"));
2092   if((havewhat & HF_MUTANT) || (bounded && among(specialland, laOvergrown, laClearing))) movemutant();
2093   DEBB(DF_TURN, ("bugs"));
2094   if(havewhat & HF_BUG) hive::movebugs();
2095   DEBB(DF_TURN, ("whirlpool"));
2096   if(havewhat & HF_WHIRLPOOL) whirlpool::move();
2097   DEBB(DF_TURN, ("whirlwind"));
2098   if(havewhat & HF_WHIRLWIND) whirlwind::move();
2099   #if CAP_COMPLEX2
2100   DEBB(DF_TURN, ("westwall"));
2101   if(havewhat & HF_WESTWALL) westwall::move();
2102   #endif
2103   for(cell *pc: player_positions())
2104     if(pc->item == itOrbSafety)
2105       return;
2106   DEBB(DF_TURN, ("river"));
2107   if(havewhat & HF_RIVER) prairie::move();
2108   /* DEBB(DF_TURN, ("magnet"));
2109   if(havewhat & HF_MAGNET)
2110     groupmove(moSouthPole, 0),
2111     groupmove(moNorthPole, 0); */
2112   DEBB(DF_TURN, ("bugs"));
2113   if(havewhat & HF_HEXD) groupmove(moHexDemon, 0);
2114   if(havewhat & HF_DICE) groupmove(moAnimatedDie, 0);
2115   if(havewhat & HF_ALT) groupmove(moAltDemon, 0);
2116   if(havewhat & HF_MONK) groupmove(moMonk, 0);
2117 
2118   DEBB(DF_TURN, ("worm"));
2119   cell *savepos[MAXPLAYER];
2120 
2121   for(int i=0; i<numplayers(); i++)
2122     savepos[i] = playerpos(i);
2123 
2124   moveworms();
2125   if(havewhat & HF_HEX)
2126     movehex_all();
2127 
2128   if(havewhat & HF_KRAKEN) kraken::attacks(), groupmove(moKrakenH, 0);
2129   if(havewhat & HF_DRAGON) groupmove(moDragonHead, MF_NOFRIEND);
2130   if(haveMount()) groupmove(moDragonHead, MF_MOUNT);
2131 
2132   DEBB(DF_TURN, ("golems"));
2133   movegolems(0);
2134 
2135   DEBB(DF_TURN, ("fresh"));
2136   moverefresh();
2137 
2138   DEBB(DF_TURN, ("lava2"));
2139   orboflava(2);
2140 
2141   DEBB(DF_TURN, ("shadow"));
2142   moveshadow();
2143 
2144   DEBB(DF_TURN, ("wandering"));
2145   wandering();
2146 
2147   DEBB(DF_TURN, ("rosemap"));
2148   if(havewhat & HF_ROSE) buildRosemap();
2149 
2150   for(int i=0; i<numplayers(); i++)
2151     if(savepos[i] != playerpos(i)) {
2152       bfs(); break;
2153       }
2154   }
2155 
nogoSlow(cell * to,cell * from)2156 EX bool nogoSlow(cell *to, cell *from) {
2157   if(cellEdgeUnstable(to) && gravityLevelDiff(to, from) >= 0) return true;
2158   if(cellUnstable(to)) return true;
2159   return false;
2160   }
2161 
beastcrash(cell * c,cell * beast)2162 EX void beastcrash(cell *c, cell *beast) {
2163   if(c->wall == waPetrified || c->wall == waDeadTroll || c->wall == waDeadTroll2 ||
2164     c->wall == waGargoyle) {
2165     addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->wall));
2166     c->wall = waNone;
2167     }
2168   else if(c->wall == waDeadwall || c->wall == waCavewall || c->wall == waSandstone ||
2169     c->wall == waVinePlant || c->wall == waIcewall ||
2170     c->wall == waMirror || c->wall == waCloud || c->wall == waBigTree || c->wall ==
2171     waSmallTree || c->wall == waGlass || c->wall == waClosedGate || c->wall == waStone || c->wall == waRuinWall) {
2172     addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->wall));
2173     c->wall = waNone;
2174     }
2175   else if(cellHalfvine(c)) {
2176     addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->wall));
2177     destroyHalfvine(c);
2178     }
2179   else if(c->wall == waThumperOff) {
2180     addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->wall));
2181     c->wall = waThumperOn;
2182     c->wparam = 100;
2183     }
2184   else if(c->wall == waExplosiveBarrel) {
2185     addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->wall));
2186     explodeBarrel(c);
2187     }
2188   else if(realred(c)) {
2189     addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->wall));
2190     if(c->wall == waRed1) c->wall = waNone;
2191     else if(c->wall == waRed2) c->wall = waRed1;
2192     else if(c->wall == waRed3) c->wall = waRed2;
2193     }
2194   else if(isBull(c->monst) || isSwitch(c->monst)) {
2195     addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->monst));
2196     if(c->monst == moSleepBull) c->monst = moRagingBull, c->stuntime = 3;
2197     }
2198   }
2199 
stayEffect(cell * c)2200 EX void stayEffect(cell *c) {
2201   eMonster m = c->monst;
2202   if(m == moAirElemental) airmap.push_back(make_pair(c, 0));
2203   if(m == moRagingBull && c->mondir != NODIR) {
2204     playSound(NULL, "hit-axe"+pick123());
2205     forCellIdEx(c2, d, c) {
2206       bool opposite = anglestraight(c, d, c->mondir);
2207       if(opposite) beastcrash(c2, c);
2208       }
2209     c->mondir = NODIR; c->stuntime = 3;
2210     }
2211   }
2212 
realstuntime(cell * c)2213 EX int realstuntime(cell *c) {
2214   if(isMutantIvy(c)) return (c->stuntime - mutantphase) & 15;
2215   return c->stuntime;
2216   }
2217 
2218 
2219 }
2220