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