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