1
0
mirror of https://github.com/zenorogue/hyperrogue.git synced 2025-12-18 20:08:06 +00:00
Files
hyperrogue/passable.cpp
Joseph C. Sible 3ac6126ece Rework removed tiles in Crystal World
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.
2025-11-12 02:35:33 -05:00

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);
}
}