mirror of
https://github.com/zenorogue/hyperrogue.git
synced 2025-12-18 20:08:06 +00:00
They're now treated specially in passable, so even flying/aethereal/etc. beings can't occupy them, since they're not supposed to exist at all. This also fixes a bug where trying to move onto chasms that used to be trapdoors in Crystal World would give the generic "You cannot move there!" message instead of the correct "You cannot move through the chasm!" message, and a bug where things like Slime Beasts could make them walkable.
704 lines
24 KiB
C++
704 lines
24 KiB
C++
// Hyperbolic Rogue - passability
|
|
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
|
|
|
|
/** \file checkmove.cpp
|
|
* \brief check whether monster/PC can move in the given direction
|
|
*/
|
|
|
|
#include "hyper.h"
|
|
|
|
namespace hr {
|
|
|
|
// === MOVEMENT FUNCTIONS ===
|
|
|
|
// w = from->move(d)
|
|
EX bool againstCurrent(cell *w, cell *from) {
|
|
if(from->land != laWhirlpool) return false;
|
|
if(againstWind(from, w)) return false; // wind is stronger than current
|
|
if(!eubinary && (!from->master->alt || !w->master->alt)) return false;
|
|
int dfrom = celldistAlt(from);
|
|
int dw = celldistAlt(w);
|
|
if(dw < dfrom) return false;
|
|
if(dfrom < dw) return true;
|
|
for(int d=0; d<from->type; d++)
|
|
if(from->move(d) == w) {
|
|
cell *c3 = from->modmove(d-1);
|
|
if(!c3) return false;
|
|
return celldistAlt(c3) < dfrom;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
EX bool boatGoesThrough(cell *c) {
|
|
if(isGravityLand(c->land)) return false;
|
|
return
|
|
(c->wall == waNone && c->land != laMotion && c->land != laZebra && c->land != laReptile) ||
|
|
isAlchAny(c) ||
|
|
c->wall == waCavefloor || c->wall == waFrozenLake || isReptile(c->wall) ||
|
|
c->wall == waDeadfloor || c->wall == waCIsland || c->wall == waCIsland2 ||
|
|
c->wall == waMineUnknown || c->wall == waMineMine || c->wall == waMineOpen ||
|
|
c->wall == waBonfireOff || c->wall == waFire || c->wall == waPartialFire ||
|
|
c->wall == waArrowTrap || c->wall == waShallow;
|
|
}
|
|
|
|
EX void become_water(cell *c) {
|
|
if(isIcyLand(c))
|
|
c->wall = waLake;
|
|
else if(isCoastal(c) || isSealand(c))
|
|
c->wall = waSea;
|
|
else
|
|
c->wall = waDeepWater;
|
|
}
|
|
|
|
EX void placeWater(cell *c, cell *c2) {
|
|
destroyTrapsOn(c);
|
|
if(isWatery(c)) ;
|
|
else if(c2 && isAlchAny(c2))
|
|
c->wall = c2->wall;
|
|
else become_water(c);
|
|
// destroy the ancient treasure!
|
|
if(c->item == itBarrow) c->item = itNone;
|
|
}
|
|
|
|
EX int incline(cell *cfrom, cell *cto) {
|
|
return snakelevel(cto) - snakelevel(cfrom);
|
|
}
|
|
|
|
#define F(x) checkflags(flags,x)
|
|
|
|
EX bool checkflags(flagtype flags, flagtype x) {
|
|
if(flags & x) return true;
|
|
if(flags & P_ISPLAYER) {
|
|
if((x & P_WINTER) && (markOrb(itOrbWinter) || markOrb(itCurseWater))) return true;
|
|
if((x & P_IGNORE37) && markOrb(itOrb37)) return true;
|
|
if((x & P_FISH) && markOrb(itOrbFish)) return true;
|
|
if((x & P_MARKWATER) && markOrb(itOrbWater)) return true;
|
|
if((x & P_AETHER) && markOrb2(itOrbAether) && !(flags&P_NOAETHER)) return true;
|
|
if((x & P_WATERCURSE)&& markOrb2(itCurseWater)) return true;
|
|
}
|
|
if(flags & P_ISFRIEND) if(items[itOrbEmpathy])
|
|
if(checkflags(flags ^ P_ISPLAYER ^ P_ISFRIEND, x) && markOrb(itOrbEmpathy))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
EX bool strictlyAgainstGravity(cell *w, cell *from, bool revdir, flagtype flags) {
|
|
return
|
|
cellEdgeUnstable(w, flags) && cellEdgeUnstable(from, flags) &&
|
|
!(shmup::on && from == w) && gravityLevelDiff(from, w) != (revdir?-1:1) * gravity_zone_diff(from);
|
|
}
|
|
|
|
EX bool anti_alchemy(cell *w, cell *from) {
|
|
if(!from) return false;
|
|
if(!isAlchAny(w)) return false;
|
|
if(!isAlchAny(from)) return false;
|
|
if(w->item) return false;
|
|
if(from->item) return false;
|
|
if(!nonorientable) return w->wall != from->wall;
|
|
forCellIdEx(c1, i, w)
|
|
if(c1 == from && (w->c.mirror(i) ? w->wall != from->wall : w->wall == from->wall))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
#if HDR
|
|
#define P_MONSTER Flag(0) // can move through monsters
|
|
#define P_MIRROR Flag(1) // can move through mirrors
|
|
// unused
|
|
#define P_WIND Flag(3) // can move against the wind
|
|
#define P_GRAVITY Flag(4) // can move against the gravity
|
|
#define P_ISPLAYER Flag(5) // player-only moves (like the Round Table jump)
|
|
#define P_ONPLAYER Flag(6) // always can step on the player
|
|
#define P_FLYING Flag(7) // is flying
|
|
#define P_BULLET Flag(8) // bullet can fly through more things
|
|
#define P_MIRRORWALL Flag(9) // mirror images go through mirror walls
|
|
#define P_JUMP1 Flag(10) // first part of a jump
|
|
#define P_JUMP2 Flag(11) // second part of a jump
|
|
#define P_TELE Flag(12) // teleport onto
|
|
#define P_BLOW Flag(13) // Orb of Air -- blow, or push
|
|
#define P_AETHER Flag(14) // aethereal
|
|
#define P_FISH Flag(15) // swimming
|
|
#define P_WINTER Flag(16) // fire resistant
|
|
#define P_USEBOAT Flag(17) // can use boat
|
|
#define P_NOAETHER Flag(18) // disable AETHER
|
|
#define P_FRIENDSWAP Flag(19) // can move on friends (to swap with tem)
|
|
#define P_ISFRIEND Flag(20) // is a friend (can use Empathy + Winter/Aether/Fish combo)
|
|
#define P_LEADER Flag(21) // can push statues and use boats
|
|
#define P_MARKWATER Flag(22) // mark Orb of Water as used
|
|
#define P_EARTHELEM Flag(23) // Earth Elemental
|
|
#define P_WATERELEM Flag(24) // Water Elemental
|
|
#define P_IGNORE37 Flag(25) // ignore the triheptagonal board
|
|
#define P_CHAIN Flag(26) // for chaining moves with boats
|
|
#define P_DEADLY Flag(27) // suicide moves allowed
|
|
#define P_ROSE Flag(28) // rose smell
|
|
#define P_CLIMBUP Flag(29) // allow climbing up
|
|
#define P_CLIMBDOWN Flag(30) // allow climbing down
|
|
#define P_REPTILE Flag(31) // is reptile
|
|
#define P_VOID Flag(32) // void beast
|
|
#define P_PHASE Flag(33) // phasing movement
|
|
#define P_PULLMAGNET Flag(34) // pull the other part of the magnet
|
|
#define P_WATERCURSE Flag(35) // Curse of Water
|
|
#endif
|
|
|
|
EX bool passable(cell *w, cell *from, flagtype flags) {
|
|
bool vrevdir = bool(flags&P_VOID);
|
|
|
|
if(w->land == laDual && pseudohept(w) && !F(P_BULLET)) return false;
|
|
|
|
if(from && from != w && nonAdjacent(from, w) && !F(P_IGNORE37 | P_BULLET)) return false;
|
|
|
|
if((isWateryOrBoat(w) || w->wall == waShallow) && F(P_WATERCURSE))
|
|
return false;
|
|
|
|
for(cell *pp: player_positions()) {
|
|
if(w == pp && F(P_ONPLAYER)) return true;
|
|
|
|
if(from && !((flags & P_ISPLAYER) && pp->monst)) {
|
|
int i = vrevdir ? incline(w, from) : incline(from, w);
|
|
if(in_gravity_zone(w)) {
|
|
if(gravity_state == gsLevitation) i = 0;
|
|
if(gravity_state == gsAnti && i > 1) i = 1;
|
|
}
|
|
if(i < -1 && F(P_ROSE)) return false;
|
|
if((i > 1) && !F(P_JUMP1 | P_JUMP2 | P_BULLET | P_FLYING | P_BLOW | P_CLIMBUP | P_AETHER | P_REPTILE))
|
|
return false;
|
|
if((i < -2) && !F(P_DEADLY | P_JUMP1 | P_JUMP2 | P_BULLET | P_FLYING | P_BLOW | P_CLIMBDOWN | P_AETHER | P_REPTILE))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if(F(P_ROSE)) {
|
|
if(airdist(w) < 3) return false;
|
|
if(againstWind(w,from)) return false;
|
|
if(isGravityLand(w)) return false;
|
|
}
|
|
|
|
if(from && strictlyAgainstGravity(w, from, vrevdir, flags)
|
|
&& !((flags & P_ISPLAYER) && shmup::on)
|
|
&& !F(P_GRAVITY | P_BLOW | P_JUMP1 | P_JUMP2 | P_FLYING | P_BULLET | P_AETHER)
|
|
) return false;
|
|
|
|
if(from && (vrevdir ? againstWind(from,w) : againstWind(w, from)) && !F(P_WIND | P_BLOW | P_JUMP1 | P_JUMP2 | P_BULLET | P_AETHER)) return false;
|
|
|
|
if(!shmup::on && sword::at(w, flags & P_ISPLAYER) && !F(P_DEADLY | P_BULLET | P_ROSE))
|
|
return false;
|
|
|
|
bool alch1 = anti_alchemy(w, from);
|
|
|
|
if(alch1) {
|
|
bool alchok = (in_gravity_zone(w) || in_gravity_zone(from));
|
|
alchok = alchok || (F(P_JUMP1 | P_JUMP2 | P_FLYING | P_TELE | P_BLOW | P_AETHER | P_BULLET)
|
|
&& !F(P_ROSE));
|
|
if(!alchok) return false;
|
|
}
|
|
|
|
if(from && thruVine(from, w) && !F(P_AETHER)) return false;
|
|
|
|
if(w->monst == moMouse && F(P_JUMP1)) ;
|
|
else if(w->monst && isFriendly(w) && F(P_FRIENDSWAP)) ;
|
|
else if(w->monst && !F(P_MONSTER)) return false;
|
|
|
|
if(w->wall == waMirror || w->wall == waCloud)
|
|
return F(P_MIRROR | P_AETHER);
|
|
|
|
if(w->wall == waMirrorWall)
|
|
return F(P_MIRRORWALL);
|
|
|
|
if(F(P_BULLET)) {
|
|
if(isFire(w) || w->wall == waBonfireOff || cellHalfvine(w) ||
|
|
w->wall == waMagma || isNonblock(w->wall))
|
|
return true;
|
|
}
|
|
|
|
if(F(P_LEADER)) {
|
|
if(from && from->wall == waBoat && isWatery(w) && from->item == itOrbYendor)
|
|
return false;
|
|
|
|
if(from && from->wall == waBoat && isWateryOrBoat(w) && !againstCurrent(w, from))
|
|
return true;
|
|
|
|
if(from && isWatery(from) && w->wall == waBoat && F(P_CHAIN))
|
|
return true;
|
|
|
|
if(from && isWatery(from) && isWatery(w) && F(P_CHAIN) && !againstCurrent(w, from))
|
|
return true;
|
|
|
|
if(w->wall == waBigStatue && from && canPushStatueOn(from, flags)) return true;
|
|
}
|
|
|
|
if(F(P_EARTHELEM)) {
|
|
// cannot go through Living Caves...
|
|
if(w->wall == waCavefloor) return false;
|
|
// but can dig through...
|
|
if(w->wall == waDeadwall || w->wall == waDune || w->wall == waStone)
|
|
return true;
|
|
// and can swim through...
|
|
if(w->wall == waSea && w->land == laLivefjord)
|
|
return true;
|
|
}
|
|
|
|
if(F(P_WATERELEM)) {
|
|
if(isWatery(w) || boatGoesThrough(w) ||
|
|
w->wall == waBoat ||
|
|
w->wall == waDeadTroll || w->wall == waDeadTroll2) return true;
|
|
return false;
|
|
}
|
|
|
|
if(isThorny(w->wall) && F(P_BLOW | P_DEADLY)) return true;
|
|
|
|
if(isFire(w) || w->wall == waMagma) {
|
|
if(w->wall == waMagma && in_gravity_zone(w)) ;
|
|
else if(!F(P_AETHER | P_WINTER | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY)) return false;
|
|
}
|
|
|
|
if(in_gravity_zone(w) && gravity_state == gsAnti && !isGravityLand(w->land) && (!from || !isGravityLand(from->land)))
|
|
if(!F(P_AETHER | P_BLOW | P_JUMP1 | P_BULLET | P_FLYING)) {
|
|
bool next_to_wall = false;
|
|
forCellEx(c2, w) if(isJWall(c2)) next_to_wall = true;
|
|
if(from) forCellEx(c2, from) if(isJWall(c2)) next_to_wall = true;
|
|
if(!next_to_wall && (!from || incline(from, w) * (vrevdir?-1:1) <= 0)) return false;
|
|
}
|
|
|
|
if(isWatery(w)) {
|
|
if((flags & P_ISPLAYER) && from && isWatery(from) && pickable_from_water(w->item)) ;
|
|
else if(in_gravity_zone(w)) ;
|
|
else if(from && from->wall == waBoat && F(P_USEBOAT) &&
|
|
(!againstCurrent(w, from) || F(P_MARKWATER)) && !(from->item == itOrbYendor)) ;
|
|
else if(from && isWatery(from) && F(P_CHAIN) && F(P_USEBOAT) && !againstCurrent(w, from)) ;
|
|
else if(!from && F(P_CHAIN) && F(P_USEBOAT)) ;
|
|
else if(!F(P_AETHER | P_FISH | P_FLYING | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY | P_REPTILE)) return false;
|
|
}
|
|
if(isChasmy(w)) {
|
|
if(in_gravity_zone(w)) ;
|
|
else if(!F(P_AETHER | P_FLYING | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY | P_REPTILE)) return false;
|
|
}
|
|
|
|
if(w->wall == waRoundTable && from && from->wall != waRoundTable && (flags & P_ISPLAYER)) return true;
|
|
if(isNoFlight(w) && F(P_FLYING | P_BLOW | P_JUMP1)) return false;
|
|
|
|
if(isWall(w)) {
|
|
// a special case: empathic aethereal beings cannot go through Round Table
|
|
// (but truly aetheral beings can)
|
|
if(w->wall == waRoundTable) {
|
|
if(!(flags & P_AETHER)) return false;
|
|
}
|
|
else if(!F(P_AETHER)) return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
EX vector<pair<cell*, int> > airmap;
|
|
|
|
EX int airdist(cell *c) {
|
|
if(!(havewhat & HF_AIR)) return 3;
|
|
vector<pair<cell*, int> >::iterator it =
|
|
lower_bound(airmap.begin(), airmap.end(), make_pair(c,0));
|
|
if(it != airmap.end() && it->first == c) return it->second;
|
|
return 3;
|
|
}
|
|
|
|
EX ld calcAirdir(cell *c) {
|
|
if(!c || c->monst == moAirElemental || !passable(c, NULL, P_BLOW))
|
|
return 0;
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = c->move(i);
|
|
if(c2 && c2->monst == moAirElemental) {
|
|
return c->c.spin(i) * TAU / c2->type;
|
|
}
|
|
}
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = c->move(i);
|
|
if(!c2) continue;
|
|
if(!passable(c2, c, P_BLOW | P_MONSTER)) continue;
|
|
if(!passable(c, c2, P_BLOW | P_MONSTER)) continue;
|
|
for(int i=0; i<c2->type; i++) {
|
|
cell *c3 = c2->move(i);
|
|
if(c3 && c3->monst == moAirElemental) {
|
|
return c2->c.spin(i) * TAU / c3->type;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
EX bool againstWind(cell *cto, cell *cfrom) {
|
|
if(!cfrom || !cto) return false;
|
|
int dcto = airdist(cto), dcfrom = airdist(cfrom);
|
|
if(dcto < dcfrom) return true;
|
|
#if CAP_FIELD
|
|
if(cfrom->land == laBlizzard && !shmup::on && cto->land == laBlizzard && dcto == 3 && dcfrom == 3) {
|
|
char vfrom = windmap::at(cfrom);
|
|
char vto = windmap::at(cto);
|
|
int z = (vfrom-vto) & 255;
|
|
if(z >= windmap::NOWINDBELOW && z < windmap::NOWINDFROM)
|
|
return true;
|
|
}
|
|
#endif
|
|
whirlwind::calcdirs(cfrom);
|
|
int d = neighborId(cfrom, cto);
|
|
if(whirlwind::winddir(d) == -1) return true;
|
|
return false;
|
|
}
|
|
|
|
EX bool ghostmove(eMonster m, cell* to, cell* from, flagtype extra) {
|
|
if(!isGhost(m) && nonAdjacent(to, from)) return false;
|
|
if(sword::at(to, 0)) return false;
|
|
if(!shmup::on && isPlayerOn(to)) return false;
|
|
if(to->monst && !(to->monst == moTentacletail && isGhost(m) && m != moFriendlyGhost)
|
|
&& !(to->monst == moTortoise && isGhost(m) && m != moFriendlyGhost) && !(extra & P_MONSTER))
|
|
return false;
|
|
if((m == moWitchGhost || m == moWitchWinter) && to->land != laPower)
|
|
return false;
|
|
if(isGhost(m))
|
|
for(int i=0; i<to->type; i++) if(to->move(i)) {
|
|
if(inmirror(to->move(i))) return false;
|
|
if(to->move(i) && to->move(i) != from && isGhost(to->move(i)->monst) &&
|
|
(to->move(i)->monst == moFriendlyGhost) == (m== moFriendlyGhost))
|
|
return false;
|
|
}
|
|
if(isGhost(m) || m == moWitchGhost) return true;
|
|
if(m == moGreaterShark) return isWatery(to);
|
|
if(m == moWitchWinter)
|
|
return passable(to, from, P_WINTER | P_ONPLAYER);
|
|
return false;
|
|
}
|
|
|
|
bool slimepassable(cell *w, cell *c) {
|
|
if(w == c || !c) return true;
|
|
int u = neighborId(c, w);
|
|
if(nonAdjacent(w,c)) return false;
|
|
if(isPlayerOn(w)) return true;
|
|
int group = slimegroup(c);
|
|
if(!group) return false;
|
|
int ogroup = slimegroup(w);
|
|
if(!ogroup) return false;
|
|
bool hv = (group == ogroup);
|
|
if(nonorientable && isAlchAny(c) && isAlchAny(w))
|
|
hv = !anti_alchemy(c, w);
|
|
|
|
if(sword::at(w, 0)) return false;
|
|
|
|
if(w->item) return false;
|
|
|
|
// only travel to halfvines correctly
|
|
if(cellHalfvine(c)) {
|
|
int i=0;
|
|
for(int t=0; t<c->type; t++) if(c->move(t) && c->move(t)->wall == c->wall) i=t;
|
|
int z = i-u; if(z<0) z=-z; z%=6;
|
|
if(z>1) return false;
|
|
hv=(group == ogroup);
|
|
}
|
|
// only travel from halfvines correctly
|
|
if(cellHalfvine(w)) {
|
|
int i=0;
|
|
for(int t=0; t<w->type; t++) if(w->move(t) && w->move(t)->wall == w->wall) i=t;
|
|
int z = i-c->c.spin(u); if(z<0) z=-z; z%=6;
|
|
if(z>1) return false;
|
|
hv=(group == ogroup);
|
|
}
|
|
if(!hv) return false;
|
|
return true;
|
|
}
|
|
|
|
bool sharkpassable(cell *w, cell *c) {
|
|
if(w == c || !c) return true;
|
|
if(nonAdjacent(w,c)) return false;
|
|
if(isPlayerOn(w)) return true;
|
|
if(!isWatery(w) && w->wall != waShallow) return false;
|
|
if(sword::at(w, 0)) return false;
|
|
|
|
// don't go against the current
|
|
if(isWateryOrBoat(w) && isWateryOrBoat(c))
|
|
return !againstCurrent(w, c);
|
|
|
|
return true;
|
|
}
|
|
|
|
EX bool canPushStatueOn(cell *c, flagtype flags) {
|
|
return passable(c, NULL, P_MONSTER | flags) && !snakelevel(c) &&
|
|
!isWorm(c->monst) && !isReptile(c->wall) && !peace::on &&
|
|
!cellHalfvine(c) && !isDie(c->wall) &&
|
|
!among(c->wall, waBoat, waFireTrap, waArrowTrap);
|
|
}
|
|
|
|
EX void moveBoat(const movei& mi) {
|
|
changes.ccell(mi.t);
|
|
changes.ccell(mi.s);
|
|
eWall x = mi.t->wall; mi.t->wall = mi.s->wall; mi.s->wall = x;
|
|
mi.t->mondir = mi.rev_dir_or(NODIR);
|
|
changes.map_value(rosemap, mi.t);
|
|
rosemap.erase(mi.t);
|
|
moveItem(mi.s, mi.t, false);
|
|
animateMovement(mi, LAYER_BOAT);
|
|
}
|
|
|
|
EX void moveBoatIfUsingOne(const movei& mi) {
|
|
if(mi.s->wall == waBoat && isWatery(mi.t)) moveBoat(mi);
|
|
else if(mi.s->wall == waBoat && boatGoesThrough(mi.t) && isFriendly(mi.t) && markEmpathy(itOrbWater)) {
|
|
placeWater(mi.t, mi.s);
|
|
moveBoat(mi);
|
|
}
|
|
}
|
|
|
|
bool againstMagnet(cell *c1, cell *c2, eMonster m) { // (from, to)
|
|
if(false) forCellEx(c3, c2) {
|
|
if(c3 == c1) continue;
|
|
if(c3->monst == m)
|
|
return true;
|
|
/* if(c3->monst == otherpole(m) && c3->move(c3->mondir) != c1) {
|
|
int i = 0;
|
|
forCellEx(c4, c3) if(c4->monst == m) i++;
|
|
if(i == 2) return true;
|
|
} */
|
|
}
|
|
if(c1->monst == m && !isNeighbor(c2, c1->move(c1->mondir)))
|
|
return true;
|
|
forCellEx(c3, c1)
|
|
if(c3->monst != m && isMagneticPole(c3->monst))
|
|
if(!isNeighbor(c3, c2))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
EX bool againstPair(cell *c1, cell *c2, eMonster m) { // (from, to)
|
|
if(c1->monst == m && !isNeighbor(c2, c1->move(c1->mondir)))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
EX bool notNearItem(cell *c) {
|
|
forCellCM(c2, c) if(c2->item) return false;
|
|
return true;
|
|
}
|
|
|
|
EX bool isNeighbor1(cell *f, cell *w) {
|
|
return !f || f == w || isNeighbor(f, w);
|
|
}
|
|
|
|
EX bool passable_for(eMonster m, cell *w, cell *from, flagtype extra) {
|
|
jumpdata jdummy;
|
|
if(w->monst && !(extra & P_MONSTER) && !isPlayerOn(w))
|
|
return false;
|
|
if(m == moWolf) {
|
|
return (isIcyLand(w) || w->land == laVolcano) && (isPlayerOn(w) || passable(w, from, extra));
|
|
}
|
|
if(isMagneticPole(m))
|
|
return !(w && from && againstMagnet(from, w, m)) && passable(w, from, extra);
|
|
if(m == moPair)
|
|
return !(w && from && againstPair(from, w, m)) && passable(w, from, extra);
|
|
if(m == passive_switch) return false;
|
|
if(minf[m].mgroup == moYeti || isBug(m) || isDemon(m) || m == moHerdBull || m == moMimic || m == moAsteroid) {
|
|
if((isWitch(m) || m == moEvilGolem) && w->land != laPower && w->land != laHalloween)
|
|
return false;
|
|
return passable(w, from, extra);
|
|
}
|
|
if(m == moDragonHead && prairie::isriver(w))
|
|
return false;
|
|
if(isShark(m))
|
|
return sharkpassable(w, from);
|
|
if(isSlimeMover(m))
|
|
return slimepassable(w, from);
|
|
if(m == moKrakenH) {
|
|
if(extra & P_ONPLAYER) {
|
|
if(isPlayerOn(w)) return true;
|
|
}
|
|
if((extra & P_ONPLAYER) && isPlayerOn(w))
|
|
return true;
|
|
if(kraken_pseudohept(w) || kraken_pseudohept(from)) return false;
|
|
if(w->wall != waBoat && !slimepassable(w, from)) return false;
|
|
forCellEx(w2, w) if(w2->wall != waBoat && !passable(w2, w, P_FISH | P_MONSTER)) return false;
|
|
return true;
|
|
}
|
|
if(m == moEarthElemental)
|
|
return passable(w, from, extra | P_EARTHELEM);
|
|
if(m == moWaterElemental)
|
|
return passable(w, from, extra | P_WATERELEM);
|
|
if(m == moGreaterShark)
|
|
return isWatery(w) || w->wall == waBoat || w->wall == waFrozenLake;
|
|
if(isGhostMover(m) || m == moFriendlyGhost)
|
|
return ghostmove(m, w, from, extra);
|
|
// for the purpose of Shmup this is correct
|
|
if(m == moTameBomberbird)
|
|
return passable(w, from, extra | P_FLYING | P_ISFRIEND);
|
|
if(m == moHexSnake)
|
|
return !pseudohept(w) && passable(w, from, extra|P_WIND|P_FISH);
|
|
if(isBird(m)) {
|
|
if(bird_disruption(w) && (!from || bird_disruption(from)) && markOrb(itOrbGravity))
|
|
return passable(w, from, extra);
|
|
else
|
|
return passable(w, from, extra | P_FLYING);
|
|
}
|
|
if(m == moReptile)
|
|
return passable(w, from, extra | P_REPTILE);
|
|
if(isDragon(m))
|
|
return passable(w, from, extra | P_FLYING | P_WINTER);
|
|
if(m == moAirElemental)
|
|
return passable(w, from, extra | P_FLYING | P_WIND);
|
|
if(isLeader(m)) {
|
|
if(from && from->wall == waBoat && from->item == itCoral && !from->monst) return false; // don't move Corals!
|
|
return passable(w, from, extra | P_LEADER);
|
|
}
|
|
if(isPrincess(m))
|
|
return passable(w, from, extra | P_ISFRIEND | P_USEBOAT);
|
|
if(isGolemOrKnight(m))
|
|
return passable(w, from, extra | P_ISFRIEND);
|
|
if(isWorm(m))
|
|
return passable(w, from, extra) && !cellUnstable(w) && ((m != moWorm && m != moTentacle) || !cellEdgeUnstable(w));
|
|
if(m == moVoidBeast)
|
|
return passable(w, from, extra | P_VOID);
|
|
if(m == moHexDemon) {
|
|
if(extra & P_ONPLAYER) {
|
|
if(isPlayerOn(w)) return true;
|
|
}
|
|
return !pseudohept(w) && passable(w, from, extra);
|
|
}
|
|
#if CAP_COMPLEX2
|
|
if(m == moAnimatedDie) {
|
|
if(extra & P_ONPLAYER) {
|
|
if(isPlayerOn(w)) return true;
|
|
}
|
|
if(from && isDie(from->monst)) {
|
|
bool ok = false;
|
|
for(int i=0; i<from->type; i++) {
|
|
if(from->move(i) != w) continue;
|
|
if(dice::can_roll(movei(from, i))) ok = true;
|
|
}
|
|
if(!ok) return false;
|
|
}
|
|
if(from && !dice::die_possible(from))
|
|
return false;
|
|
else if(!dice::die_possible(w))
|
|
return false;
|
|
else
|
|
return passable(w, from, extra);
|
|
}
|
|
#endif
|
|
if(m == moFrog) {
|
|
return isNeighbor1(from, w) ? passable(w, from, extra) : check_jump(from, w, extra, jdummy) == 3;
|
|
}
|
|
if(m == moPhaser)
|
|
return isNeighbor1(from, w) ? passable(w, from, extra) : check_phase(from, w, extra, jdummy) == 3;
|
|
if(m == moVaulter)
|
|
return isNeighbor1(from, w) ? passable(w, from, extra) : check_vault(from, w, extra, jdummy) == 6;
|
|
if(m == moAltDemon) {
|
|
if(extra & P_ONPLAYER) {
|
|
if(isPlayerOn(w)) return true;
|
|
}
|
|
return (!w || !from || w==from || pseudohept(w) || pseudohept(from)) && passable(w, from, extra);
|
|
}
|
|
if(m == moMonk) {
|
|
if(extra & P_ONPLAYER) {
|
|
if(isPlayerOn(w)) return true;
|
|
}
|
|
return notNearItem(w) && passable(w, from, extra);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
EX eMonster movegroup(eMonster m) { return minf[m].mgroup; }
|
|
|
|
EX bool logical_adjacent(cell *c1, eMonster m1, cell *c2) {
|
|
if(!c1 || !c2) return true; // cannot really check
|
|
eMonster m2 = c2->monst;
|
|
if(!isNeighbor(c1, c2))
|
|
return false;
|
|
if(thruVine(c1, c2) && !attackThruVine(m1) && !attackThruVine(m2) &&
|
|
!checkOrb(m1, itOrbAether) && !checkOrb(m2, itOrbAether))
|
|
return false;
|
|
if(nonAdjacent(c1, c2) && !attackNonAdjacent(m1) && !attackNonAdjacent(m2) &&
|
|
!checkOrb(m1, itOrb37) && !checkOrb(m1, itOrbAether) && !checkOrb(m2, itOrbAether))
|
|
return false;
|
|
return true;
|
|
}
|
|
|
|
EX void buildAirmap() {
|
|
for(int k=0; k<isize(airmap); k++) {
|
|
int d = airmap[k].second;
|
|
if(d == 2) break;
|
|
cell *c = airmap[k].first;
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = c->move(i);
|
|
if(!c2) continue;
|
|
if(!passable(c2, c, P_BLOW | P_MONSTER)) continue;
|
|
if(!passable(c, c2, P_BLOW | P_MONSTER)) continue;
|
|
airmap.push_back(make_pair(c2, d+1));
|
|
}
|
|
}
|
|
sort(airmap.begin(), airmap.end());
|
|
}
|
|
|
|
EX int rosewave, rosephase;
|
|
|
|
/** current state of the rose scent
|
|
* rosemap[c] &3 can be:
|
|
* 0 - wave not reached
|
|
* 1 - wave expanding
|
|
* 2 - wave phase 1
|
|
* 3 - wave phase 2
|
|
*/
|
|
EX map<cell*, int> rosemap;
|
|
|
|
EX int rosedist(cell *c) {
|
|
if(!(havewhat&HF_ROSE)) return 0;
|
|
int&r (rosemap[c]);
|
|
if((r&7) == 7) return 0;
|
|
if(r&3) return (r&3)-1;
|
|
return 0;
|
|
}
|
|
|
|
EX bool againstRose(cell *cfrom, cell *cto) {
|
|
if(rosedist(cfrom) != 1) return false;
|
|
if(cto && rosedist(cto) == 2) return false;
|
|
return true;
|
|
}
|
|
|
|
EX bool withRose(cell *cfrom, cell *cto) {
|
|
if(rosedist(cfrom) != 1) return false;
|
|
if(rosedist(cto) != 2) return false;
|
|
return true;
|
|
}
|
|
|
|
EX void buildRosemap() {
|
|
|
|
rosephase++; rosephase &= 7;
|
|
|
|
if((havewhat&HF_ROSE) && !rosephase) {
|
|
rosewave++;
|
|
for(int k=0; k<isize(dcal); k++) {
|
|
cell *c = dcal[k];
|
|
if(c->wall == waRose && c->cpdist <= gamerange() - 2)
|
|
rosemap[c] = rosewave * 8 + 2;
|
|
}
|
|
}
|
|
|
|
for(map<cell*, int>::iterator it = rosemap.begin(); it != rosemap.end(); it++) {
|
|
cell *c = it->first;
|
|
int r = it->second;
|
|
if(r < (rosewave) * 8) continue;
|
|
if((r&7) == 2) if(c->wall == waRose || !isWall(c)) for(int i=0; i<c->type; i++) {
|
|
cell *c2 = c->move(i);
|
|
if(!c2) continue;
|
|
// if(snakelevel(c2) <= snakelevel(c) - 2) continue;
|
|
if(!passable(c2, c, P_BLOW | P_MONSTER | P_ROSE)) continue;
|
|
int& r2 = rosemap[c2];
|
|
if(r2 < r) r2 = r-1;
|
|
}
|
|
}
|
|
|
|
for(map<cell*, int>::iterator it = rosemap.begin(); it != rosemap.end(); it++) {
|
|
int& r = it->second;
|
|
if((r&7) == 1 || (r&7) == 2 || (r&7) == 3) r++;
|
|
if(airdist(it->first) < 3 || whirlwind::cat(it->first)) r |= 7;
|
|
if(it->first->land == laBlizzard) r |= 7;
|
|
forCellEx(c2, it->first) if(airdist(c2) < 3) r |= 7;
|
|
}
|
|
|
|
}
|
|
|
|
EX bool scentResistant() {
|
|
return markOrb(itOrbBeauty) || markOrb(itOrbAether) || markOrb(itOrbShield);
|
|
}
|
|
|
|
|
|
}
|