// 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 dcal; // queue for cpdist vector pathq; // queue for pathdist vector offscreen; // offscreen cells to take care off vector pathqm; // list of monsters to move (pathq restriced to monsters) vector targets; // list of monster targets // monsters of specific types to move vector worms, ivies, ghosts, golems, mirrors, mirrors2, hexsnakes; vector temps; // temporary changes during bfs vector tempval; // restore temps // a bit nicer way of DFS vector 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 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= 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; iwall == waBoat || c->wall == waStrandedBoat); for(int i=0; iwall == 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; iland == 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= 10) i++; return i; } bool hellUnlocked() { return orbsUnlocked() >= 9; } void countHyperstoneQuest(int& i1, int& i2) { i1 = 0; i2 = 0; for(int t=1; 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 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; dtype; 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; itype; i++) { cell *c2 = c->mov[i]; if(c2 && c2->monst == moAirElemental) { airdir = c->spn[i] * 42 / c2->type; return 1; } } for(int i=0; itype; i++) { cell *c2 = c->mov[i]; if(!c2) continue; if(!blowable(c2, c)) continue; if(!blowable(c, c2)) continue; for(int i=0; itype; 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; itype; 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; ttype; 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; ttype; 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; itype; i++) if(c1->mov[i] == c2) return true; return false; } int neighborId(cell *c1, cell *c2) { for(int i=0; itype; 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; ttype; 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; ttype; t++) { cell *c2 = c->mov[t]; for(int u=0; utype; 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; ttype; 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; 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; imonst == 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; itype; 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 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; iprincess == 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; imonst = moPrincessArmed; c->stuntime = 0; c->hitpoints = palaceHP(); drawFlash(c); info *inf = NULL; for(int i=0; iprincess && 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; iprincess = 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; i++) { cell *c2 = c->mov[i]; if(c2) for(int j=0; jtype; 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; itype; 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; ttype; 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= 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= 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= 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= 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= 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; itype; i++) describeCell(c->mov[i]); z: c->item = itPirate; for(int i=0; itype; 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; i++) { cell *c2 = createMov(c, i); if(c2 && c2->landparam) { for(int j=0; jtype; 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; dtype; 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; itype; i++) describeCell(c2->mov[i]); // halted = true; // c->heat = -1; c2->item = itCompass; return; } } } if(c->landparam) { // for(int i=0; itype; 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; itype; 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; itype; 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; itype; 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 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; ibardir != 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; itype; 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; itype; 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; itype; 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; itype; 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; iwall != 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; 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; ktype; 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; itype; 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; jtype; 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; 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; itype; i++) { cell *c2 = c->mov[i]; earthFloor(c2); } for(int i=0; itype; 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; itype; 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; ttype; 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; dtype; 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; itype; 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; itype; 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+iland == 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; icpdist = 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; ipathdist = INFD; pathq.clear(); for(int i=0; icpdist == 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; jtype; 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; ipathdist = 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; imonst = 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; jtype; 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; itype; 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; itype; 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; itype; 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; itype; 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; jtype; 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; jtype; 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; jtype; 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; imonst == 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; jtype; 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; jtype; 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; itype; 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; imonst != moIvyHead) continue; ivynext(c); cell *mto = NULL; int pd = c->pathdist; int sp = 0; while(c->monst != moIvyRoot) { for(int j=0; jtype; 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 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; jtype; 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; itype; 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; itype; t++) groupmove2(c->mov[t], c, t, movtype, noattacks); } } // Hex monsters vector 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; jtype; 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; ptype; i++) if(c->mov[i]->monst == moHexSnake && !attackingForbidden(cwt.c, cwt.c->mov[i])) killThePlayer(moHexSnake, p); } sval++; hexdfs.clear(); for(int i=0; iaitmp = sval; } //hexdfs.push_back(cwt.c); for(int i=0; itype; t++) if(c->mov[t] && !ishept(c->mov[t])) hexvisit(c->mov[t], c, t); } for(int i=0; imonst == 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; jtype; j++) { cell *c2 = c->mov[(i+42+next*j) % c->type]; if(celldistAlt(c2) == d) return c2; } return NULL; } void buildWhirlline(vector& whirlline, int d) { again: cell *at = whirlline[size(whirlline)-1]; cell *prev = whirlline[size(whirlline)-2]; for(int i=0; itype; 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; iland == laWhirlpool && c->aitmp != sval && (euclid || c->master->alt)) { cell *c2 = getWhirlpool(c, 1); if(!c2) continue; int d = celldistAlt(c); vector 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; iaitmp = sval; whirlMove(NULL, whirlline[0]); for(int i=0; imonst == 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; istuntime) 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; ktype; 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; istuntime) return; if(isGhostMover(c->monst) && c->cpdist > 1) { int mdir[7]; for(int j=0; jtype; 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; ktype; 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; ttype; t++) { cell *c = mf->mov[t]; if(!isUnarmed(who)) if(c->monst == moHedge || (!who && c->monst && isKillable(c) && markOrb(itOrbThorns))) { for(int u=0; utype; 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; utype; 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; ttype; 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; imonst; 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; ktype; 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; icpdist == 7) { c->monst = moOrangeDog; return; } int q = 0; cell *ctab[8]; for(int i=0; itype; 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; vector bugqueue[BUGCOLORS]; vector 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; vector deadbug; vector 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; ktype; 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; dirtype; dir++) { cell *c2 = c->mov[dir]; if(c2) { // if(isBug(c)) bugcellq.push_back(c2); => does not help... for(int t=0; ttype; 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; dirtype; dir++) { cell *c2 = c->mov[dir]; if(c2) { for(int t=0; ttype; 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; dirtype; 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 void movebugs() { buginfo.clear(); for(int k=0; k check; for(int t=0; tstuntime) continue; eMonster m = c->monst; int k = (m - moBug0) % BUGCOLORS; int gmoves[8], q=0, bqual = -1; for(int dir=0; dirtype; 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; imonst = moNone; if(battlecount) addMessage(XLAT("The Hyperbugs are fighting!")); int maxbug = 0; for(int k=0; 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; itype; 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; itype; 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; iland != 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; ttype; t++) bugcitycell(createMov(c,t), d+1); } // place everything for(int i=0; iwall == 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; imonst == moWitchSpeed) { if(c->cpdist == 1 && c->monst != moGhost) { killThePlayer(c->monst, ONLY_ONE_PLAYER_POSSIBLE); break; } int goodmoves = 0; for(int t=0; ttype; 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; imonst != moNone) moveNormal(c); } } void activateFlashFrom(cell *cf); vector 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; jtype; 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; jtype; 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; itype; i++) sageheat(cwt.c->mov[i], .3); } } else if(normalMover(m)) { int goodmoves = 0; for(int t=0; ttype; 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; imonst == 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; ipathdist == INFD) considerEnemyMove(c); } for(int d=0; d<8; d++) for(int i=0; imonst)) 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 vinefires; void processheat(double rate = 1, bool tick = true) { if(markOrb(itOrbSpeed)) rate /= 2; int oldmelt = kills[0]; vector offscreen2; for(int i=0; icpdist > 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; iland == 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; iland == 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; itype; 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; itype; i++) { cell *c2 = c->mov[i]; if(c2 && (c2->wall == waVinePlant)) { for(int j=0; jtype; 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; jtype; 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; iwall == 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; iwall == 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 bringlife; for(int i=0; icpdist > 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; jtype; 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; jtype; 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; ktype; 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; icpdist > 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; iwall == 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; icpdist > 8) break; if(c->land != laDryForest) continue; for(int j=0; jtype; 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; icpdist > 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; itmp = 0; c->heat = 0; if(c->cpdist > 8) break; if(c->land != laDryForest) continue; for(int j=0; jtype; 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; itype - 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; imonst == 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; imonst; if(isMimic(m)) c->monst = moNone; } mirrors.clear(); } void destroyStrayMirrors() { for(int i=0; icpdist > 7 && isMimic(c)) { c->monst = moNone; } } } void gomirrors(bool go) { int tk = tkills(); int nummirage = 0; mirrors2.clear(); for(int i=0; imonst; 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; imonst; 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; iland == 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; itype; 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; itype; t++) for(int u=0; utype; 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; icpdist > 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; itype; 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 yi; int yii = 0; enum eYendorState { ysUntouched, ysLocked, ysUnlocked }; eYendorState yendorState(cell *yendor) { for(int i=0; itype)); cell *prev = yendor; generatingYendor = true; for(int i=0; itype == 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; itype; 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; icpdist <= 7) { yendorPath = true; } } } int countMyGolems(eMonster m) { int g=0, dcs = size(dcal); for(int i=0; imonst == m) g++; } return g; } int countMyGolemsHP(eMonster m) { int g=0, dcs = size(dcal); for(int i=0; imonst == m) g += c->hitpoints; } return g; } void restoreGolems(int qty, eMonster m, int hp = 0) { int dcs = size(dcal); for(int i=1; qty && impdist >= 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; itype; i++) if(movepcto(1, -1, true)) canmove = true; if(!canmove) for(int i=0; itype; 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; imonst = 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= 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; imaster->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; iitem == 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; iitem == 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; itype; i++) if(c->mov[i] && c->mov[i]->wall == waMineMine) minesNearby = true; if(!minesNearby) for(int i=0; itype; 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; iland != 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; iitem == itKey) { for(int i=0; iitem; 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(; dtype; d++) if(c->mov[d] && c->mov[d]->cpdist < c->cpdist) break; if(dtype) for(int e=d; etype; 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; itype; 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; iland); 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\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; iland == 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; itype; 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; itype; 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; itype; 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; iland); 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; itype; 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; itype == 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); }