1 // Hyperbolic Rogue - passability
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file checkmove.cpp
5  *  \brief check whether monster/PC can move in the given direction
6  */
7 
8 #include "hyper.h"
9 
10 namespace hr {
11 
12 // === MOVEMENT FUNCTIONS ===
13 
14 // w = from->move(d)
againstCurrent(cell * w,cell * from)15 EX bool againstCurrent(cell *w, cell *from) {
16   if(from->land != laWhirlpool) return false;
17   if(againstWind(from, w)) return false; // wind is stronger than current
18   if(!eubinary && (!from->master->alt || !w->master->alt)) return false;
19   int dfrom = celldistAlt(from);
20   int dw = celldistAlt(w);
21   if(dw < dfrom) return false;
22   if(dfrom < dw) return true;
23   for(int d=0; d<from->type; d++)
24     if(from->move(d) == w) {
25        cell *c3 = from->modmove(d-1);
26        if(!c3) return false;
27        return celldistAlt(c3) < dfrom;
28        }
29   return false;
30   }
31 
boatGoesThrough(cell * c)32 EX bool boatGoesThrough(cell *c) {
33   if(isGravityLand(c->land)) return false;
34   return
35     (c->wall == waNone && c->land != laMotion && c->land != laZebra && c->land != laReptile) ||
36     isAlchAny(c) ||
37     c->wall == waCavefloor || c->wall == waFrozenLake || isReptile(c->wall) ||
38     c->wall == waDeadfloor || c->wall == waCIsland || c->wall == waCIsland2 ||
39     c->wall == waMineUnknown || c->wall == waMineMine || c->wall == waMineOpen ||
40     c->wall == waBonfireOff || c->wall == waFire || c->wall == waPartialFire ||
41     c->wall == waArrowTrap || c->wall == waShallow;
42   }
43 
become_water(cell * c)44 EX void become_water(cell *c) {
45   if(isIcyLand(c))
46     c->wall = waLake;
47   else if(isCoastal(c) || isSealand(c))
48     c->wall = waSea;
49   else
50     c->wall = waDeepWater;
51   }
52 
placeWater(cell * c,cell * c2)53 EX void placeWater(cell *c, cell *c2) {
54   destroyTrapsOn(c);
55   if(isWatery(c)) ;
56   else if(c2 && isAlchAny(c2))
57     c->wall = c2->wall;
58   else become_water(c);
59   // destroy the ancient treasure!
60   if(c->item == itBarrow) c->item = itNone;
61   }
62 
incline(cell * cfrom,cell * cto)63 EX int incline(cell *cfrom, cell *cto) {
64   return snakelevel(cto) - snakelevel(cfrom);
65   }
66 
67 #define F(x) checkflags(flags,x)
68 
checkflags(flagtype flags,flagtype x)69 EX bool checkflags(flagtype flags, flagtype x) {
70   if(flags & x) return true;
71   if(flags & P_ISPLAYER) {
72     if((x & P_WINTER)    && (markOrb(itOrbWinter) || markOrb(itCurseWater))) return true;
73     if((x & P_IGNORE37)  && markOrb(itOrb37)) return true;
74     if((x & P_FISH)      && markOrb(itOrbFish)) return true;
75     if((x & P_MARKWATER) && markOrb(itOrbWater)) return true;
76     if((x & P_AETHER)    && markOrb2(itOrbAether) && !(flags&P_NOAETHER)) return true;
77     if((x & P_WATERCURSE)&& markOrb2(itCurseWater)) return true;
78     }
79   if(flags & P_ISFRIEND) if(items[itOrbEmpathy])
80     if(checkflags(flags ^ P_ISPLAYER ^ P_ISFRIEND, x) && markOrb(itOrbEmpathy))
81       return true;
82   return false;
83   }
84 
strictlyAgainstGravity(cell * w,cell * from,bool revdir,flagtype flags)85 EX bool strictlyAgainstGravity(cell *w, cell *from, bool revdir, flagtype flags) {
86   return
87     cellEdgeUnstable(w, flags) && cellEdgeUnstable(from, flags) &&
88     !(shmup::on && from == w) && gravityLevelDiff(from, w) != (revdir?-1:1) * gravity_zone_diff(from);
89   }
90 
anti_alchemy(cell * w,cell * from)91 EX bool anti_alchemy(cell *w, cell *from) {
92   bool alch1 = w->wall == waFloorA && from && from->wall == waFloorB && !w->item && !from->item;
93   alch1 |= w->wall == waFloorB && from && from->wall == waFloorA && !w->item && !from->item;
94   return alch1;
95   }
96 
97 #if HDR
98 #define P_MONSTER    Flag(0)  // can move through monsters
99 #define P_MIRROR     Flag(1)  // can move through mirrors
100 #define P_REVDIR     Flag(2)  // reverse direction movement
101 #define P_WIND       Flag(3)  // can move against the wind
102 #define P_GRAVITY    Flag(4)  // can move against the gravity
103 #define P_ISPLAYER   Flag(5)  // player-only moves (like the Round Table jump)
104 #define P_ONPLAYER   Flag(6)  // always can step on the player
105 #define P_FLYING     Flag(7)  // is flying
106 #define P_BULLET     Flag(8)  // bullet can fly through more things
107 #define P_MIRRORWALL Flag(9)  // mirror images go through mirror walls
108 #define P_JUMP1      Flag(10) // first part of a jump
109 #define P_JUMP2      Flag(11) // second part of a jump
110 #define P_TELE       Flag(12) // teleport onto
111 #define P_BLOW       Flag(13) // Orb of Air -- blow, or push
112 #define P_AETHER     Flag(14) // aethereal
113 #define P_FISH       Flag(15) // swimming
114 #define P_WINTER     Flag(16) // fire resistant
115 #define P_USEBOAT    Flag(17) // can use boat
116 #define P_NOAETHER   Flag(18) // disable AETHER
117 #define P_FRIENDSWAP Flag(19) // can move on friends (to swap with tem)
118 #define P_ISFRIEND   Flag(20) // is a friend (can use Empathy + Winter/Aether/Fish combo)
119 #define P_LEADER     Flag(21) // can push statues and use boats
120 #define P_MARKWATER  Flag(22) // mark Orb of Water as used
121 #define P_EARTHELEM  Flag(23) // Earth Elemental
122 #define P_WATERELEM  Flag(24) // Water Elemental
123 #define P_IGNORE37   Flag(25) // ignore the triheptagonal board
124 #define P_CHAIN      Flag(26) // for chaining moves with boats
125 #define P_DEADLY     Flag(27) // suicide moves allowed
126 #define P_ROSE       Flag(28) // rose smell
127 #define P_CLIMBUP    Flag(29) // allow climbing up
128 #define P_CLIMBDOWN  Flag(30) // allow climbing down
129 #define P_REPTILE    Flag(31) // is reptile
130 #define P_VOID       Flag(32) // void beast
131 #define P_PHASE      Flag(33) // phasing movement
132 #define P_PULLMAGNET Flag(34) // pull the other part of the magnet
133 #define P_WATERCURSE Flag(35) // Curse of Water
134 #endif
135 
passable(cell * w,cell * from,flagtype flags)136 EX bool passable(cell *w, cell *from, flagtype flags) {
137   bool revdir = (flags&P_REVDIR);
138   bool vrevdir = revdir ^ bool(flags&P_VOID);
139 
140   if(from && from != w && nonAdjacent(from, w) && !F(P_IGNORE37 | P_BULLET)) return false;
141 
142   if((isWateryOrBoat(w) || w->wall == waShallow) && F(P_WATERCURSE))
143     return false;
144 
145   for(cell *pp: player_positions()) {
146     if(w == pp && F(P_ONPLAYER)) return true;
147     if(from == pp && F(P_ONPLAYER) && F(P_REVDIR)) return true;
148 
149     if(from && !((flags & P_ISPLAYER) && pp->monst)) {
150       int i = vrevdir ? incline(w, from) : incline(from, w);
151       if(in_gravity_zone(w)) {
152         if(gravity_state == gsLevitation) i = 0;
153         if(gravity_state == gsAnti && i > 1) i = 1;
154         }
155       if(i < -1 && F(P_ROSE)) return false;
156       if((i > 1) && !F(P_JUMP1 | P_JUMP2 | P_BULLET | P_FLYING | P_BLOW | P_CLIMBUP | P_AETHER | P_REPTILE))
157         return false;
158       if((i < -2) && !F(P_DEADLY | P_JUMP1 | P_JUMP2 | P_BULLET | P_FLYING | P_BLOW | P_CLIMBDOWN | P_AETHER | P_REPTILE))
159         return false;
160       }
161     }
162 
163   if(F(P_ROSE)) {
164     if(airdist(w) < 3) return false;
165     if(againstWind(w,from)) return false;
166     if(isGravityLand(w)) return false;
167     }
168 
169   if(from && strictlyAgainstGravity(w, from, vrevdir, flags)
170     && !((flags & P_ISPLAYER) && shmup::on)
171     && !F(P_GRAVITY | P_BLOW | P_JUMP1 | P_JUMP2 | P_FLYING | P_BULLET | P_AETHER)
172     ) return false;
173 
174   if(from && (vrevdir ? againstWind(from,w) : againstWind(w, from)) && !F(P_WIND | P_BLOW | P_JUMP1 | P_JUMP2 | P_BULLET | P_AETHER)) return false;
175 
176   if(revdir && from && w->monst && passable(from, w, flags &~ (P_REVDIR|P_MONSTER)))
177     return true;
178 
179   if(!shmup::on && sword::at(w, flags & P_ISPLAYER) && !F(P_DEADLY | P_BULLET | P_ROSE))
180     return false;
181 
182   bool alch1 = anti_alchemy(w, from);
183 
184   if(alch1) {
185     bool alchok = (in_gravity_zone(w) || in_gravity_zone(from));
186     alchok = alchok || (F(P_JUMP1 | P_JUMP2 | P_FLYING | P_TELE | P_BLOW | P_AETHER | P_BULLET)
187       && !F(P_ROSE));
188     if(!alchok) return false;
189     }
190 
191   if(from && thruVine(from, w) && !F(P_AETHER)) return false;
192 
193   if(w->monst == moMouse && F(P_JUMP1)) ;
194   else if(w->monst && isFriendly(w) && F(P_FRIENDSWAP)) ;
195   else if(w->monst && !F(P_MONSTER)) return false;
196 
197   if(w->wall == waMirror || w->wall == waCloud)
198     return F(P_MIRROR | P_AETHER);
199 
200   if(w->wall == waMirrorWall)
201     return F(P_MIRRORWALL);
202 
203   if(F(P_BULLET)) {
204     if(isFire(w) || w->wall == waBonfireOff || cellHalfvine(w) ||
205       w->wall == waMagma ||
206       w->wall == waAncientGrave || w->wall == waFreshGrave || w->wall == waRoundTable)
207       return true;
208     }
209 
210   if(F(P_LEADER)) {
211     if(from && from->wall == waBoat && isWatery(w) && from->item == itOrbYendor)
212       return false;
213 
214     if(from && from->wall == waBoat && isWateryOrBoat(w) && !againstCurrent(w, from))
215       return true;
216 
217     if(from && isWatery(from) && w->wall == waBoat && F(P_CHAIN))
218       return true;
219 
220     if(from && isWatery(from) && isWatery(w) && F(P_CHAIN) && !againstCurrent(w, from))
221       return true;
222 
223     if(w->wall == waBigStatue && from && canPushStatueOn(from, flags)) return true;
224     }
225 
226   if(F(P_EARTHELEM)) {
227     // cannot go through Living Caves...
228     if(w->wall == waCavefloor) return false;
229     // but can dig through...
230     if(w->wall == waDeadwall || w->wall == waDune || w->wall == waStone)
231       return true;
232     // and can swim through...
233     if(w->wall == waSea && w->land == laLivefjord)
234       return true;
235     }
236 
237   if(F(P_WATERELEM)) {
238     if(isWatery(w) || boatGoesThrough(w) ||
239       w->wall == waBoat ||
240       w->wall == waDeadTroll || w->wall == waDeadTroll2) return true;
241     return false;
242     }
243 
244   if(isThorny(w->wall) && F(P_BLOW | P_DEADLY)) return true;
245 
246   if(isFire(w) || w->wall == waMagma) {
247     if(w->wall == waMagma && in_gravity_zone(w)) ;
248     else if(!F(P_AETHER | P_WINTER | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY)) return false;
249     }
250 
251   if(in_gravity_zone(w) && gravity_state == gsAnti && !isGravityLand(w->land) && (!from || !isGravityLand(from->land)))
252   if(!F(P_AETHER | P_BLOW | P_JUMP1 | P_BULLET | P_FLYING)) {
253     bool next_to_wall = false;
254     forCellEx(c2, w) if(isJWall(c2)) next_to_wall = true;
255     if(from) forCellEx(c2, from) if(isJWall(c2)) next_to_wall = true;
256     if(!next_to_wall && (!from || incline(from, w) * (vrevdir?-1:1) <= 0)) return false;
257     }
258 
259   if(isWatery(w)) {
260     if(in_gravity_zone(w)) ;
261     else if(from && from->wall == waBoat && F(P_USEBOAT) &&
262       (!againstCurrent(w, from) || F(P_MARKWATER)) && !(from->item == itOrbYendor)) ;
263     else if(!F(P_AETHER | P_FISH | P_FLYING | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY | P_REPTILE)) return false;
264     }
265   if(isChasmy(w)) {
266     if(in_gravity_zone(w)) ;
267     else if(!F(P_AETHER | P_FLYING | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY | P_REPTILE)) return false;
268     }
269 
270   if(w->wall == waRoundTable && from && from->wall != waRoundTable && (flags & P_ISPLAYER)) return true;
271   if(isNoFlight(w) && F(P_FLYING | P_BLOW | P_JUMP1)) return false;
272 
273   if(isWall(w)) {
274     // a special case: empathic aethereal beings cannot go through Round Table
275     // (but truly aetheral beings can)
276     if(w->wall == waRoundTable) {
277       if(!(flags & P_AETHER)) return false;
278       }
279     else if(!F(P_AETHER)) return false;
280     }
281   return true;
282   }
283 
284 EX vector<pair<cell*, int> > airmap;
285 
airdist(cell * c)286 EX int airdist(cell *c) {
287   if(!(havewhat & HF_AIR)) return 3;
288   vector<pair<cell*, int> >::iterator it =
289     lower_bound(airmap.begin(), airmap.end(), make_pair(c,0));
290   if(it != airmap.end() && it->first == c) return it->second;
291   return 3;
292   }
293 
calcAirdir(cell * c)294 EX ld calcAirdir(cell *c) {
295   if(!c || c->monst == moAirElemental || !passable(c, NULL, P_BLOW))
296     return 0;
297   for(int i=0; i<c->type; i++) {
298     cell *c2 = c->move(i);
299     if(c2 && c2->monst == moAirElemental) {
300       return c->c.spin(i) * 2 * M_PI / c2->type;
301       }
302     }
303   for(int i=0; i<c->type; i++) {
304     cell *c2 = c->move(i);
305     if(!c2) continue;
306     if(!passable(c2, c, P_BLOW | P_MONSTER)) continue;
307     if(!passable(c, c2, P_BLOW | P_MONSTER)) continue;
308     for(int i=0; i<c2->type; i++) {
309       cell *c3 = c2->move(i);
310       if(c3 && c3->monst == moAirElemental) {
311         return c2->c.spin(i) * 2 * M_PI / c3->type;
312         }
313       }
314     }
315   return 0;
316   }
317 
againstWind(cell * cto,cell * cfrom)318 EX bool againstWind(cell *cto, cell *cfrom) {
319   if(!cfrom || !cto) return false;
320   int dcto = airdist(cto), dcfrom = airdist(cfrom);
321   if(dcto < dcfrom) return true;
322   #if CAP_FIELD
323   if(cfrom->land == laBlizzard && !shmup::on && cto->land == laBlizzard && dcto == 3 && dcfrom == 3) {
324     char vfrom = windmap::at(cfrom);
325     char vto = windmap::at(cto);
326     int z = (vfrom-vto) & 255;
327     if(z >= windmap::NOWINDBELOW && z < windmap::NOWINDFROM)
328       return true;
329     }
330   #endif
331   whirlwind::calcdirs(cfrom);
332   int d = neighborId(cfrom, cto);
333   if(whirlwind::winddir(d) == -1) return true;
334   return false;
335   }
336 
ghostmove(eMonster m,cell * to,cell * from,flagtype extra)337 EX bool ghostmove(eMonster m, cell* to, cell* from, flagtype extra) {
338   if(!isGhost(m) && nonAdjacent(to, from)) return false;
339   if(sword::at(to, 0)) return false;
340   if(!shmup::on && isPlayerOn(to)) return false;
341   if(to->monst && !(to->monst == moTentacletail && isGhost(m) && m != moFriendlyGhost)
342     && !(to->monst == moTortoise && isGhost(m) && m != moFriendlyGhost) && !(extra & P_MONSTER))
343     return false;
344   if((m == moWitchGhost || m == moWitchWinter) && to->land != laPower)
345     return false;
346   if(isGhost(m))
347     for(int i=0; i<to->type; i++) if(to->move(i)) {
348       if(inmirror(to->move(i))) return false;
349       if(to->move(i) && to->move(i) != from && isGhost(to->move(i)->monst) &&
350         (to->move(i)->monst == moFriendlyGhost) == (m== moFriendlyGhost))
351         return false;
352       }
353   if(isGhost(m) || m == moWitchGhost) return true;
354   if(m == moGreaterShark) return isWatery(to);
355   if(m == moWitchWinter)
356     return passable(to, from, P_WINTER | P_ONPLAYER);
357   return false;
358   }
359 
slimepassable(cell * w,cell * c)360 bool slimepassable(cell *w, cell *c) {
361   if(w == c || !c) return true;
362   int u = neighborId(c, w);
363   if(nonAdjacent(w,c)) return false;
364   if(isPlayerOn(w)) return true;
365   int group = slimegroup(c);
366   if(!group) return false;
367   int ogroup = slimegroup(w);
368   if(!ogroup) return false;
369   bool hv = (group == ogroup);
370 
371   if(sword::at(w, 0)) return false;
372 
373   if(w->item) return false;
374 
375   // only travel to halfvines correctly
376   if(cellHalfvine(c)) {
377     int i=0;
378     for(int t=0; t<c->type; t++) if(c->move(t) && c->move(t)->wall == c->wall) i=t;
379     int z = i-u; if(z<0) z=-z; z%=6;
380     if(z>1) return false;
381     hv=(group == ogroup);
382     }
383   // only travel from halfvines correctly
384   if(cellHalfvine(w)) {
385     int i=0;
386     for(int t=0; t<w->type; t++) if(w->move(t) && w->move(t)->wall == w->wall) i=t;
387     int z = i-c->c.spin(u); if(z<0) z=-z; z%=6;
388     if(z>1) return false;
389     hv=(group == ogroup);
390     }
391   if(!hv) return false;
392   return true;
393   }
394 
sharkpassable(cell * w,cell * c)395 bool sharkpassable(cell *w, cell *c) {
396   if(w == c || !c) return true;
397   if(nonAdjacent(w,c)) return false;
398   if(isPlayerOn(w)) return true;
399   if(!isWatery(w) && w->wall != waShallow) return false;
400   if(sword::at(w, 0)) return false;
401 
402   // don't go against the current
403   if(isWateryOrBoat(w) && isWateryOrBoat(c))
404     return !againstCurrent(w, c);
405 
406   return true;
407   }
408 
canPushStatueOn(cell * c,flagtype flags)409 EX bool canPushStatueOn(cell *c, flagtype flags) {
410   return passable(c, NULL, P_MONSTER | flags) && !snakelevel(c) &&
411     !isWorm(c->monst) && !isReptile(c->wall) && !peace::on &&
412     !cellHalfvine(c) && !isDie(c->wall) &&
413     !among(c->wall, waBoat, waFireTrap, waArrowTrap);
414   }
415 
moveBoat(const movei & mi)416 EX void moveBoat(const movei& mi) {
417   eWall x = mi.t->wall; mi.t->wall = mi.s->wall; mi.s->wall = x;
418   mi.t->mondir = mi.rev_dir_or(NODIR);
419   moveItem(mi.s, mi.t, false);
420   animateMovement(mi, LAYER_BOAT);
421   }
422 
moveBoatIfUsingOne(const movei & mi)423 EX void moveBoatIfUsingOne(const movei& mi) {
424   if(mi.s->wall == waBoat && isWatery(mi.t)) moveBoat(mi);
425   else if(mi.s->wall == waBoat && boatGoesThrough(mi.t) && isFriendly(mi.t) && markEmpathy(itOrbWater)) {
426     placeWater(mi.t, mi.s);
427     moveBoat(mi);
428     }
429   }
430 
againstMagnet(cell * c1,cell * c2,eMonster m)431 bool againstMagnet(cell *c1, cell *c2, eMonster m) { // (from, to)
432   if(false) forCellEx(c3, c2) {
433     if(c3 == c1) continue;
434     if(c3->monst == m)
435       return true;
436     /* if(c3->monst == otherpole(m) && c3->move(c3->mondir) != c1) {
437       int i = 0;
438       forCellEx(c4, c3) if(c4->monst == m) i++;
439       if(i == 2) return true;
440       } */
441     }
442   if(c1->monst == m && !isNeighbor(c2, c1->move(c1->mondir)))
443     return true;
444   forCellEx(c3, c1)
445     if(c3->monst != m && isMagneticPole(c3->monst))
446       if(!isNeighbor(c3, c2))
447         return true;
448   return false;
449   }
450 
againstPair(cell * c1,cell * c2,eMonster m)451 EX bool againstPair(cell *c1, cell *c2, eMonster m) { // (from, to)
452   if(c1->monst == m && !isNeighbor(c2, c1->move(c1->mondir)))
453     return true;
454   return false;
455   }
456 
notNearItem(cell * c)457 EX bool notNearItem(cell *c) {
458   forCellCM(c2, c) if(c2->item) return false;
459   return true;
460   }
461 
isNeighbor1(cell * f,cell * w)462 EX bool isNeighbor1(cell *f, cell *w) {
463   return !f || f == w || isNeighbor(f, w);
464   }
465 
passable_for(eMonster m,cell * w,cell * from,flagtype extra)466 EX bool passable_for(eMonster m, cell *w, cell *from, flagtype extra) {
467   cell *dummy;
468   if(w->monst && !(extra & P_MONSTER) && !isPlayerOn(w))
469     return false;
470   if(m == moWolf) {
471     return (isIcyLand(w) || w->land == laVolcano) && (isPlayerOn(w) || passable(w, from, extra));
472     }
473   if(isMagneticPole(m))
474     return !(w && from && againstMagnet(from, w, m)) && passable(w, from, extra);
475   if(m == moPair)
476     return !(w && from && againstPair(from, w, m)) && passable(w, from, extra);
477   if(m == passive_switch) return false;
478   if(minf[m].mgroup == moYeti || isBug(m) || isDemon(m) || m == moHerdBull || m == moMimic || m == moAsteroid) {
479     if((isWitch(m) || m == moEvilGolem) && w->land != laPower && w->land != laHalloween)
480       return false;
481     return passable(w, from, extra);
482     }
483   if(m == moDragonHead && prairie::isriver(w))
484     return false;
485   if(isShark(m))
486     return sharkpassable(w, from);
487   if(isSlimeMover(m))
488     return slimepassable(w, from);
489   if(m == moKrakenH) {
490     if(extra & P_ONPLAYER) {
491       if(isPlayerOn(w)) return true;
492       }
493     if((extra & P_ONPLAYER) && isPlayerOn(w))
494       return true;
495     if(kraken_pseudohept(w) || kraken_pseudohept(from)) return false;
496     if(w->wall != waBoat && !slimepassable(w, from)) return false;
497     forCellEx(w2, w) if(w2->wall != waBoat && !passable(w2, w, P_FISH | P_MONSTER)) return false;
498     return true;
499     }
500   if(m == moEarthElemental)
501     return passable(w, from, extra | P_EARTHELEM);
502   if(m == moWaterElemental)
503     return passable(w, from, extra | P_WATERELEM);
504   if(m == moGreaterShark)
505     return isWatery(w) || w->wall == waBoat || w->wall == waFrozenLake;
506   if(isGhostMover(m) || m == moFriendlyGhost)
507     return ghostmove(m, w, from, extra);
508     // for the purpose of Shmup this is correct
509   if(m == moTameBomberbird)
510     return passable(w, from, extra | P_FLYING | P_ISFRIEND);
511   if(m == moHexSnake)
512     return !pseudohept(w) && passable(w, from, extra|P_WIND|P_FISH);
513   if(isBird(m)) {
514     if(bird_disruption(w) && (!from || bird_disruption(from)) && markOrb(itOrbGravity))
515       return passable(w, from, extra);
516     else
517       return passable(w, from, extra | P_FLYING);
518     }
519   if(m == moReptile)
520     return passable(w, from, extra | P_REPTILE);
521   if(isDragon(m))
522     return passable(w, from, extra | P_FLYING | P_WINTER);
523   if(m == moAirElemental)
524     return passable(w, from, extra | P_FLYING | P_WIND);
525   if(isLeader(m)) {
526     if(from && from->wall == waBoat && from->item == itCoral && !from->monst) return false; // don't move Corals!
527     return passable(w, from, extra | P_LEADER);
528     }
529   if(isPrincess(m))
530     return passable(w, from, extra | P_ISFRIEND | P_USEBOAT);
531   if(isGolemOrKnight(m))
532     return passable(w, from, extra | P_ISFRIEND);
533   if(isWorm(m))
534     return passable(w, from, extra) && !cellUnstable(w) && ((m != moWorm && m != moTentacle) || !cellEdgeUnstable(w));
535   if(m == moVoidBeast)
536     return passable(w, from, extra | P_VOID);
537   if(m == moHexDemon) {
538     if(extra & P_ONPLAYER) {
539       if(isPlayerOn(w)) return true;
540       }
541     return !pseudohept(w) && passable(w, from, extra);
542     }
543   #if CAP_COMPLEX2
544   if(m == moAnimatedDie) {
545     if(extra & P_ONPLAYER) {
546       if(isPlayerOn(w)) return true;
547       }
548     if(from && isDie(from->monst)) {
549       bool ok = false;
550       for(int i=0; i<from->type; i++) {
551         if(from->move(i) != w) continue;
552         if(dice::can_roll(movei(from, i))) ok = true;
553         }
554       if(!ok) return false;
555       }
556     if(from && !dice::die_possible(from))
557       return false;
558     else if(!dice::die_possible(w))
559       return false;
560     else
561       return passable(w, from, extra);
562     }
563   #endif
564   if(m == moFrog) {
565     return isNeighbor1(from, w) ? passable(w, from, extra) : check_jump(from, w, extra, dummy) == 3;
566     }
567   if(m == moPhaser)
568     return isNeighbor1(from, w) ? passable(w, from, extra) : check_phase(from, w, extra, dummy) == 3;
569   if(m == moVaulter)
570     return isNeighbor1(from, w) ? passable(w, from, extra) : check_vault(from, w, extra, dummy) == 6;
571   if(m == moAltDemon) {
572     if(extra & P_ONPLAYER) {
573       if(isPlayerOn(w)) return true;
574       }
575     return (!w || !from || w==from || pseudohept(w) || pseudohept(from)) && passable(w, from, extra);
576     }
577   if(m == moMonk) {
578     if(extra & P_ONPLAYER) {
579       if(isPlayerOn(w)) return true;
580       }
581     return notNearItem(w) && passable(w, from, extra);
582     }
583   return false;
584   }
585 
movegroup(eMonster m)586 EX eMonster movegroup(eMonster m) { return minf[m].mgroup; }
587 
logical_adjacent(cell * c1,eMonster m1,cell * c2)588 EX bool logical_adjacent(cell *c1, eMonster m1, cell *c2) {
589   if(!c1 || !c2) return true; // cannot really check
590   eMonster m2 = c2->monst;
591   if(!isNeighbor(c1, c2))
592     return false;
593   if(thruVine(c1, c2) && !attackThruVine(m1) && !attackThruVine(m2) &&
594     !checkOrb(m1, itOrbAether) && !checkOrb(m2, itOrbAether))
595     return false;
596   if(nonAdjacent(c1, c2) && !attackNonAdjacent(m1) && !attackNonAdjacent(m2) &&
597     !checkOrb(m1, itOrb37) && !checkOrb(m1, itOrbAether) && !checkOrb(m2, itOrbAether))
598     return false;
599   return true;
600   }
601 
buildAirmap()602 EX void buildAirmap() {
603   for(int k=0; k<isize(airmap); k++) {
604     int d = airmap[k].second;
605     if(d == 2) break;
606     cell *c = airmap[k].first;
607     for(int i=0; i<c->type; i++) {
608       cell *c2 = c->move(i);
609       if(!c2) continue;
610       if(!passable(c2, c, P_BLOW | P_MONSTER)) continue;
611       if(!passable(c, c2, P_BLOW | P_MONSTER)) continue;
612       airmap.push_back(make_pair(c2, d+1));
613       }
614     }
615   sort(airmap.begin(), airmap.end());
616   }
617 
618 EX int rosewave, rosephase;
619 
620 /** current state of the rose scent
621  *  rosemap[c] &3 can be:
622  *  0 - wave not reached
623  *  1 - wave expanding
624  *  2 - wave phase 1
625  *  3 - wave phase 2
626  */
627 EX map<cell*, int> rosemap;
628 
rosedist(cell * c)629 EX int rosedist(cell *c) {
630   if(!(havewhat&HF_ROSE)) return 0;
631   int&r (rosemap[c]);
632   if((r&7) == 7) return 0;
633   if(r&3) return (r&3)-1;
634   return 0;
635   }
636 
againstRose(cell * cfrom,cell * cto)637 EX bool againstRose(cell *cfrom, cell *cto) {
638   if(rosedist(cfrom) != 1) return false;
639   if(cto && rosedist(cto) == 2) return false;
640   return true;
641   }
642 
withRose(cell * cfrom,cell * cto)643 EX bool withRose(cell *cfrom, cell *cto) {
644   if(rosedist(cfrom) != 1) return false;
645   if(rosedist(cto) != 2) return false;
646   return true;
647   }
648 
buildRosemap()649 EX void buildRosemap() {
650 
651   rosephase++; rosephase &= 7;
652 
653   if((havewhat&HF_ROSE) && !rosephase) {
654     rosewave++;
655     for(int k=0; k<isize(dcal); k++) {
656       cell *c = dcal[k];
657       if(c->wall == waRose && c->cpdist <= gamerange() - 2)
658         rosemap[c] = rosewave * 8 + 2;
659       }
660     }
661 
662   for(map<cell*, int>::iterator it = rosemap.begin(); it != rosemap.end(); it++) {
663     cell *c = it->first;
664     int r = it->second;
665     if(r < (rosewave) * 8) continue;
666     if((r&7) == 2) if(c->wall == waRose || !isWall(c)) for(int i=0; i<c->type; i++) {
667       cell *c2 = c->move(i);
668       if(!c2) continue;
669       // if(snakelevel(c2) <= snakelevel(c) - 2) continue;
670       if(!passable(c2, c, P_BLOW | P_MONSTER | P_ROSE)) continue;
671       int& r2 = rosemap[c2];
672       if(r2 < r) r2 = r-1;
673       }
674     }
675 
676   for(map<cell*, int>::iterator it = rosemap.begin(); it != rosemap.end(); it++) {
677     int& r = it->second;
678     if((r&7) == 1 || (r&7) == 2 || (r&7) == 3) r++;
679     if(airdist(it->first) < 3 || whirlwind::cat(it->first)) r |= 7;
680     if(it->first->land == laBlizzard) r |= 7;
681     forCellEx(c2, it->first) if(airdist(c2) < 3) r |= 7;
682     }
683 
684   }
685 
scentResistant()686 EX bool scentResistant() {
687   return markOrb(itOrbBeauty) || markOrb(itOrbAether) || markOrb(itOrbShield);
688   }
689 
690 
691 }
692