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