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