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