1 // Hyperbolic Rogue - environment
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file environment.cpp
5  *  \brief game environment: routines related to the game that affect all the map. Monsters to move are detected here, but their moves are implemented in monstermove.cpp
6  */
7 
8 #include "hyper.h"
9 
10 namespace hr {
11 
12 #if HDR
13 #define HF_BUG        Flag(0)
14 #define HF_EARTH      Flag(1)
15 #define HF_BIRD       Flag(2)
16 #define HF_LEADER     Flag(3)
17 #define HF_HEX        Flag(4)
18 #define HF_WHIRLPOOL  Flag(5)
19 #define HF_WATER      Flag(6)
20 #define HF_AIR        Flag(7)
21 #define HF_MUTANT     Flag(8)
22 #define HF_OUTLAW     Flag(9)
23 #define HF_WHIRLWIND  Flag(10)
24 #define HF_ROSE       Flag(11)
25 #define HF_DRAGON     Flag(12)
26 #define HF_KRAKEN     Flag(13)
27 #define HF_SHARK      Flag(14)
28 #define HF_BATS       Flag(15)
29 #define HF_REPTILE    Flag(16)
30 #define HF_EAGLES     Flag(17)
31 #define HF_SLOW       Flag(18)
32 #define HF_FAST       Flag(19)
33 #define HF_WARP       Flag(20)
34 #define HF_MOUSE      Flag(21)
35 #define HF_RIVER      Flag(22)
36 #define HF_MIRROR     Flag(23)
37 #define HF_VOID       Flag(24)
38 #define HF_HUNTER     Flag(25)
39 #define HF_FAILED_AMBUSH     Flag(26)
40 #define HF_MAGNET     Flag(27)
41 #define HF_HEXD       Flag(28)
42 #define HF_ALT        Flag(29)
43 #define HF_MONK       Flag(30)
44 #define HF_WESTWALL   Flag(31)
45 #define HF_JUMP       Flag(32)
46 #define HF_DICE       Flag(33)
47 #endif
48 
49 EX flagtype havewhat, hadwhat;
50 
51 /** monsters of specific types to move */
52 EX vector<cell*> worms, ivies, ghosts, golems, hexsnakes;
53 
54 /** temporary changes during bfs */
55 vector<pair<cell*, eMonster>> tempmonsters;
56 
57 /** additional direction information for BFS algorithms.
58  *  It remembers from where we have got to this location
59  *  the opposite cell will be added to the queue first,
60  *  which helps the AI.
61  **/
62 EX vector<int> reachedfrom;
63 
64 /** The position of the first cell in dcal in distance 7. New wandering monsters can be generated in dcal[first7..]. */
65 EX int first7;
66 
67 /** the list of all nearby cells, according to cpdist */
68 EX vector<cell*> dcal;
69 /** the list of all nearby cells, according to current pathdist */
70 EX vector<cell*> pathq;
71 
72 /** the number of big statues -- they increase monster generation */
73 EX int statuecount;
74 
75 /** the number of slimes in Wetland -- they create ghosts */
76 EX int wetslime;
77 
78 /** list of monsters to move (pathq restriced to monsters) */
79 EX vector<cell*> pathqm;
80 
81 /** which hex snakes are there */
82 EX set<int> snaketypes;
83 
84 EX int gamerange_bonus = 0;
gamerange()85 EX int gamerange() { return getDistLimit() + gamerange_bonus; }
86 
87 // pathdist begin
88 EX cell *pd_from;
89 EX int pd_range;
90 
onpath(cell * c,int d)91 EX void onpath(cell *c, int d) {
92   c->pathdist = d;
93   pathq.push_back(c);
94   }
95 
onpath(cell * c,int d,int sp)96 EX void onpath(cell *c, int d, int sp) {
97   c->pathdist = d;
98   pathq.push_back(c);
99   reachedfrom.push_back(sp);
100   }
101 
clear_pathdata()102 EX void clear_pathdata() {
103   for(auto c: pathq) c->pathdist = PINFD;
104   pathq.clear();
105   pathqm.clear();
106   reachedfrom.clear();
107   }
108 
109 EX int pathlock = 0;
110 
compute_graphical_distance()111 EX void compute_graphical_distance() {
112   if(pathlock) { printf("path error: compute_graphical_distance\n"); }
113   cell *c1 = centerover ? centerover : pd_from ? pd_from : cwt.at;
114   int sr = get_sightrange_ambush();
115   if(pd_from == c1 && pd_range == sr) return;
116   clear_pathdata();
117 
118   pd_from = c1;
119   pd_range = sr;
120   c1->pathdist = 0;
121   pathq.push_back(pd_from);
122 
123   for(int qb=0; qb<isize(pathq); qb++) {
124     cell *c = pathq[qb];
125     if(c->pathdist == pd_range) break;
126     if(qb == 0) forCellCM(c1, c) ;
127     forCellEx(c1, c)
128       if(c1->pathdist == PINFD)
129         onpath(c1, c->pathdist + 1);
130     }
131   }
132 
133 const int max_radius = 16;
134 
135 struct visit_set {
136   set<cell*> visited;
137   queue<cell*> q;
visithr::visit_set138   void visit(cell *c) {
139     if(visited.count(c)) return;
140     visited.insert(c);
141     q.push(c);
142     }
143   };
144 
145 struct princess_ai {
146   array<visit_set, max_radius+1> info;
visit_gatehr::princess_ai147   void visit_gate(cell *g) { info[0].visit(g); }
148   void run();
149   };
150 
run()151 void princess_ai::run() {
152   int radius = toggle_radius(waOpenPlate);
153   if(pathq.empty()) return;
154   int d = pathq.back()->pathdist;
155   if(d == PINFD - 1) return;
156   d++;
157   if(d < 5) d = 5; /* the Princess AI avoids plates when too close to the player */
158   hassert(radius <= max_radius);
159 
160   for(int k=0; k<=radius; k++) while(!info[k].q.empty()) {
161     cell *c = info[k].q.front();
162     info[k].q.pop();
163     if(k < radius) forCellEx(c1, c) {
164       info[k+1].visit(c1);
165       if(k == 0 && c1->wall == waClosedGate)
166         info[0].visit(c1);
167       }
168     if(k == radius && c->wall == waOpenPlate && c->pathdist == PINFD)
169       onpath(c, d, hrand(c->type));
170     }
171   }
172 
computePathdist(eMonster param,bool include_allies IS (true))173 EX void computePathdist(eMonster param, bool include_allies IS(true)) {
174 
175   for(cell *c: targets)
176     if(include_allies || isPlayerOn(c))
177       onpath(c, isPlayerOn(c) ? 0 : 1, hrand(c->type));
178 
179   int qtarg = isize(targets);
180 
181   int limit = gamerange();
182 
183   int qb = 0;
184 
185   bool princess = isPrincess(param);
186   princess_ai gd;
187   princess_retry:
188 
189   for(; qb < isize(pathq); qb++) {
190     cell *c = pathq[qb];
191     int fd = reachedfrom[qb] + c->type/2;
192     if(c->monst && !isBug(c) && !(isFriendly(c) && !c->stuntime)) {
193       pathqm.push_back(c);
194       continue; // no paths going through monsters
195       }
196     if(isMounted(c) && !isPlayerOn(c)) {
197       // don't treat the Worm you are riding as passable
198       pathqm.push_back(c);
199       continue;
200       }
201     if(c->cpdist > limit && !(c->land == laTrollheim && turncount < c->landparam) && c->wall != waThumperOn) continue;
202     int d = c->pathdist;
203     if(d == PINFD - 1) continue;
204     for(int j=0; j<c->type; j++) {
205       int i = (fd+j) % c->type;
206       // printf("i=%d cd=%d\n", i, c->move(i)->cpdist);
207       cell *c2 = c->move(i);
208 
209       flagtype f = P_MONSTER | P_REVDIR;
210       if(param == moTameBomberbird) f |= P_FLYING;
211 
212       if(c2 && c2->pathdist == PINFD &&
213         passable(c2, (qb<qtarg) && !nonAdjacent(c,c2) && !thruVine(c,c2) ?NULL:c, f)) {
214 
215         if(qb >= qtarg) {
216           if(param == moTortoise && nogoSlow(c, c2)) continue;
217           if(param == moIvyRoot  && strictlyAgainstGravity(c, c2, false, MF_IVY)) continue;
218           if(param == moWorm && (cellUnstable(c) || cellEdgeUnstable(c) || prairie::no_worms(c))) continue;
219           if(!isFriendly(param) && items[itOrbLava] && c2->cpdist <= 5 && pseudohept(c) && makeflame(c2, 1, true))
220             continue;
221           }
222 
223         onpath(c2, d+1, c->c.spin(i));
224         }
225 
226       else if(c2 && c2->wall == waClosedGate && princess)
227         gd.visit_gate(c2);
228       }
229     }
230 
231   if(princess) {
232     gd.run();
233     if(qb < isize(pathq)) goto princess_retry;
234     }
235   }
236 
237 #if HDR
238 struct pathdata {
checklockhr::pathdata239   void checklock() {
240     if(pd_from) pd_from = NULL, clear_pathdata();
241     if(pathlock) printf("path error\n");
242     pathlock++;
243     }
~pathdatahr::pathdata244   ~pathdata() {
245     pathlock--;
246     clear_pathdata();
247     }
pathdatahr::pathdata248   pathdata(eMonster m, bool include_allies IS(true)) {
249     checklock();
250     computePathdist(m, include_allies);
251     }
pathdatahr::pathdata252   pathdata(int i) {
253     checklock();
254     }
255   };
256 #endif
257 // pathdist end
258 
259 /** calculate cpdist, 'have' flags, and do general fixings */
bfs()260 EX void bfs() {
261 
262   calcTidalPhase();
263 
264   yendor::onpath();
265 
266   int dcs = isize(dcal);
267   for(int i=0; i<dcs; i++) dcal[i]->cpdist = INFD;
268   worms.clear(); ivies.clear(); ghosts.clear(); golems.clear();
269   tempmonsters.clear(); targets.clear();
270   statuecount = 0;
271   wetslime = 0;
272   hexsnakes.clear();
273 
274   hadwhat = havewhat;
275   havewhat = 0; jiangshi_on_screen = 0;
276   snaketypes.clear();
277   if(!(hadwhat & HF_WARP)) { avengers = 0; }
278   if(!(hadwhat & HF_MIRROR)) { mirrorspirits = 0; }
279 
280   elec::havecharge = false;
281   elec::afterOrb = false;
282   elec::haveelec = false;
283   airmap.clear();
284   if(!(hadwhat & HF_ROSE)) rosemap.clear();
285 
286   dcal.clear(); reachedfrom.clear();
287 
288   recalcTide = false;
289 
290   for(cell *c: player_positions()) {
291     if(c->cpdist == 0) continue;
292     c->cpdist = 0;
293     checkTide(c);
294     dcal.push_back(c);
295     reachedfrom.push_back(hrand(c->type));
296     if(!invismove) targets.push_back(c);
297     }
298 
299   int distlimit = gamerange();
300 
301   for(cell *c: player_positions()) {
302     if(items[itOrbDomination])
303     if(c->monst == moTentacle || c->monst == moTentaclewait || c->monst == moTentacleEscaping)
304       worms.push_back(c);
305     }
306 
307   int qb = 0;
308   first7 = 0;
309   while(true) {
310     if(qb == isize(dcal)) break;
311     int i, fd = reachedfrom[qb] + 3;
312     cell *c = dcal[qb++];
313 
314     int d = c->cpdist;
315 
316     if(WDIM == 2 && d == distlimit) { first7 = qb; break; }
317 
318     for(int j=0; j<c->type; j++) if(i = (fd+j) % c->type, c->move(i)) {
319       // printf("i=%d cd=%d\n", i, c->move(i)->cpdist);
320       cell *c2 = c->move(i);
321       if(!c2) continue;
322 
323       if(isWarpedType(c2->land)) havewhat |= HF_WARP;
324       if(c2->land == laMirror) havewhat |= HF_MIRROR;
325 
326       if((c->wall == waBoat || c->wall == waSea) &&
327         (c2->wall == waSulphur || c2->wall == waSulphurC))
328         c2->wall = waSea;
329 
330       if(c2 && signed(c2->cpdist) > d+1) {
331         if(WDIM == 3 && !gmatrix.count(c2)) {
332           if(!first7) first7 = qb;
333           continue;
334           }
335         c2->cpdist = d+1;
336 
337         // remove treasures
338         if(!peace::on && c2->item && c2->cpdist == distlimit && itemclass(c2->item) == IC_TREASURE &&
339           c2->item != itBabyTortoise &&
340           (items[c2->item] >= (ls::any_chaos()?10:20) + currentLocalTreasure || getGhostcount() >= 2)) {
341             c2->item = itNone;
342             if(c2->land == laMinefield) { c2->landparam &= ~3; }
343             }
344 
345         if(c2->item == itBombEgg && c2->cpdist == distlimit && items[itBombEgg] >= c2->landparam) {
346           c2->item = itNone;
347           c2->landparam |= 2;
348           c2->landparam &= ~1;
349           if(!c2->monst) c2->monst = moBomberbird, c2->stuntime = 0;
350           }
351 
352         if(c2->item == itBarrow && c2->cpdist == distlimit && c2->wall != waBarrowDig) {
353           c2->item = itNone;
354           }
355 
356         if(c2->item == itLotus && c2->cpdist == distlimit && items[itLotus] >= getHauntedDepth(c2)) {
357           c2->item = itNone;
358           }
359 
360         if(c2->item == itMutant2 && timerghost) {
361           bool rotten = true;
362           for(int i=0; i<c2->type; i++)
363             if(c2->move(i) && c2->move(i)->monst == moMutant)
364               rotten = false;
365           if(rotten) c2->item = itNone;
366           }
367 
368         if(c2->item == itDragon && (shmup::on ? shmup::curtime-c2->landparam>300000 :
369           turncount-c2->landparam > 500))
370           c2->item = itNone;
371 
372         if(c2->item == itTrollEgg && c2->cpdist == distlimit && !shmup::on && c2->landparam && turncount-c2->landparam > 650)
373           c2->item = itNone;
374 
375         if(c2->item == itWest && c2->cpdist == distlimit && items[itWest] >= c2->landparam + 4)
376           c2->item = itNone;
377 
378         if(c2->item == itMutant && c2->cpdist == distlimit && items[itMutant] >= c2->landparam) {
379           c2->item = itNone;
380           }
381 
382         if(c2->item == itIvory && c2->cpdist == distlimit && items[itIvory] >= c2->landparam) {
383           c2->item = itNone;
384           }
385 
386         if(c2->item == itAmethyst && c2->cpdist == distlimit && items[itAmethyst] >= -celldistAlt(c2)/5) {
387           c2->item = itNone;
388           }
389 
390         if(!keepLightning) c2->ligon = 0;
391         dcal.push_back(c2);
392         reachedfrom.push_back(c->c.spin(i));
393 
394         checkTide(c2);
395 
396         if(c2->wall == waBigStatue && c2->land != laTemple)
397           statuecount++;
398 
399         if(isAlch(c2->wall) && c2->land == laWet)
400           wetslime++;
401 
402         if(cellHalfvine(c2) && isWarped(c2)) {
403           addMessage(XLAT("%The1 is destroyed!", c2->wall));
404           destroyHalfvine(c2);
405           }
406 
407         if(c2->wall == waCharged) elec::havecharge = true;
408         if(isElectricLand(c2)) elec::haveelec = true;
409 
410         if(c2->land == laWhirlpool) havewhat |= HF_WHIRLPOOL;
411         if(c2->land == laWhirlwind) havewhat |= HF_WHIRLWIND;
412         if(c2->land == laWestWall) havewhat |= HF_WESTWALL;
413         if(c2->land == laPrairie) havewhat |= HF_RIVER;
414         if(c2->land == laClearing) havewhat |= HF_MUTANT;
415 
416         if(c2->wall == waRose) havewhat |= HF_ROSE;
417 
418         if((hadwhat & HF_ROSE) && (rosemap[c2] & 3)) havewhat |= HF_ROSE;
419 
420         if(c2->monst) {
421           if(isHaunted(c2->land) &&
422             c2->monst != moGhost && c2->monst != moZombie && c2->monst != moNecromancer)
423             fail_survivalist();
424           if(c2->monst == moHexSnake || c2->monst == moHexSnakeTail) {
425             havewhat |= HF_HEX;
426             if(c2->mondir != NODIR)
427               snaketypes.insert(snake_pair(c2));
428             if(c2->monst == moHexSnake) hexsnakes.push_back(c2);
429             else findWormIvy(c2);
430             }
431           else if(c2->monst == moKrakenT || c2->monst == moKrakenH) {
432             havewhat |= HF_KRAKEN;
433             }
434           else if(c2->monst == moDragonHead || c2->monst == moDragonTail) {
435             havewhat |= HF_DRAGON;
436             }
437           else if(c2->monst == moWitchSpeed)
438             havewhat |= HF_FAST;
439           else if(c2->monst == moMutant)
440             havewhat |= HF_MUTANT;
441           else if(c2->monst == moJiangshi)
442             jiangshi_on_screen++;
443           else if(c2->monst == moOutlaw)
444             havewhat |= HF_OUTLAW;
445           else if(isGhostMover(c2->monst))
446             ghosts.push_back(c2);
447           else if(isWorm(c2) || isIvy(c2)) findWormIvy(c2);
448           else if(isBug(c2)) {
449             havewhat |= HF_BUG;
450             targets.push_back(c2);
451             }
452           else if(isFriendly(c2)) {
453             if(c2->monst != moMouse && !markEmpathy(itOrbInvis) && !(isWatery(c2) && markEmpathy(itOrbFish)) &&
454               !c2->stuntime) targets.push_back(c2);
455             if(c2->monst == moGolem) golems.push_back(c2);
456             if(c2->monst == moFriendlyGhost) golems.push_back(c2);
457             if(c2->monst == moKnight) golems.push_back(c2);
458             if(c2->monst == moTameBomberbird) golems.push_back(c2);
459             if(c2->monst == moMouse) { golems.push_back(c2); havewhat |= HF_MOUSE; }
460             if(c2->monst == moPrincess || c2->monst == moPrincessArmed) golems.push_back(c2);
461             if(c2->monst == moIllusion) {
462               if(items[itOrbIllusion]) items[itOrbIllusion]--;
463               else c2->monst = moNone;
464               }
465             }
466           else if(c2->monst == moButterfly) {
467             addButterfly(c2);
468             }
469           else if(isAngryBird(c2->monst)) {
470             havewhat |= HF_BIRD;
471             if(c2->monst == moBat) havewhat |= HF_BATS | HF_EAGLES;
472             if(c2->monst == moEagle) havewhat |= HF_EAGLES;
473             }
474           else if(among(c2->monst, moFrog, moVaulter, moPhaser))
475             havewhat |= HF_JUMP;
476           else if(c2->monst == moReptile) havewhat |= HF_REPTILE;
477           else if(isLeader(c2->monst)) havewhat |= HF_LEADER;
478           else if(c2->monst == moEarthElemental) havewhat |= HF_EARTH;
479           else if(c2->monst == moWaterElemental) havewhat |= HF_WATER;
480           else if(c2->monst == moVoidBeast) havewhat |= HF_VOID;
481           else if(c2->monst == moHunterDog) havewhat |= HF_HUNTER;
482           else if(isMagneticPole(c2->monst)) havewhat |= HF_MAGNET;
483           else if(c2->monst == moAltDemon) havewhat |= HF_ALT;
484           else if(c2->monst == moHexDemon) havewhat |= HF_HEXD;
485           else if(among(c2->monst, moAnimatedDie, moAngryDie)) havewhat |= HF_DICE;
486           else if(c2->monst == moMonk) havewhat |= HF_MONK;
487           else if(c2->monst == moShark || c2->monst == moCShark || among(c2->monst, moRusalka, moPike)) havewhat |= HF_SHARK;
488           else if(c2->monst == moAirElemental)
489             havewhat |= HF_AIR, airmap.push_back(make_pair(c2,0));
490           }
491         // pheromones!
492         if(c2->land == laHive && c2->landparam >= 50 && c2->wall != waWaxWall)
493           havewhat |= HF_BUG;
494         if(c2->wall == waThumperOn)
495           targets.push_back(c2);
496 
497         }
498       }
499     }
500 
501   for(int i=first7; i<isize(dcal); i++)
502     forCellEx(c2, dcal[i])
503       if(c2->wall == waThumperOn) {
504         targets.push_back(c2);
505         }
506 
507   while(recalcTide) {
508     recalcTide = false;
509     for(int i=0; i<isize(dcal); i++) checkTide(dcal[i]);
510     }
511 
512   for(auto& t: tempmonsters) t.first->monst = t.second;
513 
514   buildAirmap();
515   }
516 
moverefresh(bool turn IS (true))517 EX void moverefresh(bool turn IS(true)) {
518   int dcs = isize(dcal);
519 
520   for(int i=0; i<dcs; i++) {
521     cell *c = dcal[i];
522 
523     if(c->monst == moWolfMoved) c->monst = moWolf;
524     if(c->monst == moIvyNext) {
525       c->monst = moIvyHead; ivynext(c);
526       }
527     if(c->monst == moIvyDead)
528       removeIvy(c);
529     refreshFriend(c);
530     if(c->monst == moSlimeNextTurn) c->monst = moSlime;
531     if(c->monst == moLesser && !cellEdgeUnstable(c)) c->monst = moLesserM;
532     else if(c->monst == moLesserM) c->monst = moLesser;
533     if(c->monst == moGreater && !cellEdgeUnstable(c)) c->monst = moGreaterM;
534     else if(c->monst == moGreaterM) c->monst = moGreater;
535 
536     if(c->monst == moPair && !c->stuntime) {
537       cell *c2 = c->move(c->mondir);
538       if(c2->monst != moPair) continue;
539       if(true) for(int i: {-1, 1}) {
540         cell *c3 = c->modmove(c->mondir + i);
541         if(among(c3->wall, waRuinWall, waColumn, waStone, waVinePlant, waPalace)) {
542           drawParticles(c3, winf[c3->wall].color, 30);
543           c3->wall = waNone;
544           }
545         }
546       }
547 
548     if(c->stuntime && !isMutantIvy(c)) {
549       if(turn) c->stuntime--;
550       int breathrange = sphere ? 2 : 3;
551       if(c->stuntime == 0 && c->monst == moDragonHead)  {
552         // if moDragonHead is renamed to "Dragon Head", we might need to change this
553         eMonster subject = c->monst;
554         if(!c->hitpoints) c->hitpoints = 1;
555         else if(shmup::on && dragon::totalhp(c) > 2 && shmup::dragonbreath(c)) {
556           c->hitpoints = 0;
557           }
558         else if(dragon::totalhp(c) <= 2) ;
559         else if(isMounted(c)) {
560           if(dragon::target && celldistance(c, dragon::target) <= breathrange && makeflame(dragon::target, 5, true)) {
561             addMessage(XLAT("%The1 breathes fire!", subject));
562             makeflame(dragon::target, 5, false);
563             playSound(dragon::target, "fire");
564             c->hitpoints = 0;
565             }
566           }
567         else {
568           for(int i=0; i<isize(targets); i++) {
569             cell *t = targets[i];
570             if(celldistance(c, t) <= breathrange && makeflame(t, 5, true)) {
571               if(isPlayerOn(t)) addMessage(XLAT("%The1 breathes fire at you!", subject));
572               else if(t->monst)
573                 addMessage(XLAT("%The1 breathes fire at %the2!", subject, t->monst));
574               else
575                 addMessage(XLAT("%The1 breathes fire!", subject));
576               makeflame(t, 5, false);
577               playSound(t, "fire");
578               c->hitpoints = 0;
579               }
580             }
581           }
582         }
583       }
584 
585     // tortoises who have found their children no longer move
586     if(saved_tortoise_on(c))
587       c->stuntime = 2;
588 
589     if(c->monst == moReptile) {
590       if(c->wall == waChasm || cellUnstable(c)) {
591         c->monst = moNone;
592         c->wall = waReptile;
593         c->wparam = reptilemax();
594         playSound(c, "click");
595         }
596       else if(isChasmy(c) || isWatery(c)) {
597         if(c->wall == waMercury) {
598           fallMonster(c, AF_FALL);
599           c->wall = waNone;
600           }
601         else {
602           c->wall = waReptileBridge;
603           c->wparam = reptilemax();
604           c->monst = moNone;
605           }
606         c->item = itNone;
607         playSound(c, "click");
608         }
609       }
610 
611     if(c->wall == waChasm) {
612       if(c->land != laWhirlwind) c->item = itNone;
613 
614       if(c->monst && !survivesChasm(c->monst) && c->monst != moReptile && normal_gravity_at(c)) {
615         if(c->monst != moRunDog && c->land == laMotion)
616           achievement_gain_once("FALLDEATH1");
617         addMessage(XLAT("%The1 falls!", c->monst));
618         fallMonster(c, AF_FALL);
619         }
620       }
621 
622     else if(isReptile(c->wall) && turn) {
623       if(c->monst || isPlayerOn(c)) c->wparam = -1;
624       else if(c->cpdist <= 7) {
625         c->wparam--;
626         if(c->wparam == 0) {
627           if(c->wall == waReptile) c->wall = waChasm;
628           else placeWater(c, NULL);
629           c->monst = moReptile;
630           c->hitpoints = 3;
631           c->stuntime = 0;
632           vector<int> gooddirs;
633           // in the peace mode, a reptile will
634           // prefer to walk on the ground, rather than the chasm
635           for(int i=0; i<c->type; i++) {
636             int i0 = (i+3) % c->type;
637             int i1 = (i+c->type-3) % c->type;
638             if(c->move(i0) && passable(c->move(i0), c, 0))
639             if(c->move(i1) && passable(c->move(i1), c, 0))
640               gooddirs.push_back(i);
641             }
642           c->mondir = hrand_elt(gooddirs, c->mondir);
643           playSound(c, "click");
644           }
645         }
646       }
647 
648     else if(isFire(c)) {
649       if(c->monst == moSalamander) c->stuntime = max<int>(c->stuntime, 1);
650       else if(c->monst == moVaulter && c->mondir == JUMP)
651         c->mondir = NODIR;
652       else if(c->monst && !survivesFire(c->monst) && !isWorm(c->monst)) {
653         addMessage(XLAT("%The1 burns!", c->monst));
654         if(isBull(c->monst)) {
655           addMessage(XLAT("Fire is extinguished!"));
656           c->wall = waNone;
657           }
658         fallMonster(c, AF_CRUSH);
659         }
660       if(c->item && itemBurns(c->item)) {
661         addMessage(XLAT("%The1 burns!", c->item));
662         c->item = itNone;
663         }
664       }
665 
666     else if(isWatery(c)) {
667       if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM)
668         c->monst = moGreaterShark;
669       if(c->monst && !survivesWater(c->monst) && normal_gravity_at(c)) {
670         playSound(c, "splash"+pick12());
671         if(isNonliving(c->monst))
672           addMessage(XLAT("%The1 sinks!", c->monst));
673         else
674           addMessage(XLAT("%The1 drowns!", c->monst));
675         if(isBull(c->monst)) {
676           addMessage(XLAT("%The1 is filled!", c->wall));
677           c->wall = waShallow;
678           }
679         fallMonster(c, AF_FALL);
680         }
681       }
682     else if(c->wall == waSulphur || c->wall == waSulphurC || c->wall == waMercury) {
683       if(c->monst && !survivesPoison(c->monst, c->wall) && normal_gravity_at(c)) {
684         playSound(c, "splash"+pick12());
685         if(isNonliving(c->monst))
686           addMessage(XLAT("%The1 sinks!", c->monst));
687         else
688           addMessage(XLAT("%The1 drowns!", c->monst));
689         if(isBull(c->monst)) {
690           addMessage(XLAT("%The1 is filled!", c->wall));
691           c->wall = waNone;
692           }
693         fallMonster(c, AF_FALL);
694         }
695       }
696     else if(c->wall == waMagma) {
697       if(c->monst == moSalamander) c->stuntime = max<int>(c->stuntime, 1);
698       else if(c->monst && !survivesPoison(c->monst, c->wall) && normal_gravity_at(c)) {
699         if(isNonliving(c->monst))
700           addMessage(XLAT("%The1 is destroyed by lava!", c->monst));
701         else
702           addMessage(XLAT("%The1 is killed by lava!", c->monst));
703         playSound(c, "steamhiss", 70);
704         fallMonster(c, AF_FALL);
705         }
706       }
707     else if(!isWateryOrBoat(c) && c->wall != waShallow) {
708       if(c->monst == moGreaterShark)
709         c->monst = moGreaterM;
710       else if(c->monst == moShark || c->monst == moCShark) {
711         addMessage(XLAT("%The1 suffocates!", c->monst));
712         fallMonster(c, AF_CRUSH);
713         }
714       else if(c->monst == moKrakenH) {
715         addMessage(XLAT("%The1 suffocates!", c->monst));
716         kraken::kill(c, moNone);
717         }
718       }
719 
720     if(c->monst == moVineSpirit && !cellHalfvine(c) && c->wall != waVinePlant) {
721       addMessage(XLAT("%The1 is destroyed!", c->monst));
722       fallMonster(c, AF_CRUSH);
723       }
724 
725     if(c->monst) mayExplodeMine(c, c->monst);
726 
727     if(c->monst && c->wall == waClosedGate && !survivesWall(c->monst)) {
728       playSound(c, "hit-crush"+pick123());
729       addMessage(XLAT("%The1 is crushed!", c->monst));
730       fallMonster(c, AF_CRUSH);
731       }
732 
733     if(c->monst && cellUnstable(c) && !ignoresPlates(c->monst) && !shmup::on)
734       doesFallSound(c);
735     }
736   }
737 
738 // find worms and ivies
settemp(cell * c)739 EX void settemp(cell *c) {
740   tempmonsters.emplace_back(c, (eMonster) c->monst);
741   c->monst = moNone;
742   }
743 
findWormIvy(cell * c)744 EX void findWormIvy(cell *c) {
745   while(true) {
746     if(c->monst == moWorm || c->monst == moTentacle || c->monst == moWormwait || c->monst == moTentaclewait ||
747       c->monst == moTentacleEscaping) {
748       worms.push_back(c); settemp(c);
749       break;
750       }
751     else if(c->monst == moHexSnake) {
752       hexsnakes.push_back(c); settemp(c);
753       }
754     else if(c->monst == moWormtail || c->monst == moHexSnakeTail) {
755       bool bug = true;
756       for(int i=0; i<c->type; i++) {
757         cell* c2 = c->move(i);
758         if(c2 && isWorm(c2) && c2->mondir != NODIR && c2->move(c2->mondir) == c) {
759           settemp(c);
760           c = c2;
761           bug = false;
762           }
763         }
764       if(bug) break;
765       }
766     else if(c->monst == moIvyWait) {
767       cell* c2 = c->move(c->mondir);
768       settemp(c); c=c2;
769       }
770     else if(c->monst == moIvyHead) {
771       ivies.push_back(c); settemp(c);
772       break;
773       }
774     else if(c->monst == moIvyBranch || c->monst == moIvyRoot) {
775       bool bug = true;
776       for(int i=0; i<c->type; i++) {
777         cell* c2 = c->move(i);
778         if(c2 && (c2->monst == moIvyHead || c2->monst == moIvyBranch) && c2->move(c2->mondir) == c) {
779           settemp(c);
780           c = c2;
781           bug = false;
782           }
783         }
784       if(bug) break;
785       }
786     else break;
787     }
788   }
789 
monstersTurn()790 EX void monstersTurn() {
791   checkSwitch();
792   mirror::breakAll();
793   DEBB(DF_TURN, ("bfs"));
794   bfs();
795   DEBB(DF_TURN, ("charge"));
796   if(elec::havecharge) elec::act();
797   DEBB(DF_TURN, ("mmo"));
798   int phase2 = (1 & items[itOrbSpeed]);
799   if(!phase2) movemonsters();
800 
801   for(cell *pc: player_positions()) if(pc->item == itOrbSafety)  {
802     collectItem(pc, true);
803     return;
804     }
805 
806   if(playerInPower() && (phase2 || !items[itOrbSpeed]) && (havewhat & HF_FAST))
807     moveNormals(moWitchSpeed);
808 
809   if(phase2 && markOrb(itOrbEmpathy)) {
810     bfs();
811     movegolems(AF_FAST);
812     for(int i=0; i<isize(dcal); i++) {
813       if(dcal[i]->monst == moFriendlyGhost && dcal[i]->stuntime)
814         dcal[i]->stuntime--;
815       refreshFriend(dcal[i]);
816       }
817     }
818   DEBB(DF_TURN, ("rop"));
819   if(!dual::state) reduceOrbPowers();
820   int phase1 = (1 & items[itOrbSpeed]);
821   if(dual::state && items[itOrbSpeed]) phase1 = !phase1;
822   DEBB(DF_TURN, ("lc"));
823   if(!phase1) livecaves();
824   if(!phase1) ca::simulate();
825   if(!phase1) heat::processfires();
826 
827   for(cell *c: crush_now) {
828     changes.ccell(c);
829     playSound(NULL, "closegate");
830     if(canAttack(c, moCrusher, c, c->monst, AF_GETPLAYER | AF_CRUSH)) {
831       attackMonster(c, AF_MSG | AF_GETPLAYER | AF_CRUSH, moCrusher);
832       }
833     moveEffect(movei(c, FALL), moDeadBird);
834     destroyBoats(c, NULL, true);
835     explodeBarrel(c);
836     }
837 
838   changes.value_keep(crush_now);
839   changes.value_keep(crush_next);
840   crush_now = move(crush_next);
841   crush_next.clear();
842 
843   DEBB(DF_TURN, ("heat"));
844   heat::processheat();
845   // if(elec::havecharge) elec::drawcharges();
846 
847   orbbull::check();
848 
849   #if CAP_COMPLEX2
850   if(!phase1) terracotta::check();
851   #endif
852 
853   if(items[itOrbFreedom])
854     for(cell *pc: player_positions())
855       checkFreedom(pc);
856 
857   DEBB(DF_TURN, ("check"));
858   checkmove();
859   if(canmove) elec::checklightningfast();
860 
861 
862 #if CAP_HISTORY
863   for(cell *pc: player_positions())
864     history::movehistory.push_back(pc);
865 #endif
866   }
867 
868 }
869