mirror of
https://github.com/zenorogue/hyperrogue.git
synced 2024-12-24 17:10:36 +00:00
5612 lines
167 KiB
C++
5612 lines
167 KiB
C++
// Hyperbolic Rogue
|
|
|
|
// Copyright (C) 2011-2013 Zeno Rogue, see 'hyper.cpp' for details
|
|
|
|
#define DEB(x) // printf("%s\n", x); fflush(stdout);
|
|
|
|
#define SAGEMELT .1
|
|
|
|
#define TEMPLE_EACH 6
|
|
|
|
time_t timerstart, savetime;
|
|
bool timerstopped;
|
|
int savecount;
|
|
int turncount;
|
|
int lastexplore;
|
|
eLand lastland;
|
|
|
|
int sagephase = 0;
|
|
|
|
// number of Grails collected, to show you as a knight
|
|
int knighted = 0;
|
|
|
|
bool showoff = false;
|
|
bool safety = false;
|
|
int showid = 0;
|
|
|
|
bool lifebrought = false; // was Life brought to the Dead Caves?
|
|
bool invismove = false; // last move was invisible
|
|
|
|
int currentLocalTreasure;
|
|
|
|
bool landvisited[landtypes];
|
|
|
|
eLand showlist[10] = {
|
|
laHell, laRlyeh, laAlchemist, laGraveyard, laCaves, laDesert, laIce, laJungle, laMotion, laMirror
|
|
};
|
|
|
|
eLand firstland = laIce, euclidland = laIce;
|
|
|
|
extern void DEBT(const char *buf);
|
|
|
|
#define DEBT(x) // printf("%s\n", x);
|
|
|
|
bool eq(short a, short b) { return a==b; }
|
|
|
|
// game state
|
|
int items[ittypes], kills[motypes], explore[10], exploreland[10][landtypes], landcount[landtypes];
|
|
bool playerdead = false; // obsolete
|
|
bool playermoved = true; // center on the PC?
|
|
bool flipplayer = true; // flip the player image after move, do not flip after attack
|
|
int cheater = 0; // did the player cheat?
|
|
|
|
#define INF 9999
|
|
#define INFD 20
|
|
#define BARLEV (ISANDROID?9:10)
|
|
#define BUGLEV 15
|
|
// #define BARLEV 9
|
|
|
|
vector<cell*> dcal; // queue for cpdist
|
|
vector<cell*> pathq; // queue for pathdist
|
|
|
|
vector<cell*> pathqm; // list of monsters to move (pathq restriced to monsters)
|
|
|
|
vector<cell*> targets; // list of monster targets
|
|
|
|
// monsters of specific types to move
|
|
vector<cell*> worms, ivies, ghosts, golems, mirrors, mirrors2;
|
|
|
|
vector<cell*> temps; // temporary changes during bfs
|
|
vector<eMonster> tempval; // restore temps
|
|
|
|
// a bit nicer way of DFS
|
|
vector<int> reachedfrom;
|
|
|
|
// 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
|
|
vector<cell*> movesofgood[8];
|
|
|
|
int first7; // the position of the first monster at distance 7 in dcal
|
|
|
|
cellwalker cwt; // player character position
|
|
|
|
bool isIcyLand(cell *c) {
|
|
return c->land == laIce || c->land == laCocytus;
|
|
}
|
|
|
|
void initcell(cell *c) {
|
|
c->mpdist = INFD; // minimum distance from the player, ever
|
|
c->cpdist = INFD; // current distance from the player
|
|
c->pathdist = INFD; // current distance from the player, along paths (used by yetis)
|
|
c->heat = 0;
|
|
c->wall = waNone;
|
|
c->item = itNone;
|
|
c->monst = moNone;
|
|
c->bardir = NODIR;
|
|
c->land = laNone;
|
|
c->tmp = -1;
|
|
c->ligon = 0;
|
|
lastexplore = turncount;
|
|
}
|
|
|
|
// 0 = basic treasure, 1 = something else, 2 = power orb
|
|
#define IC_TREASURE 0
|
|
#define IC_OTHER 1
|
|
#define IC_ORB 2
|
|
int itemclass(eItem i) {
|
|
if(i == 0) return -1;
|
|
if(i < itKey || i == itFernFlower ||
|
|
i == itWine || i == itSilver || i == itEmerald || i == itRoyalJelly || i == itPower ||
|
|
i == itGrimoire)
|
|
return IC_TREASURE;
|
|
if(i == itKey || i == itOrbYendor || i == itGreenStone || i == itHolyGrail)
|
|
return IC_OTHER;
|
|
return IC_ORB;
|
|
}
|
|
|
|
int puregold() {
|
|
int i = items[itOrbYendor] * 50 + items[itHolyGrail] * 10;
|
|
for(int t=0; t<ittypes; t++)
|
|
if(itemclass(eItem(t)) == IC_TREASURE)
|
|
i += items[t];
|
|
return i;
|
|
}
|
|
|
|
eItem treasureType(eLand l) {
|
|
switch(l) {
|
|
case laIce: return itDiamond;
|
|
case laJungle: return itRuby;
|
|
case laCaves: return itGold;
|
|
case laDesert: return itSpice;
|
|
|
|
case laAlchemist: return itElixir;
|
|
case laMirror: return itShard;
|
|
case laMotion: return itFeather;
|
|
|
|
case laGraveyard: return itBone;
|
|
case laRlyeh: return itStatue;
|
|
case laDryForest: return itFernFlower;
|
|
|
|
case laHell: return itHell;
|
|
case laCocytus: return itSapphire;
|
|
case laCrossroads: return itHyperstone;
|
|
|
|
case laNone: return itNone;
|
|
case laBarrier: return itNone;
|
|
case laGameBoard: return itNone;
|
|
|
|
case laFjord: return itEmerald;
|
|
case laWineyard: return itWine;
|
|
case laHive: return itRoyalJelly;
|
|
case laDeadCaves: return itSilver;
|
|
case laPower: return itPower;
|
|
case laCamelot: return itHolyGrail;
|
|
case laTemple: return itGrimoire;
|
|
}
|
|
return itNone;
|
|
}
|
|
|
|
eItem localTreasureType() {
|
|
lastland = cwt.c->land;
|
|
return treasureType(lastland);
|
|
}
|
|
|
|
void countLocalTreasure() {
|
|
eItem i = localTreasureType();
|
|
currentLocalTreasure = i ? items[i] : 0;
|
|
}
|
|
|
|
bool hellUnlocked() {
|
|
int i = 0;
|
|
for(int t=0; t<ittypes; t++)
|
|
if(itemclass(eItem(t)) == IC_TREASURE && items[t] >= 10)
|
|
i++;
|
|
return i >= 9;
|
|
}
|
|
|
|
bool hyperstonesUnlocked() {
|
|
for(int t=1; t<ittypes; t++)
|
|
if(t != itHyperstone && itemclass(eItem(t)) == IC_TREASURE && items[t] < 10) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
int gold() {
|
|
return puregold();
|
|
}
|
|
|
|
int maxgold() {
|
|
int mg = 0;
|
|
for(int i=0; i<ittypes; i++)
|
|
if(itemclass(eItem(i)) == IC_TREASURE && items[i] > mg)
|
|
mg = items[i];
|
|
return mg;
|
|
}
|
|
|
|
int tkills() {
|
|
return
|
|
kills[moYeti] + kills[moWolf] +
|
|
kills[moRanger] + kills[moTroll] + kills[moGoblin] +
|
|
kills[moWorm] + kills[moDesertman] + kills[moIvyRoot] +
|
|
kills[moMonkey] + kills[moEagle] + kills[moSlime] + kills[moSeep] +
|
|
kills[moRunDog] +
|
|
kills[moCultist] + kills[moTentacle] + kills[moPyroCultist] +
|
|
kills[moLesser] + kills[moGreater] +
|
|
kills[moZombie] + kills[moGhost] + kills[moNecromancer] +
|
|
kills[moHedge] + kills[moFireFairy] +
|
|
kills[moCrystalSage] + kills[moShark] + kills[moGreaterShark] +
|
|
kills[moMiner] + kills[moFlailer] + kills[moLancer] +
|
|
kills[moVineSpirit] + kills[moVineBeast] +
|
|
kills[moBug0] + kills[moBug1] + kills[moBug2] +
|
|
kills[moDarkTroll] + kills[moEarthElemental] +
|
|
kills[moWitch] + kills[moEvilGolem] + kills[moWitchFlash] + kills[moWitchFire] +
|
|
kills[moWitchWinter] + kills[moWitchSpeed] +
|
|
kills[moCultistLeader];
|
|
}
|
|
|
|
bool passable(cell *w, cell *from, bool monster_passable, bool mirror_passable) {
|
|
if(w->wall == waFloorA && from && from->wall == waFloorB && !w->item && !from->item)
|
|
return false;
|
|
if(w->wall == waFloorB && from && from->wall == waFloorA && !w->item && !from->item)
|
|
return false;
|
|
if(w->wall == waMirror || w->wall == waCloud) return mirror_passable;
|
|
if((w->wall == waVineHalfA || w->wall == waVineHalfB) && from && from->wall == w->wall)
|
|
return false;
|
|
if(w->wall == waNone || w->wall == waFloorA || w->wall == waFloorB ||
|
|
w->wall == waCavefloor || w->wall == waFrozenLake || w->wall == waVineHalfA ||
|
|
w->wall == waVineHalfB || w->wall == waDeadfloor || w->wall == waDeadfloor2) {
|
|
if(w->monst) return monster_passable;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool cellUnstable(cell *c) {
|
|
return c->land == laMotion && c->wall == waNone;
|
|
}
|
|
|
|
bool cellUnstableOrChasm(cell *c) {
|
|
return c->land == laMotion && (c->wall == waNone || c->wall == waChasm);
|
|
}
|
|
|
|
bool cellHalfvine(cell *c) {
|
|
return c->wall == waVineHalfA || c->wall == waVineHalfB;
|
|
}
|
|
|
|
bool thruVine(cell *c, cell *c2) {
|
|
return cellHalfvine(c) && c2->wall == c->wall;
|
|
}
|
|
|
|
bool itemBurns(eItem it) {
|
|
return it && it != itOrbDragon && it != itOrbFire;
|
|
}
|
|
|
|
bool attackThruVine(eMonster m) {
|
|
return m == moGhost || m == moVineSpirit;
|
|
}
|
|
|
|
// target, source
|
|
bool attackingForbidden(cell *c, cell *c2) {
|
|
return thruVine(c, c2) && !attackThruVine(c2->monst) && !attackThruVine(c->monst);
|
|
}
|
|
|
|
bool isFire(cell *w) {
|
|
return w->wall == waBonfire || w->wall == waPartialFire;
|
|
}
|
|
|
|
// eagles can go through lakes, chasms, and slime
|
|
// todo vines?
|
|
bool eaglepassable(cell *w) {
|
|
if(w->monst) return false;
|
|
return
|
|
w->wall == waNone || w->wall == waFloorA || w->wall == waFloorB ||
|
|
w->wall == waCavefloor || w->wall == waFrozenLake || w->wall == waLake ||
|
|
w->wall == waDeadfloor || w->wall == waDeadfloor2 || w->wall == waCamelotMoat ||
|
|
w->wall == waSulphur || w->wall == waSulphurC || w->wall == waChasm;
|
|
}
|
|
|
|
bool isActiv(cell *c) {
|
|
return c->wall == waThumper || c->wall == waBonfire || c->wall == waPartialFire;
|
|
}
|
|
|
|
bool isMimic(eMonster m) {
|
|
return m == moMirror || m == moMirage;
|
|
}
|
|
|
|
bool isMimic(cell *c) {
|
|
return isMimic(c->monst);
|
|
}
|
|
|
|
bool isGolemOrKnight(cell *c) {
|
|
return
|
|
c->monst == moGolem || c->monst == moGolemMoved ||
|
|
c->monst == moKnight ||c->monst == moKnightMoved;
|
|
}
|
|
|
|
bool isFriendly(cell *c) {
|
|
return isMimic(c->monst) || isGolemOrKnight(c) || c->monst == moIllusion;
|
|
}
|
|
|
|
bool isBug(eMonster m) {
|
|
return m >= moBug0 && m < moBug0+BUGCOLORS;
|
|
}
|
|
|
|
bool isBug(cell *c) {
|
|
return isBug(c->monst);
|
|
}
|
|
|
|
bool isFriendlyOrBug(cell *c) {
|
|
return isFriendly(c) || isBug(c);
|
|
}
|
|
|
|
bool isIvy(cell *c) {
|
|
return c->monst == moIvyRoot || c->monst == moIvyHead || c->monst == moIvyBranch || c->monst == moIvyWait ||
|
|
c->monst == moIvyNext || c->monst == moIvyDead;
|
|
}
|
|
|
|
bool isDemon(cell *c) {
|
|
return c->monst == moLesser || c->monst == moLesserM ||
|
|
c->monst == moGreater || c->monst == moGreaterM;
|
|
}
|
|
|
|
bool isWorm(cell *c) {
|
|
return c->monst == moWorm || c->monst == moWormtail || c->monst == moWormwait ||
|
|
c->monst == moTentacle || c->monst == moTentacletail || c->monst == moTentaclewait ||
|
|
c->monst == moTentacleEscaping;
|
|
}
|
|
|
|
void useup(cell *c) {
|
|
c->tmp--;
|
|
if(c->tmp == 0) c->wall = waNone;
|
|
}
|
|
|
|
bool isInactiveEnemy(cell *w) {
|
|
if(w->monst == moWormtail || w->monst == moWormwait || w->monst == moTentacletail || w->monst == moTentaclewait)
|
|
return true;
|
|
if(w->monst == moLesserM || w->monst == moGreaterM)
|
|
return true;
|
|
if(w->monst == moIvyRoot || w->monst == moIvyWait || w->monst == moIvyNext || w->monst == moIvyDead)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool isActiveEnemy(cell *w, cell *killed) {
|
|
if(w->monst == moNone || w == killed) return false;
|
|
if(isFriendly(w)) return false;
|
|
if(isInactiveEnemy(w)) return false;
|
|
if(w->monst == moIvyHead || w->monst == moIvyBranch) {
|
|
while(w != killed && w->mondir != NODIR) w = w->mov[w->mondir];
|
|
return w != killed;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool isArmedEnemy(cell *w, cell *killed) {
|
|
return w->monst != moCrystalSage && isActiveEnemy(w, killed);
|
|
}
|
|
|
|
bool player_passable(cell *w, cell *from, bool mon) {
|
|
if(w->monst && !isFriendly(w)) return false;
|
|
if(isFire(w) && items[itOrbWinter]) return true;
|
|
if(w->wall == waRoundTable && from->wall != waRoundTable)
|
|
return true;
|
|
if(items[itOrbGhost] > 1) return true;
|
|
return passable(w, from, mon, true);
|
|
}
|
|
|
|
bool isHive(eLand l) {
|
|
return l == laHive;
|
|
}
|
|
|
|
bool isKillable(cell *c) {
|
|
return c->monst != moShadow && !isWorm(c) && c->monst != moGreater && c->monst != moGreaterM
|
|
&& c->monst != moHedge && c->monst != moFlailer;
|
|
// && !isBug(c->monst);
|
|
}
|
|
|
|
bool isKillableSomehow(cell *c) {
|
|
return isKillable(c)
|
|
|| c->monst == moHedge || c->monst == moLancer || c->monst == moFlailer;
|
|
}
|
|
|
|
bool isNeighbor(cell *c1, cell *c2) {
|
|
for(int i=0; i<c1->type; i++) if(c1->mov[i] == c2) return true;
|
|
return false;
|
|
}
|
|
|
|
// how many monsters are near
|
|
eMonster which;
|
|
|
|
bool mirrorkill(cell *c) {
|
|
for(int t=0; t<c->type; t++)
|
|
if(c->mov[t] && isMimic(c->mov[t]) && c->mov[t]->mov[c->mov[t]->mondir] == c)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// friendly==false: would the flash kill enemy monsters (i.e., allied with the witch)?
|
|
// friendly==true: would the flash kill friendly monsters (or bugs)?
|
|
bool flashWouldKill(cell *c, bool friendly) {
|
|
for(int t=0; t<c->type; t++) {
|
|
cell *c2 = c->mov[t];
|
|
for(int u=0; u<c2->type; u++) {
|
|
cell *c3 = c2->mov[u];
|
|
if(isWorm(c3)) continue; // immune to Flash
|
|
if(c3->monst == moEvilGolem) continue; // evil golems don't count
|
|
if(c3 != c && c3->monst) {
|
|
bool b = isFriendly(c3) || isBug(c3);
|
|
if(friendly ? b: !b) return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int monstersnear(cell *c, cell *nocount = NULL, bool shielded = true) {
|
|
int res = 0;
|
|
bool fast = false;
|
|
if(shielded) {
|
|
if(items[itOrbShield] > 1) return 0;
|
|
fast = (items[itOrbSpeed] && !(items[itOrbSpeed] & 1));
|
|
}
|
|
|
|
for(int t=0; t<c->type; t++) {
|
|
cell *c2 = c->mov[t];
|
|
|
|
// consider monsters who attack from distance 2
|
|
if(c2)
|
|
if(!thruVine(c, c2)) for(int u=2; u<=c2->type-2; u++) {
|
|
cell *c3 = c2->mov[(c->spn[t]+u) % c2->type];
|
|
if(!c3) continue;
|
|
// only these monsters can attack from two spots...
|
|
if(c3->monst != moLancer && c3->monst != moWitchSpeed && c3->monst != moWitchFlash) continue;
|
|
// speedwitches can only attack not-fastened monsters,
|
|
// others can only attack if the move is not fastened
|
|
if(c3->monst == moWitchSpeed && items[itOrbSpeed]) continue;
|
|
if(c3->monst != moWitchSpeed && fast) continue;
|
|
// cannot attack if the immediate cell is impassable (except flashwitches)
|
|
if(c3->monst != moWitchFlash) {
|
|
if(!passable(c2, c3, !isArmedEnemy(c2, nocount), false)) continue;
|
|
if(c2 == cwt.c && items[itOrbFire]) continue;
|
|
}
|
|
// flashwitches cannot attack if it would kill another enemy
|
|
if(c3->monst == moWitchFlash && flashWouldKill(c3, false)) continue;
|
|
if(nocount && mirrorkill(c3)) continue;
|
|
res++, which = c3->monst;
|
|
}
|
|
|
|
// consider normal monsters
|
|
if(c2 && isArmedEnemy(c2, nocount) && c2->monst != moLancer) {
|
|
if(fast && c2->monst != moWitchSpeed) continue;
|
|
// they cannot attack through vines
|
|
if(attackingForbidden(c, c2))
|
|
continue;
|
|
// do not count if it would be killed by a mimic
|
|
if(nocount && mirrorkill(c2)) continue;
|
|
// do not include stabbed enemies
|
|
if(
|
|
(c2->monst == moHedge || (isKillable(c2) && items[itOrbThorns]))
|
|
&& c2->cpdist == 1 && c != cwt.c)
|
|
continue;
|
|
res++, which = c2->monst;
|
|
}
|
|
}
|
|
|
|
return res;
|
|
}
|
|
|
|
// reduce c->mpdist to d; also generate the landscape
|
|
|
|
bool checkBarriersBack(cellwalker& bb, int q=5);
|
|
|
|
bool checkBarriersFront(cellwalker& bb, int q=5) {
|
|
if(bb.c->mpdist < BARLEV) return false;
|
|
if(bb.c->mpdist == BUGLEV) return false;
|
|
if(bb.c->bardir != NODIR) return false;
|
|
if(bb.spin == 0) {q--; if(!q) return true; }
|
|
|
|
if(1) for(int i=0; i<7; i++) {
|
|
cellwalker bb2 = bb;
|
|
cwspin(bb2, i); cwstep(bb2);
|
|
if(bb2.c->bardir != NODIR) return false;
|
|
cwspin(bb2, 4); cwstep(bb2);
|
|
if(bb2.c->bardir != NODIR) return false;
|
|
}
|
|
|
|
cwstep(bb); cwspin(bb, 3); cwstep(bb); cwspin(bb, 3); cwstep(bb);
|
|
return checkBarriersBack(bb, q);
|
|
}
|
|
|
|
bool checkBarriersBack(cellwalker& bb, int q) {
|
|
// printf("back, %p, s%d\n", bb.c, bb.spin);
|
|
if(bb.c->mpdist < BARLEV) return false;
|
|
if(bb.c->mpdist == BUGLEV) return false;
|
|
if(bb.c->bardir != NODIR) return false;
|
|
// if(bb.spin == 0 && bb.c->mpdist == INFD) return true;
|
|
|
|
if(1) for(int i=0; i<7; i++) {
|
|
cellwalker bb2 = bb;
|
|
cwspin(bb2, i); cwstep(bb2);
|
|
if(bb2.c->bardir != NODIR) return false;
|
|
cwspin(bb2, 4); cwstep(bb2);
|
|
if(bb2.c->bardir != NODIR) return false;
|
|
}
|
|
|
|
cwspin(bb, 3); cwstep(bb); cwspin(bb, 4);
|
|
// bool create = cwstepcreates(bb);
|
|
cwstep(bb); cwspin(bb, 3);
|
|
// if(create && bb.spin == 0) return true;
|
|
return checkBarriersFront(bb, q);
|
|
}
|
|
|
|
void setbarrier(cell *c) {
|
|
c->wall = waBarrier;
|
|
c->land = laBarrier;
|
|
/*if(isHive(c->barleft) && isHive(c->barright))
|
|
c->wall = waWaxWall, c->land = c->barleft; */
|
|
}
|
|
|
|
void killIvy(cell *c) {
|
|
if(c->monst == moIvyDead) return;
|
|
for(int i=0; i<c->type; i++) if(c->mov[i])
|
|
if(isIvy(c->mov[i]) && c->mov[i]->mondir == c->spn[i])
|
|
killIvy(c->mov[i]);
|
|
c->monst = moIvyDead;
|
|
}
|
|
|
|
int buildIvy(cell *c, int children, int minleaf) {
|
|
c->mondir = NODIR;
|
|
c->monst = moIvyRoot;
|
|
|
|
cell *child = NULL;
|
|
|
|
int leaf = 0;
|
|
int leafchild = 0;
|
|
for(int i=0; i<c->type; i++) {
|
|
createMov(c, i);
|
|
if(passable(c->mov[i], c, false, false)) {
|
|
if(children && !child)
|
|
child = c->mov[i], leafchild = buildIvy(c->mov[i], children-1, 5);
|
|
else
|
|
c->mov[i]->monst = (leaf++) ? moIvyWait : moIvyHead,
|
|
c->mov[i]->mondir = c->spn[i];
|
|
}
|
|
}
|
|
|
|
leaf += leafchild;
|
|
if(leaf < minleaf) {
|
|
if(child) killIvy(child);
|
|
killIvy(c);
|
|
return 0;
|
|
}
|
|
else return leaf;
|
|
}
|
|
|
|
bool isIcyWall(cell *c) {
|
|
return c->wall == waNone || c->wall == waIcewall || c->wall == waFrozenLake || c->wall == waLake;
|
|
}
|
|
|
|
bool destroyHalfvine(cell *c, eWall newwall = waNone, int tval = 6);
|
|
|
|
void killMonster(cell *c);
|
|
|
|
void prespill(cell* c, eWall t, int rad) {
|
|
// these monsters block spilling
|
|
if(c->monst == moSeep || c->monst == moVineSpirit || c->monst == moShark ||
|
|
c->monst == moGreaterShark)
|
|
return;
|
|
// turn statues into Slimes!
|
|
if(c->wall == waBigStatue && t != waNone) {
|
|
c->wall = waNone;
|
|
c->monst = moSlimeNextTurn;
|
|
}
|
|
// slimedeath spill
|
|
if((c->monst == moSlime || c->monst == moSlimeNextTurn) && t == waNone) {
|
|
c->wall = waNone; killMonster(c);
|
|
}
|
|
// these walls block spilling completely
|
|
if(c->wall == waIcewall || c->wall == waBarrier || c->wall == waDeadTroll ||
|
|
c->wall == waDune || c->wall == waAncientGrave || c->wall == waThumper ||
|
|
c->wall == waFreshGrave || c->wall == waColumn || c->wall == waPartialFire ||
|
|
c->wall == waDeadwall || c->wall == waWaxWall || c->wall == waCamelot || c->wall == waRoundTable ||
|
|
c->wall == waBigStatue)
|
|
return;
|
|
// these walls block further spilling
|
|
if(c->wall == waCavewall || cellUnstable(c) || c->wall == waSulphur ||
|
|
c->wall == waSulphurC || c->wall == waLake || c->wall == waChasm ||
|
|
c->wall == waDryTree || c->wall == waWetTree || c->wall == waTemporary ||
|
|
c->wall == waVinePlant || c->wall == waBonfire || c->wall == waVineHalfA || c->wall == waVineHalfB ||
|
|
c->wall == waCamelotMoat)
|
|
t = waTemporary;
|
|
|
|
if(c->wall == waSulphur) {
|
|
// remove the center as it would not look good
|
|
for(int i=0; i<c->type; i++) if(c->mov[i] && c->mov[i]->wall == waSulphurC)
|
|
c->mov[i]->wall = waSulphur;
|
|
}
|
|
|
|
destroyHalfvine(c);
|
|
c->wall = t;
|
|
// destroy items...
|
|
c->item = itNone;
|
|
// block spill
|
|
if(t == waTemporary) return;
|
|
// cwt.c->item = itNone;
|
|
if(rad) for(int i=0; i<c->type; i++) if(c->mov[i])
|
|
prespill(c->mov[i], t, rad-1);
|
|
}
|
|
|
|
void spillfix(cell* c, eWall t, int rad) {
|
|
if(c->wall == waTemporary) c->wall = t;
|
|
if(rad) for(int i=0; i<c->type; i++) if(c->mov[i])
|
|
spillfix(c->mov[i], t, rad-1);
|
|
}
|
|
|
|
void spill(cell* c, eWall t, int rad) {
|
|
prespill(c,t,rad); spillfix(c,t,rad);
|
|
}
|
|
|
|
void degradeDemons() {
|
|
addMessage(XLAT("You feel more experienced in demon fighting!"));
|
|
int dcs = size(dcal);
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(c->monst == moGreaterM || c->monst == moGreater)
|
|
achievement_gain("DEMONSLAYER");
|
|
if(c->monst == moGreaterM) c->monst = moLesserM;
|
|
if(c->monst == moGreater) c->monst = moLesser;
|
|
}
|
|
}
|
|
|
|
void ivynext(cell *c);
|
|
|
|
void earthFloor(cell *c) {
|
|
if(c->monst) return;
|
|
if(c->wall == waDeadwall) c->wall = waDeadfloor;
|
|
if(c->wall == waDune) c->wall = waNone;
|
|
if(c->wall == waAncientGrave || c->wall == waFreshGrave) c->wall = waNone;
|
|
}
|
|
|
|
void earthWall(cell *c) {
|
|
if(c->wall == waDeadfloor || c->wall == waDeadfloor2 || c->wall == waEarthD) {
|
|
c->item = itNone;
|
|
c->wall = waDeadwall;
|
|
}
|
|
if(c->wall == waNone && c->land == laDesert) {
|
|
c->item = itNone;
|
|
c->wall = waDune;
|
|
}
|
|
}
|
|
|
|
bool isWitch(eMonster m) {
|
|
// evil golems don't count
|
|
return m >= moWitch && m < moWitch+NUMWITCH-1;
|
|
}
|
|
|
|
void killMonster(cell *c) {
|
|
DEB("killmonster");
|
|
eMonster m = c->monst;
|
|
|
|
if(!m) return;
|
|
if(isWorm(c)) return;
|
|
if(m == moShadow) return;
|
|
if(m == moGolemMoved) m = moGolem;
|
|
if(m == moKnightMoved) m = moKnight;
|
|
if(m == moSlimeNextTurn) m = moSlime;
|
|
if(m == moLesserM) m = moLesser;
|
|
if(m == moGreater) m = moLesser;
|
|
if(m == moGreaterM) m = moLesser;
|
|
kills[m]++;
|
|
|
|
if(m == moTroll) {
|
|
destroyHalfvine(c);
|
|
c->wall = cellUnstableOrChasm(c) ? waChasm : waDeadTroll;
|
|
c->item = itNone;
|
|
for(int i=0; i<c->type; i++) if(c->mov[i]) {
|
|
c->mov[i]->item = itNone;
|
|
if(c->mov[i]->wall == waDeadwall || c->mov[i]->wall == waDeadfloor2) c->mov[i]->wall = waCavewall;
|
|
if(c->mov[i]->wall == waDeadfloor) c->mov[i]->wall = waCavefloor;
|
|
}
|
|
}
|
|
if(m == moMiner) {
|
|
destroyHalfvine(c);
|
|
c->wall = cellUnstableOrChasm(c) ? waChasm : waNone;
|
|
for(int i=0; i<c->type; i++) if(passable(c->mov[i], c, true, true)) {
|
|
destroyHalfvine(c->mov[i]);
|
|
c->mov[i]->wall = cellUnstableOrChasm(c) ? waChasm : waNone;
|
|
if(c->mov[i]->monst == moSlime || c->mov[i]->monst == moSlimeNextTurn)
|
|
killMonster(c->mov[i]);
|
|
}
|
|
}
|
|
if(m == moVineBeast) {
|
|
destroyHalfvine(c);
|
|
c->wall = cellUnstableOrChasm(c) ? waChasm : waVinePlant;
|
|
c->item = itNone;
|
|
}
|
|
if(m == moVineSpirit) {
|
|
destroyHalfvine(c);
|
|
c->wall = waNone;
|
|
}
|
|
if(isWitch(m) && (c->wall == waBonfire || passable(c, NULL, true, false)) && !c->item) {
|
|
if(m == moWitchFire) c->item = itOrbFire;
|
|
if(m == moWitchFlash) c->item = itOrbFlash;
|
|
if(m == moWitchGhost) c->item = itOrbGhost;
|
|
if(m == moWitchWinter) c->item = itOrbWinter;
|
|
if(m == moWitchSpeed) c->item = itOrbSpeed;
|
|
if(c->wall == waBonfire && itemBurns(c->item))
|
|
c->item = itNone;
|
|
}
|
|
if(m == moFireFairy) {
|
|
c->wall = cellUnstableOrChasm(c) ? waChasm : waBonfire;
|
|
c->item = itNone, c->tmp = 50;
|
|
}
|
|
if(m == moPyroCultist && c->item == itNone && c->wall != waChasm) {
|
|
// a reward for killing him before he shoots!
|
|
c->item = itOrbDragon;
|
|
}
|
|
if(m == moSlime) { c->monst = moNone; spill(c, c->wall, 2); }
|
|
// if(c->monst == moShark) c->heat += 1;
|
|
// if(c->monst == moGreaterShark) c->heat += 10;
|
|
if(isIcyLand(c)) {
|
|
if(m == moCultist) c->heat += 3;
|
|
if(m == moPyroCultist) c->heat += 6;
|
|
if(m == moLesser) c->heat += 10;
|
|
}
|
|
if(m == moLesser && !(kills[m] % 10))
|
|
degradeDemons();
|
|
if(isIvy(c)) {
|
|
eMonster m = c->monst;
|
|
/*if((m == moIvyBranch || m == moIvyHead) && c->mov[c->mondir]->monst == moIvyRoot)
|
|
ivynext(c, moIvyNext); */
|
|
killIvy(c);
|
|
if(m == moIvyBranch || m == moIvyHead || m == moIvyNext) {
|
|
int qty = 0;
|
|
cell *c2 = c->mov[c->mondir];
|
|
for(int i=0; i<c2->type; i++)
|
|
if(c2->mov[i]->monst == moIvyWait && c2->mov[i]->mondir == c2->spn[i])
|
|
qty++;
|
|
if(c->mov[c->mondir]->monst == moIvyRoot || qty) {
|
|
c->monst = moIvyNext;
|
|
/* c->monst = moIvyHead;
|
|
ivynext(c);
|
|
if(c->monst == moIvyHead) c->monst = moIvyNext;
|
|
else c->monst = moNone; */
|
|
}
|
|
else {
|
|
c->mov[c->mondir]->monst = moIvyHead;
|
|
}
|
|
}
|
|
}
|
|
else c->monst = moNone;
|
|
|
|
if(m == moEarthElemental) earthWall(c);
|
|
}
|
|
|
|
void burnMonstersAndItems(cell *c, int val, eWall firetype = waBonfire) {
|
|
if(itemBurns(c->item))
|
|
addMessage(XLAT("%The1 burns!", c->item)), c->item = itNone;
|
|
if(c->monst && c->monst != moGhost && !isWorm(c) && c->monst != moShadow) {
|
|
addMessage(XLAT("%The1 burns!", c->monst));
|
|
killMonster(c);
|
|
}
|
|
c->tmp = val;
|
|
c->wall = firetype;
|
|
}
|
|
|
|
void flameHalfvine(cell *c, int val) {
|
|
burnMonstersAndItems(c, val);
|
|
c->wall = waPartialFire;
|
|
}
|
|
|
|
bool destroyHalfvine(cell *c, eWall newwall, int tval) {
|
|
if(cellHalfvine(c)) {
|
|
for(int t=0; t<c->type; t++) if(c->mov[t]->wall == c->wall) {
|
|
if(newwall == waPartialFire) burnMonstersAndItems(c->mov[t], tval, newwall);
|
|
else c->mov[t]->wall = newwall;
|
|
}
|
|
if(newwall == waPartialFire) burnMonstersAndItems(c, tval, newwall);
|
|
else c->wall = newwall;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool orbChance(cell *c, eLand usual, int chthere, int chcross) {
|
|
if(c->land == usual) return chthere && rand() % chthere == 0;
|
|
if(chcross && c->land == laCrossroads) {
|
|
chcross = (chcross / 50) * (50 + items[itHyperstone]);
|
|
return rand() % chcross == 0;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void buildBarrier(cell *c) {
|
|
// printf("build barrier at %p", c);
|
|
if(c->wall == waBarrier) {
|
|
// printf("-> ready\n");
|
|
return;
|
|
}
|
|
// if(c->wall == waWaxWall) return;
|
|
if(c->mpdist > BARLEV) {
|
|
// printf("-> too far\n");
|
|
return; // == INFD) return;
|
|
}
|
|
|
|
cellwalker bb(c, c->bardir); setbarrier(bb.c);
|
|
cwstep(bb);
|
|
bb.c->barleft = c->barright;
|
|
bb.c->barright = c->barleft;
|
|
setbarrier(bb.c);
|
|
cwspin(bb, 2); cwstep(bb); bb.c->land = c->barleft; cwstep(bb);
|
|
cwspin(bb, 2); cwstep(bb); bb.c->land = c->barright; cwstep(bb);
|
|
cwspin(bb, 2);
|
|
|
|
cwspin(bb, 3); cwstep(bb);
|
|
bb.c->barleft = c->barright;
|
|
bb.c->barright = c->barleft;
|
|
setbarrier(bb.c);
|
|
cwspin(bb, 3); cwstep(bb);
|
|
|
|
bb.c->bardir = bb.spin;
|
|
bb.c->barleft = c->barright;
|
|
bb.c->barright = c->barleft;
|
|
// printf("#1\n");
|
|
buildBarrier(bb.c);
|
|
|
|
for(int a=-3; a<=3; a++) if(a) {
|
|
bb.c = c; bb.spin = c->bardir; cwspin(bb, a); cwstep(bb);
|
|
bb.c->land = a > 0 ? c->barright : c->barleft;
|
|
}
|
|
|
|
bb.c = c; bb.spin = c->bardir;
|
|
cwspin(bb, 3); cwstep(bb); cwspin(bb, 4); bb.c->land = c->barright; cwstep(bb); cwspin(bb, 3);
|
|
bb.c->bardir = bb.spin;
|
|
bb.c->barleft = c->barright;
|
|
bb.c->barright = c->barleft;
|
|
// printf("#2\n");
|
|
buildBarrier(bb.c);
|
|
}
|
|
|
|
void chasmify(cell *c) {
|
|
c->wall = waChasm; c->item = itNone;
|
|
int q = 0;
|
|
cell *c2[10];
|
|
for(int i=0; i<c->type; i++) if(c->mov[i] && c->mov[i]->mpdist > c->mpdist && cellUnstable(c->mov[i]))
|
|
c2[q++] = c->mov[i];
|
|
if(q) {
|
|
cell *c3 = c2[rand() % q];
|
|
c3->wall = waChasmD;
|
|
}
|
|
}
|
|
|
|
void chasmifyEarth(cell *c) {
|
|
int q = 0;
|
|
int d2[10];
|
|
for(int i=2; i<=c->type-2; i++) {
|
|
int j = (i+c->mondir)%c->type;
|
|
cell *c2 = c->mov[j];
|
|
if(c2 && c2->mpdist > c->mpdist && (
|
|
c2->wall == waDeadfloor || c2->wall == waDeadwall ||
|
|
c2->wall == waDeadfloor2))
|
|
d2[q++] = j;
|
|
}
|
|
if(!q) printf("no further move!\n");
|
|
if(q) {
|
|
int d = d2[rand() % q];
|
|
cell *c3 = c->mov[d];
|
|
c3->wall = waEarthD;
|
|
for(int i=0; i<c3->type; i++) {
|
|
cell *c4 = createMov(c3, i);
|
|
earthFloor(c4);
|
|
}
|
|
c3->mondir = c->spn[d];
|
|
}
|
|
earthWall(c); c->item = itNone;
|
|
}
|
|
|
|
eLand getNewLand(eLand old) {
|
|
eLand tab[32];
|
|
int cnt = 0;
|
|
|
|
/* if(isHive(old) && rand() % 100 < 90) {
|
|
eLand n = old;
|
|
while(n == old) n = eLand(laHive0 + rand() % 3);
|
|
return n;
|
|
} */
|
|
|
|
// return (rand() % 2) ? laMotion : laJungle;
|
|
|
|
// the basic lands, always available
|
|
tab[cnt++] = laCrossroads;
|
|
tab[cnt++] = laIce;
|
|
tab[cnt++] = laDesert;
|
|
tab[cnt++] = laJungle;
|
|
if(old != laDeadCaves) tab[cnt++] = laCaves;
|
|
|
|
// the advanced lands
|
|
if(gold() >= 30) {
|
|
tab[cnt++] = laCrossroads;
|
|
tab[cnt++] = laAlchemist;
|
|
tab[cnt++] = laMirror;
|
|
tab[cnt++] = laMotion;
|
|
}
|
|
|
|
if(gold() >= 60) {
|
|
tab[cnt++] = laCrossroads;
|
|
tab[cnt++] = laRlyeh;
|
|
if(old != laDeadCaves && items[itGold] >= 5 && items[itFernFlower] >= 5) tab[cnt++] = laFjord;
|
|
if(old != laPower) tab[cnt++] = laDryForest;
|
|
if(old != laPower) tab[cnt++] = laWineyard;
|
|
if(old != laCaves && old != laFjord && items[itGold] >= 10) tab[cnt++] = laDeadCaves;
|
|
}
|
|
|
|
if(tkills() >= 100) {
|
|
tab[cnt++] = laGraveyard;
|
|
if(gold() >= 60) tab[cnt++] = laHive;
|
|
}
|
|
|
|
if(hellUnlocked()) {
|
|
tab[cnt++] = laCrossroads;
|
|
tab[cnt++] = laHell;
|
|
}
|
|
|
|
if(items[itHell] >= 10) {
|
|
tab[cnt++] = laCocytus;
|
|
if(old != laWineyard && old != laDryForest) tab[cnt++] = laPower;
|
|
}
|
|
|
|
eLand n = old;
|
|
while(n == old) n = tab[rand() % cnt];
|
|
|
|
return n;
|
|
}
|
|
|
|
bool notDippingFor(eItem i) {
|
|
int v = items[i] - currentLocalTreasure;
|
|
if(v <= 10) return true;
|
|
if(v >= 20) return false;
|
|
return v >= rand() % 10 + 10;
|
|
}
|
|
|
|
bool notDippingForExtra(eItem i, eItem x) {
|
|
int v = items[i] - min(items[x], currentLocalTreasure);
|
|
if(v <= 10) return true;
|
|
if(v >= 20) return false;
|
|
return v >= rand() % 10 + 10;
|
|
}
|
|
|
|
eLand euland[65536];
|
|
|
|
eLand switchable(eLand nearland, eLand farland, eucoord c) {
|
|
if(nearland == laCrossroads) {
|
|
if(rand() % 4 == 0 && (short(c)%3==0))
|
|
return laBarrier;
|
|
return laCrossroads;
|
|
}
|
|
else if(nearland == laBarrier) {
|
|
return getNewLand(farland);
|
|
}
|
|
else {
|
|
if(rand() % 20 == 0 && (short(c)%3==0))
|
|
return laBarrier;
|
|
return nearland;
|
|
}
|
|
}
|
|
|
|
eLand getEuclidLand(eucoord c) {
|
|
if(euland[c]) return euland[c];
|
|
if(c == 0 || c == eucoord(-1) || c == 1)
|
|
return euland[c] = laCrossroads;
|
|
if(euland[eucoord(c-2)] && ! euland[eucoord(c-1)]) getEuclidLand(c-1);
|
|
if(euland[eucoord(c+2)] && ! euland[eucoord(c+1)]) getEuclidLand(c+1);
|
|
if(euland[eucoord(c-1)]) return
|
|
euland[c] = switchable(euland[c-1], euland[eucoord(c-2)], c);
|
|
if(euland[eucoord(c+1)]) return
|
|
euland[c] = switchable(euland[c+1], euland[eucoord(c+2)], c);
|
|
return euland[c] = laCrossroads;
|
|
}
|
|
|
|
void createBugArmy(cell *c);
|
|
|
|
int newRoundTableRadius() {
|
|
return 28 + 2 * items[itHolyGrail];
|
|
}
|
|
|
|
int roundTableRadius(cell *c) {
|
|
if(euclid) return 28;
|
|
return c->master->alt->alt->fjordval & GRAIL_RADIUS_MASK;
|
|
}
|
|
|
|
bool grailWasFound(cell *c) {
|
|
if(euclid) return items[itHolyGrail];
|
|
return c->master->alt->alt->fjordval & GRAIL_FOUND;
|
|
}
|
|
|
|
int euclidAlt(short x, short y) {
|
|
if(euclidland == laTemple) {
|
|
return max(int(x), x+y);
|
|
}
|
|
else return eudist(x-20, y-10);
|
|
}
|
|
|
|
int celldistAltRelative(cell *c) {
|
|
return celldistAlt(c) - roundTableRadius(c);
|
|
}
|
|
|
|
heptagon *createAlternateMap(cell *c, int rad, hstate firststate);
|
|
|
|
void generateAlts(heptagon *h);
|
|
|
|
void buildCamelotWall(cell *c) {
|
|
c->wall = waCamelot;
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = createMov(c, i);
|
|
if(c2->wall == waNone && celldistAlt(c2) > celldistAlt(c) && c2->monst == moNone)
|
|
c2->wall = waCamelotMoat;
|
|
}
|
|
}
|
|
|
|
// This function generates all lands. Warning: it's very long!
|
|
void setdist(cell *c, int d, cell *from) {
|
|
DEB("setdist");
|
|
if(signed(c->mpdist) <= d) return;
|
|
c->mpdist = d;
|
|
|
|
if(d >= BARLEV) {
|
|
if(!c->land) c->land = from->land;
|
|
if(c->land == laTemple) c->land = laRlyeh;
|
|
if(c->land == laCamelot) c->land = laCrossroads;
|
|
|
|
if(euclid) {
|
|
c->land = euclidland;
|
|
if(euclidland == laCrossroads) {
|
|
eucoord x, y;
|
|
decodeMaster(c->master, x, y);
|
|
c->land = getEuclidLand(y+2*x);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(d == BARLEV && !euclid) {
|
|
|
|
if(c->type == 7 && rand() % 10000 < (
|
|
showoff ? (cwt.c->mpdist > 7 ? 0 : 10000) :
|
|
c->land == laCrossroads ? 5000 : 50) &&
|
|
c->land != laGameBoard) {
|
|
|
|
int bd = 2 + (rand() % 2) * 3;
|
|
|
|
cellwalker bb(c, bd);
|
|
cellwalker bb2 = bb;
|
|
|
|
if(checkBarriersFront(bb) && checkBarriersBack(bb2)) {
|
|
c->bardir = bd;
|
|
eLand oldland = c->land;
|
|
eLand newland = getNewLand(oldland);
|
|
if(showoff) newland = showlist[(showid++) % 10];
|
|
landcount[newland]++;
|
|
|
|
if(bd == 5) c->barleft = oldland, c->barright = newland;
|
|
else c->barleft = newland, c->barright = oldland;
|
|
}
|
|
}
|
|
|
|
if(c->land == laCrossroads && c->type == 7 && rand() % 2000 < 200 && items[itEmerald] >= 5) {
|
|
int rtr = newRoundTableRadius();
|
|
heptagon *alt = createAlternateMap(c, rtr+14, hsOrigin);
|
|
if(alt) {
|
|
alt->fjordval = rtr;
|
|
}
|
|
}
|
|
|
|
if(c->land == laRlyeh && c->type == 7 && rand() % 2000 < 100 && items[itStatue] >= 5)
|
|
createAlternateMap(c, 2, hsA);
|
|
|
|
if(c->bardir != NODIR && c->bardir != NOBARRIERS)
|
|
buildBarrier(c);
|
|
}
|
|
|
|
if(d < 10) {
|
|
explore[d]++;
|
|
exploreland[d][c->land]++;
|
|
|
|
if(d < BARLEV)
|
|
for(int i=0; i<c->type; i++) {
|
|
createMov(c, i);
|
|
setdist(c->mov[i], d+1, c);
|
|
}
|
|
|
|
if(d==8 && c->land == laFjord) {
|
|
if(euclid) {
|
|
eucoord x, y;
|
|
decodeMaster(c->master, x, y);
|
|
if(((y-2)&7) < 4) c->wall = waCavewall;
|
|
else c->wall = waCavefloor;
|
|
}
|
|
else {
|
|
int v = fjordval(c);
|
|
if((v&3) >= 2)
|
|
c->wall = waCavewall;
|
|
else c->wall = waCavefloor;
|
|
}
|
|
}
|
|
|
|
if(d==8 && c->land == laPower) {
|
|
int v;
|
|
if(euclid) {
|
|
eucoord x, y;
|
|
decodeMaster(c->master, x, y);
|
|
int y0 = ((short)y) % 6;
|
|
if(y0<0) y0+=6;
|
|
if(y0 == 3 || y0 == 4) v=24; else v=0;
|
|
}
|
|
else v = fjordval(c);
|
|
v &= ~3;
|
|
if((v == 24 || v == 32 || v == 56))
|
|
c->wall = waBonfire, c->tmp = 9;
|
|
else if(rand() % 100 < 10) {
|
|
c->wall = waGlass;
|
|
eItem protectedItems[17] = {
|
|
itPower, itPower, itPower, itPower, itPower, itPower,
|
|
itOrbLightning, itOrbLightning, itOrbThorns, itOrbThorns,
|
|
itOrbInvis, itOrbInvis,
|
|
itOrbShield, itOrbTeleport, itOrbPsi,
|
|
itOrbDragon, itOrbIllusion
|
|
};
|
|
c->item = protectedItems[rand() % 17];
|
|
}
|
|
}
|
|
|
|
if(d==8 && c->land == laWineyard) {
|
|
if(euclid) {
|
|
eucoord x, y;
|
|
decodeMaster(c->master, x, y);
|
|
int dy = ((short)y)%3; if(dy<0) dy += 3;
|
|
if(dy == 1) c->wall = waVinePlant;
|
|
}
|
|
else {
|
|
int v = fjordval(c);
|
|
int w = v / 4;
|
|
if(w == 9 || w == 10 || w == 7 || w == 8) {
|
|
c->wall = waVinePlant;
|
|
}
|
|
else if(v == 24 || v == 58 || v == 26 || v == 56)
|
|
c->wall = waVineHalfA;
|
|
else if(v == 25 || v == 59 || v == 27 || v == 57)
|
|
c->wall = waVineHalfB;
|
|
else c->wall = waNone;
|
|
}
|
|
}
|
|
|
|
if(d == 7 && cellHalfvine(c)) {
|
|
int i = -1;
|
|
for(int k=0; k<c->type; k++) if(c->mov[k] && c->mov[k]->wall == c->wall)
|
|
i = 0;
|
|
if(i == -1) c->wall = waNone;
|
|
}
|
|
|
|
// 24-58
|
|
// 26-56
|
|
if(d == 9) {
|
|
|
|
// if(c->land == laIce && ((celldist(c) % 10) + 10) % 10 == 5)
|
|
// c->wall = waColumn;
|
|
|
|
if(c->land == laIce) if(rand() % 100 < 5 && c->wall != waBarrier) {
|
|
c->wall = waIcewall;
|
|
for(int i=0; i<c->type; i++) if(rand() % 100 < 50) {
|
|
createMov(c, i);
|
|
setdist(c->mov[i], d+1, c);
|
|
cell *c2 = c->mov[i];
|
|
if(c2->wall == waBarrier || c2->land != laIce) continue;
|
|
c2->wall = waIcewall;
|
|
for(int j=0; j<c2->type; j++) if(rand() % 100 < 20) {
|
|
createMov(c2, j);
|
|
setdist(c->mov[i], d+2, c);
|
|
cell *c3 = c2->mov[j];
|
|
if(c3->wall == waBarrier || c3->land != laIce) continue;
|
|
c3->wall = waIcewall;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(c->land == laIce || c->land == laCocytus) if(c->wall == waIcewall && items[itDiamond] >= 5 && rand() % 200 == 1)
|
|
c->wall = waBonfire, c->tmp = -1;;
|
|
|
|
if(c->land == laCaves)
|
|
c->wall = rand() % 100 < 55 ? waCavewall : waCavefloor;
|
|
|
|
if(c->land == laDeadCaves) {
|
|
int i = rand() % 100;
|
|
if(i<50) c->wall = waDeadwall;
|
|
else if(i<55) c->wall = waDeadfloor2;
|
|
else c->wall = waDeadfloor;
|
|
}
|
|
|
|
if(c->land == laAlchemist)
|
|
c->wall = rand() % 2 ? waFloorA : waFloorB;
|
|
|
|
if(c->land == laDryForest)
|
|
c->wall = rand() % 100 < 50 ? (rand() % 100 < 50 ? waDryTree : waWetTree) :
|
|
waNone;
|
|
|
|
if(c->land == laGraveyard && ishept(c))
|
|
c->wall = rand() % 5 ? waAncientGrave : waFreshGrave;
|
|
|
|
if(c->land == laRlyeh) {
|
|
if(rand() % 500 < 5) {
|
|
for(int i=0; i<c->type; i++) {
|
|
createMov(c, i);
|
|
setdist(c->mov[i], d+1, c);
|
|
if(c->mov[i] && c->mov[i]->land == laRlyeh)
|
|
c->mov[i]->wall = waColumn;
|
|
}
|
|
|
|
for(int j=0; j<2; j++) {
|
|
int i = rand() % c->type;
|
|
if(c->mov[i] && c->mov[i]->land == laRlyeh)
|
|
c->mov[i]->wall = waNone;
|
|
}
|
|
}
|
|
if(ishept(c) && rand() % 2) c->wall = waColumn;
|
|
}
|
|
|
|
if(c->land == laHell) {
|
|
if(rand() % 100 < 4) {
|
|
for(int i=0; i<c->type; i++) {
|
|
createMov(c, i);
|
|
setdist(c->mov[i], d+1, c);
|
|
if(c->mov[i] && c->mov[i]->land == laHell)
|
|
if(c->mov[i]->wall != waSulphurC)
|
|
c->mov[i]->wall = waSulphur;
|
|
}
|
|
|
|
c->wall = waSulphurC;
|
|
}
|
|
}
|
|
|
|
if(c->land == laCocytus) {
|
|
if(c->wall == waNone) c->wall = waFrozenLake;
|
|
if(rand() % 100 < 5) {
|
|
for(int i=0; i<c->type; i++) {
|
|
createMov(c, i);
|
|
setdist(c->mov[i], d+1, c);
|
|
if(c->mov[i] && c->mov[i]->land == laCocytus)
|
|
c->mov[i]->wall = waLake;
|
|
}
|
|
|
|
c->wall = waLake;
|
|
|
|
if(rand() % 500 < 100 + 2 * (items[itSapphire]))
|
|
c->monst = moShark;
|
|
}
|
|
}
|
|
|
|
if(isHive(c->land) && rand() % 2000 < 2)
|
|
createBugArmy(c);
|
|
|
|
if((c->land == laCrossroads && !euclid) || c->land == laCamelot)
|
|
if(euclid || c->master->alt) {
|
|
int d = celldistAltRelative(c);
|
|
{
|
|
eucoord x,y;
|
|
decodeMaster(c->master, x, y);
|
|
}
|
|
if(d <= 14) {
|
|
if(!euclid) generateAlts(c->master);
|
|
c->bardir = NOBARRIERS;
|
|
int d = celldistAltRelative(c);
|
|
if(d == 10) {
|
|
if(ishept(c)) buildCamelotWall(c);
|
|
else {
|
|
if(!euclid) for(int i=0; i<7; i++) generateAlts(c->master->move[i]);
|
|
int q = 0;
|
|
for(int t=0; t<6; t++) {
|
|
createMov(c, t);
|
|
if(celldistAltRelative(c->mov[t]) == 10 && !ishept(c->mov[t])) q++;
|
|
}
|
|
if(q == 1) buildCamelotWall(c);
|
|
// towers of Camelot
|
|
if(q == 0) {
|
|
c->monst = moKnight;
|
|
for(int i=0; i<6; i++)
|
|
buildCamelotWall(c->mov[i]);
|
|
for(int i=0; i<c->type; i++) if(celldistAltRelative(c->mov[i]) < d)
|
|
c->mondir = i;
|
|
}
|
|
}
|
|
}
|
|
if(d == 0) c->wall = waRoundTable;
|
|
if(celldistAlt(c) == 0) c->item = itHolyGrail;
|
|
if(d < 0 && rand() % 7000 <= 10 + items[itHolyGrail] * 5) {
|
|
eMonster m[3] = { moHedge, moLancer, moFlailer };
|
|
c->monst = m[rand() % 3];
|
|
}
|
|
if(d == 1) {
|
|
// roughly as many knights as table cells
|
|
if(rand() % 1720 < 1000)
|
|
c->monst = moKnight;
|
|
if(!euclid) for(int i=0; i<7; i++) generateAlts(c->master->move[i]);
|
|
for(int i=0; i<c->type; i++)
|
|
if(c->mov[i] && celldistAltRelative(c->mov[i]) < d)
|
|
c->mondir = (i+3) % 6;
|
|
}
|
|
if(d <= 10) c->land = laCamelot;
|
|
if(d > 10 && !euclid) c->land = laCrossroads;
|
|
}
|
|
}
|
|
|
|
if((c->land == laRlyeh && !euclid) || c->land == laTemple) {
|
|
if(euclid || (c->master->alt && c->master->alt->distance <= 2)) {
|
|
if(!euclid) generateAlts(c->master);
|
|
c->bardir = NOBARRIERS;
|
|
int d = celldistAlt(c);
|
|
if(d <= 0) {
|
|
c->land = laTemple, c->wall = waNone, c->monst = moNone, c->item = itNone;
|
|
}
|
|
if(d % TEMPLE_EACH==0) {
|
|
if(ishept(c))
|
|
c->wall = waColumn;
|
|
else {
|
|
if(!euclid) for(int i=0; i<7; i++) generateAlts(c->master->move[i]);
|
|
int q = 0;
|
|
for(int t=0; t<6; t++) {
|
|
createMov(c, t);
|
|
if(celldistAlt(c->mov[t]) % TEMPLE_EACH == 0 && !ishept(c->mov[t])) q++;
|
|
}
|
|
if(q == 2) c->wall = waColumn;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(isHive(c->land) && rand() % 2000 < 100 && !c->wall && !c->item && !c->monst) {
|
|
int nww = 0;
|
|
for(int i=0; i<c->type; i++) if(c->mov[i] && c->mov[i]->wall == waWaxWall)
|
|
nww++;
|
|
if(nww == 0) {
|
|
c->wall = waWaxWall;
|
|
c->monst = moNone;
|
|
c->heat = rand() & 0xFFFFFF;
|
|
}
|
|
/* for(int i=0; i<c->type; i++) {
|
|
if(rand() % 6 < 5) {
|
|
createMov(c,i);
|
|
cell *c2 = c->mov[i];
|
|
c2->wall = waWaxWall;
|
|
c2->monst = moNone;
|
|
}
|
|
} */
|
|
}
|
|
|
|
if(c->land == laDesert) {
|
|
if(rand() % 100 < 5) {
|
|
for(int i=0; i<c->type; i++) {
|
|
createMov(c, i);
|
|
setdist(c->mov[i], d+1, c);
|
|
if(c->mov[i] && c->mov[i]->land == laDesert)
|
|
c->mov[i]->wall = waDune;
|
|
}
|
|
|
|
for(int j=0; j<2; j++) {
|
|
int i = rand() % c->type;
|
|
if(c->mov[i] && c->mov[i]->land == laDesert)
|
|
c->mov[i]->wall = waNone;
|
|
}
|
|
}
|
|
|
|
if(rand() % 300 == 1 && items[itSpice] >= 5)
|
|
c->wall = waThumper, c->tmp = -1;
|
|
}
|
|
}
|
|
|
|
if(d == 7 && c->land == laCaves && c->wall == waCavewall && rand() % 5000 < items[itGold])
|
|
c->monst = moSeep;
|
|
|
|
if(d == 7 && c->land == laFjord && c->wall == waCavewall && rand() % 5000 < items[itEmerald])
|
|
c->monst = moSeep;
|
|
|
|
if(d == 7 && c->land == laDeadCaves && c->wall == waDeadwall && rand() % 1000 < items[itSilver])
|
|
c->monst = moSeep;
|
|
|
|
if(d == 7 && c->wall == waVinePlant && rand() % 100 < 10)
|
|
c->monst = moVineSpirit;
|
|
|
|
// repair the buggy walls flowing in from another land, like ice walls flowing into the Caves
|
|
if(d == 7 && c->land == laCaves && c->wall != waCavewall && c->wall != waCavefloor)
|
|
c->wall = waCavefloor;
|
|
|
|
if(d == 7 && c->land == laDeadCaves && c->wall != waDeadwall &&
|
|
c->wall != waDeadfloor && c->wall != waDeadfloor2 && c->wall != waEarthD)
|
|
c->wall = waDeadfloor2;
|
|
|
|
if(d == 7 && c->land == laCocytus && c->wall != waFrozenLake && c->wall != waLake && c->wall != waIcewall)
|
|
c->wall = waFrozenLake;
|
|
|
|
if(d == 7 && c->land == laAlchemist && c->wall != waFloorA && c->wall != waFloorB)
|
|
c->wall = waFloorA;
|
|
|
|
if(d == 7 && c->wall == waIcewall && c->land != laIce && c->land != laCocytus)
|
|
c->wall = waNone;
|
|
|
|
if(d == 7 && c->wall == waChasmD) {
|
|
chasmify(c);
|
|
}
|
|
|
|
if(d == 7 && c->wall == waEarthD) {
|
|
chasmifyEarth(c);
|
|
}
|
|
|
|
// seal entrances to the Land of Power.
|
|
if(d == 7 && c->land == laPower && c->type == 7) {
|
|
bool onwall = false;
|
|
for(int i=0; i<7; i++) if(c->mov[i] && c->mov[i]->land != laPower)
|
|
onwall = true;
|
|
if(!onwall) for(int i=0; i<7; i++) {
|
|
cell *c2 = c->mov[i];
|
|
cell *c3 = c2->mov[(c->spn[i] + 3) % 6];
|
|
if(c3->land != laPower && c3->land != laBarrier)
|
|
if(c2->wall != waBonfire && c2->wall != waGlass) {
|
|
if(c->wall == waBonfire) c->monst = moWitchWinter;
|
|
else if(c->wall == waGlass) c->monst = moWitchGhost;
|
|
else c->monst = moEvilGolem;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(d == 7 && passable(c, NULL, false, false)) {
|
|
if(c->land == laBarrier) c->wall = waBarrier;
|
|
}
|
|
|
|
if(d == 7 && passable(c, NULL, false, false) && !safety) {
|
|
int hard = items[itOrbYendor] * 5;
|
|
|
|
if(c->land == laIce) {
|
|
if(rand() % 5000 < 100 + 2 * (kills[moYeti] + kills[moWolf]) && notDippingFor(itDiamond))
|
|
c->item = itDiamond;
|
|
if(rand() % 8000 < 2 * (items[itDiamond] + hard))
|
|
c->monst = rand() % 2 ? moYeti : moWolf;
|
|
}
|
|
if(c->land == laCaves) {
|
|
if(rand() % 5000 < 100 + 2 * (kills[moTroll] + kills[moGoblin]) && notDippingFor(itGold))
|
|
c->item = itGold;
|
|
if(rand() % 8000 < 10 + 2 * (items[itGold] + hard))
|
|
c->monst = rand() % 2 ? moTroll : moGoblin;
|
|
}
|
|
if(c->land == laDeadCaves) {
|
|
if(rand() % 5000 < 100 + 2 * (kills[moDarkTroll] + kills[moEarthElemental]) && notDippingFor(itSilver))
|
|
c->item = itSilver;
|
|
if(rand() % 16000 < (items[itSilver] + hard)) {
|
|
c->monst = moEarthElemental;
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = c->mov[i];
|
|
earthFloor(c2);
|
|
}
|
|
for(int i=0; i<c->type; i++) if(c->mov[i]->mpdist < c->mpdist) c->mondir = i;
|
|
chasmifyEarth(c); c->wall = waDeadfloor2;
|
|
}
|
|
else if(rand() % 8000 < 60 + 8 * (items[itSilver] + hard)) {
|
|
if(rand() % 100 < 25) {
|
|
}
|
|
else c->monst = rand() % 2 ? moDarkTroll : moGoblin;
|
|
}
|
|
}
|
|
if(c->land == laDesert) {
|
|
if(rand() % 5000 < 100 + 2 * (kills[moWorm] + kills[moDesertman]) && notDippingFor(itSpice))
|
|
c->item = itSpice;
|
|
if(rand() % 8000 < 10 + 2 * (items[itSpice] + hard))
|
|
c->monst = rand() % 2 ? moWorm : moDesertman,
|
|
c->mondir = NODIR;
|
|
}
|
|
if(c->land == laWineyard) {
|
|
if(rand() % 5000 < 100 + 2 * (kills[moVineBeast] + kills[moVineSpirit]) && notDippingFor(itWine))
|
|
c->item = itWine;
|
|
if(rand() % 8000 < 12 * (items[itWine] + hard))
|
|
c->monst = moVineBeast;
|
|
}
|
|
if(c->land == laFjord) {
|
|
if(rand() % 1000 < 100 + 2 * (kills[moMiner] + kills[moLancer] + kills[moFlailer]) && notDippingFor(itEmerald)) {
|
|
// do not destroy walls!
|
|
bool ok = true;
|
|
for(int i=0; i<c->type; i++) if(c->mov[i]->wall == waCavewall) ok = false;
|
|
if(ok) c->item = itEmerald;
|
|
}
|
|
if(rand() % 8000 < 50 + 10 * (items[itEmerald] + hard)) {
|
|
static eMonster fjordmonsters[4] = { moHedge, moLancer, moFlailer, moMiner };
|
|
c->monst = fjordmonsters[rand() % 4];
|
|
}
|
|
}
|
|
if(c->land == laJungle) {
|
|
if(rand() % 5000 < 25 + 2 * (kills[moIvyRoot] + kills[moMonkey]) && notDippingFor(itRuby))
|
|
c->item = itRuby;
|
|
if(rand() % 15000 < 5 + 1 * (items[itRuby] + hard))
|
|
c->monst = moMonkey;
|
|
else if(rand() % 80000 < 5 + items[itRuby] + hard)
|
|
c->monst = moEagle;
|
|
else if(ishept(c) && rand() % 4000 < 300 + items[itRuby]) {
|
|
bool hard = rand() % 100 < 25;
|
|
if(hard ? buildIvy(c, 1, 9) : buildIvy(c, 0, c->type))
|
|
c->item = itRuby;
|
|
}
|
|
}
|
|
if(c->land == laAlchemist) {
|
|
if(rand() % 5000 < 25 + min(kills[moSlime], 200) && notDippingFor(itElixir))
|
|
c->item = itElixir;
|
|
else if(rand() % 3500 < 10 + items[itElixir] + hard)
|
|
c->monst = moSlime;
|
|
}
|
|
if(c->land == laPower) {
|
|
if(rand() % (5000+50*items[itPower]) < 1200) {
|
|
eItem powerorbs[6] = {
|
|
itOrbFlash, itOrbSpeed, itOrbFire, itOrbWinter, itOrbGhost, itOrbLife};
|
|
c->item = powerorbs[rand() % 6];
|
|
}
|
|
else if(c->type == 6 && rand() % 5000 < 10)
|
|
c->wall = rand() % 2 ? waMirror : waCloud;
|
|
else if(rand() % 1000 < 10 + (items[itPower] ? 10:0) + (items[itPower] + hard))
|
|
c->monst = eMonster(moWitch + rand() % NUMWITCH);
|
|
}
|
|
if(c->land == laCrossroads) {
|
|
if(c->type == 6 && rand() % 8000 < 120 && items[itShard] >= 10)
|
|
c->wall = rand() % 2 ? waMirror : waCloud;
|
|
else if(hyperstonesUnlocked() && rand() % 5000 < tkills() && notDippingFor(itHyperstone))
|
|
c->item = itHyperstone;
|
|
else if(rand() % 4000 < items[itHyperstone] + 2 * items[itHolyGrail]) {
|
|
// only interesting monsters here!
|
|
static eMonster m[14] = {
|
|
moWorm, moTroll, moEagle,
|
|
moLesser, moGreater, moPyroCultist, moGhost,
|
|
moFireFairy, moIvyRoot, moTentacle, moHedge,
|
|
moLancer, moFlailer, moVineBeast
|
|
};
|
|
eMonster cm = m[rand() % 14];
|
|
if(cm == moIvyRoot) buildIvy(c, 0, c->type);
|
|
else c->monst = cm;
|
|
if(cm == moWorm || cm == moTentacle)
|
|
c->mondir = NODIR;
|
|
}
|
|
}
|
|
if(c->land == laMirror) {
|
|
if(c->type == 6 && rand() % 5000 < 120 && notDippingFor(itShard))
|
|
c->wall = rand() % 2 ? waMirror : waCloud;
|
|
if(rand() % 12000 < 8 + items[itShard] + hard)
|
|
c->monst = moRanger;
|
|
else if(rand() % 60000 < 8 + items[itShard] + hard)
|
|
c->monst = moEagle;
|
|
}
|
|
if(c->land == laGraveyard) {
|
|
if(rand() % 5000 < 30 + 2 * (kills[moZombie] + kills[moGhost] + kills[moNecromancer]) && notDippingFor(itBone))
|
|
c->item = itBone;
|
|
if(rand() % 20000 < 10 + items[itBone] + hard + (kills[moZombie] + kills[moGhost] + kills[moNecromancer])/60) {
|
|
eMonster grm[6] = { moZombie, moZombie, moZombie, moGhost, moGhost, moNecromancer};
|
|
c->monst = grm[rand() % 6];
|
|
}
|
|
}
|
|
if(c->land == laRlyeh) {
|
|
if(rand() % 5000 < 30 + 2 * (kills[moCultist] + kills[moTentacle] + kills[moPyroCultist]) && notDippingFor(itStatue))
|
|
c->item = itStatue;
|
|
if(rand() % 8000 < 5 + items[itStatue] + hard)
|
|
c->monst = moTentacle, c->item = itStatue, c->mondir = NODIR;
|
|
else if(rand() % 12000 < 5 + items[itStatue] + hard)
|
|
c->monst = rand() % 3 ? ((rand() % 40 < items[itStatue]-25) ? moCultistLeader : moCultist) : moPyroCultist;
|
|
else if(rand() % 8000 < 5 + items[itStatue] + hard && c->type == 6) {
|
|
for(int t=0; t<c->type; t++) {
|
|
if(c->mov[t] && c->mov[t]->monst == moNone && (c->wall == waNone || c->wall == waColumn))
|
|
c->mov[t]->wall = ishept(c->mov[t]) ? waColumn : waNone;
|
|
if(c->mov[t]->wall == waColumn)
|
|
c->mov[t]->item = itNone;
|
|
}
|
|
if(buildIvy(c, 0, 3)) c->item = itStatue;
|
|
}
|
|
}
|
|
if(c->land == laTemple) {
|
|
// depth!
|
|
int d = celldistAlt(c);
|
|
// remember: d is negative
|
|
if(d % TEMPLE_EACH == 0) {
|
|
if(rand() % 5000 < 20 - 2*d)
|
|
c->monst = moTentacle, c->mondir = NODIR;
|
|
}
|
|
else {
|
|
int d0 = d % TEMPLE_EACH;
|
|
if(d0<0) d0=-d0;
|
|
if(rand() % 100 < 30) // && d0 != 1 && d0 != TEMPLE_EACH-1)
|
|
c->wall = waBigStatue;
|
|
else if(rand() % 20000 < -d)
|
|
c->monst = rand() % 3 ? moCultist : moPyroCultist;
|
|
else if(rand() % 100000 < -d)
|
|
c->monst = moCultistLeader;
|
|
else if(rand() % 5000 < 250)
|
|
c->item = itGrimoire;
|
|
else if(rand() % 5000 < 10 && -d > TEMPLE_EACH * 10)
|
|
c->item = itOrbDragon;
|
|
}
|
|
}
|
|
if(c->land == laDryForest) {
|
|
if(rand() % 5000 < 100 + 2 * (kills[moFireFairy]*2 + kills[moHedge]) && notDippingFor(itFernFlower))
|
|
c->item = itFernFlower;
|
|
if(rand() % 4000 < 40 + items[itFernFlower] + hard)
|
|
c->monst = moHedge;
|
|
else if(rand() % 8000 < 2 * items[itFernFlower] + hard)
|
|
c->monst = moFireFairy;
|
|
}
|
|
if(c->land == laHell) {
|
|
if(rand() % 1500 < 30 + (kills[moCultist] + kills[moTentacle]) && notDippingFor(itHell))
|
|
c->item = itHell;
|
|
if(rand() % 8000 < 40 + items[itHell] + hard)
|
|
c->monst = moLesser;
|
|
else if(rand() % 24000 < 40 + items[itHell] + hard)
|
|
c->monst = moGreater;
|
|
}
|
|
if(c->land == laCocytus) {
|
|
if(rand() % 5000 < 100 + 2 * (kills[moShark] + kills[moGreaterShark] + kills[moCrystalSage]) && notDippingFor(itSapphire))
|
|
c->item = itSapphire;
|
|
if(rand() % 5000 < 2 * (items[itSapphire] + hard)) {
|
|
eMonster ms[3] = { moYeti, moGreaterShark, moCrystalSage };
|
|
c->monst = ms[rand() % 3];
|
|
if(c->monst == moGreaterShark) c->wall = waLake;
|
|
}
|
|
}
|
|
if(c->land == laMotion) {
|
|
if(rand() % 1500 < 30 + (kills[moRunDog]) && notDippingFor(itFeather))
|
|
c->item = itFeather;
|
|
if(rand() % 20000 < 25 + items[itFeather] + hard) {
|
|
c->monst = moRunDog;
|
|
// preset the movement direction
|
|
// this will make the dog go in the direction of the center,
|
|
// if the player is unreachable/invisible
|
|
for(int d=0; d<c->type; d++) if(c->mov[d] == from) {
|
|
c->mondir = (d+3) % c->type;
|
|
}
|
|
chasmify(c);
|
|
}
|
|
}
|
|
if(c->land == laHive) {
|
|
if(isHive(c->land) && rand() % 6000 < (items[itRoyalJelly] + hard-15))
|
|
c->monst = eMonster(moBug0 + rand() % BUGCOLORS);
|
|
|
|
/* if(rand() % 1500 < 30 + (kills[moBug0] + kills[moBug1] + kills[moBug2]) && notDippingFor(itRoyalJelly))
|
|
c->item = itRoyalJelly; */
|
|
/* if(rand() % 2000 < 2)
|
|
c->monst = eMonster(moBug0 + rand() % 3); */
|
|
}
|
|
if(!c->item && c->wall != waCloud && c->wall != waMirror) {
|
|
if(orbChance(c, laJungle, 1200, 1500) && items[itRuby] >= 10)
|
|
c->item = itOrbLightning;
|
|
if(orbChance(c, laIce, 2000, 1500) && items[itDiamond] >= 10)
|
|
c->item = itOrbFlash;
|
|
if(orbChance(c, laCaves, 1800, 2000) && items[itGold] >= 10)
|
|
c->item = itOrbLife;
|
|
if(orbChance(c, laAlchemist, 600, 800) && items[itElixir] >= 10)
|
|
c->item = itOrbSpeed;
|
|
if(orbChance(c, laGraveyard, 200, 200) && items[itBone] >= 10)
|
|
c->item = itGreenStone;
|
|
if(orbChance(c, laDesert, 2500, 600) && items[itSpice] >= 10)
|
|
c->item = itOrbShield;
|
|
if(orbChance(c, laHell, 2000, 1000) && items[itHell] >= 10)
|
|
c->item = itOrbYendor;
|
|
if(orbChance(c, laRlyeh, 1500, 1000) && items[itStatue] >= 10)
|
|
c->item = itOrbTeleport;
|
|
if(orbChance(c, laMotion, 2000, 700) && items[itFeather] >= 10) {
|
|
c->item = itOrbSafety;
|
|
}
|
|
|
|
if(orbChance(c, laIce, 1500, 0) && items[itDiamond] >= 10)
|
|
c->item = itOrbWinter;
|
|
if(orbChance(c, laDryForest, 2500, 0) && items[itFernFlower] >= 10)
|
|
c->item = itOrbWinter;
|
|
if(orbChance(c, laCocytus, 1500, 1500) && items[itSapphire] >= 10)
|
|
c->item = itOrbWinter;
|
|
if(orbChance(c, laCaves, 1200, 0) && items[itGold] >= 10)
|
|
c->item = itOrbDigging;
|
|
if(orbChance(c, laDryForest, 500, 2000) && items[itFernFlower] >= 10)
|
|
c->item = itOrbThorns;
|
|
|
|
if(orbChance(c, laDeadCaves, 1800, 0) && items[itSilver] >= 10)
|
|
c->item = itGreenStone;
|
|
if(orbChance(c, laDeadCaves, 1800, 1000) && items[itSilver] >= 10)
|
|
c->item = itOrbDigging;
|
|
if(orbChance(c, laFjord, 1500, 2500) && items[itEmerald] >= 10)
|
|
c->item = itOrbPsi;
|
|
if(orbChance(c, laWineyard, 900, 1200) && items[itWine] >= 10)
|
|
c->item = itOrbGhost;
|
|
if(orbChance(c, laHive, 800, 1200) && items[itRoyalJelly] >= 10)
|
|
c->item = itOrbInvis;
|
|
if(orbChance(c, laPower, 0, 1500) && items[itPower] >= 10)
|
|
c->item = itOrbFire;
|
|
if(orbChance(c, laTemple, 0, 2500) && items[itGrimoire] >= 10)
|
|
c->item = itOrbDragon;
|
|
if(orbChance(c, laCamelot, 1000, 1500) && items[itHolyGrail])
|
|
c->item = itOrbIllusion;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// find worms and ivies
|
|
void settemp(cell *c) {
|
|
temps.push_back(c); tempval.push_back(c->monst); c->monst = moNone;
|
|
}
|
|
|
|
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 == moWormtail) {
|
|
bool bug = true;
|
|
for(int i=0; i<c->type; i++) {
|
|
cell* c2 = c->mov[i];
|
|
if(c2 && isWorm(c2) && c2->mov[c2->mondir] == c) {
|
|
settemp(c);
|
|
c = c2;
|
|
bug = false;
|
|
}
|
|
}
|
|
if(bug) break;
|
|
}
|
|
else if(c->monst == moIvyWait) {
|
|
cell* c2 = c->mov[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->mov[i];
|
|
if(c2 && (c2->monst == moIvyHead || c2->monst == moIvyBranch) && c2->mov[c2->mondir] == c) {
|
|
settemp(c);
|
|
c = c2;
|
|
bug = false;
|
|
}
|
|
}
|
|
if(bug) break;
|
|
}
|
|
else break;
|
|
}
|
|
}
|
|
|
|
bool isGhostMover(eMonster m) {
|
|
return m == moGhost || m == moGreaterShark ||
|
|
(cwt.c->land == laPower && (m == moWitchGhost || m == moWitchWinter));
|
|
}
|
|
|
|
bool havebugs, haveearth, haveeagles, haveleader;
|
|
|
|
bool bugsfighting;
|
|
|
|
// calculate cpdist and pathdist
|
|
void bfs() {
|
|
|
|
int dcs = size(dcal);
|
|
for(int i=0; i<dcs; i++) dcal[i]->cpdist = INFD;
|
|
worms.clear(); ivies.clear(); ghosts.clear(); golems.clear(); mirrors.clear();
|
|
temps.clear(); tempval.clear(); targets.clear(); havebugs = false;
|
|
haveearth = false; haveeagles = false; haveleader = false;
|
|
|
|
dcal.clear(); dcal.push_back(cwt.c);
|
|
reachedfrom.clear(); reachedfrom.push_back(rand() % cwt.c->type);
|
|
|
|
int pqs = size(pathq);
|
|
for(int i=0; i<pqs; i++) pathq[i]->pathdist = INFD;
|
|
pathq.clear(); if(!invismove) targets.push_back(cwt.c);
|
|
|
|
cwt.c->cpdist = 0;
|
|
int qb = 0;
|
|
while(true) {
|
|
int i, fd = reachedfrom[qb] + 3;
|
|
cell *c = dcal[qb++];
|
|
int d = c->cpdist;
|
|
if(d == 7) { first7 = qb; break; }
|
|
for(int j=0; j<c->type; j++) if(i = (fd+j) % c->type, c->mov[i]) {
|
|
// printf("i=%d cd=%d\n", i, c->mov[i]->cpdist);
|
|
cell *c2 = c->mov[i];
|
|
if(c2 && signed(c2->cpdist) > d+1) {
|
|
c2->cpdist = d+1;
|
|
|
|
// remove treasures
|
|
if(c2->item && c2->cpdist == 7 && itemclass(c2->item) == IC_TREASURE &&
|
|
items[c2->item] >= 20 + currentLocalTreasure)
|
|
c2->item = itNone;
|
|
|
|
c2->ligon = 0;
|
|
dcal.push_back(c2);
|
|
reachedfrom.push_back(c->spn[i]);
|
|
if(c2->monst) {
|
|
if(isWorm(c2) || isIvy(c2)) findWormIvy(c2);
|
|
else if(isGhostMover(c2->monst))
|
|
ghosts.push_back(c2);
|
|
else if(isBug(c2)) {
|
|
havebugs = true;
|
|
targets.push_back(c2);
|
|
}
|
|
else if(isFriendly(c2)) {
|
|
targets.push_back(c2);
|
|
if(c2->monst == moGolem) golems.push_back(c2);
|
|
if(c2->monst == moKnight) golems.push_back(c2);
|
|
if(c2->monst == moIllusion) {
|
|
if(items[itOrbIllusion]) items[itOrbIllusion]--;
|
|
else c2->monst = moNone;
|
|
}
|
|
if(isMimic(c2)) mirrors.push_back(c2);
|
|
}
|
|
else if(c2->monst == moEagle) haveeagles = true;
|
|
else if(c2->monst == moEarthElemental) haveearth = true;
|
|
else if(c2->monst == moCultistLeader) haveleader = true;
|
|
}
|
|
// pheromones!
|
|
if(c2->land == laHive && c2->heat >= 50 && c2->wall != waWaxWall)
|
|
havebugs = true;
|
|
if(c2->wall == waThumper && c2->tmp > 0) {
|
|
useup(c2);
|
|
targets.push_back(c2);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int i=0; i<size(targets); i++) {
|
|
targets[i]->pathdist = (targets[i] == cwt.c) ? 0 : 1;
|
|
pathq.push_back(targets[i]);
|
|
}
|
|
|
|
int qtemp = size(temps);
|
|
for(int i=0; i<qtemp; i++) temps[i]->monst = tempval[i];
|
|
|
|
pathqm.clear();
|
|
reachedfrom.clear(); reachedfrom.push_back(rand() % cwt.c->type);
|
|
|
|
qb = 0;
|
|
for(qb=0; qb < size(pathq); qb++) {
|
|
int fd = reachedfrom[qb] + 3;
|
|
cell *c = pathq[qb];
|
|
eucoord x, y;
|
|
decodeMaster(c->master, x, y);
|
|
if(c->monst && !isBug(c) && !isFriendly(c)) {
|
|
pathqm.push_back(c);
|
|
continue; // no paths going through monsters
|
|
}
|
|
if(c->cpdist > 7) continue;
|
|
int d = c->pathdist;
|
|
for(int j=0; j<c->type; j++) {
|
|
int i = (fd+j) % c->type;
|
|
// printf("i=%d cd=%d\n", i, c->mov[i]->cpdist);
|
|
if(c->mov[i] && c->mov[i]->pathdist == INFD && !thruVine(c, c->mov[i]) &&
|
|
(c->mov[i]->monst || passable(c->mov[i], (d==0)?NULL:c, true, false))) {
|
|
c->mov[i]->pathdist = d+1;
|
|
pathq.push_back(c->mov[i]); reachedfrom.push_back(c->spn[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extern void cleargraphmemory();
|
|
|
|
// initialize the game
|
|
void initgame() {
|
|
cwt.c = origin.c7; cwt.spin = 0;
|
|
cwt.c->land = euclid ? euclidland : firstland;
|
|
createMov(cwt.c, 0);
|
|
|
|
for(int i=0; i<65536; i++) euland[i] = laNone;
|
|
|
|
// extern int sightrange; sightrange = 9;
|
|
// cwt.c->land = laHell; items[itHell] = 10;
|
|
for(int i=9; i>=0; i--) {
|
|
setdist(cwt.c, i, NULL);
|
|
verifycells(&origin);
|
|
}
|
|
if(cwt.c->land == laCocytus)
|
|
cwt.c->wall = waFrozenLake;
|
|
else if(cwt.c->land == laAlchemist || cwt.c->land == laGameBoard)
|
|
;
|
|
else if(cwt.c->land == laCaves || cwt.c->land == laFjord)
|
|
cwt.c->wall = waCavefloor;
|
|
else if(cwt.c->land == laDeadCaves)
|
|
cwt.c->wall = waDeadfloor2;
|
|
else
|
|
cwt.c->wall = waNone;
|
|
cwt.c->item = itNone;
|
|
cwt.c->monst = moNone;
|
|
|
|
cleargraphmemory();
|
|
|
|
if(!safety) {
|
|
timerstart = time(NULL); turncount = 0; sagephase = 0;
|
|
timerstopped = false;
|
|
savecount = 0; savetime = 0;
|
|
cheater = 0;
|
|
if(firstland != laIce) cheater++;
|
|
}
|
|
else safety = false;
|
|
|
|
// items[itGreenStone] = 100;
|
|
// items[itOrbTeleport] = 100;
|
|
|
|
/* items[itGold] = 10;
|
|
items[itDiamond] = 10;
|
|
items[itSpice] = 10;
|
|
items[itElixir] = 10;
|
|
items[itRuby] = 10;
|
|
items[itShard] = 10;
|
|
items[itSilver] = 10;
|
|
items[itWine] = 10;
|
|
items[itRoyalJelly] = 10;
|
|
items[itEmerald] = 10;
|
|
items[itFeather] = 10;
|
|
kills[moYeti] = 90; */
|
|
|
|
/*
|
|
items[itGold] = 20;
|
|
items[itDiamond] = 20;
|
|
items[itSpice] = 20;
|
|
items[itRuby] = 20;
|
|
items[itElixir] = 20;
|
|
*/
|
|
|
|
/*
|
|
items[itOrbShield] = 100;
|
|
items[itOrbSpeed] = 100;
|
|
items[itOrbWinter] = 100;
|
|
items[itOrbLightning] = 100;
|
|
*/
|
|
|
|
// items[itOrbLightning] = 100;
|
|
// items[itRuby] = 100;
|
|
|
|
// items[itOrbWinter] = 1000;
|
|
|
|
bfs();
|
|
}
|
|
|
|
void firetrail(cell *c) {
|
|
if(!passable(c, NULL, true, true)) return;
|
|
destroyHalfvine(c);
|
|
c->item = itNone;
|
|
if(c->wall == waFrozenLake)
|
|
c->wall = waLake;
|
|
if(cellUnstableOrChasm(c))
|
|
return;
|
|
c->wall = waBonfire, c->tmp = 10;
|
|
}
|
|
|
|
void moveNormal(cell *c) {
|
|
bool repeat = true;
|
|
eMonster m = c->monst;
|
|
|
|
again:
|
|
|
|
for(int j=0; j<c->type; j++)
|
|
if(c->mov[j] && isFriendlyOrBug(c->mov[j]) && repeat && !attackingForbidden(c->mov[j], c)) {
|
|
// XLATC enemy destroys a friend
|
|
addMessage(XLAT("%The1 destroys %the2!", m, c->mov[j]->monst));
|
|
c->mov[j]->monst = moNone;
|
|
return;
|
|
}
|
|
|
|
int posdir[10], nc = 0;
|
|
|
|
for(int j=0; j<c->type; j++) {
|
|
cell *c2 = c->mov[j];
|
|
if(!c2) continue;
|
|
if(c2->pathdist >= c->pathdist) continue;
|
|
if(!passable(c2, c, false, false)) continue;
|
|
// crystal sages can't stand out of Cocytus
|
|
if(m == moCrystalSage && (c2->land != laCocytus || c2->heat > SAGEMELT))
|
|
continue;
|
|
// lancers don't move next to other monsters
|
|
if(c->monst == moLancer) {
|
|
bool lancerok = true;
|
|
for(int u=2; u<=c2->type-2; u++) {
|
|
cell *c3 = c2->mov[(c->spn[j]+u)%c2->type];
|
|
if(c3->monst && !isFriendlyOrBug(c3) && isKillableSomehow(c3))
|
|
lancerok = false;
|
|
}
|
|
if(!lancerok) continue;
|
|
}
|
|
|
|
|
|
// if(m == moNecromancer ? c2->land == laGraveyard : true)
|
|
// it is more fun when demons step into the Land of Eternal Motion, IMO
|
|
// if((m == moLesser || m == moGreater) ? c2->land != laMotion : true)
|
|
if(c2->cpdist > 0) {
|
|
posdir[nc] = j;
|
|
nc++;
|
|
}
|
|
}
|
|
|
|
if(nc == 0 && !passable(c, NULL, true, true)) {
|
|
// running dogs run away!
|
|
// they prefer a straight direction
|
|
int sgn = rand() % 2 ? 1 : -1;
|
|
for(int b=3; b>=0; b--) for(int s=-1; s<=1; s+=2) if(!nc) {
|
|
int d = (c->mondir + b*s*sgn) % c->type;
|
|
d += c->type; d %= c->type;
|
|
cell *c2 = c->mov[d];
|
|
if(passable(c2, c, false, false))
|
|
posdir[nc++] = d;
|
|
}
|
|
}
|
|
|
|
/*
|
|
// Eagles can fly through chasms/slime if needed
|
|
if(m == moEagle && nc == 0) {
|
|
printf("no eagle no fly\n");
|
|
for(int j=0; j<c->type; j++) printf("%d from %d\n", c->mov[j]->cpdist, c->cpdist);
|
|
for(int j=0; j<c->type; j++)
|
|
if(c->mov[j] && c->mov[j]->cpdist < c->cpdist &&
|
|
!c->mov[j]->monst &&
|
|
(c->mov[j]->land == laMotion || c->mov[j]->land == laAlchemist) && c->wall != waDeadTroll)
|
|
if(c->mov[j]->cpdist > 0)
|
|
posdest[nc++] = c->mov[j];
|
|
printf("nc=%d\n", nc);
|
|
} */
|
|
|
|
if(!nc) return;
|
|
nc = rand() % nc;
|
|
cell *c2 = c->mov[posdir[nc]];
|
|
c2->monst = m, c->monst = moNone;
|
|
c2->mondir = c->spn[posdir[nc]];
|
|
if(m == moEagle && repeat && c->pathdist > 1) { repeat = false; c = c2; goto again; }
|
|
|
|
// lancers pierce our friends :(
|
|
if(m == moLancer) {
|
|
// printf("lancer stab?\n");
|
|
for(int u=2; u<=c2->type-2; u++) {
|
|
cell *c3 = c2->mov[(c->spn[posdir[nc]]+u)%c2->type];
|
|
if(c3->monst && isKillableSomehow(c3)) {
|
|
addMessage(XLAT("%The1 pierces %the2!", m, c3->monst));
|
|
killMonster(c3);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(m == moWitchFire) firetrail(c);
|
|
if(isWitch(m) && c2->item == itOrbLife && passable(c, NULL, true, true)) {
|
|
// note that Fire Witches don't pick up Orbs of Life,
|
|
addMessage(XLAT("%The1 picks up %the2!", moWitch, c2->item));
|
|
c->monst = moEvilGolem; c2->item = itNone;
|
|
}
|
|
else if(m == moWitch) {
|
|
bool pickup = false;
|
|
if(c2->item == itOrbFlash)
|
|
pickup = true, m = moWitchFlash;
|
|
if(c2->item == itOrbWinter)
|
|
pickup = true, m = moWitchWinter;
|
|
if(c2->item == itOrbGhost)
|
|
pickup = true, m = moWitchGhost;
|
|
if(c2->item == itOrbFire)
|
|
pickup = true, m = moWitchFire;
|
|
// Orbs of Speed are a special case here, because we don't want
|
|
// the witch to move immediately
|
|
if(c2->item == itOrbLife)
|
|
pickup = true, c->monst = moEvilGolem;
|
|
if(pickup) {
|
|
addMessage(XLAT("%The1 picks up %the2!", moWitch, c2->item));
|
|
c2->monst = m; c2->item = itNone;
|
|
}
|
|
}
|
|
}
|
|
|
|
void explodeAround(cell *c) {
|
|
for(int j=0; j<c->type; j++) {
|
|
cell* c2 = c->mov[j];
|
|
if(c2) {
|
|
if(isIcyLand(c2)) c2->heat += 0.5;
|
|
if((c2->wall == waDune || c2->wall == waIcewall ||
|
|
c2->wall == waAncientGrave || c2->wall == waFreshGrave ||
|
|
c2->wall == waColumn || c2->wall == waThumper || c2->wall == waBonfire ||
|
|
c2->wall == waDryTree || c2->wall == waWetTree ||
|
|
c2->wall == waVinePlant || c2->wall == waVineHalfA || c2->wall == waVineHalfB)) {
|
|
destroyHalfvine(c2); c2->wall = waNone;
|
|
}
|
|
if(c2->wall == waCavewall || c2->wall == waDeadTroll) c2->wall = waCavefloor;
|
|
if(c2->wall == waDeadfloor2) c2->wall = waDeadfloor;
|
|
if(c2->wall == waDeadwall) c2->wall = waDeadfloor2;
|
|
if(c2->wall == waBigStatue) c2->wall = waNone;
|
|
if(c2->wall == waFloorA || c2->wall == waFloorB)
|
|
if(c->wall == waFloorA || c->wall == waFloorB)
|
|
c2->wall = c->wall;
|
|
}
|
|
}
|
|
}
|
|
|
|
void moveWorm(cell *c) {
|
|
|
|
if(c->monst == moWormwait) { c->monst = moWorm; return; }
|
|
else if(c->monst == moTentaclewait) { c->monst = moTentacle; return; }
|
|
else if(c->monst == moTentacleEscaping) {
|
|
// explodeAround(c);
|
|
c->monst = moNone;
|
|
if(c->mondir != NODIR) c->mov[c->mondir]->monst = moTentacleEscaping;
|
|
return;
|
|
}
|
|
else if(c->monst != moWorm && c->monst != moTentacle) return;
|
|
|
|
int ncg = 0, ncb = 0;
|
|
cell *gmov[7], *bmov[7];
|
|
|
|
int id = c->monst - moWorm;
|
|
|
|
for(int j=0; j<c->type; j++) {
|
|
if(c->mov[j] && isFriendlyOrBug(c->mov[j]) && !attackingForbidden(c->mov[j], c)) {
|
|
addMessage(XLAT("%The1 eats %the2!", c->monst, c->mov[j]->monst));
|
|
ncg = 1; gmov[0] = c->mov[j];
|
|
break;
|
|
}
|
|
if(c->mov[j] && passable(c->mov[j], c, false, false) && !cellUnstable(c->mov[j]) && c->mov[j] != cwt.c) {
|
|
if(c->mov[j]->pathdist < c->pathdist) gmov[ncg++] = c->mov[j]; else bmov[ncb++] = c->mov[j];
|
|
}
|
|
}
|
|
|
|
if(ncg == 0 && ncb == 0) {
|
|
int spices = 0;
|
|
if(id) {
|
|
addMessage(XLAT("Cthulhu withdraws his tentacle!"));
|
|
kills[moTentacle]++;
|
|
c->monst = moTentacleEscaping;
|
|
moveWorm(c);
|
|
}
|
|
else {
|
|
addMessage(XLAT("The sandworm explodes in a cloud of Spice!"));
|
|
kills[moWorm]++;
|
|
spices = 3;
|
|
}
|
|
eItem loc = treasureType(c->land);
|
|
while(c->monst == moWorm || c->monst == moWormtail || c->monst == moTentacle || c->monst == moTentacletail) {
|
|
// if(!id)
|
|
explodeAround(c);
|
|
if(spices > 0 && c->land == laDesert) {
|
|
if(notDippingForExtra(itSpice, loc))
|
|
c->item = itSpice;
|
|
spices--;
|
|
}
|
|
c->monst = moNone;
|
|
if(c->mondir != NODIR) c = c->mov[c->mondir];
|
|
}
|
|
return;
|
|
}
|
|
|
|
cell* goal;
|
|
if(ncg) goal = gmov[rand() % ncg];
|
|
else goal = bmov[rand() % ncb];
|
|
|
|
for(int j=0; j<c->type; j++) if(c->mov[j] == goal) {
|
|
goal->monst = eMonster(moWormwait + id);
|
|
|
|
c->monst = eMonster(moWormtail + id);
|
|
goal->mondir = c->spn[j];
|
|
|
|
if(id) break;
|
|
|
|
cell *c2 = c, *c3 = c2;
|
|
for(int a=0; a<15; a++)
|
|
if(c2->monst == moWormtail) {
|
|
if(c2->mondir == NODIR) return;
|
|
c3 = c2, c2 = c3->mov[c2->mondir];
|
|
}
|
|
|
|
if(c2->monst == moWormtail) c2->monst = moNone, c3->mondir = NODIR;
|
|
}
|
|
|
|
}
|
|
|
|
void ivynext(cell *c) {
|
|
cellwalker cw(c, c->mondir);
|
|
cw.c->monst = moIvyWait;
|
|
bool findleaf = false;
|
|
while(true) {
|
|
cwspin(cw, 1);
|
|
if(cw.spin == signed(cw.c->mondir)) {
|
|
if(findleaf) {
|
|
cw.c->monst = moIvyHead; break;
|
|
}
|
|
cw.c->monst = moIvyWait;
|
|
cwstep(cw);
|
|
continue;
|
|
}
|
|
cwstep(cw);
|
|
if(cw.c->monst == moIvyWait && signed(cw.c->mondir) == cw.spin) {
|
|
cw.c->monst = moIvyBranch;
|
|
findleaf = true; continue;
|
|
}
|
|
cwstep(cw);
|
|
}
|
|
}
|
|
|
|
// this removes Ivy, but also potentially causes Vines to grow
|
|
void removeIvy(cell *c) {
|
|
c->monst = moNone;
|
|
for(int i=0; i<c->type; i++)
|
|
// note that semi-vines don't count
|
|
if(c->mov[i]->wall == waVinePlant) {
|
|
destroyHalfvine(c);
|
|
c->wall = waVinePlant;
|
|
}
|
|
}
|
|
|
|
void moveivy() {
|
|
for(int i=0; i<size(ivies); i++) {
|
|
cell *c = ivies[i];
|
|
cell *co = c;
|
|
if(c->monst != moIvyHead) continue;
|
|
ivynext(c);
|
|
|
|
cell *mto = NULL;
|
|
int pd = c->pathdist;
|
|
int sp = 0;
|
|
|
|
while(c->monst != moIvyRoot) {
|
|
for(int j=0; j<c->type; j++) {
|
|
if(c->mov[j] && isFriendly(c->mov[j])) {
|
|
addMessage(XLAT("The ivy destroys %the1!", c->mov[j]->monst));
|
|
c->mov[j]->monst = moNone;
|
|
continue;
|
|
}
|
|
if(c->mov[j] && signed(c->mov[j]->pathdist) < pd && passable(c->mov[j], c, false, false))
|
|
mto = c->mov[j], pd = mto->pathdist, sp = c->spn[j];
|
|
}
|
|
c = c->mov[c->mondir];
|
|
}
|
|
|
|
if(mto && mto->cpdist) {
|
|
mto->monst = moIvyWait, mto->mondir = sp;
|
|
// if this is the only branch, we want to move the head immediately to mto instead
|
|
if(mto->mov[mto->mondir]->monst == moIvyHead) {
|
|
mto->monst = moIvyHead; co->monst = moIvyBranch;
|
|
}
|
|
}
|
|
else if(co->mov[co->mondir]->monst != moIvyRoot) {
|
|
// shrink useless branches, but do not remove them completely (at the root)
|
|
if(co->monst == moIvyHead) co->mov[co->mondir]->monst = moIvyHead;
|
|
removeIvy(co);
|
|
}
|
|
}
|
|
}
|
|
|
|
// move slimes, and also seeps
|
|
|
|
int sval = 1;
|
|
vector<cell*> slimedfs;
|
|
|
|
bool isSlimeMover(cell *c) {
|
|
return
|
|
c->monst == moSlime || c->monst == moSeep || c->monst == moShark ||
|
|
c->monst == moVineSpirit;
|
|
}
|
|
|
|
void slimevisit(cell *c, cell *from, int u) {
|
|
if(!c) return;
|
|
if(eq(c->tmp, sval)) return;
|
|
if(c->wall != waCavewall && c->wall != waLake && c->wall != waDeadwall &&
|
|
c->wall != waFloorA && c->wall != waFloorB && c->wall != waVinePlant &&
|
|
c->wall != waVineHalfB && c->wall != waVineHalfA
|
|
) return;
|
|
bool hv = false;
|
|
// cavewalls/deadwalls are the same for seeps
|
|
if(c->wall == waCavewall && from->wall == waDeadwall) hv++;
|
|
if(c->wall == waDeadwall && from->wall == waCavewall) hv++;
|
|
// only travel to halfvines correctly
|
|
if(cellHalfvine(c) && from != cwt.c) {
|
|
int i=0;
|
|
for(int t=0; t<c->type; t++) if(c->mov[t] && c->mov[t]->wall == c->wall) i=t;
|
|
int z = i-u; if(z<0) z=-z; z%=6;
|
|
if(z>1) return;
|
|
hv=true;
|
|
}
|
|
// only travel from halfvines correctly
|
|
if(cellHalfvine(from) && from != cwt.c) {
|
|
int i=0;
|
|
for(int t=0; t<from->type; t++) if(from->mov[t] && from->mov[t]->wall == from->wall) i=t;
|
|
int z = i-c->spn[u]; if(z<0) z=-z; z%=6;
|
|
if(z>1) return;
|
|
hv=true;
|
|
}
|
|
if(c->wall != from->wall && from != cwt.c && !hv) return;
|
|
if(c->item) return;
|
|
// if(c->wall == waThumper || c->wall == waBonfire) return;
|
|
c->tmp = sval;
|
|
if(size(slimedfs) < 1000) slimedfs.push_back(c), reachedfrom.push_back(u);
|
|
if(!isWorm(c) && !isIvy(c) && !isMimic(c))
|
|
for(int i=0; i<c->type; i++) if(c->mov[i] == from)
|
|
c->mondir = i;
|
|
if(isSlimeMover(c)) {
|
|
|
|
for(int j=0; j<c->type; j++)
|
|
if(c->mov[j] && isFriendlyOrBug(c->mov[j])) {
|
|
// XLATC slime/seep/shark
|
|
addMessage(XLAT("%The1 eats %the2!", c->monst, c->mov[j]->monst));
|
|
c->mov[j]->monst = moNone;
|
|
return;
|
|
}
|
|
|
|
if(from->cpdist == 0 || from->monst) return;
|
|
|
|
from->monst = c->monst, c->monst = moNone;
|
|
}
|
|
}
|
|
|
|
void moveslimes() {
|
|
sval++;
|
|
slimedfs.clear(); reachedfrom.clear();
|
|
for(int i=0; i<size(targets); i++) {
|
|
slimedfs.push_back(targets[i]);
|
|
reachedfrom.push_back(rand() % targets[i]->type);
|
|
}
|
|
for(int i=0; i<size(slimedfs); i++) {
|
|
cell *c = slimedfs[i];
|
|
int j = reachedfrom[i];
|
|
for(int t=0; t<c->type; t++) {
|
|
int u = (j+t) % c->type;
|
|
slimevisit(c->mov[u], c, c->spn[u]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// move eagles
|
|
|
|
vector<cell*> eagledfs;
|
|
|
|
void eaglevisit(cell *c, cell *from, int id) {
|
|
if(!c) return;
|
|
if(eq(c->tmp, sval)) return;
|
|
if(c->monst == moEagle) {
|
|
if(id == 1) for(int j=0; j<c->type; j++)
|
|
if(c->mov[j] && isFriendlyOrBug(c->mov[j]) && !attackingForbidden(c->mov[j], c)) {
|
|
// XLATC eagle
|
|
addMessage(XLAT("%The1 claws %the2!", c->monst, c->mov[j]->monst));
|
|
c->mov[j]->monst = moNone;
|
|
return;
|
|
}
|
|
|
|
if(from->cpdist == 0 || from->monst) return;
|
|
|
|
from->monst = c->monst, c->monst = moNone;
|
|
}
|
|
if(c->wall == waThumper || isFire(c)) return;
|
|
c->tmp = sval;
|
|
if(!eaglepassable(c)) return;
|
|
if(size(eagledfs) < 1000) eagledfs.push_back(c);
|
|
}
|
|
|
|
void moveeagles(int id) {
|
|
if(invismove) return;
|
|
sval++;
|
|
eagledfs.clear();
|
|
for(int i=0; i<size(targets); i++)
|
|
eagledfs.push_back(targets[i]);
|
|
//eagledfs.push_back(cwt.c);
|
|
for(int i=0; i<size(eagledfs); i++) {
|
|
cell *c = eagledfs[i];
|
|
for(int t=0; t<c->type; t++)
|
|
eaglevisit(c->mov[t], c, id);
|
|
}
|
|
}
|
|
|
|
vector<cell*> earthdfs;
|
|
|
|
bool earthpassable(cell *c, cell *from) {
|
|
// cannot go through Living Caves...
|
|
if(c->wall == waCavefloor) return false;
|
|
// but can dig through...
|
|
if(c->wall == waDeadwall || c->wall == waDune)
|
|
return true;
|
|
return passable(c, from, true, false);
|
|
}
|
|
|
|
void earthMove(cell *from, int dir) {
|
|
cell *c2 = from->mov[dir];
|
|
int d = from->spn[dir];
|
|
earthWall(from);
|
|
if(c2) for(int u=2; u<=c2->type-2; u++) {
|
|
cell *c3 = c2->mov[(d + u)% c2->type];
|
|
earthFloor(c3);
|
|
}
|
|
}
|
|
|
|
void earthvisit(cell *c, cell *from, int d) {
|
|
if(!c) return;
|
|
if(eq(c->tmp, sval)) return;
|
|
|
|
if(!earthpassable(c, from)) return;
|
|
|
|
if(c->monst == moEarthElemental) {
|
|
// note: move from 'c' to 'from'!
|
|
for(int j=0; j<c->type; j++)
|
|
if(c->mov[j] && isFriendlyOrBug(c->mov[j]) && !attackingForbidden(c->mov[j], c)) {
|
|
// XLATC eagle
|
|
addMessage(XLAT("%The1 punches %the2!", c->monst, c->mov[j]->monst));
|
|
c->mov[j]->monst = moNone;
|
|
return;
|
|
}
|
|
|
|
if(!passable(from, c, false, false)) {
|
|
earthFloor(from);
|
|
return;
|
|
}
|
|
if(from->cpdist == 0 || from->monst) return;
|
|
|
|
earthMove(c, from->spn[d]);
|
|
|
|
from->monst = c->monst, c->monst = moNone;
|
|
}
|
|
if(c->wall == waThumper || isFire(c)) return;
|
|
c->tmp = sval;
|
|
if(size(earthdfs) < 1000) earthdfs.push_back(c);
|
|
}
|
|
|
|
void moveearth() {
|
|
if(invismove) return;
|
|
sval++;
|
|
earthdfs.clear();
|
|
for(int i=0; i<size(targets); i++)
|
|
earthdfs.push_back(targets[i]);
|
|
for(int i=0; i<size(earthdfs); i++) {
|
|
cell *c = earthdfs[i];
|
|
for(int t=0; t<c->type; t++)
|
|
earthvisit(c->mov[t], c, t);
|
|
}
|
|
}
|
|
|
|
vector<cell*> leaderdfs;
|
|
|
|
bool canPushStatueOn(cell *c) {
|
|
return passable(c, NULL, true, false);
|
|
}
|
|
|
|
// note: move from 'c' to 'from'!
|
|
void leadervisit(cell *c, cell *from, int d) {
|
|
if(!c) return;
|
|
if(eq(c->tmp, sval)) return;
|
|
|
|
if(c->monst == moCultistLeader) {
|
|
// he cannot push big statues into bonfires, just like the player
|
|
if(c->wall == waBonfire && from->wall == waBigStatue)
|
|
return;
|
|
// note: move from 'c' to 'from'!
|
|
for(int j=0; j<c->type; j++)
|
|
if(c->mov[j] && isFriendlyOrBug(c->mov[j]) && !attackingForbidden(c->mov[j], c)) {
|
|
// XLATC eagle
|
|
addMessage(XLAT("%The1 punches %the2!", c->monst, c->mov[j]->monst));
|
|
c->mov[j]->monst = moNone;
|
|
return;
|
|
}
|
|
|
|
if(from->wall != waBigStatue && !passable(from, c, false, false)) {
|
|
return;
|
|
}
|
|
if(from->cpdist == 0 || from->monst) return;
|
|
|
|
if(from->wall == waBigStatue) {
|
|
from->wall = c->wall;
|
|
c->wall = waBigStatue;
|
|
}
|
|
// earthMove(c, from->spn[d]);
|
|
|
|
from->monst = c->monst, c->monst = moNone;
|
|
}
|
|
|
|
else if(c->wall == waBigStatue ? !canPushStatueOn(from) : !passable(c, from, false, false))
|
|
return;
|
|
|
|
if(c->wall == waThumper || isFire(c)) return;
|
|
c->tmp = sval;
|
|
|
|
if(size(leaderdfs) < 1000) leaderdfs.push_back(c);
|
|
}
|
|
|
|
void moveleader() {
|
|
if(invismove) return;
|
|
sval++;
|
|
leaderdfs.clear();
|
|
for(int i=0; i<size(targets); i++)
|
|
leaderdfs.push_back(targets[i]);
|
|
//leaderdfs.push_back(cwt.c);
|
|
for(int i=0; i<size(leaderdfs); i++) {
|
|
cell *c = leaderdfs[i];
|
|
for(int t=0; t<c->type; t++)
|
|
leadervisit(c->mov[t], c, t);
|
|
}
|
|
}
|
|
|
|
#define SHSIZE 16
|
|
|
|
cell *shpos[SHSIZE];
|
|
int cshpos = 0;
|
|
|
|
void clearshadow() {
|
|
for(int i=0; i<SHSIZE; i++) shpos[i] = NULL;
|
|
}
|
|
|
|
void moveshadow() {
|
|
if(shpos[cshpos] && shpos[cshpos]->monst == moShadow)
|
|
shpos[cshpos]->monst = moNone;
|
|
shpos[cshpos] = cwt.c;
|
|
cshpos = (cshpos+1) % SHSIZE;
|
|
if(shpos[cshpos] && shpos[cshpos]->monst == moNone && shpos[cshpos]->cpdist && shpos[cshpos]->land == laGraveyard)
|
|
shpos[cshpos]->monst = moShadow;
|
|
}
|
|
|
|
// from-to
|
|
bool ghostmove(cell* c1, cell* c2) {
|
|
if(c1->monst == moGhost || c1->monst == moWitchGhost) return true;
|
|
if(c1->monst == moGreaterShark) return c2->wall == waLake;
|
|
if(c1->monst == moWitchWinter)
|
|
return c2->wall == waBonfire || passable(c2, c1, false, false);
|
|
return false;
|
|
}
|
|
|
|
void moveghosts() {
|
|
|
|
if(invismove) return;
|
|
for(int d=0; d<8; d++) movesofgood[d].clear();
|
|
|
|
for(int i=0; i<size(ghosts); i++) {
|
|
cell *c = ghosts[i];
|
|
if(isGhostMover(c->monst) && c->cpdist > 1) {
|
|
int goodmoves = 0;
|
|
|
|
for(int k=0; k<c->type; k++) if(c->mov[k] && !c->mov[k]->monst && c->mov[k]->cpdist < c->cpdist)
|
|
if(ghostmove(c, c->mov[k]))
|
|
goodmoves++;
|
|
|
|
movesofgood[goodmoves].push_back(c);
|
|
}
|
|
}
|
|
|
|
for(int d=0; d<8; d++) for(int i=0; i<size(movesofgood[d]); i++) {
|
|
cell *c = movesofgood[d][i];
|
|
|
|
if(isGhostMover(c->monst) && c->cpdist > 1) {
|
|
|
|
int mdir[7];
|
|
|
|
for(int j=0; j<c->type; j++)
|
|
if(c->mov[j] && isFriendlyOrBug(c->mov[j])) {
|
|
// XLATC ghost/greater shark
|
|
addMessage(XLAT("%The1 scares %the2!", c->monst, c->mov[j]->monst));
|
|
c->mov[j]->monst = moNone;
|
|
return;
|
|
}
|
|
|
|
int qmpos = 0;
|
|
for(int k=0; k<c->type; k++) if(c->mov[k] && !c->mov[k]->monst && c->mov[k]->cpdist < c->cpdist)
|
|
if(ghostmove(c, c->mov[k]))
|
|
mdir[qmpos++] = k;
|
|
if(!qmpos) continue;
|
|
int d = mdir[rand() % qmpos];
|
|
cell *c2 = c->mov[d];
|
|
c2->monst = c->monst; c->monst = moNone;
|
|
}
|
|
}
|
|
}
|
|
|
|
int lastdouble = -3;
|
|
|
|
void stabbingAttack(cell *mf, cell *mt, eMonster who = moNone) {
|
|
int numsh = 0, numflail = 0, numlance = 0;
|
|
for(int t=0; t<mf->type; t++) {
|
|
cell *c = mf->mov[t];
|
|
if(c->monst == moHedge || (!who && items[itOrbThorns] && c->monst && isKillable(c))) {
|
|
for(int u=0; u<c->type; u++) {
|
|
if(c->mov[u] == mt) {
|
|
if(who)
|
|
addMessage(XLAT("%The1 stabs %the2.", who, c->monst));
|
|
else
|
|
addMessage(XLAT("You stab %the1.", c->monst));
|
|
int k = tkills();
|
|
killMonster(c);
|
|
if(tkills() > k) numsh++;
|
|
}
|
|
}
|
|
}
|
|
if(c->monst == moFlailer) {
|
|
bool away = true;
|
|
for(int u=0; u<c->type; u++) if(c->mov[u] == mt) away = false;
|
|
if(away) {
|
|
if(who)
|
|
addMessage(XLAT("%The1 tricks %the2.", who, c->monst));
|
|
else
|
|
addMessage(XLAT("You trick %the1.", c->monst));
|
|
int k = tkills();
|
|
killMonster(c);
|
|
if(tkills() > k) numflail++;
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int t=0; t<mt->type; t++) {
|
|
cell *c = mt->mov[t];
|
|
if(c->monst == moLancer) {
|
|
if(who)
|
|
addMessage(XLAT("%The1 tricks %the2.", who, c->monst));
|
|
else
|
|
addMessage(XLAT("You trick %the1.", c->monst));
|
|
int k = tkills();
|
|
killMonster(c);
|
|
if(tkills() > k) numlance++;
|
|
}
|
|
}
|
|
|
|
if(numsh) achievement_count("STAB", numsh, 0);
|
|
|
|
if(numlance && numflail && numsh) achievement_gain("MELEE3");
|
|
|
|
if(numlance + numflail + numsh >= 5) achievement_gain("MELEE5");
|
|
|
|
if(numsh == 2) {
|
|
if(lastdouble == turncount-1) achievement_count("STAB", 4, 0);
|
|
lastdouble = turncount;
|
|
}
|
|
}
|
|
|
|
void movegolems() {
|
|
int qg = 0;
|
|
for(int i=0; i<size(golems); i++) {
|
|
cell *c = golems[i];
|
|
eMonster m = c->monst;
|
|
if(m == moGolem || m == moKnight) {
|
|
if(m == moGolem) qg++;
|
|
int bestv = 100, bq = 0, bdirs[7];
|
|
for(int k=0; k<c->type; k++) if(c->mov[k]) {
|
|
int val;
|
|
if(c->mov[k] == cwt.c) val = 0;
|
|
else if(isActiveEnemy(c->mov[k], NULL) && isKillable(c->mov[k]) &&
|
|
!attackingForbidden(c->mov[k], c))
|
|
val = 12000;
|
|
else if(isInactiveEnemy(c->mov[k]) && isKillable(c->mov[k]) &&
|
|
!attackingForbidden(c->mov[k], c))
|
|
val = 10000;
|
|
else if(isIvy(c->mov[k]) && !attackingForbidden(c->mov[k], c)) val = 8000;
|
|
else if(monstersnear(c->mov[k], NULL, false)) val = 0;
|
|
else if(passable(c->mov[k], c, false, false)) val = 4000;
|
|
else val = 0;
|
|
if(c->monst == moGolem)
|
|
val -= c->mov[k]->cpdist;
|
|
if(c->monst == moKnight && c->mov[k]->master->alt)
|
|
val -= celldistAlt(c->mov[k]);
|
|
if(val > bestv) bestv = val, bq = 0;
|
|
if(val == bestv) bdirs[bq++] = k;
|
|
}
|
|
if(bestv <= 100) continue;
|
|
int dir = bdirs[rand() % bq];
|
|
cell *c2 = c->mov[dir];
|
|
if(c2->monst) {
|
|
addMessage(XLAT("%The1 destroys %the2!", c->monst, c2->monst));
|
|
killMonster(c2);
|
|
}
|
|
else {
|
|
stabbingAttack(c, c2, m);
|
|
c2->monst = m == moGolem ? moGolemMoved : moKnightMoved;
|
|
c2->mondir = c->spn[dir];
|
|
c->monst = moNone;
|
|
}
|
|
}
|
|
}
|
|
achievement_count("GOLEM", qg, 0);
|
|
}
|
|
|
|
bool wchance(int a, int of) {
|
|
of *= 10;
|
|
a += items[itOrbYendor] * 5 + items[itHolyGrail] + 1;
|
|
if(cwt.c->land == laCrossroads) a+= items[itHyperstone] * 10;
|
|
for(int i=0; i<ittypes; i++) if(itemclass(eItem(i)) == IC_TREASURE)
|
|
a = max(a, (items[i]-10) / 10);
|
|
return rand() % (a+of) < a;
|
|
}
|
|
|
|
void wandering() {
|
|
int t = turncount - lastexplore;
|
|
int seepcount = 0;
|
|
if(t > 40) seepcount = (t-40 + rand() % 20) / 20;
|
|
int ghostcount = 0;
|
|
if(t > 80) ghostcount = (t-80 + rand() % 20) / 20;
|
|
|
|
while(first7 < size(dcal)) {
|
|
int i = first7 + rand() % (size(dcal) - first7);
|
|
cell *c = dcal[i];
|
|
|
|
// wandering seeps & ghosts
|
|
if(seepcount && c->wall == waCavewall && !c->monst && eq(c->tmp, sval)) {
|
|
c->monst = moSeep;
|
|
seepcount--;
|
|
continue;
|
|
}
|
|
|
|
if(ghostcount && !c->monst && cwt.c->type != laCaves) {
|
|
c->monst = moGhost;
|
|
ghostcount--;
|
|
continue;
|
|
}
|
|
|
|
if((c->wall == waCavewall || c->wall == waDeadwall) && !c->monst && eq(c->tmp, sval) &&
|
|
wchance(items[treasureType(c->land)], 10)) {
|
|
c->monst = moSeep;
|
|
continue;
|
|
}
|
|
|
|
else if(c->monst || c->pathdist == INFD) break;
|
|
|
|
else if(c->land == laIce && wchance(items[itDiamond], 10))
|
|
c->monst = rand() % 2 ? moWolf : moYeti;
|
|
|
|
else if(c->land == laDesert && wchance(items[itSpice], 10))
|
|
c->monst = rand() % 10 ? moDesertman : moWorm;
|
|
|
|
else if(c->land == laCaves && wchance(items[itGold], 5))
|
|
c->monst = rand() % 3 ? moTroll : moGoblin;
|
|
|
|
else if(c->land == laDeadCaves && wchance(items[itSilver], 5))
|
|
c->monst = rand() % 20 ? (rand() % 3 ? moDarkTroll : moGoblin) : moEarthElemental;
|
|
|
|
else if(c->land == laJungle && wchance(items[itRuby], 40))
|
|
c->monst = rand() % 10 ? moMonkey : moEagle;
|
|
|
|
else if(c->land == laMirror && wchance(items[itShard], 15))
|
|
c->monst = rand() % 10 ? moRanger : moEagle;
|
|
|
|
else if(c->land == laHell && wchance(items[itHell], 20))
|
|
c->monst = rand() % 3 ? moLesser : moGreater;
|
|
|
|
else if(c->land == laRlyeh && wchance(items[itStatue], 15))
|
|
c->monst = rand() % 3 ? moPyroCultist :
|
|
(rand() % 40 < items[itStatue]-25) ? moCultistLeader : moCultist;
|
|
|
|
else if(c->land == laGraveyard && wchance(items[itBone], 15))
|
|
c->monst = rand() % 5 ? moGhost : moNecromancer;
|
|
|
|
else if(c->land == laDryForest && wchance(items[itFernFlower], 5))
|
|
c->monst = rand() % 5 ? moHedge : moFireFairy;
|
|
|
|
else if(c->land == laCocytus && wchance(items[itSapphire], 45))
|
|
c->monst = moCrystalSage;
|
|
|
|
else if(c->land == laAlchemist && wchance(items[itElixir], 3) && eq(c->tmp, sval) && c->item == itNone)
|
|
c->monst = moSlime;
|
|
|
|
else if(c->land == laFjord && wchance(items[itEmerald], 5)) {
|
|
static eMonster m[4] = {moHedge, moLancer, moMiner, moFlailer};
|
|
c->monst = m[rand() % 4];
|
|
}
|
|
|
|
else if(c->land == laWineyard && wchance(items[itWine], 10)) {
|
|
c->monst = moVineBeast;
|
|
}
|
|
|
|
else if(c->land == laPower && wchance(items[itPower], 10)) {
|
|
c->monst = eMonster(moWitch + rand() % NUMWITCH);
|
|
}
|
|
|
|
else if(c->land == laCamelot && rand() % 30 == 0 && celldistAltRelative(c) < 0) {
|
|
eMonster m[3] = { moHedge, moLancer, moFlailer };
|
|
c->monst = m[rand() % 3];
|
|
}
|
|
|
|
else if(c->land == laCrossroads && items[itHyperstone] && wchance(items[itHyperstone], 20)) {
|
|
// only interesting monsters here!
|
|
static eMonster m[12] = {
|
|
moWorm, moTroll, moEagle,
|
|
moLesser, moGreater, moPyroCultist, moGhost,
|
|
moFireFairy, moHedge,
|
|
moLancer, moFlailer, moVineBeast
|
|
};
|
|
c->monst = m[rand() % 9];
|
|
}
|
|
|
|
else break;
|
|
|
|
if(c->monst == moWorm) c->mondir = NODIR;
|
|
|
|
// laMotion -> no respawn!
|
|
}
|
|
}
|
|
|
|
void sageheat(cell *c, double v) {
|
|
c->heat += v;
|
|
if(c->wall == waFrozenLake && c->heat > .6) c->wall = waLake;
|
|
}
|
|
|
|
bool normalMover(eMonster m) {
|
|
return
|
|
m == moYeti || m == moRanger || m == moGoblin || m == moTroll || m == moDesertman ||
|
|
m == moMonkey || m == moZombie || m == moNecromancer || m == moCultist ||
|
|
m == moLesser || m == moGreater || m == moRunDog || m == moPyroCultist ||
|
|
m == moFireFairy || m == moCrystalSage || m == moHedge ||
|
|
m == moVineBeast || m == moLancer || m == moFlailer ||
|
|
m == moMiner || m == moDarkTroll ||
|
|
(cwt.c->land == laPower && (
|
|
(isWitch(m) && m != moWitchGhost && m != moWitchWinter) || m == moEvilGolem
|
|
));
|
|
}
|
|
|
|
struct buginfo_t {
|
|
cell *where;
|
|
short dist[BUGCOLORS];
|
|
};
|
|
|
|
vector<buginfo_t> buginfo;
|
|
|
|
vector<int> bugqueue[BUGCOLORS];
|
|
vector<int> bugqueue4[BUGCOLORS];
|
|
|
|
struct bugtomove_t {
|
|
int dist, moves, index;
|
|
bugtomove_t(int d, int m, int i) { dist=d; moves=m; index=i; }
|
|
};
|
|
|
|
bool operator < (const bugtomove_t& m1, const bugtomove_t& m2) {
|
|
if(m1.dist != m2.dist) return m1.dist < m2.dist;
|
|
if(m1.moves != m2.moves) return m1.moves < m2.moves;
|
|
return false;
|
|
}
|
|
|
|
vector<bugtomove_t> bugtomove;
|
|
vector<cell*> deadbug;
|
|
vector<cell*> bugcellq;
|
|
|
|
int bugcount[BUGCOLORS];
|
|
|
|
bool isBugEnemy(cell *c, int k) {
|
|
if(c == cwt.c && !invismove) return true;
|
|
if(!c->monst) return false;
|
|
if(c->monst == moBug0+k) return false;
|
|
if(isIvy(c)) return false;
|
|
return (isBug(c) || isKillableSomehow(c));
|
|
}
|
|
|
|
// list bugs and targets for each color
|
|
#define BUGINF 29999
|
|
|
|
void bugQueueInsert(int k, int i, int d) {
|
|
if(buginfo[i].dist[k] > d) {
|
|
if(buginfo[i].dist[k] != BUGINF) {
|
|
printf("%d -> %d\n", buginfo[i].dist[k], d);
|
|
}
|
|
buginfo[i].dist[k] = d;
|
|
bugqueue[k].push_back(i);
|
|
}
|
|
}
|
|
|
|
void bugcell(cell *c) {
|
|
if(isActiv(c) || !passable(c, NULL, true, false))
|
|
return;
|
|
short& i(c->tmp);
|
|
if(i >= 0 && i < size(buginfo) && buginfo[i].where == c)
|
|
return;
|
|
i = size(buginfo);
|
|
buginfo.resize(i+1);
|
|
buginfo_t& b(buginfo[i]);
|
|
b.where = c;
|
|
for(int k=0; k<BUGCOLORS; k++) {
|
|
b.dist[k] = BUGINF;
|
|
bool havebug = false, haveother = false;
|
|
for(int dir=0; dir<c->type; dir++) {
|
|
cell *c2 = c->mov[dir];
|
|
if(c2 && isBugEnemy(c2,k) && !attackingForbidden(c2, c)) {
|
|
if(isBug(c2)) havebug = true;
|
|
else haveother = true;
|
|
}
|
|
}
|
|
if(havebug) bugQueueInsert(k, i, 0);
|
|
else if(haveother) bugqueue4[k].push_back(i);
|
|
}
|
|
/*// bugs communicate if the distance is at most 2
|
|
// also all nearby cells are inserted to the buginfo structure
|
|
if(size(buginfo) < 30000) {
|
|
for(int dir=0; dir<c->type; dir++) {
|
|
cell *c2 = c->mov[dir];
|
|
if(c2) {
|
|
// if(isBug(c)) bugcellq.push_back(c2); => does not help...
|
|
for(int t=0; t<c2->type; t++)
|
|
if(c2->mov[t] && isBug(c2->mov[t]))
|
|
bugcellq.push_back(c2),
|
|
bugcellq.push_back(c2->mov[t]);
|
|
}
|
|
}
|
|
}*/
|
|
|
|
// use pheromones!
|
|
if(c->land == laHive && c->heat > 1) {
|
|
c->heat -= 1;
|
|
for(int dir=0; dir<c->type; dir++) {
|
|
cell *c2 = c->mov[dir];
|
|
if(c2) {
|
|
for(int t=0; t<c2->type; t++)
|
|
if(c2->mov[t])
|
|
bugcellq.push_back(c2),
|
|
bugcellq.push_back(c2->mov[t]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int last_d = -1;
|
|
|
|
void handleBugQueue(int k, int t) {
|
|
int i = bugqueue[k][t];
|
|
buginfo_t& b(buginfo[i]);
|
|
cell *c = b.where;
|
|
int d = b.dist[k];
|
|
last_d = d;
|
|
int goodmoves = 0;
|
|
for(int dir=0; dir<c->type; dir++) {
|
|
cell *c2 = c->mov[dir];
|
|
if(!c2) continue;
|
|
if(c2->tmp < 0 || c2->tmp >= size(buginfo)) continue;
|
|
if(!passable(c, c2, true, false)) continue;
|
|
int j = c2->tmp;
|
|
if(buginfo[j].where != c2) continue;
|
|
if(buginfo[j].dist[k] < d) goodmoves++;
|
|
bugQueueInsert(k, j, d+1);
|
|
}
|
|
if(isBug(c) && c->monst == moBug0+k) {
|
|
bugcount[c->monst - moBug0]++;
|
|
bugtomove.push_back(bugtomove_t(d,goodmoves,i));
|
|
}
|
|
}
|
|
|
|
#include <set>
|
|
|
|
void movebugs() {
|
|
buginfo.clear();
|
|
for(int k=0; k<BUGCOLORS; k++) bugqueue[k].clear();
|
|
for(int k=0; k<BUGCOLORS; k++) bugqueue4[k].clear();
|
|
for(int k=0; k<BUGCOLORS; k++) bugcount[k] = 0;
|
|
bugtomove.clear();
|
|
deadbug.clear();
|
|
|
|
int xdcs = size(dcal); for(int i=0; i<xdcs; i++) bugcell(dcal[i]);
|
|
for(int i=0; i<size(bugcellq); i++) bugcell(bugcellq[i]);
|
|
bugcellq.clear();
|
|
|
|
// printf("buginfo = %d\n", size(buginfo));
|
|
|
|
for(int k=0; k<BUGCOLORS; k++) {
|
|
int t = 0;
|
|
last_d = -1;
|
|
int invadist = 4 - (items[itRoyalJelly]+10) / 20;
|
|
if(invadist<0) invadist = 0;
|
|
for(; t<size(bugqueue[k]) && last_d < invadist-1; t++) handleBugQueue(k, t);
|
|
for(int u=0; u<size(bugqueue4[k]); u++)
|
|
bugQueueInsert(k, bugqueue4[k][u], invadist);
|
|
bugqueue4[k].clear();
|
|
for(; t<size(bugqueue[k]); t++) handleBugQueue(k, t);
|
|
}
|
|
|
|
for(int k=0; k<BUGCOLORS; k++) {
|
|
set<int> check;
|
|
for(int t=0; t<size(bugqueue[k]); t++) {
|
|
if(check.count(bugqueue[k][t])) {
|
|
printf("REPETITION! [%d]\n", t);
|
|
}
|
|
check.insert(bugqueue[k][t]);
|
|
}
|
|
}
|
|
|
|
random_shuffle(bugtomove.begin(), bugtomove.end());
|
|
sort(bugtomove.begin(), bugtomove.end());
|
|
|
|
int battlecount = 0;
|
|
for(int t=0; t<size(bugtomove); t++) {
|
|
bugtomove_t& bm(bugtomove[t]);
|
|
int i = bm.index;
|
|
|
|
buginfo_t& b(buginfo[i]);
|
|
cell *c = b.where;
|
|
if(!isBug(c)) continue;
|
|
eMonster m = c->monst;
|
|
int k = (m - moBug0) % BUGCOLORS;
|
|
int gmoves[8], q=0, bqual = -1;
|
|
|
|
for(int dir=0; dir<c->type; dir++) {
|
|
cell *c2 = c->mov[dir];
|
|
int qual = -10;
|
|
if(!c2) continue;
|
|
else if(isBugEnemy(c2, k) && c2->monst != moDeadBug)
|
|
qual = c2 != cwt.c ? 2 : -5;
|
|
else {
|
|
if(c2->monst) continue;
|
|
if(!passable(c2, c, false, false)) continue;
|
|
if(c2->tmp < 0 || c2->tmp >= size(buginfo)) continue;
|
|
if(buginfo[c2->tmp].where != c2) continue;
|
|
if(buginfo[c2->tmp].dist[k] < b.dist[k])
|
|
qual = 1;
|
|
else if(buginfo[c2->tmp].dist[k] == b.dist[k])
|
|
qual = 0;
|
|
}
|
|
// printf("%d->#%d %d: %d\n", i, dir, c2->tmp, qual);
|
|
if(qual > bqual) bqual = qual, q=0;
|
|
if(qual == bqual) gmoves[q++] = dir;
|
|
}
|
|
|
|
if(!q) { c->heat += 3; continue; }
|
|
int d = gmoves[rand() % q];
|
|
cell *c2 = c->mov[d];
|
|
if(c2->monst) {
|
|
eMonster killed = c2->monst;
|
|
if(isBug(killed)) battlecount++;
|
|
else addMessage(XLAT("%The1 fights with %the2!", c->monst, c2->monst));
|
|
killMonster(c2);
|
|
// killMonster(c);
|
|
if(isBug(killed)) {
|
|
c2->monst = moDeadBug, deadbug.push_back(c2);
|
|
bugcount[killed - moBug0]--;
|
|
}
|
|
// c->monst = moDeadBug, deadbug.push_back(c);
|
|
}
|
|
else {
|
|
c2->monst = c->monst;
|
|
c2->mondir = c->spn[d];
|
|
c->monst = moNone;
|
|
// pheromones!
|
|
if(c->heat < 90) c->heat += 5;
|
|
if(c2->heat < 90) c2->heat += 5;
|
|
// if(isHive(c2->land)) c2->land = eLand(laHive0+k);
|
|
/* if(c2->item == itRoyalJelly && !isQueen(m)) {
|
|
// advance!
|
|
c2->monst = eMonster(m+BUGCOLORS);
|
|
c2->item = itNone;
|
|
} */
|
|
}
|
|
}
|
|
|
|
// cleanup
|
|
for(int i=0; i<size(deadbug); i++) deadbug[i]->monst = moNone;
|
|
if(battlecount)
|
|
addMessage(XLAT("The Hyperbugs are fighting!"));
|
|
|
|
int maxbug = 0;
|
|
for(int k=0; k<BUGCOLORS; k++) if(bugcount[k] > maxbug) maxbug = bugcount[k];
|
|
|
|
achievement_count("BUG", maxbug, 0);
|
|
}
|
|
|
|
void bugcitycell(cell *c, int d) {
|
|
if(isActiv(c) || !passable(c, NULL, true, false)) return;
|
|
short& i = c->tmp;
|
|
if(i >= 0 && i < size(buginfo) && buginfo[i].where == c)
|
|
return;
|
|
i = size(buginfo);
|
|
buginfo_t b;
|
|
b.where = c;
|
|
b.dist[0] = d;
|
|
buginfo.push_back(b);
|
|
}
|
|
|
|
void createBugArmy(cell *c) {
|
|
int k = rand() % BUGCOLORS;
|
|
int minbugs = 50, maxbugs = 50;
|
|
int var = 5 + items[itRoyalJelly];
|
|
if(var>25) var=25;
|
|
// minbugs += 100; maxbugs += 100;
|
|
minbugs -= var; maxbugs += var;
|
|
maxbugs += items[itRoyalJelly];
|
|
int numbugs = minbugs + rand() % (maxbugs - minbugs + 1);
|
|
|
|
/* int i = items[itRoyalJelly];
|
|
int chance = 20 + 25 * i + 9000;
|
|
// i=0: 16%
|
|
// i=10: 73%
|
|
// i=50: 1270 vs 6000
|
|
eMonster m = eMonster(moBug0 + rand() % BUGCOLORS);
|
|
if(c->wall) return;
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = createMov(c,i);
|
|
if(rand() % (100+chance) < chance) {
|
|
if(!c2->wall) c2->monst = m;
|
|
for(int j=2; j<=c2->type-2; j++) {
|
|
int jj = (j+c->spn[i]) % c2->type;
|
|
cell *c3 = createMov(c2, jj);
|
|
if(rand() % (6000+chance) < chance && !c3->wall)
|
|
c3->monst = m;
|
|
}
|
|
}
|
|
}
|
|
c->monst = eMonster(m + BUGCOLORS); */
|
|
|
|
int gdir = -1;
|
|
for(int i=0; i<c->type; i++) {
|
|
if(c->mov[i] && c->mov[i]->mpdist < c->mpdist) gdir = i;
|
|
}
|
|
if(!gdir) return;
|
|
cellwalker bf(c, gdir);
|
|
for(int i=0; i<7; i++) {
|
|
if(bf.c->type == 6)
|
|
cwspin(bf, 3);
|
|
else
|
|
cwspin(bf, 3 + rand() % 2);
|
|
cwstep(bf);
|
|
}
|
|
cell *citycenter = bf.c;
|
|
buginfo.clear();
|
|
|
|
// mark the area with BFS
|
|
bugcitycell(citycenter, 0);
|
|
for(int i=0; i<size(buginfo); i++) {
|
|
buginfo_t& b(buginfo[i]);
|
|
cell *c = b.where;
|
|
int d = b.dist[0];
|
|
// ERRORS!
|
|
if(c->land != laHive && c->land != laNone) return;
|
|
if(c->bardir != NODIR) return;
|
|
if(c->heat >= 100) return;
|
|
// bfs
|
|
if(d < 9) for(int t=0; t<c->type; t++)
|
|
bugcitycell(createMov(c,t), d+1);
|
|
}
|
|
|
|
// place everything
|
|
for(int i=0; i<size(buginfo); i++) {
|
|
buginfo_t& b(buginfo[i]);
|
|
cell *c = b.where;
|
|
int d = b.dist[0];
|
|
if(d <= 1 && c->wall == waNone)
|
|
c->item = itRoyalJelly;
|
|
c->bardir = NOBARRIERS;
|
|
if(d == 9 || d == 6 || d == 3)
|
|
c->barleft = eLand(d/3),
|
|
c->barright = eLand(k);
|
|
else
|
|
c->barleft = laNone;
|
|
if(numbugs && c->wall == waNone)
|
|
c->monst = eMonster(moBug0 + k), numbugs--;
|
|
c->land = laHive;
|
|
// prevent barriers
|
|
if(c->mpdist == INFD) c->mpdist = BUGLEV;
|
|
}
|
|
}
|
|
|
|
void moveFastMonsters() {
|
|
for(int i=0; i<size(pathqm); i++) {
|
|
cell *c = pathqm[i];
|
|
|
|
if(c->monst == moWitchSpeed) {
|
|
|
|
if(c->pathdist == 1 && c->monst != moGhost) {
|
|
if(items[itOrbShield]) continue;
|
|
addMessage(XLAT("%The1 is confused!", c->monst));
|
|
break;
|
|
}
|
|
|
|
int goodmoves = 0;
|
|
for(int t=0; t<c->type; t++) {
|
|
cell *c2 = c->mov[t];
|
|
if(c2 && c2->pathdist < c->pathdist)
|
|
goodmoves++;
|
|
}
|
|
movesofgood[goodmoves].push_back(c);
|
|
}
|
|
}
|
|
|
|
for(int d=0; d<8; d++) for(int i=0; i<size(movesofgood[d]); i++) {
|
|
cell *c = movesofgood[d][i];
|
|
if(c->monst != moNone)
|
|
moveNormal(c);
|
|
}
|
|
}
|
|
|
|
void activateFlashFrom(cell *cf);
|
|
|
|
vector<cell*> nonmovers;
|
|
|
|
bool sagefresh = true;
|
|
|
|
void considerEnemyMove(cell *c) {
|
|
eMonster m = c->monst;
|
|
|
|
if(isActiveEnemy(c, NULL)) {
|
|
|
|
if(c->pathdist == 1 && c->monst != moGhost) {
|
|
// c->iswall = true; c->ismon = false;
|
|
if(items[itOrbShield] || c->monst == moCrystalSage) return;
|
|
|
|
addMessage(XLAT("%The1 is confused!", m));
|
|
// playerdead = true;
|
|
return;
|
|
}
|
|
|
|
if(c->monst == moWitch && c->item == itOrbSpeed) {
|
|
addMessage(XLAT("%The1 picks up %the2!", moWitch, c->item));
|
|
c->monst = moWitchSpeed; c->item = itNone;
|
|
}
|
|
|
|
if(c->monst == moNecromancer) {
|
|
int gravenum = 0, zombienum = 0;
|
|
cell *gtab[8], *ztab[8];
|
|
for(int j=0; j<c->type; j++) if(c->mov[j]) {
|
|
if(c->mov[j]->wall == waFreshGrave) gtab[gravenum++] = c->mov[j];
|
|
if(passable(c->mov[j], c, false, false) && c->mov[j]->pathdist < c->pathdist)
|
|
ztab[zombienum++] = c->mov[j];
|
|
}
|
|
if(gravenum && zombienum) {
|
|
cell *gr = gtab[rand() % gravenum];
|
|
gr->wall = waAncientGrave;
|
|
gr->monst = moGhost;
|
|
ztab[rand() % zombienum]->monst = moZombie;
|
|
addMessage(XLAT("%The1 raises some undead!", c->monst));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(c->monst == moWolf) {
|
|
int bhd = NODIR;
|
|
ld besth = c->heat;
|
|
for(int j=0; j<c->type; j++) if(c->mov[j]->heat > besth && passable(c->mov[j], c, false, false))
|
|
besth = c->mov[j]->heat, bhd = j;
|
|
if(bhd != NODIR) {
|
|
// printf("wolf moved from %Lf (%p) to %Lf (%p)\n", c->heat, c, besth, c->mov[bhd]);
|
|
c->mov[bhd]->monst = moWolfMoved, c->monst = moNone;
|
|
}
|
|
}
|
|
|
|
else if(c->monst == moPyroCultist && c->cpdist <= 4 && cwt.c->wall == waNone && !cellUnstable(cwt.c)) {
|
|
addMessage(XLAT("%The1 throws fire at you!", c->monst));
|
|
if(itemBurns(cwt.c->item))
|
|
addMessage(XLAT("%The1 burns!", cwt.c->item)), cwt.c->item = itNone;
|
|
cwt.c->wall = waBonfire;
|
|
cwt.c->tmp = 20;
|
|
c->monst = moCultist;
|
|
}
|
|
|
|
else if(c->monst == moWitchFlash && flashWouldKill(c, true) && !flashWouldKill(c, false)) {
|
|
addMessage(XLAT("%The1 activates her Flash spell!", c->monst));
|
|
c->monst = moWitch;
|
|
activateFlashFrom(c);
|
|
}
|
|
|
|
else if(c->monst == moCrystalSage && c->cpdist <= 4 && isIcyLand(cwt.c)) {
|
|
// only one sage attacks
|
|
if(sagefresh) {
|
|
sagefresh = false;
|
|
if(sagephase == 0) {
|
|
addMessage(XLAT("%The1 shows you two fingers.", c->monst));
|
|
addMessage(XLAT("You wonder what does it mean?"));
|
|
}
|
|
else if(sagephase == 1) {
|
|
addMessage(XLAT("%The1 shows you a finger.", c->monst));
|
|
addMessage(XLAT("You think about possible meanings."));
|
|
}
|
|
else {
|
|
addMessage(XLAT("%The1 moves his finger downwards.", c->monst));
|
|
addMessage(XLAT("Your brain is steaming."));
|
|
}
|
|
sagephase++;
|
|
sageheat(cwt.c, .0);
|
|
for(int i=0; i<cwt.c->type; i++)
|
|
sageheat(cwt.c->mov[i], .3);
|
|
}
|
|
}
|
|
|
|
else if(normalMover(m)) {
|
|
int goodmoves = 0;
|
|
for(int t=0; t<c->type; t++) {
|
|
cell *c2 = c->mov[t];
|
|
if(c2 && c2->pathdist < c->pathdist)
|
|
goodmoves++;
|
|
}
|
|
movesofgood[goodmoves].push_back(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
void movemonsters() {
|
|
|
|
sagefresh = true;
|
|
turncount++;
|
|
|
|
DEBT("ghosts");
|
|
moveghosts();
|
|
|
|
DEBT("normal");
|
|
|
|
for(int d=0; d<8; d++) movesofgood[d].clear();
|
|
|
|
for(int i=0; i<size(pathqm); i++) {
|
|
cell *c = pathqm[i];
|
|
considerEnemyMove(c);
|
|
}
|
|
|
|
int dcs = size(dcal);
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(c->pathdist == INFD)
|
|
considerEnemyMove(c);
|
|
}
|
|
|
|
for(int d=0; d<8; d++) for(int i=0; i<size(movesofgood[d]); i++) {
|
|
cell *c = movesofgood[d][i];
|
|
if(normalMover(c->monst))
|
|
moveNormal(c);
|
|
}
|
|
|
|
if(sagefresh) sagephase = 0;
|
|
|
|
DEBT("worm");
|
|
int wrm = size(worms);
|
|
for(int i=0; i<wrm; i++) {
|
|
moveWorm(worms[i]);
|
|
}
|
|
|
|
DEBT("ivy");
|
|
moveivy();
|
|
DEBT("slimes");
|
|
moveslimes();
|
|
DEBT("eagles");
|
|
if(haveeagles) moveeagles(1);
|
|
if(haveeagles) moveeagles(2);
|
|
DEBT("earth");
|
|
if(haveearth) moveearth();
|
|
DEBT("leader");
|
|
if(haveleader) moveleader();
|
|
DEBT("bugs");
|
|
if(havebugs) movebugs();
|
|
|
|
DEBT("golems");
|
|
movegolems();
|
|
|
|
DEBT("fresh");
|
|
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);
|
|
if(c->monst == moGolemMoved) c->monst = moGolem;
|
|
if(c->monst == moKnightMoved) c->monst = moKnight;
|
|
if(c->monst == moSlimeNextTurn) c->monst = moSlime;
|
|
if(c->monst == moLesser) c->monst = moLesserM;
|
|
else if(c->monst == moLesserM) c->monst = moLesser;
|
|
if(c->monst == moGreater) c->monst = moGreaterM;
|
|
else if(c->monst == moGreaterM) c->monst = moGreater;
|
|
|
|
if(c->wall == waChasm) {
|
|
c->item = itNone;
|
|
if(c->monst && c->monst != moGhost && c->monst != moEagle) {
|
|
if(c->monst != moRunDog) achievement_gain("FALLDEATH1");
|
|
killMonster(c);
|
|
}
|
|
}
|
|
|
|
if(c->wall == waBonfire) {
|
|
if(c->monst && c->monst != moGhost && c->monst != moWitchWinter && c->monst != moWitchGhost) {
|
|
addMessage(XLAT("%The1 burns!", c->monst));
|
|
killMonster(c);
|
|
}
|
|
}
|
|
|
|
if(c->wall == waLake) {
|
|
c->item = itNone;
|
|
if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM)
|
|
c->monst = moGreaterShark;
|
|
if(c->monst && c->monst != moShark && c->monst != moGreaterShark &&
|
|
c->monst != moGhost && c->monst != moEagle) killMonster(c);
|
|
}
|
|
|
|
if(c->monst && cellUnstable(c) && c->monst != moGhost && c->monst != moEagle) {
|
|
c->wall = waChasm;
|
|
}
|
|
}
|
|
|
|
DEBT("shadow");
|
|
moveshadow();
|
|
|
|
DEBT("wandering");
|
|
wandering();
|
|
}
|
|
|
|
// move heat
|
|
|
|
vector<cell*> vinefires;
|
|
|
|
void heat() {
|
|
double rate = items[itOrbSpeed] ? .5 : 1;
|
|
int oldmelt = kills[0];
|
|
|
|
/* if(cwt.c->heat > .5) cwt.c->heat += .3;
|
|
if(cwt.c->heat > 1.) cwt.c->heat += .3;
|
|
if(cwt.c->heat > 1.4) cwt.c->heat += .5; */
|
|
if(isIcyLand(cwt.c))
|
|
cwt.c->heat += (items[itOrbWinter] ? -1.2 : 1.2) * rate;
|
|
|
|
vinefires.clear();
|
|
|
|
int dcs = size(dcal);
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(c->cpdist > 8) break;
|
|
if(isFire(c) && c->tmp > 0) {
|
|
if(c->land != laPower) useup(c);
|
|
if(c->wall == waBonfire) for(int i=0; i<c->type; i++) {
|
|
cell *c2 = c->mov[i];
|
|
if(c2 && c2->wall == waVinePlant)
|
|
vinefires.push_back(c2);
|
|
// both halfvines have to be near fire at once
|
|
if(c2 && cellHalfvine(c2) && c->mov[(i+1)%c->type]->wall == c2->wall)
|
|
vinefires.push_back(c2);
|
|
}
|
|
|
|
// two semifires are required to spread
|
|
if(c->wall == waPartialFire) for(int i=0; i<c->type; i++) {
|
|
cell *c2 = c->mov[i];
|
|
if(c2 && (c2->wall == waVinePlant)) {
|
|
for(int j=0; j<c2->type; j++) if(c2->mov[j] && c2->mov[j]->wall == waPartialFire &&
|
|
c2->mov[j] != c)
|
|
vinefires.push_back(c2);
|
|
}
|
|
}
|
|
}
|
|
if(!isIcyLand(c)) continue;
|
|
if(c->monst == moRanger) c->heat += 3 * rate;
|
|
if(c->monst == moDesertman) c->heat += 4 * rate;
|
|
if(c->monst == moMonkey) c->heat += rate;
|
|
if(c->wall == waDeadTroll) c->heat -= 2 * rate;
|
|
if(c->wall == waBigStatue) c->heat -= 3 * rate;
|
|
if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM)
|
|
c->heat += (c->land == laCocytus ? 1.5 : 10) * rate;
|
|
if(c->monst == moGreaterShark)
|
|
c->heat += 2 * rate;
|
|
if(c->monst == moCultist) c->heat += 3 * rate;
|
|
if(c->monst == moCultistLeader) c->heat += 4 * rate;
|
|
if(c->monst == moPyroCultist) c->heat += 6 * rate;
|
|
if(c->monst == moGhost) c->heat -= rate;
|
|
if(c->wall == waBonfire && c->tmp > 0) c->heat += 4 * rate;
|
|
|
|
ld hmod = 0;
|
|
|
|
for(int j=0; j<c->type; j++) if(c->mov[j]) {
|
|
if(!isIcyLand(c->mov[j])) {
|
|
// make sure that we can still enter Cocytus,
|
|
// it won't heat up right away even without Orb of Winter or Orb of Speed
|
|
if(c->mov[j] == cwt.c && (c->land == laIce || items[itOrbWinter]))
|
|
hmod += rate * (items[itOrbWinter] ? -1.2 : 1.2) / 4;
|
|
continue;
|
|
}
|
|
ld hdiff = c->mov[j]->heat - c->heat;
|
|
hdiff /= 10;
|
|
if(c->mov[j]->cpdist <= 8)
|
|
c->mov[j]->heat -= hdiff;
|
|
else
|
|
hdiff = -c->heat / 250;
|
|
hmod += hdiff;
|
|
}
|
|
|
|
c->heat += hmod * rate;
|
|
if(c->monst == moCrystalSage && c->heat >= SAGEMELT) {
|
|
addMessage(XLAT("%The1 melts away!", c->monst));
|
|
killMonster(c);
|
|
}
|
|
}
|
|
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(c->wall == waIcewall && c->heat > .4) c->wall = waNone, kills[0]++;
|
|
if(c->wall == waFrozenLake && c->heat > .6) c->wall = waLake, kills[0]++;
|
|
if(c->wall == waLake && c->heat < -.4 && c->monst != moGreaterShark) {
|
|
c->wall = waFrozenLake;
|
|
if(c->monst == moShark) {
|
|
addMessage(XLAT("%The1 is frozen!", c->monst));
|
|
killMonster(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int i=0; i<size(vinefires); i++) {
|
|
cell* c = vinefires[i];
|
|
if(c->wall == waVinePlant) {
|
|
if(c->monst == moVineBeast) killMonster(c);
|
|
c->wall = waBonfire, c->tmp = 6;
|
|
}
|
|
else if(cellHalfvine(c)) destroyHalfvine(c, waPartialFire);
|
|
}
|
|
|
|
if(kills[0] != oldmelt) bfs();
|
|
}
|
|
|
|
bool gardener = false;
|
|
|
|
void livecaves() {
|
|
int dcs = size(dcal);
|
|
|
|
vector<cell*> bringlife;
|
|
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(c->cpdist > 8) break;
|
|
|
|
if(c->wall != waCavefloor && c->wall != waCavewall) continue;
|
|
|
|
// if(c->wall == waThumper || c->wall == waBonfire) continue;
|
|
c->tmp = 0;
|
|
if(c->monst == moDarkTroll) c->monst = moTroll;
|
|
if(c->item || c->monst || c->cpdist == 0) continue;
|
|
for(int j=0; j<c->type; j++) if(c->mov[j]) {
|
|
if(c->mov[j]->wall == waDeadfloor) c->tmp++, bringlife.push_back(c->mov[j]);
|
|
else if(c->mov[j]->wall == waDeadwall || c->mov[j]->wall == waDeadfloor2) c->tmp--, bringlife.push_back(c->mov[j]);
|
|
else if(c->mov[j]->wall == waCavefloor) c->tmp++;
|
|
else if(c->mov[j]->wall == waCavewall) c->tmp--;
|
|
else if(c->mov[j]->wall == waDeadTroll) c->tmp -= 5;
|
|
else if(c->mov[j]->wall == waVinePlant) c->tmp--;
|
|
else if(c->mov[j]->wall != waBarrier) c->tmp += 5;
|
|
if(c->mov[j]->cpdist == 0 && items[itOrbDigging]) c->tmp+=100;
|
|
if(c->mov[j]->wall == waThumper && c->mov[j]->tmp > 0) c->tmp+=100;
|
|
if(c->mov[j]->wall == waBonfire) c->tmp+=100;
|
|
if(c->mov[j]->wall == waBigStatue) c->tmp-=100;
|
|
if(c->mov[j]->item) c->tmp+=2;
|
|
if(c->mov[j]->monst == moZombie) c->tmp += 10;
|
|
if(c->mov[j]->monst == moGhost) c->tmp += 10;
|
|
if(c->mov[j]->monst == moNecromancer) c->tmp += 10;
|
|
if(c->mov[j]->monst == moWormtail) c->tmp++;
|
|
if(c->mov[j]->monst == moTentacletail) c->tmp-=2;
|
|
if(isIvy(c->mov[j])) c->tmp--;
|
|
if(isDemon(c->mov[j])) c->tmp-=3;
|
|
// if(c->mov[j]->monst) c->tmp++;
|
|
// if(c->mov[j]->monst == moTroll) c->tmp -= 3;
|
|
}
|
|
}
|
|
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(c->cpdist > 8) break;
|
|
if(c->wall != waCavefloor && c->wall != waCavewall) continue;
|
|
// if(c->land != laCaves) continue;
|
|
// if(c->wall == waThumper || c->wall == waBonfire) continue;
|
|
|
|
if(c->tmp > 0) c->wall = waCavefloor;
|
|
if(c->tmp < 0) {
|
|
c->wall = waCavewall;
|
|
if(c->land != laCaves && c->land != laDeadCaves && c->land != laFjord && !gardener) {
|
|
gardener = true;
|
|
achievement_gain("GARDENER");
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int i=0; i<size(bringlife); i++) {
|
|
cell *c = bringlife[i];
|
|
if(!lifebrought) { lifebrought = true;
|
|
achievement_gain("LIFEBRINGER");
|
|
}
|
|
if(c->wall == waDeadfloor) c->wall = waCavefloor;
|
|
if(c->wall == waDeadfloor2) c->wall = waCavewall;
|
|
if(c->wall == waDeadwall) c->wall = waCavewall;
|
|
if(c->wall == waCavewall && c->item) c->wall = waCavefloor;
|
|
if(c->land == laDeadCaves) c->land = laCaves;
|
|
if(c->item == itSilver) c->item = itGold;
|
|
if(c->item == itGreenStone) c->item = itOrbLife;
|
|
if(c->monst == moEarthElemental) {
|
|
addMessage(XLAT("%The1 is destroyed by the forces of Life!", c->monst));
|
|
killMonster(c);
|
|
c->item = itOrbDigging;
|
|
}
|
|
}
|
|
}
|
|
|
|
void dryforest() {
|
|
int dcs = size(dcal);
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(c->cpdist > 8) break;
|
|
if(c->land != laDryForest) continue;
|
|
if(c->wall == waThumper) continue;
|
|
if(c->wall == waBonfire)
|
|
c->heat = 0;
|
|
|
|
for(int j=0; j<c->type; j++) if(c->mov[j]) {
|
|
if(c->mov[j]->wall == waBonfire) c->heat++;
|
|
}
|
|
}
|
|
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(c->cpdist > 8) break;
|
|
if(c->land != laDryForest) continue;
|
|
if(c->wall == waNone && c->heat >= 10)
|
|
burnMonstersAndItems(c, 50);
|
|
if((c->wall == waDryTree || c->wall == waWetTree || c->wall == waBonfire) && c->heat >= 1)
|
|
c->wall = waBonfire, c->tmp = 50;
|
|
}
|
|
|
|
/*
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
c->tmp = 0;
|
|
c->heat = 0;
|
|
if(c->cpdist > 8) break;
|
|
if(c->land != laDryForest) continue;
|
|
|
|
for(int j=0; j<c->type; j++) if(c->mov[j]) {
|
|
if(c->mov[j]->wall == waWetTree)
|
|
c->tmp++;
|
|
if(c->mov[j]->wall == waDryTree)
|
|
c->heat++;
|
|
}
|
|
}
|
|
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
int a = c->type - c->tmp - int(c->heat);
|
|
if(c->tmp > a && c->tmp > c->heat)
|
|
c->wall = waWetTree;
|
|
else if(c->heat > a && c->heat > c->tmp)
|
|
c->wall = waDryTree;
|
|
else if(a > c->heat && a > c->tmp)
|
|
c->wall = waNone;
|
|
} */
|
|
}
|
|
|
|
// mirror management
|
|
|
|
bool cellMirrorable(cell *c) {
|
|
return
|
|
c->wall == waNone || c->wall == waCavefloor || c->wall == waFloorA || c->wall == waFloorB ||
|
|
c->wall == waFrozenLake || c->wall == waDeadfloor || c->wall == waDeadfloor2;
|
|
}
|
|
|
|
void castLightningBolt(cellwalker lig);
|
|
|
|
void createMM(cellwalker& cw, eMonster type) {
|
|
if(type == moLightningBolt)
|
|
castLightningBolt(cw);
|
|
else if(cw.c->monst == moNone && cellMirrorable(cw.c) && cw.c != cwt.c) {
|
|
cw.c->monst = type;
|
|
cw.c->mondir = cw.spin;
|
|
}
|
|
}
|
|
|
|
void createMirrors(cell *c, int dir, eMonster type) {
|
|
cellwalker C(c, dir);
|
|
|
|
if(type == moMirror) type = moMirage;
|
|
else if(type == moMirage) type = moMirror;
|
|
|
|
for(int i=0; i<6; i++) {
|
|
cwstep(C);
|
|
if(C.c->type == 6) {
|
|
cwspin(C, i);
|
|
createMM(C, type);
|
|
cwspin(C, -i);
|
|
}
|
|
cwstep(C);
|
|
cwspin(C, 1);
|
|
}
|
|
}
|
|
|
|
void createMirages(cell *c, int dir, eMonster type) {
|
|
cellwalker C(c, dir);
|
|
for(int i=0; i<6; i++) {
|
|
cwstep(C);
|
|
if(C.c->type == 6) {
|
|
cwspin(C, 2);
|
|
cwstep(C);
|
|
cwspin(C, 4-i);
|
|
createMM(C, type);
|
|
cwspin(C, 6-4+i);
|
|
cwstep(C);
|
|
cwspin(C, 2);
|
|
cwstep(C);
|
|
cwspin(C, 2-i);
|
|
createMM(C, type);
|
|
cwspin(C, 6-2+i);
|
|
cwstep(C);
|
|
cwspin(C, 2);
|
|
}
|
|
cwstep(C);
|
|
cwspin(C, 1);
|
|
}
|
|
}
|
|
|
|
void spinmirrors(int d) {
|
|
|
|
for(int i=0; i<size(mirrors); i++) {
|
|
cell *c = mirrors[i];
|
|
if(c->monst == moMirror)
|
|
mirrors[i]->mondir = (mirrors[i]->mondir - d + 42) % mirrors[i]->type;
|
|
if(c->monst == moMirage)
|
|
mirrors[i]->mondir = (mirrors[i]->mondir + d + 42) % mirrors[i]->type;
|
|
}
|
|
|
|
}
|
|
|
|
void destroyMirrors() {
|
|
for(int i=0; i<size(mirrors); i++) {
|
|
cell *c = mirrors[i];
|
|
eMonster m = c->monst;
|
|
if(isMimic(m)) c->monst = moNone;
|
|
}
|
|
mirrors.clear();
|
|
}
|
|
|
|
void destroyStrayMirrors() {
|
|
for(int i=0; i<size(mirrors2); i++) {
|
|
cell *c = mirrors2[i];
|
|
if(c->cpdist > 7 && isMimic(c)) {
|
|
c->monst = moNone;
|
|
}
|
|
}
|
|
}
|
|
|
|
void gomirrors(bool go) {
|
|
int tk = tkills();
|
|
int nummirage = 0;
|
|
mirrors2.clear();
|
|
for(int i=0; i<size(mirrors); i++) {
|
|
cell *c = mirrors[i];
|
|
eMonster m = c->monst;
|
|
if(isMimic(m)) {
|
|
if(m == moMirage) nummirage++;
|
|
cell *c2 = c->mov[c->mondir];
|
|
if(c2 && c2->monst != moNone && !isMimic(c2) && isKillable(c2)) {
|
|
addMessage(XLAT("%The1 destroys %the2!", m, c2->monst));
|
|
killMonster(c2);
|
|
}
|
|
if(!go) continue;
|
|
c->monst = moNone;
|
|
if(!c2) continue;
|
|
stabbingAttack(c, c2, m);
|
|
if(!passable(c2, c, true, true)) continue;
|
|
if(isWorm(c2)) continue;
|
|
if(c2->monst == moGreater) {
|
|
c2->monst = moLesser; continue;
|
|
}
|
|
if(c2->monst == moGreaterM) {
|
|
c2->monst = moLesserM; continue;
|
|
}
|
|
if(c2 == cwt.c) {
|
|
addMessage(XLAT("You join %the1.", m));
|
|
continue;
|
|
}
|
|
if(isMimic(c2)) {
|
|
addMessage(XLAT("Two of your images crash and disappear!"));
|
|
c2->monst = moNone;
|
|
continue;
|
|
}
|
|
if(isIvy(c2)) {
|
|
// killIvy(c2);
|
|
continue;
|
|
}
|
|
c2->monst = m;
|
|
c2->mondir = c->spn[c->mondir];
|
|
mirrors2.push_back(c2);
|
|
}
|
|
}
|
|
for(int i=0; i<size(mirrors2); i++) {
|
|
cell *c = mirrors2[i];
|
|
eMonster m = c->monst;
|
|
if(c->wall == waMirror) {
|
|
addMessage(XLAT("%The1 breaks the mirror!", m));
|
|
createMirrors(c, c->mondir, m);
|
|
c->wall = waNone;
|
|
}
|
|
if(c->wall == waCloud) {
|
|
addMessage(XLAT("%The1 disperses the cloud!", m));
|
|
createMirages(c, c->mondir, m);
|
|
c->wall = waNone;
|
|
}
|
|
}
|
|
achievement_count("MIRRORKILL", tkills(), tk);
|
|
achievement_count("MIRAGE", nummirage, 0);
|
|
}
|
|
|
|
void reduceOrbPowers() {
|
|
if(items[itOrbLightning]) items[itOrbLightning]--;
|
|
if(items[itOrbSpeed]) items[itOrbSpeed]--;
|
|
if(items[itOrbFlash]) items[itOrbFlash]--;
|
|
if(items[itOrbShield]) items[itOrbShield]--;
|
|
if(items[itOrbWinter]) items[itOrbWinter]--;
|
|
if(items[itOrbFire]) items[itOrbFire]--;
|
|
if(items[itOrbIllusion]) items[itOrbIllusion]--;
|
|
if(items[itOrbDragon]) items[itOrbDragon]--;
|
|
if(items[itOrbPsi]) items[itOrbPsi]--;
|
|
if(items[itOrbInvis]) items[itOrbInvis]--;
|
|
if(items[itOrbGhost]) items[itOrbGhost]--;
|
|
if(items[itOrbDigging]) items[itOrbDigging]--;
|
|
if(items[itOrbTeleport]) items[itOrbTeleport]--;
|
|
if(items[itOrbSafety]) items[itOrbSafety]--;
|
|
if(items[itOrbThorns]) items[itOrbThorns]--;
|
|
}
|
|
|
|
void flashAlchemist(cell *c) {
|
|
if(c->wall == waFloorA || c->wall == waFloorB) {
|
|
if(cwt.c->wall == waFloorA || cwt.c->wall == waFloorB)
|
|
c->wall = cwt.c->wall;
|
|
else
|
|
c->wall = eWall(c->wall ^ waFloorB ^ waFloorA);
|
|
}
|
|
}
|
|
|
|
void flashCell(cell *c, bool msg) {
|
|
flashAlchemist(c);
|
|
if(msg && c->monst && !isWorm(c) && c->monst != moShadow)
|
|
addMessage(XLAT("%The1 is destroyed by the Flash.", c->monst));
|
|
killMonster(c);
|
|
if(isIcyLand(c))
|
|
c->heat += 2;
|
|
if(c->land == laDryForest)
|
|
c->heat += 2;
|
|
if(c->wall == waCavewall) c->wall = waCavefloor;
|
|
if(c->wall == waDeadTroll) c->wall = waCavefloor;
|
|
if(c->wall == waDeadfloor2) c->wall = waDeadfloor;
|
|
if(c->wall == waDeadwall) c->wall = waDeadfloor2;
|
|
if(c->wall == waMirror) c->wall = waNone;
|
|
if(c->wall == waCloud) c->wall = waNone;
|
|
if(c->wall == waDune) c->wall = waNone;
|
|
if(c->wall == waAncientGrave) c->wall = waNone;
|
|
if(c->wall == waFreshGrave) c->wall = waNone;
|
|
if(c->wall == waColumn) c->wall = waNone;
|
|
if(c->wall == waGlass) c->wall = waNone;
|
|
if(c->wall == waDryTree || c->wall == waWetTree) c->wall = waNone;
|
|
if(c->wall == waBigStatue) c->wall = waNone;
|
|
if(isActiv(c)) c->tmp = 77;
|
|
}
|
|
|
|
extern void drawFlash(cell* c);
|
|
|
|
void activateFlashFrom(cell *cf) {
|
|
drawFlash(cf);
|
|
for(int i=0; i<size(dcal); i++) {
|
|
cell *c = dcal[i];
|
|
if(c == cf) continue;
|
|
for(int t=0; t<c->type; t++)
|
|
for(int u=0; u<cf->type; u++)
|
|
if(c->mov[t] == cf->mov[u] && c->mov[t] != NULL) {
|
|
flashCell(c, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
void activateFlash() {
|
|
int tk = tkills();
|
|
drawFlash(cwt.c);
|
|
addMessage(XLAT("You activate the Flash spell!"));
|
|
items[itOrbFlash] = 0;
|
|
for(int i=0; i<size(dcal); i++) {
|
|
cell *c = dcal[i];
|
|
if(c->cpdist > 2) break;
|
|
flashCell(c, false);
|
|
}
|
|
achievement_count("FLASH", tkills(), tk);
|
|
}
|
|
|
|
bool barrierAt(cellwalker& c, int d) {
|
|
if(d >= 7) return true;
|
|
if(d <= -7) return true;
|
|
d = c.spin + d + 42;
|
|
d%=c.c->type;
|
|
if(!c.c->mov[d]) return true;
|
|
if(c.c->mov[d]->wall == waBarrier) return true;
|
|
return false;
|
|
}
|
|
|
|
void castLightningBolt(cellwalker lig) {
|
|
int bnc = 0;
|
|
while(true) {
|
|
// printf("at: %p i=%d d=%d\n", lig.c, i, lig.spin);
|
|
if(lig.c->mov[lig.spin] == 0) break;
|
|
cwstep(lig);
|
|
|
|
cell *c = lig.c;
|
|
|
|
flashAlchemist(c);
|
|
killMonster(c);
|
|
if(isIcyLand(c) || c->land == laDryForest) c->heat += 2;
|
|
c->ligon = 1;
|
|
|
|
bool brk = false, spin = false;
|
|
|
|
if(c->wall == waCavewall) c->wall = waCavefloor, brk = true;
|
|
if(c->wall == waDeadTroll) c->wall = waCavefloor, brk = true;
|
|
if(c->wall == waDeadfloor2)c->wall = waDeadfloor;
|
|
if(c->wall == waDeadwall) c->wall = waDeadfloor2, brk = true;
|
|
if(c->wall == waGlass) c->wall = waNone, spin = true;
|
|
if(c->wall == waDune) c->wall = waNone, brk = true;
|
|
if(c->wall == waIcewall) c->wall = waNone, brk = true;
|
|
if(c->wall == waAncientGrave) c->wall = waNone, spin = true;
|
|
if(c->wall == waFreshGrave) c->wall = waNone, spin = true;
|
|
if(c->wall == waBigStatue) c->wall = waNone, spin = true;
|
|
if(c->wall == waColumn) c->wall = waNone, spin = true;
|
|
if(isActiv(c)) c->tmp = 77;
|
|
if(c->wall == waDryTree || c->wall == waWetTree || c->wall == waVinePlant) {
|
|
burnMonstersAndItems(c, 4);
|
|
brk = true;
|
|
}
|
|
if(cellHalfvine(c) && c->mov[lig.spin] && c->wall == c->mov[lig.spin]->wall) {
|
|
destroyHalfvine(c, waPartialFire, 4);
|
|
brk = true;
|
|
}
|
|
|
|
if(c == cwt.c) {bnc++; if(bnc > 10) break; }
|
|
if(spin) cwspin(lig, rand() % lig.c->type);
|
|
|
|
if(brk) break;
|
|
|
|
if(c->wall == waBarrier || c->wall == waCamelot) {
|
|
int left = -1;
|
|
int right = 1;
|
|
while(barrierAt(lig, left)) left--;
|
|
while(barrierAt(lig, right)) right++;
|
|
cwspin(lig, -(right + left));
|
|
bnc++; if(bnc > 10) break;
|
|
}
|
|
else {
|
|
cwspin(lig, 3);
|
|
if(c->type == 7) cwspin(lig, rand() % 2);
|
|
}
|
|
|
|
if(c->wall == waCloud) {
|
|
c->wall = waNone;
|
|
createMirages(c, lig.spin, moLightningBolt);
|
|
}
|
|
|
|
if(c->wall == waMirror) {
|
|
c->wall = waNone;
|
|
createMirrors(c, lig.spin, moLightningBolt);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void activateLightning() {
|
|
int tk = tkills();
|
|
extern void drawLightning();
|
|
drawLightning();
|
|
addMessage(XLAT("You activate the Lightning spell!"));
|
|
items[itOrbLightning] = 0;
|
|
for(int i=0; i<cwt.c->type; i++)
|
|
castLightningBolt(cellwalker(cwt.c, i));
|
|
achievement_count("LIGHTNING", tkills(), tk);
|
|
}
|
|
|
|
// move the PC in direction d (or stay in place for d == -1)
|
|
|
|
bool canmove = true;
|
|
|
|
bool checkNeedMove(bool checkonly) {
|
|
if(items[itOrbGhost] > 1) return false;
|
|
if(cwt.c->wall == waRoundTable) {
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("It would be impolite to land on the table!"));
|
|
}
|
|
else if(cwt.c->wall == waLake) {
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("Ice below you is melting! RUN!"));
|
|
}
|
|
else if(isFire(cwt.c) && items[itOrbShield] < 2 && !items[itOrbWinter]) {
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("This spot will be burning soon! RUN!"));
|
|
}
|
|
else if(items[itOrbGhost] == 1 && !player_passable(cwt.c, NULL, false)) {
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("Your Aether power has expired! RUN!"));
|
|
}
|
|
else if(cwt.c->wall == waChasm) {
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("The floor has collapsed! RUN!"));
|
|
}
|
|
else return false;
|
|
return true;
|
|
}
|
|
|
|
#define YDIST 101
|
|
|
|
struct yendorinfo {
|
|
cell *path[YDIST];
|
|
bool found;
|
|
};
|
|
|
|
vector<yendorinfo> yi;
|
|
|
|
int yii = 0;
|
|
|
|
bool checkYendor(cell *yendor, bool checkonly) {
|
|
int byi = size(yi);
|
|
for(int i=0; i<size(yi); i++) if(yi[i].path[0] == yendor) byi = i;
|
|
if(byi < size(yi) && yi[byi].found) return true;
|
|
if(checkonly) return false;
|
|
if(byi == size(yi)) {
|
|
yendorinfo nyi;
|
|
nyi.path[0] = yendor;
|
|
|
|
cellwalker lig(yendor, rand() % yendor->type);
|
|
|
|
cell *prev = yendor;
|
|
|
|
for(int i=0; i<YDIST-1; i++) {
|
|
nyi.path[i] = lig.c;
|
|
|
|
prev = lig.c;
|
|
cwstep(lig);
|
|
cwspin(lig, 3);
|
|
if(lig.c->type == 7) cwspin(lig, rand() % 2);
|
|
|
|
setdist(lig.c, 10, prev);
|
|
setdist(lig.c, 9, prev);
|
|
}
|
|
|
|
nyi.path[YDIST-1] = lig.c;
|
|
nyi.found = false;
|
|
|
|
cell *key = lig.c;
|
|
|
|
for(int b=10; b>=7; b--) setdist(key, b, prev);
|
|
|
|
for(int i=-1; i<key->type; i++) {
|
|
cell *c2 = i >= 0 ? key->mov[i] : key;
|
|
c2->monst = moNone; c2->item = itNone;
|
|
if(!passable(c2, NULL, true, true)) {
|
|
if(c2->wall == waCavewall) c2->wall = waCavefloor;
|
|
else if(c2->wall == waDeadwall) c2->wall = waDeadfloor2;
|
|
else if(c2->wall == waLake) c2->wall = waFrozenLake;
|
|
else c2->wall = waNone;
|
|
}
|
|
}
|
|
key->item = itKey;
|
|
|
|
yi.push_back(nyi);
|
|
}
|
|
yii = byi;
|
|
addMessage(XLAT("You need to find the right Key to unlock this Orb of Yendor!"));
|
|
achievement_gain("YENDOR1");
|
|
return false;
|
|
}
|
|
|
|
int countMyGolems() {
|
|
int g=0, dcs = size(dcal);
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(c->monst == moGolem) g++;
|
|
}
|
|
return g;
|
|
}
|
|
|
|
void restoreGolems(int qty) {
|
|
int dcs = size(dcal);
|
|
for(int i=1; qty && i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(passable(c, NULL, false, false))
|
|
c->monst = moGolem, qty--;
|
|
}
|
|
}
|
|
|
|
void activateSafety(eLand l) {
|
|
extern void drawSafety();
|
|
int g = countMyGolems();
|
|
drawSafety();
|
|
addMessage(XLAT("You fall into a wormhole!"));
|
|
eLand f = firstland;
|
|
if(l == laTemple) l = laRlyeh;
|
|
if(l == laCamelot) l = laCrossroads;
|
|
firstland = l;
|
|
for(int i=0; i<65536; i++) euland[i] = laNone;
|
|
euland[0] = euland[1] = firstland;
|
|
safety = true;
|
|
clearMemory();
|
|
initcells();
|
|
initgame();
|
|
firstland = f;
|
|
safety = false;
|
|
restoreGolems(g);
|
|
extern void restartGraph();
|
|
restartGraph();
|
|
}
|
|
|
|
bool hasSafeOrb(cell *c) {
|
|
return
|
|
c->item == itOrbSafety ||
|
|
c->item == itOrbShield ||
|
|
c->item == itOrbYendor;
|
|
}
|
|
|
|
|
|
bool movepcto(int d, bool checkonly = false);
|
|
|
|
void checkmove() {
|
|
canmove = false;
|
|
if(movepcto(-1, true)) canmove = true;
|
|
for(int i=0; i<cwt.c->type; i++)
|
|
if(movepcto(1, true)) canmove = true;
|
|
if(!canmove)
|
|
achievement_final(true);
|
|
if(canmove && timerstopped) {
|
|
timerstart = time(NULL);
|
|
timerstopped = false;
|
|
}
|
|
}
|
|
|
|
// move the PC. Warning: a very long function! todo: refactor
|
|
|
|
void placeGolem(cell *on, cell *moveto, eMonster m) {
|
|
if(passable(on, moveto, false, false))
|
|
cwt.c->monst = m;
|
|
else if(on->wall == waBonfire)
|
|
addMessage(XLAT("%The1 burns!", m));
|
|
}
|
|
|
|
void movecost(cell* from, cell *to) {
|
|
if(from->land == laPower && to->land != laPower) {
|
|
int n=0;
|
|
for(int i=0; i<ittypes; i++)
|
|
if(itemclass(eItem(i)) == IC_ORB && items[i] >= 2 && i != itOrbFire)
|
|
items[i] = 2, n++;
|
|
if(n)
|
|
addMessage(XLAT("As you leave, your powers are drained!"));
|
|
}
|
|
}
|
|
|
|
enum orbAction { roMouse, roKeyboard, roCheck, roMouseForce };
|
|
|
|
// roCheck: return orb type if successful, 0 otherwise
|
|
// roMouse/roKeyboard:
|
|
// return orb type if successful, eItem(-1) if do nothing, 0 otherwise
|
|
eItem targetRangedOrb(cell *c, orbAction a);
|
|
|
|
bool haveRangedOrb() {
|
|
return
|
|
items[itOrbPsi] || items[itOrbDragon] || items[itOrbTeleport] ||
|
|
items[itOrbIllusion];
|
|
}
|
|
|
|
bool isRangedOrb(eItem i) {
|
|
return i == itOrbPsi || i == itOrbDragon || i == itOrbTeleport || i == itOrbIllusion;
|
|
}
|
|
|
|
bool haveRangedTarget() {
|
|
if(!haveRangedOrb())
|
|
return false;
|
|
for(int i=0; i<size(dcal); i++) {
|
|
cell *c = dcal[i];
|
|
if(targetRangedOrb(c, roCheck)) return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool movepcto(int d, bool checkonly) {
|
|
if(checkonly && haveRangedTarget()) return true;
|
|
if(!checkonly) flipplayer = false;
|
|
if(!checkonly) DEB("movepc");
|
|
if(d >= 0) {
|
|
cwspin(cwt, d);
|
|
spinmirrors(d);
|
|
d = cwt.spin;
|
|
}
|
|
if(d != -1 && !checkonly) playermoved = true;
|
|
if(!checkonly) invismove = false;
|
|
if(d >= 0) {
|
|
cell *c2 = cwt.c->mov[d];
|
|
|
|
if(!player_passable(c2, cwt.c, false) && items[itOrbFlash]) {
|
|
if(checkonly) return true;
|
|
activateFlash();
|
|
checkmove();
|
|
return true;
|
|
}
|
|
|
|
if(!player_passable(c2, cwt.c, false) && items[itOrbLightning]) {
|
|
if(checkonly) return true;
|
|
activateLightning();
|
|
checkmove();
|
|
return true;
|
|
}
|
|
|
|
if(isActiv(c2) && c2->tmp == -1) {
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("You activate %the1.", c2->wall));
|
|
c2->tmp = 100;
|
|
checkmove();
|
|
return true;
|
|
}
|
|
|
|
if((c2->wall == waThumper/* || (c2->wall == waBigStatue && c2->type == 6)*/) && !monstersnear(c2) && !c2->monst) {
|
|
eWall w = c2->wall;
|
|
cellwalker push = cwt;
|
|
cwstep(push);
|
|
cwspin(push, 3);
|
|
cwstep(push);
|
|
/* if(w == waBigStatue && push.c->type == 7) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("%The1 is too heavy to put it back on the pedestal.", c2->wall));
|
|
return false;
|
|
} */
|
|
if((!passable(push.c, c2, false, true) || !passable(push.c, cwt.c, false, true) || push.c->item) && c2->type == 7) {
|
|
cwstep(push);
|
|
cwspin(push, 1);
|
|
cwstep(push);
|
|
}
|
|
if(!passable(push.c, c2, false, true) || !passable(push.c, cwt.c, false, true) || push.c->item) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("No room to push %the1.", c2->wall));
|
|
return false;
|
|
}
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("You push %the1.", c2->wall));
|
|
push.c->tmp = c2->tmp;
|
|
if(c2->land == laAlchemist)
|
|
c2->wall = (cwt.c->wall == waFloorB || cwt.c->wall == waFloorA) ? cwt.c->wall : push.c->wall;
|
|
else c2->wall = waNone;
|
|
push.c->wall = w;
|
|
}
|
|
|
|
/* if((c2->wall == waBigStatue) && c2->type == 7 && !monstersnear(c2)) {
|
|
int q = 0;
|
|
for(int i=3; i<=4; i++) {
|
|
cellwalker push = cwt;
|
|
cwstep(push);
|
|
cwspin(push, i);
|
|
cwstep(push);
|
|
if(passable(push.c, c2, false, true)) q++;
|
|
}
|
|
if(!q) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("No room to push %the1.", c2->wall));
|
|
return false;
|
|
}
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("You push %the1.", c2->wall));
|
|
c2->wall = waNone;
|
|
for(int i=3; i<=4; i++) {
|
|
cellwalker push = cwt;
|
|
cwstep(push);
|
|
cwspin(push, i);
|
|
cwstep(push);
|
|
if(passable(push.c, c2, false, true))
|
|
push.c->wall = waBigStatue;
|
|
}
|
|
} */
|
|
|
|
if(c2->wall == waBigStatue && !monstersnear(c2) && !c2->monst) {
|
|
if(!canPushStatueOn(cwt.c)) {
|
|
if(checkonly) return false;
|
|
if(cwt.c->wall == waBonfire)
|
|
addMessage(XLAT("You have to escape first!"));
|
|
else
|
|
addMessage(XLAT("There is not enough space!"));
|
|
return false;
|
|
}
|
|
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("You push %the1 behind you!", c2->wall));
|
|
c2->wall = cwt.c->wall;
|
|
if(cellUnstable(cwt.c))
|
|
cwt.c->wall = waChasm;
|
|
else
|
|
cwt.c->wall = waBigStatue;
|
|
}
|
|
|
|
if(c2->wall == waDryTree && !monstersnear(cwt.c)) {
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("You start cutting down the tree."));
|
|
c2->wall = waWetTree;
|
|
}
|
|
else if(c2->wall == waWetTree && !monstersnear(cwt.c)) {
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("You cut down the tree."));
|
|
c2->wall = waNone;
|
|
}
|
|
else if(c2->monst == moKnight) {
|
|
if(checkonly) return false;
|
|
|
|
bool tooeasy = (roundTableRadius(c2) < newRoundTableRadius());
|
|
if(grailWasFound(cwt.c) && rand() % 5 == 0) {
|
|
addMessage(XLAT("\"I would like to congratulate you again!\""));
|
|
}
|
|
else if(rand() % 5 != 0 && !tooeasy) {
|
|
static int i;
|
|
i++;
|
|
if(i%2)
|
|
addMessage(XLAT("\"Find the Holy Grail to become one of us!\""));
|
|
else
|
|
addMessage(XLAT("\"The Holy Grail is in the center of the Round Table.\""));
|
|
}
|
|
else {
|
|
int i = rand() % 3;
|
|
if(i == 0)
|
|
addMessage(XLAT("\"I enjoy watching the hyperbug battles.\""));
|
|
if(i == 1)
|
|
addMessage(XLAT("\"Have you visited a temple in R'Lyeh?\""));
|
|
if(i == 2)
|
|
addMessage(XLAT("\"Nice castle, eh?\""));
|
|
}
|
|
return false;
|
|
}
|
|
else if(c2->monst && !isFriendly(c2)) {
|
|
if(c2->monst == moWorm || c2->monst == moWormtail || c2->monst == moWormwait) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("You cannot attack Sandworms directly!"));
|
|
return false;
|
|
}
|
|
|
|
if(attackingForbidden(c2, cwt.c)) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("You cannot attack through the Vine!"));
|
|
return false;
|
|
}
|
|
|
|
if(c2->monst == moTentacle || c2->monst == moTentacletail || c2->monst == moTentaclewait) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("You cannot attack Tentacles directly!"));
|
|
return false;
|
|
}
|
|
|
|
/* if(isBug(c2)) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("You cannot win with %the1!", c2->monst));
|
|
return false;
|
|
}
|
|
*/
|
|
if(c2->monst == moHedge && !items[itOrbThorns]) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("You cannot attack %the1 directly!", c2->monst));
|
|
addMessage(XLAT("Stab them by walking around them."));
|
|
return false;
|
|
}
|
|
|
|
if(c2->monst == moFlailer) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("You cannot attack %the1 directly!", c2->monst));
|
|
addMessage(XLAT("Make him hit himself by walking away from him."));
|
|
return false;
|
|
}
|
|
|
|
if(c2->monst == moShadow) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("You cannot defeat the Shadow!"));
|
|
return false;
|
|
}
|
|
|
|
if(c2->monst == moGreater || c2->monst == moGreaterM) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("You cannot defeat the Greater Demon yet!"));
|
|
return false;
|
|
}
|
|
|
|
if(monstersnear(cwt.c, c2)) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("You would be killed by %the1!", which));
|
|
return false;
|
|
}
|
|
|
|
if(checkNeedMove(checkonly))
|
|
return false;
|
|
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("You kill %the1.", c2->monst));
|
|
|
|
int mt = c2->monst;
|
|
int tk = tkills();
|
|
killMonster(c2);
|
|
|
|
int ntk = tkills();
|
|
|
|
if(tk == 0 && ntk > 0)
|
|
addMessage(XLAT("That was easy, but groups could be dangerous."));
|
|
|
|
if(tk < 10 && ntk >= 10)
|
|
addMessage(XLAT("Good to know that your fighting skills serve you well in this strange world."));
|
|
|
|
if(tk < 50 && ntk >= 50)
|
|
addMessage(XLAT("You wonder where all these monsters go, after their death..."));
|
|
|
|
if(tk < 100 && ntk >= 100)
|
|
addMessage(XLAT("You feel that the souls of slain enemies pull you to the Graveyard..."));
|
|
|
|
if(mt == moIvyRoot && ntk>tk)
|
|
achievement_gain("IVYSLAYER");
|
|
|
|
gomirrors(0);
|
|
}
|
|
else if(!player_passable(c2, cwt.c, true)) {
|
|
if(checkonly) return false;
|
|
if(c2->wall == waFloorA || c2->wall == waFloorB)
|
|
addMessage(XLAT("Wrong color!"));
|
|
else if(c2->wall == waRoundTable)
|
|
addMessage(XLAT("It would be impolite to land on the table!"));
|
|
else
|
|
addMessage(XLAT("You cannot move through %the1!", c2->wall));
|
|
return false;
|
|
}
|
|
else if(c2->land == laGameBoard) {
|
|
// do not pick up!
|
|
if(checkonly) return true;
|
|
flipplayer = true;
|
|
cwstep(cwt);
|
|
setdist(cwt.c, 0, NULL);
|
|
bfs();
|
|
checkmove();
|
|
return true;
|
|
}
|
|
else {
|
|
if(c2->item == itOrbYendor && !checkYendor(c2, checkonly)) {
|
|
return false;
|
|
}
|
|
if(c2->item == itHolyGrail) {
|
|
if(roundTableRadius(c2) < newRoundTableRadius()) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("That was not a challenge. Find a larger castle!"));
|
|
return false;
|
|
}
|
|
}
|
|
if(!hasSafeOrb(c2) && monstersnear(c2)) {
|
|
if(checkonly) return false;
|
|
|
|
if(items[itOrbFlash]) {
|
|
if(checkonly) return true;
|
|
activateFlash();
|
|
checkmove();
|
|
return true;
|
|
}
|
|
|
|
if(items[itOrbLightning]) {
|
|
if(checkonly) return true;
|
|
activateLightning();
|
|
checkmove();
|
|
return true;
|
|
}
|
|
|
|
addMessage(XLAT("%The1 would kill you there!", which));
|
|
return false;
|
|
}
|
|
if(checkonly) return true;
|
|
flipplayer = true;
|
|
if(c2->item && c2->land == laAlchemist) c2->wall = cwt.c->wall;
|
|
if(c2->wall == waRoundTable) {
|
|
addMessage(XLAT("You jump over the table!"));
|
|
}
|
|
|
|
if(cwt.c->wall == waRoundTable) {
|
|
int dd = celldistAltRelative(c2) - celldistAltRelative(cwt.c);
|
|
|
|
bool tooeasy = (roundTableRadius(c2) < newRoundTableRadius());
|
|
|
|
if(dd>0) {
|
|
if(grailWasFound(cwt.c)) {
|
|
addMessage(XLAT("The Knights congratulate you on your success!"));
|
|
knighted = roundTableRadius(cwt.c);
|
|
}
|
|
else if(!tooeasy)
|
|
addMessage(XLAT("The Knights laugh at your failure!"));
|
|
}
|
|
else {
|
|
if(grailWasFound(cwt.c))
|
|
addMessage(XLAT("The Knights stare at you!"));
|
|
else if(tooeasy)
|
|
addMessage(XLAT("Come on, this is too easy... find a bigger castle!"));
|
|
else
|
|
addMessage(XLAT("The Knights wish you luck!"));
|
|
}
|
|
}
|
|
|
|
int pg = gold();
|
|
bool dopickup = true;
|
|
|
|
invismove = items[itOrbInvis] > 0;
|
|
if(items[itOrbFire]) {
|
|
invismove = false;
|
|
firetrail(cwt.c);
|
|
}
|
|
|
|
if(items[itOrbDigging]) {
|
|
invismove = false;
|
|
earthMove(cwt.c, d);
|
|
}
|
|
|
|
if(c2->item) {
|
|
invismove = false;
|
|
string s0 = "";
|
|
if(0) ;
|
|
if(gold() == 0)
|
|
addMessage(XLAT("Wow! %1! This trip should be worth it!", c2->item));
|
|
else if(gold() == 1)
|
|
addMessage(XLAT("For now, collect as much treasure as possible..."));
|
|
else if(gold() == 2)
|
|
addMessage(XLAT("Prove yourself here, then find new lands, with new quests..."));
|
|
else if(!items[c2->item] && itemclass(c2->item) == IC_TREASURE)
|
|
addMessage(XLAT("You collect your first %1!", c2->item));
|
|
else if(c2->item == itKey)
|
|
addMessage(XLAT("You have found the Key! Now unlock this Orb of Yendor!"));
|
|
else if(c2->item == itGreenStone && !items[itGreenStone])
|
|
addMessage(XLAT("This orb is dead..."));
|
|
else if(c2->item == itGreenStone)
|
|
addMessage(XLAT("Another Dead Orb."));
|
|
else if(itemclass(c2->item) != IC_TREASURE)
|
|
addMessage(XLAT("You have found %the1!", c2->item));
|
|
else if(items[c2->item] == 4 && maxgold() == 4) {
|
|
addMessage(XLAT("You feel that %the2 become%s2 more dangerous.", c2->item, c2->land));
|
|
addMessage(XLAT("With each %1 you collect...", c2->item, c2->land));
|
|
}
|
|
else if(items[c2->item] == 9 && maxgold() == 9)
|
|
addMessage(XLAT("Are there any magical orbs in %the1?...", c2->land));
|
|
else if(items[c2->item] == 10 && maxgold() == 10) {
|
|
addMessage(XLAT("You feel that %the1 slowly become%s1 dangerous...", c2->land));
|
|
addMessage(XLAT("Better find some other place."));
|
|
}
|
|
else if(c2->item == itSpice && items[itSpice] == 7)
|
|
addMessage(XLAT("You have a vision of the future, fighting demons in Hell..."));
|
|
else if(c2->item == itElixir && items[itElixir] == 4)
|
|
addMessage(XLAT("With this Elixir, your life should be long and prosperous..."));
|
|
else if(c2->item == itBone && items[itBone] == 6)
|
|
addMessage(XLAT("The Necromancer's Totem contains hellish incantations..."));
|
|
else if(c2->item == itStatue && items[itStatue] == 6)
|
|
addMessage(XLAT("The inscriptions on the Statue of Cthulhu point you toward your destiny..."));
|
|
else if(c2->item == itStatue && items[itStatue] == 4)
|
|
addMessage(XLAT("There must be some temples of Cthulhu in R'Lyeh..."));
|
|
else if(c2->item == itDiamond && items[itDiamond] == 8)
|
|
addMessage(XLAT("Still, even greater treasures lie ahead..."));
|
|
else if(c2->item == itFernFlower && items[itFernFlower] == 4)
|
|
addMessage(XLAT("You overheard Hedgehog Warriors talking about emeralds..."));
|
|
else if(c2->item == itEmerald && items[itEmerald] == 4)
|
|
addMessage(XLAT("You overhear miners talking about a castle..."));
|
|
else if(c2->item == itEmerald && items[itEmerald] == 5)
|
|
addMessage(XLAT("A castle in the Crossroads..."));
|
|
else {
|
|
string t = XLAT("You collect %the1.", c2->item);
|
|
addMessage(t);
|
|
}
|
|
}
|
|
|
|
if(c2->item == itOrbSpeed) {
|
|
items[c2->item] += 31;
|
|
if(items[c2->item] > 67) items[c2->item] = 67;
|
|
}
|
|
else if(c2->item == itOrbLife) {
|
|
placeGolem(cwt.c, c2, moGolem);
|
|
}
|
|
else if(c2->item == itOrbSafety) {
|
|
items[c2->item] += 7;
|
|
activateSafety(c2->land);
|
|
return true;
|
|
}
|
|
else if(c2->item == itOrbLightning) {
|
|
items[c2->item] += 78;
|
|
if(items[c2->item] > 777) items[c2->item] = 777;
|
|
}
|
|
else if(c2->item == itOrbThorns) {
|
|
items[c2->item] += 78;
|
|
if(items[c2->item] > 151) items[c2->item] = 151;
|
|
}
|
|
else if(c2->item == itOrbFlash) {
|
|
items[c2->item] += 78;
|
|
if(items[c2->item] > 777) items[c2->item] = 777;
|
|
}
|
|
else if(c2->item == itOrbShield) {
|
|
items[c2->item] += 16;
|
|
if(items[c2->item] > 77) items[c2->item] = 77;
|
|
}
|
|
else if(c2->item == itOrbWinter) {
|
|
items[c2->item] += 31;
|
|
if(items[c2->item] > 77) items[c2->item] = 77;
|
|
}
|
|
else if(c2->item == itOrbFire) {
|
|
items[c2->item] += 31;
|
|
if(items[c2->item] > 77) items[c2->item] = 77;
|
|
}
|
|
else if(c2->item == itOrbDragon) {
|
|
items[c2->item] += 78;
|
|
if(items[c2->item] > 111) items[c2->item] = 111;
|
|
}
|
|
else if(c2->item == itOrbIllusion) {
|
|
items[c2->item] += 78;
|
|
if(items[c2->item] > 111) items[c2->item] = 111;
|
|
}
|
|
else if(c2->item == itOrbPsi) {
|
|
items[c2->item] += 78;
|
|
if(items[c2->item] > 111) items[c2->item] = 111;
|
|
}
|
|
else if(c2->item == itOrbInvis) {
|
|
items[c2->item] += 31;
|
|
if(items[c2->item] > 77) items[c2->item] = 77;
|
|
}
|
|
else if(c2->item == itOrbGhost) {
|
|
items[c2->item] += 31;
|
|
if(items[c2->item] > 77) items[c2->item] = 77;
|
|
}
|
|
else if(c2->item == itOrbDigging) {
|
|
items[c2->item] += 78;
|
|
if(items[c2->item] > 101) items[c2->item] = 101;
|
|
}
|
|
else if(c2->item == itOrbTeleport) {
|
|
items[c2->item] += 78;
|
|
if(items[c2->item] > 201) items[c2->item] = 201;
|
|
}
|
|
else if(c2->item == itOrbYendor) {
|
|
for(int i=0; i<4; i++) switch(rand() % 12) {
|
|
case 0: items[itOrbSpeed] += 31; break;
|
|
case 1: items[itOrbLightning] += 78; break;
|
|
case 2: items[itOrbFlash] += 78; break;
|
|
case 3: items[itOrbShield] += 31; break;
|
|
case 4: items[itOrbWinter] += 151; break;
|
|
case 5: items[itOrbDigging] += 151; break;
|
|
case 6: items[itOrbTeleport] += 151; break;
|
|
case 7: items[itOrbThorns] += 151; break;
|
|
case 8: items[itOrbInvis] += 151; break;
|
|
case 9: items[itOrbPsi] += 151; break;
|
|
case 10: items[itOrbGhost] += 151; break;
|
|
case 11: items[itOrbFire] += 151; break;
|
|
}
|
|
items[itOrbYendor]++;
|
|
items[itKey]--;
|
|
addMessage(XLAT("CONGRATULATIONS!"));
|
|
achievement_collection(itOrbYendor, pg, gold());
|
|
achievement_victory(false);
|
|
}
|
|
else if(c2->item == itHolyGrail) {
|
|
int v = newRoundTableRadius() + 12;
|
|
items[itOrbTeleport] += v;
|
|
items[itOrbSpeed] += v;
|
|
items[itHolyGrail]++;
|
|
addMessage(XLAT("Congratulations! You have found the Holy Grail!"));
|
|
if(!euclid) c2->master->alt->fjordval |= GRAIL_FOUND;
|
|
achievement_collection(c2->item, pg, gold());
|
|
}
|
|
else if(c2->item == itKey) {
|
|
for(int i=0; i<size(yi); i++) if(yi[i].path[YDIST-1] == c2)
|
|
yi[i].found = true;
|
|
items[itKey]++;
|
|
}
|
|
else if(c2->item == itGrimoire && items[itGrimoire] > celldistAlt(c2)/-TEMPLE_EACH) {
|
|
addMessage(XLAT("You already have this Grimoire! Seek new tomes in the inner circles."));
|
|
dopickup = false;
|
|
}
|
|
else {
|
|
bool lhu = hellUnlocked();
|
|
if(c2->item) items[c2->item]++;
|
|
int g2 = gold();
|
|
|
|
if(c2->item == itHyperstone && items[itHyperstone] == 10)
|
|
achievement_victory(true);
|
|
|
|
achievement_collection(c2->item, pg, g2);
|
|
|
|
if(pg < 15 && g2 >= 15)
|
|
addMessage(XLAT("Collect treasure to access more different lands..."));
|
|
if(pg < 30 && g2 >= 30)
|
|
addMessage(XLAT("You feel that you have enough treasure to access new lands!"));
|
|
if(pg < 45 && g2 >= 45)
|
|
addMessage(XLAT("Collect more treasures, there are still more lands waiting..."));
|
|
if(pg < 60 && g2 >= 60)
|
|
addMessage(XLAT("You feel that the stars are right, and you can access R'Lyeh!"));
|
|
if(pg < 75 && g2 >= 75)
|
|
addMessage(XLAT("Kill monsters and collect treasures, and you may get access to Hell..."));
|
|
if(pg < 90 && g2 >= 90)
|
|
addMessage(XLAT("To access Hell, collect 10 treasures each of 9 kinds..."));
|
|
if(hellUnlocked() && !lhu) {
|
|
addMessage(XLAT("Abandon all hope, the gates of Hell are opened!"));
|
|
addMessage(XLAT("And the Orbs of Yendor await!"));
|
|
}
|
|
}
|
|
|
|
if(dopickup) c2->item = itNone;
|
|
// if(c2->land == laHive)
|
|
// c2->heat = 1;
|
|
|
|
int numOrb = 0;
|
|
for(int i=0; i<ittypes; i++)
|
|
if(itemclass(eItem(i)) == IC_ORB && items[i])
|
|
numOrb++;
|
|
if(numOrb) achievement_count("ORB", numOrb, 0);
|
|
|
|
if(items[itOrbWinter] && isIcyLand(cwt.c)) {
|
|
invismove = false;
|
|
if(cwt.c->wall == waNone)
|
|
cwt.c->wall = waIcewall;
|
|
}
|
|
|
|
movecost(cwt.c, c2);
|
|
|
|
if(c2->monst == moGolem || c2->monst == moIllusion) {
|
|
addMessage(XLAT("You switch places with %the1.", c2->monst));
|
|
placeGolem(cwt.c, c2, c2->monst);
|
|
c2->monst = moNone;
|
|
}
|
|
else if(c2->monst) {
|
|
addMessage(XLAT("You rejoin %the1.", c2->monst));
|
|
killMonster(c2);
|
|
}
|
|
|
|
stabbingAttack(cwt.c, c2);
|
|
cwstep(cwt);
|
|
|
|
gomirrors(1);
|
|
|
|
if(c2->wall == waMirror) {
|
|
invismove = false;
|
|
addMessage(XLAT("The mirror shatters!"));
|
|
if(c2->land == laMirror) {
|
|
int g = gold();
|
|
items[itShard]++;
|
|
achievement_collection(itShard, g+1, g);
|
|
}
|
|
c2->wall = waNone;
|
|
createMirrors(cwt.c, cwt.spin, moMirage);
|
|
}
|
|
|
|
if(c2->wall == waGlass && items[itOrbGhost] > 2) {
|
|
addMessage(XLAT("Your Aether powers are drained by %the1!", c2->wall));
|
|
items[itOrbGhost] = 2;
|
|
}
|
|
|
|
if(c2->wall == waCloud) {
|
|
invismove = false;
|
|
addMessage(XLAT("The cloud turns into a bunch of images!"));
|
|
if(c2->land == laMirror) {
|
|
int g = gold();
|
|
items[itShard]++;
|
|
achievement_collection(itShard, g+1, g);
|
|
}
|
|
c2->wall = waNone;
|
|
createMirages(cwt.c, cwt.spin, moMirage);
|
|
}
|
|
|
|
if(cellUnstable(c2) && !items[itOrbGhost])
|
|
c2->wall = waChasm;
|
|
|
|
countLocalTreasure();
|
|
landvisited[cwt.c->land] = true;
|
|
setdist(cwt.c, 0, NULL);
|
|
}
|
|
}
|
|
else {
|
|
if(checkNeedMove(checkonly))
|
|
return false;
|
|
if(monstersnear(cwt.c)) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("%The1 would get you!", which));
|
|
return false;
|
|
}
|
|
if(checkonly) return true;
|
|
if(d == -2 && items[itGreenStone] && cwt.c->item == itNone) {
|
|
items[itGreenStone]--;
|
|
if(false) {
|
|
cwt.c->item = itNone;
|
|
spill(cwt.c, eWall(cwt.c->wall ^ waFloorA ^ waFloorB), 3);
|
|
addMessage(XLAT("The slime reacts with %the1!", itGreenStone));
|
|
}
|
|
else {
|
|
cwt.c->item = itGreenStone;
|
|
addMessage(XLAT("You drop %the1.", itGreenStone));
|
|
}
|
|
}
|
|
else if(d == -2) {
|
|
if(gold() >= 300)
|
|
addMessage(XLAT("You feel great, like a true treasure hunter."));
|
|
else if(gold() >= 200)
|
|
addMessage(XLAT("Your eyes shine like gems."));
|
|
else if(gold() >= 100)
|
|
addMessage(XLAT("Your eyes shine as you glance at your precious treasures."));
|
|
else if(gold() >= 50)
|
|
addMessage(XLAT("You glance at your great treasures."));
|
|
else if(gold() >= 10)
|
|
addMessage(XLAT("You glance at your precious treasures."));
|
|
else if(gold() > 0)
|
|
addMessage(XLAT("You glance at your precious treasure."));
|
|
else
|
|
addMessage(XLAT("Your inventory is empty."));
|
|
}
|
|
}
|
|
DEBT("bfs");
|
|
bfs();
|
|
destroyStrayMirrors();
|
|
DEBT("heat");
|
|
heat();
|
|
DEBT("rop");
|
|
int phase1 = (1 & items[itOrbSpeed]);
|
|
reduceOrbPowers();
|
|
DEBT("mmo");
|
|
int phase2 = (1 & items[itOrbSpeed]);
|
|
if(!phase2) movemonsters();
|
|
if(cwt.c->land == laPower && !phase1) { bfs(); moveFastMonsters(); }
|
|
DEBT("lc");
|
|
if(!phase1) livecaves();
|
|
if(!phase1) dryforest();
|
|
DEBT("check");
|
|
checkmove();
|
|
DEBT("done");
|
|
return true;
|
|
}
|
|
|
|
/* bool isPsiTarget(cell *dst) {
|
|
return
|
|
dst->cpdist > 1 &&
|
|
dst->monst &&
|
|
!(isWorm(dst) || dst->monst == moShadow);
|
|
} */
|
|
|
|
void teleportTo(cell *dest) {
|
|
cwt.c->monst = dest->monst;
|
|
dest->monst = moNone;
|
|
movecost(cwt.c, dest);
|
|
cwt.c = dest; cwt.spin = rand() % dest->type; flipplayer = !!(rand() % 2);
|
|
items[itOrbTeleport] = 0;
|
|
|
|
addMessage(XLAT("You teleport to a new location!"));
|
|
destroyMirrors();
|
|
|
|
for(int i=9; i>=0; i--)
|
|
setdist(cwt.c, i, NULL);
|
|
|
|
bfs();
|
|
checkmove();
|
|
}
|
|
|
|
void psi_attack(cell *dest) {
|
|
addMessage(XLAT("You kill %the1 with a mental blast!", dest->monst));
|
|
killMonster(dest);
|
|
items[itOrbPsi] -= 30;
|
|
if(items[itOrbPsi]<0) items[itOrbPsi] = 0;
|
|
checkmove();
|
|
}
|
|
|
|
bool flammable(cell *c) {
|
|
return
|
|
c->wall == waNone || c->wall == waFloorA || c->wall == waFloorB ||
|
|
c->wall == waCavefloor || c->wall == waDeadfloor ||
|
|
c->wall == waDryTree || c->wall == waWetTree ||
|
|
c->wall == waVinePlant || c->wall == waVineHalfA || c->wall == waVineHalfB ||
|
|
c->wall == waIcewall;
|
|
}
|
|
|
|
void placeDragonfire(cell *c) {
|
|
if(cellUnstable(c))
|
|
c->wall = waChasm;
|
|
else if(c->wall == waNone && c->land == laCocytus)
|
|
c->wall = waLake, c->heat += 20;
|
|
else if(cellHalfvine(c))
|
|
flameHalfvine(c, 20);
|
|
else {
|
|
c->wall = waBonfire;
|
|
c->tmp = 20;
|
|
}
|
|
if(itemBurns(c->item))
|
|
addMessage(XLAT("%The1 burns!", c->item)), c->item = itNone;
|
|
addMessage(XLAT("You throw fire!"));
|
|
items[itOrbDragon] -= 5;
|
|
if(items[itOrbDragon]<0) items[itOrbDragon] = 0;
|
|
checkmove();
|
|
}
|
|
|
|
void placeIllusion(cell *c) {
|
|
c->monst = moIllusion;
|
|
items[itOrbIllusion] -= 5;
|
|
if(items[itOrbIllusion]<0) items[itOrbIllusion] = 0;
|
|
addMessage(XLAT("You create an Illusion!"));
|
|
checkmove();
|
|
}
|
|
|
|
eItem targetRangedOrb(cell *c, orbAction a) {
|
|
if(!haveRangedOrb()) return itNone;
|
|
if(c == cwt.c || isNeighbor(cwt.c, c)) {
|
|
if(a == roKeyboard || a == roMouseForce )
|
|
addMessage(XLAT("You cannot target that close!"));
|
|
return itNone;
|
|
}
|
|
if(c->cpdist > 7) {
|
|
if(a != roCheck)
|
|
addMessage(XLAT("You cannot target that far away!"));
|
|
return itNone;
|
|
}
|
|
|
|
// (1) switch with an illusion
|
|
if(items[itOrbTeleport] && c->monst == moIllusion) {
|
|
if(a != roCheck) teleportTo(c);
|
|
return itOrbTeleport;
|
|
}
|
|
|
|
// (2) place illusion
|
|
if(items[itOrbIllusion] && c->monst == moNone && c->item == itNone && passable(c, NULL, false, true)) {
|
|
if(a != roCheck) placeIllusion(c);
|
|
return itOrbIllusion;
|
|
}
|
|
|
|
// (3) teleport
|
|
if(items[itOrbTeleport] && c->monst == moNone && c->item == itNone && passable(c, NULL, false, true)) {
|
|
if(a != roCheck) teleportTo(c);
|
|
return itOrbTeleport;
|
|
}
|
|
|
|
// (4) remove an illusion
|
|
if(items[itOrbIllusion] && c->monst == moIllusion) {
|
|
if(a != roCheck) {
|
|
addMessage(XLAT("You take the Illusion away."));
|
|
items[itOrbIllusion] += 4;
|
|
c->monst = moNone;
|
|
}
|
|
return itOrbIllusion;
|
|
}
|
|
|
|
// (5) psi blast
|
|
if(items[itOrbPsi] && c->monst && !isWorm(c) && c->monst != moShadow) {
|
|
if(a != roCheck) psi_attack(c);
|
|
return itOrbPsi;
|
|
}
|
|
|
|
// (6) place fire
|
|
if(items[itOrbDragon] && flammable(c)) {
|
|
if(a != roCheck) placeDragonfire(c);
|
|
return itOrbDragon;
|
|
}
|
|
|
|
if(a == roCheck) return itNone;
|
|
|
|
if(items[itOrbPsi] && c->monst) {
|
|
addMessage(XLAT("%The1 is immune to mental blasts!", c->monst));
|
|
}
|
|
else if(items[itOrbTeleport] && c->monst) {
|
|
addMessage(XLAT("Cannot teleport on a monster!"));
|
|
}
|
|
else if(items[itOrbIllusion] && c->item)
|
|
addMessage(XLAT("Cannot cast illusion on an item!"));
|
|
else if(items[itOrbIllusion] && c->monst)
|
|
addMessage(XLAT("Cannot cast illusion on a monster!"));
|
|
else if(items[itOrbIllusion] && !passable(c, NULL, false, true))
|
|
addMessage(XLAT("Cannot cast illusion here!"));
|
|
else if(items[itOrbTeleport] && c->item) {
|
|
addMessage(XLAT("Cannot teleport on an item!"));
|
|
}
|
|
else if(items[itOrbTeleport] && !passable(c, NULL, false, true)) {
|
|
addMessage(XLAT("Cannot teleport here!"));
|
|
}
|
|
else if(items[itOrbDragon] && !flammable(c)) {
|
|
addMessage(XLAT("Cannot throw fire there!"));
|
|
}
|
|
else return eItem(0);
|
|
|
|
return eItem(-1);
|
|
}
|
|
|
|
#define MAXBOX 120
|
|
#define POSSCORE 107 // update this when new boxes are added!
|
|
|
|
struct score {
|
|
string ver;
|
|
int box[MAXBOX];
|
|
};
|
|
|
|
int savebox[MAXBOX], boxid;
|
|
bool saving;
|
|
|
|
string boxname[MAXBOX];
|
|
bool fakebox[MAXBOX];
|
|
|
|
void applyBox(int& t) {
|
|
if(saving) savebox[boxid++] = t;
|
|
else t = savebox[boxid++];
|
|
}
|
|
|
|
void applyBoxNum(int& i, string name = "") {
|
|
fakebox[boxid] = (name == "");
|
|
boxname[boxid] = name;
|
|
applyBox(i);
|
|
}
|
|
|
|
// just skips the value when loading
|
|
void applyBoxSave(int i, string name = "") {
|
|
fakebox[boxid] = (name == "");
|
|
boxname[boxid] = name;
|
|
applyBox(i);
|
|
}
|
|
|
|
int applyBoxLoad(string name = "") {
|
|
fakebox[boxid] = (name == "");
|
|
boxname[boxid] = name;
|
|
int i=0; applyBox(i);
|
|
return i;
|
|
}
|
|
|
|
void applyBoxI(eItem it, bool f = false) {
|
|
boxname[boxid] = iinf[it].name;
|
|
fakebox[boxid] = f;
|
|
applyBox(items[it]);
|
|
}
|
|
|
|
void applyBoxM(eMonster m, bool f = false) {
|
|
fakebox[boxid] = f;
|
|
boxname[boxid] = minf[m].name;
|
|
applyBox(kills[m]);
|
|
}
|
|
|
|
void killbox(eMonster m, int& val) {
|
|
if(saving) kills[m] = val;
|
|
else { val = kills[m]; kills[m] = 0; }
|
|
}
|
|
|
|
void applyBoxes() {
|
|
|
|
applyBoxSave(timerstart, "time elapsed");
|
|
time_t timer = time(NULL);
|
|
applyBoxSave(timer, "date");
|
|
applyBoxSave(gold(), "treasure collected");
|
|
applyBoxSave(tkills(), "total kills");
|
|
applyBoxNum(turncount, "turn count");
|
|
applyBoxNum(cellcount, "cells generated");
|
|
|
|
if(!saving) timerstart = time(NULL);
|
|
|
|
for(int i=0; i<itOrbLightning; i++)
|
|
if(i == 0) items[i] = 0, applyBoxI(itFernFlower);
|
|
else applyBoxI(eItem(i));
|
|
|
|
for(int i=0; i<43; i++) {
|
|
kills[i] = 0;
|
|
bool fake =
|
|
i == moLesserM || i == moNone || i == moWolfMoved || i == moTentacletail;
|
|
if(i == moWormtail) applyBoxM(moCrystalSage);
|
|
else if(i == moWormwait) applyBoxM(moFireFairy);
|
|
else if(i == moTentacleEscaping) applyBoxM(moMiner);
|
|
else if(i == moGolemMoved) applyBoxM(moIllusion);
|
|
else if(i == moTentaclewait) applyBoxI(itOrbThorns, true);
|
|
else if(i == moGreater) applyBoxI(itOrbDragon, true);
|
|
else if(i == moGreaterM) applyBoxI(itOrbIllusion, true);
|
|
else applyBoxM(eMonster(i), fake);
|
|
}
|
|
|
|
if(saving) {
|
|
applyBoxSave(savetime + timer - timerstart);
|
|
}
|
|
else savetime = applyBoxLoad();
|
|
|
|
savecount++; applyBoxNum(savecount, "number of saves"); savecount--;
|
|
applyBoxNum(cheater);
|
|
|
|
if(saving) applyBoxSave(cwt.c->land);
|
|
else firstland = eLand(applyBoxLoad());
|
|
|
|
for(int i=itOrbLightning; i<25; i++) applyBoxI(eItem(i), true);
|
|
|
|
applyBoxI(itRoyalJelly);
|
|
applyBoxI(itWine);
|
|
applyBoxI(itSilver);
|
|
applyBoxI(itEmerald);
|
|
applyBoxI(itPower);
|
|
applyBoxI(itOrbFire, true);
|
|
applyBoxI(itOrbInvis, true);
|
|
applyBoxI(itOrbGhost, true);
|
|
applyBoxI(itOrbPsi, true);
|
|
applyBoxM(moBug0);
|
|
applyBoxM(moBug1);
|
|
applyBoxM(moBug2);
|
|
applyBoxM(moVineBeast);
|
|
applyBoxM(moVineSpirit);
|
|
applyBoxM(moLancer);
|
|
applyBoxM(moFlailer);
|
|
applyBoxM(moEarthElemental);
|
|
applyBoxM(moDarkTroll);
|
|
applyBoxM(moWitch);
|
|
applyBoxM(moWitchFire);
|
|
applyBoxM(moWitchFlash);
|
|
applyBoxM(moWitchGhost);
|
|
applyBoxM(moWitchSpeed);
|
|
applyBoxM(moEvilGolem);
|
|
applyBoxM(moWitchWinter);
|
|
applyBoxI(itHolyGrail);
|
|
applyBoxI(itGrimoire);
|
|
applyBoxM(moKnight);
|
|
applyBoxM(moCultistLeader);
|
|
}
|
|
|
|
void saveBox() {
|
|
boxid = 0; saving = true; applyBoxes();
|
|
}
|
|
|
|
void loadBox() {
|
|
// have boxid
|
|
boxid = 0; saving = false; applyBoxes();
|
|
}
|
|
|
|
// certify that saves and achievements were received
|
|
// in an official version of HyperRogue
|
|
#ifdef CERTIFY
|
|
#include "certify.cpp"
|
|
#else
|
|
bool tampered;
|
|
|
|
void saveCertificate(FILE *f) {}
|
|
bool loadCertificate(FILE *f, score& sc) {return true; }
|
|
|
|
int achievement_certify(const char *s, int a, int b, int c) { return 0; }
|
|
|
|
#endif
|
|
|
|
void saveStats() {
|
|
if(euclid) return;
|
|
#ifndef ANDROID
|
|
|
|
FILE *f = fopen(scorefile, "at");
|
|
if(!f) {
|
|
printf("Could not open the score file '%s'!\n", scorefile);
|
|
addMessage(XLAT("Could not open the score file: ", scorefile));
|
|
return;
|
|
}
|
|
|
|
if(showoff) return;
|
|
|
|
time_t timer;
|
|
timer = time(NULL);
|
|
char sbuf[128]; strftime(sbuf, 128, "%c", localtime(&timerstart));
|
|
char buf[128]; strftime(buf, 128, "%c", localtime(&timer));
|
|
|
|
fprintf(f, "HyperRogue: game statistics (version "VER")\n");
|
|
if(cheater)
|
|
fprintf(f, "CHEATER! (cheated %d times)\n", cheater);
|
|
if(true) {
|
|
|
|
fprintf(f, VER);
|
|
saveBox();
|
|
|
|
for(int i=0; i<boxid; i++) fprintf(f, " %d", savebox[i]);
|
|
saveCertificate(f);
|
|
|
|
fprintf(f, "\n");
|
|
}
|
|
fprintf(f, "Played on: %s - %s (%d turns)\n", sbuf, buf, turncount);
|
|
fprintf(f, "Total wealth: %d\n", gold());
|
|
fprintf(f, "Total enemies killed: %d\n", tkills());
|
|
fprintf(f, "cells generated: %d\n", cellcount);
|
|
fprintf(f, "Number of cells explored, by distance from the player:\n");
|
|
for(int i=0; i<10; i++) fprintf(f, " %d", explore[i]); fprintf(f, "\n");
|
|
/*for(int j=0; j<landtypes; j++) {
|
|
bool haveland = false;
|
|
for(int i=0; i<10; i++)
|
|
if(exploreland[i][j])
|
|
haveland = true;
|
|
if(haveland)
|
|
for(int i=0; i<10; i++)
|
|
fprintf(f, " %d", exploreland[i][j]);
|
|
fprintf(f, " %s\n", linf[j].name);
|
|
} */
|
|
if(kills[0]) fprintf(f, "walls melted: %d\n", kills[0]);
|
|
fprintf(f, "cells travelled: %d\n", celldist(cwt.c));
|
|
|
|
fprintf(f, "\n");
|
|
|
|
for(int i=0; i<ittypes; i++) if(items[i])
|
|
fprintf(f, "%4dx %s\n", items[i], iinf[i].name);
|
|
|
|
fprintf(f, "\n");
|
|
|
|
for(int i=1; i<motypes; i++) if(kills[i])
|
|
fprintf(f, "%4dx %s <%d>\n", kills[i], minf[i].name, i);
|
|
|
|
fprintf(f, "\n\n\n");
|
|
|
|
printf("Game statistics saved to %s\n", scorefile);
|
|
addMessage(XLAT("Game statistics saved to %1", scorefile));
|
|
fclose(f);
|
|
#endif
|
|
}
|
|
|
|
bool havesave = true;
|
|
|
|
#ifndef ANDROID
|
|
// load the save
|
|
void loadsave() {
|
|
|
|
printf("Trying to load a save.\n");
|
|
FILE *f = fopen(scorefile, "rt");
|
|
havesave = f;
|
|
if(!f) return;
|
|
score sc;
|
|
bool ok = false;
|
|
bool tamper = false;
|
|
while(!feof(f)) {
|
|
char buf[120];
|
|
if(fgets(buf, 120, f) == NULL) break;
|
|
if(buf[0] == 'H' && buf[1] == 'y') {
|
|
if(fscanf(f, "%s", buf) <= 0) break; sc.ver = buf;
|
|
if(sc.ver < "4.4" || sc.ver == "CHEATER!") continue;
|
|
ok = true;
|
|
for(int i=0; i<MAXBOX; i++) {
|
|
if(fscanf(f, "%d", &sc.box[i]) <= 0) {
|
|
boxid = i;
|
|
tamper = loadCertificate(f, sc);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
fclose(f);
|
|
if(ok && sc.box[65 + 4 + itOrbSafety - itOrbLightning]) {
|
|
tampered = tamper;
|
|
// printf("box = %d (%d)\n", sc.box[65 + 4 + itOrbSafety - itOrbLightning], boxid);
|
|
for(int i=0; i<boxid; i++) savebox[i] = sc.box[i];
|
|
for(int i=boxid; i<MAXBOX; i++) savebox[i] = 0;
|
|
loadBox();
|
|
if(items[itHolyGrail]) {
|
|
items[itHolyGrail]--;
|
|
knighted = newRoundTableRadius();
|
|
items[itHolyGrail]++;
|
|
}
|
|
else knighted = 0;
|
|
safety = true;
|
|
addMessage(XLAT("Game loaded."));
|
|
}
|
|
}
|
|
#endif
|
|
|
|
void restartGame() {
|
|
DEB("savestats");
|
|
achievement_final(true);
|
|
saveStats();
|
|
DEB("clear");
|
|
for(int i=0; i<ittypes; i++) items[i] = 0;
|
|
for(int i=0; i<motypes; i++) kills[i] = 0;
|
|
for(int i=0; i<10; i++) explore[i] = 0;
|
|
tampered = false; achievementsReceived.clear();
|
|
knighted = 0;
|
|
// items[itGreenStone] = 100;
|
|
cellcount = 0;
|
|
DEB("clearmem");
|
|
clearMemory();
|
|
DEB("initc");
|
|
initcells();
|
|
DEB("initg");
|
|
initgame();
|
|
canmove = true;
|
|
DEB("restg");
|
|
extern void restartGraph();
|
|
restartGraph();
|
|
extern void resetmusic();
|
|
resetmusic();
|
|
}
|
|
|
|
void restartGameSwitchEuclid() {
|
|
DEB("savestats");
|
|
achievement_final(true);
|
|
saveStats();
|
|
DEB("clear");
|
|
for(int i=0; i<ittypes; i++) items[i] = 0;
|
|
for(int i=0; i<motypes; i++) kills[i] = 0;
|
|
for(int i=0; i<10; i++) explore[i] = 0;
|
|
// items[itGreenStone] = 100;
|
|
cellcount = 0;
|
|
DEB("clearmem");
|
|
clearMemory();
|
|
euclid = !euclid;
|
|
DEB("initc");
|
|
initcells();
|
|
DEB("initg");
|
|
initgame();
|
|
canmove = true;
|
|
DEB("restg");
|
|
extern void restartGraph();
|
|
restartGraph();
|
|
extern void resetmusic();
|
|
resetmusic();
|
|
}
|
|
|
|
void clearGameMemory() {
|
|
pathq.clear();
|
|
dcal.clear();
|
|
yii = 0; yi.clear();
|
|
clearshadow();
|
|
}
|
|
|
|
bool fjordwalled[64];
|
|
|
|
#ifndef ANDROID
|
|
|
|
void applyGameBoard(char u, int sym, cell *c, char boardmode) {
|
|
if(!c) c = cwt.c;
|
|
if(sym == SDLK_RETURN) {
|
|
items[c->item]++;
|
|
c->item = itNone;
|
|
}
|
|
if(u == ' ')
|
|
c->item = itNone;
|
|
|
|
extern void movepckeydir(int);
|
|
if(sym == 'd' || sym == SDLK_KP6) movepckeydir(0);
|
|
if( sym == SDLK_KP3) movepckeydir(1);
|
|
if(sym == 'x' || sym == SDLK_KP2) movepckeydir(2);
|
|
if( sym == SDLK_KP1) movepckeydir(3);
|
|
if(sym == 'a' || sym == SDLK_KP4) movepckeydir(4);
|
|
if( sym == SDLK_KP7) movepckeydir(5);
|
|
if(sym == 'w' || sym == SDLK_KP8) movepckeydir(6);
|
|
if( sym == SDLK_KP9) movepckeydir(7);
|
|
|
|
if(u == 'g')
|
|
c->item = itGreenStone;
|
|
if(u == 'r')
|
|
c->item = itOrbSpeed;
|
|
if(u == 't')
|
|
c->item = itOrbFlash;
|
|
if(u == 'o')
|
|
c->item = itOrbShield;
|
|
if(u == 'i')
|
|
c->item = itOrbTeleport;
|
|
if(u == 'p')
|
|
c->item = itOrbLightning;
|
|
if(u == 'h')
|
|
c->item = itDiamond;
|
|
if(u == 'j')
|
|
c->item = itHell;
|
|
if(u == 'k')
|
|
c->item = itFernFlower;
|
|
if(u == 'l')
|
|
c->item = itSapphire;
|
|
|
|
if(boardmode == 'f') {
|
|
int fv = fjordval(c);
|
|
if(u == '0' || u == '1' || u == '2' || u == '3' || u == '4') fjordwalled[fv] = !fjordwalled[fv];
|
|
if(u == '1' || u == '4') fjordwalled[fv^1] = !fjordwalled[fv^1];
|
|
if(u == '2' || u == '4') fjordwalled[fv^2] = !fjordwalled[fv^2];
|
|
if(u == '3' || u == '4') fjordwalled[fv^3] = !fjordwalled[fv^3];
|
|
return;
|
|
}
|
|
|
|
if(u == '0')
|
|
c->wall = waNone;
|
|
if(u == '1')
|
|
c->wall = waFloorA;
|
|
if(u == '2')
|
|
c->wall = waFloorB;
|
|
if(u == '3')
|
|
c->wall = waFrozenLake;
|
|
if(u == '4')
|
|
c->wall = waCavefloor;
|
|
if(u == '5')
|
|
c->wall = waLake;
|
|
if(u == '6')
|
|
c->wall = waCavewall;
|
|
if(u == '7')
|
|
c->wall = waIcewall;
|
|
if(u == '8')
|
|
c->wall = waDryTree;
|
|
if(u == '9')
|
|
c->wall = waColumn;
|
|
}
|
|
#endif
|
|
|
|
static int orbid = 0;
|
|
|
|
eItem nextOrb() {
|
|
orbid++;
|
|
eItem i = eItem(orbid % ittypes);
|
|
if(itemclass(i) == IC_ORB) return i;
|
|
else return nextOrb();
|
|
}
|
|
|
|
eItem randomTreasure() {
|
|
eItem i = eItem(rand() % ittypes);
|
|
if(itemclass(i) == IC_TREASURE) return i;
|
|
else return randomTreasure();
|
|
}
|
|
|
|
eItem randomTreasure2(int cv) {
|
|
int bq = 60000, cq = 0;
|
|
eItem best = itDiamond;
|
|
eItem lt = localTreasureType();
|
|
for(int a=1; a<ittypes; a++) {
|
|
eItem i = eItem(a);
|
|
if(itemclass(i) != IC_TREASURE) continue;
|
|
int q = 2*items[i];
|
|
if(a == lt) q -= (2*cv-1);
|
|
if(a == itEmerald && cwt.c->land == laCrossroads) q -= 5;
|
|
if(q < bq) bq = q, cq = 0;
|
|
if(q == bq) { cq++; if(rand() % cq == 0) best = i; }
|
|
}
|
|
return best;
|
|
}
|
|
|
|
extern int webdisplay;
|
|
|
|
void applyCheat(char u, cell *c = NULL) {
|
|
if(u == 'M' && cwt.c->type == 6) {
|
|
addMessage(XLAT("You summon some Mirages!"));
|
|
cheater++;
|
|
createMirrors(cwt.c, cwt.spin, moMirage),
|
|
createMirages(cwt.c, cwt.spin, moMirage);
|
|
}
|
|
if(u == 'G') {
|
|
addMessage(XLAT("You summon a golem!"));
|
|
cheater++;
|
|
int i = cwt.spin;
|
|
if(passable(cwt.c->mov[i], NULL, false, false))
|
|
cwt.c->mov[i]->monst = moGolem;
|
|
}
|
|
if(u == 'L') {
|
|
do {
|
|
firstland = eLand(firstland+1);
|
|
if(firstland == landtypes) firstland = eLand(2);
|
|
}
|
|
while(firstland == laGameBoard || firstland == laCamelot || firstland == laTemple);
|
|
euclidland = firstland;
|
|
cheater++; addMessage(XLAT("You will now start your games in %1", firstland));
|
|
}
|
|
if(u == 'C') {
|
|
cheater++;
|
|
activateSafety(laCrossroads);
|
|
addMessage(XLAT("Activated the Hyperstone Quest!"));
|
|
for(int i=0; i<itHyperstone; i++) items[i] = 10;
|
|
items[itFernFlower] = 10;
|
|
items[itHyperstone] = 0;
|
|
items[itOrbShield] = 10;
|
|
kills[moYeti] = 20;
|
|
kills[moDesertman] = 20;
|
|
kills[moRunDog] = 20;
|
|
kills[moZombie] = 20;
|
|
kills[moMonkey] = 20;
|
|
kills[moCultist] = 20;
|
|
kills[moTroll] = 20;
|
|
}
|
|
if(u == 'P') {
|
|
for(int i=0; i<ittypes; i++)
|
|
if(itemclass(eItem(i)) == IC_ORB)
|
|
items[i] = 0;
|
|
cheater++; addMessage(XLAT("Orb power depleted!"));
|
|
}
|
|
if(u == 'O') {
|
|
cheater++; addMessage(XLAT("Orbs summoned!"));
|
|
for(int i=0; i<cwt.c->type; i++)
|
|
if(passable(cwt.c->mov[i], NULL, false, false)) {
|
|
eItem it = nextOrb();
|
|
cwt.c->mov[i]->item = it;
|
|
}
|
|
}
|
|
if(u == 'F') {
|
|
items[itOrbFlash] += 1;
|
|
items[itOrbTeleport] += 1;
|
|
items[itOrbLightning] += 1;
|
|
items[itOrbSpeed] += 1;
|
|
items[itOrbShield] += 1;
|
|
cheater++; addMessage(XLAT("Orb power gained!"));
|
|
}
|
|
if(u == 'D') {
|
|
items[itGreenStone] += 10;
|
|
cheater++; addMessage(XLAT("Dead orbs gained!"));
|
|
}
|
|
if(u == 'Y') {
|
|
items[itOrbYendor] ++;
|
|
cheater++; addMessage(XLAT("Orb of Yendor gained!"));
|
|
}
|
|
if(u == 'T') {
|
|
items[randomTreasure2(10)] += 10;
|
|
cheater++; addMessage(XLAT("Treasure gained!"));
|
|
}
|
|
if(u == 'T'-64) {
|
|
items[randomTreasure2(100)] += 100;
|
|
cheater++; addMessage(XLAT("Lots of treasure gained!"));
|
|
}
|
|
if(u == 'W') {
|
|
addMessage(XLAT("You summon a sandworm!"));
|
|
cheater++;
|
|
int i = cwt.spin;
|
|
if(passable(cwt.c->mov[i], NULL, false, false))
|
|
cwt.c->mov[i]->monst = moWorm,
|
|
cwt.c->mov[i]->mondir = NODIR;
|
|
}
|
|
if(u == 'I') {
|
|
addMessage(XLAT("You summon an Ivy!"));
|
|
cheater++;
|
|
int i = cwt.spin;
|
|
int j = cwt.c->spn[i];
|
|
cell* c = cwt.c->mov[i]->mov[(j+3)%cwt.c->mov[i]->type];
|
|
if(passable(c, NULL, false, false)) buildIvy(c, 0, 1);
|
|
}
|
|
if(u == 'E') {
|
|
addMessage(XLAT("You summon a monster!"));
|
|
cheater++;
|
|
int i = cwt.spin;
|
|
if(cwt.c->mov[i]->wall == waChasm)
|
|
cwt.c->mov[i]->wall = waNone;
|
|
if(passable(cwt.c->mov[i], NULL, true, false)) {
|
|
eMonster mo[7] = { moEagle, moPyroCultist, moGhost, moTroll, moKnight, moMiner, moVineBeast };
|
|
cwt.c->mov[i]->monst = mo[rand() % 7];
|
|
}
|
|
}
|
|
if(u == 'H') {
|
|
addMessage(XLAT("You summon some Thumpers!"));
|
|
cheater++;
|
|
for(int i=0; i<cwt.c->type; i++)
|
|
if(passable(cwt.c->mov[i], NULL, false, false))
|
|
cwt.c->mov[i]->wall = rand() % 2 ? waThumper : waBigStatue, cwt.c->mov[i]->tmp = -1;
|
|
}
|
|
if(u == 'B') {
|
|
addMessage(XLAT("You summon a bonfire!"));
|
|
cheater++;
|
|
int i = cwt.spin;
|
|
if(passable(cwt.c->mov[i], NULL, false, false))
|
|
cwt.c->mov[i]->wall = waBonfire, cwt.c->mov[i]->tmp = -1;
|
|
}
|
|
if(u == 'Z') {
|
|
cwt.spin++; flipplayer = false;
|
|
cwt.spin %= cwt.c->type;
|
|
}
|
|
if(u == 'J') {
|
|
if(items[localTreasureType()] > 0)
|
|
items[localTreasureType()] = 0;
|
|
else for(int i=1; i<ittypes; i++)
|
|
if(itemclass(eItem(i)) == IC_TREASURE)
|
|
items[i] = 0;
|
|
cheater++; addMessage(XLAT("Treasure lost!"));
|
|
}
|
|
if(u == 'K') {
|
|
for(int i=0; i<motypes; i++) kills[i] += 10;
|
|
cheater++; addMessage(XLAT("Kills gained!"));
|
|
}
|
|
if(u == 'S') {
|
|
activateSafety(cwt.c->land);
|
|
items[itOrbSafety] += 3;
|
|
cheater++; addMessage(XLAT("Activated Orb of Safety!"));
|
|
}
|
|
if(u == 'U') {
|
|
activateSafety(firstland);
|
|
cheater++; addMessage(XLAT("Teleported to %1!", firstland));
|
|
}
|
|
if(u == 'W'-64) {
|
|
webdisplay++;
|
|
cheater++; addMessage(XLAT("Cheat-changed the display.", firstland));
|
|
}
|
|
}
|
|
|
|
void generateAlts(heptagon *h) {
|
|
if(!h->alt) return;
|
|
h->c7->bardir = NOBARRIERS;
|
|
for(int i=0; i<7; i++) if(h->c7->mov[i])
|
|
h->c7->mov[i]->bardir = NOBARRIERS;
|
|
for(int i=0; i<7; i++)
|
|
createStep(h->alt, i)->alt = h->alt->alt;
|
|
int relspin = -4; // for horocycles it must go the other way
|
|
for(int i=0; i<7; i++) for(int j=0; j<7; j++) {
|
|
createStep(h, i);
|
|
if(h->move[i]->alt == h->alt->move[j])
|
|
relspin = (i-j+7) % 7;
|
|
}
|
|
if(relspin == -4) {
|
|
if(h->alt != h->alt->alt) {
|
|
printf("relspin {%p:%p}\n", h->alt, h->alt->alt);
|
|
for(int i=0; i<7; i++) printf("%p ", h->alt->move[i]); printf(" ALT\n");
|
|
for(int i=0; i<7; i++) printf("%p ", h->move[i]); printf(" REAL\n");
|
|
for(int i=0; i<7; i++) printf("%p ", h->move[i]->alt); printf(" REAL ALT\n");
|
|
}
|
|
relspin = 3;
|
|
}
|
|
// h[relspin] matches alt[0]
|
|
//printf("{%d~%d}\n", h->distance, h->alt->distance);
|
|
for(int i=0; i<7; i++) {
|
|
int ir = (7+i-relspin)%7;
|
|
heptagon *hm = h->alt->move[ir];
|
|
heptagon *ho = createStep(h, i);
|
|
// printf("[%p:%d ~ %p:%d] %p ~ %p\n",
|
|
// h, i, h->alt, ir,
|
|
// ho, hm);
|
|
if(ho->alt && ho->alt != hm) {
|
|
if(ho->alt->alt == hm->alt) {
|
|
printf("ERROR: alt cross! [%d -> %d]\n", ho->alt->distance, hm->distance);
|
|
// exit(1);
|
|
}
|
|
continue;
|
|
}
|
|
ho->alt = hm;
|
|
}
|
|
}
|
|
|
|
heptagon *createAlternateMap(cell *c, int rad, hstate firststate) {
|
|
|
|
// check for direction
|
|
int gdir = -1;
|
|
for(int i=0; i<c->type; i++) {
|
|
if(c->mov[i] && c->mov[i]->mpdist < c->mpdist) gdir = i;
|
|
}
|
|
if(!gdir) return NULL;
|
|
|
|
// check for non-crossing
|
|
int bd = 2;
|
|
cellwalker bb(c, bd);
|
|
cellwalker bb2 = bb;
|
|
if(!(checkBarriersFront(bb) && checkBarriersBack(bb2))) {
|
|
return NULL;
|
|
}
|
|
|
|
// okay, let's go then!
|
|
cellwalker bf(c, gdir);
|
|
cell *cx[rad+1];
|
|
for(int i=0; i<rad; i++) {
|
|
cx[i] = bf.c;
|
|
if(bf.c->type == 6)
|
|
cwspin(bf, 3);
|
|
else
|
|
cwspin(bf, 3 + rand() % 2);
|
|
cwstep(bf);
|
|
}
|
|
cx[rad] = bf.c;
|
|
heptagon *h = bf.c->master;
|
|
|
|
heptagon *alt = new heptagon;
|
|
allAlts.push_back(alt);
|
|
//printf("new alt {%p}\n", alt);
|
|
alt->s = firststate;
|
|
alt->fjordval = 0;
|
|
for(int i=0; i<7; i++) alt->move[i] = NULL;
|
|
alt->distance = 0;
|
|
alt->c7 = NULL;
|
|
alt->alt = alt;
|
|
h->alt = alt;
|
|
|
|
for(int d=rad; d>=0; d--) {
|
|
generateAlts(cx[d]->master);
|
|
cx[d]->bardir = NOBARRIERS;
|
|
}
|
|
|
|
return alt;
|
|
//for(int d=rad; d>=0; d--) printf("%3d. %p {%d}\n", d, cx[d]->master, cx[d]->master->alt->distance);
|
|
}
|