1 // Hyperbolic Rogue - Map effects
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file mapeffects.cpp
5  *  \brief Routines handling the map effects
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
initcell(cell * c)11 EX void initcell(cell *c) {
12   c->mpdist = INFD;   // minimum distance from the player, ever
13   c->cpdist = INFD;   // current distance from the player
14   c->pathdist = PINFD;// current distance from the player, along paths (used by yetis)
15   c->landparam = 0; c->landflags = 0; c->wparam = 0;
16   c->listindex = -1;
17   c->wall  = waNone;
18   c->item  = itNone;
19   c->monst = moNone;
20   c->bardir = NODIR;
21   c->mondir = NODIR;
22   c->barleft = c->barright = laNone;
23   c->land = laNone;
24   c->ligon = 0;
25   c->stuntime = 0;
26   c->monmirror = 0;
27   }
28 
doesnotFall(cell * c)29 EX bool doesnotFall(cell *c) {
30   changes.ccell(c);
31   if(c->wall == waChasm) return false;
32   else if(cellUnstable(c) && !in_gravity_zone(c)) {
33     fallingFloorAnimation(c);
34     c->wall = waChasm;
35     return false;
36     }
37   return true;
38   }
39 
doesFall(cell * c)40 EX bool doesFall(cell *c) { return !doesnotFall(c); }
41 
doesFallSound(cell * c)42 EX bool doesFallSound(cell *c) {
43   if(c->land != laMotion && c->land != laZebra)
44     playSound(c, "trapdoor");
45   return !doesnotFall(c);
46   }
47 
destroyBoats(cell * c,cell * c2,bool strandedToo)48 EX void destroyBoats(cell *c, cell *c2, bool strandedToo) {
49   changes.ccell(c);
50   if(c->wall == waBoat) placeWater(c, c2);
51   if(strandedToo && c->wall == waStrandedBoat) c->wall = waNone;
52   shmup::destroyBoats(c);
53   }
54 
55 #if HDR
56 enum eGravity { gsNormal, gsLevitation, gsAnti };
57 #endif
58 EX eGravity gravity_state, last_gravity_state;
59 
bird_disruption(cell * c)60 EX bool bird_disruption(cell *c) {
61   return c->cpdist <= 5 && items[itOrbGravity];
62   }
63 
in_gravity_zone(cell * c)64 EX bool in_gravity_zone(cell *c) {
65   return gravity_state && c->cpdist <= 5;
66   }
67 
gravity_zone_diff(cell * c)68 EX int gravity_zone_diff(cell *c) {
69   if(in_gravity_zone(c)) {
70     if(gravity_state == gsLevitation) return 0;
71     if(gravity_state == gsAnti) return -1;
72     }
73   return 1;
74   }
75 
isJWall(cell * c)76 EX bool isJWall(cell *c) {
77   return isWall(c) || c->monst == passive_switch;
78   }
79 
get_static_gravity(cell * c)80 EX eGravity get_static_gravity(cell *c) {
81   if(isGravityLand(c->land))
82     return gsLevitation;
83   if(among(c->wall, waArrowTrap, waFireTrap, waClosePlate, waOpenPlate, waTrapdoor))
84     return gsNormal;
85   forCellEx(c2, c) if(isJWall(c2))
86     return gsAnti;
87   if(isWatery(c) || isChasmy(c) || among(c->wall, waMagma, waMineUnknown, waMineMine, waMineOpen))
88     return gsLevitation;
89   return gsNormal;
90   }
91 
get_move_gravity(cell * c,cell * c2)92 EX eGravity get_move_gravity(cell *c, cell *c2) {
93   if(isGravityLand(c->land) && isGravityLand(c2->land)) {
94     int d = gravityLevelDiff(c, c2);
95     if(d > 0) return gsNormal;
96     if(d == 0) return gsLevitation;
97     if(d < 0) return gsAnti;
98     return gsNormal;
99     }
100   else {
101     if(snakelevel(c) != snakelevel(c2)) {
102       int d = snakelevel(c2) - snakelevel(c);
103       if(d > 0) return gsAnti;
104       if(d == -3) return gsLevitation;
105       return gsNormal;
106       }
107     forCellEx(c3, c) if(isJWall(c3))
108       return gsAnti;
109     forCellEx(c3, c2) if(isJWall(c3))
110       return gsAnti;
111     if(isWatery(c2) && c->wall == waBoat && !againstCurrent(c2, c))
112       return gsNormal;
113     if(isWatery(c2) || isChasmy(c2) || among(c2->wall, waMagma, waMineUnknown, waMineMine, waMineOpen) || anti_alchemy(c2, c))
114       return gsLevitation;
115     return gsNormal;
116     }
117   }
118 
isWarped(cell * c)119 EX bool isWarped(cell *c) {
120   return isWarpedType(c->land) || (!inmirrororwall(c->land) && (items[itOrb37] && c->cpdist <= 4));
121   }
122 
nonAdjacent(cell * c,cell * c2)123 EX bool nonAdjacent(cell *c, cell *c2) {
124   if(isWarped(c) && isWarped(c2) && warptype(c) == warptype(c2)) {
125     /* int i = neighborId(c, c2);
126     cell *c3 = c->modmove(i+1), *c4 = c->modmove(i-1);
127     if(c3 && !isTrihepta(c3)) return false;
128     if(c4 && !isTrihepta(c4)) return false; */
129     return true;
130     }
131   return false;
132   }
133 
nonAdjacentPlayer(cell * c,cell * c2)134 EX bool nonAdjacentPlayer(cell *c, cell *c2) {
135   return nonAdjacent(c, c2) && !markOrb(itOrb37);
136   }
137 
thruVine(cell * c,cell * c2)138 EX bool thruVine(cell *c, cell *c2) {
139   return (cellHalfvine(c) && c2->wall == c->wall && c2 != c);
140   // ((c->wall == waFloorC || c->wall == waFloorD) && c2->wall == c->wall && !c2->item && !c->item);
141   }
142 
useup(cell * c)143 EX void useup(cell *c) {
144   changes.ccell(c);
145   c->wparam--;
146   if(c->wparam == 0) {
147     drawParticles(c, c->wall == waFire ? 0xC00000 : winf[c->wall].color, 10, 50);
148     if(c->wall == waTempFloor)
149       c->wall = waChasm;
150     else if(c->wall == waTempBridge || c->wall == waTempBridgeBlocked || c->wall == waBurningDock || c->land == laBrownian)
151       placeWater(c, c);
152     else {
153       c->wall = c->land == laCaribbean ? waCIsland2 : waNone;
154       }
155     }
156   }
157 
earthFloor(cell * c)158 EX bool earthFloor(cell *c) {
159   changes.ccell(c);
160   if(c->monst) return false;
161   if(c->wall == waDeadwall) { c->wall = waDeadfloor; return true; }
162   if(c->wall == waDune) { c->wall = waNone; return true; }
163   if(c->wall == waStone && c->land != laTerracotta) { c->wall = waNone; return true; }
164   if(c->wall == waAncientGrave || c->wall == waFreshGrave || c->wall == waRuinWall) {
165     c->wall = waNone;
166     return true;
167     }
168   if(c->land == laWet && among(c->wall, waDeepWater, waShallow, waStone)) {
169     c->wall = waNone;
170     return true;
171     }
172   if((c->wall == waSea || c->wall == waNone) && c->land == laOcean) {
173     c->wall = waCIsland;
174     return true;
175     }
176   if(c->wall == waSea && c->land == laCaribbean) {
177     c->wall = waCIsland;
178     return true;
179     }
180   if(c->wall == waSea && c->land == laWarpSea)
181     c->wall = waNone;
182   if(c->wall == waBoat && c->land == laWarpSea)
183     c->wall = waStrandedBoat;
184   if(c->wall == waMercury) {
185     c->wall = waNone;
186     return true;
187     }
188   if((c->wall == waBarrowDig || c->wall == waBarrowWall) && c->land == laBurial) {
189     c->item = itNone;
190     c->wall = waNone;
191     return true;
192     }
193   if(c->wall == waPlatform && c->land == laMountain) {
194     c->wall = waNone;
195     return true;
196     }
197   if(c->wall == waChasm && c->land == laHunting) {
198     c->wall = waNone;
199     return true;
200     }
201   if(c->land == laCursed && among(c->wall, waDeepWater, waShallow)) {
202     c->wall = waNone;
203     return true;
204     }
205   return false;
206   }
207 
earthWall(cell * c)208 EX bool earthWall(cell *c) {
209   changes.ccell(c);
210   if(c->wall == waDeadfloor || c->wall == waDeadfloor2 || c->wall == waEarthD) {
211     c->item = itNone;
212     c->wall = waDeadwall;
213     return true;
214     }
215   if(c->land == laWet && among(c->wall, waDeepWater, waShallow, waNone)) {
216     c->wall = waStone;
217     return true;
218     }
219   if(c->wall == waNone && c->land == laFrog)
220     c->wall = waStone;
221   if(c->wall == waNone && c->land == laEclectic)
222     c->wall = waDeadwall;
223   if(c->wall == waNone && c->land == laMountain) {
224     c->wall = waPlatform;
225     return true;
226     }
227   if(c->wall == waNone && c->land == laDesert) {
228     c->item = itNone;
229     c->wall = waDune;
230     return true;
231     }
232   if(c->wall == waNone && c->land == laRuins) {
233     c->item = itNone;
234     c->wall = waRuinWall;
235     return true;
236     }
237   if(c->wall == waNone && isElemental(c->land)) {
238     c->item = itNone;
239     c->wall = waStone;
240     return true;
241     }
242   if(c->wall == waNone && c->land == laRedRock) {
243     c->item = itNone;
244     c->wall = waRed3;
245     return true;
246     }
247   if(c->wall == waNone && c->land == laSnakeNest) {
248     c->item = itNone;
249     c->wall = waRed3;
250     return true;
251     }
252   if(c->wall == waNone && c->land == laBurial) {
253     c->item = itNone;
254     c->wall = waBarrowDig;
255     return true;
256     }
257   if(c->wall == waNone && c->land == laHunting) {
258     c->item = itNone;
259     c->wall = waChasm;
260     return true;
261     }
262   if(c->wall == waNone && c->land == laTerracotta) {
263     c->wall = waMercury;
264     return true;
265     }
266   if(c->wall == waArrowTrap && c->land == laTerracotta) {
267     destroyTrapsOn(c);
268     c->wall = waMercury;
269     return true;
270     }
271   if(c->land == laCursed && among(c->wall, waNone, waShallow)) {
272     c->wall = waSea;
273     return true;
274     }
275   if(c->wall == waCIsland || c->wall == waCIsland2 || (c->wall == waNone && c->land == laOcean)) {
276     c->item = itNone;
277     c->wall = waSea;
278     if(c->land == laOcean) c->landparam = 40;
279     return true;
280     }
281   return false;
282   }
283 
snakepile(cell * c,eMonster m)284 EX bool snakepile(cell *c, eMonster m) {
285   changes.ccell(c);
286   if(c->wall == waSea && c->land == laOcean) {
287     c->land = laBrownian, c->landparam = 0;
288     }
289   if(c->land == laWestWall) return false;
290   if(c->land == laBrownian) {
291     if(c->wall == waNone) {
292       #if CAP_COMPLEX2
293       c->landparam += brownian::level;
294       #endif
295       return true;
296       }
297     if(c->wall == waSea || c->wall == waBoat) {
298       c->wall = waNone;
299       c->landparam++;
300       return true;
301       }
302     }
303   if(c->item && c->wall != waRed3) c->item = itNone;
304   if(c->wall == waRed1 || c->wall == waOpenGate) c->wall = waRed2;
305   else if(c->wall == waRed2) c->wall = waRed3;
306   else if(doesFall(c)) return false;
307   else if((c->wall == waSea && c->land == laLivefjord))
308     c->wall = waNone;
309   else if((c->wall == waSea && isWarpedType(c->land)))
310     c->wall = waShallow;
311   else if(isGravityLand(c->land)) {
312     if(m == moHexSnake)
313       c->wall = waPlatform;
314     else
315       c->wall = waDeadTroll2;
316     }
317   else if(c->wall == waNone || isAlchAny(c) ||
318     c->wall == waCIsland || c->wall == waCIsland2 ||
319     c->wall == waOpenPlate || c->wall == waClosePlate ||
320     c->wall == waMineUnknown || c->wall == waMineOpen || isReptile(c->wall)) {
321     if(isReptile(c->wall)) kills[moReptile]++;
322     c->wall = waRed1;
323     if(among(m, moDarkTroll, moBrownBug)) c->wall = waDeadfloor2;
324     }
325   else if(c->wall == waDeadfloor)
326     c->wall = waDeadfloor2;
327   else if(c->wall == waDeadfloor2) {
328     if(m == moDarkTroll && c->land == laDeadCaves) return false;
329     else
330       c->wall = waDeadwall;
331     }
332   else if(c->wall == waRubble || c->wall == waGargoyleFloor || c->wall == waGargoyleBridge ||
333     c->wall == waTempFloor || c->wall == waTempBridge || c->wall == waPetrifiedBridge) {
334     if(c->land == laWhirlpool) return false;
335     c->wall = waRed2;
336     if(m == moDarkTroll) c->wall = waDeadwall;
337     }
338   else if(c->wall == waCavefloor) c->wall = waCavewall;
339   else if(c->wall == waSea && c->land == laCaribbean) c->wall = waShallow;
340   else if(c->wall == waSea && c->land == laWhirlpool) return false;
341   else if(c->wall == waSea) c->wall = waShallow;
342   else if(c->wall == waDeepWater) c->wall = waShallow;
343   else if(c->wall == waShallow) c->wall = waNone;
344   else if(c->wall == waLake) c->wall = waShallow;
345   else if(isWateryOrBoat(c) || c->wall == waFrozenLake) c->wall = waNone;
346   else if(isWateryOrBoat(c) || c->wall == waFrozenLake) c->wall = waNone;
347   else if(cellHalfvine(c)) {
348     destroyHalfvine(c, waRed1);
349     if(c->wall == waRed1 && m == moDarkTroll) c->wall = waDeadfloor2;
350     }
351   else return false;
352   return true;
353   }
354 
makeflame(cell * c,int timeout,bool checkonly)355 EX bool makeflame(cell *c, int timeout, bool checkonly) {
356   changes.ccell(c);
357   if(!checkonly) destroyTrapsOn(c);
358   if(itemBurns(c->item) && !isWatery(c) && c->wall != waShallow) {
359     if(checkonly) return true;
360     if(c->cpdist <= 7)
361       addMessage(XLAT("%The1 burns!", c->item));
362     c->item = itNone;
363     }
364   if(cellUnstable(c)) {
365     if(checkonly) return true;
366     doesFall(c);
367     }
368   else if(c->wall == waChasm || c->wall == waOpenGate || c->wall == waRed2 || c->wall == waRed3 ||
369     c->wall == waTower)
370     return false;
371   else if(c->wall == waBoat) {
372     if(isPlayerOn(c) && markOrb(itOrbWinter)) {
373       if(!checkonly) addMessage(XLAT("%the1 protects your boat!", itOrbWinter));
374       return true;
375       }
376     if(checkonly) return true;
377     if(c->cpdist <= 7)
378       addMessage(XLAT("%The1 burns!", winf[c->wall].name));
379     drawFireParticles(c, 24);
380     placeWater(c, c);
381     if(isIcyLand(c)) HEAT(c) += 1;
382     }
383   else if(c->wall == waNone && c->land == laCocytus) {
384     if(checkonly) return true;
385     c->wall = waLake, HEAT(c) += 1;
386     }
387   else if(c->wall == waFireTrap) {
388     if(checkonly) return true;
389     if(c->wparam == 0) c->wparam = 1;
390     }
391   else if(c->wall == waFrozenLake) {
392     if(checkonly) return true;
393     drawParticles(c, MELTCOLOR, 8, 8);
394     c->wall = waLake, HEAT(c) += 1;
395     }
396   else if(c->wall == waIcewall) {
397     if(checkonly) return true;
398     drawParticles(c, MELTCOLOR, 8, 8);
399     c->wall = waNone;
400     }
401   else if(c->wall == waMineMine) {
402     if(checkonly) return true;
403     explodeMine(c);
404     }
405   else if(c->wall != waCTree && c->wall != waBigTree && c->wall != waSmallTree &&
406     c->wall != waVinePlant && !passable(c, NULL, P_MONSTER | P_MIRROR) &&
407     c->wall != waSaloon && c->wall != waRose) return false;
408   // reptiles are able to use the water to put the fire off
409   else if(c->wall == waReptileBridge) return false;
410   else if(c->wall == waDock) {
411     if(checkonly) return true;
412     c->wall = waBurningDock;
413     c->wparam = 3;
414     return false;
415     }
416   else {
417     eWall w = eternalFire(c) ? waEternalFire : waFire;
418     if(!checkonly) drawFireParticles(c, 10);
419     if(w == c->wall) return false;
420     if(checkonly) return true;
421     if(isReptile(c->wall)) kills[moReptile]++;
422     destroyHalfvine(c);
423     if(!isFire(c)) c->wparam = 0;
424     c->wall = w;
425     c->wparam = max(c->wparam, (char) timeout);
426     if(c->land == laBrownian) c->landparam = 0;
427     }
428   return true;
429   }
430 
makeshallow(cell * c,int timeout,bool checkonly)431 EX bool makeshallow(cell *c, int timeout, bool checkonly) {
432   changes.ccell(c);
433   if(!checkonly) destroyTrapsOn(c);
434   #if CAP_COMPLEX2
435   if(c->land == laBrownian) {
436     if(checkonly) return true;
437     brownian::dissolve(c, 1);
438     }
439   #endif
440   if(c->wall == waChasm || c->wall == waOpenGate || c->wall == waRed2 || c->wall == waRed3 ||
441     c->wall == waTower)
442     return false;
443   else if(c->wall == waNone && c->land == laCocytus) {
444     if(checkonly) return true;
445     c->wall = waLake;
446     }
447   else if(c->wall == waFireTrap) {
448     if(checkonly) return true;
449     c->wall = waShallow;
450     }
451   else if(c->wall == waFrozenLake) {
452     if(checkonly) return true;
453     drawParticles(c, MELTCOLOR, 8, 8);
454     c->wall = waLake;
455     }
456   else if(isFire(c)) {
457     if(checkonly) return true;
458     c->wall = waNone;
459     }
460   else if(c->wall == waMineMine) {
461     if(checkonly) return true;
462     c->wall = waShallow;
463     }
464   else if(among(c->wall, waNone, waRubble, waDeadfloor2, waCavefloor, waDeadfloor, waFloorA, waFloorB) && !cellUnstable(c) && !isGravityLand(c)) {
465     if(checkonly) return true;
466     c->wall = waShallow;
467     }
468   else if(c->wall == waDock) {
469     if(checkonly) return true;
470     c->wall = waSea;
471     c->wparam = 3;
472     return false;
473     }
474   return true;
475   }
476 
explosion(cell * c,int power,int central)477 EX void explosion(cell *c, int power, int central) {
478   changes.ccell(c);
479   playSound(c, "explosion");
480   drawFireParticles(c, 30, 150);
481 
482   #if CAP_COMPLEX2
483   brownian::dissolve_brownian(c, 2);
484   #endif
485   makeflame(c, central, false);
486 
487   forCellEx(c2, c) {
488     changes.ccell(c2);
489     destroyTrapsOn(c2);
490     #if CAP_COMPLEX2
491     brownian::dissolve_brownian(c2, 1);
492     #endif
493     if(c2->wall == waRed2 || c2->wall == waRed3)
494       c2->wall = waRed1;
495     else if(c2->wall == waDeadTroll || c2->wall == waDeadTroll2 || c2->wall == waPetrified || c2->wall == waGargoyle) {
496       c2->wall = waNone;
497       makeflame(c2, power/2, false);
498       }
499     else if(c2->wall == waPetrifiedBridge || c2->wall == waGargoyleBridge) {
500       placeWater(c, c);
501       }
502     else if(c2->wall == waPalace || c2->wall == waOpenGate || c2->wall == waClosedGate ||
503       c2->wall == waSandstone || c2->wall == waMetal || c2->wall == waSaloon || c2->wall == waRuinWall) {
504       c2->wall = waNone;
505       makeflame(c2, power/2, false);
506       }
507     else if(c2->wall == waTower)
508       c2->wall = waRubble;
509     else if(c2->wall == waBarrowWall)
510       c2->wall = waBarrowDig;
511     else if(c2->wall == waBarrowDig)
512       c2->wall = waNone;
513     else if(c2->wall == waFireTrap) {
514       if(c2->wparam == 0)
515         c2->wparam = 1;
516       }
517     else if(c2->wall == waExplosiveBarrel)
518       explodeBarrel(c2);
519     else makeflame(c2, power, false);
520     }
521   }
522 
explodeMine(cell * c)523 EX void explodeMine(cell *c) {
524   if(c->wall != waMineMine)
525     return;
526 
527   changes.ccell(c);
528   c->wall = waMineOpen;
529   explosion(c, 20, 20);
530   #if CAP_COMPLEX2
531   changes.at_commit(mine::auto_teleport_charges);
532   #endif
533   }
534 
explodeBarrel(cell * c)535 EX void explodeBarrel(cell *c) {
536   if(c->wall != waExplosiveBarrel)
537     return;
538 
539   changes.ccell(c);
540   c->wall = waNone;
541   explosion(c, 20, 20);
542   }
543 
mayExplodeMine(cell * c,eMonster who)544 EX bool mayExplodeMine(cell *c, eMonster who) {
545   if(c->wall != waMineMine) return false;
546   if(ignoresPlates(who)) return false;
547   explodeMine(c);
548   return true;
549   }
550 
flameHalfvine(cell * c,int val)551 EX void flameHalfvine(cell *c, int val) {
552   changes.ccell(c);
553   if(itemBurns(c->item)) {
554     addMessage(XLAT("%The1 burns!", c->item));
555     c->item = itNone;
556     }
557   c->wall = waPartialFire;
558   c->wparam = val;
559   }
560 
561 EX bool destroyHalfvine(cell *c, eWall newwall IS(waNone), int tval IS(6)) {
562   if(cellHalfvine(c)) {
563     changes.ccell(c);
564     forCellEx(c1, c) if(c1->wall == c->wall) {
565       changes.ccell(c1);
566       if(newwall == waPartialFire) flameHalfvine(c1, tval);
567       else if(newwall == waRed1) c1->wall = waVinePlant;
568       else c1->wall = newwall;
569       }
570     if(newwall == waPartialFire) flameHalfvine(c, tval);
571     else c->wall = newwall;
572     return true;
573     }
574   return false;
575   }
576 
coastvalEdge(cell * c)577 EX int coastvalEdge(cell *c) { return coastval(c, laIvoryTower); }
578 
gravityLevel(cell * c)579 EX int gravityLevel(cell *c) {
580   if(c->land == laIvoryTower || c->land == laEndorian)
581     return coastval(c, laIvoryTower);
582   if(c->land == laDungeon)
583     return -coastval(c, laIvoryTower);
584   if(c->land == laMountain)
585     return 1-celldistAlt(c);
586   return 0;
587   }
588 
gravityLevelDiff(cell * c,cell * d)589 EX int gravityLevelDiff(cell *c, cell *d) {
590   if(c->land != laWestWall || d->land != laWestWall)
591     return gravityLevel(c) - gravityLevel(d);
592 
593   if(shmup::on) return 0;
594 
595   int nid = neighborId(c, d);
596   int id1 = parent_id(c, 1, coastvalEdge) + 1;
597   int di1 = angledist(c->type, id1, nid);
598 
599   int id2 = parent_id(c, -1, coastvalEdge) - 1;
600   int di2 = angledist(c->type, id2, nid);
601 
602   if(di1 < di2) return 1;
603   if(di1 > di2) return -1;
604   return 0;
605   }
606 
canUnstable(eWall w,flagtype flags)607 EX bool canUnstable(eWall w, flagtype flags) {
608   return w == waNone || w == waCanopy || w == waOpenPlate || w == waClosePlate ||
609     w == waOpenGate || ((flags & MF_STUNNED) && (w == waLadder || w == waTrunk || w == waSolidBranch || w == waWeakBranch
610     || w == waBigBush || w == waSmallBush));
611   }
612 
613 EX bool cellEdgeUnstable(cell *c, flagtype flags IS(0)) {
614   if(!isGravityLand(c->land) || !canUnstable(c->wall, flags)) return false;
615   if(shmup::on && c->land == laWestWall) return false;
forCellEx(c2,c)616   forCellEx(c2, c) {
617     if(isAnyIvy(c2->monst) &&
618       c->land == laMountain && !(flags & MF_IVY)) return false;
619     int d = gravityLevelDiff(c, c2);
620     if(d == gravity_zone_diff(c)) {
621       if(againstWind(c2, c)) return false;
622       if(!passable(c2, NULL, P_MONSTER | P_DEADLY))
623         return false;
624       if(isWorm(c2))
625         return false;
626       }
627     if(WDIM == 3) {
628       if(d == 0 && !passable(c2, NULL, P_MONSTER | P_DEADLY)) return false;
629       if(d == -1 && !passable(c2, NULL, P_MONSTER | P_DEADLY)) forCellEx(c3, c2) if(c3 != c && gravityLevelDiff(c3, c) == 0) return false;
630       }
631     }
632   return true;
633   }
634 
635 int tidalphase;
636 
637 EX int tidalsize, tide[200];
638 
calcTidalPhase()639 EX void calcTidalPhase() {
640   if(!tidalsize) {
641     for(int i=0; i<5; i++) tide[tidalsize++] = 1;
642 
643     for(int i=1; i<4; i++) for(int j=0; j<3; j++)
644       tide[tidalsize++] = i;
645 
646     for(int i=4; i<7; i++) for(int j=0; j<2; j++)
647       tide[tidalsize++] = i;
648 
649     for(int i=7; i<20; i++)
650       tide[tidalsize++] = i;
651 
652     for(int i=20; i<23; i++) for(int j=0; j<2; j++)
653       tide[tidalsize++] = i;
654 
655     for(int i=23; i<26; i++) for(int j=0; j<3; j++)
656       tide[tidalsize++] = i;
657 
658     for(int i=0; i+i<tidalsize; i++) tide[tidalsize++] = 27 - tide[i];
659 
660     /* printf("tidalsize = %d\n", tidalsize);
661     for(int i=0; i<tidalsize; i++) printf("%d ", tide[i]);
662     printf("\n"); */
663     }
664   tidalphase = tide[
665     (shmup::on ? shmup::curtime/600 : turncount)
666     % tidalsize];
667   if(peace::on)
668     tidalphase = 5 + tidalphase / 6;
669   }
670 
tidespeed()671 EX int tidespeed() {
672   return tide[(turncount+6) % tidalsize] - tidalphase;
673   }
674 
675 EX bool recalcTide;
676 
677 #if HDR
678 #define SEADIST LHU.bytes[0]
679 #define LANDDIST LHU.bytes[1]
680 #define CHAOSPARAM LHU.bytes[2]
681 #endif
682 
683 #if CAP_FIELD
lavatide(cell * c,int t)684 EX int lavatide(cell *c, int t) {
685   int tc = (shmup::on ? shmup::curtime/400 : turncount);
686   return (windmap::at(c) + (tc+t)*4) & 255;
687   }
688 #endif
689 
checkTide(cell * c)690 EX void checkTide(cell *c) {
691   if(c->land == laOcean) {
692     int t = c->landparam;
693 
694     if(ls::any_chaos()) {
695       char& csd(c->SEADIST); if(csd == 0) csd = 7;
696       char& cld(c->LANDDIST); if(cld == 0) cld = 7;
697       int seadist=csd, landdist=cld;
698       for(int i=0; i<c->type; i++) {
699         cell *c2 = c->move(i);
700         if(!c2) continue;
701         if(c2->land == laBarrier || c2->land == laOceanWall) ;
702         else if(c2->land == laOcean)
703           seadist = min(seadist, c2->SEADIST ? c2->SEADIST+1 : 7),
704           landdist = min(landdist, c2->LANDDIST ? c2->LANDDIST+1 : 7);
705         else if(isSealand(c2->land)) seadist = 1;
706         else landdist = 1;
707         }
708       if(seadist < csd) csd = seadist, recalcTide = true;
709       if(landdist < cld) cld = landdist, recalcTide = true;
710       if(seadist == 1 && landdist == 1) t = 15;
711       else t = c->CHAOSPARAM = 1 + (29 * (landdist-1)) / (seadist+landdist-2);
712       }
713 
714     if(c->wall == waStrandedBoat || c->wall == waBoat)
715       c->wall = t >= tidalphase ? waBoat : waStrandedBoat;
716     if(c->wall == waSea || c->wall == waNone)
717       c->wall = t >= tidalphase ? waSea : waNone;
718     if(isFire(c) && t >= tidalphase)
719       c->wall = waSea;
720     }
721   #if CAP_FIELD
722   if(c->land == laVolcano) {
723     int id = lavatide(c, 0);
724     if(id < 96) {
725       if(c->wall == waNone || isWateryOrBoat(c) || c->wall == waVinePlant || isAlch(c)) {
726         if(isWateryOrBoat(c) || isAlch(c))
727           playSound(c, "steamhiss");
728         c->wall = waMagma;
729         if(itemBurns(c->item)) {
730           addMessage(XLAT("%The1 burns!", c->item)), c->item = itNone;
731           playSound(c, "steamhiss", 30);
732           }
733         }
734       }
735     else if(c->wall == waMagma) c->wall = waNone;
736     }
737   #endif
738   }
739 
makeEmpty(cell * c)740 EX bool makeEmpty(cell *c) {
741 
742   if(c->monst != moPrincess) {
743     if(isAnyIvy(c->monst)) killMonster(c, moPlayer, 0);
744     else if(c->monst == moPair) {
745       if(c->move(c->mondir)->monst == moPair)
746         c->move(c->mondir)->monst = moNone;
747       }
748     else if(isWorm(c->monst)) {
749       if(!items[itOrbDomination]) return false;
750       }
751     else c->monst = moNone;
752     }
753 
754   if(c->land == laCanvas) ;
755   else if(c->land == laCocytus)
756     c->wall = waFrozenLake;
757   else if(c->land == laAlchemist || c->land == laCanvas)
758     ;
759   else if(c->land == laDual)
760     ;
761   else if(c->land == laCaves || c->land == laEmerald)
762     c->wall = waCavefloor;
763   else if(c->land == laDeadCaves)
764     c->wall = waDeadfloor2;
765   else if(c->land == laCaribbean || c->land == laOcean || c->land == laWhirlpool || c->land == laLivefjord || c->land == laWarpSea || c->land == laKraken || c->wall == waBoat)
766     c->wall = waBoat; // , c->item = itOrbYendor;
767   else if(c->land == laMinefield)
768     c->wall = waMineOpen;
769   else if(c->wall == waFan && bounded)
770     ;
771   else if(c->wall == waOpenPlate && bounded)
772     ;
773   else if(c->wall == waTrunk || c->wall == waSolidBranch || c->wall == waWeakBranch)
774     ;
775   else if(c->wall == waGiantRug)
776     ;
777   else if(c->wall == waInvisibleFloor)
778     ;
779   else if(c->wall == waDock)
780     ;
781   else if(c->wall == waLadder)
782     ;
783   else if(c->land == laDocks)
784     c->wall = waBoat;
785   else if(c->wall == waFreshGrave && bounded)
786     ;
787   else if(c->wall == waBarrier && sphere && WDIM == 3)
788     ;
789   else if(isReptile(c->wall))
790     c->wparam = reptilemax();
791   else if(c->wall == waAncientGrave && bounded)
792     ;
793   else if(c->wall != waRoundTable)
794     c->wall = waNone;
795 
796   if(c->land == laBrownian && c->wall == waNone && c->landparam == 0)
797     c->landparam = 1;
798 
799   if(c->item != itCompass)
800     c->item = itNone;
801 
802   if(c->land == laEclectic) {
803     c->wall = waNone;
804     forCellEx(c1, c)
805       c1->wall = waNone;
806     }
807 
808   if(c->land == laWildWest) {
809     forCellEx(c2, c)
810     forCellEx(c3, c2)
811       if(c3->wall != waBarrier)
812         c3->wall = waNone;
813     }
814 
815   return true;
816   }
817 
818 int numgates = 0;
819 
toggleGates(cell * c,eWall type,int rad)820 EX void toggleGates(cell *c, eWall type, int rad) {
821   if(!c) return;
822   celllister cl(c, rad, 1000000, NULL);
823   for(cell *ct: cl.lst) {
824     if(ct->wall == waOpenGate && type == waClosePlate) {
825       bool onWorm = false;
826       if(isWorm(ct)) onWorm = true;
827       for(int i=0; i<ct->type; i++)
828         if(ct->move(i) && ct->move(i)->wall == waOpenGate && isWorm(ct->move(i))) onWorm = true;
829       if(!onWorm) {
830         changes.ccell(ct);
831         ct->wall = waClosedGate, numgates++;
832         if(ct->item) {
833           playSound(ct, "hit-crush"+pick123());
834           addMessage(XLAT("%The1 is crushed!", ct->item));
835           ct->item = itNone;
836           }
837         toggleGates(ct, type, 1);
838         }
839       }
840     if(ct->wall == waClosedGate && type == waOpenPlate) {
841       changes.ccell(ct);
842       ct->wall = waOpenGate, numgates++;
843       toggleGates(ct, type, 1);
844       }
845     }
846   }
847 
toggle_radius(eWall type)848 EX int toggle_radius(eWall type) {
849   if(type == waClosePlate && PURE)
850     return 2;
851   else
852     return (GOLDBERG && !sphere && !a4) ? gp::dist_3() : 3;
853   }
854 
toggleGates(cell * ct,eWall type)855 EX void toggleGates(cell *ct, eWall type) {
856   playSound(ct, "click");
857   numgates = 0;
858   toggleGates(ct, type, toggle_radius(type));
859   if(numgates && type == waClosePlate)
860     playSound(ct, "closegate");
861   if(numgates && type == waOpenPlate)
862     playSound(ct, "opengate");
863   }
864 
destroyTrapsOn(cell * c)865 EX void destroyTrapsOn(cell *c) {
866   if(c->wall == waArrowTrap) {
867     changes.ccell(c);
868     c->wall = waNone;
869     drawParticles(c, 0xFF0000, 4);
870     destroyTrapsAround(c);
871     }
872   }
873 
destroyTrapsAround(cell * c)874 EX void destroyTrapsAround(cell *c) {
875   forCellEx(c2, c) destroyTrapsOn(c2);
876   }
877 
destroyWeakBranch(cell * cf,cell * ct,eMonster who)878 EX void destroyWeakBranch(cell *cf, cell *ct, eMonster who) {
879   if(cf && ct && cf->wall == waWeakBranch && cellEdgeUnstable(ct) &&
880     gravityLevelDiff(ct, cf) >= 0 && !ignoresPlates(who)) {
881     changes.ccell(cf);
882     cf->wall = waCanopy;
883     if(!cellEdgeUnstable(cf)) { cf->wall = waWeakBranch; return; }
884     playSound(cf, "trapdoor");
885     drawParticles(cf, winf[waWeakBranch].color, 4);
886     }
887   if(cf && ct && cf->wall == waSmallBush && cellEdgeUnstable(ct) &&
888     gravityLevelDiff(ct, cf) >= 0 && !ignoresPlates(who)) {
889     changes.ccell(cf);
890     cf->wall = waNone;
891     if(!cellEdgeUnstable(cf)) { cf->wall = waSmallBush; return; }
892     playSound(cf, "trapdoor");
893     drawParticles(cf, winf[waWeakBranch].color, 4);
894     }
895   }
896 
isCentralTrap(cell * c)897 EX bool isCentralTrap(cell *c) {
898   if(c->wall != waArrowTrap) return false;
899   int i = 0;
900   forCellEx(c2, c) if(c2->wall == waArrowTrap) i++;
901   return i == 2;
902   }
903 
traplimits(cell * c)904 EX array<cell*, 5> traplimits(cell *c) {
905   array<cell*, 5> res;
906   int q = 0;
907   res[2] = c;
908   for(int d=0; d<c->type; d++) {
909     cellwalker cw(c, d);
910     cw += wstep;
911     if(cw.at->wall != waArrowTrap) continue;
912     res[1+q*2] = cw.at;
913     cw += (cw.at->type/2);
914     if((cw.at->type&1) && (cw+wstep).at->wall != waStone) cw += 1;
915     cw += wstep;
916     res[(q++)*4] = cw.at;
917     }
918   while(q<2) { res[q*4] = res[1+q*2] = NULL; q++; }
919   return res;
920   }
921 
activateArrowTrap(cell * c)922 EX void activateArrowTrap(cell *c) {
923   if(c->wall == waArrowTrap && c->wparam == 0) {
924     changes.ccell(c);
925     playSound(c, "click");
926     c->wparam = shmup::on ? 2 : 1;
927     forCellEx(c2, c) activateArrowTrap(c2);
928     if(shmup::on) shmup::activateArrow(c);
929     }
930   }
931 
932 #if HDR
933 template<class T>
determinePush(cellwalker who,int subdir,const T & valid)934 movei determinePush(cellwalker who, int subdir, const T& valid) {
935   if(subdir != 1 && subdir != -1) {
936     subdir = 1;
937     static bool first = true;
938     if(first)
939       first = false,
940       addMessage("bad push: " + its(subdir));
941     }
942   cellwalker push = who;
943   push += wstep;
944   cell *c2 = push.at;
945   if(bt::in()) {
946     auto rd = reverse_directions(push.at, push.spin);
947     for(int i: rd) {
948       push.spin = i;
949       movei mi = movei(push.at, i);
950       if(valid(mi)) return mi;
951       }
952     return movei(c2, NO_SPACE);
953     }
954   int pd = push.at->type/2;
955   push += pd * -subdir;
956   movei mi(push.at, push.spin);
957   push += wstep;
958   if(valid(mi)) return mi;
959   if(c2->type&1) {
960     push = push + wstep - subdir + wstep;
961     if(valid(mi)) return mi;
962     }
963   if(gravityLevelDiff(push.at, c2) < 0) {
964     push = push + wstep + 1 + wstep;
965     if(gravityLevelDiff(push.at, c2) < 0) {
966       push = push + wstep - 2 + wstep;
967       }
968     if(gravityLevelDiff(push.at, c2) < 0) {
969       push = push + wstep + 1 + wstep;
970       }
971     movei mi = movei(c2, (push+wstep).spin);
972     if(valid(mi)) return mi;
973     }
974   return movei(c2, NO_SPACE);
975   }
976 #endif
977 
978 // for sandworms
explodeAround(cell * c)979 EX void explodeAround(cell *c) {
980   forCellEx(c2, c) {
981       if(isIcyLand(c2)) HEAT(c2) += 0.5;
982       eWall ow = c2->wall;
983       changes.ccell(c2);
984       if((c2->wall == waDune || c2->wall == waIcewall ||
985         c2->wall == waAncientGrave || c2->wall == waFreshGrave ||
986         c2->wall == waColumn || c2->wall == waThumperOff || c2->wall == waThumperOn ||
987         (isFire(c2) && !eternalFire(c2)) ||
988         c2->wall == waBigTree || c2->wall == waSmallTree ||
989         c2->wall == waVinePlant || c2->wall == waVineHalfA || c2->wall == waVineHalfB)) {
990         destroyHalfvine(c2);
991         c2->wall = waNone;
992         }
993       if(c2->wall == waExplosiveBarrel) explodeBarrel(c2);
994       if(c2->wall == waCavewall || c2->wall == waDeadTroll) c2->wall = waCavefloor;
995       if(c2->wall == waDeadTroll2) c2->wall = waNone;
996       if(c2->wall == waPetrified) c2->wall = waNone;
997       if(c2->wall == waDeadfloor2) c2->wall = waDeadfloor;
998       if(c2->wall == waGargoyleFloor) c2->wall = waChasm;
999       if(c2->wall == waGargoyleBridge || c2->wall == waPetrifiedBridge) placeWater(c2, c2);
1000       if(c2->wall == waRubble) c2->wall = waNone;
1001       if(c2->wall == waPlatform) c2->wall = waNone;
1002       if(c2->wall == waStone) c2->wall = waNone, destroyTrapsAround(c2);
1003       if(c2->wall == waRose) c2->wall = waNone;
1004       if(c2->wall == waRuinWall) c2->wall = waNone;
1005       if(c2->wall == waLadder) c2->wall = waNone;
1006       if(c2->wall == waGargoyle) c2->wall = waNone;
1007       if(c2->wall == waSandstone) c2->wall = waNone;
1008       if(c2->wall == waSaloon) c2->wall = waNone;
1009       if(c2->wall == waDeadwall) c2->wall = waDeadfloor2;
1010       if(c2->wall == waBigStatue) c2->wall = waNone;
1011       if(c2->wall == waPalace || c2->wall == waOpenGate || c2->wall == waClosedGate)
1012         c2->wall = waNone;
1013       if(isAlch(c2) && isAlch(c))
1014         c2->wall = c->wall;
1015       if(c2->wall != ow && ow) drawParticles(c2, winf[ow].color, 16);
1016       }
1017   }
1018 
earthMove(const movei & mi)1019 EX bool earthMove(const movei& mi) {
1020   auto& from = mi.s;
1021   bool b = false;
1022   cell *c2 = mi.t;
1023   b |= earthWall(from);
1024   if(!mi.proper()) return b;
1025   int d = mi.rev_dir_or(0);
1026   if(c2) for(int u=2; u<=c2->type-2; u++) {
1027     cell *c3 = c2->modmove(d + u);
1028     if(c3) b |= earthFloor(c3);
1029     }
1030   return b;
1031   }
1032 
cellDangerous(cell * c)1033 EX bool cellDangerous(cell *c) {
1034   return cellUnstableOrChasm(c) || isFire(c) || c->wall == waClosedGate;
1035   }
1036 
1037 
1038 }
1039