hyperrogue/environment.cpp

935 lines
31 KiB
C++

// Hyperbolic Rogue - environment
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
/** \file environment.cpp
* \brief game environment: routines related to the game that affect all the map. Monsters to move are detected here, but their moves are implemented in monstermove.cpp
*/
#include "hyper.h"
namespace hr {
#if HDR
#define HF_BUG Flag(0)
#define HF_EARTH Flag(1)
#define HF_BIRD Flag(2)
#define HF_LEADER Flag(3)
#define HF_HEX Flag(4)
#define HF_WHIRLPOOL Flag(5)
#define HF_WATER Flag(6)
#define HF_AIR Flag(7)
#define HF_MUTANT Flag(8)
#define HF_OUTLAW Flag(9)
#define HF_WHIRLWIND Flag(10)
#define HF_ROSE Flag(11)
#define HF_DRAGON Flag(12)
#define HF_KRAKEN Flag(13)
#define HF_SHARK Flag(14)
#define HF_BATS Flag(15)
#define HF_REPTILE Flag(16)
#define HF_EAGLES Flag(17)
#define HF_SLOW Flag(18)
#define HF_FAST Flag(19)
#define HF_WARP Flag(20)
#define HF_MOUSE Flag(21)
#define HF_RIVER Flag(22)
#define HF_MIRROR Flag(23)
#define HF_VOID Flag(24)
#define HF_HUNTER Flag(25)
#define HF_FAILED_AMBUSH Flag(26)
#define HF_MAGNET Flag(27)
#define HF_HEXD Flag(28)
#define HF_ALT Flag(29)
#define HF_MONK Flag(30)
#define HF_WESTWALL Flag(31)
#define HF_JUMP Flag(32)
#define HF_DICE Flag(33)
#endif
EX flagtype havewhat, hadwhat;
/** monsters of specific types to move */
EX vector<cell*> worms, ivies, ghosts, golems, hexsnakes;
/** temporary changes during bfs */
vector<pair<cell*, eMonster>> tempmonsters;
/** The position of the first cell in dcal in distance 7. New wandering monsters can be generated in dcal[first7..]. */
EX int first7;
/** the list of all nearby cells, according to cpdist */
EX vector<cell*> dcal;
/** the list of all nearby cells, according to current pathdist */
EX vector<cellwalker> pathq;
/** the number of big statues -- they increase monster generation */
EX int statuecount;
/** the number of slimes in Wetland -- they create ghosts */
EX int wetslime;
/** list of monsters to move (pathq restriced to monsters) */
EX vector<cell*> pathqm;
/** which hex snakes are there */
EX set<int> snaketypes;
EX int gamerange_bonus = 0;
EX int gamerange() { return getDistLimit() + gamerange_bonus; }
// pathdist begin
EX cell *pd_from;
EX int pd_range;
#if HDR
/** The pathdata is used to keep a list of visited cells. It is used as follows:
* 1) create pathdata object: pathdata pd(identifier)
* 2) use one of the following methods to mark cells as visited:
* 2a) onpath_with_dir or onpath_random_dir, to mark a cell together with its distance and the direction we came from (used by computePathdist to make pathfinding not sensitive to direction indexing)
* 2b) onpath, to mark a cell at its distance (used when ordering is irrelevant: compute_graphical_distance and in shmup)
* 2c) onpatk_mark, to just mark a cell (used in groupmove2)
* 3) All the visited cells are listed in pathq, and they have 'pathdist' set to their recorded distance (0 in case of onpath_mark).
* 4) When the pathdata object is deleted, all the pathdist values are cleared back to PINFD.
* The variable 'pathlock' ensures that we do not use two pathdata objects at once.
**/
struct pathdata {
void checklock();
~pathdata();
pathdata(eMonster m, bool include_allies IS(true));
pathdata(int i);
};
#endif
/** using pathdata, record a cell (together with direction) as visited */
EX void onpath_with_dir(cellwalker cw, int d) {
if(!pathlock) {
println(hlog, "onpath(", cw, ", ", d, ") without pathlock");
}
cw.at->pathdist = d;
pathq.push_back(cw);
}
/** using pathdata, record a cell as visited, with random direction */
EX void onpath_random_dir(cell *c, int d) {
onpath_with_dir(cellwalker(c, hrand(c->type), hrand(2)), d);
}
EX void onpath(cell *c, int d) {
onpath_with_dir(cellwalker(c, 0, 0), d);
}
EX void onpath_mark(cell *c) {
onpath_with_dir(cellwalker(c, 0, 0), 0);
}
EX void clear_pathdata() {
for(auto c: pathq) c.at->pathdist = PINFD;
pathq.clear();
pathqm.clear();
}
/** This ensures that we do not use two pathdata objects at once */
EX int pathlock = 0;
/** compute_graphical_distance determines the distance of every cell
* from the current FOV center. It uses the pathq structures but
* does not lock them */
EX void compute_graphical_distance() {
if(pathlock) { printf("path error: compute_graphical_distance\n"); }
cell *c1 = centerover ? centerover : pd_from ? pd_from : cwt.at;
int sr = get_sightrange_ambush();
if(pd_from == c1 && pd_range == sr) return;
clear_pathdata();
pathlock++;
pd_from = c1;
pd_range = sr;
onpath(c1, 0);
for(int qb=0; qb<isize(pathq); qb++) {
cell *c = pathq[qb].at;
if(c->pathdist == pd_range) break;
if(qb == 0) forCellCM(c1, c) ;
forCellEx(c1, c)
if(c1->pathdist == PINFD)
onpath(c1, c->pathdist + 1);
}
pathlock--;
}
const int max_radius = 16;
struct visit_set {
set<cell*> visited;
queue<cell*> q;
void visit(cell *c) {
if(visited.count(c)) return;
visited.insert(c);
q.push(c);
}
};
struct princess_ai {
array<visit_set, max_radius+1> info;
void visit_gate(cell *g) { info[0].visit(g); }
void run();
};
void princess_ai::run() {
int radius = toggle_radius(waOpenPlate);
if(pathq.empty()) return;
int d = pathq.back().at->pathdist;
if(d == PINFD - 1) return;
d++;
if(d < 5) d = 5; /* the Princess AI avoids plates when too close to the player */
hassert(radius <= max_radius);
for(int k=0; k<=radius; k++) while(!info[k].q.empty()) {
cell *c = info[k].q.front();
info[k].q.pop();
if(k < radius) forCellEx(c1, c) {
info[k+1].visit(c1);
if(k == 0 && c1->wall == waClosedGate)
info[0].visit(c1);
}
if(k == radius && c->wall == waOpenPlate && c->pathdist == PINFD)
onpath_random_dir(c, d);
}
}
EX void computePathdist(eMonster param, bool include_allies IS(true)) {
for(cell *c: targets)
if(include_allies || isPlayerOn(c))
onpath_random_dir(c, isPlayerOn(c) ? 0 : 1);
int qtarg = isize(targets);
int limit = gamerange();
int qb = 0;
bool princess = isPrincess(param);
princess_ai gd;
princess_retry:
for(; qb < isize(pathq); qb++) {
cellwalker cw = pathq[qb];
/* The opposite cell will be added to the queue first, which helps the AI. */
cw += cw.at->type/2;
cell*& c = cw.at;
if(c->monst && !isBug(c) && !(isFriendly(c) && !c->stuntime)) {
pathqm.push_back(c);
continue; // no paths going through monsters
}
if(isMounted(c) && !isPlayerOn(c)) {
// don't treat the Worm you are riding as passable
pathqm.push_back(c);
continue;
}
if(c->cpdist > limit && !(c->land == laTrollheim && turncount < c->landparam) && c->wall != waThumperOn) continue;
int d = c->pathdist;
if(d == PINFD - 1) continue;
for(int j=0; j<c->type; j++) {
cellwalker cw1 = cw + j;
// printf("i=%d cd=%d\n", i, c->move(i)->cpdist);
cell *c2 = cw1.peek();
flagtype f = P_MONSTER;
if(param == moTameBomberbird) f |= P_FLYING | P_ISFRIEND;
if(isPrincess(param)) f |= P_ISFRIEND | P_USEBOAT | P_CHAIN;
if(param == moGolem) f |= P_ISFRIEND;
bool pass = c2 && c2->pathdist == PINFD;
if(pass && qb < qtarg && !nonAdjacent(c, c2) && !thruVine(c,c2)) pass = passable(c2, NULL, f);
else pass = pass && passable(c, c2, f);
if(pass) {
if(qb >= qtarg) {
if(param == moTortoise && nogoSlow(c, c2)) continue;
if(param == moIvyRoot && strictlyAgainstGravity(c, c2, false, MF_IVY)) continue;
if(param == moWorm && (cellUnstable(c) || cellEdgeUnstable(c) || prairie::no_worms(c))) continue;
if(!isFriendly(param) && items[itOrbLava] && c2->cpdist <= 5 && pseudohept(c) && makeflame(c2, 1, true))
continue;
}
onpath_with_dir(cw1 + wstep, d+1);
}
else if(c2 && c2->wall == waClosedGate && princess)
gd.visit_gate(c2);
}
}
if(princess) {
gd.run();
if(qb < isize(pathq)) goto princess_retry;
}
}
pathdata::~pathdata() {
pathlock--;
clear_pathdata();
}
void pathdata::checklock() {
if(pd_from) pd_from = NULL, clear_pathdata();
if(pathlock) printf("path error\n");
pathlock++;
}
pathdata::pathdata(int i) { checklock(); }
pathdata::pathdata(eMonster m, bool include_allies IS(true)) {
checklock();
if(isize(pathq))
println(hlog, "! we got tiles on pathq: ", isize(pathq));
computePathdist(m, include_allies);
}
// pathdist end
/** additional direction information for BFS algorithms.
* It remembers from where we have got to this location
* the opposite cell will be added to the queue first,
* which helps the AI. Used in bfs().
**/
EX vector<int> bfs_reachedfrom;
/** calculate cpdist, 'have' flags, and do general fixings */
EX void bfs() {
yendor::onpath();
int dcs = isize(dcal);
for(int i=0; i<dcs; i++) dcal[i]->cpdist = INFD;
worms.clear(); ivies.clear(); ghosts.clear(); golems.clear();
tempmonsters.clear(); targets.clear();
statuecount = 0;
wetslime = 0;
hexsnakes.clear();
hadwhat = havewhat;
havewhat = 0; jiangshi_on_screen = 0;
snaketypes.clear();
if(!(hadwhat & HF_WARP)) { avengers = 0; }
if(!(hadwhat & HF_MIRROR)) { mirrorspirits = 0; }
elec::havecharge = false;
elec::afterOrb = false;
elec::haveelec = false;
airmap.clear();
if(!(hadwhat & HF_ROSE)) rosemap.clear();
dcal.clear(); bfs_reachedfrom.clear();
for(cell *c: player_positions()) {
if(c->cpdist == 0) continue;
c->cpdist = 0;
dcal.push_back(c);
bfs_reachedfrom.push_back(hrand(c->type));
if(!invismove) targets.push_back(c);
}
int distlimit = gamerange();
for(cell *c: player_positions()) {
if(items[itOrbDomination])
if(c->monst == moTentacle || c->monst == moTentaclewait || c->monst == moTentacleEscaping)
worms.push_back(c);
}
int qb = 0;
first7 = 0;
while(true) {
if(qb == isize(dcal)) break;
int i, fd = bfs_reachedfrom[qb] + dcal[qb]->type/2;
cell *c = dcal[qb++];
int d = c->cpdist;
if(WDIM == 2 && d == distlimit) { first7 = qb; break; }
for(int j=0; j<c->type; j++) if(i = (fd+j) % c->type, c->move(i)) {
// printf("i=%d cd=%d\n", i, c->move(i)->cpdist);
cell *c2 = c->move(i);
if(!c2) continue;
if(isWarpedType(c2->land)) havewhat |= HF_WARP;
if(c2->land == laMirror) havewhat |= HF_MIRROR;
if((c->wall == waBoat || c->wall == waSea) &&
(c2->wall == waSulphur || c2->wall == waSulphurC))
c2->wall = waSea;
if(c2 && signed(c2->cpdist) > d+1) {
if(WDIM == 3 && !gmatrix.count(c2)) {
if(!first7) first7 = qb;
continue;
}
c2->cpdist = d+1;
// remove treasures
if(!peace::on && c2->item && c2->cpdist == distlimit && itemclass(c2->item) == IC_TREASURE &&
!among(c2->item, itBrownian, itBabyTortoise) && WDIM != 3 &&
(items[c2->item] >= (ls::any_chaos()?10:20) + currentLocalTreasure || getGhostcount() >= 2)) {
c2->item = itNone;
if(c2->land == laMinefield) { c2->landparam &= ~3; }
}
if(c2->item == itBombEgg && c2->cpdist == distlimit && items[itBombEgg] >= c2->landparam) {
c2->item = itNone;
c2->landparam |= 2;
c2->landparam &= ~1;
if(!c2->monst) c2->monst = moBomberbird, c2->stuntime = 0;
}
if(c2->item == itBarrow && c2->cpdist == distlimit && c2->wall != waBarrowDig) {
c2->item = itNone;
}
if(c2->item == itLotus && c2->cpdist == distlimit && items[itLotus] >= getHauntedDepth(c2)) {
c2->item = itNone;
}
if(c2->item == itMutant2 && timerghost) {
bool rotten = true;
for(int i=0; i<c2->type; i++)
if(c2->move(i) && c2->move(i)->monst == moMutant)
rotten = false;
if(rotten) c2->item = itNone;
}
if(c2->item == itDragon && (shmup::on ? shmup::curtime-c2->landparam>300000 :
turncount-c2->landparam > 500))
c2->item = itNone;
if(c2->item == itTrollEgg && c2->cpdist == distlimit && !shmup::on && c2->landparam && turncount-c2->landparam > 650)
c2->item = itNone;
if(c2->item == itWest && c2->cpdist == distlimit && items[itWest] >= c2->landparam + 4)
c2->item = itNone;
if(c2->item == itMutant && c2->cpdist == distlimit && items[itMutant] >= c2->landparam) {
c2->item = itNone;
}
if(c2->item == itIvory && c2->cpdist == distlimit && items[itIvory] >= c2->landparam) {
c2->item = itNone;
}
if(c2->item == itAmethyst && c2->cpdist == distlimit && items[itAmethyst] >= -celldistAlt(c2)/5) {
c2->item = itNone;
}
if(!keepLightning) c2->ligon = 0;
dcal.push_back(c2);
bfs_reachedfrom.push_back(c->c.spin(i));
if(c2->wall == waBigStatue && c2->land != laTemple)
statuecount++;
if(isAlch(c2->wall) && c2->land == laWet)
wetslime++;
if(cellHalfvine(c2) && isWarped(c2)) {
addMessage(XLAT("%The1 is destroyed!", c2->wall));
destroyHalfvine(c2);
}
if(c2->wall == waCharged) elec::havecharge = true;
if(isElectricLand(c2)) elec::haveelec = true;
if(c2->land == laWhirlpool) havewhat |= HF_WHIRLPOOL;
if(c2->land == laWhirlwind) havewhat |= HF_WHIRLWIND;
if(c2->land == laWestWall) havewhat |= HF_WESTWALL;
if(c2->land == laPrairie) havewhat |= HF_RIVER;
if(c2->land == laClearing) havewhat |= HF_MUTANT;
if(c2->wall == waRose) havewhat |= HF_ROSE;
if((hadwhat & HF_ROSE) && (rosemap[c2] & 3)) havewhat |= HF_ROSE;
if(c2->monst) {
if(isHaunted(c2->land) &&
c2->monst != moGhost && c2->monst != moZombie && c2->monst != moNecromancer)
fail_survivalist();
if(c2->monst == moHexSnake || c2->monst == moHexSnakeTail) {
havewhat |= HF_HEX;
if(c2->mondir != NODIR)
snaketypes.insert(snake_pair(c2));
if(c2->monst == moHexSnake) hexsnakes.push_back(c2);
else findWormIvy(c2);
}
else if(c2->monst == moKrakenT || c2->monst == moKrakenH) {
havewhat |= HF_KRAKEN;
}
else if(c2->monst == moDragonHead || c2->monst == moDragonTail) {
havewhat |= HF_DRAGON;
}
else if(c2->monst == moWitchSpeed)
havewhat |= HF_FAST;
else if(c2->monst == moMutant)
havewhat |= HF_MUTANT;
else if(c2->monst == moJiangshi)
jiangshi_on_screen++;
else if(c2->monst == moOutlaw)
havewhat |= HF_OUTLAW;
else if(isGhostMover(c2->monst))
ghosts.push_back(c2);
else if(isWorm(c2) || isIvy(c2)) findWormIvy(c2);
else if(isBug(c2)) {
havewhat |= HF_BUG;
targets.push_back(c2);
}
else if(isFriendly(c2)) {
if(c2->monst != moMouse && !markEmpathy(itOrbInvis) && !(isWatery(c2) && markEmpathy(itOrbFish)) &&
!c2->stuntime) targets.push_back(c2);
if(c2->monst == moGolem) golems.push_back(c2);
if(c2->monst == moFriendlyGhost) golems.push_back(c2);
if(c2->monst == moKnight) golems.push_back(c2);
if(c2->monst == moTameBomberbird) golems.push_back(c2);
if(c2->monst == moMouse) { golems.push_back(c2); havewhat |= HF_MOUSE; }
if(c2->monst == moPrincess || c2->monst == moPrincessArmed) golems.push_back(c2);
if(c2->monst == moIllusion) {
if(items[itOrbIllusion]) items[itOrbIllusion]--;
else c2->monst = moNone;
}
}
else if(c2->monst == moButterfly) {
addButterfly(c2);
}
else if(isAngryBird(c2->monst)) {
havewhat |= HF_BIRD;
if(c2->monst == moBat) havewhat |= HF_BATS | HF_EAGLES;
if(c2->monst == moEagle) havewhat |= HF_EAGLES;
}
else if(among(c2->monst, moFrog, moVaulter, moPhaser))
havewhat |= HF_JUMP;
else if(c2->monst == moReptile) havewhat |= HF_REPTILE;
else if(isLeader(c2->monst)) havewhat |= HF_LEADER;
else if(c2->monst == moEarthElemental) havewhat |= HF_EARTH;
else if(c2->monst == moWaterElemental) havewhat |= HF_WATER;
else if(c2->monst == moVoidBeast) havewhat |= HF_VOID;
else if(c2->monst == moHunterDog) havewhat |= HF_HUNTER;
else if(isMagneticPole(c2->monst)) havewhat |= HF_MAGNET;
else if(c2->monst == moAltDemon) havewhat |= HF_ALT;
else if(c2->monst == moHexDemon) havewhat |= HF_HEXD;
else if(among(c2->monst, moAnimatedDie, moAngryDie)) havewhat |= HF_DICE;
else if(c2->monst == moMonk) havewhat |= HF_MONK;
else if(c2->monst == moShark || c2->monst == moCShark || among(c2->monst, moRusalka, moPike)) havewhat |= HF_SHARK;
else if(c2->monst == moAirElemental)
havewhat |= HF_AIR, airmap.push_back(make_pair(c2,0));
}
// pheromones!
if(c2->land == laHive && c2->landparam >= 50 && c2->wall != waWaxWall)
havewhat |= HF_BUG;
if(c2->wall == waThumperOn)
targets.push_back(c2);
}
}
}
for(int i=first7; i<isize(dcal); i++)
forCellEx(c2, dcal[i])
if(c2->wall == waThumperOn) {
targets.push_back(c2);
}
for(auto& t: tempmonsters) t.first->monst = t.second;
buildAirmap();
}
EX void moverefresh(bool turn IS(true)) {
int dcs = isize(dcal);
for(int i=0; i<dcs; i++) {
cell *c = dcal[i];
if(c->monst == moWolfMoved) c->monst = moWolf;
if(c->monst == moIvyNext) {
c->monst = moIvyHead; ivynext(c);
}
if(c->monst == moIvyDead)
removeIvy(c);
refreshFriend(c);
if(c->monst == moSlimeNextTurn) c->monst = moSlime;
if(c->monst == moLesser && !cellEdgeUnstable(c)) c->monst = moLesserM;
else if(c->monst == moLesserM) c->monst = moLesser;
if(c->monst == moGreater && !cellEdgeUnstable(c)) c->monst = moGreaterM;
else if(c->monst == moGreaterM) c->monst = moGreater;
if(c->monst == moPair && !c->stuntime) {
cell *c2 = c->move(c->mondir);
if(c2->monst != moPair) continue;
if(true) for(int i: {-1, 1}) {
cell *c3 = c->modmove(c->mondir + i);
if(among(c3->wall, waRuinWall, waColumn, waStone, waVinePlant, waPalace)) {
drawParticles(c3, winf[c3->wall].color, 30);
c3->wall = waNone;
}
}
}
if(c->stuntime && !isMutantIvy(c)) {
if(turn) c->stuntime--;
int breathrange = sphere ? 2 : 3;
if(c->stuntime == 0 && c->monst == moDragonHead) {
// if moDragonHead is renamed to "Dragon Head", we might need to change this
eMonster subject = c->monst;
if(!c->hitpoints) c->hitpoints = 1;
else if(shmup::on && dragon::totalhp(c) > 2 && shmup::dragonbreath(c)) {
c->hitpoints = 0;
}
else if(dragon::totalhp(c) <= 2) ;
else if(isMounted(c)) {
if(dragon::target && celldistance(c, dragon::target) <= breathrange && makeflame(dragon::target, 5, true)) {
addMessage(XLAT("%The1 breathes fire!", subject));
makeflame(dragon::target, 5, false);
playSound(dragon::target, "fire");
c->hitpoints = 0;
}
}
else {
for(int i=0; i<isize(targets); i++) {
cell *t = targets[i];
if(celldistance(c, t) <= breathrange && makeflame(t, 5, true)) {
if(isPlayerOn(t)) addMessage(XLAT("%The1 breathes fire at you!", subject));
else if(t->monst)
addMessage(XLAT("%The1 breathes fire at %the2!", subject, t->monst));
else
addMessage(XLAT("%The1 breathes fire!", subject));
makeflame(t, 5, false);
playSound(t, "fire");
c->hitpoints = 0;
}
}
}
}
}
// tortoises who have found their children no longer move
if(saved_tortoise_on(c))
c->stuntime = 2;
if(c->monst == moReptile) {
if(c->wall == waChasm || cellUnstable(c)) {
c->monst = moNone;
c->wall = waReptile;
c->wparam = reptilemax();
playSound(c, "click");
}
else if(isChasmy(c) || isWatery(c)) {
if(c->wall == waMercury) {
fallMonster(c, AF_FALL);
c->wall = waNone;
}
else {
c->wall = waReptileBridge;
c->wparam = reptilemax();
c->monst = moNone;
}
c->item = itNone;
playSound(c, "click");
}
}
if(c->wall == waChasm) {
if(c->land != laWhirlwind) c->item = itNone;
if(c->monst && !survivesChasm(c->monst) && c->monst != moReptile && normal_gravity_at(c)) {
if(c->monst != moRunDog && c->land == laMotion)
achievement_gain_once("FALLDEATH1");
addMessage(XLAT("%The1 falls!", c->monst));
fallMonster(c, AF_FALL);
}
}
else if(isReptile(c->wall) && turn) {
if(c->monst || isPlayerOn(c)) c->wparam = -1;
else if(c->cpdist <= 7) {
c->wparam--;
if(c->wparam == 0) {
if(c->wall == waReptile) c->wall = waChasm;
else placeWater(c, NULL);
c->monst = moReptile;
c->hitpoints = 3;
c->stuntime = 0;
vector<int> gooddirs;
// in the peace mode, a reptile will
// prefer to walk on the ground, rather than the chasm
for(int i=0; i<c->type; i++) {
int i0 = (i+3) % c->type;
int i1 = (i+c->type-3) % c->type;
if(c->move(i0) && passable(c->move(i0), c, 0))
if(c->move(i1) && passable(c->move(i1), c, 0))
gooddirs.push_back(i);
}
c->mondir = hrand_elt(gooddirs, c->mondir);
playSound(c, "click");
}
}
}
else if(isFire(c)) {
if(c->monst == moSalamander) c->stuntime = max<int>(c->stuntime, 1);
else if(c->monst == moVaulter && c->mondir == JUMP)
c->mondir = NODIR;
else if(c->monst && !survivesFire(c->monst) && !isWorm(c->monst)) {
addMessage(XLAT("%The1 burns!", c->monst));
if(isBull(c->monst)) {
addMessage(XLAT("Fire is extinguished!"));
c->wall = waNone;
}
fallMonster(c, AF_CRUSH);
}
if(c->item && itemBurns(c->item)) {
addMessage(XLAT("%The1 burns!", c->item));
c->item = itNone;
}
}
else if(isWatery(c)) {
if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM)
c->monst = moGreaterShark;
if(c->monst && !survivesWater(c->monst) && normal_gravity_at(c)) {
playSound(c, "splash"+pick12());
if(isNonliving(c->monst))
addMessage(XLAT("%The1 sinks!", c->monst));
else
addMessage(XLAT("%The1 drowns!", c->monst));
if(isBull(c->monst)) {
addMessage(XLAT("%The1 is filled!", c->wall));
c->wall = waShallow;
}
fallMonster(c, AF_FALL);
}
}
else if(c->wall == waSulphur || c->wall == waSulphurC || c->wall == waMercury) {
if(c->monst && !survivesPoison(c->monst, c->wall) && normal_gravity_at(c)) {
playSound(c, "splash"+pick12());
if(isNonliving(c->monst))
addMessage(XLAT("%The1 sinks!", c->monst));
else
addMessage(XLAT("%The1 drowns!", c->monst));
if(isBull(c->monst)) {
addMessage(XLAT("%The1 is filled!", c->wall));
c->wall = waNone;
}
fallMonster(c, AF_FALL);
}
}
else if(c->wall == waMagma) {
if(c->monst == moSalamander) c->stuntime = max<int>(c->stuntime, 1);
else if(c->monst && !survivesPoison(c->monst, c->wall) && normal_gravity_at(c)) {
if(isNonliving(c->monst))
addMessage(XLAT("%The1 is destroyed by lava!", c->monst));
else
addMessage(XLAT("%The1 is killed by lava!", c->monst));
playSound(c, "steamhiss", 70);
fallMonster(c, AF_FALL);
}
}
else if(!isWateryOrBoat(c) && c->wall != waShallow) {
if(c->monst == moGreaterShark)
c->monst = moGreaterM;
else if(c->monst == moShark || c->monst == moCShark) {
addMessage(XLAT("%The1 suffocates!", c->monst));
fallMonster(c, AF_CRUSH);
}
else if(c->monst == moKrakenH) {
addMessage(XLAT("%The1 suffocates!", c->monst));
kraken::kill(c, moNone);
}
}
if(c->monst == moVineSpirit && !cellHalfvine(c) && c->wall != waVinePlant) {
addMessage(XLAT("%The1 is destroyed!", c->monst));
fallMonster(c, AF_CRUSH);
}
if(c->monst) mayExplodeMine(c, c->monst);
if(c->monst && c->wall == waClosedGate && !survivesWall(c->monst)) {
playSound(c, "hit-crush"+pick123());
addMessage(XLAT("%The1 is crushed!", c->monst));
fallMonster(c, AF_CRUSH);
}
if(c->monst && cellUnstable(c) && !ignoresPlates(c->monst) && !shmup::on)
doesFallSound(c);
}
}
// find worms and ivies
EX void settemp(cell *c) {
tempmonsters.emplace_back(c, (eMonster) c->monst);
c->monst = moNone;
}
EX void findWormIvy(cell *c) {
while(true) {
if(c->monst == moWorm || c->monst == moTentacle || c->monst == moWormwait || c->monst == moTentaclewait ||
c->monst == moTentacleEscaping) {
worms.push_back(c); settemp(c);
break;
}
else if(c->monst == moHexSnake) {
hexsnakes.push_back(c); settemp(c);
}
else if(c->monst == moWormtail || c->monst == moHexSnakeTail) {
bool bug = true;
for(int i=0; i<c->type; i++) {
cell* c2 = c->move(i);
if(c2 && isWorm(c2) && c2->mondir != NODIR && c2->move(c2->mondir) == c) {
settemp(c);
c = c2;
bug = false;
}
}
if(bug) break;
}
else if(c->monst == moIvyWait) {
cell* c2 = c->move(c->mondir);
settemp(c); c=c2;
}
else if(c->monst == moIvyHead) {
ivies.push_back(c); settemp(c);
break;
}
else if(c->monst == moIvyBranch || c->monst == moIvyRoot) {
bool bug = true;
for(int i=0; i<c->type; i++) {
cell* c2 = c->move(i);
if(c2 && (c2->monst == moIvyHead || c2->monst == moIvyBranch) && c2->move(c2->mondir) == c) {
settemp(c);
c = c2;
bug = false;
}
}
if(bug) break;
}
else break;
}
}
EX void advance_tides() {
calcTidalPhase();
recalcTide = true;
while(recalcTide) {
recalcTide = false;
for(int i=0; i<isize(dcal); i++) checkTide(dcal[i]);
}
}
EX void monstersTurn() {
reset_spill();
checkSwitch();
mirror::breakAll();
DEBB(DF_TURN, ("bfs"));
bfs();
DEBB(DF_TURN, ("charge"));
if(elec::havecharge) elec::act();
DEBB(DF_TURN, ("mmo"));
int phase2 = (1 & items[itOrbSpeed]);
if(!phase2) movemonsters();
for(cell *pc: player_positions()) if(pc->item == itOrbSafety) {
collectItem(pc, pc, true);
return;
}
if(playerInPower() && (phase2 || !items[itOrbSpeed]) && (havewhat & HF_FAST))
moveNormals(moWitchSpeed);
if(phase2 && markOrb(itOrbEmpathy)) {
bfs();
movegolems(AF_FAST);
for(int i=0; i<isize(dcal); i++) {
if(dcal[i]->monst == moFriendlyGhost && dcal[i]->stuntime)
dcal[i]->stuntime--;
refreshFriend(dcal[i]);
}
}
DEBB(DF_TURN, ("rop"));
if(!dual::state) reduceOrbPowers();
int phase1 = (1 & items[itOrbSpeed]);
if(dual::state && items[itOrbSpeed]) phase1 = !phase1;
DEBB(DF_TURN, ("lc"));
if(!phase1) livecaves();
if(!phase1) ca::simulate();
if(!phase1) heat::processfires();
// this depends on turncount, so we do it always
advance_tides();
for(cell *c: crush_now) {
changes.ccell(c);
playSound(NULL, "closegate");
if(canAttack(c, moCrusher, c, c->monst, AF_GETPLAYER | AF_CRUSH)) {
attackMonster(c, AF_MSG | AF_GETPLAYER | AF_CRUSH, moCrusher);
}
moveEffect(movei(c, FALL), moDeadBird);
destroyBoats(c, NULL, true);
explodeBarrel(c);
}
changes.value_keep(crush_now);
changes.value_keep(crush_next);
crush_now = std::move(crush_next);
crush_next.clear();
DEBB(DF_TURN, ("heat"));
heat::processheat();
// if(elec::havecharge) elec::drawcharges();
orbbull::check();
#if CAP_COMPLEX2
if(!phase1) terracotta::check();
#endif
if(items[itOrbFreedom])
for(cell *pc: player_positions())
checkFreedom(pc);
DEBB(DF_TURN, ("check"));
checkmove();
if(canmove) elec::checklightningfast();
#if CAP_HISTORY
for(cell *pc: player_positions())
history::movehistory.push_back(pc);
#endif
}
/** check if whirlline is looped, if yes, remove the repeat; may not detect loops immediately */
EX bool looped(vector<cell*>& whirlline) {
if(isize(whirlline) == 1)
return false;
if(whirlline.back() == whirlline.front()) {
whirlline.pop_back();
return true;
}
int pos = isize(whirlline)/2;
if(isize(whirlline) > 2 && whirlline.back() == whirlline[pos]) {
while(pos && whirlline.back() == whirlline[pos])
whirlline.pop_back();
/* something weird must have happened... */
static bool once = true;
if(once) addMessage("warning: a looped line");
once = false;
return true;
}
return false;
}
}