mirror of
https://github.com/zenorogue/hyperrogue.git
synced 2024-11-17 18:54:48 +00:00
10187 lines
302 KiB
C++
10187 lines
302 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;
|
|
|
|
bool hardcore = false;
|
|
int hardcoreAt;
|
|
|
|
bool randomPatternsMode = false;
|
|
int randompattern[landtypes];
|
|
|
|
#define RANDLANDS 14
|
|
eLand randlands[14] = {
|
|
laIce, laDesert, laCaves, laAlchemist, laGraveyard, laPower, laLivefjord, laZebra,
|
|
laRlyeh, laDryForest, laEmerald, laWineyard, laDeadCaves, laRedRock
|
|
};
|
|
|
|
bool seenSevenMines = false;
|
|
|
|
#define PUREHARDCORE_LEVEL 10
|
|
|
|
bool pureHardcore() { return hardcore && hardcoreAt < PUREHARDCORE_LEVEL; }
|
|
|
|
bool canmove = true;
|
|
|
|
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 escaped = false; // escaped the Whirlpool?
|
|
|
|
bool invismove = false, invisfish = false; // last move was invisible [due to Fish]
|
|
|
|
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], hiitems[ittypes], kills[motypes], explore[10], exploreland[10][landtypes], landcount[landtypes];
|
|
bool orbused[ittypes], lastorbused[ittypes];
|
|
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*> offscreen; // offscreen cells to take care off
|
|
|
|
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, hexsnakes;
|
|
|
|
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
|
|
|
|
// heat <-> Celsius calculations:
|
|
|
|
bool isIcyLand(eLand l) {
|
|
return l == laIce || l == laCocytus;
|
|
}
|
|
|
|
bool isIcyLand(cell *c) {
|
|
return isIcyLand(c->land);
|
|
}
|
|
|
|
inline float& HEAT(cell *c) {
|
|
return c->LHU.heat;
|
|
}
|
|
|
|
#include "mtrand.cpp"
|
|
|
|
MTRand_int32 r;
|
|
|
|
void shrand(int i) {
|
|
r.seed(i);
|
|
}
|
|
|
|
int hrand(int i) {
|
|
return r() % i;
|
|
}
|
|
|
|
double absheat(cell *c) {
|
|
if(c->land == laCocytus) return HEAT(c) -.6;
|
|
if(c->land == laIce) return HEAT(c) -.4;
|
|
return 0;
|
|
}
|
|
|
|
double celsius(cell *c) { return absheat(c) * 60; }
|
|
|
|
// watery
|
|
|
|
bool isWatery(cell *c) {
|
|
return c->wall == waCamelotMoat || c->wall == waSea || c->wall == waLake;
|
|
}
|
|
|
|
bool isWateryOrBoat(cell *c) {
|
|
return isWatery(c) || c->wall == waBoat;
|
|
}
|
|
|
|
bool boatStrandable(cell *c) {
|
|
return c->wall == waNone && (c->land == laLivefjord || c->land == laOcean);
|
|
}
|
|
|
|
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->landparam = 0; c->landflags = 0;
|
|
c->wall = waNone;
|
|
c->item = itNone;
|
|
c->monst = moNone;
|
|
c->bardir = NODIR;
|
|
c->land = laNone;
|
|
c->ligon = 0;
|
|
c->stuntime = 0;
|
|
lastexplore = shmup::on ? shmup::curtime : turncount;
|
|
}
|
|
|
|
// 0 = basic treasure, 1 = something else, 2 = power orb
|
|
#define IC_TREASURE 0
|
|
#define IC_OTHER 1
|
|
#define IC_ORB 2
|
|
|
|
bool isElementalShard(eItem i) {
|
|
return
|
|
i == itFireShard || i == itAirShard || i == itEarthShard || i == itWaterShard;
|
|
}
|
|
|
|
eMonster elementalOf(eLand l) {
|
|
if(l == laEFire) return moFireElemental;
|
|
if(l == laEWater) return moWaterElemental;
|
|
if(l == laEAir) return moAirElemental;
|
|
if(l == laEEarth) return moEarthElemental;
|
|
return moNone;
|
|
}
|
|
|
|
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 || i == itPirate || i == itRedGem || i == itBombEgg ||
|
|
i == itCoast || i == itWhirlpool || i == itPalace || i == itFjord ||
|
|
i == itElemental || i == itZebra || i == itEdge)
|
|
return IC_TREASURE;
|
|
if(i == itKey || i == itOrbYendor || i == itGreenStone || i == itHolyGrail || i == itCompass ||
|
|
i == itSavedPrincess || isElementalShard(i))
|
|
return IC_OTHER;
|
|
return IC_ORB;
|
|
}
|
|
|
|
bool itemHidden(cell *c) {
|
|
return isWatery(c);
|
|
}
|
|
|
|
bool itemHiddenFromSight(cell *c) {
|
|
return isWatery(c) && !items[itOrbInvis] && !(items[itOrbFish] && isWatery(cwt.c));
|
|
}
|
|
|
|
int puregold() {
|
|
int i = items[itOrbYendor] * 50 + items[itHolyGrail] * 10;
|
|
if(items[itOrbLove]) i += 30;
|
|
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 laCrossroads2: return itHyperstone;
|
|
case laCrossroads3: return itHyperstone;
|
|
|
|
case laNone: return itNone;
|
|
case laBarrier: return itNone;
|
|
case laOceanWall: return itNone;
|
|
case laCanvas: return itNone;
|
|
|
|
case laEmerald: 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;
|
|
|
|
case laCaribbean: return itPirate;
|
|
case laRedRock: return itRedGem;
|
|
|
|
case laMinefield: return itBombEgg;
|
|
case laOcean: return itCoast;
|
|
case laWhirlpool: return itWhirlpool;
|
|
case laPalace: return itPalace;
|
|
case laLivefjord: return itFjord;
|
|
|
|
case laEdge: return itEdge;
|
|
case laZebra: return itZebra;
|
|
|
|
case laEAir: case laEEarth: case laEWater: case laEFire:
|
|
case laElementalWall: return itElemental;
|
|
|
|
case laPrincessQuest: return itSavedPrincess;
|
|
}
|
|
return itNone;
|
|
}
|
|
|
|
#define ORBLINES 33
|
|
|
|
struct orbinfo {
|
|
eLand l;
|
|
int lchance;
|
|
int gchance;
|
|
eItem orb;
|
|
};
|
|
|
|
orbinfo orbinfos[ORBLINES] = {
|
|
{laJungle, 1200, 1500,itOrbLightning},
|
|
{laIce, 2000, 1500,itOrbFlash},
|
|
{laCaves, 1800, 2000,itOrbLife},
|
|
{laAlchemist, 800, 800,itOrbSpeed},
|
|
{laGraveyard, 200, 200,itGreenStone},
|
|
{laDesert, 2500, 900,itOrbShield},
|
|
{laHell, 2000, 1000,itOrbYendor},
|
|
{laRlyeh, 1500, 1500,itOrbTeleport},
|
|
{laMotion, 2000, 700, itOrbSafety},
|
|
{laIce, 1500, 0, itOrbWinter},
|
|
{laDryForest, 2500, 0, itOrbWinter},
|
|
{laCocytus, 1500, 1500, itOrbWinter},
|
|
{laCaves, 1200, 0, itOrbDigging},
|
|
{laDryForest, 500, 2000, itOrbThorns},
|
|
{laDeadCaves, 1800, 0, itGreenStone},
|
|
{laDeadCaves, 1800, 1500, itOrbDigging},
|
|
{laEmerald, 1500, 3500, itOrbPsi},
|
|
{laWineyard, 900, 1200, itOrbGhost},
|
|
{laHive, 800, 1200, itOrbInvis},
|
|
{laPower, 0, 3000, itOrbFire},
|
|
{laMinefield, 0, 3500, itOrbFriend},
|
|
{laTemple, 0, 3000, itOrbDragon},
|
|
{laCaribbean, 0, 3500, itOrbPreserve},
|
|
{laRedRock, 0, 2500, itOrbTelekinesis},
|
|
{laCamelot, 1000, 1500, itOrbIllusion},
|
|
{laOcean, 0, 3000, itOrbAir},
|
|
{laPalace, 0, 4000, itOrbDiscord},
|
|
{laZebra, 500, 1500, itOrbFrog},
|
|
{laLivefjord, 0, 1800, itOrbFish},
|
|
{laPrincessQuest, 0, 2500, itOrbLove},
|
|
{laWhirlpool, 0, 2000, itOrbWater},
|
|
{laEdge, 500, 4000, itOrbMatter},
|
|
{laElementalWall, 1500, 4000, itOrbSummon},
|
|
};
|
|
|
|
bool isElemental(eLand l);
|
|
|
|
eItem orbType(eLand l) {
|
|
if(isElemental(l)) l = laElementalWall;
|
|
for(int i=0; i<ORBLINES; i++)
|
|
if(orbinfos[i].l == l && orbinfos[i].gchance)
|
|
return orbinfos[i].orb;
|
|
return itNone;
|
|
}
|
|
|
|
enum eOrbLandRelation {
|
|
olrForbidden, // never appears: forbidden
|
|
olrDangerous, // never appears: would be dangerous
|
|
olrUseless, // never appears: useless here
|
|
olrNoPrizes, // no prizes in this land
|
|
olrNoPrizeOrb,// orb not allowed as a prize
|
|
olrPrize25, // prize for collecting 25
|
|
olrPrize3, // prize for collecting 3
|
|
olrNative, // native orb in this land
|
|
olrNative1, // native orb in this land (1)
|
|
olrGuest, // extra orb in this land
|
|
olrPNative, // Land of Power: native
|
|
olrPBasic, // Land of Power: basic orbs
|
|
olrPPrized, // Land of Power: prized orbs
|
|
olrPNever, // Land of Power: foreign orbs
|
|
olrHub, // hub lands
|
|
olrMonster // available from a monster
|
|
};
|
|
|
|
string olrDescriptions[] = {
|
|
"forbidden to find in %the1",
|
|
"too dangerous to use in %the1",
|
|
"useless in %the1",
|
|
"only native Orbs allowed in %the1",
|
|
"this Orb is never unlocked globally (only hubs)",
|
|
"collect 25 %2 to unlock it in %the1",
|
|
"collect 3 %2 to unlock it in %the1",
|
|
"native in %the1 (collect 10 %2)",
|
|
"native in %the1 (collect 1 %2)",
|
|
"secondary in %the1 (collect 10 %3, or 25 %2)",
|
|
"the native Orb of %the1",
|
|
"this Orb appears on floors and is used by witches",
|
|
"a prized Orb, it appears only in cabinets",
|
|
"this Orb never appears in %the1",
|
|
"Hub Land: orbs appear here if unlocked in their native land",
|
|
"kill a monster, or collect 25 %2"
|
|
};
|
|
|
|
eOrbLandRelation getOLR(eItem it, eLand l) {
|
|
|
|
if(l == laPower) {
|
|
if(it == itOrbFire) return olrPNative;
|
|
|
|
if(
|
|
it == itOrbFlash || it == itOrbSpeed || it == itOrbWinter || it == itOrbGhost ||
|
|
it == itOrbLife) return olrPBasic;
|
|
|
|
if(
|
|
it == itOrbLightning || it == itOrbThorns || it == itOrbInvis ||
|
|
it == itOrbShield || it == itOrbTeleport || it == itOrbPsi ||
|
|
it == itOrbDragon || it == itOrbIllusion || it == itOrbPreserve)
|
|
return olrPPrized;
|
|
|
|
return olrPNever;
|
|
}
|
|
|
|
if(it == itOrbYendor && l == laWhirlpool) return olrUseless;
|
|
|
|
if(it == itOrbAir && l == laAlchemist) return olrUseless;
|
|
// if(it == itOrbShield && l == laMotion) return olrUseless;
|
|
|
|
if(it == itOrbIllusion && l == laCamelot) return olrNative1;
|
|
if(it == itOrbLove) return olrNoPrizeOrb;
|
|
if(orbType(l) == it) return olrNative;
|
|
if(it == itOrbWinter && (l == laIce || l == laDryForest))
|
|
return olrGuest;
|
|
if(it == itOrbDigging && l == laCaves)
|
|
return olrGuest;
|
|
if(it == itOrbFrog && (l == laPalace || l == laPrincessQuest))
|
|
return olrGuest;
|
|
if(it == itOrbDragon && l == laRlyeh)
|
|
return olrMonster;
|
|
if(it == itOrbWater && l == laLivefjord)
|
|
return olrMonster;
|
|
if(isCrossroads(l) || l == laOcean)
|
|
return olrHub;
|
|
|
|
if(l == laCocytus)
|
|
if(it == itOrbDragon || it == itOrbFire || it == itOrbFlash || it == itOrbLightning)
|
|
return olrDangerous;
|
|
|
|
if(it == itOrbSafety)
|
|
if(l == laCaves || l == laLivefjord || l == laRedRock || l == laCocytus || l == laHell ||
|
|
l == laDesert || l == laAlchemist || l == laDeadCaves || l == laMinefield)
|
|
return olrDangerous;
|
|
|
|
if(it == itOrbMatter)
|
|
if(l == laCaves || l == laEmerald || l == laAlchemist || l == laCaribbean ||
|
|
l == laMinefield || l == laCocytus) return olrUseless;
|
|
|
|
if(l == laPrincessQuest)
|
|
if(it == itOrbGhost || it == itOrbFlash || it == itOrbTeleport || it == itOrbSummon)
|
|
return olrForbidden;
|
|
|
|
if(l == laTemple)
|
|
return olrNoPrizes;
|
|
|
|
if(it == itOrbDigging) {
|
|
if(l == laCaves || l == laOcean || l == laLivefjord || l == laEmerald ||
|
|
l == laDesert || l == laDeadCaves || l == laRedRock || l == laCaribbean || l == laGraveyard)
|
|
return olrPrize25;
|
|
return olrUseless;
|
|
}
|
|
|
|
if(it == itShard) {
|
|
if(l == laDesert || l == laIce || l == laJungle || l == laGraveyard ||
|
|
l == laRlyeh || l == laHell || l == laDryForest || l == laWineyard ||
|
|
l == laHive || l == laCamelot || l == laRedRock || l == laPalace ||
|
|
l == laLivefjord || l == laZebra || isElemental(l) || l == laPrincessQuest)
|
|
return olrPrize25;
|
|
return olrForbidden;
|
|
}
|
|
|
|
if(it == itOrbWater)
|
|
if(l == laMotion || l == laZebra || l == laEdge)
|
|
return olrUseless;
|
|
|
|
if(it == itOrbWinter && l != laRlyeh && l != laTemple)
|
|
return olrUseless;
|
|
|
|
if(it == itOrbLife && l == laMotion)
|
|
return olrUseless;
|
|
|
|
if(it == itOrbFish && l != laOcean && l != laLivefjord && l != laWhirlpool && l != laCamelot)
|
|
return olrUseless;
|
|
|
|
if(it == itOrbIllusion) return olrPrize3;
|
|
|
|
return olrPrize25;
|
|
}
|
|
|
|
int gold();
|
|
int tkills();
|
|
bool hellUnlocked();
|
|
|
|
int landMultiplier(eLand l) {
|
|
if(l == laCamelot || l == laPrincessQuest) return 10;
|
|
return 1;
|
|
}
|
|
|
|
// 2 = always available, 1 = highscore required, 0 = never available
|
|
int isRandland(eLand l) {
|
|
if(l == laIce || l == laDesert || l == laCaves)
|
|
return 2;
|
|
for(int i=0; i<RANDLANDS; i++) if(randlands[i] == l) return 1;
|
|
return 0;
|
|
}
|
|
|
|
bool landUnlocked(eLand l) {
|
|
if(randomPatternsMode) {
|
|
int i = isRandland(l);
|
|
if(i == 2) return true;
|
|
if(i == 1) return hiitems[treasureType(l)] >= 10;
|
|
return false;
|
|
}
|
|
|
|
switch(l) {
|
|
case laIce: case laJungle: case laCaves: case laDesert:
|
|
case laMotion: case laCrossroads:
|
|
return true;
|
|
|
|
case laMirror: case laMinefield: case laAlchemist: case laPalace:
|
|
case laOcean: case laLivefjord:
|
|
return gold() >= 30;
|
|
|
|
case laCaribbean: case laWhirlpool:
|
|
return exploreland[0][laOcean] || items[itCoast] || items[itStatue];
|
|
|
|
case laRlyeh: case laDryForest: case laWineyard: case laCrossroads2:
|
|
return gold() >= 60;
|
|
|
|
case laDeadCaves:
|
|
return gold() >= 60 && items[itGold] >= 10;
|
|
|
|
case laGraveyard:
|
|
return tkills() >= 100;
|
|
|
|
case laHive:
|
|
return tkills() >= 100 && gold() >= 60;
|
|
|
|
case laRedRock:
|
|
return gold() >= 60 && items[itSpice] >= 10;
|
|
|
|
case laEmerald:
|
|
return (items[itFernFlower] >= 5 && items[itGold] >= 5) || kills[moVizier];
|
|
|
|
case laCamelot:
|
|
return items[itEmerald] >= 5;
|
|
|
|
case laHell: case laCrossroads3:
|
|
return hellUnlocked();
|
|
|
|
case laCocytus: case laPower:
|
|
return items[itHell] >= 10;
|
|
|
|
case laTemple:
|
|
return items[itStatue] >= 5;
|
|
|
|
case laEdge: return gold() >= 60;
|
|
case laZebra: return gold() >= 30 && items[itFeather] >= 10;
|
|
|
|
case laEAir: case laEEarth: case laEWater: case laEFire: case laElementalWall:
|
|
return
|
|
kills[moFireElemental] || kills[moWaterElemental] ||
|
|
kills[moEarthElemental] || kills[moAirElemental];
|
|
|
|
case laBarrier: case laNone: case laOceanWall: case laCanvas:
|
|
return false;
|
|
|
|
case laPrincessQuest: return kills[moVizier];
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int numplayers() {
|
|
if(shmup::on) return shmup::players;
|
|
return 1;
|
|
}
|
|
|
|
cell *playerpos(int i) {
|
|
if(!shmup::on) return cwt.c;
|
|
return shmup::playerpos(i);
|
|
}
|
|
|
|
bool isPlayerOn(cell *c) {
|
|
if(!shmup::on) return c == cwt.c;
|
|
for(int i=0; i<numplayers(); i++) if(playerpos(i) == c) return true;
|
|
return false;
|
|
}
|
|
|
|
bool isPlayerInBoatOn(cell *c) {
|
|
if(!shmup::on) return c == cwt.c && (c->wall == waBoat || c->wall == waStrandedBoat);
|
|
for(int i=0; i<numplayers(); i++)
|
|
if(playerpos(i) == c && (
|
|
c->wall == waBoat || c->wall == waStrandedBoat || shmup::playerInBoat(i)
|
|
))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
bool playerInPower() {
|
|
if(!shmup::on) return cwt.c->land == laPower;
|
|
for(int i=0; i<numplayers(); i++) if(playerpos(i)->land == laPower)
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
eItem localTreasureType() {
|
|
lastland = cwt.c->land;
|
|
return treasureType(lastland);
|
|
}
|
|
|
|
void countLocalTreasure() {
|
|
eItem i = localTreasureType();
|
|
currentLocalTreasure = i ? items[i] : 0;
|
|
}
|
|
|
|
int orbsUnlocked() {
|
|
int i = 0;
|
|
for(int t=0; t<ittypes; t++)
|
|
if(itemclass(eItem(t)) == IC_TREASURE && items[t] >= 10)
|
|
i++;
|
|
return i;
|
|
}
|
|
|
|
bool hellUnlocked() {
|
|
return orbsUnlocked() >= 9;
|
|
}
|
|
|
|
void countHyperstoneQuest(int& i1, int& i2) {
|
|
i1 = 0; i2 = 0;
|
|
for(int t=1; t<ittypes; t++)
|
|
if(t != itHyperstone && itemclass(eItem(t)) == IC_TREASURE) {
|
|
i2++; if(items[t] >= 10) i1++;
|
|
}
|
|
}
|
|
|
|
bool hyperstonesUnlocked() {
|
|
int i1, i2;
|
|
countHyperstoneQuest(i1, i2);
|
|
return i1 == i2;
|
|
}
|
|
|
|
bool markOrb(eItem it) {
|
|
if(!items[it]) return false;
|
|
orbused[it] = true;
|
|
return true;
|
|
}
|
|
|
|
bool markOrb2(eItem it) {
|
|
if(!items[it]) return false;
|
|
orbused[it] = true;
|
|
return items[it] > 1;
|
|
}
|
|
|
|
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] +
|
|
kills[moPirate] + kills[moCShark] + kills[moParrot] +
|
|
kills[moHexSnake] + kills[moRedTroll] +
|
|
kills[moPalace] + kills[moSkeleton] + kills[moFatGuard] + kills[moVizier] +
|
|
kills[moViking] + kills[moFjordTroll] + kills[moWaterElemental] +
|
|
kills[moAlbatross] +
|
|
kills[moAirElemental] + kills[moFireElemental] +
|
|
kills[moGargoyle] + kills[moEdgeMonkey] + kills[moOrangeDog]
|
|
;
|
|
}
|
|
|
|
// monster/wall types
|
|
|
|
bool isFire(cell *w) {
|
|
return w->wall == waFire || w->wall == waPartialFire || w->wall == waEternalFire;
|
|
}
|
|
|
|
bool isThumper(eWall w) {
|
|
return w == waThumperOff || w == waThumperOn;
|
|
}
|
|
|
|
bool isThumper(cell *c) {
|
|
return isThumper(c->wall);
|
|
}
|
|
|
|
bool isActivable(cell *c) {
|
|
return c->wall == waThumperOff || c->wall == waBonfireOff;
|
|
}
|
|
|
|
bool hasTimeout(cell *c) {
|
|
return c->wall == waThumperOn || c->wall == waFire || c->wall == waPartialFire ||
|
|
c->wall == waTempWall || c->wall == waTempFloor || c->wall == waTempBridge;
|
|
}
|
|
|
|
bool isMimic(eMonster m) {
|
|
return m == moMirror || m == moMirage;
|
|
}
|
|
|
|
bool isMimic(cell *c) {
|
|
return isMimic(c->monst);
|
|
}
|
|
|
|
bool isPrincess(eMonster m) {
|
|
return
|
|
m == moPrincess || m == moPrincessMoved ||
|
|
m == moPrincessArmed || m == moPrincessArmedMoved;
|
|
}
|
|
|
|
bool isGolemOrKnight(eMonster m) {
|
|
return
|
|
m == moGolem || m == moGolemMoved ||
|
|
m == moKnight || m == moKnightMoved ||
|
|
m == moTameBomberbird || m == moTameBomberbirdMoved ||
|
|
m == moMouse || m == moMouseMoved ||
|
|
isPrincess(m);
|
|
}
|
|
|
|
bool isGolemOrKnight(cell *c) { return isGolemOrKnight(c->monst); }
|
|
|
|
bool isNonliving(eMonster m) {
|
|
return
|
|
m == moMirage || m == moMirror || m == moGolem || m == moGolemMoved ||
|
|
m == moZombie || m == moGhost || m == moShadow || m == moSkeleton ||
|
|
m == moEvilGolem || m == moIllusion || m == moEarthElemental ||
|
|
m == moWaterElemental;
|
|
}
|
|
|
|
bool isStunnable(eMonster m) {
|
|
return m == moPalace || m == moFatGuard || m == moSkeleton || isPrincess(m);
|
|
}
|
|
|
|
bool hasHitpoints(eMonster m) {
|
|
return m == moPalace || m == moFatGuard || m == moVizier || isPrincess(m);
|
|
}
|
|
|
|
bool isFriendly(eMonster m) {
|
|
return isMimic(m) || isGolemOrKnight(m) || m == moIllusion;
|
|
}
|
|
|
|
bool isFriendly(cell *c) { return isFriendly(c->monst); }
|
|
|
|
bool isBug(eMonster m) {
|
|
return m >= moBug0 && m < moBug0+BUGCOLORS;
|
|
}
|
|
|
|
bool isBug(cell *c) {
|
|
return isBug(c->monst);
|
|
}
|
|
|
|
bool isKillable(cell *c);
|
|
bool isKillableSomehow(cell *c);
|
|
|
|
bool isFriendlyOrBug(cell *c) { // or killable discord!
|
|
// do not attack the stunned Princess
|
|
if(isPrincess(c->monst) && c->stuntime) return false;
|
|
return isFriendly(c) || isBug(c) || (c->monst && markOrb(itOrbDiscord) && isKillable(c) && !c->stuntime);
|
|
}
|
|
|
|
bool isFriendlyOrBugS(cell *c) { // or killable discord!
|
|
// do not attack the stunned Princess
|
|
if(isPrincess(c->monst) && c->stuntime) return false;
|
|
return isFriendly(c) || isBug(c) || (c->monst && markOrb(itOrbDiscord) && isKillableSomehow(c) && !c->stuntime);
|
|
}
|
|
|
|
bool isIvy(eMonster m) {
|
|
return m == moIvyRoot || m == moIvyHead || m == moIvyBranch || m == moIvyWait ||
|
|
m == moIvyNext || m == moIvyDead;
|
|
}
|
|
|
|
bool isIvy(cell *c) {
|
|
return isIvy(c->monst);
|
|
}
|
|
|
|
bool isDemon(eMonster m) {
|
|
return m == moLesser || m == moLesserM || m == moGreater || m == moGreaterM;
|
|
}
|
|
|
|
bool isDemon(cell *c) {
|
|
return isDemon(c->monst);
|
|
}
|
|
|
|
bool isWorm(eMonster m) {
|
|
return m == moWorm || m == moWormtail || m == moWormwait ||
|
|
m == moTentacle || m == moTentacletail || m == moTentaclewait ||
|
|
m == moTentacleEscaping || m == moTentacleGhost ||
|
|
m == moHexSnake || m == moHexSnakeTail;
|
|
}
|
|
|
|
bool isWorm(cell *c) {
|
|
return isWorm(c->monst);
|
|
}
|
|
|
|
bool isWitch(eMonster m) {
|
|
// evil golems don't count
|
|
return m >= moWitch && m < moWitch+NUMWITCH-1;
|
|
}
|
|
|
|
bool isOnCIsland(cell *c) {
|
|
return (c->wall == waCIsland || c->wall == waCTree || c->wall == waCIsland2);
|
|
}
|
|
|
|
bool cellUnstable(cell *c) {
|
|
return (c->land == laMotion && c->wall == waNone) || c->wall == waTrapdoor;
|
|
}
|
|
|
|
bool ignoresPlates(eMonster m) {
|
|
return m == moMouse;
|
|
}
|
|
|
|
bool cellUnstableOrChasm(cell *c) {
|
|
return
|
|
(c->land == laMotion && c->wall == waNone) ||
|
|
c->wall == waChasm || c->wall == waTrapdoor;
|
|
}
|
|
|
|
bool cellHalfvine(cell *c) {
|
|
return c->wall == waVineHalfA || c->wall == waVineHalfB;
|
|
}
|
|
|
|
bool thruVine(cell *c, cell *c2) {
|
|
return cellHalfvine(c) && c2->wall == c->wall && c2 != c;
|
|
}
|
|
|
|
// === MOVEMENT FUNCTIONS ===
|
|
|
|
bool againstWind(cell *c2, cell *c1);
|
|
|
|
// w = from->mov[d]
|
|
bool againstCurrent(cell *w, cell *from) {
|
|
if(from->land != laWhirlpool) return false;
|
|
if(againstWind(from, w)) return false; // wind is stronger than current
|
|
if(!euclid && (!from->master->alt || !w->master->alt)) return false;
|
|
int dfrom = celldistAlt(from);
|
|
int dw = celldistAlt(w);
|
|
if(dw < dfrom) return false;
|
|
if(dfrom < dw) return true;
|
|
for(int d=0; d<from->type; d++)
|
|
if(from->mov[d] == w) {
|
|
cell *c3 = from->mov[(d+from->type-1) % from->type];
|
|
if(!c3) return false;
|
|
return celldistAlt(c3) < dfrom;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool boatGoesThrough(cell *c) {
|
|
if(c->land == laEdge) return false;
|
|
return
|
|
(c->wall == waNone && c->land != laMotion && c->land != laZebra) ||
|
|
c->wall == waFloorA || c->wall == waFloorB ||
|
|
c->wall == waCavefloor || c->wall == waFrozenLake ||
|
|
c->wall == waDeadfloor || c->wall == waCIsland || c->wall == waCIsland2 ||
|
|
c->wall == waMineUnknown || c->wall == waMineMine || c->wall == waMineOpen ||
|
|
c->wall == waBonfireOff || c->wall == waFire || c->wall == waPartialFire;
|
|
}
|
|
|
|
void placeWater(cell *c, cell *c2) {
|
|
if(isWatery(c)) ;
|
|
else if(c2 && (c2->wall == waFloorA || c2->wall == waFloorB))
|
|
c->wall = c2->wall;
|
|
else if(isIcyLand(c))
|
|
c->wall = waLake;
|
|
else
|
|
c->wall = waSea;
|
|
}
|
|
|
|
bool realred(eWall w) {
|
|
return w == waRed1 || w == waRed2 || w == waRed3;
|
|
}
|
|
|
|
int snakelevel(eWall w) {
|
|
if(w == waRed1 || w == waDeadfloor2 || w == waRubble || w == waGargoyleFloor ||
|
|
w == waGargoyleBridge || w == waTempFloor || w == waTempBridge)
|
|
return 1;
|
|
if(w == waRed2) return 2;
|
|
if(w == waRed3) return 3;
|
|
return 0;
|
|
}
|
|
|
|
int snakelevel(cell *c) { return snakelevel(c->wall); }
|
|
|
|
int incline(cell *cfrom, cell *cto) {
|
|
return snakelevel(cto) - snakelevel(cfrom);
|
|
}
|
|
|
|
bool inclineInRange(cell *cfrom, cell *cto) {
|
|
int i = incline(cfrom, cto);
|
|
return i <= 1 && i >= -2;
|
|
}
|
|
|
|
bool cellEdgeUnstable(cell *c);
|
|
int coastval(cell *c);
|
|
|
|
bool passable(cell *w, cell *from, bool monster_passable, bool mirror_passable, bool revdir) {
|
|
|
|
if(from && !(revdir ? inclineInRange(w, from) : inclineInRange(from, w))) return false;
|
|
|
|
if(from && cellEdgeUnstable(w) && cellEdgeUnstable(from) &&
|
|
coastval(w) != coastval(from) + (revdir?1:-1)) return false;
|
|
|
|
if(from && (revdir ? againstWind(from,w) : againstWind(w, from))) return false;
|
|
|
|
if(revdir && from && w->monst && passable(from, w, false, mirror_passable, false))
|
|
return true;
|
|
|
|
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(from && thruVine(from, w)) 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 ||
|
|
w->wall == waRubble || w->wall == waGargoyleFloor || w->wall == waGargoyleBridge ||
|
|
w->wall == waTempFloor || w->wall == waTempBridge ||
|
|
w->wall == waBoat || w->wall == waCIsland || w->wall == waCIsland2 ||
|
|
w->wall == waRed1 || w->wall == waRed2 || w->wall == waRed3 ||
|
|
w->wall == waMineUnknown || w->wall == waMineMine || w->wall == waMineOpen ||
|
|
w->wall == waStrandedBoat || w->wall == waOpenGate || w->wall == waClosePlate ||
|
|
w->wall == waOpenPlate || w->wall == waTrapdoor || w->wall == waGiantRug ||
|
|
w->wall == waLadder) {
|
|
if(w->monst) return monster_passable;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool blowable0(cell *c2, cell *c) {
|
|
return
|
|
passable(c2, NULL, false, false, false) ||
|
|
c2->wall == waCamelotMoat || isFire(c2) || c2->wall == waChasm ||
|
|
c2->wall == waSea || c2->wall == waLake || c2->wall == waPartialFire ||
|
|
c2->wall == waTrapdoor || c2->wall == waSulphur || c2->wall == waSulphurC;
|
|
}
|
|
|
|
bool blowable(cell *c2, cell *c) {
|
|
if(c2->monst) return false;
|
|
return blowable0(c2, c);
|
|
}
|
|
|
|
bool jumpable(cell *c2, cell *c) {
|
|
if(c2->monst && c2->monst != moMouse) return false;
|
|
return blowable0(c2, c);
|
|
}
|
|
|
|
bool player_passable(cell *w, cell *from, bool mon);
|
|
|
|
bool jumpable2(cell *c, cell *c2) {
|
|
if(!c->monst && c->wall == waNone && c->land == laEdge && c2->wall == waNone)
|
|
return true;
|
|
return player_passable(c, c2, false);
|
|
}
|
|
|
|
extern bool haveair;
|
|
int airdir;
|
|
|
|
int airdist(cell *c) {
|
|
if(!haveair) return 3;
|
|
if(!c) return 3;
|
|
if(c->monst == moAirElemental)
|
|
return 0;
|
|
if(!blowable(c, c)) return 3;
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = c->mov[i];
|
|
if(c2 && c2->monst == moAirElemental) {
|
|
airdir = c->spn[i] * 42 / c2->type;
|
|
return 1;
|
|
}
|
|
}
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = c->mov[i];
|
|
if(!c2) continue;
|
|
if(!blowable(c2, c)) continue;
|
|
if(!blowable(c, c2)) continue;
|
|
for(int i=0; i<c2->type; i++) {
|
|
cell *c3 = c2->mov[i];
|
|
if(c3 && c3->monst == moAirElemental) {
|
|
airdir = c2->spn[i] * 42 / c3->type;
|
|
return 2;
|
|
}
|
|
}
|
|
}
|
|
return 3;
|
|
}
|
|
|
|
bool againstWind(cell *c2, cell *c1) {
|
|
if(!c1 || !c2) return false;
|
|
return airdist(c2) < airdist(c1);
|
|
}
|
|
|
|
bool player_passable(cell *w, cell *from, bool mon) {
|
|
if(w->monst && !isFriendly(w)) return false;
|
|
if(isFire(w) && markOrb(itOrbWinter)) return true;
|
|
if(isWatery(w) && !(from && isWatery(from) && againstCurrent(w, from)) && markOrb(itOrbFish)) return true;
|
|
if(isWatery(w) && from && from->wall == waBoat) {
|
|
if(againstCurrent(w, from))
|
|
return !items[itOrbWater];
|
|
return true;
|
|
}
|
|
if(w->wall == waRoundTable && from && from->wall != waRoundTable)
|
|
return true;
|
|
if(passable(w, from, mon, true, false)) return true;
|
|
return markOrb(itOrbGhost);
|
|
return false;
|
|
}
|
|
|
|
bool isAngryBird(eMonster m) {
|
|
return m == moEagle || m == moAlbatross || m == moBomberbird || m == moGargoyle ||
|
|
m == moAirElemental;
|
|
}
|
|
|
|
bool isBird(eMonster m) {
|
|
return isAngryBird(m) || m == moTameBomberbird || m == moTameBomberbirdMoved;
|
|
}
|
|
|
|
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 ||
|
|
(playerInPower() && (
|
|
(isWitch(m) && m != moWitchGhost && m != moWitchWinter) || m == moEvilGolem
|
|
)) ||
|
|
m == moRedTroll ||
|
|
m == moPalace || m == moFatGuard || m == moSkeleton || m == moVizier ||
|
|
m == moFjordTroll || m == moEdgeMonkey ||
|
|
m == moFireElemental || m == moOrangeDog;
|
|
}
|
|
|
|
// eagles can go through lakes, chasms, and slime
|
|
// todo vines?
|
|
bool eaglepassable(cell *w, cell *from) {
|
|
if(w->monst) return false;
|
|
if(isPlayerOn(w)) return true;
|
|
if(againstWind(w, from)) 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 == waRubble || w->wall == waGargoyleFloor || w->wall == waGargoyleBridge ||
|
|
w->wall == waTempFloor || w->wall == waTempBridge ||
|
|
w->wall == waSulphur || w->wall == waSulphurC || w->wall == waChasm ||
|
|
w->wall == waRed1 || w->wall == waRed2 || w->wall == waRed3 ||
|
|
w->wall == waSea || w->wall == waCIsland || w->wall == waCIsland2 ||
|
|
w->wall == waMineMine || w->wall == waMineOpen || w->wall == waMineUnknown ||
|
|
w->wall == waClosePlate || w->wall == waOpenPlate || w->wall == waOpenGate ||
|
|
w->wall == waTrapdoor || w->wall == waGiantRug || w->wall == waLadder;
|
|
}
|
|
|
|
bool earthpassable(cell *c, cell *from) {
|
|
// cannot go through Living Caves...
|
|
if(c->wall == waCavefloor) return false;
|
|
if(isPlayerOn(c)) return true;
|
|
// but can dig through...
|
|
if(c->wall == waDeadwall || c->wall == waDune || c->wall == waStone)
|
|
return true;
|
|
if(c->wall == waSea && c->land == laLivefjord)
|
|
return true;
|
|
return passable(c, from, true, false, false);
|
|
}
|
|
|
|
// from-to
|
|
|
|
bool isGhost(eMonster m) {
|
|
return m == moGhost || m == moTentacleGhost;
|
|
}
|
|
|
|
bool ghostmove(eMonster m, cell* to, cell* from) {
|
|
if(to->monst && !(to->monst == moTentacletail && isGhost(m)))
|
|
return false;
|
|
if((m == moWitchGhost || m == moWitchWinter) && to->land != laPower)
|
|
return false;
|
|
if(isGhost(m))
|
|
for(int i=0; i<to->type; i++)
|
|
if(to->mov[i] && to->mov[i] != from && isGhost(to->mov[i]->monst))
|
|
return false;
|
|
if(isGhost(m) || m == moWitchGhost) return true;
|
|
if(m == moGreaterShark) return isWatery(to);
|
|
if(m == moWitchWinter) return isFire(to) || passable(to, from, false, false, false);
|
|
if(isPlayerOn(to)) return true;
|
|
return false;
|
|
}
|
|
|
|
bool isGhostMover(eMonster m) {
|
|
return m == moGhost || m == moGreaterShark || m == moTentacleGhost ||
|
|
(playerInPower() && (m == moWitchGhost || m == moWitchWinter));
|
|
}
|
|
|
|
bool isSlimeMover(eMonster m) {
|
|
return
|
|
m == moSlime || m == moSeep || m == moShark ||
|
|
m == moVineSpirit || m == moCShark || m == moParrot;
|
|
}
|
|
|
|
bool isBlowableMonster(eMonster m) {
|
|
return m && !(
|
|
isWorm(m) || isIvy(m) || isSlimeMover(m) || m == moGhost || m == moGreaterShark ||
|
|
m == moWaterElemental || m == moWitchGhost || isMimic(m)
|
|
);
|
|
}
|
|
|
|
bool isSlimeMover(cell *c) {
|
|
return isSlimeMover(c->monst);
|
|
}
|
|
|
|
int slimegroup(cell *c) {
|
|
if(c->wall == waCavewall || c->wall == waDeadwall)
|
|
return 1;
|
|
if(isWatery(c))
|
|
return 2;
|
|
if(c->wall == waFloorA)
|
|
return 3;
|
|
if(c->wall == waFloorB)
|
|
return 4;
|
|
if(c->wall == waVinePlant || cellHalfvine(c))
|
|
return 5;
|
|
if(c->wall == waCTree)
|
|
return 6;
|
|
return 0;
|
|
}
|
|
|
|
bool slimepassable(cell *w, cell *c) {
|
|
int u = dirfromto(c, w);
|
|
if(w == c) return true;
|
|
if(isPlayerOn(w)) return true;
|
|
int group = slimegroup(c);
|
|
if(!group) return false;
|
|
int ogroup = slimegroup(w);
|
|
if(!ogroup) return false;
|
|
bool hv = (group == ogroup);
|
|
|
|
// don't go against the current
|
|
if(isWateryOrBoat(w) && isWateryOrBoat(c))
|
|
return !againstCurrent(w, c);
|
|
|
|
if(w->item) return false;
|
|
|
|
// only travel to halfvines correctly
|
|
if(cellHalfvine(c)) {
|
|
int i=0;
|
|
for(int t=0; t<c->type; t++) if(c->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 false;
|
|
hv=true;
|
|
}
|
|
// only travel from halfvines correctly
|
|
if(cellHalfvine(w)) {
|
|
int i=0;
|
|
for(int t=0; t<w->type; t++) if(w->mov[t] && w->mov[t]->wall == w->wall) i=t;
|
|
int z = i-c->spn[u]; if(z<0) z=-z; z%=6;
|
|
if(z>1) return false;
|
|
hv=true;
|
|
}
|
|
if(!hv) return false;
|
|
return true;
|
|
}
|
|
|
|
bool canPushStatueOn(cell *c) {
|
|
return passable(c, NULL, true, false, false) && c->wall != waBoat && !snakelevel(c);
|
|
}
|
|
|
|
bool boatpassable(cell *c, cell *from) {
|
|
return
|
|
from->wall == waBoat && isWatery(c) && !againstCurrent(c, from);
|
|
}
|
|
|
|
void moveBoat(cell *to, cell *from) {
|
|
eWall x = to->wall; to->wall = from->wall; from->wall = x;
|
|
to->mondir = neighborId(to, from);
|
|
moveItem(from, to, false);
|
|
}
|
|
|
|
void moveBoatIfUsingOne(cell *to, cell *from) {
|
|
if(from->wall == waBoat && isWatery(to)) moveBoat(to, from);
|
|
}
|
|
|
|
bool leaderpassable(cell *c, cell *from) {
|
|
if(isPlayerOn(c)) return true;
|
|
if(c->monst) return false;
|
|
if(isWateryOrBoat(c) && isWateryOrBoat(from) && (c->wall != waBoat || from->wall != waBoat))
|
|
return !againstCurrent(c, from);
|
|
if(c->wall == waBigStatue) return canPushStatueOn(from);
|
|
return passable(c, from, false, false, false);
|
|
}
|
|
|
|
bool isLeader(eMonster m) {
|
|
return m == moPirate || m == moCultistLeader || m == moViking;
|
|
}
|
|
|
|
bool waterpassable(cell *w, cell *from) {
|
|
return isWatery(w) || boatGoesThrough(w) || w->wall == waDeadTroll || isPlayerOn(w);
|
|
}
|
|
|
|
bool passable_for(eMonster m, cell *w, cell *from, bool monster_passable) {
|
|
if(w->monst && !monster_passable)
|
|
return false;
|
|
if(m == moWolf) {
|
|
return isIcyLand(w) && (isPlayerOn(w) || passable(w, from, monster_passable, false, false));
|
|
}
|
|
if(normalMover(m) || isBug(m) || isDemon(m)) {
|
|
if((isWitch(m) || m == moEvilGolem) && w->land != laPower)
|
|
return false;
|
|
return isPlayerOn(w) || passable(w, from, monster_passable, false, false);
|
|
}
|
|
if(isSlimeMover(m))
|
|
return slimepassable(w, from);
|
|
if(m == moEarthElemental)
|
|
return earthpassable(w, from);
|
|
if(m == moWaterElemental)
|
|
return waterpassable(w, from);
|
|
if(m == moGreaterShark)
|
|
return isWatery(w) || w->wall == waBoat || w->wall == waFrozenLake;
|
|
if(isGhostMover(m))
|
|
return ghostmove(m, w, from);
|
|
// for the purpose of Shmup this is correct
|
|
if(isBird(m))
|
|
return eaglepassable(w, from);
|
|
if(isLeader(m))
|
|
return leaderpassable(w, from);
|
|
if(isPrincess(m))
|
|
return passable(w, from, monster_passable, false, false) || boatpassable(w, from);
|
|
if(isGolemOrKnight(m))
|
|
return passable(w, from, monster_passable, false, false);
|
|
return false;
|
|
}
|
|
|
|
eMonster movegroup(eMonster m) {
|
|
if(isWitch(m) || m == moEvilGolem) {
|
|
if(m == moWitchGhost) return moWitchGhost;
|
|
if(m == moWitchWinter) return moWitchWinter;
|
|
return moWitch;
|
|
}
|
|
if(normalMover(m)) return moYeti;
|
|
if(isSlimeMover(m)) return moSlime;
|
|
if(m == moEarthElemental) return moEarthElemental;
|
|
if(isLeader(m)) return moPirate;
|
|
if(isAngryBird(m)) return moEagle;
|
|
if(isBird(m)) return moTameBomberbird;
|
|
if(m == moGhost) return moGhost;
|
|
if(m == moGreaterShark) return moGreaterShark;
|
|
if(m == moWolf) return moWolf;
|
|
if(isDemon(m)) return moLesser;
|
|
if(isBug(m)) return m;
|
|
if(m == moWaterElemental) return moWaterElemental;
|
|
return moNone;
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
void useup(cell *c) {
|
|
c->wparam--;
|
|
if(c->wparam == 0) {
|
|
if(c->wall == waTempFloor)
|
|
c->wall = waChasm;
|
|
else if(c->wall == waTempBridge)
|
|
placeWater(c, c);
|
|
else
|
|
c->wall = c->land == laCaribbean ? waCIsland2 : waNone;
|
|
}
|
|
}
|
|
|
|
bool isInactiveEnemy(cell *w, bool forpc) {
|
|
if(w->monst == moWormtail || w->monst == moWormwait || w->monst == moTentacletail || w->monst == moTentaclewait || w->monst == moHexSnakeTail)
|
|
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;
|
|
if((forpc ? w->stuntime : w->stuntime > 1) && !isFriendly(w))
|
|
return true;
|
|
return false;
|
|
}
|
|
|
|
// forpc = true (for PC), false (for golems)
|
|
bool isActiveEnemy(cell *w, cell *killed, bool forpc) {
|
|
if((forpc ? w->stuntime : w->stuntime > 1))
|
|
return false;
|
|
if(w->monst == moNone || w == killed) return false;
|
|
if(isFriendly(w)) return false;
|
|
if(isInactiveEnemy(w, forpc)) 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 isUnarmed(eMonster m) {
|
|
return
|
|
m == moMouse || m == moMouseMoved || m == moPrincess || m == moPrincessMoved ||
|
|
m == moCrystalSage;
|
|
}
|
|
|
|
bool isArmedEnemy(cell *w, cell *killed, bool forpc) {
|
|
return w->monst != moCrystalSage && isActiveEnemy(w, killed, forpc);
|
|
}
|
|
|
|
bool isHive(eLand l) {
|
|
return l == laHive;
|
|
}
|
|
|
|
bool isKillable(cell *c) {
|
|
return c->monst != moShadow && (!isWorm(c) || c->monst == moTentacleGhost) && c->monst != moGreater && c->monst != moGreaterM
|
|
&& c->monst != moHedge && c->monst != moFlailer && !(c->monst == moVizier && c->hitpoints > 1);
|
|
// && !isBug(c->monst);
|
|
}
|
|
|
|
bool isKillableSomehow(cell *c) {
|
|
return isKillable(c)
|
|
|| c->monst == moHedge || c->monst == moLancer || c->monst == moFlailer ||
|
|
c->monst == moVizier;
|
|
}
|
|
|
|
bool isNeighbor(cell *c1, cell *c2) {
|
|
for(int i=0; i<c1->type; i++) if(c1->mov[i] == c2) return true;
|
|
return false;
|
|
}
|
|
|
|
int neighborId(cell *c1, cell *c2) {
|
|
for(int i=0; i<c1->type; i++) if(c1->mov[i] == c2) return i;
|
|
return -1;
|
|
}
|
|
|
|
// 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 = isFriendlyOrBugS(c3);
|
|
if(friendly ? b: !b) return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int monstersnear(cell *c, cell *nocount = NULL, eMonster who = moNone) {
|
|
if(hardcore && !who) return 0;
|
|
int res = 0;
|
|
bool fast = false;
|
|
|
|
if(!who) {
|
|
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, true), false, false)) continue;
|
|
if(isPlayerOn(c2) && 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, !who) && (c2->monst != moLancer || isUnarmed(who))) {
|
|
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 && !isPlayerOn(c))
|
|
continue;
|
|
res++, which = c2->monst;
|
|
}
|
|
}
|
|
|
|
if(!who && res && markOrb2(itOrbShield))
|
|
res = 0;
|
|
|
|
return res;
|
|
}
|
|
|
|
// reduce c->mpdist to d; also generate the landscape
|
|
|
|
bool buggyGeneration = false;
|
|
|
|
|
|
bool checkBarriersBack(cellwalker bb, int q=5, bool cross = false);
|
|
|
|
bool checkBarriersFront(cellwalker bb, int q=5, bool cross = false) {
|
|
|
|
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(!cross) 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, bool cross) {
|
|
// 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(!cross) 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);
|
|
}
|
|
|
|
bool isSealand(eLand l) {
|
|
return l == laOcean || l == laCaribbean || l == laWhirlpool || l == laLivefjord ||
|
|
l == laOceanWall;
|
|
}
|
|
|
|
bool isElemental(eLand l) {
|
|
return l == laEAir || l == laEWater || l == laEEarth || l == laEFire ||
|
|
l == laElementalWall;
|
|
}
|
|
|
|
eWall getElementalWall(eLand l) {
|
|
if(l == laEAir) return waChasm;
|
|
if(l == laEEarth) return waStone;
|
|
if(l == laEFire) return waEternalFire;
|
|
if(l == laEWater) return waSea;
|
|
return waNone;
|
|
}
|
|
|
|
void setbarrier(cell *c) {
|
|
if(isSealand(c->barleft) && isSealand(c->barright)) {
|
|
c->wall = c->type == 7 ? waBarrier : waSea;
|
|
c->land = laOceanWall;
|
|
}
|
|
else if(isElemental(c->barleft) && isElemental(c->barright)) {
|
|
c->land = laElementalWall;
|
|
c->wall = getElementalWall(c->barleft);
|
|
}
|
|
else {
|
|
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, 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;
|
|
}
|
|
|
|
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 || c->monst == moParrot || c->monst == moCShark)
|
|
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 == waThumperOff || c->wall == waThumperOn ||
|
|
c->wall == waFreshGrave || c->wall == waColumn || c->wall == waPartialFire ||
|
|
c->wall == waDeadwall || c->wall == waWaxWall || c->wall == waCamelot || c->wall == waRoundTable ||
|
|
c->wall == waBigStatue || c->wall == waRed1 || c->wall == waRed2 || c->wall == waRed3 ||
|
|
c->wall == waPalace || c->wall == waOpenGate || c->wall == waClosedGate ||
|
|
c->wall == waPlatform || c->wall == waStone || c->wall == waTempWall ||
|
|
c->wall == waTempFloor || c->wall == waTempBridge)
|
|
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 || isFire(c) || c->wall == waBonfireOff || c->wall == waVineHalfA || c->wall == waVineHalfB ||
|
|
c->wall == waCamelotMoat || c->wall == waSea || c->wall == waCTree ||
|
|
c->wall == waRubble || c->wall == waGargoyleFloor || c->wall == waGargoyle)
|
|
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;
|
|
shmup::degradeDemons();
|
|
}
|
|
}
|
|
|
|
void ivynext(cell *c);
|
|
|
|
bool earthFloor(cell *c) {
|
|
if(c->monst) return false;
|
|
if(c->wall == waDeadwall) { c->wall = waDeadfloor; return true; }
|
|
if(c->wall == waDune) { c->wall = waNone; return true; }
|
|
if(c->wall == waStone) { c->wall = waNone; return true; }
|
|
if(c->wall == waAncientGrave || c->wall == waFreshGrave) {
|
|
c->wall = waNone;
|
|
return true;
|
|
}
|
|
if((c->wall == waSea || c->wall == waNone) && c->land == laOcean) {
|
|
c->wall = waCIsland;
|
|
return true;
|
|
}
|
|
if(c->wall == waSea && c->land == laCaribbean) {
|
|
c->wall = waCIsland;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool earthWall(cell *c) {
|
|
if(c->wall == waDeadfloor || c->wall == waDeadfloor2 || c->wall == waEarthD) {
|
|
c->item = itNone;
|
|
c->wall = waDeadwall;
|
|
return true;
|
|
}
|
|
if(c->wall == waNone && c->land == laDesert) {
|
|
c->item = itNone;
|
|
c->wall = waDune;
|
|
return true;
|
|
}
|
|
if(c->wall == waNone && isElemental(c->land)) {
|
|
c->item = itNone;
|
|
c->wall = waStone;
|
|
return true;
|
|
}
|
|
if(c->wall == waNone && c->land == laRedRock) {
|
|
c->item = itNone;
|
|
c->wall = waRed3;
|
|
return true;
|
|
}
|
|
if(c->wall == waCIsland || c->wall == waCIsland2 || (c->wall == waNone && c->land == laOcean)) {
|
|
c->item = itNone;
|
|
c->wall = waSea;
|
|
if(c->land == laOcean) c->landparam = 40;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void snakepile(cell *c) {
|
|
if(c->wall == waRed1 || c->wall == waOpenGate) c->wall = waRed2;
|
|
else if(c->wall == waRed2) c->wall = waRed3;
|
|
else if(c->wall == waNone || c->wall == waFloorA || c->wall == waFloorB ||
|
|
c->wall == waCIsland || c->wall == waCIsland2 ||
|
|
(c->wall == waSea && c->land == laOcean) ||
|
|
(c->wall == waSea && c->land == laLivefjord) ||
|
|
c->wall == waOpenPlate || c->wall == waClosePlate ||
|
|
c->wall == waMineUnknown || c->wall == waMineOpen)
|
|
c->wall = waRed1;
|
|
else if(c->wall == waDeadfloor)
|
|
c->wall = waDeadfloor2;
|
|
else if(c->wall == waDeadfloor2)
|
|
c->wall = waDeadwall;
|
|
else if(c->wall == waRubble || c->wall == waGargoyleFloor || c->wall == waGargoyleBridge ||
|
|
c->wall == waTempFloor || c->wall == waTempBridge)
|
|
c->wall = waRed2;
|
|
else if(c->wall == waCavefloor) c->wall = waCavewall;
|
|
else if(c->wall == waSea) c->wall = waCIsland;
|
|
else if(isWateryOrBoat(c) || c->wall == waFrozenLake) c->wall = waNone;
|
|
else if(isWateryOrBoat(c) || c->wall == waFrozenLake) c->wall = waNone;
|
|
else if(cellHalfvine(c)) {
|
|
destroyHalfvine(c, waRed1);
|
|
}
|
|
}
|
|
|
|
bool eternalFire(cell *c) {
|
|
return c->land == laDryForest || c->land == laPower || c->land == laMinefield ||
|
|
c->land == laEFire || c->land == laElementalWall;
|
|
}
|
|
|
|
void explodeMine(cell *c);
|
|
|
|
bool makeflame(cell *c, int timeout, bool checkonly) {
|
|
if(itemBurns(c->item)) {
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("%The1 burns!", c->item)), c->item = itNone;
|
|
}
|
|
if(cellUnstable(c)) {
|
|
if(checkonly) return true;
|
|
c->wall = waChasm;
|
|
}
|
|
else if(c->wall == waChasm || c->wall == waOpenGate || c->wall == waRed2 || c->wall == waRed3)
|
|
return false;
|
|
else if(c->wall == waBoat) {
|
|
if(checkonly) return true;
|
|
placeWater(c, c);
|
|
addMessage(XLAT("%The1 burns!", winf[c->wall].name));
|
|
if(isIcyLand(c)) HEAT(c) += 1;
|
|
}
|
|
else if(c->wall == waNone && c->land == laCocytus) {
|
|
if(checkonly) return true;
|
|
c->wall = waLake, HEAT(c) += 1;
|
|
}
|
|
else if(c->wall == waFrozenLake && c->land == laCocytus) {
|
|
if(checkonly) return true;
|
|
c->wall = waLake, HEAT(c) += 1;
|
|
}
|
|
else if(c->wall == waIcewall) {
|
|
if(checkonly) return true;
|
|
c->wall = waNone;
|
|
}
|
|
else if(c->wall == waMineMine) {
|
|
if(checkonly) return true;
|
|
explodeMine(c);
|
|
}
|
|
else if(c->wall != waCTree && c->wall != waDryTree && c->wall != waWetTree &&
|
|
c->wall != waVinePlant && !passable(c, NULL, true, true, false)) return false;
|
|
else {
|
|
eWall w = eternalFire(c) ? waEternalFire : waFire;
|
|
if(w == c->wall) return false;
|
|
if(checkonly) return true;
|
|
destroyHalfvine(c);
|
|
if(!isFire(c)) c->wparam = 0;
|
|
c->wall = w;
|
|
c->wparam = max(c->wparam, (char) timeout);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void explodeMine(cell *c) {
|
|
if(c->wall != waMineMine)
|
|
return;
|
|
|
|
c->wall = waMineOpen;
|
|
makeflame(c, 20, false);
|
|
|
|
for(int i=0; i<c->type; i++) if(c->mov[i]) {
|
|
cell *c2 = c->mov[i];
|
|
if(c2->wall == waRed2 || c2->wall == waRed3)
|
|
c2->wall = waRed1;
|
|
else if(c2->wall == waDeadTroll || c2->wall == waGargoyle) {
|
|
c2->wall = waNone;
|
|
makeflame(c2, 10, false);
|
|
}
|
|
else if(c2->wall == waPalace || c2->wall == waOpenGate || c2->wall == waClosedGate) {
|
|
c2->wall = waNone;
|
|
makeflame(c2, 10, false);
|
|
}
|
|
else makeflame(c2, 20, false);
|
|
}
|
|
}
|
|
|
|
void stunMonster(cell *c2) {
|
|
if(c2->monst != moSkeleton)
|
|
c2->hitpoints--;
|
|
c2->stuntime = (
|
|
c2->monst == moFatGuard ? 2 :
|
|
c2->monst == moSkeleton && c2->land != laPalace ? 7 :
|
|
3);
|
|
}
|
|
|
|
bool attackJustStuns(cell *c2) {
|
|
return isStunnable(c2->monst) && c2->hitpoints > 1;
|
|
}
|
|
|
|
void killOrStunMonster(cell *c2) {
|
|
if(attackJustStuns(c2))
|
|
stunMonster(c2);
|
|
else
|
|
killMonster(c2);
|
|
}
|
|
|
|
void moveEffect(cell *ct, cell *cf, eMonster m);
|
|
|
|
namespace princess {
|
|
|
|
#define EPX 39
|
|
#define EPY 21
|
|
|
|
#define OUT_OF_PRISON 200
|
|
#define OUT_OF_PALACE 250
|
|
|
|
bool generating = false;
|
|
bool challenge = false;
|
|
bool saved = false;
|
|
bool everSaved = false;
|
|
bool everGotYendorVictory = false;
|
|
|
|
bool forceVizier = false;
|
|
bool forceMouse = false;
|
|
|
|
int saveHP = 0, saveArmedHP = 0;
|
|
|
|
struct info {
|
|
int id; // id of this info
|
|
cell *prison; // where was the Princess locked
|
|
heptagon *alt; // alt of the prison
|
|
int bestdist; // best dist achieved
|
|
int bestnear; // best dist achieved, by the player
|
|
int value; // number of Rugs at 120
|
|
cell *princess; // where is the Princess currently
|
|
};
|
|
|
|
vector<info*> infos;
|
|
|
|
void assign(info *i) {
|
|
if(i->alt) i->alt->emeraldval = i->id;
|
|
}
|
|
|
|
void newInfo(cell *c) {
|
|
info *i = new info;
|
|
i->prison = c;
|
|
i->princess = c;
|
|
i->alt = c->master->alt;
|
|
i->id = size(infos);
|
|
i->bestdist = 0;
|
|
i->bestnear = OUT_OF_PRISON;
|
|
infos.push_back(i);
|
|
assign(i);
|
|
}
|
|
|
|
void newFakeInfo(cell *c) {
|
|
info *i = new info;
|
|
i->prison = NULL;
|
|
i->princess = c;
|
|
i->alt = NULL;
|
|
i->id = size(infos);
|
|
i->bestdist = OUT_OF_PALACE;
|
|
i->bestnear = 0;
|
|
infos.push_back(i);
|
|
assign(i);
|
|
}
|
|
|
|
info *getPrisonInfo(cell *c) {
|
|
if(euclid) return NULL;
|
|
if(c->land != laPalace) return NULL;
|
|
if(!c->master->alt) return NULL;
|
|
return infos[c->master->alt->emeraldval];
|
|
}
|
|
|
|
info *getPrincessInfo(cell *c) {
|
|
for(int i=0; i<size(infos); i++) if(infos[i]->princess == c) {
|
|
while(i) {
|
|
infos[i]->id = i-1; assign(infos[i]);
|
|
infos[i-1]->id = i; assign(infos[i-1]);
|
|
i--;
|
|
}
|
|
return infos[i];
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
int dist(cell *c) {
|
|
if(c->land != laPalace) return OUT_OF_PALACE;
|
|
else if(euclid) return celldistAlt(c);
|
|
else if(!c->master->alt) return OUT_OF_PRISON;
|
|
else return celldistAlt(c);
|
|
}
|
|
|
|
void clear() {
|
|
for(int i=0; i<size(infos); i++) delete infos[i];
|
|
infos.clear();
|
|
}
|
|
|
|
bool bringBackAt(cell *c) {
|
|
if(!c) return false;
|
|
if(!passable(c, NULL, false, false, false)) return false;
|
|
c->monst = moPrincessArmed;
|
|
c->stuntime = 0;
|
|
c->hitpoints = palaceHP();
|
|
drawFlash(c);
|
|
|
|
info *inf = NULL;
|
|
for(int i=0; i<size(infos); i++)
|
|
if(infos[i]->princess && infos[i]->bestdist == OUT_OF_PALACE)
|
|
inf = infos[i];
|
|
|
|
if(inf) { inf->princess->monst = moNone; inf->princess = c; }
|
|
else newFakeInfo(c);
|
|
return true;
|
|
}
|
|
|
|
void bringBack() {
|
|
if(bringBackAt(cwt.c->mov[cwt.spin])) return;
|
|
for(int i=1; i<size(dcal); i++)
|
|
if(bringBackAt(dcal[i])) return;
|
|
}
|
|
|
|
void move(cell *ct, cell *cf) {
|
|
if(euclid) return;
|
|
princess::info *i = princess::getPrincessInfo(cf);
|
|
if(!i) {
|
|
static bool warn = true;
|
|
// note: OK if mapediting or loading
|
|
if(warn) printf("Warning: unknown princess\n");
|
|
warn = false;
|
|
}
|
|
else {
|
|
i->princess = ct;
|
|
int newdist = dist(ct);
|
|
// printf("newdist = %d (vs %d)\n", newdist, i->bestdist);
|
|
if(newdist < ALTDIST_ERROR && newdist > i->bestdist) {
|
|
i->bestdist = newdist;
|
|
// printf("Improved dist to %d\n", newdist);
|
|
if(newdist == OUT_OF_PALACE) {
|
|
if(!princess::saved)
|
|
achievement_gain("PRINCESS1");
|
|
princess::saved = true;
|
|
princess::everSaved = false;
|
|
items[itSavedPrincess]++;
|
|
}
|
|
if(newdist == OUT_OF_PRISON && princess::challenge) {
|
|
addMessage(XLAT("Congratulations! Your score is %1.", its(i->value)));
|
|
achievement_gain("PRINCESS2");
|
|
if(!cheater) achievement_score(36, i->value);
|
|
showMissionScreen();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void mouseSqueak(cell *c) {
|
|
eMonster m = c->monst;
|
|
info *i = getPrisonInfo(c);
|
|
int d = dist(c);
|
|
|
|
if(!i)
|
|
addMessage(XLAT("%The1 squeaks in a confused way.", m));
|
|
else if(i->bestdist >= 6)
|
|
addMessage(XLAT("%The1 squeaks gratefully!", m));
|
|
else if(!i->princess)
|
|
addMessage(XLAT("%The1 squeaks hopelessly.", m));
|
|
else if(d > 120)
|
|
addMessage(XLAT("%The1 squeaks in despair.", m));
|
|
else if(d > 90)
|
|
addMessage(XLAT("%The1 squeaks sadly.", m));
|
|
else if(d > 60)
|
|
addMessage(XLAT("%The1 squeaks with hope!", m));
|
|
else if(d > 30)
|
|
addMessage(XLAT("%The1 squeaks happily!", m));
|
|
else
|
|
addMessage(XLAT("%The1 squeaks excitedly!", m));
|
|
}
|
|
|
|
void line(cell *c) {
|
|
int d = (euclid || c->master->alt) ? celldistAlt(c) : 200;
|
|
eMonster m = c->monst;
|
|
|
|
static int msgid = 0;
|
|
|
|
retry:
|
|
if(msgid >= 32) msgid = 0;
|
|
|
|
if(msgid == 0 && d < 20 && c->land == laPalace) {
|
|
addMessage(XLAT("%The1 kisses you, and begs you to bring %him1 away from here.", m));
|
|
}
|
|
else if(msgid == 1 && d >= 20 && c->land == laPalace) {
|
|
if(m == moPrincess)
|
|
addMessage(XLAT("\"I want my revenge. Stun a guard and leave him for me!\"", m));
|
|
else
|
|
addMessage(XLAT("\"That felt great. Thanks!\"", m));
|
|
}
|
|
else if(msgid == 2 && d >= 70 && c->land == laPalace) {
|
|
addMessage(XLAT("\"Bring me out of here please!\"", m));
|
|
}
|
|
else if(msgid == 3 && c->land != laPalace) {
|
|
addMessage(XLAT("%The1 kisses you, and thanks you for saving %him1.", m));
|
|
}
|
|
else if(msgid == 4 && c->land != laPalace && m == moPrincess) {
|
|
addMessage(XLAT("\"I have been trained to fight with a Hypersian scimitar, you know?\"", m));
|
|
}
|
|
else if(msgid == 5 && c->land != laPalace) {
|
|
addMessage(XLAT("\"I would love to come to your world with you!\"", m));
|
|
}
|
|
else if(msgid == 6 && c->land != laPalace) {
|
|
addMessage(XLAT("\"Straight lines stay close to each other forever, this is so romantic!\"", m));
|
|
}
|
|
else if(msgid == 7 && c->land != laPalace) {
|
|
addMessage(XLAT("\"Maps... Just like the world, but smaller... how is that even possible?!\"", m));
|
|
}
|
|
else {
|
|
msgid++; goto retry;
|
|
}
|
|
|
|
msgid++;
|
|
}
|
|
|
|
void playernear(cell *c) {
|
|
info *i = getPrisonInfo(c);
|
|
int d = dist(c);
|
|
// if(i) printf("d=%d bn=%d\n", d, i->bestnear);
|
|
if(i && d < i->bestnear) {
|
|
if(i->bestnear > 100 && d <= 100) {
|
|
i->value = items[itPalace];
|
|
if(princess::challenge)
|
|
addMessage(XLAT("Hardness frozen at %1.", its(i->value)));
|
|
}
|
|
i->bestnear = d;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
bool grailWasFound(cell *c) {
|
|
if(euclid) return items[itHolyGrail];
|
|
return c->master->alt->alt->emeraldval & GRAIL_FOUND;
|
|
}
|
|
|
|
int euclidAlt(short x, short y) {
|
|
if(euclidland == laTemple) {
|
|
return max(int(x), x+y);
|
|
}
|
|
else if(euclidland == laCaribbean || euclidland == laWhirlpool) {
|
|
return
|
|
min(
|
|
min(max(int(-x), -x-y) + 3,
|
|
max(int(x+y), int(y)) + 3),
|
|
max(int(x), int(-y)) + 3
|
|
);
|
|
}
|
|
else if(euclidland == laPrincessQuest)
|
|
return eudist(x-EPX, y-EPY);
|
|
else return eudist(x-20, y-10);
|
|
}
|
|
|
|
void flameHalfvine(cell *c, int val) {
|
|
if(itemBurns(c->item)) {
|
|
c->item = itNone;
|
|
addMessage(XLAT("%The1 burns!", c->item)), c->item = itNone;
|
|
}
|
|
c->wall = waPartialFire;
|
|
c->wparam = val;
|
|
}
|
|
|
|
void killMonster(cell *c) {
|
|
DEB("killmonster");
|
|
eMonster m = c->monst;
|
|
|
|
if(!m) return;
|
|
if(isWorm(c) && m != moTentacleGhost) 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;
|
|
if(isPrincess(m)) m = moPrincess;
|
|
if(m == moTentacleGhost) m = moGhost;
|
|
kills[m]++;
|
|
|
|
if(m == moPrincess) {
|
|
princess::info *i = princess::getPrincessInfo(c);
|
|
if(i) {
|
|
i->princess = NULL;
|
|
if(i->bestdist == OUT_OF_PALACE) items[itSavedPrincess]--;
|
|
if(princess::challenge) showMissionScreen();
|
|
}
|
|
}
|
|
|
|
if(m == moGargoyle && c->wall != waMineMine) {
|
|
bool connected = false;
|
|
|
|
if(c->land == laEdge) {
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = c->mov[i];
|
|
if(c2->wall == waPlatform || c2->wall == waGargoyle || c2->wall == waBarrier ||
|
|
c2->wall == waDeadTroll)
|
|
connected = true;
|
|
}
|
|
}
|
|
else {
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = c->mov[i];
|
|
if(!cellUnstableOrChasm(c2) && !isWatery(c2)) connected = true;
|
|
}
|
|
}
|
|
|
|
if(connected) {
|
|
destroyHalfvine(c);
|
|
if(cellUnstableOrChasm(c)) c->wall = waGargoyleFloor;
|
|
else if(isWatery(c)) c->wall = waGargoyleBridge;
|
|
else c->wall = waGargoyle;
|
|
c->item = itNone;
|
|
}
|
|
}
|
|
|
|
if(m == moTroll || m == moFjordTroll) {
|
|
destroyHalfvine(c);
|
|
c->wall = cellUnstableOrChasm(c) ? waChasm : waDeadTroll;
|
|
c->item = itNone;
|
|
for(int i=0; i<c->type; i++) if(c->mov[i]) {
|
|
if(m == moTroll) 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, false)) {
|
|
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(isBird(m)) moveEffect(c, c, moDeadBird);
|
|
if(m == moBomberbird || m == moTameBomberbird) {
|
|
if(c->wall == waNone || c->wall == waMineUnknown || c->wall == waMineOpen ||
|
|
c->wall == waCavefloor || c->wall == waDeadfloor || c->wall == waDeadfloor2 ||
|
|
c->wall == waRubble || c->wall == waGargoyleFloor ||
|
|
c->wall == waCIsland || c->wall == waCIsland2 ||
|
|
c->wall == waStrandedBoat || c->wall == waRed1 || c->wall == waGiantRug)
|
|
c->wall = waMineMine;
|
|
else if(c->wall == waFrozenLake)
|
|
c->wall = waLake;
|
|
else if(c->wall == waGargoyleBridge)
|
|
placeWater(c, c);
|
|
else if(c->wall == waRed3 || c->wall == waRed2) {
|
|
c->wall = waRed1;
|
|
for(int i=0; i<c->type; i++) if(c->mov[i]->wall == waRed3)
|
|
c->mov[i]->wall = waRed2;
|
|
c->item = itNone;
|
|
}
|
|
if(isFire(c)) {
|
|
c->wall = waMineMine;
|
|
explodeMine(c);
|
|
}
|
|
}
|
|
if(m == moVineSpirit) {
|
|
destroyHalfvine(c);
|
|
if(!isFire(c)) c->wall = waNone;
|
|
}
|
|
if(m == moRedTroll) snakepile(c);
|
|
if(isWitch(m) && (isFire(c) || passable(c, NULL, true, false, 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(isFire(c) && itemBurns(c->item))
|
|
c->item = itNone;
|
|
}
|
|
if(m == moFireFairy)
|
|
makeflame(c, 50, false);
|
|
if(m == moPyroCultist && c->item == itNone && c->wall != waChasm) {
|
|
// a reward for killing him before he shoots!
|
|
c->item = itOrbDragon;
|
|
}
|
|
// note: an Orb appears underwater!
|
|
if(m == moWaterElemental && c->item == itNone)
|
|
c->item = itOrbWater;
|
|
|
|
if(m == moPirate && isOnCIsland(c) && c->item == itNone && (euclid||c->master->alt) && celldistAlt(c) <= -5) {
|
|
bool toomany = false;
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = c->mov[i];
|
|
if(c2) for(int j=0; j<c2->type; j++)
|
|
if(c2->mov[j] && c2->mov[j]->item == itCompass)
|
|
toomany = true;
|
|
}
|
|
if(!toomany) c->item = itCompass;
|
|
}
|
|
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) HEAT(c) += 3;
|
|
if(m == moPyroCultist) HEAT(c) += 6;
|
|
if(m == moLesser) HEAT(c) += 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 if(c->monst == moTentacleGhost)
|
|
c->monst = moTentacletail;
|
|
else c->monst = moNone;
|
|
|
|
if(m == moEarthElemental) earthWall(c);
|
|
}
|
|
|
|
void killWithMessage(cell *c, bool orStun = false) {
|
|
eMonster m = c->monst;
|
|
int tk = tkills();
|
|
if(orStun)
|
|
killOrStunMonster(c);
|
|
else
|
|
killMonster(c);
|
|
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(m == moVizier && c->monst != moVizier && kills[moVizier] == 1) {
|
|
addMessage(XLAT("Hmm, he has been training in the Emerald Mine. Interesting..."));
|
|
princess::forceMouse = true;
|
|
}
|
|
|
|
if(m == moIvyRoot && ntk>tk)
|
|
achievement_gain("IVYSLAYER");
|
|
}
|
|
|
|
void pushMonster(cell *ct, cell *cf) {
|
|
moveMonster(ct, cf);
|
|
}
|
|
|
|
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) flameHalfvine(c->mov[t], tval);
|
|
else if(newwall == waRed1) c->mov[t]->wall = waVinePlant;
|
|
else c->mov[t]->wall = newwall;
|
|
}
|
|
if(newwall == waPartialFire) flameHalfvine(c, tval);
|
|
else c->wall = newwall;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool isCrossroads(eLand l) {
|
|
return l == laCrossroads || l == laCrossroads2 || l == laCrossroads3;
|
|
}
|
|
|
|
/*
|
|
bool orbChance(cell *c, eLand usual, int chthere, int chcross) {
|
|
if(usual == laElementalWall && isElementalLand(c->land))
|
|
usual = c->land;
|
|
if(c->land == usual) return chthere && hrand(chthere) == 0;
|
|
if(chcross && isCrossroads(c->land)) {
|
|
chcross = (chcross / 50) * (50 + items[itHyperstone]);
|
|
return hrand(chcross) == 0;
|
|
}
|
|
if(chcross && c->land == laOcean && c->wall == waBoat && c->landparam > 25) {
|
|
return hrand(chcross) < 20;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void placeOrbs(cell *c) {
|
|
|
|
if(c->land == laZebra && c->wall == waTrapdoor) return;
|
|
if(c->land == laEdge && cellEdgeUnstable(c)) return;
|
|
|
|
for(int i=0; i<ORBLINES; i++) {
|
|
orbinfo& oi(orbinfos[i]);
|
|
if(orbChance(c, oi.l, oi.lchance, oi.gchance) && items[treasureType(oi.l)] * landMultiplier(oi.l) >= 10)
|
|
c->item = oi.orb;
|
|
if(oi.orb == itOrbWater && c->land != laOcean) c->wall = waStrandedBoat;
|
|
}
|
|
|
|
}
|
|
*/
|
|
|
|
void buildPrizeMirror(cell *c) {
|
|
if(c->type == 7) return;
|
|
if(items[itShard] < 25) return;
|
|
c->wall = hrand(2) ? waCloud : waMirror;
|
|
}
|
|
|
|
void placePrizeOrb(cell *c) {
|
|
eLand l = c->land;
|
|
if(isElemental(l)) l = laElementalWall;
|
|
|
|
// these two lands would have too much orbs according to normal rules
|
|
if(l == laPalace && hrand(100) >= 20) return;
|
|
if(l == laGraveyard && hrand(100) >= 15) return;
|
|
if(l == laLivefjord && hrand(100) >= 35) return;
|
|
if(l == laMinefield && hrand(100) >= 25) return;
|
|
if(l == laElementalWall && hrand(100) >= 25) return;
|
|
|
|
if(l == laPalace && princess::dist(c) < OUT_OF_PRISON)
|
|
l = laPrincessQuest;
|
|
for(int i=0; i<ORBLINES; i++) {
|
|
orbinfo& oi(orbinfos[i]);
|
|
eOrbLandRelation olr = getOLR(oi.orb, l);
|
|
if(olr != olrPrize25 && olr != olrPrize3) continue;
|
|
if(olr == olrPrize25 || olr == olrGuest || olr == olrMonster) {
|
|
if(items[treasureType(oi.l)] < 25) continue;
|
|
}
|
|
else if(olr == olrPrize3) { if(items[treasureType(oi.l)] < 3) continue; }
|
|
else continue;
|
|
|
|
if(!oi.gchance) continue;
|
|
if(hrand(oi.gchance) >= 60) continue;
|
|
if(oi.orb == itOrbWater && c->land != laOcean) {
|
|
if(cellHalfvine(c)) continue;
|
|
c->wall = waStrandedBoat;
|
|
return;
|
|
}
|
|
c->item = oi.orb;
|
|
}
|
|
|
|
// printf("land: %s orb: %s\n", dnameof(l), dnameof(c->item));
|
|
}
|
|
|
|
#define PRIZEMUL 7
|
|
|
|
void placeLocalOrbs(cell *c) {
|
|
eLand l = c->land;
|
|
if(l == laZebra && c->wall == waTrapdoor) return;
|
|
if(l == laEdge && cellEdgeUnstable(c)) return;
|
|
if(isElemental(l)) l = laElementalWall;
|
|
|
|
for(int i=0; i<ORBLINES; i++) {
|
|
orbinfo& oi(orbinfos[i]);
|
|
if(oi.l != l) continue;
|
|
if(!oi.lchance) continue;
|
|
int ch = hrand(oi.lchance);
|
|
if(ch == 0 && items[treasureType(oi.l)] * landMultiplier(oi.l) >= 10) {
|
|
// printf("local orb\n");
|
|
c->item = oi.orb;
|
|
if(oi.orb == itOrbWater && c->land != laOcean) c->wall = waStrandedBoat;
|
|
return;
|
|
}
|
|
else if(oi.gchance && ch == 1 && getOLR(itShard, l) == olrPrize25 && l != laRedRock)
|
|
buildPrizeMirror(c);
|
|
else if(oi.gchance && (ch >= 2 && ch < 2+PRIZEMUL))
|
|
placePrizeOrb(c);
|
|
}
|
|
}
|
|
|
|
void placeCrossroadOrbs(cell *c) {
|
|
for(int i=0; i<ORBLINES; i++) {
|
|
orbinfo& oi(orbinfos[i]);
|
|
if(!oi.gchance) continue;
|
|
if(items[treasureType(oi.l)] * landMultiplier(oi.l) < 10) continue;
|
|
if(hrand(oi.gchance)) continue;
|
|
if(hrand(50+items[itHyperstone]) >= 50) continue;
|
|
c->item = oi.orb;
|
|
if(oi.orb == itOrbWater && c->land != laOcean) c->wall = waStrandedBoat;
|
|
}
|
|
}
|
|
|
|
void placeOceanOrbs(cell *c) {
|
|
for(int i=0; i<ORBLINES; i++) {
|
|
orbinfo& oi(orbinfos[i]);
|
|
if(items[treasureType(oi.l)] * landMultiplier(oi.l) < 10) continue;
|
|
if(!oi.gchance) continue;
|
|
if(hrand(oi.gchance) >= 20) continue;
|
|
c->item = oi.orb;
|
|
}
|
|
}
|
|
|
|
bool errorReported = false;
|
|
|
|
void describeCell(cell *c) {
|
|
if(!c) { printf("NULL\n"); return; }
|
|
printf("describe %p: ", c);
|
|
printf("%-15s", linf[c->land].name);
|
|
printf("%-15s", winf[c->wall].name);
|
|
printf("%-15s", iinf[c->item].name);
|
|
printf("%-15s", iinf[c->monst].name);
|
|
printf("%3d", c->landparam);
|
|
printf("%3d", c->mpdist);
|
|
printf("\n");
|
|
}
|
|
|
|
void raiseBuggyGeneration(cell *c, const char *s) {
|
|
|
|
printf("procgen error in: %s\n", s);
|
|
|
|
if(!errorReported) {
|
|
addMessage(string("something strange happened in: ") + s);
|
|
errorReported = true;
|
|
}
|
|
// return;
|
|
|
|
describeCell(c);
|
|
for(int i=0; i<c->type; i++) describeCell(c->mov[i]);
|
|
|
|
z:
|
|
c->item = itPirate;
|
|
for(int i=0; i<c->type; i++) if(c->mov[i] && c->mov[i]->mpdist < c->mpdist) {
|
|
c = c->mov[i];
|
|
goto z;
|
|
}
|
|
|
|
buggyGeneration = true; return;
|
|
}
|
|
|
|
void setland(cell *c, eLand l) {
|
|
if(c->land != l)
|
|
c->landparam = 0;
|
|
c->land = l;
|
|
}
|
|
|
|
void buildBarrier(cell *c);
|
|
|
|
bool buildBarrier4(cell *c, int d, int mode, eLand ll, eLand lr);
|
|
|
|
void buildBarrierFront(cell *c) {
|
|
int ht = c->landparam;
|
|
|
|
cellwalker bb(c, c->bardir); setbarrier(bb.c);
|
|
cwstep(bb);
|
|
bb.c->barleft = c->barleft;
|
|
bb.c->barright = c->barright;
|
|
bb.c->landparam = (ht-4);
|
|
//printf("[A heat %d]\n", ht-4);
|
|
setbarrier(bb.c);
|
|
cwspin(bb, 2); cwstep(bb); setland(bb.c, c->barleft); cwstep(bb);
|
|
cwspin(bb, 2); cwstep(bb); setland(bb.c, c->barright); cwstep(bb);
|
|
cwspin(bb, 2);
|
|
|
|
cwspin(bb, 3); cwstep(bb);
|
|
bb.c->landparam = (ht-4)^2;
|
|
//printf("[B heat %d]\n", (ht-4)^2);
|
|
bb.c->barleft = c->barright;
|
|
bb.c->barright = c->barleft;
|
|
setbarrier(bb.c);
|
|
cwspin(bb, 3); cwstep(bb);
|
|
|
|
bb.c->landparam = ht ^ 2;
|
|
//printf("[C heat %d]\n", (ht)^2);
|
|
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);
|
|
setland(bb.c, a > 0 ? c->barright : c->barleft);
|
|
}
|
|
}
|
|
|
|
void buildBarrierBack(cell *c) {
|
|
int ht = c->landparam;
|
|
|
|
cellwalker bb(c, c->bardir); setbarrier(bb.c);
|
|
cwspin(bb, 3); cwstep(bb); cwspin(bb, 4); setland(bb.c, c->barright); cwstep(bb); cwspin(bb, 3);
|
|
bb.c->bardir = bb.spin;
|
|
bb.c->barleft = c->barright;
|
|
bb.c->barright = c->barleft;
|
|
bb.c->landparam = ht ^ 11;
|
|
//printf("[D heat %d]\n", (ht^11));
|
|
|
|
// needed for CR2 to work
|
|
cwstep(bb);
|
|
bb.c->barleft = c->barright;
|
|
bb.c->barright = c->barleft;
|
|
bb.c->landparam = (ht^11)-4;
|
|
cwstep(bb);
|
|
//printf("[E heat %d]\n", (ht^11));
|
|
|
|
// printf("#2\n");
|
|
buildBarrier(bb.c);
|
|
}
|
|
|
|
eLand oppositeElement(eLand l) {
|
|
if(l == laEFire) return laEWater;
|
|
if(l == laEWater) return laEFire;
|
|
if(l == laEAir) return laEEarth;
|
|
if(l == laEEarth) return laEAir;
|
|
return l;
|
|
}
|
|
|
|
void buildBarrier(cell *c) {
|
|
if(buggyGeneration) return;
|
|
|
|
// printf("build barrier at %p", c);
|
|
if(c->wall == waBarrier || c->land == laElementalWall) {
|
|
// printf("-> ready\n");
|
|
return;
|
|
}
|
|
// if(c->wall == waWaxWall) return;
|
|
if(c->mpdist > BARLEV) {
|
|
// printf("-> too far\n");
|
|
return; // == INFD) return;
|
|
}
|
|
|
|
if(((c->barleft == laCrossroads3 || c->barright == laCrossroads3) && hrand(100) < 66) ||
|
|
(isElemental(c->barleft) && isElemental(c->barright) && hrand(100) < 25)) {
|
|
cellwalker cw(c, c->bardir);
|
|
cwspin(cw, 3); cwstep(cw);
|
|
cell *cp = cwpeek(cw, 4);
|
|
if(cp->wall != waBarrier && cp->land != laElementalWall) {
|
|
cwspin(cw, 2); cwstep(cw);
|
|
bool b = buildBarrier4(cw.c, cw.spin, 2, oppositeElement(c->barleft), c->barright);
|
|
if(b) return;
|
|
}
|
|
else {
|
|
bool b = buildBarrier4(c, c->bardir, 1, c->barleft, c->barright);
|
|
if(b) return;
|
|
}
|
|
}
|
|
|
|
buildBarrierFront(c);
|
|
buildBarrierBack(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[hrand(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[hrand(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;
|
|
}
|
|
|
|
void chasmifyElemental(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->land == c->land)
|
|
d2[q++] = j;
|
|
}
|
|
if(q) {
|
|
int d = d2[hrand(q)];
|
|
cell *c3 = c->mov[d];
|
|
c3->wall = waElementalD;
|
|
for(int i=0; i<c3->type; i++) {
|
|
cell *c4 = createMov(c3, i);
|
|
c4->wall = waNone;
|
|
}
|
|
c3->mondir = c->spn[d];
|
|
}
|
|
c->wall = getElementalWall(c->land);
|
|
c->wparam = 100; c->item = itNone;
|
|
}
|
|
|
|
bool incompatible1(eLand l1, eLand l2) {
|
|
if(isCrossroads(l1) && isCrossroads(l2)) return true;
|
|
if(l1 == laJungle && l2 == laMotion) return true;
|
|
if(l1 == laMirror && l2 == laMotion) return true;
|
|
if(l1 == laPower && l2 == laWineyard) return true;
|
|
if(l1 == laPower && l2 == laDryForest) return true;
|
|
if(l1 == laEFire && l2 == laWineyard) return true;
|
|
if(l1 == laEFire && l2 == laDryForest) return true;
|
|
if(l1 == laGraveyard && l2 == laDryForest) return true;
|
|
if(l1 == laGraveyard && l2 == laRedRock) return true;
|
|
if(l1 == laGraveyard && l2 == laEmerald) return true;
|
|
if(l1 == laDeadCaves && l2 == laEmerald) return true;
|
|
if(l1 == laDeadCaves && l2 == laCaves) return true;
|
|
if(isElemental(l1) && isElemental(l2)) return true;
|
|
return false;
|
|
}
|
|
|
|
eLand randomElementalLand() {
|
|
int i = hrand(4);
|
|
eLand t[4] = { laEFire, laEWater, laEAir, laEEarth };
|
|
return t[i];
|
|
}
|
|
|
|
bool incompatible(eLand nw, eLand old) {
|
|
return (nw == old) || incompatible1(nw, old) || incompatible1(old, nw);
|
|
}
|
|
|
|
extern bool generatingEquidistant;
|
|
|
|
bool rlyehComplete() {
|
|
return items[itStatue] >= 10 || items[itGrimoire] >= 10;
|
|
}
|
|
|
|
bool lchance() { return hrand(100) < 90; }
|
|
|
|
eLand pickLandRPM(eLand old) {
|
|
while(true) {
|
|
eLand n = randlands[hrand(RANDLANDS)];
|
|
if(incompatible(n, old)) continue;
|
|
if(n == laIce || n == laCaves || n == laDesert)
|
|
return n;
|
|
if(hiitems[treasureType(n)] < 10)
|
|
continue;
|
|
return n;
|
|
}
|
|
}
|
|
|
|
eLand getNewLand(eLand old) {
|
|
|
|
if(randomPatternsMode) return pickLandRPM(old);
|
|
|
|
if(old == laCaribbean)
|
|
return laOcean;
|
|
|
|
if(old == laEEarth && lchance()) return hrand(2) ? laEWater : laEFire;
|
|
if(old == laEAir && lchance()) return hrand(2) ? laEWater : laEFire;
|
|
if(old == laEWater && lchance()) return hrand(2) ? laEEarth : laEAir;
|
|
if(old == laEFire && lchance()) return hrand(2) ? laEEarth : laEAir;
|
|
|
|
if(old == laLivefjord && hrand(2))
|
|
return laOcean;
|
|
|
|
if(old == laOcean && !generatingEquidistant)
|
|
return hrand(2) ? laCaribbean : laLivefjord;
|
|
|
|
if(old == laOcean && gold() >= 60 && hrand(100) < 75 && !rlyehComplete())
|
|
return laRlyeh;
|
|
|
|
if(old == laRlyeh && !rlyehComplete())
|
|
return laOcean;
|
|
|
|
eLand tab[256];
|
|
int cnt = 0;
|
|
|
|
/* if(isHive(old) && hrand(100) < 90) {
|
|
eLand n = old;
|
|
while(n == old) n = eLand(laHive0 + hrand(3));
|
|
return n;
|
|
} */
|
|
|
|
// return (hrand(2)) ? laMotion : laJungle;
|
|
|
|
// the basic lands, always available
|
|
tab[cnt++] = laCrossroads;
|
|
tab[cnt++] = laIce;
|
|
tab[cnt++] = laDesert;
|
|
tab[cnt++] = laJungle;
|
|
tab[cnt++] = laMotion;
|
|
if(old != laDeadCaves) tab[cnt++] = laCaves;
|
|
|
|
// the intermediate lands
|
|
if(gold() >= 30) {
|
|
tab[cnt++] = laCrossroads;
|
|
tab[cnt++] = laAlchemist;
|
|
tab[cnt++] = laMirror;
|
|
tab[cnt++] = laOcean;
|
|
tab[cnt++] = laLivefjord;
|
|
tab[cnt++] = laMinefield;
|
|
tab[cnt++] = laPalace;
|
|
if(kills[moVizier]) tab[cnt++] = laEmerald;
|
|
if(items[itFeather] >= 10) tab[cnt++] = laZebra;
|
|
}
|
|
|
|
// the advanced lands
|
|
if(gold() >= 60) {
|
|
// REMOVETHIS
|
|
tab[cnt++] = laEdge;
|
|
tab[cnt++] = laCrossroads2;
|
|
if(rlyehComplete()) tab[cnt++] = laRlyeh;
|
|
if(old == laCrossroads || old == laCrossroads2) tab[cnt++] = laOcean;
|
|
if(old == laOcean) tab[cnt++] = laCrossroads;
|
|
if(items[itGold] >= 5 && items[itFernFlower] >= 5 && !kills[moVizier])
|
|
tab[cnt++] = laEmerald;
|
|
tab[cnt++] = laDryForest;
|
|
tab[cnt++] = laWineyard;
|
|
if(items[itGold] >= 10) tab[cnt++] = laDeadCaves;
|
|
// tab[cnt++] = laCaribbean;
|
|
if(items[itSpice] >= 10) {
|
|
tab[cnt++] = laRedRock;
|
|
if(old == laDesert) tab[cnt++] = laRedRock;
|
|
}
|
|
if(old == laRedRock) tab[cnt++] = laDesert;
|
|
}
|
|
|
|
if(tkills() >= 100) {
|
|
tab[cnt++] = laGraveyard;
|
|
if(gold() >= 60) tab[cnt++] = laHive;
|
|
}
|
|
|
|
if(kills[moWaterElemental] + kills[moEarthElemental] + kills[moFireElemental] + kills[moAirElemental])
|
|
tab[cnt++] = randomElementalLand();
|
|
|
|
if(hellUnlocked()) {
|
|
tab[cnt++] = laCrossroads3;
|
|
tab[cnt++] = laHell;
|
|
}
|
|
|
|
if(items[itHell] >= 10) {
|
|
tab[cnt++] = laCocytus;
|
|
if(old == laHell || old == laIce) tab[cnt++ ] = laCocytus;
|
|
if(old == laCocytus) { tab[cnt++] = laIce; tab[cnt++] = laHell; }
|
|
tab[cnt++] = laPower;
|
|
if(old == laCrossroads || old == laCrossroads2) tab[cnt++] = laOcean;
|
|
if(old == laOcean) tab[cnt++] = laCrossroads2;
|
|
}
|
|
|
|
// for(int i=0; i<20; i++) tab[cnt++] = laRedRock;
|
|
// for(int i=0; i<20; i++) tab[cnt++] = laCaribbean;
|
|
// for(int i=0; i<20; i++) tab[cnt++] = laCocytus;
|
|
|
|
// for(int i=0; i<20; i++) tab[cnt++] = laCrossroads;
|
|
|
|
eLand n = old;
|
|
while(incompatible(n, old)) n = tab[hrand(cnt)];
|
|
|
|
return n;
|
|
}
|
|
|
|
bool notDippingFor(eItem i) {
|
|
int v = items[i] - currentLocalTreasure;
|
|
if(v <= 10) return true;
|
|
if(v >= 20) return false;
|
|
return v >= hrand(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 >= hrand(10) + 10;
|
|
}
|
|
|
|
eLand euland[65536];
|
|
|
|
eLand switchable(eLand nearland, eLand farland, eucoord c) {
|
|
if(nearland == laCrossroads) {
|
|
if(hrand(4) == 0 && (short(c)%3==0))
|
|
return laBarrier;
|
|
return laCrossroads;
|
|
}
|
|
else if(nearland == laBarrier) {
|
|
return getNewLand(farland);
|
|
}
|
|
else {
|
|
if(hrand(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;
|
|
}
|
|
|
|
int newRoundTableRadius() {
|
|
return 28 + 2 * items[itHolyGrail];
|
|
}
|
|
|
|
int roundTableRadius(cell *c) {
|
|
if(euclid) return 28;
|
|
return c->master->alt->alt->emeraldval & GRAIL_RADIUS_MASK;
|
|
}
|
|
|
|
int celldistAltRelative(cell *c) {
|
|
return celldistAlt(c) - roundTableRadius(c);
|
|
}
|
|
|
|
void buildCamelotWall(cell *c) {
|
|
c->wall = waCamelot;
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = createMov(c, i);
|
|
if(c2->wall == waNone && (euclid || (c2->master->alt && c->master->alt)) && celldistAlt(c2) > celldistAlt(c) && c2->monst == moNone)
|
|
c2->wall = waCamelotMoat;
|
|
}
|
|
}
|
|
|
|
// This function generates all lands. Warning: it's very long!
|
|
|
|
void buildBarrier(cell *c, int d) {
|
|
d %= 7;
|
|
cellwalker bb(c, d);
|
|
|
|
if(checkBarriersFront(bb) && checkBarriersBack(bb)) {
|
|
c->bardir = d;
|
|
eLand oldland = c->land;
|
|
eLand newland = getNewLand(oldland);
|
|
if(showoff) newland = showlist[(showid++) % 10];
|
|
landcount[newland]++;
|
|
|
|
if(d == 5) c->barleft = oldland, c->barright = newland;
|
|
else c->barleft = newland, c->barright = oldland;
|
|
c->landparam = 40;
|
|
}
|
|
}
|
|
|
|
bool buildBarrier4(cell *c, int d, int mode, eLand ll, eLand lr) {
|
|
d %= 7;
|
|
cellwalker b1(c, d);
|
|
|
|
cellwalker b2(c, d);
|
|
cwstep(b2); cwspin(b2, 3); cwstep(b2); cwspin(b2, 3); cwstep(b2);
|
|
|
|
cellwalker b3(c, d);
|
|
cwstep(b3); cwspin(b3, 4); cwstep(b3); cwspin(b3, 4);
|
|
|
|
cellwalker b4(c, d);
|
|
cwstep(b4); cwspin(b4, -4); cwstep(b4); cwspin(b4, -4);
|
|
|
|
if(mode == 0) {
|
|
if(!((checkBarriersBack(b1) && checkBarriersBack(b2)))) return false;
|
|
if(!((checkBarriersFront(b3) && checkBarriersFront(b4)))) return false;
|
|
}
|
|
|
|
if(mode == 1) {
|
|
if(!(checkBarriersFront(b3, 5, true) && checkBarriersFront(b4, 5, true)))
|
|
return false;
|
|
}
|
|
|
|
if(mode == 2) {
|
|
if(!((checkBarriersBack(b1, 5, true) && checkBarriersBack(b2, 5, true))))
|
|
return false;
|
|
}
|
|
|
|
eLand xl = oppositeElement(ll);
|
|
eLand xr = oppositeElement(lr);
|
|
|
|
c->bardir = d, c->barleft = ll, c->barright = lr; buildBarrierBack(c);
|
|
|
|
c= b2.c; d=b2.spin;
|
|
c->bardir = d, c->barleft = xl, c->barright = xr; buildBarrierBack(c);
|
|
|
|
c= b3.c; d=b3.spin;
|
|
c->bardir = d, c->barleft = xl, c->barright = lr; buildBarrierFront(c);
|
|
|
|
c= b4.c; d=b4.spin;
|
|
c->bardir = d, c->barleft = ll, c->barright = xr; buildBarrierFront(c);
|
|
|
|
for(int a=-3; a<=3; a++) if(a) {
|
|
setland(cwpeek(b1, a), a > 0 ? lr : ll);
|
|
setland(cwpeek(b2, a), a > 0 ? xr : xl);
|
|
setland(cwpeek(b3, a), a > 0 ? lr : xl);
|
|
setland(cwpeek(b4, a), a > 0 ? xr : ll);
|
|
}
|
|
|
|
cell *cp;
|
|
cp = cwpeek(b1, 0);
|
|
cp->barleft = ll; cp->barright = lr; setbarrier(cp);
|
|
cp = cwpeek(b2, 0);
|
|
cp->barleft = xl; cp->barright = xr; setbarrier(cp);
|
|
|
|
return true;
|
|
}
|
|
|
|
void buildBarrierStrong(cell *c, int d, bool oldleft) {
|
|
d %= 7;
|
|
cellwalker bb(c, d);
|
|
|
|
c->bardir = d;
|
|
eLand oldland = c->land;
|
|
eLand newland = getNewLand(oldland);
|
|
if(showoff) newland = showlist[(showid++) % 10];
|
|
landcount[newland]++;
|
|
|
|
if(oldleft) c->barleft = oldland, c->barright = newland;
|
|
else c->barleft = newland, c->barright = oldland;
|
|
}
|
|
|
|
void buildCrossroads2(cell *c) {
|
|
|
|
if(buggyGeneration) return;
|
|
|
|
if(!c) return;
|
|
|
|
for(int i=0; i<c->type; i++)
|
|
if(c->mov[i] && !c->mov[i]->landparam && c->mov[i]->mpdist < c->mpdist)
|
|
buildCrossroads2(c->mov[i]);
|
|
|
|
if(c->bardir != NODIR && c->bardir != NOBARRIERS)
|
|
buildBarrier(c);
|
|
|
|
if(c->land != laCrossroads2) return;
|
|
|
|
if(!c->landparam) {
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = createMov(c, i);
|
|
if(c2 && c2->landparam) {
|
|
for(int j=0; j<c2->type; j++) {
|
|
createMov(c2, j);
|
|
cell *c3 = c2->mov[j];
|
|
if(c3 && c3->landparam) {
|
|
int h2 = c2->landparam;
|
|
int h3 = c3->landparam;
|
|
|
|
// ambiguous
|
|
if(h2/4 == 1 && h3/4 == 3) continue;
|
|
if(h2/4 == 3 && h3/4 == 1) continue;
|
|
|
|
for(int d=0; d<c2->type; d++)
|
|
if(emeraldtable[h2][d] == h3) {
|
|
int nh = emeraldtable[h2][(42+d + c->spn[i] - j) % c2->type];
|
|
if(c->landparam>0 && c->landparam != nh) printf("CONFLICT\n");
|
|
c->landparam = nh;
|
|
}
|
|
|
|
if(c->landparam == 0)
|
|
printf("H2 = %d H3=%d\n", c2->landparam, c3->landparam);
|
|
}
|
|
}
|
|
if(c->landparam == 0) {
|
|
printf("H2 = %d\n", c2->landparam);
|
|
describeCell(c2);
|
|
for(int i=0; i<c2->type; i++) describeCell(c2->mov[i]);
|
|
// halted = true;
|
|
// c->heat = -1;
|
|
c2->item = itCompass;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(c->landparam) {
|
|
// for(int i=0; i<c->type; i++) {
|
|
// cell *c2 = c->mov[i];
|
|
// buildCrossroads2(c2);
|
|
// }
|
|
}
|
|
else {
|
|
raiseBuggyGeneration(c, "buildCrossroads2");
|
|
return;
|
|
}
|
|
}
|
|
|
|
int h = c->landparam;
|
|
|
|
if(h/4 >= 8 && h/4 <= 11) {
|
|
for(int i=0; i<c->type; i++) {
|
|
createMov(c, i)->land = laCrossroads2;
|
|
if(!c->mov[i]->landparam) buildCrossroads2(c->mov[i]);
|
|
}
|
|
if(h/4 == 8 || h/4 == 10)
|
|
for(int i=0; i<c->type; i++) {
|
|
if(c->mov[i] && c->mov[i]->landparam == h-4) {
|
|
bool oldleft = true;
|
|
for(int j=1; j<=3; j++)
|
|
if(c->mov[(i+j)%7] && c->mov[(i+j)%7]->mpdist < c->mpdist)
|
|
oldleft = false;
|
|
|
|
buildBarrierStrong(c, i, oldleft);
|
|
c->landparam = h;
|
|
buildBarrier(c);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
extern bool bugtrack;
|
|
|
|
bool generatingEquidistant = false;
|
|
|
|
void buildAnotherEquidistant(cell *c) {
|
|
// printf("building another coast\n");
|
|
|
|
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;
|
|
|
|
generatingEquidistant = true;
|
|
|
|
cellwalker cw(c, (gdir+3) % c->type);
|
|
vector<cell*> coastpath;
|
|
while(coastpath.size() < 30 || cw.c->type != 7) {
|
|
coastpath.push_back(cw.c);
|
|
cwstep(cw); cwspin(cw, 3);
|
|
if(cw.c->type == 7 && hrand(2) == 0) cwspin(cw, 1);
|
|
}
|
|
coastpath.push_back(cw.c);
|
|
// printf("setdists\n");
|
|
for(int i=1; i<size(coastpath) - 1; i++) {
|
|
setdist(coastpath[i], BARLEV, coastpath[i-1]);
|
|
setdist(coastpath[i], BARLEV-1, coastpath[i-1]);
|
|
}
|
|
// printf("building barrier\n");
|
|
cell *c2 = coastpath[coastpath.size() - 1];
|
|
int bd = 2 + (hrand(2)) * 3;
|
|
buildBarrier(c2, bd);
|
|
// printf("building barrier II\n");
|
|
if(c2->bardir != NODIR && c2->bardir != NOBARRIERS)
|
|
buildBarrier(c2);
|
|
// printf("setdisting\n");
|
|
for(int i=size(coastpath)-2; i>=0; i--) {
|
|
for(int j=BARLEV; j>=6; j--)
|
|
setdist(coastpath[i], j, NULL);
|
|
// buildEquidistant(coastpath[i]);
|
|
// printf("i=%d heat=%f\n", i, coastpath[i]->heat);
|
|
// coastpath[i]->item = itCoast;
|
|
}
|
|
generatingEquidistant = false;
|
|
}
|
|
|
|
bool bugtrack = false, bugfound = false;
|
|
|
|
#define UNKNOWN 65535
|
|
|
|
int coastval(cell *c) {
|
|
if(!c) return UNKNOWN;
|
|
if(c->land == laNone) return UNKNOWN;
|
|
if(c->land == laOceanWall || c->land == laCaribbean || c->land == laWhirlpool ||
|
|
c->land == laLivefjord)
|
|
return 30;
|
|
if(c->land != laOcean && c->land != laEdge) {
|
|
return 0;
|
|
}
|
|
if(!c->landparam) return UNKNOWN;
|
|
return c->landparam;
|
|
}
|
|
|
|
bool cellEdgeUnstable(cell *c) {
|
|
if(c->land != laEdge || c->wall != waNone) return false;
|
|
int d = coastval(c);
|
|
for(int i=0; i<c->type; i++)
|
|
if(coastval(c->mov[i]) == d-1) {
|
|
if(againstWind(c->mov[i], c)) return false;
|
|
if(!passable(c->mov[i], NULL, true, false, false) &&
|
|
!isFire(c->mov[i]))
|
|
return false;
|
|
if(isWorm(c->mov[i]))
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
eMonster crossroadsMonster() {
|
|
|
|
static eMonster weak[9] = {
|
|
moYeti, moGoblin, moRanger, moOrangeDog, moRunDog, moMonkey, moZombie,
|
|
moDesertman, moCultist
|
|
};
|
|
|
|
if(hrand(10) == 0) return weak[hrand(9)];
|
|
|
|
static eMonster m[24] = {
|
|
moWorm, moTentacle,
|
|
moTroll, moEagle,
|
|
moLesser, moGreater, moPyroCultist, moGhost,
|
|
moFireFairy, moIvyRoot, moHedge,
|
|
moLancer, moFlailer, moVineBeast,
|
|
moBomberbird, moAlbatross, moRedTroll,
|
|
moWaterElemental, moAirElemental, moFireElemental,
|
|
moFatGuard, moMiner, moPalace, moVizier
|
|
};
|
|
return m[hrand(24)];
|
|
}
|
|
|
|
eMonster wanderingCrossroadsMonster() {
|
|
while(true) {
|
|
eMonster m = crossroadsMonster();
|
|
if(!isIvy(m) && m != moTentacle) return m;
|
|
}
|
|
}
|
|
|
|
void buildEquidistant(cell *c) {
|
|
if(!c) return;
|
|
if(c->landparam) return;
|
|
int mcv = UNKNOWN;
|
|
|
|
// find the lowest coastval
|
|
for(int i=0; i<c->type; i++) {
|
|
int cv = coastval(c->mov[i]);
|
|
if(cv < mcv) mcv = cv;
|
|
}
|
|
|
|
int mcv2 = 0;
|
|
|
|
if(bugfound) { mcv = 1; c->landparam = 10; }
|
|
|
|
else if(mcv == 0) {
|
|
// if(generatingEquidistant) printf("mcv=0\n");
|
|
c->landparam = 1;
|
|
}
|
|
else {
|
|
// if it appears twice, increase it
|
|
int qcv = 0;
|
|
int sid = 0;
|
|
for(int i=0; i<c->type; i++)
|
|
if(coastval(c->mov[i]) == mcv)
|
|
qcv++, sid = i;
|
|
|
|
// if(generatingEquidistant) printf("qcv=%d mcv=%d\n", qcv, mcv);
|
|
if(qcv >= 2) c->landparam = mcv+1;
|
|
else {
|
|
// if(qcv != 1) { printf("qcv = %d\n", qcv); exit(1); }
|
|
cell *c2 = c->mov[sid];
|
|
int bsid = c->spn[sid];
|
|
for(int j=0; j<7; j++) {
|
|
int q = (bsid+j+42) % c2->type;
|
|
cell *c3 = c2->mov[q];
|
|
if(coastval(c3) < mcv) {
|
|
cell *c4 = createMovR(c2, bsid+1);
|
|
buildEquidistant(c4);
|
|
mcv2 = coastval(c4);
|
|
break;
|
|
}
|
|
q = (bsid-j+42) % c2->type;
|
|
c3 = c2->mov[q];
|
|
if(coastval(c3) < mcv) {
|
|
cell *c4 = createMovR(c2, bsid-1);
|
|
buildEquidistant(c4);
|
|
mcv2 = coastval(c4);
|
|
break;
|
|
}
|
|
}
|
|
if(bugfound) c->item = itSapphire;
|
|
if(mcv2 > mcv) mcv2 = mcv;
|
|
if(mcv2 == 0) mcv2 = mcv;
|
|
c->landparam = mcv2+1;
|
|
|
|
/* if(c->heat < 3)
|
|
raiseBuggyGeneration(c, "low heat"); */
|
|
|
|
}
|
|
}
|
|
|
|
if(!c->landparam) {
|
|
// int z = int(c->heat);
|
|
if(c->item || c->monst)
|
|
printf("building coast over %s/%s, mpdist = %d\n", iinf[c->item].name, minf[c->monst].name,
|
|
c->mpdist);
|
|
if(c->land == laOcean) c->wall = waSea;
|
|
}
|
|
|
|
if(c->landparam > 30 && c->land == laOcean && !generatingEquidistant && hrand(10) < 5)
|
|
buildAnotherEquidistant(c);
|
|
}
|
|
|
|
void beCIsland(cell *c) {
|
|
int i = hrand(3);
|
|
if(i == 0) c->wall = waCIsland;
|
|
if(i == 1) c->wall = waCIsland2;
|
|
if(i == 2) c->wall = waCTree;
|
|
return;
|
|
}
|
|
|
|
void generateTreasureIsland(cell *c) {
|
|
if(!euclid) generateAlts(c->master);
|
|
if(isOnCIsland(c)) return;
|
|
|
|
bool src = hrand(100) < 10;
|
|
if(src) {
|
|
beCIsland(c);
|
|
if(c->wall == waCTree) return;
|
|
}
|
|
cell* ctab[7];
|
|
int qc = 0, qlo, qhi;
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = createMov(c, i);
|
|
if((euclid || (c->master->alt && c2->master->alt)) && celldistAlt(c2) < celldistAlt(c)) {
|
|
ctab[qc++] = c2;
|
|
qlo = i; qhi = i;
|
|
while(true) {
|
|
qlo--;
|
|
c2 = createMovR(c, qlo);
|
|
if(!c2->master->alt) break;
|
|
if(celldistAlt(c2) >= celldistAlt(c)) break;
|
|
ctab[qc++] = c2;
|
|
}
|
|
while(true) {
|
|
qhi++;
|
|
c2 = createMovR(c, qhi);
|
|
if(!c2->master->alt) break;
|
|
if(celldistAlt(c2) >= celldistAlt(c)) break;
|
|
ctab[qc++] = c2;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
if(!qc) { printf("NO QC\n"); c->wall = waSea; return; }
|
|
cell* c2 = createMovR(c, (qlo+qhi)/2);
|
|
generateTreasureIsland(c2);
|
|
if(!src) {
|
|
c->wall = c2->wall;
|
|
if(c->wall != waCTree && hrand(100) < 15)
|
|
c->wall = (c->wall == waCIsland ? waCIsland2 : waCIsland);
|
|
}
|
|
if(src && c2->wall == waCTree && (euclid||c->master->alt) && celldistAlt(c) <= -10) {
|
|
bool end = true;
|
|
for(int i=0; i<qc; i++) {
|
|
generateTreasureIsland(ctab[i]);
|
|
if(ctab[i]->wall != waCTree)
|
|
end = false;
|
|
}
|
|
// printf("%p: end=%d, qc=%d, dist=%d\n", c, end, qc, celldistAlt(c));
|
|
if(end) c->item = itPirate;
|
|
else c->item = itCompass;
|
|
}
|
|
}
|
|
|
|
#define ROCKSNAKELENGTH 50
|
|
|
|
void buildRedWall(cell *c, int gemchance) {
|
|
if(c->monst) return;
|
|
int ki = kills[moHexSnake] + kills[moRedTroll];
|
|
c->wall = waRed3;
|
|
if(hrand(100+ki) < gemchance + ki)
|
|
c->item = itRedGem;
|
|
if(items[itRedGem] >= 10 && hrand(18000) < gemchance)
|
|
c->item = itOrbTelekinesis;
|
|
}
|
|
|
|
bool generatingYendor = false;
|
|
bool yendorPath = false;
|
|
|
|
int palaceHP() {
|
|
if(items[itPalace] < 3) // 1+2
|
|
return 2;
|
|
else if(items[itPalace] < 10) // 1+2+3+4
|
|
return 3;
|
|
else if(items[itPalace] < 21) // 1+2+3+4+5+6
|
|
return 4;
|
|
else if(items[itPalace] < 35)
|
|
return 5;
|
|
else if(items[itPalace] < 50)
|
|
return 6;
|
|
else return 7;
|
|
}
|
|
|
|
bool havemouse;
|
|
|
|
cell *randomDown(cell *c) {
|
|
cell *tab[7];
|
|
int q=0;
|
|
for(int i=0; i<c->type; i++)
|
|
if(c->mov[i] && coastval(c->mov[i]) < coastval(c))
|
|
tab[q++] = c->mov[i];
|
|
if(!q) return NULL;
|
|
if(q==1) return tab[0];
|
|
return tab[hrand(q)];
|
|
}
|
|
|
|
// which=1 => right, which=-1 => left
|
|
// set which=1,bonus=1 to get right neighbor on level
|
|
|
|
cell *chosenDown(cell *c, int which, int bonus) {
|
|
int d = coastval(c)-1;
|
|
for(int i=0; i<c->type; i++) {
|
|
if(!c->mov[i]) createMov(c, i);
|
|
if(c->mov[i]->mpdist > BARLEV) setdist(c->mov[i], BARLEV, c);
|
|
|
|
if(coastval(c->mov[i]) == d) {
|
|
int i2 = (i+which+42)%c->type;
|
|
if(coastval(c->mov[i2]) == d)
|
|
return createMovR(c, i2+bonus);
|
|
else return createMovR(c, i+bonus);
|
|
}
|
|
}
|
|
// printf("nothing down here\n");
|
|
return NULL;
|
|
}
|
|
|
|
int edgeDepth(cell *c) {
|
|
if(c->land == laEdge) return coastval(c);
|
|
else if(c->land != laBarrier) {
|
|
for(int i=0; i<c->type; i++) if(c->mov[i] && c->mov[i]->land == laBarrier)
|
|
return -20+c->cpdist;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int towerval(cell *c) {
|
|
if(c->land != laEdge) return 0;
|
|
cell *cp1 = chosenDown(c, 1, 1);
|
|
if(!cp1) return 0;
|
|
/* cell *cp2 = chosenDown(cp1, 1, 1);
|
|
if(!cp2) return 0;
|
|
cell *cm1 = chosenDown(c, -1, -1);
|
|
if(!cm1) return 0;
|
|
cell *cm2 = chosenDown(cm1, -1, -1);
|
|
if(!cm2) return 0; */
|
|
|
|
/* return
|
|
(c->type-6)
|
|
+ 2*(cp1->type-6) + 4*(cp2->type-6)
|
|
+ 8*(cm1->type-6) +16*(cm2->type-6); */
|
|
|
|
int under = 0;
|
|
for(int i=0; i<c->type; i++) if(c->mov[i]->landparam < c->landparam)
|
|
under++;
|
|
return (c->type-6) + 2*(cp1->type-6) + 4*under;
|
|
}
|
|
|
|
int hivehard() {
|
|
return items[itRoyalJelly] + 5 * items[itOrbYendor] * (items[itOrbYendor] * 3 - 2);
|
|
// 0, 5, 40, 135
|
|
}
|
|
|
|
eMonster randomHyperbug() {
|
|
int h = hivehard();
|
|
if(hrand(200) < h)
|
|
return moBug2;
|
|
return eMonster(moBug0 + hrand(BUGCOLORS));
|
|
// 50: 25/25/50
|
|
// 100:
|
|
}
|
|
|
|
#define RANDPATC(c) (randpattern(c,randompattern[c->land]))
|
|
#define RANDPAT (randpattern(c,randompattern[c->land]))
|
|
#define RANDPAT3(i) (randpatternMajority(c,i,RANDITER))
|
|
#define RANDPATV(x) (randpattern(c,randompattern[x]))
|
|
|
|
void setdist(cell *c, int d, cell *from) {
|
|
DEB("setdist");
|
|
if(buggyGeneration) return;
|
|
if(signed(c->mpdist) <= d) return;
|
|
c->mpdist = d;
|
|
|
|
if(d >= BARLEV) {
|
|
if(!c->land && from->land != laElementalWall) c->land = from->land;
|
|
if(c->land == laTemple) c->land = laRlyeh;
|
|
if(c->land == laWhirlpool) c->land = laOcean;
|
|
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(euclidland == laWhirlpool) {
|
|
c->land = laOcean;
|
|
c->landparam = 99;
|
|
}
|
|
if(euclidland == laPrincessQuest)
|
|
c->land = laPalace;
|
|
if(euclidland == laOcean) {
|
|
eucoord x, y;
|
|
decodeMaster(c->master, x, y);
|
|
int y0 = y; if(y>50000) y0 -= 65536; y0 += 10;
|
|
if(y0 == 0)
|
|
{c->land = laBarrier; if(ishept(c)) c->land = laRlyeh; }
|
|
else if(y0<0) c->land = laRlyeh;
|
|
else c->landparam = y0;
|
|
}
|
|
if(euclidland == laEdge) {
|
|
eucoord x, y;
|
|
decodeMaster(c->master, x, y);
|
|
int y0 = y; if(y>50000) y0 -= 65536; y0 = -y0; y0 -= 5;
|
|
if(y0 == 0)
|
|
{c->land = laBarrier; if(ishept(c)) c->land = laAlchemist; }
|
|
else if(y0<0) c->land = laAlchemist;
|
|
else {
|
|
c->landparam = y0;
|
|
}
|
|
}
|
|
if(euclidland == laElementalWall) {
|
|
eucoord x, y;
|
|
decodeMaster(c->master, x, y);
|
|
int y0 = y; if(y>32768) y0 -= 65536;
|
|
int x0 = x +y/2;
|
|
|
|
int id = 0;
|
|
if(y0&16) id += 2;
|
|
if(x0&16) id += 1;
|
|
|
|
x0 += 8; y0 += 8;
|
|
|
|
y0--; x0++;
|
|
int id2 = 0;
|
|
if(y0&16) id2 += 2;
|
|
if(x0&16) id2 += 1;
|
|
|
|
c->land = eLand(laEFire + id);
|
|
|
|
if(((y0&15) == 15 && (x0&1)) || ((x0&15) == 0 && ((y0+1)&1))) {
|
|
if(c->land == laEFire) c->wall = waEternalFire;
|
|
if(c->land == laEWater) c->wall = waSea;
|
|
if(c->land == laEAir) c->wall = waChasm;
|
|
if(c->land == laEEarth) c->wall = waStone;
|
|
c->barright = c->land;
|
|
c->barleft = eLand(laEFire+id2);
|
|
c->land = laElementalWall;
|
|
}
|
|
}
|
|
if(euclidland == laCrossroads3) {
|
|
eucoord x, y;
|
|
decodeMaster(c->master, x, y);
|
|
int y0 = y; if(y>32768) y0 -= 65536;
|
|
int x0 = x +y/2;
|
|
|
|
x0 += 24; y0 += 8;
|
|
|
|
int id = 0;
|
|
if(y0&16) id ^= 1;
|
|
if(x0&16) id ^= 1;
|
|
|
|
c->land = id ? laCrossroads3 : laDesert;
|
|
|
|
if(((y0&15) == 15 && (x0&1)) || ((x0&15) == 0 && ((y0+1)&1))) {
|
|
c->wall = waBarrier;
|
|
c->land = laBarrier;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef MOBILE
|
|
if(d == BARLEV && c->land == laCanvas)
|
|
c->landparam = mapeditor::generateCanvas(c);
|
|
#endif
|
|
|
|
if(d == BARLEV && !euclid && c != cwt.c) {
|
|
|
|
bool deepOcean = false;
|
|
|
|
if(c->land == laOcean) {
|
|
if(!from) deepOcean = true;
|
|
else for(int i=0; i<from->type; i++) {
|
|
cell *c2 = from->mov[i];
|
|
if(c2 && c2->landparam > 30)
|
|
deepOcean = true;
|
|
}
|
|
}
|
|
|
|
if(generatingEquidistant) deepOcean = false;
|
|
|
|
if(c->land == laCrossroads2)
|
|
buildCrossroads2(c);
|
|
|
|
else if(c->type == 7 && hrand(10000) < (
|
|
showoff ? (cwt.c->mpdist > 7 ? 0 : 10000) :
|
|
princess::challenge ? 0 :
|
|
c->land == laCrossroads3 ? 10000 :
|
|
isElemental(c->land) ? 4000 :
|
|
c->land == laCrossroads ? 5000 :
|
|
c->land == laCaribbean ? 500 :
|
|
c->land == laCanvas ? 0 :
|
|
c->land == laOcean ? (deepOcean ? 1000 : 0) :
|
|
c->land == laEdge ? 0 :
|
|
50))
|
|
{
|
|
|
|
int bd = 2 + hrand(2) * 3;
|
|
|
|
buildBarrier(c, bd);
|
|
|
|
/* int bd = 2;
|
|
buildBarrier4(c, bd, 0, getNewLand(c->land), c->land); */
|
|
}
|
|
|
|
if(c->land == laCrossroads && c->type == 7 && hrand(2000) < 200 && items[itEmerald] >= 5) {
|
|
int rtr = newRoundTableRadius();
|
|
heptagon *alt = createAlternateMap(c, rtr+14, hsOrigin);
|
|
if(alt) {
|
|
alt->emeraldval = rtr;
|
|
}
|
|
}
|
|
|
|
if(true) {
|
|
if(c->land == laRlyeh && c->type == 7 && hrand(2000) < 100 && items[itStatue] >= 5 && !randomPatternsMode)
|
|
createAlternateMap(c, 2, hsA);
|
|
|
|
if(c->land == laOcean && c->type == 7 && hrand(2000) < 500 && deepOcean)
|
|
createAlternateMap(c, 2, hsA);
|
|
|
|
if(c->land == laCaribbean && c->type == 7)
|
|
createAlternateMap(c, 2, hsA);
|
|
|
|
if(c->land == laPalace && c->type == 7 && !princess::generating && !shmup::on &&
|
|
(princess::forceMouse ? (from && from->pathdist != INF) : (hrand(2000) < 20)) &&
|
|
!c->master->alt &&
|
|
(princess::challenge || kills[moVizier]))
|
|
createAlternateMap(c, 141, hsOrigin, waPalace);
|
|
|
|
if(c->bardir != NODIR && c->bardir != NOBARRIERS)
|
|
buildBarrier(c);
|
|
}
|
|
}
|
|
|
|
if(d < 10) {
|
|
explore[d]++;
|
|
exploreland[d][c->land]++;
|
|
|
|
if(d == BARLEV-2 && c->land == laOcean) {
|
|
buildEquidistant(c);
|
|
}
|
|
|
|
if(d < BARLEV) for(int i=0; i<c->type; i++) {
|
|
setdist(createMov(c, i), d+1, c);
|
|
}
|
|
|
|
if(d == 8 && c->land == laEdge) {
|
|
buildEquidistant(c);
|
|
}
|
|
|
|
if(d == 8 && c->land == laEdge && !euclid) {
|
|
|
|
if(hrand(1000) < 75 && // chosenDown(c, 1, 0)->landflags != 3 && chosenDown(c,-1,0)->landflags != 3 &&
|
|
(c->landparam&1) ) {
|
|
c->landflags = 3;
|
|
}
|
|
else c->landflags = 0;
|
|
}
|
|
|
|
if(d == 7 && c->land == laEdge) {
|
|
/* if(int(c->landparam) % 5 == 0)
|
|
c->wall = waCamelot;
|
|
*/
|
|
|
|
if(euclid) {
|
|
eucoord x, y;
|
|
decodeMaster(c->master, x, y);
|
|
string tab[] = {
|
|
".####...",
|
|
"L...L...",
|
|
".L..L...",
|
|
"L...L...",
|
|
"........",
|
|
"........"
|
|
};
|
|
int y0 = y; if(y>32768) y0 -= 65536;
|
|
|
|
y0 += 5; y0 %= 12; if(y0<0) y0+=12;
|
|
|
|
if(y0 >= 6) { y0 -= 6; x += 4; }
|
|
|
|
char ch = tab[y0][(x+(y+1)/2)&7];
|
|
|
|
if(ch == '#')
|
|
c->wall = waPlatform;
|
|
else if(ch == 'L')
|
|
c->wall = waLadder;
|
|
}
|
|
|
|
else if(true) {
|
|
|
|
cell *c2 = c;
|
|
cell *c3 = c;
|
|
|
|
bool rdepths[5];
|
|
for(int i=0; i<5; i++) {
|
|
if(coastval(c2) == 0) {
|
|
rdepths[i++] = false;
|
|
}
|
|
else {
|
|
cell *c4 = c2;
|
|
if(c2 != c3 && !isNeighbor(c2, c3)) {
|
|
for(int i=0; i<c2->type; i++) if(c2->mov[i] && isNeighbor(c2->mov[i], c3))
|
|
c4 = c2->mov[i];
|
|
}
|
|
rdepths[i] = c2 && c3 && c4 && (c2->landflags == 3 || c3->landflags == 3 || c4->landflags == 3);
|
|
c2 = chosenDown(c2, 1, 0);
|
|
c3 = chosenDown(c3, -1, 0);
|
|
}
|
|
}
|
|
|
|
if(rdepths[3]) {
|
|
c->wall = waPlatform;
|
|
// if(!c4->item) c4->item = itPalace;
|
|
}
|
|
else if(!rdepths[2] && !rdepths[4] && !rdepths[1]) {
|
|
c2 = c;
|
|
c3 = c;
|
|
cell *c4 = chosenDown(c, 1, 1);
|
|
cell *c5 = chosenDown(c, -1, -1);
|
|
for(int i=0; i<3; i++) {
|
|
if(coastval(c2) == 0) break;
|
|
if(c2 && c4 && c4->landflags == 3 && c2->landflags != 3 && c4 == chosenDown(c2, 1, 1))
|
|
c->wall = waLadder;
|
|
if(c3 && c5 && c5->landflags == 3 && c3->landflags != 3 && c5 == chosenDown(c3, -1, -1))
|
|
c->wall = waLadder;
|
|
buildEquidistant(c4); buildEquidistant(c5);
|
|
if(c2) c2 = chosenDown(c2, 1, 0);
|
|
if(c3) c3 = chosenDown(c3, -1, 0);
|
|
if(c4) c4 = chosenDown(c4, 1, 0);
|
|
if(c5) c5 = chosenDown(c5, -1, 0);
|
|
}
|
|
}
|
|
}
|
|
else c->wall = waCIsland;
|
|
}
|
|
|
|
if(d == 9 && c->land == laPalace) {
|
|
|
|
/* ELEMENTAL:
|
|
items[itOrbGhost] = 999;
|
|
int h = fiftyval(c);
|
|
eWall colorwalls[8] = {
|
|
waNone, waSea, waCIsland, waFloorA, waColumn, waDryTree, waChasm, waMirror
|
|
};
|
|
c->wall = colorwalls[h&7];
|
|
*/
|
|
|
|
if(cdist50(c) == 3 && polarb50(c) == 1)
|
|
c->wall = waPalace;
|
|
}
|
|
|
|
if(d == 8 && c->land == laPalace) {
|
|
|
|
// note: Princess Challenge brings back the normal Palace generation
|
|
bool lookingForPrincess = !euclid && c->master->alt && !princess::challenge;
|
|
|
|
if(cdist50(c) == 3 && polarb50(c) == 1) {
|
|
int q = 0, s = 0;
|
|
if(!ishept(c)) for(int i=0; i<6; i++)
|
|
if(cdist50(c->mov[i]) == 3 && polarb50(c->mov[i]) == 1 && !ishept(c->mov[i]))
|
|
q++, s += i;
|
|
if(q == 1 && c->mov[s]->land == laPalace) {
|
|
switch(princess::generating ? 0 : hrand(2)) {
|
|
case 0:
|
|
c->wall = waClosedGate;
|
|
c->mov[s]->wall = waClosedGate;
|
|
break;
|
|
case 1:
|
|
c->wall = waOpenGate;
|
|
c->mov[s]->wall = waOpenGate;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
else if((hrand(100) < (lookingForPrincess ? 7 : 5) && cdist50(c)) ||
|
|
(cdist50(c) == 0 && polarb50(c) && hrand(100) < 60)) {
|
|
c->wall = hrand(100) < (lookingForPrincess ? 30:50) ? waClosePlate : waOpenPlate;
|
|
}
|
|
else if(hrand(100) < (lookingForPrincess ? 3 : 5))
|
|
c->wall = waTrapdoor;
|
|
}
|
|
|
|
if(d==8 && c->land == laEmerald) {
|
|
if(randomPatternsMode)
|
|
c->wall = RANDPAT3(0) ? waCavewall : waCavefloor;
|
|
else 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 = emeraldval(c);
|
|
if((v&3) >= 2)
|
|
c->wall = waCavewall;
|
|
else c->wall = waCavefloor;
|
|
}
|
|
}
|
|
|
|
if(d == 8 && isIcyLand(c)) c->landparam = 0;
|
|
if(d == 8 && c->land == laDryForest) c->landparam = 0;
|
|
|
|
if(d==8 && c->land == laPower) {
|
|
int v;
|
|
if(randomPatternsMode)
|
|
v = RANDPAT ? 24 : 0;
|
|
else 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 = emeraldval(c);
|
|
v &= ~3;
|
|
if((v == 24 || v == 32 || v == 56))
|
|
c->wall = waEternalFire;
|
|
else if(hrand(100) < 10) {
|
|
c->wall = waGlass;
|
|
eItem protectedItems[18] = {
|
|
itPower, itPower, itPower, itPower, itPower, itPower,
|
|
itOrbLightning, itOrbLightning, itOrbThorns, itOrbThorns,
|
|
itOrbInvis, itOrbInvis,
|
|
itOrbShield, itOrbTeleport, itOrbPsi,
|
|
itOrbDragon, itOrbIllusion, itOrbPreserve
|
|
};
|
|
c->item = protectedItems[hrand(18)];
|
|
}
|
|
}
|
|
|
|
if(d==8 && c->land == laZebra) {
|
|
if(euclid) {
|
|
eucoord x, y;
|
|
decodeMaster(c->master, x, y);
|
|
if(y&1) c->wall = waTrapdoor;
|
|
else c->wall = waNone;
|
|
}
|
|
else
|
|
c->wall = (randomPatternsMode ? RANDPAT : (zebra40(c)&2)) ? waTrapdoor : waNone;
|
|
}
|
|
|
|
if(d==8 && isElemental(c->land)) {
|
|
if(hrand(c->land == laEAir ? 6 : 25) == 0) {
|
|
if(c->land == laEFire) c->wall = waEternalFire;
|
|
else if(c->land == laEWater) c->wall = waSea;
|
|
else if(c->land == laEAir) c->wall = waChasm;
|
|
else if(c->land == laEEarth) c->wall = waStone;
|
|
}
|
|
}
|
|
|
|
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 = emeraldval(c);
|
|
int w = v / 4;
|
|
if(randomPatternsMode) c->wall = RANDPAT ? waVinePlant : waNone;
|
|
else 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(randomPatternsMode) c->wall = RANDPAT ? waIcewall : waNone;
|
|
else if(hrand(100) < 5 && c->wall != waBarrier) {
|
|
c->wall = waIcewall;
|
|
for(int i=0; i<c->type; i++) if(hrand(100) < 50) {
|
|
cell *c2 = createMov(c, i);
|
|
setdist(c2, d+1, c);
|
|
if(c2->wall == waBarrier || c2->land != laIce) continue;
|
|
c2->wall = waIcewall;
|
|
for(int j=0; j<c2->type; j++) if(hrand(100) < 20) {
|
|
cell *c3 = createMov(c2, j);
|
|
setdist(c->mov[i], d+2, c);
|
|
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 && hrand(200) == 1)
|
|
c->wall = waBonfireOff;
|
|
|
|
if(c->land == laCaves)
|
|
c->wall = (randomPatternsMode ? RANDPAT3(1) : hrand(100) < 55) ? waCavewall : waCavefloor;
|
|
|
|
if(c->land == laLivefjord) {
|
|
int die = (randomPatternsMode ? (RANDPAT3(2)?100:0) : hrand(100));
|
|
if(die < 50)
|
|
c->wall = waSea;
|
|
else
|
|
c->wall = waNone;
|
|
}
|
|
|
|
if(c->land == laDeadCaves) {
|
|
int die = (randomPatternsMode ? (RANDPAT?100:0) : hrand(100));
|
|
if(die<50) c->wall = waDeadwall;
|
|
else if(die<55) c->wall = waDeadfloor2;
|
|
else c->wall = waDeadfloor;
|
|
}
|
|
|
|
if(c->land == laAlchemist)
|
|
c->wall = (randomPatternsMode ? RANDPAT : hrand(2)) ? waFloorA : waFloorB;
|
|
|
|
if(c->land == laDryForest) {
|
|
if(randomPatternsMode)
|
|
c->wall = RANDPAT ? waNone : RANDPATV(laHell) ? waDryTree : waWetTree;
|
|
else
|
|
c->wall =
|
|
(hrand(100) < 50) ? (hrand(100) < 50 ? waDryTree : waWetTree) : waNone;
|
|
}
|
|
|
|
if(c->land == laGraveyard) {
|
|
if(randomPatternsMode)
|
|
c->wall = RANDPAT ? ((RANDPATV(laCrossroads) || RANDPATV(laCrossroads2)) ? waAncientGrave : waFreshGrave) : waNone;
|
|
else if(ishept(c))
|
|
c->wall = hrand(5) ? waAncientGrave : waFreshGrave;
|
|
}
|
|
|
|
if(c->land == laRlyeh) {
|
|
if(randomPatternsMode) {
|
|
c->wall = RANDPAT ? waColumn : waNone;
|
|
}
|
|
else {
|
|
if(hrand(500) < 5) {
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = createMov(c, i);
|
|
setdist(c2, d+1, c);
|
|
if(c2 && c2->land == laRlyeh)
|
|
c2->wall = waColumn;
|
|
}
|
|
|
|
for(int j=0; j<2; j++) {
|
|
int i = hrand(c->type);
|
|
if(c->mov[i] && c->mov[i]->land == laRlyeh)
|
|
c->mov[i]->wall = waNone;
|
|
}
|
|
}
|
|
if(ishept(c) && hrand(2)) c->wall = waColumn;
|
|
}
|
|
}
|
|
|
|
if(c->land == laHell) {
|
|
if(hrand(1000) < 36) {
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = createMov(c, i);
|
|
setdist(c2, d+1, c);
|
|
if(c2 && c2->land == laHell)
|
|
if(c2->wall != waSulphurC)
|
|
c2->wall = waSulphur;
|
|
}
|
|
|
|
c->wall = waSulphurC;
|
|
}
|
|
}
|
|
|
|
if(c->land == laCocytus) {
|
|
if(c->wall == waNone) c->wall = waFrozenLake;
|
|
if(hrand(100) < 5) {
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = createMov(c, i);
|
|
setdist(c2, d+1, c);
|
|
if(c2 && c2->land == laCocytus)
|
|
c2->wall = waLake;
|
|
}
|
|
|
|
c->wall = waLake;
|
|
|
|
if(hrand(500) < 100 + 2 * (items[itSapphire]))
|
|
c->monst = moShark;
|
|
}
|
|
}
|
|
|
|
if(isHive(c->land) && hrand(2000) < 2)
|
|
createBugArmy(c);
|
|
|
|
if(c->land == laPalace && !euclid && c->master->alt) {
|
|
int d = celldistAlt(c);
|
|
if(d <= 150) generateAlts(c->master);
|
|
}
|
|
|
|
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 && roundTableRadius(c) > 20) {
|
|
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 && hrand(7000) <= 10 + items[itHolyGrail] * 5) {
|
|
eMonster m[3] = { moHedge, moLancer, moFlailer };
|
|
c->monst = m[hrand(3)];
|
|
}
|
|
if(d == 1) {
|
|
// roughly as many knights as table cells
|
|
if(hrand(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(c->land == laOcean || c->land == laWhirlpool) {
|
|
if(euclid || (c->master->alt && c->master->alt->distance <= 2)) {
|
|
if(!euclid) generateAlts(c->master);
|
|
c->bardir = NOBARRIERS;
|
|
int dd = celldistAlt(c);
|
|
if(dd <= 0) {
|
|
c->land = laWhirlpool, c->wall = waSea, c->monst = moNone, c->item = itNone;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(c->land == laCaribbean) {
|
|
if(!euclid) {
|
|
if(c->master->alt && c->master->alt->distance <= 2) {
|
|
if(!euclid) generateAlts(c->master);
|
|
c->bardir = NOBARRIERS;
|
|
int d = celldistAlt(c);
|
|
if(d <= 0)
|
|
// c->wall = waChasm;
|
|
generateTreasureIsland(c);
|
|
else
|
|
c->wall = waSea;
|
|
}
|
|
else c->wall = waSea;
|
|
}
|
|
else {
|
|
int d = celldistAlt(c);
|
|
if(d <= 0)
|
|
generateTreasureIsland(c);
|
|
else
|
|
c->wall = waSea;
|
|
}
|
|
}
|
|
|
|
if(isHive(c->land) && hrand(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->landparam = rand() & 0xFFFFFF;
|
|
}
|
|
/* for(int i=0; i<c->type; i++) {
|
|
if(hrand(6) < 5) {
|
|
createMov(c,i);
|
|
cell *c2 = c->mov[i];
|
|
c2->wall = waWaxWall;
|
|
c2->monst = moNone;
|
|
}
|
|
} */
|
|
}
|
|
|
|
if(c->land == laDesert) {
|
|
if(randomPatternsMode)
|
|
c->wall = RANDPAT ? waDune : waNone;
|
|
else {
|
|
if(hrand(100) < 5) {
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = createMov(c, i);
|
|
setdist(c2, d+1, c);
|
|
if(c2 && c2->land == laDesert)
|
|
c2->wall = waDune;
|
|
}
|
|
|
|
for(int j=0; j<2; j++) {
|
|
int i = hrand(c->type);
|
|
if(c->mov[i] && c->mov[i]->land == laDesert)
|
|
c->mov[i]->wall = waNone;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(hrand(300) == 1 && items[itSpice] >= 5) c->wall = waThumperOff;
|
|
}
|
|
|
|
if(c->land == laRedRock) {
|
|
if(randomPatternsMode) {
|
|
c->wall = waNone;
|
|
if(!ishept(c)) { if(RANDPAT) buildRedWall(c, 20); }
|
|
else {
|
|
int k = 0;
|
|
for(int i=0; i<20; i++)
|
|
if(RANDPATC(c->mov[i%7]) && !RANDPATC(c->mov[(i+1)%7]))
|
|
k++;
|
|
if(k>=4) buildRedWall(c, 20);
|
|
}
|
|
}
|
|
else if(ishept(c) && hrand(100) < 8 && !c->monst) {
|
|
buildRedWall(c, 80);
|
|
int i = hrand(7);
|
|
buildRedWall(createMovR(c, i), 33);
|
|
if(hrand(2) == 0)
|
|
buildRedWall(createMovR(createMovR(c, i), c->spn[i]+(hrand(2)?2:4)), 20);
|
|
i += 3 + hrand(2);
|
|
if(hrand(6) < 4)
|
|
buildRedWall(createMovR(c, i), 33);
|
|
if(hrand(2) == 0)
|
|
buildRedWall(createMovR(createMovR(c, i), c->spn[i]+(hrand(2)?2:4)), 20);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(d == 8 && c->land == laCaribbean && !euclid) {
|
|
int mindist = 9;
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = c->mov[i];
|
|
if((euclid || c2->master->alt) && celldistAlt(c2) < mindist)
|
|
mindist = celldistAlt(c2);
|
|
}
|
|
if(mindist == 0) beCIsland(c);
|
|
}
|
|
|
|
if(d == 7 && c->land == laRedRock && c->wall == waNone && hrand(1000) == 0)
|
|
buildPrizeMirror(c);
|
|
|
|
if(d == 7 && c->land == laCaribbean && c->wall == waSea) {
|
|
bool coast = false;
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = c->mov[i];
|
|
if(passable(c2, c, false, false, false) && c2->wall != waBoat) coast = true;
|
|
}
|
|
if(coast && hrand(10) < 5) {
|
|
c->wall = waBoat;
|
|
if(items[itPirate] >= 10 && hrand(100) < 2)
|
|
c->item = itOrbPreserve;
|
|
else if(hrand(100) < 2*PRIZEMUL)
|
|
placePrizeOrb(c);
|
|
}
|
|
}
|
|
|
|
if(d == 7 && c->land == laCaves && c->wall == waCavewall && hrand(5000) < items[itGold])
|
|
c->monst = moSeep;
|
|
|
|
if(d == 7 && c->land == laLivefjord && c->wall == waSea && hrand(5000) < 15 + items[itFjord]) {
|
|
if(items[itFjord] >= 5 && hrand(100) < 20)
|
|
c->monst = moWaterElemental;
|
|
else {
|
|
c->monst = moViking;
|
|
c->wall = waBoat;
|
|
}
|
|
}
|
|
|
|
if(d == 7 && c->land == laLivefjord && hrand(2000) < 50 + kills[moViking] && notDippingFor(itFjord)) {
|
|
c->item = itFjord;
|
|
}
|
|
|
|
if(d == 7 && c->land == laLivefjord && items[itFjord] >= 10 && hrand(2000) < 2)
|
|
c->item = itOrbFish;
|
|
|
|
if(d == 7 && c->land == laLivefjord && hrand(2000) < 2*PRIZEMUL)
|
|
placePrizeOrb(c);
|
|
|
|
if(d == 7 && c->land == laLivefjord && hrand(2000) < 2)
|
|
buildPrizeMirror(c);
|
|
|
|
if(d == 7 && c->land == laEmerald && c->wall == waCavewall && hrand(5000) < items[itEmerald])
|
|
c->monst = moSeep;
|
|
|
|
if(d == 7 && c->land == laDeadCaves && c->wall == waDeadwall && hrand(1000) < items[itSilver])
|
|
c->monst = moSeep;
|
|
|
|
if(d == 7 && c->wall == waVinePlant && hrand(100) < (randomPatternsMode ? 2 : 10))
|
|
c->monst = moVineSpirit;
|
|
|
|
if(d == 7 && c->land == laOcean && !safety) {
|
|
if(c->landparam >= 1 && c->landparam <= 25) {
|
|
if(hrand(1000) < 5)
|
|
c->wall = waBoat;
|
|
if(hrand(1000) < 50 + kills[moAlbatross]/2)
|
|
c->item = itCoast;
|
|
if(hrand(15000) < 10 + 2 * items[itCoast])
|
|
c->monst = moAlbatross;
|
|
if(items[itCoast] >= 10 && hrand(10000) < 5)
|
|
c->item = itOrbAir;
|
|
if(hrand(10000) < 5*PRIZEMUL)
|
|
placePrizeOrb(c);
|
|
if(hrand(10000) < 5)
|
|
buildPrizeMirror(c);
|
|
}
|
|
else if(c->landparam > 25) {
|
|
if(hrand(30000) < 10) {
|
|
c->wall = waBoat;
|
|
c->monst = moPirate;
|
|
// orbs are possible!
|
|
placeOceanOrbs(c);
|
|
}
|
|
else if(hrand(30000) < 10)
|
|
c->monst = moCShark;
|
|
}
|
|
}
|
|
|
|
if(d == 7 && c->land == laCaribbean && c->wall == waSea && hrand(10000) < 20 + items[itPirate])
|
|
c->monst = moCShark;
|
|
|
|
if(d == 7 && c->wall == waCTree && hrand(5000) < 100 + items[itPirate])
|
|
c->monst = moParrot;
|
|
|
|
// 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 == 3 && c->land == laMinefield && safety && c->wall == waMineMine)
|
|
c->wall = waMineOpen;
|
|
|
|
if(d == 7 && c->land == laMinefield) {
|
|
c->wall = waMineUnknown;
|
|
// 250: rare mines
|
|
// 1250: at 25
|
|
int minefreq = 0;
|
|
int treas = items[itBombEgg];
|
|
if(treas <= 10) minefreq = 250 + 30 * treas;
|
|
if(treas <= 110) minefreq = 550 + 10 * (treas-10);
|
|
else minefreq = 1550 + (treas - 110);
|
|
|
|
// experimentation says that 600 is slightly too hard to find the Orb of Yendor
|
|
if(generatingYendor || yendorPath)
|
|
if(minefreq < 550)
|
|
minefreq = 550;
|
|
|
|
int tfreq =
|
|
treas < 10 ? 50 + 5 * treas :
|
|
treas < 25 ? 100 + (treas-10) * 2:
|
|
treas < 50 ? 150 + (treas-25) :
|
|
175;
|
|
|
|
if(hrand(5000) < minefreq)
|
|
c->wall = waMineMine;
|
|
else if(hrand(5000) < tfreq && !safety) {
|
|
c->item = itBombEgg;
|
|
c->landparam = items[itBombEgg] + 5 + hrand(11);
|
|
}
|
|
else if(hrand(5000) < treas - 20 + items[itOrbYendor] * 5 && !safety)
|
|
c->monst = moBomberbird;
|
|
else if(treas >= 10 && hrand(5000) < 10 && !safety)
|
|
c->item = itOrbFriend;
|
|
else if(hrand(5000) < 10*PRIZEMUL && !safety)
|
|
placePrizeOrb(c);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
if(d == 7 && c->wall == waElementalD) {
|
|
chasmifyElemental(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 != waFire && c2->wall != waGlass) {
|
|
if(c->wall == waFire) c->monst = moWitchWinter;
|
|
else if(c->wall == waGlass) c->monst = moWitchGhost;
|
|
else c->monst = moEvilGolem;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(d == 7 && passable(c, NULL, false, false, false)) {
|
|
if(c->land == laBarrier) c->wall = waBarrier;
|
|
if(c->land == laOceanWall) c->wall = c->type == 7 ? waBarrier : waSea;
|
|
}
|
|
|
|
if(d == 7 && c->land == laWhirlpool)
|
|
whirlGenerate(c);
|
|
|
|
if(c->land == laPalace && princess::generating) {
|
|
// no Opening Plates nearby
|
|
if(d <= 7 && c->wall == waOpenPlate)
|
|
c->wall = waNone;
|
|
// no monsters nearby
|
|
if(d>0) c->monst = moNone;
|
|
// no Plates or Trapdoors in the Princess cell
|
|
if(d < 3 && (c->wall == waClosePlate || c->wall == waOpenPlate || c->wall == waTrapdoor))
|
|
c->wall = waNone;
|
|
if(d > 1) c->item = itNone;
|
|
// the Princess herself
|
|
if(d == 0) {
|
|
c->monst = moPrincess;
|
|
c->hitpoints = palaceHP();
|
|
c->wall = waGiantRug;
|
|
cell *c2 = NULL;
|
|
for(int i=0; i<c->type; i++) {
|
|
cellwalker cw(c, i);
|
|
cwstep(cw);
|
|
cwspin(cw, 4);
|
|
cwstep(cw);
|
|
cwspin(cw, 2);
|
|
cwstep(cw);
|
|
cwspin(cw, 4);
|
|
cwstep(cw);
|
|
cwspin(cw, 2 + hrand(3));
|
|
cwstep(cw);
|
|
if(!c2) c2 = cw.c;
|
|
else if(celldist(cw.c) > celldist(c2)) c2 = cw.c;
|
|
cw.c->monst = moMouse;
|
|
}
|
|
c2->wall = waOpenPlate;
|
|
}
|
|
|
|
}
|
|
|
|
if(d == 7 && passable(c, NULL, false, false, false) && !safety) {
|
|
int hard = items[itOrbYendor] * 5;
|
|
|
|
if(c->land == laEdge) {
|
|
if(hrand(20000) < 20 + items[itEdge]) {
|
|
if(cellEdgeUnstable(c))
|
|
c->monst = moGargoyle;
|
|
else
|
|
c->monst = moEdgeMonkey;
|
|
}
|
|
else if(c->landparam >= 14 && hrand(2000) < 50+kills[moGargoyle]+kills[moEdgeMonkey] && !cellEdgeUnstable(c) ) {
|
|
c->item = itEdge;
|
|
}
|
|
}
|
|
|
|
if(c->land == laIce) {
|
|
if(hrand(5000) < 100 + 2 * (kills[moYeti] + kills[moWolf]) && notDippingFor(itDiamond))
|
|
c->item = itDiamond;
|
|
if(hrand(8000) < 2 * (items[itDiamond] + hard))
|
|
c->monst = hrand(2) ? moYeti : moWolf;
|
|
}
|
|
if(c->land == laPalace) {
|
|
bool lookingForPrincess0 = !euclid && c->master->alt;
|
|
bool lookingForPrincess = lookingForPrincess0 && !princess::challenge;
|
|
int hardness = lookingForPrincess ? 5 : items[itPalace] + hard;
|
|
|
|
if(hrand(5000) < 100 + 2 * (kills[moPalace] + kills[moFatGuard] + kills[moVizier] + kills[moSkeleton]) && notDippingFor(itPalace) &&
|
|
c->wall != waOpenGate && !lookingForPrincess0)
|
|
c->item = itPalace;
|
|
if(items[itPalace] >= 10 && hrand(5000) < 16 && c->wall != waOpenGate)
|
|
c->item = hrand(100) < 80 ? itOrbFrog : itOrbDiscord;
|
|
if(hrand(5000) < 20*PRIZEMUL && c->wall != waOpenGate)
|
|
placePrizeOrb(c);
|
|
if(hrand(5000) < 20 && c->wall == waNone)
|
|
buildPrizeMirror(c);
|
|
if(c->land == laPalace && (euclid || c->master->alt) && celldistAlt(c) <= 150 && !havemouse && !princess::generating &&
|
|
(euclid || (princess::getPrisonInfo(c)->bestdist < 6 && princess::getPrisonInfo(c)->princess))) {
|
|
c->monst = moMouse;
|
|
addMessage(XLAT("You hear a distant squeak!"));
|
|
drawFlash(c);
|
|
/* {
|
|
cell *c2= c;
|
|
z:
|
|
c2->item = itPirate;
|
|
printf("AT %p\n", c2);
|
|
for(int i=0; i<c2->type; i++) if(c2->mov[i] && c2->mov[i]->mpdist < c2->mpdist) {
|
|
c2 = c2->mov[i];
|
|
goto z;
|
|
}
|
|
} */
|
|
havemouse = true;
|
|
}
|
|
else if(hrand(15000) < 10 + hardness) {
|
|
c->monst = moPalace;
|
|
c->hitpoints = palaceHP();
|
|
if(hrand(10 + items[itPalace]) >= 14 && !lookingForPrincess)
|
|
c->monst = moSkeleton;
|
|
}
|
|
else if(hrand(20000) < hardness) {
|
|
c->monst = moFatGuard;
|
|
c->hitpoints = palaceHP();
|
|
}
|
|
else if(hrand(20000) < hardness - 7) {
|
|
c->monst = moVizier;
|
|
c->hitpoints = palaceHP();
|
|
}
|
|
else if(princess::forceVizier && from->pathdist != INF) {
|
|
c->monst = moVizier;
|
|
c->hitpoints = palaceHP();
|
|
princess::forceVizier = false;
|
|
}
|
|
}
|
|
if(c->land == laCaves) {
|
|
if(hrand(5000) < 100 + 2 * min(kills[moTroll] + kills[moGoblin], 150) && notDippingFor(itGold))
|
|
c->item = itGold;
|
|
if(hrand(8000) < 10 + 2 * (items[itGold] + hard))
|
|
c->monst = hrand(2) ? moTroll : moGoblin;
|
|
}
|
|
if(c->land == laLivefjord) {
|
|
if(hrand(16000) < 10 + 2 * (items[itFjord] + hard))
|
|
c->monst = moFjordTroll;
|
|
}
|
|
if(c->land == laDeadCaves) {
|
|
if(hrand(5000) < 100 + 2 * (kills[moDarkTroll] + kills[moEarthElemental]) && notDippingFor(itSilver))
|
|
c->item = itSilver;
|
|
if(hrand(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(hrand(8000) < 60 + 8 * (items[itSilver] + hard)) {
|
|
if(hrand(100) < 25) {
|
|
}
|
|
else c->monst = hrand(2) ? moDarkTroll : moGoblin;
|
|
}
|
|
}
|
|
if(c->land == laDesert) {
|
|
if(hrand(5000) < 100 + 2 * (kills[moWorm] + kills[moDesertman]) && notDippingFor(itSpice))
|
|
c->item = itSpice;
|
|
if(hrand(8000) < 10 + 2 * (items[itSpice] + hard))
|
|
c->monst = hrand(2) ? moWorm : moDesertman,
|
|
c->mondir = NODIR;
|
|
}
|
|
if(c->land == laRedRock) {
|
|
if(hrand(16000) < 30+items[itRedGem] && !ishept(c)) {
|
|
int i = -1;
|
|
for(int t=0; t<6; t++) if(c->mov[t]->mpdist > c->mpdist && !ishept(c->mov[t]))
|
|
i = t;
|
|
if(i != -1) {
|
|
c->monst = moHexSnake;
|
|
c->bardir = NOBARRIERS;
|
|
int len = ROCKSNAKELENGTH;
|
|
cell *c2 = c;
|
|
while(--len) {
|
|
c2->bardir = NOBARRIERS;
|
|
c2->mondir = i;
|
|
createMov(c2, i);
|
|
int j = c2->spn[i];
|
|
c2 = c2->mov[i];
|
|
c2->monst = moHexSnakeTail;
|
|
if(c2->bardir != NODIR) return;
|
|
i = (j + (len%2 ? 2 : 4)) % 6;
|
|
}
|
|
c2->mondir = NODIR;
|
|
}
|
|
}
|
|
else if(hrand(16000) < 50+items[itRedGem] && !ishept(c))
|
|
c->monst = moRedTroll,
|
|
c->mondir = NODIR;
|
|
}
|
|
if(c->land == laWineyard) {
|
|
if(hrand(5000) < 100 + 2 * (kills[moVineBeast] + kills[moVineSpirit]) && notDippingFor(itWine))
|
|
c->item = itWine;
|
|
if(hrand(8000) < 12 * (items[itWine] + hard))
|
|
c->monst = moVineBeast;
|
|
}
|
|
if(c->land == laZebra) {
|
|
if(c->wall == waNone && hrand(2500) < 100 + 2 * (kills[moOrangeDog]) && notDippingFor(itZebra))
|
|
c->item = itZebra;
|
|
if(hrand(10000) < 40 + 2*(items[itZebra] + hard))
|
|
c->monst = moOrangeDog;
|
|
}
|
|
if(isElemental(c->land) && c->land != laElementalWall) {
|
|
eItem localshard = eItem(itFireShard + (c->land - laEFire));
|
|
int danger = 5 * items[localshard] * items[localshard];
|
|
eMonster elof = elementalOf(c->land);
|
|
int elkills = kills[elof];
|
|
|
|
if(hrand(8000) < 12 + (items[itElemental] + danger + hard)) {
|
|
c->monst = elof;
|
|
if(c->land != laEAir) chasmifyElemental(c);
|
|
c->wall = waNone;
|
|
if(c->land == laEWater) c->wall = waSea;
|
|
}
|
|
else if(hrand(5000) < 100 + elkills*3 && notDippingFor(itElemental))
|
|
c->item = localshard;
|
|
else if(hrand(5000) < 10 && items[itElemental] >= 10)
|
|
c->item = itOrbSummon;
|
|
else if(hrand(5000) < 10*PRIZEMUL)
|
|
placePrizeOrb(c);
|
|
}
|
|
if(c->land == laEmerald) {
|
|
if(hrand(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(hrand(8000) < 50 + 10 * (items[itEmerald] + hard)) {
|
|
static eMonster emeraldmonsters[4] = { moHedge, moLancer, moFlailer, moMiner };
|
|
c->monst = emeraldmonsters[hrand(4)];
|
|
}
|
|
}
|
|
if(c->land == laJungle) {
|
|
if(hrand(5000) < 25 + 2 * (kills[moIvyRoot] + kills[moMonkey]) && notDippingFor(itRuby))
|
|
c->item = itRuby;
|
|
if(hrand(15000) < 5 + 1 * (items[itRuby] + hard))
|
|
c->monst = moMonkey;
|
|
else if(hrand(80000) < 5 + items[itRuby] + hard)
|
|
c->monst = moEagle;
|
|
else if(ishept(c) && hrand(4000) < 300 + items[itRuby]) {
|
|
bool hard = hrand(100) < 25;
|
|
if(hard ? buildIvy(c, 1, 9) : buildIvy(c, 0, c->type))
|
|
c->item = itRuby;
|
|
}
|
|
}
|
|
if(c->land == laAlchemist) {
|
|
if(hrand(5000) < 25 + min(kills[moSlime], 200) && notDippingFor(itElixir))
|
|
c->item = itElixir;
|
|
else if(hrand(3500) < 10 + items[itElixir] + hard)
|
|
c->monst = moSlime;
|
|
}
|
|
if(c->land == laPower) {
|
|
if(hrand(5000+50*items[itPower]) < 1200) {
|
|
eItem powerorbs[6] = {
|
|
itOrbFlash, itOrbSpeed, itOrbFire, itOrbWinter, itOrbGhost, itOrbLife};
|
|
c->item = powerorbs[hrand(6)];
|
|
}
|
|
else if(c->type == 6 && hrand(5000) < 10)
|
|
c->wall = hrand(2) ? waMirror : waCloud;
|
|
else if(hrand(1000) < 10 + (items[itPower] ? 10:0) + (items[itPower] + hard))
|
|
c->monst = eMonster(moWitch + hrand(NUMWITCH));
|
|
}
|
|
if(isCrossroads(c->land)) {
|
|
if(c->type == 6 && hrand(8000) < 120 && items[itShard] >= 10)
|
|
c->wall = hrand(2) ? waMirror : waCloud;
|
|
else {
|
|
if(hyperstonesUnlocked() && hrand(25000) < tkills() && notDippingFor(itHyperstone))
|
|
c->item = itHyperstone;
|
|
if(hrand(4000) < items[itHyperstone] + 2 * items[itHolyGrail]) {
|
|
// only interesting monsters here!
|
|
eMonster cm = crossroadsMonster();
|
|
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 && hrand(5000) < 120 && notDippingFor(itShard))
|
|
c->wall = hrand(2) ? waMirror : waCloud;
|
|
else if(c->type == 7 && hrand(5000) < 10 * PRIZEMUL)
|
|
placePrizeOrb(c);
|
|
else if(hrand(12000) < 8 + items[itShard] + hard)
|
|
c->monst = moRanger;
|
|
else if(hrand(60000) < 8 + items[itShard] + hard)
|
|
c->monst = moEagle;
|
|
}
|
|
if(c->land == laGraveyard) {
|
|
if(hrand(5000) < 30 + 2 * (kills[moZombie] + kills[moGhost] + kills[moNecromancer]) && notDippingFor(itBone))
|
|
c->item = itBone;
|
|
if(hrand(20000) < 10 + items[itBone] + hard + (kills[moZombie] + kills[moGhost] + kills[moNecromancer])/60) {
|
|
eMonster grm[6] = { moZombie, moZombie, moZombie, moGhost, moGhost, moNecromancer};
|
|
c->monst = grm[hrand(6)];
|
|
}
|
|
}
|
|
if(c->land == laRlyeh) {
|
|
if(hrand(5000) < 30 + 2 * (kills[moCultist] + kills[moTentacle] + kills[moPyroCultist]) && notDippingFor(itStatue))
|
|
c->item = itStatue;
|
|
if(hrand(8000) < 5 + items[itStatue] + hard)
|
|
c->monst = moTentacle, c->item = itStatue, c->mondir = NODIR;
|
|
else if(hrand(12000) < 5 + items[itStatue] + hard)
|
|
c->monst = hrand(3) ? ((hrand(40) < items[itStatue]-25) ? moCultistLeader : moCultist) : moPyroCultist;
|
|
else if(hrand(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 = (euclid || c->master->alt) ? celldistAlt(c) : 10;
|
|
// remember: d is negative
|
|
if(d % TEMPLE_EACH == 0) {
|
|
if(hrand(5000) < 20 - 2*d)
|
|
c->monst = moTentacle, c->mondir = NODIR;
|
|
}
|
|
else {
|
|
// int d0 = d % TEMPLE_EACH;
|
|
// if(d0<0) d0=-d0;
|
|
if(hrand(100) < 30) // && d0 != 1 && d0 != TEMPLE_EACH-1)
|
|
c->wall = waBigStatue;
|
|
else if(hrand(20000) < -d)
|
|
c->monst = hrand(3) ? moCultist : moPyroCultist;
|
|
else if(hrand(100000) < -d)
|
|
c->monst = moCultistLeader;
|
|
else if(hrand(5000) < 250)
|
|
c->item = itGrimoire;
|
|
else if(hrand(5000) < 10 && -d > TEMPLE_EACH * 10)
|
|
c->item = itOrbDragon;
|
|
}
|
|
}
|
|
if(c->land == laDryForest) {
|
|
if(hrand(5000) < 100 + 2 * (kills[moFireFairy]*2 + kills[moHedge]) && notDippingFor(itFernFlower))
|
|
c->item = itFernFlower;
|
|
if(hrand(4000) < 40 + items[itFernFlower] + hard)
|
|
c->monst = moHedge;
|
|
else if(hrand(8000) < 2 * items[itFernFlower] + hard)
|
|
c->monst = moFireFairy;
|
|
}
|
|
if(c->land == laHell) {
|
|
if(hrand(6000) < 120 + (kills[moLesser]) && notDippingFor(itHell))
|
|
c->item = itHell;
|
|
if(hrand(8000) < 40 + items[itHell] + hard)
|
|
c->monst = moLesser;
|
|
else if(hrand(24000) < 40 + items[itHell] + hard)
|
|
c->monst = moGreater;
|
|
}
|
|
if(c->land == laCocytus) {
|
|
if(hrand(5000) < 100 + 2 * (kills[moShark] + kills[moGreaterShark] + kills[moCrystalSage]) && notDippingFor(itSapphire))
|
|
c->item = itSapphire;
|
|
if(hrand(5000) < 2 * (items[itSapphire] + hard)) {
|
|
eMonster ms[3] = { moYeti, moGreaterShark, moCrystalSage };
|
|
c->monst = ms[hrand(3)];
|
|
if(c->monst == moGreaterShark) c->wall = waLake;
|
|
}
|
|
}
|
|
if(c->land == laMotion) {
|
|
if(hrand(1500) < 30 + (kills[moRunDog]) && notDippingFor(itFeather))
|
|
c->item = itFeather;
|
|
if(hrand(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);
|
|
c->wall = shmup::on ? waNone : waChasm;
|
|
}
|
|
}
|
|
if(c->land == laHive) {
|
|
if(isHive(c->land) && hrand(6000) < (hivehard() - 15))
|
|
c->monst = randomHyperbug();
|
|
|
|
/* if(hrand(1500) < 30 + (kills[moBug0] + kills[moBug1] + kills[moBug2]) && notDippingFor(itRoyalJelly))
|
|
c->item = itRoyalJelly; */
|
|
/* if(hrand(2000) < 2)
|
|
c->monst = eMonster(moBug0 + hrand(3)); */
|
|
}
|
|
if(c->land == laCaribbean) {
|
|
// if(hrand(1500) < 60 && celldistAlt(c) <= -5)
|
|
// c->item = itCompass;
|
|
if(hrand(16000) < 40 + (items[itPirate] + hard))
|
|
c->monst = moPirate;
|
|
}
|
|
if(!c->item && c->wall != waCloud && c->wall != waMirror) {
|
|
if(isCrossroads(c->land))
|
|
placeCrossroadOrbs(c);
|
|
else
|
|
placeLocalOrbs(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
#ifndef MOBILE
|
|
if(d >= 7 && mapeditor::whichPattern)
|
|
mapeditor::applyModelcell(c);
|
|
#endif
|
|
}
|
|
|
|
// 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 == moHexSnake) {
|
|
hexsnakes.push_back(c); settemp(c);
|
|
}
|
|
else if(c->monst == moWormtail || c->monst == moHexSnakeTail) {
|
|
bool bug = true;
|
|
for(int i=0; i<c->type; i++) {
|
|
cell* c2 = c->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 havebugs, haveearth, haveeagles, haveleader, havehex, havewhirlpool, havewater,
|
|
haveair;
|
|
|
|
bool bugsfighting;
|
|
|
|
bool keepLightning = false;
|
|
|
|
int statuecount;
|
|
|
|
int tidalphase;
|
|
|
|
int tidalsize, tide[200];
|
|
|
|
void calcTidalPhase() {
|
|
if(!tidalsize) {
|
|
for(int i=0; i<5; i++) tide[tidalsize++] = 1;
|
|
|
|
for(int i=1; i<4; i++) for(int j=0; j<3; j++)
|
|
tide[tidalsize++] = i;
|
|
|
|
for(int i=4; i<7; i++) for(int j=0; j<2; j++)
|
|
tide[tidalsize++] = i;
|
|
|
|
for(int i=7; i<20; i++)
|
|
tide[tidalsize++] = i;
|
|
|
|
for(int i=20; i<23; i++) for(int j=0; j<2; j++)
|
|
tide[tidalsize++] = i;
|
|
|
|
for(int i=23; i<26; i++) for(int j=0; j<3; j++)
|
|
tide[tidalsize++] = i;
|
|
|
|
for(int i=0; i+i<tidalsize; i++) tide[tidalsize++] = 27 - tide[i];
|
|
|
|
/* printf("tidalsize = %d\n", tidalsize);
|
|
for(int i=0; i<tidalsize; i++) printf("%d ", tide[i]);
|
|
printf("\n"); */
|
|
}
|
|
tidalphase = tide[
|
|
(shmup::on ? shmup::curtime/600 : turncount)
|
|
% tidalsize];
|
|
}
|
|
|
|
int tidespeed() {
|
|
return tide[(turncount+6) % tidalsize] - tidalphase;
|
|
}
|
|
|
|
void checkTide(cell *c) {
|
|
if(c->land == laOcean) {
|
|
if(c->wall == waStrandedBoat || c->wall == waBoat)
|
|
c->wall = c->landparam >= tidalphase ? waBoat : waStrandedBoat;
|
|
if(c->wall == waSea || c->wall == waNone)
|
|
c->wall = c->landparam >= tidalphase ? waSea : waNone;
|
|
if(isFire(c) && c->landparam >= tidalphase)
|
|
c->wall = waSea;
|
|
}
|
|
}
|
|
|
|
// calculate cpdist and pathdist
|
|
void bfs(bool tick = true) {
|
|
|
|
calcTidalPhase();
|
|
|
|
checkOnYendorPath();
|
|
|
|
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();
|
|
statuecount = 0;
|
|
hexsnakes.clear();
|
|
havebugs = false;
|
|
haveearth = false; haveeagles = false; haveleader = false; havehex = false;
|
|
havemouse = false;
|
|
havewater = false;
|
|
havewhirlpool = false;
|
|
haveair = false;
|
|
|
|
dcal.clear(); reachedfrom.clear();
|
|
|
|
int pqs = size(pathq);
|
|
for(int i=0; i<pqs; i++) pathq[i]->pathdist = INFD;
|
|
pathq.clear();
|
|
|
|
for(int i=0; i<numplayers(); i++) {
|
|
cell *c = playerpos(i);
|
|
if(c->cpdist == 0) continue;
|
|
c->cpdist = 0;
|
|
checkTide(c);
|
|
dcal.push_back(c);
|
|
reachedfrom.push_back(hrand(c->type));
|
|
if(!invismove) targets.push_back(c);
|
|
}
|
|
|
|
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((c->wall == waBoat || c->wall == waSea) &&
|
|
(c2->wall == waSulphur || c2->wall == waSulphurC))
|
|
c2->wall = waSea;
|
|
|
|
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;
|
|
|
|
if(c2->item == itBombEgg && c2->cpdist == 7 && items[itBombEgg] >= c2->landparam) {
|
|
c2->item = itNone;
|
|
if(!c2->monst) c2->monst = moBomberbird;
|
|
}
|
|
|
|
if(c2->item == itEdge && c2->cpdist == 7 && items[itEdge] >= c2->landparam) {
|
|
c2->item = itNone;
|
|
}
|
|
|
|
if(!keepLightning) c2->ligon = 0;
|
|
dcal.push_back(c2);
|
|
reachedfrom.push_back(c->spn[i]);
|
|
|
|
checkTide(c2);
|
|
|
|
if(c2->wall == waBigStatue && c2->land != laTemple)
|
|
statuecount++;
|
|
|
|
if(c2->land == laWhirlpool) havewhirlpool = true;
|
|
|
|
if(c2->monst) {
|
|
if(c2->monst == moHexSnake || c2->monst == moHexSnakeTail) {
|
|
havehex = true;
|
|
if(c2->monst == moHexSnake) hexsnakes.push_back(c2);
|
|
else findWormIvy(c2);
|
|
}
|
|
else if(isGhostMover(c2->monst))
|
|
ghosts.push_back(c2);
|
|
else if(isWorm(c2) || isIvy(c2)) findWormIvy(c2);
|
|
else if(isBug(c2)) {
|
|
havebugs = true;
|
|
targets.push_back(c2);
|
|
}
|
|
else if(isFriendly(c2)) {
|
|
if(c2->monst != moMouse) targets.push_back(c2);
|
|
if(c2->monst == moGolem) golems.push_back(c2);
|
|
if(c2->monst == moKnight) golems.push_back(c2);
|
|
if(c2->monst == moTameBomberbird) golems.push_back(c2);
|
|
if(c2->monst == moMouse) { golems.push_back(c2); havemouse = true; }
|
|
if(c2->monst == moPrincess || c2->monst == moPrincessArmed) 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(isAngryBird(c2->monst)) haveeagles = true;
|
|
else if(isLeader(c2->monst)) haveleader = true;
|
|
else if(c2->monst == moEarthElemental) haveearth = true;
|
|
else if(c2->monst == moWaterElemental) havewater = true;
|
|
if(c2->monst == moAirElemental) haveair = true;
|
|
}
|
|
// pheromones!
|
|
if(c2->land == laHive && c2->landparam >= 50 && c2->wall != waWaxWall)
|
|
havebugs = true;
|
|
if(c2->wall == waThumperOn)
|
|
targets.push_back(c2);
|
|
}
|
|
}
|
|
}
|
|
|
|
reachedfrom.clear();
|
|
for(int i=0; i<size(targets); i++) {
|
|
targets[i]->pathdist = isPlayerOn(targets[i]) ? 0 : 1;
|
|
pathq.push_back(targets[i]);
|
|
reachedfrom.push_back(hrand(targets[i]->type));
|
|
}
|
|
|
|
int qtemp = size(temps);
|
|
for(int i=0; i<qtemp; i++) temps[i]->monst = tempval[i];
|
|
|
|
pathqm.clear();
|
|
|
|
qb = 0;
|
|
for(qb=0; qb < size(pathq); qb++) {
|
|
int fd = reachedfrom[qb] + 3;
|
|
cell *c = pathq[qb];
|
|
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]) &&
|
|
passable(c->mov[i], d==0?NULL:c, true, false, true)) {
|
|
c->mov[i]->pathdist = d+1;
|
|
pathq.push_back(c->mov[i]); reachedfrom.push_back(c->spn[i]);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// initialize the game
|
|
void initgame() {
|
|
|
|
if(firstland == laNone || firstland == laBarrier)
|
|
firstland = laCrossroads;
|
|
|
|
if(firstland == laOceanWall) firstland = laOcean;
|
|
if(firstland == laEdge) firstland = laCrossroads;
|
|
if(firstland == laElementalWall) firstland = randomElementalLand();
|
|
|
|
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;
|
|
|
|
if(euclid && euclidland == laPrincessQuest) {
|
|
cell *c = euclideanAtCreate(EPX, EPY);
|
|
princess::generating = true;
|
|
c->land = laPalace;
|
|
for(int j=BARLEV; j>=0; j--) setdist(c, j, NULL);
|
|
princess::generating = false;
|
|
}
|
|
|
|
if(cwt.c->land == laCrossroads2) {
|
|
cwt.c->landparam = 12;
|
|
createMov(cwt.c, 0)->landparam = 44;
|
|
}
|
|
|
|
// extern int sightrange; sightrange = 9;
|
|
// cwt.c->land = laHell; items[itHell] = 10;
|
|
for(int i=BARLEV; 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 == laCanvas)
|
|
;
|
|
else if(cwt.c->land == laCaves || cwt.c->land == laEmerald)
|
|
cwt.c->wall = waCavefloor;
|
|
else if(cwt.c->land == laDeadCaves)
|
|
cwt.c->wall = waDeadfloor2;
|
|
else if(cwt.c->land == laCaribbean || cwt.c->land == laOcean || cwt.c->land == laWhirlpool || cwt.c->land == laLivefjord)
|
|
cwt.c->wall = waBoat; // , cwt.c->item = itOrbYendor;
|
|
else if(cwt.c->land == laMinefield)
|
|
cwt.c->wall = waMineOpen;
|
|
else
|
|
cwt.c->wall = waNone;
|
|
cwt.c->item = itNone;
|
|
cwt.c->monst = moNone;
|
|
|
|
if(shmup::on) shmup::init();
|
|
|
|
if(!safety) {
|
|
timerstart = time(NULL); turncount = 0;
|
|
sagephase = 0; hardcoreAt = 0;
|
|
timerstopped = false;
|
|
savecount = 0; savetime = 0;
|
|
cheater = 0;
|
|
if(!randomPatternsMode) {
|
|
if(firstland != (princess::challenge ? laPalace : laIce)) cheater++;
|
|
}
|
|
if(princess::challenge) {
|
|
kills[moVizier] = 1;
|
|
princess::forceMouse = true;
|
|
if(princess::everGotYendorVictory)
|
|
items[itGreenStone] = 99;
|
|
addMessage(XLAT("Welcome to %the1 Challenge!", moPrincess));
|
|
addMessage(XLAT("The more Hypersian Rugs you collect, the harder it is.", moPrincess));
|
|
}
|
|
}
|
|
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 toggleGates(cell *ct, eWall type, int rad) {
|
|
if(!ct) return;
|
|
if(ct->wall == waOpenGate && type == waClosePlate) {
|
|
bool onWorm = false;
|
|
if(isWorm(ct)) onWorm = true;
|
|
for(int i=0; i<ct->type; i++)
|
|
if(ct->mov[i] && ct->mov[i]->wall == waOpenGate && isWorm(ct->mov[i])) onWorm = true;
|
|
if(!onWorm) {
|
|
ct->wall = waClosedGate, rad = 1;
|
|
if(ct->item) {
|
|
addMessage(XLAT("%The1 is crushed!", ct->item));
|
|
ct->item = itNone;
|
|
}
|
|
}
|
|
}
|
|
if(ct->wall == waClosedGate && type == waOpenPlate)
|
|
ct->wall = waOpenGate, rad = 1;
|
|
if(rad) for(int i=0; i<ct->type; i++)
|
|
toggleGates(ct->mov[i], type, rad-1);
|
|
}
|
|
|
|
// effect of moving monster m from cf to ct
|
|
// this is called from moveMonster, or separately from moveIvy/moveWorm,
|
|
// or when a dead bird falls (then m == moDeadBird)
|
|
|
|
void moveEffect(cell *ct, cell *cf, eMonster m) {
|
|
|
|
if(!survivesMine(m))
|
|
explodeMine(ct);
|
|
|
|
if(!ignoresPlates(m) && !survivesMine(m) && (ct->wall == waClosePlate || ct->wall == waOpenPlate))
|
|
toggleGates(ct, ct->wall, 3);
|
|
|
|
if(isPrincess(m)) princess::move(ct, cf);
|
|
}
|
|
|
|
void updateHi(eItem it, int v) {
|
|
if(v > hiitems[it]) hiitems[it] = v;
|
|
}
|
|
|
|
void gainItem(eItem it) {
|
|
int g = gold();
|
|
items[it]++; updateHi(it, items[it]);
|
|
achievement_collection(it, gold(), g);
|
|
}
|
|
|
|
void playerMoveEffects(cell *c2) {
|
|
bool nomine = (c2->wall == waMineMine || c2->wall == waMineUnknown) && markOrb(itOrbGhost);
|
|
|
|
if(!nomine) {
|
|
uncoverMines(c2,
|
|
items[itBombEgg] < 10 && hiitems[itBombEgg] < 25 ? 0 :
|
|
items[itBombEgg] < 20 ? 1 :
|
|
items[itBombEgg] < 30 ? 2 :
|
|
3
|
|
);
|
|
explodeMine(c2);
|
|
}
|
|
|
|
if((c2->wall == waClosePlate || c2->wall == waOpenPlate) && !markOrb(itOrbGhost))
|
|
toggleGates(c2, c2->wall, 3);
|
|
|
|
princess::playernear(c2);
|
|
|
|
if(c2->wall == waGlass && items[itOrbGhost] > 2) {
|
|
addMessage(XLAT("Your Aether powers are drained by %the1!", c2->wall));
|
|
items[itOrbGhost] = 2;
|
|
}
|
|
|
|
if(c2->wall == waMirror && !markOrb(itOrbGhost)) {
|
|
invismove = false;
|
|
addMessage(XLAT("The mirror shatters!"));
|
|
if(c2->land == laMirror) gainItem(itShard);
|
|
c2->wall = waNone;
|
|
createMirrors(cwt.c, cwt.spin, moMirage);
|
|
}
|
|
|
|
if(c2->wall == waCloud && !markOrb(itOrbGhost)) {
|
|
invismove = false;
|
|
addMessage(XLAT("The cloud turns into a bunch of images!"));
|
|
if(c2->land == laMirror) gainItem(itShard);
|
|
c2->wall = waNone;
|
|
createMirages(cwt.c, cwt.spin, moMirage);
|
|
}
|
|
|
|
if(cellUnstable(c2) && !markOrb(itOrbGhost))
|
|
c2->wall = waChasm;
|
|
|
|
if(c2->wall == waStrandedBoat && markOrb(itOrbWater))
|
|
c2->wall = waBoat;
|
|
|
|
if(c2->land == laOcean && c2->wall == waBoat && c2->landparam < 30 && markOrb(itOrbWater))
|
|
c2->landparam = 40;
|
|
}
|
|
|
|
void moveMonster(cell *ct, cell *cf) {
|
|
eMonster m = cf->monst;
|
|
moveEffect(ct, cf, m);
|
|
if(m == moTentacleGhost) {
|
|
cf->monst = moTentacletail;
|
|
m = moGhost;
|
|
}
|
|
else cf->monst = moNone;
|
|
if(ct->monst == moTentacletail && m == moGhost) {
|
|
ct->monst = moTentacleGhost;
|
|
}
|
|
else {
|
|
ct->monst = m;
|
|
if(ct->monst != moTentacleGhost)
|
|
ct->mondir = neighborId(ct, cf);
|
|
}
|
|
ct->hitpoints = cf->hitpoints;
|
|
ct->stuntime = cf->stuntime;
|
|
|
|
if(isFriendly(m) || isBug(m) || items[itOrbDiscord]) stabbingAttack(cf, ct, m);
|
|
|
|
if(isLeader(m)) {
|
|
if(ct->wall == waBigStatue) {
|
|
ct->wall = cf->wall;
|
|
cf->wall = waBigStatue;
|
|
}
|
|
|
|
moveBoatIfUsingOne(ct, cf);
|
|
}
|
|
|
|
if(m == moEarthElemental)
|
|
earthMove(cf, neighborId(cf, ct));
|
|
|
|
if(m == moWaterElemental) {
|
|
placeWater(ct, cf);
|
|
for(int i=0; i<ct->type; i++) {
|
|
cell *c2 = ct->mov[i];
|
|
if(!c2) continue;
|
|
if(c2->wall == waBoat && !(c2 == cwt.c && markOrb(itOrbWater))) {
|
|
addMessage(XLAT("%The1 is washed away!", c2->wall, moWaterElemental));
|
|
placeWater(c2, ct);
|
|
}
|
|
else if(c2->wall == waStrandedBoat) {
|
|
addMessage(XLAT("%The1 is washed away!", c2->wall, moWaterElemental));
|
|
c2->wall = waNone;
|
|
}
|
|
else if(c2->wall == waDeadTroll) {
|
|
addMessage(XLAT("%The1 is washed away!", c2->wall, moWaterElemental));
|
|
c2->wall = waNone;
|
|
}
|
|
else if(isFire(c2) && c2->wall != waEternalFire) {
|
|
addMessage(XLAT("%The1 is extinguished!", c2->wall, moWaterElemental));
|
|
c2->wall = waNone;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(m == moGreaterShark) for(int i=0; i<ct->type; i++) {
|
|
cell *c3 = ct->mov[i];
|
|
if(c3 && c3->wall == waBoat)
|
|
makeflame(c3, 5, false);
|
|
}
|
|
|
|
// lancers pierce our friends :(
|
|
if(m == moLancer) {
|
|
// printf("lancer stab?\n");
|
|
for(int u=2; u<=ct->type-2; u++) {
|
|
cell *c3 = ct->mov[(ct->mondir+u)%ct->type];
|
|
if(c3->monst && isKillableSomehow(c3)) {
|
|
addMessage(XLAT("%The1 pierces %the2!", m, c3->monst));
|
|
killWithMessage(c3, true);
|
|
}
|
|
killThePlayerAt(m, c3);
|
|
}
|
|
}
|
|
|
|
if(m == moWitchFire) makeflame(cf, 10, false);
|
|
if(m == moFireElemental) makeflame(cf, 20, false);
|
|
}
|
|
|
|
void moveNormal(cell *c) {
|
|
bool repeat = true;
|
|
eMonster m = c->monst;
|
|
|
|
if(c->stuntime) return;
|
|
|
|
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
|
|
if(attackJustStuns(c->mov[j]))
|
|
addMessage(XLAT("%The1 attacks %the2!", m, c->mov[j]->monst));
|
|
else messageKill(m, c->mov[j]->monst);
|
|
killWithMessage(c->mov[j], true);
|
|
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, false)) continue;
|
|
// crystal sages can't stand out of Cocytus
|
|
if(m == moCrystalSage && (c2->land != laCocytus || HEAT(c2) > SAGEMELT || cwt.c->wall == waBoat))
|
|
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, false)) {
|
|
// running dogs run away!
|
|
// they prefer a straight direction
|
|
int sgn = hrand(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, false))
|
|
posdir[nc++] = d;
|
|
}
|
|
}
|
|
|
|
if(!nc) return;
|
|
nc = hrand(nc);
|
|
cell *c2 = c->mov[posdir[nc]];
|
|
moveMonster(c2, c);
|
|
|
|
if(isWitch(m) && c2->item == itOrbLife && passable(c, NULL, true, true, false)) {
|
|
// 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)) HEAT(c2) += 0.5;
|
|
if((c2->wall == waDune || c2->wall == waIcewall ||
|
|
c2->wall == waAncientGrave || c2->wall == waFreshGrave ||
|
|
c2->wall == waColumn || c2->wall == waThumperOff || c2->wall == waThumperOn ||
|
|
(isFire(c2) && !eternalFire(c2)) ||
|
|
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 == waGargoyleFloor) c2->wall = waChasm;
|
|
if(c2->wall == waGargoyleBridge) placeWater(c2, c2);
|
|
if(c2->wall == waRubble) c2->wall = waNone;
|
|
if(c2->wall == waPlatform) c2->wall = waNone;
|
|
if(c2->wall == waStone) c2->wall = waNone;
|
|
if(c2->wall == waLadder) c2->wall = waNone;
|
|
if(c2->wall == waGargoyle) c2->wall = waNone;
|
|
if(c2->wall == waDeadwall) c2->wall = waDeadfloor2;
|
|
if(c2->wall == waBigStatue) c2->wall = waNone;
|
|
if(c2->wall == waPalace || c2->wall == waOpenGate || c2->wall == waClosedGate)
|
|
c2->wall = waNone;
|
|
if(c2->wall == waFloorA || c2->wall == waFloorB)
|
|
if(c->wall == waFloorA || c->wall == waFloorB)
|
|
c2->wall = c->wall;
|
|
}
|
|
}
|
|
}
|
|
|
|
void killThePlayer(eMonster m, int id) {
|
|
if(markOrb(itOrbShield)) return;
|
|
if(shmup::on) {
|
|
shmup::cpid = id;
|
|
shmup::killThePlayer(m);
|
|
}
|
|
else if(hardcore) {
|
|
addMessage(XLAT("You are killed by %the1!", m));
|
|
canmove = false;
|
|
}
|
|
else
|
|
addMessage(XLAT("%The1 is confused!", m));
|
|
}
|
|
|
|
void killThePlayerAt(eMonster m, cell *c) {
|
|
for(int i=0; i<numplayers(); i++)
|
|
if(playerpos(i) == c)
|
|
killThePlayer(m, i);
|
|
}
|
|
|
|
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] && isFriendlyOrBugS(c->mov[j]) && !attackingForbidden(c->mov[j], c)) {
|
|
addMessage(XLAT("%The1 eats %the2!", c->monst, c->mov[j]->monst));
|
|
killWithMessage(c->mov[j], false);
|
|
ncg = 1; gmov[0] = c->mov[j];
|
|
break;
|
|
}
|
|
if(isPlayerOn(c->mov[j]) && !attackingForbidden(c->mov[j], c)) killThePlayerAt(c->monst, c->mov[j]);
|
|
if(c->mov[j] && passable(c->mov[j], c, false, false, false) && !cellUnstable(c->mov[j]) && (id || !cellEdgeUnstable(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 {
|
|
kills[moWorm]++;
|
|
spices = 3;
|
|
}
|
|
eItem loc = treasureType(c->land);
|
|
bool spiceSeen = false;
|
|
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;
|
|
if(c->cpdist <= 6) spiceSeen = true;
|
|
}
|
|
spices--;
|
|
}
|
|
c->monst = moNone;
|
|
if(c->mondir != NODIR) c = c->mov[c->mondir];
|
|
}
|
|
if(!id) {
|
|
if(spiceSeen)
|
|
addMessage(XLAT("The sandworm explodes in a cloud of Spice!"));
|
|
else
|
|
addMessage(XLAT("The sandworm explodes!"));
|
|
}
|
|
return;
|
|
}
|
|
|
|
cell* goal;
|
|
if(ncg) goal = gmov[hrand(ncg)];
|
|
else goal = bmov[hrand(ncb)];
|
|
|
|
for(int j=0; j<c->type; j++) if(c->mov[j] == goal) {
|
|
goal->monst = eMonster(moWormwait + id);
|
|
moveEffect(goal, NULL, 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]) && !attackingForbidden(c->mov[j], c)) {
|
|
if(isNonliving(c->mov[j]->monst))
|
|
addMessage(XLAT("The ivy destroys %the1!", c->mov[j]->monst));
|
|
else
|
|
addMessage(XLAT("The ivy kills %the1!", c->mov[j]->monst));
|
|
killMonster(c->mov[j]);
|
|
continue;
|
|
}
|
|
if(isPlayerOn(c->mov[j]) && !attackingForbidden(c->mov[j], c))
|
|
killThePlayerAt(c->monst, c->mov[j]);
|
|
if(c->mov[j] && signed(c->mov[j]->pathdist) < pd && passable(c->mov[j], c, false, 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;
|
|
moveEffect(mto, NULL, moIvyWait);
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool earthMove(cell *from, int dir) {
|
|
bool b = false;
|
|
cell *c2 = from->mov[dir];
|
|
int d = from->spn[dir];
|
|
b |= earthWall(from);
|
|
if(c2) for(int u=2; u<=c2->type-2; u++) {
|
|
cell *c3 = c2->mov[(d + u)% c2->type];
|
|
b |= earthFloor(c3);
|
|
}
|
|
return b;
|
|
}
|
|
|
|
int sval; vector<cell*> gendfs;
|
|
|
|
void monsterfight(cell *attacker, cell *victim) {
|
|
if(isBird(attacker->monst))
|
|
addMessage(XLAT("%The1 claws %the2!", attacker->monst, victim->monst));
|
|
else if(isSlimeMover(attacker)) {
|
|
if(attackJustStuns(victim))
|
|
addMessage(XLAT("%The1 attacks %the2!", attacker->monst, victim->monst));
|
|
else
|
|
addMessage(XLAT("%The1 eats %the2!", attacker->monst, victim->monst));
|
|
}
|
|
else
|
|
addMessage(XLAT("%The1 punches %the2!", attacker->monst, victim->monst));
|
|
killWithMessage(victim, true);
|
|
}
|
|
|
|
void groupmove2(cell *c, cell *from, int d, eMonster movtype, bool noattacks) {
|
|
if(!c) return;
|
|
if(eq(c->aitmp, sval)) return;
|
|
if(!passable_for(movtype, from, c, false)) return;
|
|
|
|
if(movegroup(c->monst) == movtype) {
|
|
if(movtype == moEagle && noattacks && c->monst != moEagle) return;
|
|
|
|
if(c->stuntime) return;
|
|
|
|
// note: move from 'c' to 'from'!
|
|
if(!noattacks) for(int j=0; j<c->type; j++)
|
|
if(c->mov[j] && isFriendlyOrBug(c->mov[j]) && !attackingForbidden(c->mov[j], c)) {
|
|
monsterfight(c, c->mov[j]);
|
|
c->aitmp = sval;
|
|
// XLATC eagle
|
|
return;
|
|
}
|
|
|
|
if(movtype == moEarthElemental && !passable(from, c, false, false, false)) {
|
|
earthFloor(from);
|
|
return;
|
|
}
|
|
|
|
if(from->cpdist == 0 || from->monst) { c->aitmp = sval; return; }
|
|
moveMonster(from, c);
|
|
}
|
|
c->aitmp = sval;
|
|
if(size(gendfs) < 1000) gendfs.push_back(c);
|
|
}
|
|
|
|
#define ONLY_ONE_PLAYER_POSSIBLE 0
|
|
|
|
void groupmove(eMonster movtype, bool noattacks = false) {
|
|
if(!noattacks) for(int i=0; i<cwt.c->type; i++) {
|
|
eMonster m = cwt.c->mov[i]->monst;
|
|
if(movegroup(m) == movtype && !attackingForbidden(cwt.c, cwt.c->mov[i]))
|
|
killThePlayer(m, ONLY_ONE_PLAYER_POSSIBLE);
|
|
}
|
|
sval++;
|
|
gendfs.clear();
|
|
for(int i=0; i<size(targets); i++)
|
|
gendfs.push_back(targets[i]);
|
|
|
|
if(invisfish && movtype == moSlime) for(int i=0; i<numplayers(); i++) {
|
|
cell *c = playerpos(i);
|
|
gendfs.push_back(c);
|
|
}
|
|
|
|
for(int i=0; i<size(gendfs); i++) {
|
|
cell *c = gendfs[i];
|
|
for(int t=0; t<c->type; t++)
|
|
groupmove2(c->mov[t], c, t, movtype, noattacks);
|
|
}
|
|
}
|
|
|
|
// Hex monsters
|
|
|
|
vector<cell*> hexdfs;
|
|
|
|
// note: move from 'c' to 'from'!
|
|
void moveHexSnake(cell *from, cell *c, int d) {
|
|
if(from->wall == waBoat) from->wall = waSea;
|
|
moveEffect(from, c, c->monst);
|
|
from->monst = c->monst; from->mondir = d;
|
|
c->monst = moHexSnakeTail;
|
|
|
|
cell *c2 = c, *c3=c2;
|
|
for(int a=0;; a++) if(c2->monst == moHexSnakeTail) {
|
|
if(a == ROCKSNAKELENGTH) { c2->monst = moNone, c3->mondir = NODIR; break; }
|
|
if(c2->mondir == NODIR) break;
|
|
c3 = c2, c2 = c3->mov[c2->mondir];
|
|
}
|
|
else break;
|
|
}
|
|
|
|
void snakeAttack(cell *c) {
|
|
for(int j=0; j<c->type; j++)
|
|
if(c->mov[j] && isFriendlyOrBug(c->mov[j]) && !attackingForbidden(c->mov[j], c)
|
|
/* &&
|
|
(passable(c->mov[j], c, true, false, false) && !ishept(c->mov[j])) */
|
|
) {
|
|
|
|
if(attackJustStuns(c->mov[j]))
|
|
addMessage(XLAT("%The1 attacks %the2!", c->monst, c->mov[j]->monst));
|
|
else
|
|
addMessage(XLAT("%The1 kills %the2!", c->monst, c->mov[j]->monst));
|
|
killWithMessage(c->mov[j], true);
|
|
}
|
|
}
|
|
|
|
// note: move from 'c' to 'from'!
|
|
void hexvisit(cell *c, cell *from, int d) {
|
|
if(!c) return;
|
|
if(cellUnstable(c) || cellEdgeUnstable(c)) return;
|
|
if(eq(c->aitmp, sval)) return;
|
|
|
|
if(cellUnstableOrChasm(c) || cellUnstableOrChasm(from)) return;
|
|
|
|
/* if(c->monst == moHexSnake)
|
|
printf("%p:%p %s %d\n", from, c, dnameof(from->monst), passable(from, c, true, false, false)); */
|
|
|
|
if(from->cpdist && (!isWatery(from) && !passable(from, c, true, false, false))) return;
|
|
|
|
if(c->monst == moHexSnake) {
|
|
// printf("got snake\n");
|
|
|
|
if(ishept(from)) return;
|
|
|
|
if(isFriendlyOrBug(from) && !attackingForbidden(from, c)) {
|
|
addMessage(XLAT("%The1 eats %the2!", c->monst, from->monst));
|
|
killWithMessage(from, false);
|
|
}
|
|
|
|
if(from->cpdist == 0 || from->monst) return;
|
|
|
|
snakeAttack(c);
|
|
if(c->monst == moRedTroll)
|
|
moveMonster(from, c);
|
|
else moveHexSnake(from, c, d);
|
|
}
|
|
|
|
c->aitmp = sval;
|
|
if(c->bardir == 0) c->bardir = NOBARRIERS;
|
|
|
|
if(size(hexdfs) < 2000) hexdfs.push_back(c);
|
|
}
|
|
|
|
void movehex() {
|
|
for(int p=0; p<numplayers(); p++) {
|
|
shmup::cpid = p;
|
|
cell *c = playerpos(p);
|
|
for(int i=0; i<c->type; i++)
|
|
if(c->mov[i]->monst == moHexSnake && !attackingForbidden(cwt.c, cwt.c->mov[i]))
|
|
killThePlayer(moHexSnake, p);
|
|
}
|
|
sval++;
|
|
hexdfs.clear();
|
|
for(int i=0; i<size(targets); i++) {
|
|
hexdfs.push_back(targets[i]);
|
|
targets[i]->aitmp = sval;
|
|
}
|
|
//hexdfs.push_back(cwt.c);
|
|
|
|
for(int i=0; i<size(hexdfs); i++) {
|
|
cell *c = hexdfs[i];
|
|
for(int t=0; t<c->type; t++) if(c->mov[t] && !ishept(c->mov[t]))
|
|
hexvisit(c->mov[t], c, t);
|
|
}
|
|
for(int i=0; i<size(hexsnakes); i++) {
|
|
cell *c = hexsnakes[i];
|
|
if(c->monst == moHexSnake) {
|
|
int t[2];
|
|
t[0] = (c->mondir+2) % 6;
|
|
t[1] = (c->mondir+4) % 6;
|
|
if(hrand(2) == 0) swap(t[0], t[1]);
|
|
for(int u=0; u<2; u++) {
|
|
createMov(c, t[u]);
|
|
if(!ishept(c->mov[t[u]]))
|
|
hexvisit(c, c->mov[t[u]], c->spn[t[u]]);
|
|
}
|
|
}
|
|
if(c->monst == moHexSnake) {
|
|
snakeAttack(c);
|
|
kills[moHexSnake]++;
|
|
cell *c2 = c;
|
|
while(c2->monst == moHexSnakeTail || c2->monst == moHexSnake) {
|
|
if(c2->monst != moHexSnake && c2->mondir != NODIR)
|
|
snakepile(c2);
|
|
snakepile(c2);
|
|
c2->monst = moNone;
|
|
if(c2->mondir == NODIR) break;
|
|
c2 = c2->mov[c2->mondir];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// next == +1 -> next
|
|
// next == -1 -> prev
|
|
cell *getWhirlpool(cell *c, int next) {
|
|
int i = 0;
|
|
if(!euclid && !c->master->alt) return NULL;
|
|
int d = celldistAlt(c);
|
|
int d2;
|
|
while(true) {
|
|
if(i == c->type) return NULL;
|
|
if(c->mov[i] && (d2 = celldistAlt(c->mov[i])) != d)
|
|
break;
|
|
i++;
|
|
}
|
|
if(i == c->type) return NULL;
|
|
if(d>d2) next = -next;
|
|
for(int j=1; j<c->type; j++) {
|
|
cell *c2 = c->mov[(i+42+next*j) % c->type];
|
|
if(celldistAlt(c2) == d) return c2;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void buildWhirlline(vector<cell*>& whirlline, int d) {
|
|
again:
|
|
cell *at = whirlline[size(whirlline)-1];
|
|
cell *prev = whirlline[size(whirlline)-2];
|
|
for(int i=0; i<at->type; i++)
|
|
if(at->mov[i] && (euclid || at->mov[i]->master->alt) && celldistAlt(at->mov[i]) == d && at->mov[i] != prev) {
|
|
whirlline.push_back(at->mov[i]);
|
|
goto again;
|
|
}
|
|
}
|
|
|
|
void whirlGenerate(cell *wto) {
|
|
if(wto->wall == waBoat || wto->monst)
|
|
return;
|
|
|
|
if(hrand(35000) < 40 + items[itWhirlpool])
|
|
wto->monst = moCShark;
|
|
else if(hrand(5000) < 500)
|
|
wto->wall = waBoat;
|
|
|
|
if(wto->wall == waBoat && (euclid || wto->master->alt)) {
|
|
int d = celldistAlt(wto);
|
|
// 250 : hard
|
|
if(hrand(5000) < 60 + 3 * items[itWhirlpool])
|
|
wto->monst = moPirate;
|
|
if(hrand(5000) < 20 && d < -20)
|
|
wto->item = itOrbSafety;
|
|
if(hrand(5000) < 20*PRIZEMUL && d < -20)
|
|
placePrizeOrb(wto);
|
|
if(items[itWhirlpool] >= 10 && hrand(5000) < 20 && d < -15)
|
|
wto->item = itOrbWater;
|
|
else if(d<-10 && hrand(5000) < 1000-d)
|
|
wto->item = itWhirlpool;
|
|
}
|
|
}
|
|
|
|
void whirlMove(cell *wto, cell *wfrom) {
|
|
// monsters don't move
|
|
if(wfrom && (wfrom == cwt.c || wfrom->monst))
|
|
return;
|
|
// disappear
|
|
if(!wto) { wfrom->wall = waSea; wfrom->item = itNone; }
|
|
|
|
if(wfrom && wto && wfrom->wall == waBoat && wto->wall == waSea && !wto->monst) {
|
|
wfrom->wall = waSea; wto->wall = waBoat;
|
|
}
|
|
|
|
if(wfrom && wto && wfrom->item && !wto->item && wfrom->wall != waBoat) {
|
|
moveItem(wfrom, wto, false);
|
|
}
|
|
|
|
if(wto && !wfrom)
|
|
whirlGenerate(wto);
|
|
}
|
|
|
|
void movewhirlpool() {
|
|
sval++;
|
|
for(int i=0; i<size(dcal); i++) {
|
|
cell *c = dcal[i];
|
|
if(c->land == laWhirlpool && c->aitmp != sval && (euclid || c->master->alt)) {
|
|
cell *c2 = getWhirlpool(c, 1);
|
|
if(!c2) continue;
|
|
int d = celldistAlt(c);
|
|
vector<cell*> whirlline;
|
|
whirlline.push_back(c);
|
|
whirlline.push_back(c2);
|
|
buildWhirlline(whirlline, d);
|
|
reverse(whirlline.begin(), whirlline.end());
|
|
buildWhirlline(whirlline, d);
|
|
int z = size(whirlline);
|
|
|
|
for(int i=0; i<z; i++)
|
|
whirlline[i]->aitmp = sval;
|
|
|
|
whirlMove(NULL, whirlline[0]);
|
|
|
|
for(int i=0; i<z-1; i++)
|
|
whirlMove(whirlline[i], whirlline[i+1]);
|
|
|
|
whirlMove(whirlline[z-1], NULL);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
#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;
|
|
}
|
|
|
|
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(c->stuntime) return;
|
|
|
|
if(isGhostMover(c->monst) && c->cpdist == 1)
|
|
killThePlayer(c->monst, ONLY_ONE_PLAYER_POSSIBLE);
|
|
|
|
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]->cpdist < c->cpdist)
|
|
if(ghostmove(c->monst, c->mov[k], c))
|
|
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->stuntime) return;
|
|
|
|
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));
|
|
killWithMessage(c->mov[j], true);
|
|
return;
|
|
}
|
|
|
|
int qmpos = 0;
|
|
for(int k=0; k<c->type; k++) if(c->mov[k] && c->mov[k]->cpdist < c->cpdist)
|
|
if(ghostmove(c->monst, c->mov[k], c))
|
|
mdir[qmpos++] = k;
|
|
if(!qmpos) continue;
|
|
int d = mdir[hrand(qmpos)];
|
|
cell *c2 = c->mov[d];
|
|
moveMonster(c2, c);
|
|
}
|
|
}
|
|
}
|
|
|
|
int lastdouble = -3;
|
|
|
|
void messageKill(eMonster killer, eMonster victim) {
|
|
if(isNonliving(victim)) {
|
|
if(killer)
|
|
addMessage(XLAT("%The1 destroys %the2!", killer, victim));
|
|
else
|
|
addMessage(XLAT("You destroy %the1.", victim));
|
|
}
|
|
else {
|
|
if(killer)
|
|
addMessage(XLAT("%The1 kills %the2!", killer, victim));
|
|
else
|
|
addMessage(XLAT("You kill %the1.", victim));
|
|
}
|
|
}
|
|
|
|
void stabbingAttack(cell *mf, cell *mt, eMonster who) {
|
|
int numsh = 0, numflail = 0, numlance = 0;
|
|
for(int t=0; t<mf->type; t++) {
|
|
cell *c = mf->mov[t];
|
|
if(!isUnarmed(who))
|
|
if(c->monst == moHedge || (!who && c->monst && isKillable(c) && markOrb(itOrbThorns))) {
|
|
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();
|
|
killWithMessage(c, true);
|
|
if(tkills() > k) numsh++;
|
|
}
|
|
}
|
|
}
|
|
if(c->monst == moFlailer || (c->monst == moVizier && !isUnarmed(who))) {
|
|
bool away = true;
|
|
if(c == mt) away = false;
|
|
for(int u=0; u<c->type; u++) if(c->mov[u] == mt) away = false;
|
|
if(away) {
|
|
if(c->monst == moVizier && c->hitpoints > 1) {
|
|
if(who)
|
|
addMessage(XLAT("%The1 hits %the2.", who, c->monst));
|
|
else
|
|
addMessage(XLAT("You hit %the1.", c->monst));
|
|
c->hitpoints--;
|
|
// c->stuntime = 1;
|
|
}
|
|
else {
|
|
if(c->monst != moFlailer) {
|
|
messageKill(who, c->monst);
|
|
}
|
|
else {
|
|
if(who)
|
|
addMessage(XLAT("%The1 tricks %the2.", who, c->monst));
|
|
else
|
|
addMessage(XLAT("You trick %the1.", c->monst));
|
|
}
|
|
if(c->monst == moFlailer && isPrincess(who) && isUnarmed(who))
|
|
achievement_gain("PRINCESS_PACIFIST");
|
|
|
|
int k = tkills();
|
|
killWithMessage(c);
|
|
if(tkills() > k) numflail++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if(!isUnarmed(who)) 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();
|
|
killWithMessage(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(isPrincess(m) && c->stuntime) continue;
|
|
if(m == moGolem || m == moKnight || m == moTameBomberbird || m == moPrincess ||
|
|
m == moPrincessArmed || m == moMouse) {
|
|
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;
|
|
cell *c2 = c->mov[k];
|
|
if(c2 == cwt.c) val = 0;
|
|
else if(m == moPrincess && isStunnable(c2->monst) && c2->stuntime && c2->monst != moSkeleton &&
|
|
!cellUnstableOrChasm(c) && !attackingForbidden(c2, c) && !isUnarmed(c2->monst)) {
|
|
val = 15000;
|
|
}
|
|
else if(m == moPrincessArmed && isPrincess(c2->monst) && !attackingForbidden(c2, c))
|
|
val = 14000; // jealousy!
|
|
else if(isActiveEnemy(c2, NULL, false) && isKillable(c2) &&
|
|
!attackingForbidden(c2, c) && !isUnarmed(m))
|
|
val = 12000;
|
|
else if(isInactiveEnemy(c2, false) && isKillable(c2) &&
|
|
!attackingForbidden(c2, c) && !isUnarmed(m) && c2->monst != moSkeleton)
|
|
val = 10000;
|
|
else if(isIvy(c2) && !attackingForbidden(c2, c) && !isUnarmed(m)) val = 8000;
|
|
else if(monstersnear(c2, NULL, m)) val = 50; // linked with mouse suicide!
|
|
else if(passable_for(m, c2, c, false)) val = 4000;
|
|
else val = 0;
|
|
if(c->monst == moGolem )
|
|
val -= c2->cpdist;
|
|
else if(c->monst == moMouse) {
|
|
int d;
|
|
if(!euclid && (c2->land != laPalace || !c2->master->alt)) d = 200;
|
|
else d = celldistAlt(c2);
|
|
// first rule: suicide if the Princess is killed,
|
|
// by monstersnear or jumping into a chasm
|
|
princess::info *i = princess::getPrisonInfo(c);
|
|
if(i && !i->princess) {
|
|
if(val == 50 || c2->wall == waChasm) val = 20000;
|
|
}
|
|
// second rule: move randomly if the Princess is saved
|
|
if(i && i->bestdist > 6)
|
|
;
|
|
// third rule: do not get too far from the Princess
|
|
else if(d > 150)
|
|
val -= (700+d);
|
|
// fourth rule: do not get too far from the Rogue
|
|
// NOTE: since Mouse is not a target, we can use
|
|
// the full pathfinding here instead of cpdist!
|
|
else if(c2->pathdist > 3 && c2->pathdist <= 19)
|
|
val -= (500+c2->pathdist * 10);
|
|
else if(c2->pathdist > 19)
|
|
val -= (700);
|
|
// fifth rule: get close to the Princess, to point the way
|
|
else
|
|
val -= (250+d);
|
|
/*
|
|
// avoid stepping on trapdoors and plates
|
|
// (REMOVED BECAUSE MICE NO LONGER ACTIVATE TRAPDOORS AND PLATES)
|
|
// note that the Mouse will still step on the trapdoor
|
|
// if it wants to get close to you and there is no other way
|
|
if(c2->wall == waTrapdoor)
|
|
val -= 5;
|
|
*/
|
|
}
|
|
if(isPrincess(c->monst)) {
|
|
|
|
int d = c2->cpdist;
|
|
if(d <= 3) val -= d;
|
|
else val -= 10 * d;
|
|
|
|
// the Princess also avoids stepping on pressure plates
|
|
if(c2->wall == waClosePlate || c2->wall == waOpenPlate || c2->wall == waTrapdoor)
|
|
val -= 5;
|
|
}
|
|
if(c->monst == moTameBomberbird) {
|
|
int d = c2->cpdist;
|
|
if(d == 1 && c->cpdist > 1) d = 5;
|
|
if(d == 2 && c->cpdist > 2) d = 4;
|
|
val -= d;
|
|
}
|
|
if(!euclid && c->monst == moKnight && c2->master->alt)
|
|
val -= celldistAlt(c2);
|
|
if(val > bestv) bestv = val, bq = 0;
|
|
if(val == bestv) bdirs[bq++] = k;
|
|
}
|
|
if(bestv <= 100) continue;
|
|
int dir = bdirs[hrand(bq)];
|
|
cell *c2 = c->mov[dir];
|
|
if(c2->monst) {
|
|
if(m == moPrincess) {
|
|
addMessage(XLAT("%The1 takes %his1 revenge on %the2!", m, c2->monst));
|
|
killWithMessage(c2, false);
|
|
c->monst = m = moPrincessArmed;
|
|
}
|
|
else {
|
|
if(attackJustStuns(c2))
|
|
addMessage(XLAT("%The1 attacks %the2!", m, c2->monst));
|
|
else {
|
|
messageKill(c->monst, c2->monst);
|
|
if(isPrincess(c->monst) && isPrincess(c2->monst))
|
|
addMessage("\"That should teach you to take me seriously!\"");
|
|
}
|
|
killWithMessage(c2, true);
|
|
}
|
|
}
|
|
else {
|
|
moveMonster(c2, c);
|
|
if(m != moTameBomberbird)
|
|
moveBoatIfUsingOne(c2, c);
|
|
|
|
if(m == moGolem) c2->monst = moGolemMoved;
|
|
if(m == moMouse) c2->monst = moMouseMoved;
|
|
if(m == moPrincess) c2->monst = moPrincessMoved;
|
|
if(m == moPrincessArmed) c2->monst = moPrincessArmedMoved;
|
|
if(m == moTameBomberbird) c2->monst = moTameBomberbirdMoved;
|
|
if(m == moKnight) c2->monst = moKnightMoved;
|
|
}
|
|
}
|
|
}
|
|
achievement_count("GOLEM", qg, 0);
|
|
}
|
|
|
|
bool wchance(int a, int of) {
|
|
of *= 10;
|
|
a += items[itOrbYendor] * 5 + items[itHolyGrail] + 1;
|
|
if(isCrossroads(cwt.c->land))
|
|
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 hrand(a+of) < a;
|
|
}
|
|
|
|
void wanderingZebra(cell *start) {
|
|
cell *c = start, *c2 = start;
|
|
for(int it=0; it<100; it++) {
|
|
if(c->cpdist == 7) {
|
|
c->monst = moOrangeDog;
|
|
return;
|
|
}
|
|
int q = 0;
|
|
cell *ctab[8];
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c3 = c->mov[i];
|
|
if(c3 && c3 != c2 && c3->land == laZebra && c3->wall == waNone)
|
|
ctab[q++] = c3;
|
|
}
|
|
if(!q) break;
|
|
c2 = c; c = ctab[hrand(q)];
|
|
}
|
|
}
|
|
|
|
void wandering() {
|
|
int t = shmup::on ? (shmup::curtime - lastexplore) / 350 : turncount - lastexplore;
|
|
int seepcount = 0;
|
|
if(t > 40) seepcount = (t-40 + hrand(20)) / 20;
|
|
int ghostcount = 0;
|
|
if(t > 80) ghostcount = (t-80 + hrand(20)) / 20;
|
|
|
|
if(cwt.c->land == laZebra && cwt.c->wall == waNone && wchance(items[itZebra], 20))
|
|
wanderingZebra(cwt.c);
|
|
|
|
while(first7 < size(dcal)) {
|
|
int i = first7 + hrand(size(dcal) - first7);
|
|
cell *c = dcal[i];
|
|
|
|
// wandering seeps & ghosts
|
|
if(seepcount && c->wall == waCavewall && !c->monst && eq(c->aitmp, 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->aitmp, sval) &&
|
|
wchance(items[treasureType(c->land)], 10)) {
|
|
c->monst = moSeep;
|
|
continue;
|
|
}
|
|
|
|
else if(c->wall == waCTree && !c->monst && eq(c->aitmp, sval) && wchance(items[itPirate], 15)) {
|
|
c->monst = moParrot;
|
|
continue;
|
|
}
|
|
|
|
else if(c->wall == waSea && !c->monst && eq(c->aitmp, sval)) {
|
|
if(c->land == laCaribbean && wchance(items[itPirate], 15)) {
|
|
c->monst = moCShark;
|
|
continue;
|
|
}
|
|
if(c->land == laOcean && cwt.c->landparam < 25 && c->landparam < 25 && wchance(items[itCoast], 25)) {
|
|
c->monst = moAlbatross;
|
|
continue;
|
|
}
|
|
if(c->land == laLivefjord && wchance(items[itFjord], 80) && items[itFjord] >= 10) {
|
|
c->monst = moWaterElemental;
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
else if(c->monst || c->pathdist == INFD) break;
|
|
|
|
else if(hrand(50) < statuecount * statuecount)
|
|
c->monst = moCultistLeader;
|
|
|
|
else if(c->land == laIce && wchance(items[itDiamond], 10))
|
|
c->monst = hrand(2) ? moWolf : moYeti;
|
|
|
|
else if(c->land == laDesert && wchance(items[itSpice], 10))
|
|
c->monst = hrand(10) ? moDesertman : moWorm;
|
|
|
|
else if(c->land == laRedRock && wchance(items[itRedGem], 10))
|
|
c->monst = hrand(10) ? moRedTroll : moHexSnake;
|
|
|
|
else if(c->land == laCaves && wchance(items[itGold], 5))
|
|
c->monst = hrand(3) ? moTroll : moGoblin;
|
|
|
|
else if(c->land == laHive && wchance(hivehard(), 25))
|
|
c->monst = randomHyperbug();
|
|
|
|
else if(c->land == laDeadCaves && wchance(items[itSilver], 5))
|
|
c->monst = hrand(20) ? (hrand(3) ? moDarkTroll : moGoblin) : moEarthElemental;
|
|
|
|
else if(c->land == laJungle && wchance(items[itRuby], 40))
|
|
c->monst = hrand(10) ? moMonkey : moEagle;
|
|
|
|
else if(c->land == laMirror && wchance(items[itShard], 15))
|
|
c->monst = hrand(10) ? moRanger : moEagle;
|
|
|
|
else if(c->land == laHell && wchance(items[itHell], 20))
|
|
c->monst = hrand(3) ? moLesser : moGreater;
|
|
|
|
else if(c->land == laCaribbean && wchance(items[itPirate], 30))
|
|
c->monst = moPirate;
|
|
|
|
else if(c->land == laRlyeh && wchance(items[itStatue], 15))
|
|
c->monst = hrand(3) ? moPyroCultist :
|
|
(hrand(40) < items[itStatue]-25) ? moCultistLeader : moCultist;
|
|
|
|
else if(c->land == laGraveyard && wchance(items[itBone], 15))
|
|
c->monst = hrand(5) ? moGhost : moNecromancer;
|
|
|
|
else if(c->land == laDryForest && wchance(items[itFernFlower], 5))
|
|
c->monst = hrand(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->aitmp, sval) && c->item == itNone)
|
|
c->monst = moSlime;
|
|
|
|
else if(isElemental(c->land) && wchance(items[itElemental], 20))
|
|
c->monst = elementalOf(c->land);
|
|
|
|
else if(c->land == laEdge && wchance(items[itEdge], 20))
|
|
c->monst = moGargoyle;
|
|
|
|
else if(c->land == laMinefield && wchance(items[itBombEgg]-20, 400))
|
|
c->monst = moBomberbird;
|
|
|
|
else if(c->land == laEmerald && wchance(items[itEmerald], 5)) {
|
|
static eMonster m[4] = {moHedge, moLancer, moMiner, moFlailer};
|
|
c->monst = m[hrand(4)];
|
|
}
|
|
|
|
else if(c->land == laWineyard && wchance(items[itWine], 10)) {
|
|
c->monst = moVineBeast;
|
|
}
|
|
|
|
else if(c->land == laPalace && wchance(items[itPalace], 50)) {
|
|
if(princess::dist(c) < OUT_OF_PRISON && !princess::challenge) break;
|
|
|
|
if(items[itPalace] >= 15 && hrand(100) < 10)
|
|
c->monst = moVizier;
|
|
else if(items[itPalace] >= 5 && hrand(100) < 50)
|
|
c->monst = hrand(2) ? moFatGuard : moSkeleton;
|
|
else c->monst = moPalace;
|
|
c->hitpoints = palaceHP();
|
|
}
|
|
|
|
else if(c->land == laLivefjord && wchance(items[itFjord], 10)) {
|
|
c->monst = moViking;
|
|
}
|
|
|
|
else if(c->land == laOcean && wchance(items[itCoast], 100)) {
|
|
c->monst = moAlbatross;
|
|
}
|
|
|
|
else if(c->land == laPower && wchance(items[itPower], 10)) {
|
|
c->monst = eMonster(moWitch + hrand(NUMWITCH));
|
|
}
|
|
|
|
else if(c->land == laCamelot && hrand(30) == 0 && (euclid || c->master->alt) && celldistAltRelative(c) < 0) {
|
|
eMonster m[3] = { moHedge, moLancer, moFlailer };
|
|
c->monst = m[hrand(3)];
|
|
}
|
|
|
|
else if(isCrossroads(c->land) && items[itHyperstone] && wchance(items[itHyperstone], 20)) {
|
|
c->monst = wanderingCrossroadsMonster();
|
|
}
|
|
|
|
else break;
|
|
|
|
if(c->monst == moWorm || c->monst == moHexSnake) c->mondir = NODIR;
|
|
|
|
// laMotion -> no respawn!
|
|
}
|
|
}
|
|
|
|
void sageheat(cell *c, double v) {
|
|
HEAT(c) += v;
|
|
if(c->wall == waFrozenLake && HEAT(c) > .6) c->wall = waLake;
|
|
}
|
|
|
|
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) {
|
|
short& i(c->aitmp);
|
|
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->landparam > 1) {
|
|
c->landparam --;
|
|
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->aitmp < 0 || c2->aitmp >= size(buginfo)) continue;
|
|
if(!passable(c, c2, true, false, false)) continue;
|
|
int j = c2->aitmp;
|
|
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;
|
|
if(c->stuntime) 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, false)) continue;
|
|
if(c2->aitmp < 0 || c2->aitmp >= size(buginfo)) continue;
|
|
if(buginfo[c2->aitmp].where != c2) continue;
|
|
if(buginfo[c2->aitmp].dist[k] < b.dist[k])
|
|
qual = 1;
|
|
else if(buginfo[c2->aitmp].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) { if(c->land == laHive) c->landparam += 3; continue; }
|
|
int d = gmoves[hrand(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));
|
|
killOrStunMonster(c2);
|
|
// killMonster(c);
|
|
if(isBug(killed)) {
|
|
c2->monst = moDeadBug, deadbug.push_back(c2);
|
|
bugcount[killed - moBug0]--;
|
|
}
|
|
// c->monst = moDeadBug, deadbug.push_back(c);
|
|
}
|
|
else {
|
|
moveMonster(c2, c);
|
|
// pheromones!
|
|
if(c->land == laHive && c->landparam < 90) c->landparam += 5;
|
|
if(c2->land == laHive && c2->landparam < 90) c2->landparam += 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) {
|
|
short& i = c->aitmp;
|
|
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 = randomHyperbug() - moBug0;
|
|
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 + hrand(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 + hrand(BUGCOLORS));
|
|
if(c->wall) return;
|
|
for(int i=0; i<c->type; i++) {
|
|
cell *c2 = createMov(c,i);
|
|
if(hrand(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(hrand(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 + hrand(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->land == laHive && c->landparam >= 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->cpdist == 1 && c->monst != moGhost) {
|
|
killThePlayer(c->monst, ONLY_ONE_PLAYER_POSSIBLE);
|
|
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, true)) {
|
|
|
|
if(c->cpdist == 1 && c->monst != moGhost && isNeighbor(c, cwt.c) && !attackingForbidden(cwt.c, c)) {
|
|
// c->iswall = true; c->ismon = false;
|
|
if(c->monst == moCrystalSage) return;
|
|
|
|
killThePlayer(m, ONLY_ONE_PLAYER_POSSIBLE);
|
|
// 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, false) && c->mov[j]->pathdist < c->pathdist)
|
|
ztab[zombienum++] = c->mov[j];
|
|
}
|
|
if(gravenum && zombienum) {
|
|
cell *gr = gtab[hrand(gravenum)];
|
|
gr->wall = waAncientGrave;
|
|
gr->monst = moGhost;
|
|
ztab[hrand(zombienum)]->monst = moZombie;
|
|
addMessage(XLAT("%The1 raises some undead!", c->monst));
|
|
return;
|
|
}
|
|
}
|
|
|
|
if(c->monst == moWolf) {
|
|
int bhd = NODIR;
|
|
ld besth = absheat(c);
|
|
for(int j=0; j<c->type; j++)
|
|
if(isIcyLand(c->mov[j]))
|
|
if(absheat(c->mov[j]) > besth && passable(c->mov[j], c, false, false, false))
|
|
besth = absheat(c->mov[j]), bhd = j;
|
|
if(bhd != NODIR) {
|
|
// printf("wolf moved from %Lf (%p) to %Lf (%p)\n", c->heat, c, besth, c->mov[bhd]);
|
|
moveMonster(c->mov[bhd], c);
|
|
c->mov[bhd]->monst = moWolfMoved;
|
|
}
|
|
}
|
|
|
|
else if(c->monst == moPyroCultist && c->cpdist <= 4 && makeflame(cwt.c, 20, true)) {
|
|
addMessage(XLAT("%The1 throws fire at you!", c->monst));
|
|
makeflame(cwt.c, 20, false);
|
|
c->monst = moCultist;
|
|
}
|
|
|
|
else if(c->monst == moPyroCultist && c->cpdist <= 3) {
|
|
// just wait until the player moves
|
|
}
|
|
|
|
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) && cwt.c->wall != waBoat) {
|
|
// 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 moveworms() {
|
|
int wrm = size(worms);
|
|
for(int i=0; i<wrm; i++) {
|
|
moveWorm(worms[i]);
|
|
}
|
|
}
|
|
|
|
bool survivesWater(eMonster m) {
|
|
return
|
|
m == moShark || m == moGreaterShark || m == moCShark ||
|
|
m == moGhost || m == moWitchGhost || m == moTentacleGhost ||
|
|
isBird(m) || m == moWaterElemental ||
|
|
isWorm(m) || isIvy(m);
|
|
}
|
|
|
|
bool survivesFire(eMonster m) {
|
|
return
|
|
m == moGhost || m == moWitchWinter || m == moWitchGhost ||
|
|
m == moBomberbird || m == moTameBomberbird || m == moTameBomberbirdMoved ||
|
|
isWorm(m);
|
|
}
|
|
|
|
bool survivesMine(eMonster m) {
|
|
return
|
|
isBird(m) || m == moGhost;
|
|
}
|
|
|
|
bool survivesWall(eMonster m) {
|
|
return m == moGhost;
|
|
}
|
|
|
|
bool survivesChasm(eMonster m) {
|
|
return
|
|
m == moGhost || isBird(m);
|
|
}
|
|
|
|
void moverefresh() {
|
|
int dcs = size(dcal);
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
|
|
if(c->monst == moWolfMoved) c->monst = moWolf;
|
|
if(c->monst == moIvyNext) {
|
|
c->monst = moIvyHead; ivynext(c);
|
|
}
|
|
if(c->monst == moIvyDead)
|
|
removeIvy(c);
|
|
if(c->monst == moGolemMoved) c->monst = moGolem;
|
|
if(c->monst == moMouseMoved) c->monst = moMouse;
|
|
if(c->monst == moPrincessMoved) c->monst = moPrincess;
|
|
if(c->monst == moPrincessArmedMoved) c->monst = moPrincessArmed;
|
|
if(c->monst == moKnightMoved) c->monst = moKnight;
|
|
if(c->monst == moTameBomberbirdMoved) c->monst = moTameBomberbird;
|
|
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->stuntime) c->stuntime--;
|
|
|
|
if(c->wall == waChasm) {
|
|
c->item = itNone;
|
|
if(c->monst && !survivesChasm(c->monst)) {
|
|
if(c->monst != moRunDog && c->land == laMotion)
|
|
achievement_gain("FALLDEATH1");
|
|
addMessage(XLAT("%The1 falls!", c->monst));
|
|
killWithMessage(c, false);
|
|
}
|
|
}
|
|
|
|
if(isFire(c)) {
|
|
if(c->monst && !survivesFire(c->monst)) {
|
|
addMessage(XLAT("%The1 burns!", c->monst));
|
|
killWithMessage(c, false);
|
|
}
|
|
}
|
|
|
|
if(c->wall == waMineMine && c->monst && !survivesMine(c->monst))
|
|
explodeMine(c);
|
|
|
|
if(isWatery(c)) {
|
|
if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM)
|
|
c->monst = moGreaterShark;
|
|
if(c->monst && !survivesWater(c->monst)) {
|
|
if(isNonliving(c->monst))
|
|
addMessage(XLAT("%The1 sinks!", c->monst));
|
|
else
|
|
addMessage(XLAT("%The1 drowns!", c->monst));
|
|
killWithMessage(c, false);
|
|
}
|
|
}
|
|
|
|
if(c->monst && c->wall == waClosedGate && !survivesWall(c->monst)) {
|
|
addMessage(XLAT("%The1 is crushed!", c->monst));
|
|
killWithMessage(c, false);
|
|
}
|
|
|
|
if(c->monst && cellUnstable(c) && !survivesChasm(c->monst) && !ignoresPlates(c->monst) && !shmup::on) {
|
|
c->wall = waChasm;
|
|
}
|
|
}
|
|
}
|
|
|
|
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");
|
|
moveworms();
|
|
DEBT("ivy");
|
|
moveivy();
|
|
DEBT("slimes");
|
|
groupmove(moSlime, false);
|
|
DEBT("eagles");
|
|
if(haveeagles) groupmove(moEagle, false), groupmove(moEagle, true);
|
|
DEBT("earth");
|
|
if(haveearth) groupmove(moEarthElemental, false);
|
|
DEBT("water");
|
|
if(havewater) groupmove(moWaterElemental, false);
|
|
DEBT("leader");
|
|
if(haveleader) groupmove(moPirate, false);
|
|
DEBT("hex");
|
|
if(havehex) movehex();
|
|
DEBT("bugs");
|
|
if(havebugs) movebugs();
|
|
DEBT("whirlpool");
|
|
if(havewhirlpool) movewhirlpool();
|
|
|
|
DEBT("golems");
|
|
movegolems();
|
|
|
|
DEBT("fresh");
|
|
moverefresh();
|
|
|
|
DEBT("shadow");
|
|
moveshadow();
|
|
|
|
DEBT("wandering");
|
|
wandering();
|
|
}
|
|
|
|
// move heat
|
|
|
|
vector<cell*> vinefires;
|
|
|
|
void processheat(double rate = 1, bool tick = true) {
|
|
if(markOrb(itOrbSpeed)) rate /= 2;
|
|
int oldmelt = kills[0];
|
|
|
|
vector<cell*> offscreen2;
|
|
|
|
for(int i=0; i<size(offscreen); i++) {
|
|
cell *c = offscreen[i];
|
|
if(c->cpdist > 7) {
|
|
bool readd = false;
|
|
if(isIcyLand(c)) {
|
|
if(HEAT(c) < .01 && HEAT(c) > -.01)
|
|
HEAT(c) = 0;
|
|
else {
|
|
HEAT(c) *= 1 - rate/10;
|
|
readd = true;
|
|
}
|
|
}
|
|
if(hasTimeout(c)) {
|
|
useup(c);
|
|
if(hasTimeout(c)) readd = true;
|
|
}
|
|
if(readd) offscreen2.push_back(c);
|
|
}
|
|
}
|
|
|
|
offscreen.clear(); swap(offscreen, offscreen2);
|
|
|
|
/* 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; */
|
|
|
|
for(int i=0; i<numplayers(); i++) {
|
|
cell *c = playerpos(i);
|
|
double xrate = (c->land == laCocytus && shmup::on) ? rate/3 : rate;
|
|
if(isIcyLand(c))
|
|
HEAT(c) += (markOrb(itOrbWinter) ? -1.2 : 1.2) * xrate;
|
|
}
|
|
|
|
vinefires.clear();
|
|
|
|
int dcs = size(dcal);
|
|
for(int i=0; i<dcs; i++) {
|
|
bool readd = false;
|
|
cell *c = dcal[i];
|
|
double xrate = (c->land == laCocytus && shmup::on) ? rate/3 : rate;
|
|
if(c->cpdist > 8) break;
|
|
|
|
if(hasTimeout(c)) {
|
|
if(tick) useup(c);
|
|
readd = true;
|
|
}
|
|
|
|
if(isFire(c) && tick) {
|
|
if(c->wall != waPartialFire) for(int i=0; i<c->type; i++) {
|
|
cell *c2 = c->mov[i];
|
|
if(c2 && c2->wall == waVinePlant)
|
|
vinefires.push_back(c2);
|
|
if(c2 && c2->wall == waBonfireOff) activateActiv(c2, false);
|
|
// 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)) {
|
|
if(c->monst == moRanger) HEAT(c) += 3 * xrate;
|
|
if(c->monst == moDesertman) HEAT(c) += 4 * xrate;
|
|
if(c->monst == moMonkey) HEAT(c) += xrate;
|
|
if(c->wall == waDeadTroll) HEAT(c) -= 2 * xrate;
|
|
if(c->wall == waBigStatue) HEAT(c) -= .5 * xrate;
|
|
if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM)
|
|
HEAT(c) += (c->land == laCocytus ? 1.5 : 10) * xrate;
|
|
if(c->monst == moGreaterShark)
|
|
HEAT(c) += 2 * xrate;
|
|
if(c->monst == moCultist) HEAT(c) += 3 * xrate;
|
|
if(c->monst == moCultistLeader) HEAT(c) += 4 * xrate;
|
|
if(c->monst == moPyroCultist) HEAT(c) += 6 * xrate;
|
|
if(c->monst == moFireFairy) HEAT(c) += 6 * xrate;
|
|
if(c->monst == moFireElemental) HEAT(c) += 8 * xrate;
|
|
if(c->monst == moGhost) HEAT(c) -= xrate;
|
|
if(c->monst == moWaterElemental) HEAT(c) -= xrate;
|
|
if(isFire(c)) HEAT(c) += 4 * xrate;
|
|
if(isPrincess(c->monst)) HEAT(c) += 1.2 * xrate;
|
|
|
|
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 || markOrb(itOrbWinter)))
|
|
hmod += (markOrb(itOrbWinter) ? -1.2 : 1.2) / 4;
|
|
continue;
|
|
}
|
|
ld hdiff = absheat(c->mov[j]) - absheat(c);
|
|
hdiff /= 10;
|
|
if(shmup::on && (c->land == laCocytus || c->mov[j]->land == laCocytus))
|
|
hdiff /= 3;
|
|
if(c->mov[j]->cpdist <= 7)
|
|
HEAT(c->mov[j]) -= hdiff * rate;
|
|
else
|
|
hdiff = -HEAT(c) / 30;
|
|
hmod += hdiff;
|
|
}
|
|
|
|
HEAT(c) += hmod * rate;
|
|
if(c->monst == moCrystalSage && HEAT(c) >= SAGEMELT) {
|
|
addMessage(XLAT("%The1 melts away!", c->monst));
|
|
killWithMessage(c, false);
|
|
}
|
|
}
|
|
|
|
if(readd || HEAT(c))
|
|
offscreen.push_back(c);
|
|
}
|
|
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(c->wall == waIcewall && HEAT(c) > .4) c->wall = waNone, kills[0]++;
|
|
if(c->wall == waFrozenLake && HEAT(c) > (c->land == laCocytus ? .6 : .4)) c->wall = waLake, kills[0]++;
|
|
|
|
if(c->wall == waLake && HEAT(c) < (c->land == laCocytus ? -.4 : .4) && c->monst != moGreaterShark) {
|
|
c->wall = waFrozenLake;
|
|
if(c->monst == moShark || c->monst == moCShark) {
|
|
addMessage(XLAT("%The1 is frozen!", c->monst));
|
|
killWithMessage(c, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
if(tick) for(int i=0; i<size(vinefires); i++) {
|
|
cell* c = vinefires[i];
|
|
if(c->wall == waVinePlant)
|
|
makeflame(c, 6, false);
|
|
else if(cellHalfvine(c)) destroyHalfvine(c, waPartialFire, 6);
|
|
}
|
|
|
|
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) {
|
|
c->aitmp = 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->aitmp++, bringlife.push_back(c->mov[j]);
|
|
else if(c->mov[j]->wall == waDeadwall || c->mov[j]->wall == waDeadfloor2) c->aitmp--, bringlife.push_back(c->mov[j]);
|
|
else if(c->mov[j]->wall == waCavefloor) c->aitmp++;
|
|
else if(c->mov[j]->wall == waCavewall) c->aitmp--;
|
|
else if(c->mov[j]->wall == waRubble) c->aitmp--;
|
|
else if(c->mov[j]->wall == waGargoyle) c->aitmp--;
|
|
else if(c->mov[j]->wall == waGargoyleFloor) c->aitmp--;
|
|
else if(c->mov[j]->wall == waGargoyleBridge) c->aitmp--;
|
|
else if(c->mov[j]->wall == waDeadTroll) c->aitmp -= 5;
|
|
else if(c->mov[j]->wall == waVinePlant) c->aitmp--;
|
|
else if(c->mov[j]->wall != waBarrier) c->aitmp += 5;
|
|
if(c->mov[j]->cpdist == 0 && markOrb(itOrbDigging)) c->aitmp+=100;
|
|
if(c->mov[j]->wall == waThumperOn) c->aitmp+=100;
|
|
if(c->mov[j]->wall == waFire) c->aitmp+=100;
|
|
if(c->mov[j]->wall == waBigStatue) c->aitmp-=100;
|
|
if(c->mov[j]->item) c->aitmp+=2;
|
|
if(c->mov[j]->monst == moZombie) c->aitmp += 10;
|
|
if(c->mov[j]->monst == moGhost) c->aitmp += 10;
|
|
if(c->mov[j]->monst == moGargoyle) c->aitmp--;
|
|
if(c->mov[j]->monst == moNecromancer) c->aitmp += 10;
|
|
if(c->mov[j]->monst == moWormtail) c->aitmp++;
|
|
if(c->mov[j]->monst == moTentacletail) c->aitmp-=2;
|
|
if(isIvy(c->mov[j])) c->aitmp--;
|
|
if(isDemon(c->mov[j])) c->aitmp-=3;
|
|
// if(c->mov[j]->monst) c->tmp++;
|
|
// if(c->mov[j]->monst == moTroll) c->tmp -= 3;
|
|
}
|
|
}
|
|
else if(c->land == laLivefjord) {
|
|
c->aitmp = 0;
|
|
if(c->monst == moWaterElemental)
|
|
c->aitmp += 1000;
|
|
if(isPlayerInBoatOn(c) && markOrb(itOrbWater))
|
|
c->aitmp += 1000;
|
|
if(c->monst == moEarthElemental)
|
|
c->aitmp -= 1000;
|
|
if(isPlayerOn(c) && markOrb(itOrbDigging))
|
|
c->aitmp -= 1000;
|
|
for(int j=0; j<c->type; j++) if(c->mov[j]) {
|
|
cell *c2 = c->mov[j];
|
|
if(c2->wall == waNone || c2->wall == waStrandedBoat)
|
|
c->aitmp -= (c2->land == laLivefjord ? 1 : 100);
|
|
if(c2->wall == waTempFloor || c2->wall == waTempBridge)
|
|
;
|
|
else if(c2->wall == waDeadTroll || c2->wall == waThumperOn || isFire(c2) || snakelevel(c2))
|
|
c->aitmp -= 10;
|
|
if(c2->wall == waBigStatue)
|
|
c->aitmp -= 10;
|
|
if(c2->wall == waSea || c2->wall == waBoat)
|
|
c->aitmp += (c2->land == laLivefjord ? 1 : 100);
|
|
if(c2->monst == moWaterElemental)
|
|
c->aitmp += 1000;
|
|
if(c2 == cwt.c && c2->wall == waBoat && markOrb(itOrbWater))
|
|
c->aitmp += 1000;
|
|
if(c2->monst == moEarthElemental)
|
|
c->aitmp -= 1000;
|
|
if(c2 == cwt.c && markOrb(itOrbDigging))
|
|
c->aitmp -= 1000;
|
|
if(c2->wall == waBarrier) {
|
|
bool landbar = false;
|
|
for(int k=0; k<c2->type; k++)
|
|
if(c2->mov[k]) {
|
|
cell *c3 = c2->mov[k];
|
|
if(!isSealand(c3->land))
|
|
landbar = true;
|
|
}
|
|
if(landbar) c->aitmp -= 5;
|
|
else c->aitmp += 5;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(c->cpdist > 8) break;
|
|
if(c->wall == waCavefloor || c->wall == waCavewall) {
|
|
// if(c->land != laCaves) continue;
|
|
// if(c->wall == waThumper || c->wall == waBonfire) continue;
|
|
|
|
if(c->aitmp > 0) c->wall = waCavefloor;
|
|
if(c->aitmp < 0) {
|
|
c->wall = waCavewall;
|
|
if(c->land != laCaves && c->land != laDeadCaves && c->land != laEmerald && !gardener) {
|
|
gardener = true;
|
|
achievement_gain("GARDENER");
|
|
}
|
|
}
|
|
}
|
|
else if(c->land == laLivefjord) {
|
|
if(c->aitmp > 0 && c->wall == waStrandedBoat) c->wall = waBoat;
|
|
if(c->aitmp > 0 && c->wall == waNone) {
|
|
if(c->item && c->cpdist == 1 && markOrb(itOrbWater))
|
|
collectItem(c);
|
|
c->wall = waSea;
|
|
}
|
|
if(c->aitmp < 0 && c->wall == waBoat) c->wall = waStrandedBoat;
|
|
if(c->aitmp < 0 && c->wall == waSea) c->wall = waNone;
|
|
}
|
|
}
|
|
|
|
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));
|
|
killWithMessage(c, false);
|
|
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;
|
|
|
|
for(int j=0; j<c->type; j++) if(c->mov[j]) {
|
|
if(isFire(c->mov[j])) c->landparam++;
|
|
}
|
|
|
|
if(c->landparam >= 10) makeflame(c, 10, false), c->landparam = 0;
|
|
}
|
|
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(c->cpdist > 8) break;
|
|
if(c->land != laDryForest) continue;
|
|
if((c->wall == waDryTree || c->wall == waWetTree || isFire(c)) && c->landparam >= 1)
|
|
c->wall = waEternalFire;
|
|
}
|
|
|
|
/*
|
|
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 ||
|
|
c->wall == waGiantRug || c->wall == waCIsland || c->wall == waCIsland2 ||
|
|
c->wall == waGargoyleFloor || c->wall == waRubble ||
|
|
c->wall == waGargoyleBridge || c->wall == waTempFloor || c->wall == waTempBridge;
|
|
}
|
|
|
|
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)) {
|
|
if(!attackJustStuns(c2))
|
|
messageKill(m, c2->monst);
|
|
killOrStunMonster(c2);
|
|
}
|
|
if(c2->wall == waDryTree)
|
|
c2->wall = waWetTree;
|
|
else if(c2->wall == waWetTree)
|
|
c2->wall = waNone;
|
|
if(!go) continue;
|
|
c->monst = moNone;
|
|
if(!c2) continue;
|
|
if(!passable(c2, c, true, true, false)) 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;
|
|
}
|
|
c->monst = m; moveMonster(c2, c);
|
|
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);
|
|
}
|
|
|
|
bool reduceOrbPower(eItem it, int cap) {
|
|
if(items[it] && (lastorbused[it] || !markOrb(itOrbPreserve) || (it == itOrbShield && items[it]>3))) {
|
|
items[it] -= numplayers();
|
|
if(items[it] < 0) items[it] = 0;
|
|
if(items[it] > cap) items[it] = cap;
|
|
if(items[it] == 0 && it == itOrbLove)
|
|
princess::bringBack();
|
|
return true;
|
|
}
|
|
if(items[it] > cap) items[it] = cap;
|
|
return false;
|
|
}
|
|
|
|
void reduceOrbPowerAlways(eItem it) {
|
|
if(items[it]) {
|
|
items[it] -= numplayers();
|
|
if(items[it] < 0) items[it] = 0;
|
|
}
|
|
}
|
|
|
|
void reduceOrbPowers() {
|
|
for(int i=0; i<ittypes; i++)
|
|
lastorbused[i] = orbused[i], orbused[i] = false;
|
|
if(items[itOrbShield]) orbused[itOrbShield] = lastorbused[itOrbShield];
|
|
reduceOrbPower(itOrbPreserve, cwt.c->land == laCaribbean ? 777 : 150);
|
|
if(invismove && !invisfish) markOrb(itOrbInvis);
|
|
reduceOrbPower(itOrbLightning, 777);
|
|
reduceOrbPower(itOrbSpeed, 67);
|
|
reduceOrbPower(itOrbShield, 77);
|
|
reduceOrbPower(itOrbFlash, 777);
|
|
reduceOrbPower(itOrbWinter, 77);
|
|
reduceOrbPower(itOrbFire, 77);
|
|
reduceOrbPower(itOrbIllusion, 111);
|
|
reduceOrbPower(itOrbDragon, 111);
|
|
reduceOrbPower(itOrbPsi, 111);
|
|
reduceOrbPower(itOrbInvis, 77);
|
|
reduceOrbPower(itOrbGhost, 77);
|
|
reduceOrbPower(itOrbDigging, 100);
|
|
reduceOrbPower(itOrbTeleport, 200);
|
|
reduceOrbPower(itOrbTelekinesis, 150);
|
|
reduceOrbPowerAlways(itOrbSafety);
|
|
reduceOrbPower(itOrbThorns, 150);
|
|
reduceOrbPower(itOrbWater, 150);
|
|
reduceOrbPower(itOrbAir, 150);
|
|
reduceOrbPower(itOrbFrog, 77);
|
|
reduceOrbPower(itOrbDiscord, 67);
|
|
reduceOrbPower(itOrbSummon, 333);
|
|
reduceOrbPower(itOrbMatter, 333);
|
|
reduceOrbPower(itOrbFish, 77);
|
|
if(!items[itSavedPrincess]) items[itOrbLove] = 0;
|
|
reduceOrbPower(itOrbLove, 777);
|
|
}
|
|
|
|
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));
|
|
killWithMessage(c, false);
|
|
if(isIcyLand(c))
|
|
HEAT(c) += 2;
|
|
if(c->land == laDryForest)
|
|
c->landparam += 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 == waGargoyleFloor) c->wall = waChasm;
|
|
if(c->wall == waGargoyleBridge) placeWater(c, c);
|
|
if(c->wall == waGargoyle) c->wall = waNone;
|
|
if(c->wall == waPlatform) c->wall = waNone;
|
|
if(c->wall == waStone) c->wall = waNone;
|
|
if(c->wall == waRubble) c->wall = waNone;
|
|
if(c->wall == waDeadwall) c->wall = waDeadfloor2;
|
|
if(c->wall == waGiantRug) c->wall = waNone;
|
|
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(c->wall == waCTree) c->wall = waCIsland2;
|
|
if(c->wall == waPalace) c->wall = waRubble;
|
|
if(c->wall == waOpenGate || c->wall == waClosedGate) {
|
|
eWall w = c->wall;
|
|
c->wall = waNone;
|
|
for(int i=0; i<c->type; i++) if(c->mov[i] && c->mov[i]->wall == w)
|
|
flashCell(c->mov[i], msg);
|
|
}
|
|
if(c->wall == waRed1) c->wall = waNone;
|
|
else if(c->wall == waRed2) c->wall = waRed1;
|
|
else if(c->wall == waRed3) c->wall = waRed2;
|
|
if(hasTimeout(c) && c->wparam < 77) c->wparam = 77;
|
|
if(isActivable(c))
|
|
activateActiv(c, false);
|
|
}
|
|
|
|
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;
|
|
// WAS:
|
|
// if(c.c->mov[d]->wall == waBarrier) return true;
|
|
if(c.c->mov[d]->land == laBarrier || c.c->mov[d]->land == laOceanWall ||
|
|
c.c->mov[d]->land == laElementalWall) 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);
|
|
killWithMessage(c, false);
|
|
if(isIcyLand(c)) HEAT(c) += 2;
|
|
if(c->land == laDryForest) c->landparam += 2;
|
|
c->ligon = 1;
|
|
|
|
bool brk = false, spin = false;
|
|
|
|
if(c->wall == waGargoyle) brk = true;
|
|
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 == waRubble) c->wall = waNone;
|
|
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(c->wall == waStone) c->wall = waNone, brk = true;
|
|
|
|
if(c->wall == waBoat) c->wall = waSea, spin = true;
|
|
if(c->wall == waStrandedBoat) c->wall = waNone, spin = true;
|
|
|
|
if((c->wall == waNone || c->wall == waSea) && c->land == laLivefjord)
|
|
c->wall = eWall(c->wall ^ waSea ^ waNone);
|
|
|
|
if(c->wall == waRed1) c->wall = waNone;
|
|
if(c->wall == waRed2) c->wall = waRed1;
|
|
if(c->wall == waRed3) c->wall = waRed2, brk = true;
|
|
|
|
if(isActivable(c)) activateActiv(c, false);
|
|
if(c->wall == waDryTree || c->wall == waWetTree || c->wall == waVinePlant) {
|
|
makeflame(c, 4, false);
|
|
brk = true;
|
|
}
|
|
if(c->wall == waCTree) makeflame(c, 12, false);
|
|
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, hrand(lig.c->type));
|
|
|
|
if(brk) break;
|
|
|
|
if(c->wall == waBarrier || c->wall == waCamelot || c->wall == waPalace || c->wall == waPlatform ||
|
|
c->wall == waTempWall) {
|
|
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, hrand(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();
|
|
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 checkNeedMove(bool checkonly, bool attacking) {
|
|
if(cwt.c->wall == waRoundTable) {
|
|
if(markOrb2(itOrbGhost)) return false;
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("It would be impolite to land on the table!"));
|
|
}
|
|
else if(cwt.c->wall == waLake) {
|
|
if(markOrb2(itOrbGhost)) return false;
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("Ice below you is melting! RUN!"));
|
|
}
|
|
else if(!attacking && cellEdgeUnstable(cwt.c)) {
|
|
if(markOrb2(itOrbGhost)) return false;
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("Nothing to stand on here!"));
|
|
}
|
|
else if(cwt.c->wall == waSea || cwt.c->wall == waCamelotMoat) {
|
|
if(markOrb(itOrbFish)) return false;
|
|
if(markOrb2(itOrbGhost)) return false;
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("You have to run away from the water!"));
|
|
}
|
|
else if(cwt.c->wall == waClosedGate) {
|
|
if(markOrb2(itOrbGhost)) return false;
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("The gate is closing right on you! RUN!"));
|
|
}
|
|
else if(isFire(cwt.c) && !markOrb(itOrbWinter) && !markOrb2(itOrbShield)) {
|
|
if(markOrb2(itOrbGhost)) return false;
|
|
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(markOrb2(itOrbGhost)) return false;
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("Your Aether power has expired! RUN!"));
|
|
}
|
|
else if(cwt.c->wall == waChasm) {
|
|
if(markOrb2(itOrbGhost)) return false;
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("The floor has collapsed! RUN!"));
|
|
}
|
|
else return false;
|
|
if(hardcore) {
|
|
canmove = false;
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
#define YDIST 101
|
|
|
|
struct yendorinfo {
|
|
cell *path[YDIST];
|
|
bool found;
|
|
};
|
|
|
|
vector<yendorinfo> yi;
|
|
|
|
int yii = 0;
|
|
|
|
enum eYendorState { ysUntouched, ysLocked, ysUnlocked };
|
|
|
|
eYendorState yendorState(cell *yendor) {
|
|
for(int i=0; i<size(yi); i++) if(yi[i].path[0] == yendor)
|
|
return yi[i].found ? ysUnlocked : ysLocked;
|
|
return ysUntouched;
|
|
}
|
|
|
|
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, hrand(yendor->type));
|
|
|
|
cell *prev = yendor;
|
|
|
|
generatingYendor = true;
|
|
|
|
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, hrand(2));
|
|
|
|
setdist(lig.c, 10, prev);
|
|
setdist(lig.c, 9, prev);
|
|
setdist(lig.c, 8, prev);
|
|
setdist(lig.c, 7, prev);
|
|
}
|
|
|
|
nyi.path[YDIST-1] = lig.c;
|
|
nyi.found = false;
|
|
|
|
cell *key = lig.c;
|
|
|
|
generatingYendor = false;
|
|
|
|
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, false)) {
|
|
if(c2->wall == waCavewall) c2->wall = waCavefloor;
|
|
else if(c2->wall == waDeadwall) c2->wall = waDeadfloor2;
|
|
else if(c2->wall == waLake) c2->wall = waFrozenLake;
|
|
else if(c2->land == laCaribbean) c2->wall = waCIsland;
|
|
else if(c2->land == laOcean) c2->wall = waCIsland;
|
|
else if(c2->land == laRedRock) c2->wall = waRed3;
|
|
else if(c2->land == laWhirlpool)
|
|
c2->wall = waBoat, c2->monst = moPirate, c2->item = itOrbWater;
|
|
else c2->wall = waNone;
|
|
}
|
|
if(c2->land == laEdge && key->land == laEdge &&
|
|
c2->landparam < key->landparam)
|
|
c2->wall = waPlatform;
|
|
}
|
|
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;
|
|
}
|
|
|
|
void checkOnYendorPath() {
|
|
yendorPath = false;
|
|
if(yii < size(yi)) {
|
|
for(int i=0; i<YDIST; i++) if(yi[yii].path[i]->cpdist <= 7) {
|
|
yendorPath = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
int countMyGolems(eMonster m) {
|
|
int g=0, dcs = size(dcal);
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(c->monst == m) g++;
|
|
}
|
|
return g;
|
|
}
|
|
|
|
int countMyGolemsHP(eMonster m) {
|
|
int g=0, dcs = size(dcal);
|
|
for(int i=0; i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(c->monst == m) g += c->hitpoints;
|
|
}
|
|
return g;
|
|
}
|
|
|
|
void restoreGolems(int qty, eMonster m, int hp = 0) {
|
|
int dcs = size(dcal);
|
|
for(int i=1; qty && i<dcs; i++) {
|
|
cell *c = dcal[i];
|
|
if(m == moTameBomberbird ?
|
|
(c->mpdist >= 3 && eaglepassable(c, NULL)) :
|
|
passable(c, NULL, false, false, false)) {
|
|
c->hitpoints = hp / qty;
|
|
c->monst = m, qty--, hp -= c->hitpoints;
|
|
if(m == moPrincess || m == moPrincessArmed)
|
|
princess::newFakeInfo(c);
|
|
}
|
|
}
|
|
}
|
|
|
|
void activateSafety(eLand l) {
|
|
int gg = countMyGolems(moGolem);
|
|
int gb = countMyGolems(moTameBomberbird);
|
|
int gp1 = countMyGolems(moPrincess);
|
|
int gp2 = countMyGolems(moPrincessArmed);
|
|
int gph1 = countMyGolemsHP(moPrincess);
|
|
int gph2 = countMyGolemsHP(moPrincessArmed);
|
|
drawSafety();
|
|
addMessage(XLAT("You fall into a wormhole!"));
|
|
|
|
eLand f = firstland;
|
|
if(l == laTemple) l = laRlyeh;
|
|
if(l == laWhirlpool) l = laOcean;
|
|
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(gg, moGolem);
|
|
restoreGolems(gb, moTameBomberbird);
|
|
restoreGolems(gp1, moPrincess, gph1);
|
|
restoreGolems(gp2, moPrincessArmed, gph2);
|
|
restartGraph();
|
|
}
|
|
|
|
bool hasSafeOrb(cell *c) {
|
|
return
|
|
c->item == itOrbSafety ||
|
|
c->item == itOrbShield ||
|
|
c->item == itOrbYendor;
|
|
}
|
|
|
|
void checkmove() {
|
|
if(hardcore) return;
|
|
bool orbusedbak[ittypes];
|
|
|
|
// do not activate orbs!
|
|
for(int i=0; i<ittypes; i++) orbusedbak[i] = orbused[i];
|
|
|
|
canmove = false;
|
|
if(movepcto(-1, 0, true)) canmove = true;
|
|
if(!canmove)
|
|
for(int i=0; i<cwt.c->type; i++)
|
|
if(movepcto(1, -1, true)) canmove = true;
|
|
if(!canmove)
|
|
for(int i=0; i<cwt.c->type; i++)
|
|
if(movepcto(1, 1, true)) canmove = true;
|
|
if(!canmove)
|
|
achievement_final(true);
|
|
if(canmove && timerstopped) {
|
|
timerstart = time(NULL);
|
|
timerstopped = false;
|
|
}
|
|
|
|
for(int i=0; i<ittypes; i++) orbused[i] = orbusedbak[i];
|
|
}
|
|
|
|
// move the PC. Warning: a very long function! todo: refactor
|
|
|
|
void placeGolem(cell *on, cell *moveto, eMonster m) {
|
|
if(passable(on, moveto, false, false, false) || m == moTameBomberbird)
|
|
cwt.c->monst = m;
|
|
else {
|
|
cwt.c->monst = m;
|
|
killWithMessage(cwt.c);
|
|
if(isFire(on))
|
|
addMessage(XLAT("%The1 burns!", m));
|
|
else if(on->wall == waChasm)
|
|
addMessage(XLAT("%The1 falls!", m));
|
|
else if(isWatery(on) && isNonliving(m))
|
|
addMessage(XLAT("%The1 sinks!", m));
|
|
else if(isWatery(on))
|
|
addMessage(XLAT("%The1 drowns!", m));
|
|
else if(on->wall == waClosedGate)
|
|
addMessage(XLAT("%The1 is crushed!", 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!"));
|
|
}
|
|
}
|
|
|
|
// 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] || items[itOrbTelekinesis] || items[itOrbAir] ||
|
|
items[itOrbFrog] || items[itOrbSummon] || items[itOrbMatter];
|
|
}
|
|
|
|
bool isRangedOrb(eItem i) {
|
|
return i == itOrbPsi || i == itOrbDragon || i == itOrbTeleport || i == itOrbIllusion ||
|
|
i == itOrbTelekinesis || i == itOrbAir || i == itOrbFrog;
|
|
}
|
|
|
|
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 cantGetGrimoire(cell *c2, bool verbose = true) {
|
|
if(!euclid && !c2->master->alt) return false;
|
|
if(c2->item == itGrimoire && items[itGrimoire] > celldistAlt(c2)/-TEMPLE_EACH) {
|
|
if(verbose)
|
|
addMessage(XLAT("You already have this Grimoire! Seek new tomes in the inner circles."));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool collectItem(cell *c2, bool telekinesis) {
|
|
|
|
int pg = gold();
|
|
bool dopickup = true;
|
|
|
|
if(itemHidden(c2) && !telekinesis && !(isWatery(c2) && markOrb(itOrbFish)))
|
|
return false;
|
|
|
|
if(c2->item == itOrbYendor && telekinesis && yendorState(c2) != ysUnlocked)
|
|
return false;
|
|
|
|
/* if(c2->item == itHolyGrail && telekinesis)
|
|
return false; */
|
|
|
|
if(c2->item) {
|
|
invismove = false;
|
|
if(shmup::on) shmup::visibleFor(2000);
|
|
string s0 = "";
|
|
|
|
if(c2->item == itPalace && items[c2->item] == 12)
|
|
princess::forceVizier = true;
|
|
|
|
if(isElementalShard(c2->item)) {
|
|
int tsh =
|
|
items[itFireShard] + items[itAirShard] + items[itWaterShard] + items[itEarthShard] +
|
|
items[itElemental];
|
|
if(tsh == 0) {
|
|
addMessage(XLAT("Collect four different Elemental Shards!"));
|
|
addMessage(XLAT("Unbalanced shards in your inventory are dangerous."));
|
|
}
|
|
else {
|
|
string t = XLAT("You collect %the1.", c2->item);
|
|
addMessage(t);
|
|
}
|
|
}
|
|
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(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(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 == itSpice && items[itSpice] == 10)
|
|
addMessage(XLAT("You will be fighting red rock snakes, too..."));
|
|
// else if(c2->item == itFeather && items[itFeather] == 10)
|
|
// addMessage(XLAT("There should be a Palace somewhere nearby..."));
|
|
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;
|
|
}
|
|
else if(c2->item == itOrbLife) {
|
|
if(shmup::on) {
|
|
items[c2->item] ++;
|
|
if(items[c2->item] > 5) items[c2->item] = 5;
|
|
}
|
|
else placeGolem(cwt.c, c2, moGolem);
|
|
}
|
|
else if(c2->item == itOrbFriend) {
|
|
if(shmup::on) {
|
|
items[itOrbLife] ++;
|
|
if(items[itOrbLife] > 5) items[itOrbLife] = 5;
|
|
}
|
|
else placeGolem(cwt.c, c2, moTameBomberbird);
|
|
}
|
|
else if(c2->item == itOrbSafety) {
|
|
items[c2->item] += 7;
|
|
if(shmup::on)
|
|
shmup::safety = true;
|
|
else
|
|
activateSafety(c2->land);
|
|
return true;
|
|
}
|
|
else if(c2->item == itOrbLightning) {
|
|
items[c2->item] += 78;
|
|
}
|
|
else if(c2->item == itOrbPreserve) {
|
|
items[c2->item] += 78;
|
|
}
|
|
else if(c2->item == itOrbLove) {
|
|
items[c2->item] += 31;
|
|
}
|
|
else if(c2->item == itOrbTelekinesis) {
|
|
items[c2->item] += 78;
|
|
}
|
|
else if(c2->item == itOrbThorns) {
|
|
items[c2->item] += 78;
|
|
}
|
|
else if(c2->item == itOrbFlash) {
|
|
items[c2->item] += 78;
|
|
}
|
|
else if(c2->item == itOrbShield) {
|
|
items[c2->item] += 16;
|
|
orbused[itOrbShield] = false;
|
|
}
|
|
else if(c2->item == itOrbWater) {
|
|
items[c2->item] += 31;
|
|
}
|
|
else if(c2->item == itOrbAir) {
|
|
items[c2->item] += 67;
|
|
}
|
|
else if(c2->item == itOrbFrog) {
|
|
items[c2->item] += 45;
|
|
}
|
|
else if(c2->item == itOrbDiscord) {
|
|
// make it seem to be 23
|
|
if(!items[c2->item]) items[c2->item]++;
|
|
items[c2->item] += 23;
|
|
}
|
|
else if(c2->item == itOrbSummon) {
|
|
items[c2->item] += 121;
|
|
}
|
|
else if(c2->item == itOrbMatter) {
|
|
items[c2->item] += 67;
|
|
}
|
|
else if(c2->item == itOrbFish) {
|
|
items[c2->item] += 31;
|
|
}
|
|
else if(c2->item == itOrbWinter) {
|
|
items[c2->item] += 31;
|
|
}
|
|
else if(c2->item == itOrbFire) {
|
|
items[c2->item] += 31;
|
|
}
|
|
else if(c2->item == itOrbDragon) {
|
|
items[c2->item] += 78;
|
|
}
|
|
else if(c2->item == itOrbIllusion) {
|
|
items[c2->item] += 78;
|
|
}
|
|
else if(c2->item == itOrbPsi) {
|
|
items[c2->item] += 78;
|
|
}
|
|
else if(c2->item == itOrbInvis) {
|
|
items[c2->item] += 31;
|
|
}
|
|
else if(c2->item == itOrbGhost) {
|
|
items[c2->item] += 31;
|
|
}
|
|
else if(c2->item == itOrbDigging) {
|
|
items[c2->item] += 78;
|
|
}
|
|
else if(c2->item == itOrbTeleport) {
|
|
items[c2->item] += 78;
|
|
}
|
|
else if(c2->item == itOrbYendor) {
|
|
items[itOrbShield] += 31;
|
|
// Shielding always, so that we know that it protects!
|
|
for(int i=0; i<4; i++) switch(hrand(13)) {
|
|
case 0: items[itOrbSpeed] += 31; break;
|
|
case 1: items[itOrbLightning] += 78; break;
|
|
case 2: items[itOrbFlash] += 78; break;
|
|
case 3: items[itOrbPreserve] += 78; 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;
|
|
case 12: items[itOrbTelekinesis] += 78; 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->emeraldval |= 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(!telekinesis && cantGetGrimoire(c2)) {
|
|
// telekinesis checks the condition earlier
|
|
dopickup = false;
|
|
}
|
|
else if(c2->item == itCompass) {
|
|
dopickup = false;
|
|
}
|
|
else {
|
|
bool lhu = hellUnlocked();
|
|
if(c2->item) gainItem(c2->item);
|
|
int g2 = gold();
|
|
|
|
if(items[itFireShard] && items[itAirShard] && items[itWaterShard] && items[itEarthShard]) {
|
|
items[itFireShard]--;
|
|
items[itAirShard]--;
|
|
items[itWaterShard]--;
|
|
items[itEarthShard]--;
|
|
gainItem(itElemental);
|
|
gainItem(itElemental);
|
|
gainItem(itElemental);
|
|
gainItem(itElemental);
|
|
addMessage(XLAT("You construct some Elemental Gems!", c2->item));
|
|
}
|
|
|
|
if(c2->item == itHyperstone && items[itHyperstone] == 10)
|
|
achievement_victory(true);
|
|
|
|
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);
|
|
|
|
return false;
|
|
}
|
|
|
|
void dropGreenStone(cell *c) {
|
|
if(!passable(c, NULL, true, false, false)) {
|
|
// NOTE: PL/CZ translations assume that itGreenStone is dropped to avoid extra forms!
|
|
addMessage("Cannot drop %the1 here!", itGreenStone);
|
|
return;
|
|
}
|
|
if(items[itGreenStone] && c->item == itNone) {
|
|
items[itGreenStone]--;
|
|
if(false) {
|
|
c->item = itNone;
|
|
spill(c, eWall(c->wall ^ waFloorA ^ waFloorB), 3);
|
|
addMessage(XLAT("The slime reacts with %the1!", itGreenStone));
|
|
}
|
|
else {
|
|
c->item = itGreenStone;
|
|
addMessage(XLAT("You drop %the1.", itGreenStone));
|
|
}
|
|
}
|
|
else {
|
|
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."));
|
|
}
|
|
}
|
|
|
|
void roundTableMessage(cell *c2) {
|
|
if(!euclid && !cwt.c->master->alt) return;
|
|
if(!euclid && !c2->master->alt) return;
|
|
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!"));
|
|
}
|
|
}
|
|
|
|
void knightFlavorMessage(cell *c2) {
|
|
|
|
long long tab[100], sum[100];
|
|
tab[0] = 1;
|
|
tab[1] = 1*7;
|
|
tab[2] = 2*7;
|
|
tab[3] = 4*7;
|
|
tab[4] = 7*7;
|
|
for(int i=5; i<100; i++)
|
|
tab[i] = tab[i-1] + tab[i-2] + tab[i-3] - tab[i-4];
|
|
sum[0] = 0;
|
|
for(int i=1; i<100; i++)
|
|
sum[i] = sum[i-1] + tab[i-1];
|
|
|
|
bool grailfound = grailWasFound(c2);
|
|
int rad = roundTableRadius(c2);
|
|
bool tooeasy = (rad < newRoundTableRadius());
|
|
|
|
static int msgid = 0;
|
|
|
|
retry:
|
|
if(msgid >= 32) msgid = 0;
|
|
|
|
if(msgid == 0 && grailfound) {
|
|
addMessage(XLAT("\"I would like to congratulate you again!\""));
|
|
}
|
|
else if(msgid == 1 && !tooeasy) {
|
|
addMessage(XLAT("\"Find the Holy Grail to become one of us!\""));
|
|
}
|
|
else if(msgid == 2 && !tooeasy) {
|
|
addMessage(XLAT("\"The Holy Grail is in the center of the Round Table.\""));
|
|
}
|
|
else if(msgid == 3) {
|
|
addMessage(XLAT("\"I enjoy watching the hyperbug battles.\""));
|
|
}
|
|
else if(msgid == 4) {
|
|
addMessage(XLAT("\"Have you visited a temple in R'Lyeh?\""));
|
|
}
|
|
else if(msgid == 5) {
|
|
addMessage(XLAT("\"Nice castle, eh?\""));
|
|
}
|
|
else if(msgid == 6 && items[itSpice] < 10) {
|
|
addMessage(XLAT("\"The Red Rock Valley is dangerous, but beautiful.\""));
|
|
}
|
|
else if(msgid == 7 && items[itSpice] < 10) {
|
|
addMessage(XLAT("\"Train in the Desert first!\""));
|
|
}
|
|
else if(msgid == 8) {
|
|
if(rad <= 76)
|
|
addMessage(XLAT("\"Our Table seats %1 Knights!\"", llts(tab[rad])));
|
|
else
|
|
addMessage(XLAT("\"By now, you should have your own formula, you know?\""));
|
|
}
|
|
else if(msgid == 9 && rad <= 76) {
|
|
addMessage(XLAT("\"There are %1 floor tiles inside our Table!\"", llts(sum[rad])));
|
|
}
|
|
else if(msgid == 10 && !items[itPirate] && !items[itWhirlpool]) {
|
|
addMessage(XLAT("\"Have you tried to take a boat and go into the Ocean? Try it!\""));
|
|
}
|
|
else if(msgid == 11 && !princess::saved) {
|
|
addMessage(XLAT("\"When I visited the Palace, a mouse wanted me to go somewhere.\""));
|
|
}
|
|
else if(msgid == 12 && !princess::saved) {
|
|
addMessage(XLAT("\"I wonder what was there...\""));
|
|
}
|
|
else {
|
|
msgid++; goto retry;
|
|
}
|
|
|
|
msgid++;
|
|
}
|
|
|
|
void uncoverMines(cell *c, int lev) {
|
|
if(c->wall == waMineUnknown)
|
|
c->wall = waMineOpen;
|
|
if(!lev) return;
|
|
|
|
if(c->wall == waMineOpen) {
|
|
bool minesNearby = false;
|
|
for(int i=0; i<c->type; i++)
|
|
if(c->mov[i] && c->mov[i]->wall == waMineMine)
|
|
minesNearby = true;
|
|
if(!minesNearby) for(int i=0; i<c->type; i++)
|
|
if(c->mov[i] && (c->mov[i]->wall == waMineUnknown || c->mov[i]->wall == waMineOpen))
|
|
uncoverMines(c->mov[i], lev-1);
|
|
}
|
|
}
|
|
|
|
void monstersTurn() {
|
|
DEBT("bfs");
|
|
bfs();
|
|
destroyStrayMirrors();
|
|
DEBT("heat");
|
|
processheat();
|
|
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();
|
|
}
|
|
|
|
void pushThumper(cell *th, cell *cto) {
|
|
eWall w = th->wall;
|
|
cto->wparam = th->wparam;
|
|
if(th->land == laAlchemist)
|
|
th->wall = (cwt.c->wall == waFloorB || cwt.c->wall == waFloorA) ? cwt.c->wall : cto->wall;
|
|
else th->wall = waNone;
|
|
if(cto->wall == waOpenPlate || cto->wall == waClosePlate) {
|
|
toggleGates(cto, cto->wall, 3);
|
|
addMessage(XLAT("%The1 destroys %the2!", waThumperOn, cto->wall));
|
|
}
|
|
if(cellUnstable(cto) && cto->land == laMotion) {
|
|
addMessage(XLAT("%The1 falls!", waThumperOn));
|
|
}
|
|
else if(cellUnstable(cto)) {
|
|
addMessage(XLAT("%The1 fills the hole!", waThumperOn));
|
|
cto->wall = waTempFloor;
|
|
}
|
|
else if(isWatery(cto)) {
|
|
addMessage(XLAT("%The1 fills the hole!", waThumperOn));
|
|
cto->wall = waTempBridge;
|
|
}
|
|
else
|
|
cto->wall = w;
|
|
}
|
|
|
|
bool canPushThumperOn(cell *tgt, cell *thumper, cell *player) {
|
|
if(isWatery(tgt) && !tgt->monst)
|
|
return true;
|
|
if(tgt->wall == waChasm && !tgt->monst)
|
|
return true;
|
|
return
|
|
passable(tgt, thumper, false, true, false) &&
|
|
passable(tgt, player, false, true, false) &&
|
|
!tgt->item;
|
|
}
|
|
|
|
void activateActiv(cell *c, bool msg) {
|
|
if(msg) addMessage(XLAT("You activate %the1.", c->wall));
|
|
if(c->wall == waThumperOff) c->wall = waThumperOn;
|
|
if(c->wall == waBonfireOff) c->wall = waFire;
|
|
c->wparam = 100;
|
|
}
|
|
|
|
bool movepcto(int d, int subdir, bool checkonly) {
|
|
if(hardcore && !canmove) return false;
|
|
if(hardcore && checkonly) { printf("hardcore check\n"); return false; }
|
|
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;
|
|
bool boatmove = 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(isActivable(c2)) {
|
|
if(checkonly) return true;
|
|
activateActiv(c2, true);
|
|
checkmove();
|
|
return true;
|
|
}
|
|
|
|
if(c2->wall == waThumperOn && !monstersnear(c2) && !c2->monst) {
|
|
cellwalker push = cwt;
|
|
cwstep(push);
|
|
cwspin(push, 3 * -subdir);
|
|
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((!canPushThumperOn(push.c, c2, cwt.c) && c2->type == 7)) {
|
|
cwstep(push);
|
|
cwspin(push, 1 * -subdir);
|
|
cwstep(push);
|
|
}
|
|
if(!canPushThumperOn(push.c, c2, cwt.c)) {
|
|
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));
|
|
pushThumper(c2, push.c);
|
|
}
|
|
|
|
/* 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(isWatery(c2) && !monstersnear(c2) && !c2->monst && cwt.c->wall == waBoat) {
|
|
|
|
if(againstWind(c2, cwt.c)) {
|
|
if(!checkonly)
|
|
addMessage(XLAT("The Air Elemental blows you away!"));
|
|
return false;
|
|
}
|
|
|
|
if(againstCurrent(c2, cwt.c) && !markOrb(itOrbWater)) {
|
|
if(markOrb(itOrbFish)) goto escape;
|
|
if(!checkonly)
|
|
addMessage(XLAT("You cannot go against the current!"));
|
|
return false;
|
|
}
|
|
if(checkonly) return true;
|
|
moveBoat(c2, cwt.c);
|
|
boatmove = true;
|
|
}
|
|
|
|
if(!monstersnear(c2) && !c2->monst && cwt.c->wall == waBoat && boatGoesThrough(c2) && markOrb(itOrbWater)) {
|
|
if(checkonly) return true;
|
|
placeWater(cwt.c, c2);
|
|
c2->wall = waBoat;
|
|
if(cwt.c->item) moveItem(cwt.c, c2, false), boatmove = true;
|
|
}
|
|
|
|
escape:
|
|
if(c2->wall == waBigStatue && !monstersnear(c2) && !c2->monst) {
|
|
if(!canPushStatueOn(cwt.c)) {
|
|
if(checkonly) return false;
|
|
if(isFire(cwt.c))
|
|
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 && !markOrb(itOrbGhost) && !monstersnear(cwt.c)) {
|
|
if(checkNeedMove(checkonly, true)) return false;
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("You start cutting down the tree."));
|
|
gomirrors(0);
|
|
c2->wall = waWetTree;
|
|
}
|
|
else if(c2->wall == waWetTree && !markOrb(itOrbGhost) && !monstersnear(cwt.c)) {
|
|
if(checkNeedMove(checkonly, true)) return false;
|
|
if(checkonly) return true;
|
|
addMessage(XLAT("You cut down the tree."));
|
|
gomirrors(0);
|
|
c2->wall = waNone;
|
|
}
|
|
else if(c2->monst == moKnight) {
|
|
if(checkonly) return false;
|
|
if(!euclid && !c2->master->alt) {
|
|
addMessage(XLAT("\"I am lost...\""));
|
|
return false;
|
|
}
|
|
knightFlavorMessage(c2);
|
|
return false;
|
|
}
|
|
else if(c2->monst && (!isFriendly(c2) || c2->monst == moTameBomberbird)) {
|
|
if(c2->monst == moWorm || c2->monst == moWormtail || c2->monst == moWormwait) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("You cannot attack Sandworms directly!"));
|
|
return false;
|
|
}
|
|
|
|
if(c2->monst == moHexSnake || c2->monst == moHexSnakeTail) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("You cannot attack Rock Snakes 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 || c2->monst == moTentacleEscaping) {
|
|
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 && !markOrb(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 == moVizier && c2->hitpoints > 1) {
|
|
if(checkonly) return false;
|
|
addMessage(XLAT("You cannot attack %the1 directly!", c2->monst));
|
|
addMessage(XLAT("Hit him 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(!(isWatery(cwt.c) && c2->monst == moWaterElemental) && checkNeedMove(checkonly, true))
|
|
return false;
|
|
|
|
if(checkonly) return true;
|
|
|
|
if(isStunnable(c2->monst) && c2->hitpoints > 1) {
|
|
addMessage(XLAT("You stun %the1.", c2->monst));
|
|
stunMonster(c2);
|
|
if(c2->monst != moFatGuard) {
|
|
cellwalker push = cwt;
|
|
cwstep(push);
|
|
cwspin(push, 3 * -subdir);
|
|
cwstep(push);
|
|
if(c2->type == 7 && !blowable(push.c, c2)) {
|
|
cwstep(push);
|
|
cwspin(push, 1 * -subdir);
|
|
cwstep(push);
|
|
}
|
|
if(blowable(push.c, c2))
|
|
pushMonster(push.c, c2);
|
|
}
|
|
}
|
|
else {
|
|
messageKill(moNone, c2->monst);
|
|
killWithMessage(c2);
|
|
}
|
|
|
|
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 if(cwt.c->wall == waRed3 && snakelevel(c2) == 0)
|
|
addMessage(XLAT("You would get hurt!", c2->wall));
|
|
else if(cellEdgeUnstable(cwt.c) && cellEdgeUnstable(c2))
|
|
addMessage(XLAT("Gravity does not allow this!"));
|
|
else if(againstWind(c2, cwt.c))
|
|
addMessage(XLAT("The Air Elemental blows you away!"));
|
|
else {
|
|
addMessage(XLAT("You cannot move through %the1!", c2->wall));
|
|
}
|
|
return false;
|
|
}
|
|
else {
|
|
if(c2->item == itOrbYendor && !boatmove && !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)
|
|
roundTableMessage(c2);
|
|
|
|
invismove = items[itOrbInvis] > 0;
|
|
if(items[itOrbFire]) {
|
|
invismove = false;
|
|
if(makeflame(cwt.c, 10, false)) markOrb(itOrbFire);
|
|
}
|
|
|
|
if(items[itOrbDigging]) {
|
|
invismove = false;
|
|
if(earthMove(cwt.c, d)) markOrb(itOrbDigging);
|
|
}
|
|
|
|
if(!boatmove && collectItem(c2)) return true;
|
|
|
|
if(isIcyLand(cwt.c) && cwt.c->wall == waNone && markOrb(itOrbWinter)) {
|
|
invismove = false;
|
|
cwt.c->wall = waIcewall;
|
|
}
|
|
|
|
movecost(cwt.c, c2);
|
|
|
|
if(c2->monst == moGolem || c2->monst == moIllusion || isPrincess(c2->monst) || c2->monst == moMouse) {
|
|
if(c2->monst == moMouse)
|
|
princess::mouseSqueak(c2);
|
|
else if(isPrincess(c2->monst)) {
|
|
princess::line(c2);
|
|
princess::move(cwt.c, c2);
|
|
}
|
|
else
|
|
addMessage(XLAT("You switch places with %the1.", c2->monst));
|
|
cwt.c->hitpoints = c2->hitpoints;
|
|
cwt.c->stuntime = c2->stuntime;
|
|
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);
|
|
|
|
playerMoveEffects(c2);
|
|
|
|
countLocalTreasure();
|
|
landvisited[cwt.c->land] = true;
|
|
setdist(cwt.c, 0, NULL);
|
|
}
|
|
}
|
|
else {
|
|
if(checkNeedMove(checkonly, false))
|
|
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)
|
|
dropGreenStone(cwt.c);
|
|
}
|
|
|
|
invisfish = false;
|
|
if(items[itOrbFish]) {
|
|
invisfish = true;
|
|
for(int i=0; i<numplayers(); i++)
|
|
if(!isWatery(playerpos(i)))
|
|
invisfish = false;
|
|
if(invisfish) invismove = true, markOrb(itOrbFish);
|
|
}
|
|
monstersTurn();
|
|
|
|
if(items[itWhirlpool] && cwt.c->land != laWhirlpool && !escaped) {
|
|
escaped = true;
|
|
achievement_gain("WHIRL1");
|
|
}
|
|
|
|
if(seenSevenMines && cwt.c->land != laMinefield) {
|
|
seenSevenMines = false;
|
|
achievement_gain("SEVENMINE");
|
|
}
|
|
|
|
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);
|
|
playerMoveEffects(dest);
|
|
cwt.c = dest; cwt.spin = hrand(dest->type); flipplayer = !!(hrand(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();
|
|
|
|
if(shmup::on)
|
|
shmup::teleported();
|
|
else
|
|
checkmove();
|
|
}
|
|
|
|
void jumpTo(cell *dest) {
|
|
movecost(cwt.c, dest);
|
|
|
|
cwt.c = dest;
|
|
|
|
playerMoveEffects(dest);
|
|
if(cwt.c->item != itOrbYendor && cwt.c->item != itHolyGrail)
|
|
collectItem(cwt.c, true);
|
|
|
|
items[itOrbFrog] -= 5;
|
|
if(items[itOrbFrog] < 0) items[itOrbFrog] = 0;
|
|
|
|
addMessage(XLAT("You jump!"));
|
|
destroyMirrors();
|
|
|
|
for(int i=9; i>=0; i--)
|
|
setdist(cwt.c, i, NULL);
|
|
|
|
if(shmup::on)
|
|
shmup::teleported();
|
|
else
|
|
monstersTurn();
|
|
}
|
|
|
|
void moveItem1(cell *from, cell *to, bool activateYendor) {
|
|
if(from->item == itOrbYendor) {
|
|
bool xnew = true;
|
|
for(int i=0; i<size(yi); i++)
|
|
if(yi[i].path[0] == from) xnew = false;
|
|
if(xnew && activateYendor) checkYendor(from, false);
|
|
for(int i=0; i<size(yi); i++)
|
|
if(yi[i].path[0] == from)
|
|
yi[i].path[0] = to;
|
|
}
|
|
|
|
if(from->item == itKey) {
|
|
for(int i=0; i<size(yi); i++) if(yi[i].path[YDIST-1] == from)
|
|
yi[i].path[YDIST-1] = to;
|
|
}
|
|
}
|
|
|
|
void moveItem (cell *from, cell *to, bool activateYendor) {
|
|
moveItem1(from, to, activateYendor);
|
|
moveItem1(to, from, activateYendor);
|
|
eItem i = to->item;
|
|
to->item = from->item;
|
|
from->item = i;
|
|
}
|
|
|
|
void telekinesis(cell *dest) {
|
|
|
|
int cost = dest->cpdist * dest->cpdist;
|
|
|
|
if(dest->land == laAlchemist &&
|
|
(dest->wall == waFloorA || dest->wall == waFloorB) &&
|
|
(cwt.c->wall == waFloorA || cwt.c->wall == waFloorB))
|
|
dest->wall = cwt.c->wall;
|
|
|
|
if(dest->land == laPower && cwt.c->land != laPower && dest->item != itOrbFire && dest->item != itOrbLife) {
|
|
items[dest->item] += 2;
|
|
dest->item = itNone;
|
|
addMessage(XLAT("The Orb loses its power as it leaves the Land of Power!"));
|
|
}
|
|
|
|
moveItem(dest, cwt.c, true);
|
|
collectItem(cwt.c, true);
|
|
items[itOrbTelekinesis] -= cost;
|
|
|
|
if(!shmup::on) checkmove();
|
|
}
|
|
|
|
eMonster summonedAt(cell *dest) {
|
|
if(dest->monst) return moNone;
|
|
if(dest->wall == waVineHalfA || dest->wall == waVineHalfB || dest->wall == waVinePlant)
|
|
return moVineSpirit;
|
|
if(dest->wall == waCTree)
|
|
return moParrot;
|
|
if(dest->wall == waLake)
|
|
return moGreaterShark;
|
|
if(dest->wall == waAncientGrave || dest->wall == waFreshGrave)
|
|
return moGhost;
|
|
if(dest->wall == waClosePlate || dest->wall == waOpenPlate)
|
|
return moPalace;
|
|
if(dest->wall == waFloorA || dest->wall == waFloorB)
|
|
return moSlime;
|
|
if(dest->wall == waCavefloor)
|
|
return moTroll;
|
|
if(dest->wall == waDeadfloor)
|
|
return moEarthElemental;
|
|
if(dest->wall == waDeadfloor2)
|
|
return moMiner;
|
|
if(dest->wall == waMineOpen || dest->wall == waMineMine || dest->wall == waMineUnknown)
|
|
return moBomberbird;
|
|
if(dest->wall == waTrapdoor)
|
|
return dest->land == laPalace ? moFatGuard : moOrangeDog;
|
|
if(dest->wall == waSea)
|
|
return
|
|
isElemental(dest->land) ? moWaterElemental :
|
|
dest->land == laLivefjord ? moViking :
|
|
moPirate;
|
|
if(dest->wall == waChasm)
|
|
return moAirElemental;
|
|
if(isFire(dest))
|
|
return moFireElemental;
|
|
if(dest->wall == waCavewall || dest->wall == waDeadwall)
|
|
return moSeep;
|
|
if(dest->wall == waRed1 || dest->wall == waRed2 || dest->wall == waRed3)
|
|
return moRedTroll;
|
|
if(dest->wall == waFrozenLake)
|
|
return moFireElemental;
|
|
if(dest->wall == waCIsland || dest->wall == waCIsland2)
|
|
return moWaterElemental;
|
|
if(dest->wall == waRubble || dest->wall == waGargoyleFloor || dest->wall == waGargoyleBridge || dest->wall == waLadder)
|
|
return moGargoyle;
|
|
if(dest->wall == waStrandedBoat)
|
|
return moWaterElemental;
|
|
if(dest->wall == waBoat)
|
|
return moAirElemental;
|
|
if(dest->wall == waStone)
|
|
return moEarthElemental;
|
|
if(dest->wall == waGiantRug)
|
|
return moVizier;
|
|
if(dest->wall == waNone) {
|
|
if(dest->land == laIce) return moFireElemental;
|
|
if(dest->land == laDesert) return moEarthElemental;
|
|
if(dest->land == laJungle) return moWaterElemental;
|
|
if(dest->land == laGraveyard) return moZombie;
|
|
if(dest->land == laRlyeh || dest->land == laTemple) return moPyroCultist;
|
|
if(dest->land == laHell) return moWaterElemental;
|
|
if(dest->land == laPower) return moWitchFire;
|
|
if(dest->land == laWineyard) return moVineBeast;
|
|
if(dest->land == laEmerald) return moMiner;
|
|
if(dest->land == laHive) return dest->type == 7 ? moBug1 : moBug0;
|
|
if(dest->land == laRedRock) return moRedTroll;
|
|
if(dest->land == laOcean) return moEarthElemental;
|
|
if(dest->land == laDryForest) return moFireFairy;
|
|
if(dest->land == laLivefjord) return moFjordTroll;
|
|
if(dest->land == laEdge) return moAirElemental;
|
|
if(dest->land == laEAir) return moAirElemental;
|
|
if(dest->land == laEWater) return moWaterElemental;
|
|
if(dest->land == laEEarth) return moEarthElemental;
|
|
if(dest->land == laEFire) return moFireElemental;
|
|
if(dest->land == laMotion) return moRunDog;
|
|
}
|
|
return moNone;
|
|
}
|
|
|
|
void summonAt(cell *dest) {
|
|
dest->monst = summonedAt(dest);
|
|
dest->stuntime = 3;
|
|
if(dest->monst == moPirate || dest->monst == moViking)
|
|
dest->wall = waBoat;
|
|
if(dest->wall == waStrandedBoat)
|
|
dest->wall = waBoat;
|
|
else if(dest->monst == moWaterElemental)
|
|
placeWater(dest, dest);
|
|
if(dest->wall == waStone)
|
|
dest->wall = waNone;
|
|
if(dest->monst == moFireElemental && isFire(dest))
|
|
dest->wall = waNone;
|
|
addMessage(XLAT("You summon %the1!", dest->monst));
|
|
moveEffect(dest, dest, dest->monst);
|
|
|
|
if(hasHitpoints(dest->monst))
|
|
dest->hitpoints = palaceHP();
|
|
|
|
items[itOrbSummon] -= 20;
|
|
if(items[itOrbSummon]<0) items[itOrbSummon] = 0;
|
|
checkmove();
|
|
}
|
|
|
|
bool tempWallPossibleAt(cell *dest) {
|
|
if(dest->monst || (dest->item && !itemHidden(dest))) return false;
|
|
return dest->wall == waChasm || isWatery(dest) || dest->wall == waNone;
|
|
}
|
|
|
|
void tempWallAt(cell *dest) {
|
|
if(dest->wall == waChasm)
|
|
dest->wall = waTempFloor;
|
|
else if(dest->wall == waNone)
|
|
dest->wall = waTempWall;
|
|
else if(isWatery(dest))
|
|
dest->wall = waTempBridge;
|
|
dest->wparam = (items[itOrbMatter]+1) / 2;
|
|
items[itOrbMatter] /= 2;
|
|
dest->item = itNone; // underwater items are destroyed by this
|
|
checkmove();
|
|
}
|
|
|
|
void psi_attack(cell *dest) {
|
|
if(isNonliving(dest->monst))
|
|
addMessage(XLAT("You destroy %the1 with a mental blast!", dest->monst));
|
|
else
|
|
addMessage(XLAT("You kill %the1 with a mental blast!", dest->monst));
|
|
killWithMessage(dest, false);
|
|
items[itOrbPsi] -= 30;
|
|
if(items[itOrbPsi]<0) items[itOrbPsi] = 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();
|
|
}
|
|
|
|
void blowoff(cell *cf, cell *ct) {
|
|
addMessage(XLAT("You blow %the1 away!", cf->monst));
|
|
pushMonster(ct, cf);
|
|
items[itOrbAir]--;
|
|
checkmove();
|
|
}
|
|
|
|
void useOrbOfDragon(cell *c) {
|
|
makeflame(c, 20, false);
|
|
addMessage(XLAT("You throw fire!"));
|
|
items[itOrbDragon] -= 5;
|
|
if(items[itOrbDragon]<0) items[itOrbDragon] = 0;
|
|
checkmove();
|
|
}
|
|
|
|
|
|
eItem targetRangedOrb(cell *c, orbAction a) {
|
|
|
|
if(false) if(a == roMouse || a == roMouseForce) {
|
|
c->item = itShard;
|
|
cell *c2 = c;
|
|
cell *c3 = c;
|
|
cell *c4 = chosenDown(c, 1, 1);
|
|
cell *c5 = chosenDown(c, -1, -1);
|
|
for(int i=0; i<3; i++) {
|
|
printf("i=%d c2=%p c3=%p c4=%p c5=%p\n", i, c2, c3, c4, c5);
|
|
c2->item = itDiamond;
|
|
c3->item = itRuby;
|
|
c4->item = itFjord;
|
|
c5->item = itSapphire;
|
|
buildEquidistant(c4); buildEquidistant(c5);
|
|
c2 = chosenDown(c2, 1, 0);
|
|
c3 = chosenDown(c3, -1, 0);
|
|
c4 = chosenDown(c4, 1, 0);
|
|
c5 = chosenDown(c5, -1, 0);
|
|
if(c4->landflags == 3 && c4 == chosenDown(c2, 1, 1)) c->wall = waCIsland;
|
|
if(c5->landflags == 3 && c5 == chosenDown(c3, -1, -1)) c->wall = waCIsland;
|
|
}
|
|
return itOrbDiscord;
|
|
}
|
|
|
|
if(!haveRangedOrb()) return itNone;
|
|
|
|
// (-2) shmup variants
|
|
eItem shmupEffect = shmup::targetRangedOrb(a);
|
|
|
|
if(shmupEffect) return shmupEffect;
|
|
|
|
// (-1) distance
|
|
|
|
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;
|
|
}
|
|
|
|
// (0) telekinesis
|
|
if(c->item && !itemHidden(c) && !cwt.c->item && items[itOrbTelekinesis] >= c->cpdist * c->cpdist && !cantGetGrimoire(c, a != roCheck)) {
|
|
if(a != roCheck) telekinesis(c);
|
|
return itOrbTelekinesis;
|
|
}
|
|
|
|
// (0') air blow
|
|
bool nowhereToBlow = false;
|
|
if(items[itOrbAir] && isBlowableMonster(c->monst)) {
|
|
int d = 0;
|
|
for(; d<c->type; d++) if(c->mov[d] && c->mov[d]->cpdist < c->cpdist) break;
|
|
if(d<c->type) for(int e=d; e<d+c->type; e++) {
|
|
nowhereToBlow = true;
|
|
cell *c2 = c->mov[e % c->type];
|
|
if(c2 && c2->cpdist > c->cpdist && blowable(c2, c)) {
|
|
if(a != roCheck) blowoff(c, c2);
|
|
return itOrbAir;
|
|
}
|
|
}
|
|
}
|
|
|
|
// (0'') jump
|
|
int jumpstate = 0;
|
|
cell *jumpthru = NULL;
|
|
|
|
if(items[itOrbFrog] && c->cpdist == 2) {
|
|
jumpstate = 1;
|
|
int i = items[itOrbGhost];
|
|
bool b = markOrb(itOrbGhost);
|
|
if(i) items[itOrbGhost] = i-1;
|
|
for(int i=0; i<cwt.c->type; i++) {
|
|
cell *c2 = cwt.c->mov[i];
|
|
if(isNeighbor(c2, c)) {
|
|
jumpthru = c2;
|
|
if((b || jumpable(c2, c)) && !thruVine(c2,cwt.c)) {
|
|
jumpstate = 2;
|
|
if(jumpable2(c, c2) && !thruVine(c2,c)) {
|
|
jumpstate = 3;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
items[itOrbGhost] = i;
|
|
if(jumpstate == 3 && !monstersnear(c)) {
|
|
jumpstate = 4;
|
|
if(a != roCheck) jumpTo(c);
|
|
return itOrbFrog;
|
|
}
|
|
}
|
|
|
|
// (1) switch with an illusion
|
|
if(items[itOrbTeleport] && c->monst == moIllusion) {
|
|
if(a != roCheck) teleportTo(c);
|
|
return itOrbTeleport;
|
|
}
|
|
|
|
// (2) place illusion
|
|
if(!shmup::on && items[itOrbIllusion] && c->monst == moNone && c->item == itNone && passable(c, NULL, false, true, false)) {
|
|
if(a != roCheck) placeIllusion(c);
|
|
return itOrbIllusion;
|
|
}
|
|
|
|
// (3) teleport
|
|
if(items[itOrbTeleport] && c->monst == moNone && (c->item == itNone || itemHidden(c)) &&
|
|
player_passable(c, NULL, false)) {
|
|
if(a != roCheck) teleportTo(c);
|
|
return itOrbTeleport;
|
|
}
|
|
|
|
// (4) remove an illusion
|
|
if(!shmup::on && 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 (non-shmup variant)
|
|
if(!shmup::on && items[itOrbPsi] && c->monst && !isWorm(c) && c->monst != moShadow) {
|
|
if(a != roCheck) psi_attack(c);
|
|
return itOrbPsi;
|
|
}
|
|
|
|
// (5a) summoning
|
|
if(items[itOrbSummon] && summonedAt(c)) {
|
|
if(a != roCheck) summonAt(c);
|
|
return itOrbSummon;
|
|
}
|
|
|
|
// (5b) matter
|
|
if(items[itOrbMatter] && tempWallPossibleAt(c)) {
|
|
if(a != roCheck) tempWallAt(c);
|
|
return itOrbMatter;
|
|
}
|
|
|
|
// (6) place fire (non-shmup variant)
|
|
if(!shmup::on && items[itOrbDragon] && makeflame(c, 20, true)) {
|
|
if(a != roCheck) useOrbOfDragon(c);
|
|
return itOrbDragon;
|
|
}
|
|
|
|
if(a == roCheck) return itNone;
|
|
|
|
if(nowhereToBlow) {
|
|
addMessage(XLAT("Nowhere to blow %the1!", c->monst));
|
|
}
|
|
else if(jumpstate == 1 && jumpthru && jumpthru->monst) {
|
|
addMessage(XLAT("Cannot jump through %the1!", jumpthru->monst));
|
|
}
|
|
else if(jumpstate == 1 && jumpthru) {
|
|
addMessage(XLAT("Cannot jump through %the1!", jumpthru->wall));
|
|
}
|
|
else if(jumpstate == 2 && c->monst) {
|
|
addMessage(XLAT("Cannot jump on %the1!", c->monst));
|
|
}
|
|
else if(jumpstate == 2 && c->wall) {
|
|
addMessage(XLAT("Cannot jump on %the1!", c->wall));
|
|
}
|
|
else if(jumpstate == 3) {
|
|
addMessage(XLAT("%The1 would get you there!", which));
|
|
}
|
|
else if(items[itOrbAir] && c->monst) {
|
|
addMessage(XLAT("%The1 is immune to wind!", c->monst));
|
|
}
|
|
else 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(c->item && items[itOrbTelekinesis]) {
|
|
addMessage(XLAT("Not enough power for telekinesis!"));
|
|
}
|
|
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, false))
|
|
addMessage(XLAT("Cannot cast illusion here!"));
|
|
else if(items[itOrbTeleport] && !player_passable(c, NULL, false)) {
|
|
addMessage(XLAT("Cannot teleport here!"));
|
|
}
|
|
else if(items[itOrbMatter] && !tempWallPossibleAt(c)) {
|
|
if(c->monst)
|
|
addMessage(XLAT("Cannot create temporary matter on a monster!"));
|
|
else if(c->item)
|
|
addMessage(XLAT("Cannot create temporary matter on an item!"));
|
|
else addMessage(XLAT("Cannot create temporary matter here!"));
|
|
}
|
|
else if(items[itOrbSummon] && !summonedAt(c)) {
|
|
if(c->monst)
|
|
addMessage(XLAT("Cannot summon on a monster!"));
|
|
else
|
|
addMessage(XLAT("No summoning possible here!"));
|
|
}
|
|
else if(items[itOrbTeleport] && c->item) {
|
|
addMessage(XLAT("Cannot teleport on an item!"));
|
|
}
|
|
else if(items[itOrbDragon] && !makeflame(c, 20, true)) {
|
|
addMessage(XLAT("Cannot throw fire there!"));
|
|
}
|
|
else return eItem(0);
|
|
|
|
return eItem(-1);
|
|
}
|
|
|
|
#define MAXBOX 180
|
|
#define POSSCORE 161 // update this when new boxes are added!
|
|
|
|
struct score {
|
|
string ver;
|
|
int box[MAXBOX];
|
|
};
|
|
|
|
int savebox[MAXBOX], boxid;
|
|
bool saving, loading, loadingHi;
|
|
|
|
string boxname[MAXBOX];
|
|
bool fakebox[MAXBOX];
|
|
|
|
void applyBox(int& t) {
|
|
if(saving) savebox[boxid++] = t;
|
|
else if(loading) t = savebox[boxid++];
|
|
else boxid++;
|
|
}
|
|
|
|
void applyBoxNum(int& i, string name = "") {
|
|
fakebox[boxid] = (name == "");
|
|
boxname[boxid] = name;
|
|
applyBox(i);
|
|
}
|
|
|
|
void applyBoxBool(bool& b, string name = "") {
|
|
int i = b;
|
|
applyBoxNum(i, name);
|
|
b = 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;
|
|
if(loadingHi) {
|
|
updateHi(it, savebox[boxid++]);
|
|
}
|
|
else applyBox(items[it]);
|
|
}
|
|
|
|
void applyBoxM(eMonster m, bool f = false) {
|
|
fakebox[boxid] = f;
|
|
boxname[boxid] = minf[m].name;
|
|
applyBox(kills[m]);
|
|
}
|
|
|
|
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++) {
|
|
if(loading) 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 if(loading) savetime = applyBoxLoad();
|
|
else boxid++;
|
|
|
|
savecount++; applyBoxNum(savecount, "number of saves"); savecount--;
|
|
applyBoxNum(cheater);
|
|
|
|
if(saving) applyBoxSave(cwt.c->land);
|
|
else if(loading) firstland = eLand(applyBoxLoad());
|
|
else boxid++;
|
|
|
|
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);
|
|
|
|
applyBoxM(moPirate);
|
|
applyBoxM(moCShark);
|
|
applyBoxM(moParrot);
|
|
applyBoxI(itPirate);
|
|
applyBoxI(itOrbPreserve, true);
|
|
|
|
applyBoxM(moHexSnake);
|
|
applyBoxM(moRedTroll);
|
|
applyBoxI(itRedGem);
|
|
applyBoxI(itOrbTelekinesis, true);
|
|
|
|
applyBoxBool(euclid);
|
|
applyBoxBool(hardcore);
|
|
applyBoxNum(hardcoreAt);
|
|
applyBoxBool(shmup::on);
|
|
if(saving) applyBoxSave(euclidland);
|
|
else if(loading) euclidland = eLand(applyBoxLoad());
|
|
else boxid++;
|
|
|
|
applyBoxI(itCoast);
|
|
applyBoxI(itWhirlpool);
|
|
applyBoxI(itBombEgg);
|
|
applyBoxM(moBomberbird);
|
|
applyBoxM(moTameBomberbird);
|
|
applyBoxM(moAlbatross);
|
|
applyBoxI(itOrbFriend, true);
|
|
applyBoxI(itOrbAir, true);
|
|
applyBoxI(itOrbWater, true);
|
|
|
|
applyBoxI(itPalace);
|
|
applyBoxI(itFjord);
|
|
applyBoxI(itOrbFrog);
|
|
applyBoxI(itOrbDiscord);
|
|
applyBoxM(moPalace);
|
|
applyBoxM(moFatGuard);
|
|
applyBoxM(moSkeleton);
|
|
applyBoxM(moVizier);
|
|
applyBoxM(moViking);
|
|
applyBoxM(moFjordTroll);
|
|
applyBoxM(moWaterElemental);
|
|
|
|
applyBoxI(itSavedPrincess);
|
|
applyBoxI(itOrbLove);
|
|
applyBoxM(moPrincess);
|
|
applyBoxM(moPrincessMoved); // live Princess for Safety
|
|
applyBoxM(moPrincessArmedMoved); // live Princess for Safety
|
|
applyBoxM(moMouse);
|
|
applyBoxNum(princess::saveArmedHP);
|
|
applyBoxNum(princess::saveHP);
|
|
|
|
applyBoxI(itEdge);
|
|
applyBoxI(itElemental);
|
|
applyBoxI(itZebra);
|
|
applyBoxI(itFireShard);
|
|
applyBoxI(itWaterShard);
|
|
applyBoxI(itAirShard);
|
|
applyBoxI(itEarthShard);
|
|
|
|
applyBoxM(moAirElemental);
|
|
applyBoxM(moFireElemental);
|
|
applyBoxM(moEdgeMonkey);
|
|
applyBoxM(moGargoyle);
|
|
applyBoxM(moOrangeDog);
|
|
applyBoxI(itOrbSummon);
|
|
applyBoxI(itOrbMatter);
|
|
}
|
|
|
|
void saveBox() {
|
|
boxid = 0; saving = true; applyBoxes(); saving = false;
|
|
}
|
|
|
|
void loadBox() {
|
|
// have boxid
|
|
boxid = 0; loading = true; applyBoxes(); loading = false;
|
|
}
|
|
|
|
void loadBoxHigh() {
|
|
// have boxid
|
|
boxid = 0; loadingHi = true; applyBoxes(); loadingHi = false;
|
|
}
|
|
|
|
// 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() {
|
|
#ifndef ANDROID
|
|
|
|
if(randomPatternsMode) return;
|
|
|
|
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);
|
|
if(!shmup::on) items[itOrbLife] = countMyGolems(moGolem);
|
|
if(!shmup::on) items[itOrbFriend] = countMyGolems(moTameBomberbird);
|
|
if(!shmup::on) kills[moPrincessMoved] = countMyGolems(moPrincess);
|
|
if(!shmup::on) kills[moPrincessArmedMoved] = countMyGolems(moPrincessArmed);
|
|
if(!shmup::on) princess::saveHP = countMyGolemsHP(moPrincess);
|
|
if(!shmup::on) princess::saveArmedHP = countMyGolemsHP(moPrincessArmed);
|
|
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 %s)\n", sbuf, buf, turncount,
|
|
shmup::on ? "knives" : "turns");
|
|
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);
|
|
|
|
for(int i=0; i<boxid; i++) savebox[i] = sc.box[i];
|
|
for(int i=boxid; i<MAXBOX; i++) savebox[i] = 0;
|
|
|
|
// Euclidean and Shmup highscores do not count for hiitems
|
|
if(savebox[116] || savebox[119]) break;
|
|
|
|
loadBoxHigh();
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
if(buf[0] == 'A' && buf[1] == 'C' && buf[2] == 'H') {
|
|
char buf1[80], buf2[80];
|
|
sscanf(buf, "%70s%70s", buf1, buf2);
|
|
if(buf2 == string("PRINCESS1")) princess::everSaved = true;
|
|
if(buf2 == string("YENDOR2")) princess::everGotYendorVictory = true;
|
|
}
|
|
}
|
|
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(char switchWhat = 0) {
|
|
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;
|
|
for(int i=0; i<10; i++) for(int l=0; l<landtypes; l++)
|
|
exploreland[i][l] = 0;
|
|
tampered = false; achievementsReceived.clear();
|
|
princess::saved = false;
|
|
princess::forceVizier = false;
|
|
princess::forceMouse = false;
|
|
knighted = 0;
|
|
// items[itGreenStone] = 100;
|
|
cellcount = 0;
|
|
DEB("clearmem");
|
|
clearMemory();
|
|
if(switchWhat == 'e') euclid = !euclid;
|
|
if(switchWhat == 's') shmup::on = !shmup::on;
|
|
if(switchWhat == 'p') {
|
|
princess::challenge = !princess::challenge;
|
|
firstland = princess::challenge ? laPalace : laIce;
|
|
if(shmup::on) shmup::on = false;
|
|
}
|
|
DEB("initc");
|
|
initcells();
|
|
DEB("initg");
|
|
|
|
if(randomPatternsMode) {
|
|
for(int i=0; i<landtypes; i++)
|
|
randompattern[i] = rand();
|
|
if(randomPatternsMode) firstland = pickLandRPM(laNone);
|
|
clearMemoRPM();
|
|
}
|
|
|
|
initgame();
|
|
canmove = true;
|
|
DEB("restg");
|
|
restartGraph();
|
|
resetmusic();
|
|
resetmusic();
|
|
}
|
|
|
|
void clearGameMemory() {
|
|
pathq.clear();
|
|
dcal.clear();
|
|
yii = 0; yi.clear();
|
|
clearshadow();
|
|
offscreen.clear();
|
|
princess::clear();
|
|
seenSevenMines = false;
|
|
}
|
|
|
|
bool emeraldwalled[64];
|
|
|
|
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(hrand(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(hrand(cq) == 0) best = i; }
|
|
}
|
|
return best;
|
|
}
|
|
|
|
bool 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);
|
|
return true;
|
|
}
|
|
if(u == 'G') {
|
|
addMessage(XLAT("You summon a golem!"));
|
|
cheater++;
|
|
int i = cwt.spin;
|
|
if(passable(cwt.c->mov[i], NULL, false, false, false))
|
|
cwt.c->mov[i]->monst = hrand(2) ? moGolem : moTameBomberbird;
|
|
return true;
|
|
}
|
|
if(u == 'L') {
|
|
do {
|
|
if(firstland == eLand(landtypes-1))
|
|
firstland = eLand(2);
|
|
else
|
|
firstland = eLand(firstland+1);
|
|
}
|
|
while(firstland == laCanvas || firstland == laCamelot || firstland == laTemple ||
|
|
firstland == laWhirlpool || firstland == laOceanWall);
|
|
euclidland = firstland;
|
|
cheater++; addMessage(XLAT("You will now start your games in %1", firstland));
|
|
return true;
|
|
}
|
|
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;
|
|
return true;
|
|
}
|
|
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!"));
|
|
return true;
|
|
}
|
|
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, false)) {
|
|
eItem it = nextOrb();
|
|
cwt.c->mov[i]->item = it;
|
|
}
|
|
return true;
|
|
}
|
|
if(u == 'F') {
|
|
items[itOrbFlash] += 1;
|
|
items[itOrbTeleport] += 1;
|
|
items[itOrbLightning] += 1;
|
|
items[itOrbSpeed] += 1;
|
|
items[itOrbShield] += 1;
|
|
cheater++; addMessage(XLAT("Orb power gained!"));
|
|
return true;
|
|
}
|
|
if(u == 'D') {
|
|
items[itGreenStone] += 10;
|
|
cheater++; addMessage(XLAT("Dead orbs gained!"));
|
|
return true;
|
|
}
|
|
if(u == 'D'-64) {
|
|
mapeditor::drawplayer = !mapeditor::drawplayer;
|
|
return true;
|
|
}
|
|
if(u == 'A') {
|
|
cmode = emMapEditor;
|
|
return true;
|
|
}
|
|
if(u == 'A'-64) {
|
|
cmode = emDraw;
|
|
return true;
|
|
}
|
|
if(u == 'Y') {
|
|
items[itOrbYendor] ++;
|
|
cheater++; addMessage(XLAT("Orb of Yendor gained!"));
|
|
return true;
|
|
}
|
|
if(u == 'T') {
|
|
items[randomTreasure2(10)] += 10;
|
|
cheater++; addMessage(XLAT("Treasure gained!"));
|
|
return true;
|
|
}
|
|
if(u == 'T'-64) {
|
|
items[randomTreasure2(100)] += 100;
|
|
cheater++; addMessage(XLAT("Lots of treasure gained!"));
|
|
return true;
|
|
}
|
|
if(u == 'I'-64) {
|
|
items[randomTreasure2(10)] += 25;
|
|
cheater++; addMessage(XLAT("Treasure gained!"));
|
|
return true;
|
|
}
|
|
if(u == 'U'-64) {
|
|
items[randomTreasure2(10)] += 50;
|
|
cheater++; addMessage(XLAT("Treasure gained!"));
|
|
return true;
|
|
}
|
|
if(u == 'W') {
|
|
addMessage(XLAT("You summon a sandworm!"));
|
|
cheater++;
|
|
int i = cwt.spin;
|
|
if(passable(cwt.c->mov[i], NULL, false, false, false))
|
|
cwt.c->mov[i]->monst = moWorm,
|
|
cwt.c->mov[i]->mondir = NODIR;
|
|
return true;
|
|
}
|
|
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, false)) buildIvy(c, 0, 1);
|
|
return true;
|
|
}
|
|
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, false)) {
|
|
eMonster mo[9] = { moEagle, moPyroCultist, moGhost, moTroll, moMiner, moVineBeast, moBug0,
|
|
moBomberbird, moSkeleton };
|
|
cwt.c->mov[i]->monst = mo[hrand(9)];
|
|
cwt.c->mov[i]->stuntime = 3;
|
|
cwt.c->mov[i]->hitpoints = 3;
|
|
}
|
|
return true;
|
|
}
|
|
if(u == 'E'-64) {
|
|
addMessage(XLAT("You summon many monsters!"));
|
|
cheater++;
|
|
for(int i=0; i<cwt.c->type; i++) {
|
|
cell *c2 = cwt.c->mov[i];
|
|
if(passable(c2, NULL, true, false, false)) {
|
|
eMonster mo[2] = { moRedTroll, moDarkTroll };
|
|
c2->monst = mo[hrand(2)];
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
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, false)) {
|
|
eWall ws[3] = { waThumperOff, waBigStatue, waBoat };
|
|
cwt.c->mov[i]->wall = ws[hrand(3)];
|
|
}
|
|
return true;
|
|
}
|
|
if(u == 'B') {
|
|
addMessage(XLAT("You summon a bonfire!"));
|
|
cheater++;
|
|
int i = cwt.spin;
|
|
if(passable(cwt.c->mov[i], NULL, false, false, false))
|
|
cwt.c->mov[i]->wall = waBonfireOff;
|
|
return true;
|
|
}
|
|
if(u == 'Z') {
|
|
cwt.spin++; flipplayer = false;
|
|
cwt.spin %= cwt.c->type;
|
|
return true;
|
|
}
|
|
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!"));
|
|
return true;
|
|
}
|
|
if(u == 'K') {
|
|
for(int i=0; i<motypes; i++) kills[i] += 10;
|
|
cheater++; addMessage(XLAT("Kills gained!"));
|
|
return true;
|
|
}
|
|
if(u == 'S') {
|
|
activateSafety(cwt.c->land);
|
|
items[itOrbSafety] += 3;
|
|
cheater++; addMessage(XLAT("Activated Orb of Safety!"));
|
|
return true;
|
|
}
|
|
if(u == 'U') {
|
|
activateSafety(firstland);
|
|
cheater++; addMessage(XLAT("Teleported to %1!", firstland));
|
|
return true;
|
|
}
|
|
if(u == 'W'-64) {
|
|
webdisplay++;
|
|
cheater++; addMessage(XLAT("Cheat-changed the display.", firstland));
|
|
return true;
|
|
}
|
|
if(u == 'F'-64) {
|
|
items[itOrbShield] += 30;
|
|
return true;
|
|
}
|
|
if(u == 'Y'-64) {
|
|
int i = cwt.spin;
|
|
cwt.c->mov[i]->item = itOrbYendor;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
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, int special) {
|
|
|
|
// 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 < 0) return NULL;
|
|
|
|
// check for non-crossing
|
|
int bd = 2;
|
|
cellwalker bb(c, bd);
|
|
if(!(checkBarriersFront(bb) && checkBarriersBack(bb))) {
|
|
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 + hrand(2));
|
|
cwstep(bf);
|
|
}
|
|
cx[rad] = bf.c;
|
|
heptagon *h = bf.c->master;
|
|
|
|
if(special == waPalace) {
|
|
|
|
// type 7 is ensured
|
|
cell *c = bf.c;
|
|
|
|
if(cdist50(c) != 0) return NULL;
|
|
if(polarb50(c) != 1) return NULL;
|
|
}
|
|
|
|
heptagon *alt = new heptagon;
|
|
allAlts.push_back(alt);
|
|
//printf("new alt {%p}\n", alt);
|
|
alt->s = firststate;
|
|
alt->emeraldval = 0;
|
|
alt->zebraval = 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;
|
|
}
|
|
|
|
if(special == waPalace) {
|
|
|
|
cell *c = bf.c;
|
|
|
|
princess::generating = true;
|
|
c->land = laPalace;
|
|
for(int j=BARLEV; j>=0; j--)
|
|
setdist(c, j, NULL);
|
|
princess::generating = false;
|
|
|
|
princess::newInfo(c);
|
|
princess::forceMouse = false;
|
|
|
|
// static bool debug=true; if(debug) debug=false, cwt.c = c;
|
|
}
|
|
|
|
return alt;
|
|
//for(int d=rad; d>=0; d--) printf("%3d. %p {%d}\n", d, cx[d]->master, cx[d]->master->alt->distance);
|
|
}
|
|
|