// Hyperbolic Rogue // main game routines: movement etc. // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details namespace hr { #if HDR enum eLastmovetype { lmSkip, lmMove, lmAttack, lmPush, lmTree, lmInstant }; extern eLastmovetype lastmovetype, nextmovetype; enum eForcemovetype { fmSkip, fmMove, fmAttack, fmInstant, fmActivate }; extern eForcemovetype forcedmovetype; #endif EX int lastsafety; EX int mutantphase; EX int turncount; int rosewave, rosephase; int avengers, mirrorspirits, wandering_jiangshi, jiangshi_on_screen; EX int gamerange_bonus = 0; EX int gamerange() { return getDistLimit() + gamerange_bonus; } cell *lastmove; eLastmovetype lastmovetype, nextmovetype; eForcemovetype forcedmovetype; bool hauntedWarning; bool survivalist; bool hardcore = false; int hardcoreAt; set snaketypes; flagtype havewhat, hadwhat; #define HF_BUG Flag(0) #define HF_EARTH Flag(1) #define HF_BIRD Flag(2) #define HF_LEADER Flag(3) #define HF_HEX Flag(4) #define HF_WHIRLPOOL Flag(5) #define HF_WATER Flag(6) #define HF_AIR Flag(7) #define HF_MUTANT Flag(8) #define HF_OUTLAW Flag(9) #define HF_WHIRLWIND Flag(10) #define HF_ROSE Flag(11) #define HF_DRAGON Flag(12) #define HF_KRAKEN Flag(13) #define HF_SHARK Flag(14) #define HF_BATS Flag(15) #define HF_REPTILE Flag(16) #define HF_EAGLES Flag(17) #define HF_SLOW Flag(18) #define HF_FAST Flag(19) #define HF_WARP Flag(20) #define HF_MOUSE Flag(21) #define HF_RIVER Flag(22) #define HF_MIRROR Flag(23) #define HF_VOID Flag(24) #define HF_HUNTER Flag(25) #define HF_FAILED_AMBUSH Flag(26) #define HF_MAGNET Flag(27) #define HF_HEXD Flag(28) #define HF_ALT Flag(29) #define HF_MONK Flag(30) #define HF_WESTWALL Flag(31) bool seenSevenMines = false; EX bool pureHardcore() { return hardcore && hardcoreAt < PUREHARDCORE_LEVEL; } EX bool canmove = true; int sagephase = 0; // number of Grails collected, to show you as a knight int knighted = 0; bool usedSafety = false; eLand safetyland; int safetyseed; int showid = 0; EX bool invismove = false; EX bool invisfish = false; // last move was invisible [due to Fish] int noiseuntil; // noise until the given turn void createNoise(int t) { noiseuntil = max(noiseuntil, turncount+t); invismove = false; if(shmup::on) shmup::visibleFor(100 * t); } int currentLocalTreasure; bool landvisited[landtypes]; bool eq(short a, short b) { return a==b; } // game state EX array items; EX array kills; EX int explore[10], exploreland[10][landtypes], landcount[landtypes]; EX map > hiitems; bool orbused[ittypes], lastorbused[ittypes]; EX bool playermoved = true; // center on the PC? EX bool flipplayer = true; // flip the player image after move, do not flip after attack EX int cheater = 0; // did the player cheat? int anthraxBonus = 0; // for using Safety in tactical Camelot EX vector dcal; // queue for cpdist vector pathq; // queue for pathdist vector offscreen; // offscreen cells to take care off vector pathqm; // list of monsters to move (pathq restriced to monsters) vector targets; // list of monster targets // monsters of specific types to move vector worms, ivies, ghosts, golems, hexsnakes; vector temps; // temporary changes during bfs vector tempval; // restore temps // a bit nicer way of DFS vector reachedfrom; // additional direction information for BFS algorithms // it remembers from where we have got to this location // the opposite cell will be added to the queue first, // which helps the AI vector movesofgood[MAX_EDGE+1]; int first7; // the position of the first monster at distance 7 in dcal EX cellwalker cwt; // single player character position EX inline cell*& singlepos() { return cwt.at; } EX inline bool singleused() { return !(shmup::on || multi::players > 1); } // the main random number generator for the game // all the random calls related to the game mechanics (land generation, AI...) should use hrngen // random calls not related to the game mechanics (graphical effects) should not use hrngen // this ensures that the game should unfold exactly the same if given the same seed and the same input std::mt19937 hrngen; EX void shrand(int i) { hrngen.seed(i); } EX int hrandpos() { return hrngen() & HRANDMAX; } // using our own implementations rather than ones from , // to make sure that they return the same values on different compilers // a random integer from [0..i), generated by the game's main generator // we want the same world to be generated if the seed is the same. For this purpose, // hrand should be used for all the game-related generation, and nowhere else EX int hrand(int i) { unsigned d = hrngen() - hrngen.min(); long long m = (long long) (hrngen.max() - hrngen.min()) + 1; m /= i; d /= m; if(d < (unsigned) i) return d; return hrand(i); } #if HDR template T pick(T x, U... u) { std::initializer_list i = {x,u...}; return *(i.begin() + hrand(1+sizeof...(u))); } template void hrandom_shuffle(T* x, int n) { for(int k=1; kmpdist = INFD; // minimum distance from the player, ever c->cpdist = INFD; // current distance from the player c->pathdist = PINFD;// current distance from the player, along paths (used by yetis) c->landparam = 0; c->landflags = 0; c->wparam = 0; c->listindex = -1; c->wall = waNone; c->item = itNone; c->monst = moNone; c->bardir = NODIR; c->mondir = NODIR; c->barleft = c->barright = laNone; c->land = laNone; c->ligon = 0; c->stuntime = 0; c->monmirror = 0; } EX bool doesnotFall(cell *c) { if(c->wall == waChasm) return false; else if(cellUnstable(c) && !in_gravity_zone(c)) { fallingFloorAnimation(c); c->wall = waChasm; return false; } return true; } EX bool doesFall(cell *c) { return !doesnotFall(c); } EX bool doesFallSound(cell *c) { if(c->land != laMotion && c->land != laZebra) playSound(c, "trapdoor"); return !doesnotFall(c); } EX bool itemHidden(cell *c) { return isWatery(c) && !(shmup::on && shmup::boatAt(c)); } EX bool playerInWater() { for(int i=0; i 1) return multi::player[i].at; return singlepos(); } EX bool allPlayersInBoats() { for(int i=0; iwall != waBoat) return true; return false; } EX int whichPlayerOn(cell *c) { if(singleused()) return c == singlepos() ? 0 : -1; for(int i=0; i= 0; } EX bool isPlayerInBoatOn(cell *c, int i) { return (playerpos(i) == c && ( c->wall == waBoat || c->wall == waStrandedBoat || (shmup::on && shmup::playerInBoat(i)) )); } EX bool playerInBoat(int i) { return isPlayerInBoatOn(playerpos(i), i); } EX bool isPlayerInBoatOn(cell *c) { for(int i=0; iwall == waBoat) placeWater(c, c2); if(strandedToo && c->wall == waStrandedBoat) c->wall = waNone; shmup::destroyBoats(c); } EX bool playerInPower() { if(singleused()) return singlepos()->land == laPower || singlepos()->land == laHalloween; for(int i=0; iland == laPower || playerpos(i)->land == laHalloween)) return true; return false; } EX eItem localTreasureType() { lastland = singlepos()->land; return treasureType(lastland); } EX void countLocalTreasure() { eItem i = localTreasureType(); currentLocalTreasure = i ? items[i] : 0; if(i != itHyperstone) for(int i=0; icpdist > 3) break; eItem i2 = treasureType(c2->land); if(i2 && items[i2] < currentLocalTreasure) currentLocalTreasure = items[i2]; } } #if HDR static const int NO_TREASURE = 1; static const int NO_YENDOR = 2; static const int NO_GRAIL = 4; static const int NO_LOVE = 8; #endif EX int gold(int no IS(0)) { int i = 0; if(!(no & NO_YENDOR)) i += items[itOrbYendor] * 50; if(!(no & NO_GRAIL)) i += items[itHolyGrail] * 10; if(!(no & NO_LOVE)) { bool love = items[itOrbLove]; #if CAP_INV if(inv::on && inv::remaining[itOrbLove]) love = true; #endif #if CAP_DAILY if(daily::on) love = false; #endif if(love) i += 30; } if(!(no & NO_TREASURE)) for(int t=0; t mg) mg = items[i]; return mg; } int* killtable[] = { &kills[moYeti], &kills[moWolf], & kills[moRanger], &kills[moTroll], &kills[moGoblin], & kills[moWorm], &kills[moDesertman], &kills[moIvyRoot], & kills[moMonkey], &kills[moEagle], &kills[moSlime], &kills[moSeep], & kills[moRunDog], & kills[moCultist], &kills[moTentacle], &kills[moPyroCultist], & kills[moLesser], &kills[moGreater], & kills[moZombie], &kills[moGhost], &kills[moNecromancer], & kills[moHedge], &kills[moFireFairy], & kills[moCrystalSage], &kills[moShark], &kills[moGreaterShark], & kills[moMiner], &kills[moFlailer], &kills[moLancer], & kills[moVineSpirit], &kills[moVineBeast], & kills[moBug0], &kills[moBug1], &kills[moBug2], & kills[moDarkTroll], &kills[moEarthElemental], & kills[moWitch], &kills[moEvilGolem], &kills[moWitchFlash], &kills[moWitchFire], & kills[moWitchWinter], &kills[moWitchSpeed], & kills[moCultistLeader], & kills[moPirate], &kills[moCShark], &kills[moParrot], & kills[moHexSnake], &kills[moRedTroll], & kills[moPalace], &kills[moSkeleton], &kills[moFatGuard], &kills[moVizier], & kills[moViking], &kills[moFjordTroll], &kills[moWaterElemental], & kills[moAlbatross], &kills[moBomberbird], & kills[moAirElemental], &kills[moFireElemental], & kills[moGargoyle], &kills[moFamiliar], &kills[moOrangeDog], & items[itMutant], &kills[moMetalBeast], &kills[moMetalBeast2], & kills[moOutlaw], &kills[moForestTroll], &kills[moStormTroll], & kills[moRedFox], &kills[moWindCrow], & kills[moFalsePrincess], &kills[moRoseLady], & kills[moRoseBeauty], & kills[moRatling], &kills[moRatlingAvenger], & kills[moDragonHead], & kills[moGadfly], &kills[moSparrowhawk], &kills[moResearcher], &kills[moKrakenH], &kills[moDraugr], &kills[moBat], &kills[moReptile], &kills[moHerdBull], &kills[moSleepBull], &kills[moRagingBull], &kills[moButterfly], &kills[moNarciss], &kills[moMirrorSpirit], &kills[moHunterDog], &kills[moIceGolem], &kills[moVoidBeast], &kills[moJiangshi], &kills[moTerraWarrior], &kills[moSalamander], &kills[moLavaWolf], &kills[moSwitch1], &kills[moSwitch2], &kills[moMonk], &kills[moCrusher], &kills[moHexDemon], &kills[moAltDemon], &kills[moPair], &kills[moBrownBug], &kills[moAcidBird], &kills[moFallingDog], &kills[moVariantWarrior], &kills[moWestHawk], NULL }; EX int tkills() { int res = 0; for(int i=0; killtable[i]; i++) res += killtable[i][0]; return res; } EX int killtypes() { int res = 0; for(int i=0; killtable[i]; i++) if(killtable[i][0]) res++; return res; } #if HDR enum eGravity { gsNormal, gsLevitation, gsAnti }; #endif EX eGravity gravity_state, last_gravity_state; bool bird_disruption(cell *c) { return c->cpdist <= 5 && items[itOrbGravity]; } EX bool in_gravity_zone(cell *c) { return gravity_state && c->cpdist <= 5; } int gravity_zone_diff(cell *c) { if(in_gravity_zone(c)) { if(gravity_state == gsLevitation) return 0; if(gravity_state == gsAnti) return -1; } return 1; } bool isJWall(cell *c) { return isWall(c) || c->monst == passive_switch; } eGravity get_static_gravity(cell *c) { if(isGravityLand(c->land)) return gsLevitation; if(among(c->wall, waArrowTrap, waFireTrap, waClosePlate, waOpenPlate, waTrapdoor)) return gsNormal; forCellEx(c2, c) if(isJWall(c2)) return gsAnti; if(isWatery(c) || isChasmy(c) || among(c->wall, waMagma, waMineUnknown, waMineMine, waMineOpen)) return gsLevitation; return gsNormal; } eGravity get_move_gravity(cell *c, cell *c2) { if(isGravityLand(c->land) && isGravityLand(c2->land)) { int d = gravityLevelDiff(c, c2); if(d > 0) return gsNormal; if(d == 0) return gsLevitation; if(d < 0) return gsAnti; return gsNormal; } else { if(snakelevel(c) != snakelevel(c2)) { int d = snakelevel(c2) - snakelevel(c); if(d > 0) return gsAnti; if(d == -3) return gsLevitation; return gsNormal; } forCellEx(c3, c) if(isJWall(c3)) return gsAnti; forCellEx(c3, c2) if(isJWall(c3)) return gsAnti; if(isWatery(c2) && c->wall == waBoat && !againstCurrent(c2, c)) return gsNormal; if(isWatery(c2) || isChasmy(c2) || among(c2->wall, waMagma, waMineUnknown, waMineMine, waMineOpen) || anti_alchemy(c2, c)) return gsLevitation; return gsNormal; } } EX bool isWarped(cell *c) { return isWarpedType(c->land) || (!inmirrororwall(c->land) && (items[itOrb37] && c->cpdist <= 4)); } EX bool nonAdjacent(cell *c, cell *c2) { if(isWarped(c) && isWarped(c2) && warptype(c) == warptype(c2)) { /* int i = neighborId(c, c2); cell *c3 = c->modmove(i+1), *c4 = c->modmove(i-1); if(c3 && !isTrihepta(c3)) return false; if(c4 && !isTrihepta(c4)) return false; */ return true; } return false; } EX bool nonAdjacentPlayer(cell *c, cell *c2) { return nonAdjacent(c, c2) && !markOrb(itOrb37); } EX bool thruVine(cell *c, cell *c2) { return (cellHalfvine(c) && c2->wall == c->wall && c2 != c); // ((c->wall == waFloorC || c->wall == waFloorD) && c2->wall == c->wall && !c2->item && !c->item); } // === MOVEMENT FUNCTIONS === // w = from->move(d) EX bool againstCurrent(cell *w, cell *from) { if(from->land != laWhirlpool) return false; if(againstWind(from, w)) return false; // wind is stronger than current if(!eubinary && (!from->master->alt || !w->master->alt)) return false; int dfrom = celldistAlt(from); int dw = celldistAlt(w); if(dw < dfrom) return false; if(dfrom < dw) return true; for(int d=0; dtype; d++) if(from->move(d) == w) { cell *c3 = from->modmove(d-1); if(!c3) return false; return celldistAlt(c3) < dfrom; } return false; } EX bool boatGoesThrough(cell *c) { if(isGravityLand(c->land)) return false; return (c->wall == waNone && c->land != laMotion && c->land != laZebra && c->land != laReptile) || isAlchAny(c) || c->wall == waCavefloor || c->wall == waFrozenLake || isReptile(c->wall) || c->wall == waDeadfloor || c->wall == waCIsland || c->wall == waCIsland2 || c->wall == waMineUnknown || c->wall == waMineMine || c->wall == waMineOpen || c->wall == waBonfireOff || c->wall == waFire || c->wall == waPartialFire || c->wall == waArrowTrap; } EX void placeWater(cell *c, cell *c2) { destroyTrapsOn(c); if(isWatery(c)) ; else if(c2 && isAlchAny(c2)) c->wall = c2->wall; else if(isIcyLand(c)) c->wall = waLake; else c->wall = waSea; // destroy the ancient treasure! if(c->item == itBarrow) c->item = itNone; } EX int incline(cell *cfrom, cell *cto) { return snakelevel(cto) - snakelevel(cfrom); } #define F(x) checkflags(flags,x) EX bool checkflags(flagtype flags, int x) { if(flags & x) return true; if(flags & P_ISPLAYER) { if((x & P_WINTER) && markOrb(itOrbWinter)) return true; if((x & P_IGNORE37) && markOrb(itOrb37)) return true; if((x & P_FISH) && markOrb(itOrbFish)) return true; if((x & P_MARKWATER) && markOrb(itOrbWater)) return true; if((x & P_AETHER) && markOrb2(itOrbAether) && !(flags&P_NOAETHER)) return true; } if(flags & P_ISFRIEND) if(items[itOrbEmpathy]) if(checkflags(flags ^ P_ISPLAYER ^ P_ISFRIEND, x) && markOrb(itOrbEmpathy)) return true; return false; } EX bool strictlyAgainstGravity(cell *w, cell *from, bool revdir, flagtype flags) { return cellEdgeUnstable(w, flags) && cellEdgeUnstable(from, flags) && !(shmup::on && from == w) && gravityLevelDiff(from, w) != (revdir?-1:1) * gravity_zone_diff(from); } EX bool anti_alchemy(cell *w, cell *from) { bool alch1 = w->wall == waFloorA && from && from->wall == waFloorB && !w->item && !from->item; alch1 |= w->wall == waFloorB && from && from->wall == waFloorA && !w->item && !from->item; return alch1; } #if HDR #define P_MONSTER Flag(0) // can move through monsters #define P_MIRROR Flag(1) // can move through mirrors #define P_REVDIR Flag(2) // reverse direction movement #define P_WIND Flag(3) // can move against the wind #define P_GRAVITY Flag(4) // can move against the gravity #define P_ISPLAYER Flag(5) // player-only moves (like the Round Table jump) #define P_ONPLAYER Flag(6) // always can step on the player #define P_FLYING Flag(7) // is flying #define P_BULLET Flag(8) // bullet can fly through more things #define P_MIRRORWALL Flag(9) // mirror images go through mirror walls #define P_JUMP1 Flag(10) // first part of a jump #define P_JUMP2 Flag(11) // second part of a jump #define P_TELE Flag(12) // teleport onto #define P_BLOW Flag(13) // Orb of Air -- blow, or push #define P_AETHER Flag(14) // aethereal #define P_FISH Flag(15) // swimming #define P_WINTER Flag(16) // fire resistant #define P_USEBOAT Flag(17) // can use boat #define P_NOAETHER Flag(18) // disable AETHER #define P_FRIENDSWAP Flag(19) // can move on friends (to swap with tem) #define P_ISFRIEND Flag(20) // is a friend (can use Empathy + Winter/Aether/Fish combo) #define P_LEADER Flag(21) // can push statues and use boats #define P_MARKWATER Flag(22) // mark Orb of Water as used #define P_EARTHELEM Flag(23) // Earth Elemental #define P_WATERELEM Flag(24) // Water Elemental #define P_IGNORE37 Flag(25) // ignore the triheptagonal board #define P_CHAIN Flag(26) // for chaining moves with boats #define P_DEADLY Flag(27) // suicide moves allowed #define P_ROSE Flag(28) // rose smell #define P_CLIMBUP Flag(29) // allow climbing up #define P_CLIMBDOWN Flag(30) // allow climbing down #define P_REPTILE Flag(31) // is reptile #define P_VOID Flag(32) // void beast #define P_PHASE Flag(33) // phasing movement #define P_PULLMAGNET Flag(34) // pull the other part of the magnet #endif EX bool passable(cell *w, cell *from, flagtype flags) { bool revdir = (flags&P_REVDIR); bool vrevdir = revdir ^ bool(flags&P_VOID); if(from && from != w && nonAdjacent(from, w) && !F(P_IGNORE37 | P_BULLET)) return false; for(int i=0; imonst)) { int i = vrevdir ? incline(w, from) : incline(from, w); if(in_gravity_zone(w)) { if(gravity_state == gsLevitation) i = 0; if(gravity_state == gsAnti && i > 1) i = 1; } if(i < -1 && F(P_ROSE)) return false; if((i > 1) && !F(P_JUMP1 | P_JUMP2 | P_BULLET | P_FLYING | P_BLOW | P_CLIMBUP | P_AETHER | P_REPTILE)) return false; if((i < -2) && !F(P_DEADLY | P_JUMP1 | P_JUMP2 | P_BULLET | P_FLYING | P_BLOW | P_CLIMBDOWN | P_AETHER | P_REPTILE)) return false; } } if(F(P_ROSE)) { if(airdist(w) < 3) return false; if(againstWind(w,from)) return false; } if(from && strictlyAgainstGravity(w, from, vrevdir, flags) && !((flags & P_ISPLAYER) && shmup::on) && !F(P_GRAVITY | P_BLOW | P_JUMP1 | P_JUMP2 | P_FLYING | P_BULLET | P_AETHER) ) return false; if(from && (vrevdir ? againstWind(from,w) : againstWind(w, from)) && !F(P_WIND | P_BLOW | P_JUMP1 | P_JUMP2 | P_BULLET | P_AETHER)) return false; if(revdir && from && w->monst && passable(from, w, flags &~ (P_REVDIR|P_MONSTER))) return true; if(!shmup::on && sword::at(w, flags & P_ISPLAYER) && !F(P_DEADLY | P_BULLET | P_ROSE)) return false; bool alch1 = anti_alchemy(w, from); if(alch1) { bool alchok = (in_gravity_zone(w) || in_gravity_zone(from)); alchok = alchok || (F(P_JUMP1 | P_JUMP2 | P_FLYING | P_TELE | P_BLOW | P_AETHER | P_BULLET) && !F(P_ROSE)); if(!alchok) return false; } if(from && thruVine(from, w) && !F(P_AETHER)) return false; if(w->monst == moMouse && F(P_JUMP1)) ; else if(w->monst && isFriendly(w) && F(P_FRIENDSWAP)) ; else if(w->monst && !F(P_MONSTER)) return false; if(w->wall == waMirror || w->wall == waCloud) return F(P_MIRROR | P_AETHER); if(w->wall == waMirrorWall) return F(P_MIRRORWALL); if(F(P_BULLET)) { if(isFire(w) || w->wall == waBonfireOff || cellHalfvine(w) || w->wall == waMagma || w->wall == waAncientGrave || w->wall == waFreshGrave || w->wall == waRoundTable) return true; } if(F(P_LEADER)) { if(from && from->wall == waBoat && isWatery(w) && from->item == itOrbYendor) return false; if(from && from->wall == waBoat && isWateryOrBoat(w) && !againstCurrent(w, from)) return true; if(from && isWatery(from) && w->wall == waBoat && F(P_CHAIN)) return true; if(from && isWatery(from) && isWatery(w) && F(P_CHAIN) && !againstCurrent(w, from)) return true; if(w->wall == waBigStatue && from && canPushStatueOn(from)) return true; } if(F(P_EARTHELEM)) { // cannot go through Living Caves... if(w->wall == waCavefloor) return false; // but can dig through... if(w->wall == waDeadwall || w->wall == waDune || w->wall == waStone) return true; // and can swim through... if(w->wall == waSea && w->land == laLivefjord) return true; } if(F(P_WATERELEM)) { if(isWatery(w) || boatGoesThrough(w) || w->wall == waBoat || w->wall == waDeadTroll || w->wall == waDeadTroll2) return true; return false; } if(isThorny(w->wall) && F(P_BLOW | P_DEADLY)) return true; if(isFire(w) || w->wall == waMagma) { if(w->wall == waMagma && in_gravity_zone(w)) ; else if(!F(P_AETHER | P_WINTER | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY)) return false; } if(in_gravity_zone(w) && gravity_state == gsAnti && !isGravityLand(w->land) && (!from || !isGravityLand(from->land))) if(!F(P_AETHER | P_BLOW | P_JUMP1 | P_BULLET | P_FLYING)) { bool next_to_wall = false; forCellEx(c2, w) if(isJWall(c2)) next_to_wall = true; if(from) forCellEx(c2, from) if(isJWall(c2)) next_to_wall = true; if(!next_to_wall && (!from || incline(from, w) * (vrevdir?-1:1) <= 0)) return false; } if(isWatery(w)) { if(in_gravity_zone(w)) ; else if(from && from->wall == waBoat && F(P_USEBOAT) && (!againstCurrent(w, from) || F(P_MARKWATER)) && !(from->item == itOrbYendor)) ; else if(!F(P_AETHER | P_FISH | P_FLYING | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY | P_REPTILE)) return false; } if(isChasmy(w)) { if(in_gravity_zone(w)) ; else if(!F(P_AETHER | P_FLYING | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY | P_REPTILE)) return false; } if(w->wall == waRoundTable && from && from->wall != waRoundTable && (flags & P_ISPLAYER)) return true; if(isNoFlight(w) && F(P_FLYING | P_BLOW | P_JUMP1)) return false; if(isWall(w)) { // a special case: empathic aethereal beings cannot go through Round Table // (but truly aetheral beings can) if(w->wall == waRoundTable) { if(!(flags & P_AETHER)) return false; } else if(!F(P_AETHER)) return false; } return true; } vector > airmap; EX int airdist(cell *c) { if(!(havewhat & HF_AIR)) return 3; vector >::iterator it = lower_bound(airmap.begin(), airmap.end(), make_pair(c,0)); if(it != airmap.end() && it->first == c) return it->second; return 3; } EX ld calcAirdir(cell *c) { if(!c || c->monst == moAirElemental || !passable(c, NULL, P_BLOW)) return 0; for(int i=0; itype; i++) { cell *c2 = c->move(i); if(c2 && c2->monst == moAirElemental) { return c->c.spin(i) * 2 * M_PI / c2->type; } } for(int i=0; itype; i++) { cell *c2 = c->move(i); if(!c2) continue; if(!passable(c2, c, P_BLOW | P_MONSTER)) continue; if(!passable(c, c2, P_BLOW | P_MONSTER)) continue; for(int i=0; itype; i++) { cell *c3 = c2->move(i); if(c3 && c3->monst == moAirElemental) { return c2->c.spin(i) * 2 * M_PI / c3->type; } } } return 0; } EX bool againstWind(cell *cto, cell *cfrom) { if(!cfrom || !cto) return false; int dcto = airdist(cto), dcfrom = airdist(cfrom); if(dcto < dcfrom) return true; #if CAP_FIELD if(cfrom->land == laBlizzard && !shmup::on && cto->land == laBlizzard && dcto == 3 && dcfrom == 3) { char vfrom = windmap::at(cfrom); char vto = windmap::at(cto); int z = (vfrom-vto) & 255; if(z >= windmap::NOWINDBELOW && z < windmap::NOWINDFROM) return true; } #endif whirlwind::calcdirs(cfrom); int d = neighborId(cfrom, cto); if(whirlwind::winddir(d) == -1) return true; return false; } bool ghostmove(eMonster m, cell* to, cell* from) { if(!isGhost(m) && nonAdjacent(to, from)) return false; if(sword::at(to, 0)) return false; if(!shmup::on && isPlayerOn(to)) return false; if(to->monst && !(to->monst == moTentacletail && isGhost(m) && m != moFriendlyGhost) && !(to->monst == moTortoise && isGhost(m) && m != moFriendlyGhost)) return false; if((m == moWitchGhost || m == moWitchWinter) && to->land != laPower) return false; if(isGhost(m)) for(int i=0; itype; i++) { if(inmirror(to->move(i))) return false; if(to->move(i) && to->move(i) != from && isGhost(to->move(i)->monst) && (to->move(i)->monst == moFriendlyGhost) == (m== moFriendlyGhost)) return false; } if(isGhost(m) || m == moWitchGhost) return true; if(m == moGreaterShark) return isWatery(to); if(m == moWitchWinter) return passable(to, from, P_WINTER | P_ONPLAYER); return false; } bool slimepassable(cell *w, cell *c) { if(w == c || !c) return true; int u = neighborId(c, w); if(nonAdjacent(w,c)) return false; if(isPlayerOn(w)) return true; int group = slimegroup(c); if(!group) return false; int ogroup = slimegroup(w); if(!ogroup) return false; bool hv = (group == ogroup); if(sword::at(w, 0)) return false; if(w->item) return false; // only travel to halfvines correctly if(cellHalfvine(c)) { int i=0; for(int t=0; ttype; t++) if(c->move(t) && c->move(t)->wall == c->wall) i=t; int z = i-u; if(z<0) z=-z; z%=6; if(z>1) return false; hv=(group == ogroup); } // only travel from halfvines correctly if(cellHalfvine(w)) { int i=0; for(int t=0; ttype; t++) if(w->move(t) && w->move(t)->wall == w->wall) i=t; int z = i-c->c.spin(u); if(z<0) z=-z; z%=6; if(z>1) return false; hv=(group == ogroup); } if(!hv) return false; return true; } bool sharkpassable(cell *w, cell *c) { if(w == c || !c) return true; if(nonAdjacent(w,c)) return false; if(isPlayerOn(w)) return true; if(!isWatery(w)) return false; if(sword::at(w, 0)) return false; // don't go against the current if(isWateryOrBoat(w) && isWateryOrBoat(c)) return !againstCurrent(w, c); return true; } EX bool canPushStatueOn(cell *c) { return passable(c, NULL, P_MONSTER) && !snakelevel(c) && !isWorm(c->monst) && !isReptile(c->wall) && !peace::on && !among(c->wall, waBoat, waFireTrap, waArrowTrap); } EX void moveBoat(cell *to, cell *from, int direction_hint) { eWall x = to->wall; to->wall = from->wall; from->wall = x; to->mondir = neighborId(to, from); moveItem(from, to, false); animateMovement(from, to, LAYER_BOAT, direction_hint); } EX void moveBoatIfUsingOne(cell *to, cell *from, int direction_hint) { if(from->wall == waBoat && isWatery(to)) moveBoat(to, from, direction_hint); else if(from->wall == waBoat && boatGoesThrough(to) && isFriendly(to) && markEmpathy(itOrbWater)) { placeWater(to, from); moveBoat(to, from, direction_hint); } } eMonster otherpole(eMonster m) { return eMonster(m ^ moNorthPole ^ moSouthPole); } bool againstMagnet(cell *c1, cell *c2, eMonster m) { // (from, to) if(false) forCellEx(c3, c2) { if(c3 == c1) continue; if(c3->monst == m) return true; /* if(c3->monst == otherpole(m) && c3->move(c3->mondir) != c1) { int i = 0; forCellEx(c4, c3) if(c4->monst == m) i++; if(i == 2) return true; } */ } if(c1->monst == m && !isNeighbor(c2, c1->move(c1->mondir))) return true; forCellEx(c3, c1) if(c3->monst != m && isMagneticPole(c3->monst)) if(!isNeighbor(c3, c2)) return true; return false; } EX bool againstPair(cell *c1, cell *c2, eMonster m) { // (from, to) if(c1->monst == m && !isNeighbor(c2, c1->move(c1->mondir))) return true; return false; } EX bool notNearItem(cell *c) { forCellCM(c2, c) if(c2->item) return false; return true; } EX bool passable_for(eMonster m, cell *w, cell *from, flagtype extra) { if(w->monst && !(extra & P_MONSTER) && !isPlayerOn(w)) return false; if(m == moWolf) { return (isIcyLand(w) || w->land == laVolcano) && (isPlayerOn(w) || passable(w, from, extra)); } if(isMagneticPole(m)) return !(w && from && againstMagnet(from, w, m)) && passable(w, from, extra); if(m == moPair) return !(w && from && againstPair(from, w, m)) && passable(w, from, extra); if(m == passive_switch) return false; if(minf[m].mgroup == moYeti || isBug(m) || isDemon(m) || m == moHerdBull || m == moMimic || m == moAsteroid) { if((isWitch(m) || m == moEvilGolem) && w->land != laPower && w->land != laHalloween) return false; return passable(w, from, extra); } if(m == moDragonHead && prairie::isriver(w)) return false; if(isShark(m)) return sharkpassable(w, from); if(isSlimeMover(m)) return slimepassable(w, from); if(m == moKrakenH) { if(extra & P_ONPLAYER) { if(isPlayerOn(w)) return true; } if((extra & P_ONPLAYER) && isPlayerOn(w)) return true; if(kraken_pseudohept(w) || kraken_pseudohept(from)) return false; if(w->wall != waBoat && !slimepassable(w, from)) return false; forCellEx(w2, w) if(w2->wall != waBoat && !passable(w2, w, P_FISH | P_MONSTER)) return false; return true; } if(m == moEarthElemental) return passable(w, from, extra | P_EARTHELEM); if(m == moWaterElemental) return passable(w, from, extra | P_WATERELEM); if(m == moGreaterShark) return isWatery(w) || w->wall == waBoat || w->wall == waFrozenLake; if(isGhostMover(m) || m == moFriendlyGhost) return ghostmove(m, w, from); // for the purpose of Shmup this is correct if(m == moTameBomberbird) return passable(w, from, extra | P_FLYING | P_ISFRIEND); if(m == moHexSnake) return !pseudohept(w) && passable(w, from, extra|P_WIND|P_FISH); if(isBird(m)) { if(bird_disruption(w) && (!from || bird_disruption(from)) && markOrb(itOrbGravity)) return passable(w, from, extra); else return passable(w, from, extra | P_FLYING); } if(m == moReptile) return passable(w, from, extra | P_REPTILE); if(isDragon(m)) return passable(w, from, extra | P_FLYING | P_WINTER); if(m == moAirElemental) return passable(w, from, extra | P_FLYING | P_WIND); if(isLeader(m)) { if(from && from->wall == waBoat && from->item == itCoral && !from->monst) return false; // don't move Corals! return passable(w, from, extra | P_LEADER); } if(isPrincess(m)) return passable(w, from, extra | P_ISFRIEND | P_USEBOAT); if(isGolemOrKnight(m)) return passable(w, from, extra | P_ISFRIEND); if(isWorm(m)) return passable(w, from, extra) && !cellUnstable(w) && ((m != moWorm && m != moTentacle) || !cellEdgeUnstable(w)); if(m == moVoidBeast) return passable(w, from, extra | P_VOID); if(m == moHexDemon) { if(extra & P_ONPLAYER) { if(isPlayerOn(w)) return true; } return !pseudohept(w) && passable(w, from, extra); } if(m == moAltDemon) { if(extra & P_ONPLAYER) { if(isPlayerOn(w)) return true; } return (!w || !from || w==from || pseudohept(w) || pseudohept(from)) && passable(w, from, extra); } if(m == moMonk) { if(extra & P_ONPLAYER) { if(isPlayerOn(w)) return true; } return notNearItem(w) && passable(w, from, extra); } return false; } EX eMonster movegroup(eMonster m) { return minf[m].mgroup; } EX void useup(cell *c) { c->wparam--; if(c->wparam == 0) { drawParticles(c, c->wall == waFire ? 0xC00000 : winf[c->wall].color, 10, 50); if(c->wall == waTempFloor) c->wall = waChasm; else if(c->wall == waTempBridge || c->wall == waTempBridgeBlocked || c->wall == waBurningDock) placeWater(c, c); else c->wall = c->land == laCaribbean ? waCIsland2 : waNone; } } EX int realstuntime(cell *c) { if(isMutantIvy(c)) return (c->stuntime - mutantphase) & 15; return c->stuntime; } bool childbug = false; // is w killed if killed is killed? EX bool isChild(cell *w, cell *killed) { if(isAnyIvy(w->monst)) { int lim = 0; // printf("w = %p mondir = %d **\n", w, w->mondir); while(w != killed && w->mondir != NODIR) { lim++; if(lim == 100000) { childbug = true; printf("childbug!\n"); w->item = itBuggy; break; } if(!isAnyIvy(w->monst)) { return false; } w = w->move(w->mondir); // printf("w = %p mondir = %d\n", w, w->mondir); } } return w == killed; } EX bool logical_adjacent(cell *c1, eMonster m1, cell *c2) { if(!c1 || !c2) return true; // cannot really check eMonster m2 = c2->monst; if(!isNeighbor(c1, c2)) return false; if(thruVine(c1, c2) && !attackThruVine(m1) && !attackThruVine(m2) && !checkOrb(m1, itOrbAether) && !checkOrb(m2, itOrbAether)) return false; if(nonAdjacent(c1, c2) && !attackNonAdjacent(m1) && !attackNonAdjacent(m2) && !checkOrb(m1, itOrb37) && !checkOrb(m1, itOrbAether) && !checkOrb(m2, itOrbAether)) return false; return true; } EX bool arrow_stuns(eMonster m) { return among(m, moCrusher, moMonk, moAltDemon, moHexDemon, moGreater, moGreaterM, moHedge); } EX bool canAttack(cell *c1, eMonster m1, cell *c2, eMonster m2, flagtype flags) { // cannot eat worms if((flags & AF_EAT) && isWorm(m2)) return false; if(m1 == passive_switch || m2 == passive_switch) return false; if((flags & AF_GETPLAYER) && isPlayerOn(c2)) m2 = moPlayer; if(!m2) return false; if(m2 == moPlayer && peace::on) return false; if((flags & AF_MUSTKILL) && attackJustStuns(c2, flags, m1)) return false; if((flags & AF_ONLY_FRIEND) && m2 != moPlayer && !isFriendly(c2)) return false; if((flags & AF_ONLY_FBUG) && m2 != moPlayer && !isFriendlyOrBug(c2)) return false; if((flags & AF_ONLY_ENEMY) && (m2 == moPlayer || isFriendlyOrBug(c2))) return false; if(m1 == moArrowTrap && arrow_stuns(m2)) return true; if(among(m2, moAltDemon, moHexDemon, moPair, moCrusher, moNorthPole, moSouthPole, moMonk) && !(flags & (AF_EAT | AF_MAGIC | AF_BULL | AF_CRUSH))) return false; if(m2 == moHedge && !(flags & (AF_STAB | AF_TOUGH | AF_EAT | AF_MAGIC | AF_LANCE | AF_SWORD_INTO | AF_HORNS | AF_BULL | AF_CRUSH))) if(!checkOrb(m1, itOrbThorns)) return false; // krakens do not try to fight even with Discord if((m1 == moKrakenT || m1 == moKrakenH) && (m2 == moKrakenT || m2 == moKrakenH)) return false; if(m2 == moDraugr && !(flags & (AF_SWORD | AF_MAGIC | AF_SWORD_INTO | AF_HORNS | AF_CRUSH))) return false; // if(m2 == moHerdBull && !(flags & AF_MAGIC)) return false; if(isBull(m2) && !(flags & (AF_MAGIC | AF_HORNS | AF_SWORD_INTO | AF_CRUSH))) return false; if(m2 == moButterfly && !(flags & (AF_MAGIC | AF_BULL | AF_HORNS | AF_SWORD_INTO | AF_CRUSH))) return false; if(!(flags & AF_NOSHIELD) && ((flags & AF_NEXTTURN) ? checkOrb2 : checkOrb)(m2, itOrbShield)) return false; if((flags & AF_STAB) && m2 != moHedge) if(!checkOrb(m1, itOrbThorns)) return false; if(flags & AF_BACK) { if(m2 == moFlailer && !c2->stuntime) flags |= AF_IGNORE_UNARMED; else if(m2 == moVizier && !isUnarmed(m1)) ; else return false; } if(flags & AF_APPROACH) { if(m2 == moLancer) ; else if((flags & AF_HORNS) && checkOrb(m1, itOrbHorns)) ; else return false; } if(!(flags & AF_IGNORE_UNARMED) && isUnarmed(m1)) return false; if(m2 == moGreater || m2 == moGreaterM) if(!(flags & (AF_MAGIC | AF_SWORD_INTO | AF_HORNS | AF_CRUSH))) return false; if(!(flags & (AF_GUN | AF_SWORD | AF_SWORD_INTO | AF_MAGIC))) if(c1 != c2 && !logical_adjacent(c1, m1, c2)) return false; if(!(flags & (AF_LANCE | AF_STAB | AF_BACK | AF_APPROACH | AF_GUN | AF_MAGIC))) if(c1 && c2 && againstRose(c1, c2) && !ignoresSmell(m1)) return false; if(m2 == moShadow && !(flags & AF_SWORD)) return false; if(isWorm(m2) && m2 != moTentacleGhost && !isDragon(m2)) return false; // dragon can't attack itself, or player who mounted it if(c1 && c2 && isWorm(c1->monst) && isWorm(c2->monst) && wormhead(c1) == wormhead(c2) && m1 != moTentacleGhost && m2 != moTentacleGhost) return false; // if(m2 == moTortoise && !(flags & AF_MAGIC)) return false; if(m2 == moRoseBeauty) if(!(flags & (AF_MAGIC | AF_LANCE | AF_GUN | AF_SWORD_INTO | AF_BULL | AF_CRUSH))) if(!isMimic(m1)) if(!checkOrb(m1, itOrbBeauty) && !checkOrb(m1, itOrbAether) && !checkOrb(m1, itOrbShield)) if(!c1 || !c2 || !withRose(c1,c2)) return false; if(m2 == moFlailer && !c2->stuntime) if(!(flags & (AF_MAGIC | AF_TOUGH | AF_EAT | AF_HORNS | AF_LANCE | AF_BACK | AF_SWORD_INTO | AF_BULL | AF_CRUSH))) return false; if(m2 == moVizier && c2->hitpoints > 1 && !c2->stuntime) if(!(flags & (AF_MAGIC | AF_TOUGH | AF_EAT | AF_HORNS | AF_LANCE | AF_BACK | AF_FAST | AF_BULL | AF_CRUSH))) return false; return true; } #if HDR struct stalemate1 { eMonster who; cell *moveto; cell *killed; cell *pushto; cell *comefrom; cell *swordlast[2], *swordtransit[2], *swordnext[2]; bool isKilled(cell *c); stalemate1(eMonster w, cell *mt, cell *ki, cell *pt, cell *cf) : who(w), moveto(mt), killed(ki), pushto(pt), comefrom(cf) {} }; #endif bool stalemate1::isKilled(cell *w) { if(w->monst == moNone || w == killed) return true; if(!moveto) return false; for(int b=0; b<2; b++) if((w == swordnext[b] || w == swordtransit[b]) && canAttack(moveto, who, w, w->monst, AF_SWORD)) return true; if(logical_adjacent(moveto, who, w) && moveto != comefrom) { int wid = neighborId(moveto, w); int wfrom = neighborId(moveto, comefrom); int flag = AF_APPROACH; if(wid >= 0 && wfrom >= 0 && anglestraight(moveto, wfrom, wid)) flag |= AF_HORNS; if(canAttack(moveto, who, w, w->monst, flag)) return true; } if(isNeighbor(w, comefrom) && comefrom == moveto && killed) { int d1 = neighborId(comefrom, w); int d2 = neighborId(comefrom, killed); int di = angledist(comefrom->type, d1, d2); if(di && items[itOrbSide1-1+di] && canAttack(moveto, who, w, w->monst, AF_SIDE)) return true; } if(logical_adjacent(comefrom, who, w) && logical_adjacent(moveto, who, w) && moveto != comefrom) if(canAttack(moveto, who, w, w->monst, AF_STAB)) return true; if(who == moPlayer && (killed || moveto != comefrom) && mirror::isKilledByMirror(w)) return true; if(w->monst == moIvyHead || w->monst == moIvyBranch || isMutantIvy(w)) return isChild(w, killed); if(isDragon(w->monst) && killed && isDragon(killed->monst) && killed->hitpoints) { cell *head1 = dragon::findhead(w); cell *head2 = dragon::findhead(killed); if(head1 == head2 && dragon::totalhp(head1) ==1) return true; } if((w->monst == moPair || isMagneticPole(w->monst)) && killed && w->move(w->mondir) == killed) return true; if(w->monst == moKrakenT && killed && killed->monst == moKrakenT && killed->hitpoints) { cell *head1 = w->move(w->mondir); cell *head2 = killed->move(killed->mondir); if(head1 == head2 && kraken::totalhp(head1) == 1) return true; } return false; } EX namespace stalemate { EX bool isKilled(cell *w) { for(int f=0; ftype; i++) if(c1->move(i) == c2) return true; return false; } EX bool isNeighborCM(cell *c1, cell *c2) { for(int i=0; itype; i++) if(createMov(c1, i) == c2) return true; return false; } EX int neighborId(cell *ofWhat, cell *whichOne) { for(int i=0; itype; i++) if(ofWhat->move(i) == whichOne) return i; return -1; } // how many monsters are near eMonster who_kills_me; EX bool flashWouldKill(cell *c, flagtype extra) { for(int t=0; ttype; t++) { cell *c2 = c->move(t); for(int u=0; utype; u++) { cell *c3 = c2->move(u); if(isWorm(c3)) continue; // immune to Flash if(c3->monst == moEvilGolem) continue; // evil golems don't count if(c3 != c && (c3->monst || isPlayerOn(c3)) && !stalemate::isKilled(c3)) { bool b = canAttack(NULL, moWitchFlash, c3, c3->monst, AF_MAGIC | extra); if(b) return true; } } } return false; } EX vector gun_targets(cell *c) { manual_celllister cl; vector dists; cl.add(c); dists.push_back(0); for(int i=0; i moves; EX bool nextturn; EX bool isMoveto(cell *c) { for(int i=0; iwall == waBoat) || (cf->wall == waBoat && c->wall == waSea); } EX bool krakensafe(cell *c) { return items[itOrbFish] || items[itOrbAether] || (c->item == itOrbFish && c->wall == waBoat) || (c->item == itOrbAether && c->wall == waBoat); } EX eMonster active_switch() { return eMonster(passive_switch ^ moSwitch1 ^ moSwitch2); } vector crush_now, crush_next; EX bool monstersnear(stalemate1& sm) { cell *c = sm.moveto; bool eaten = false; if(hardcore && sm.who == moPlayer) return false; int res = 0; bool fast = false; elec::builder b; if(elec::affected(c)) { who_kills_me = moLightningBolt; res++; } if(c->wall == waArrowTrap && c->wparam == 2) { who_kills_me = moArrowTrap; res++; } for(auto c1: crush_now) if(c == c1) { who_kills_me = moCrusher; res++; } if(sm.who == moPlayer || items[itOrbEmpathy]) { fast = (items[itOrbSpeed] && (items[itOrbSpeed] & 1)); } if(havewhat&HF_OUTLAW) { for(cell *c1: gun_targets(c)) if(c1->monst == moOutlaw && !c1->stuntime && !stalemate::isKilled(c1)) { res++; who_kills_me = moOutlaw; } } for(int t=0; ttype; t++) { cell *c2 = c->move(t); // consider monsters who attack from distance 2 if(c2) forCellEx(c3, c2) if(c3 != c) { // only these monsters can attack from two spots... if(!among(c3->monst, moLancer, moWitchSpeed, moWitchFlash)) continue; // take logical_adjacent into account if(c3->monst != moWitchFlash) if(!logical_adjacent(c3, c3->monst, c2) || !logical_adjacent(c2, c3->monst, c)) continue; if(elec::affected(c3) || stalemate::isKilled(c3)) continue; if(c3->stuntime > (sm.who == moPlayer ? 0 : 1)) 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, stalemate::isKilled(c2)?P_MONSTER:0)) continue; if(isPlayerOn(c2) && items[itOrbFire]) continue; } // flashwitches cannot attack if it would kill another enemy if(c3->monst == moWitchFlash && flashWouldKill(c3, 0)) continue; res++, who_kills_me = c3->monst; } // consider normal monsters if(c2 && isArmedEnemy(c2, sm.who) && !stalemate::isKilled(c2) && (c2->monst != moLancer || isUnarmed(sm.who) || !logical_adjacent(c, sm.who, c2))) { eMonster m = c2->monst; if(elec::affected(c2)) continue; if(fast && c2->monst != moWitchSpeed) continue; // Krakens just destroy boats if(c2->monst == moKrakenT && onboat(sm)) { if(krakensafe(c)) continue; else if(warningprotection(XLAT("This move appears dangerous -- are you sure?")) && res == 0) m = moWarning; else continue; } // they cannot attack through vines if(!canAttack(c2, c2->monst, c, sm.who, AF_NEXTTURN)) continue; if(c2->monst == moWorm || c2->monst == moTentacle || c2->monst == moHexSnake) { if(passable_for(c2->monst, c, c2, 0)) eaten = true; else if(c2->monst != moHexSnake) continue; } res++, who_kills_me = m; } } if(sm.who == moPlayer && res && (markOrb2(itOrbShield) || markOrb2(itOrbShell)) && !eaten) res = 0; if(sm.who == moPlayer && res && markOrb2(itOrbDomination) && c->monst) res = 0; return !!res; } namespace multi { bool aftermove; } EX bool monstersnear2(); int lastkills; EX bool multimove() { if(multi::cpid == 0) lastkills = tkills(); if(!multi::playerActive(multi::cpid)) return !monstersnear2(); cellwalker bcwt = cwt; cwt = multi::player[multi::cpid]; bool b = movepcto(multi::whereto[multi::cpid]); if(b) { multi::aftermove = true; multi::player[multi::cpid] = cwt; multi::whereto[multi::cpid].d = MD_UNDECIDED; int curkills = tkills(); multi::kills[multi::cpid] += (curkills - lastkills); lastkills = curkills; } cwt = bcwt; return b; } namespace multi { bool checkonly = false; } EX bool swordConflict(const stalemate1& sm1, const stalemate1& sm2) { if(items[itOrbSword] || items[itOrbSword2]) for(int b=0; b<2; b++) if(sm1.comefrom == sm2.swordlast[b] || sm1.comefrom == sm2.swordtransit[b] || sm1.comefrom == sm2.swordnext[b]) if(sm1.moveto == sm2.swordlast[b] || sm1.moveto == sm2.swordtransit[b] || sm1.moveto == sm2.swordnext[b]) return true; return false; } EX bool monstersnear2() { multi::cpid++; bool b = false; bool recorduse[ittypes]; for(int i=0; i sw(passive_switch, passive_switch); // check for safe orbs and switching first for(auto &sm: stalemate::moves) if(sm.who == moPlayer) { if(hasSafeOrb(sm.moveto)) { multi::cpid--; return 0; } if(sm.moveto->item && itemclass(sm.moveto->item) == IC_TREASURE) passive_switch = active_switch(); if(items[itOrbMagnetism]) forCellEx(c2, sm.moveto) if(canPickupItemWithMagnetism(c2, sm.comefrom)) { if(itemclass(c2->item) == IC_TREASURE) passive_switch = active_switch(); if(hasSafeOrb(c2)) { multi::cpid--; return 0; } } } for(int i=0; i 8) { b = true; who_kills_me = moAirball; } } for(int i=0; !b && i 1) wcw = &multi::player[multi::cpid].at; dynamicval x5(*wcw, c); dynamicval x6(stalemate::nextturn, true); dynamicval x7(sword::dir[multi::cpid], who == moPlayer ? sword::shift(comefrom, c, sword::dir[multi::cpid]) : sword::dir[multi::cpid]); for(int b=0; b<2; b++) { if(who == moPlayer) { sm.swordnext[b] = sword::pos(multi::cpid, b); sm.swordtransit[b] = NULL; if(sm.swordnext[b] && sm.swordnext[b] != sm.swordlast[b] && !isNeighbor(sm.swordlast[b], sm.swordnext[b])) { forCellEx(c2, sm.swordnext[b]) if(c2 != c && c2 != comefrom && isNeighbor(c2, S3==3 ? sm.swordlast[b] : *wcw)) sm.swordtransit[b] = c2; if(S3 == 4) forCellEx(c2, c) if(c2 != comefrom && isNeighbor(c2, sm.swordlast[b])) sm.swordtransit[b] = c2; } } else { sm.swordnext[b] = sm.swordtransit[b] = NULL; } } stalemate::moves.push_back(sm); // dynamicval x7(stalemate::who, who); bool b; if(who == moPlayer && c->wall == waBigStatue) { eWall w = comefrom->wall; c->wall = waNone; if(doesnotFall(comefrom)) comefrom->wall = waBigStatue; b = monstersnear2(); comefrom->wall = w; c->wall = waBigStatue; } else if(who == moPlayer && isPushable(c->wall)) { eWall w = c->wall; c->wall = waNone; b = monstersnear2(); c->wall = w; } else { b = monstersnear2(); } stalemate::moves.pop_back(); return b; } EX bool petrify(cell *c, eWall walltype, eMonster m) { destroyHalfvine(c); destroyTrapsOn(c); playSound(c, "die-troll"); if(walltype == waIcewall && !isIcyLand(c->land)) return false; if(c->land == laWestWall) return false; if(isWateryOrBoat(c) && c->land == laWhirlpool) { c->wall = waSea; return false; } if(c->wall == waRoundTable) return false; if(walltype == waGargoyle && cellUnstableOrChasm(c)) walltype = waGargoyleFloor; else if(walltype == waGargoyle && isWatery(c)) walltype = waGargoyleBridge; else if(walltype == waPetrified && isWatery(c)) walltype = waPetrifiedBridge; else if((c->wall == waTempBridge || c->wall == waTempBridgeBlocked) && c->land == laWhirlpool) { c->wall = waTempBridgeBlocked; return true; } else if(!doesnotFall(c)) { fallingFloorAnimation(c, walltype, m); return true; } if(isReptile(c->wall)) kills[moReptile]++; destroyHalfvine(c); c->wall = walltype; c->wparam = m; c->item = itNone; return true; } EX void killIvy(cell *c, eMonster who) { if(c->monst == moIvyDead) return; if(checkOrb(who, itOrbStone)) petrify(c, waPetrified, c->monst); c->monst = moIvyDead; // NEWYEARFIX for(int i=0; itype; i++) if(c->move(i)) if(isIvy(c->move(i)) && c->move(i)->mondir == c->c.spin(i)) killIvy(c->move(i), who); } EX void prespill(cell* c, eWall t, int rad, cell *from) { if(againstWind(c, from)) return; // these monsters block spilling if(c->monst == moSeep || c->monst == moVineSpirit || c->monst == moShark || c->monst == moGreaterShark || c->monst == moParrot || c->monst == moCShark) return; // turn statues into Slimes! if(among(c->wall, waBigStatue, waTerraWarrior) && t != waNone) { c->wall = waNone; c->monst = moSlimeNextTurn; } // slimedeath spill if((c->monst == moSlime || c->monst == moSlimeNextTurn) && t == waNone) { c->wall = waNone; attackMonster(c, 0, moNone); } if(c->wall == waClosedGate) { c->wall = waPalace; return; } // no slime in Whirlpool if(c->land == laWhirlpool) return; // these walls block spilling completely if(c->wall == waIcewall || c->wall == waBarrier || c->wall == waWarpGate || c->wall == waDeadTroll || c->wall == waDeadTroll2 || c->wall == waDune || c->wall == waAncientGrave || c->wall == waThumperOff || c->wall == waThumperOn || c->wall == waFreshGrave || c->wall == waColumn || c->wall == waPartialFire || c->wall == waDeadwall || c->wall == waWaxWall || c->wall == waCamelot || c->wall == waRoundTable || c->wall == waBigStatue || c->wall == waRed1 || c->wall == waRed2 || c->wall == waRed3 || c->wall == waTower || c->wall == waPalace || c->wall == waPlatform || c->wall == waStone || c->wall == waTempWall || c->wall == waTempFloor || c->wall == waTempBridge || c->wall == waPetrifiedBridge || c->wall == waTempBridgeBlocked || c->wall == waSandstone || c->wall == waCharged || c->wall == waGrounded || c->wall == waMetal || c->wall == waSaloon || c->wall == waFan || c->wall == waBarrowDig || c->wall == waBarrowWall || c->wall == waMirrorWall) return; if(c->wall == waFireTrap) { if(c->wparam == 0) c->wparam = 1; return; } if(c->wall == waExplosiveBarrel) explodeBarrel(c); destroyTrapsOn(c); // 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 == waBigTree || c->wall == waSmallTree || c->wall == waTemporary || c->wall == waVinePlant || isFire(c) || c->wall == waBonfireOff || c->wall == waVineHalfA || c->wall == waVineHalfB || c->wall == waCamelotMoat || c->wall == waSea || c->wall == waCTree || c->wall == waRubble || c->wall == waGargoyleFloor || c->wall == waGargoyle || c->wall == waRose || c->wall == waPetrified || c->wall == waPetrifiedBridge || c->wall == waRuinWall) t = waTemporary; if(c->wall == waSulphur) { // remove the center as it would not look good for(int i=0; itype; i++) if(c->move(i) && c->move(i)->wall == waSulphurC) c->move(i)->wall = waSulphur; } if(isReptile(c->wall)) { if(c->monst || isPlayerOn(c)) kills[moReptile]++; else c->monst = moReptile, c->stuntime = 3, c->hitpoints = 3; } destroyHalfvine(c); if(c->wall == waTerraWarrior) kills[waTerraWarrior]++; c->wall = t; // destroy items... c->item = itNone; // block spill if(t == waTemporary) return; // cwt.at->item = itNone; if(rad) for(int i=0; itype; i++) if(c->move(i)) prespill(c->move(i), t, rad-1, c); } EX 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->move(i)) spillfix(c->move(i), t, rad-1); } EX void spill(cell* c, eWall t, int rad) { prespill(c,t,rad, c); spillfix(c,t,rad); } EX void degradeDemons() { addMessage(XLAT("You feel more experienced in demon fighting!")); int dcs = isize(dcal); for(int i=0; imonst == moGreaterM || c->monst == moGreater) achievement_gain("DEMONSLAYER"); if(c->monst == moGreaterM) c->monst = moLesserM; if(c->monst == moGreater) c->monst = moLesser; shmup::degradeDemons(); } } EX bool earthFloor(cell *c) { if(c->monst) return false; if(c->wall == waDeadwall) { c->wall = waDeadfloor; return true; } if(c->wall == waDune) { c->wall = waNone; return true; } if(c->wall == waStone && c->land != laTerracotta) { c->wall = waNone; return true; } if(c->wall == waAncientGrave || c->wall == waFreshGrave || c->wall == waRuinWall) { c->wall = waNone; return true; } if((c->wall == waSea || c->wall == waNone) && c->land == laOcean) { c->wall = waCIsland; return true; } if(c->wall == waSea && c->land == laCaribbean) { c->wall = waCIsland; return true; } if(c->wall == waSea && c->land == laWarpSea) c->wall = waNone; if(c->wall == waBoat && c->land == laWarpSea) c->wall = waStrandedBoat; if(c->wall == waMercury) { c->wall = waNone; return true; } if((c->wall == waBarrowDig || c->wall == waBarrowWall) && c->land == laBurial) { c->item = itNone; c->wall = waNone; return true; } if(c->wall == waPlatform && c->land == laMountain) { c->wall = waNone; return true; } if(c->wall == waChasm && c->land == laHunting) { c->wall = waNone; return true; } return false; } EX bool earthWall(cell *c) { if(c->wall == waDeadfloor || c->wall == waDeadfloor2 || c->wall == waEarthD) { c->item = itNone; c->wall = waDeadwall; return true; } if(c->wall == waNone && c->land == laMountain) { c->wall = waPlatform; return true; } if(c->wall == waNone && c->land == laDesert) { c->item = itNone; c->wall = waDune; return true; } if(c->wall == waNone && c->land == laRuins) { c->item = itNone; c->wall = waRuinWall; return true; } if(c->wall == waNone && isElemental(c->land)) { c->item = itNone; c->wall = waStone; return true; } if(c->wall == waNone && c->land == laRedRock) { c->item = itNone; c->wall = waRed3; return true; } if(c->wall == waNone && c->land == laSnakeNest) { c->item = itNone; c->wall = waRed3; return true; } if(c->wall == waNone && c->land == laBurial) { c->item = itNone; c->wall = waBarrowDig; return true; } if(c->wall == waNone && c->land == laHunting) { c->item = itNone; c->wall = waChasm; return true; } if(c->wall == waNone && c->land == laTerracotta) { c->wall = waMercury; return true; } if(c->wall == waArrowTrap && c->land == laTerracotta) { destroyTrapsOn(c); c->wall = waMercury; return true; } if(c->wall == waCIsland || c->wall == waCIsland2 || (c->wall == waNone && c->land == laOcean)) { c->item = itNone; c->wall = waSea; if(c->land == laOcean) c->landparam = 40; return true; } return false; } EX bool snakepile(cell *c, eMonster m) { if(c->wall == waSea && c->land == laOcean) { c->land = laBrownian, c->landparam = 0; } if(c->land == laWestWall) return false; if(c->land == laBrownian) { if(c->wall == waNone) { #if CAP_COMPLEX2 c->landparam += brownian::level; #endif return true; } if(c->wall == waSea || c->wall == waBoat) { c->wall = waNone; c->landparam++; return true; } } if(c->item && c->wall != waRed3) c->item = itNone; if(c->wall == waRed1 || c->wall == waOpenGate) c->wall = waRed2; else if(c->wall == waRed2) c->wall = waRed3; else if(doesFall(c)) return false; else if((c->wall == waSea && c->land == laLivefjord)) c->wall = waNone; else if((c->wall == waSea && isWarpedType(c->land))) c->wall = waNone; else if(isGravityLand(c->land)) { if(m == moHexSnake) c->wall = waPlatform; else c->wall = waDeadTroll2; } else if(c->wall == waNone || isAlchAny(c) || c->wall == waCIsland || c->wall == waCIsland2 || c->wall == waOpenPlate || c->wall == waClosePlate || c->wall == waMineUnknown || c->wall == waMineOpen || isReptile(c->wall)) { if(isReptile(c->wall)) kills[moReptile]++; c->wall = waRed1; if(among(m, moDarkTroll, moBrownBug)) c->wall = waDeadfloor2; } else if(c->wall == waDeadfloor) c->wall = waDeadfloor2; else if(c->wall == waDeadfloor2) { if(m == moDarkTroll && c->land == laDeadCaves) return false; else c->wall = waDeadwall; } else if(c->wall == waRubble || c->wall == waGargoyleFloor || c->wall == waGargoyleBridge || c->wall == waTempFloor || c->wall == waTempBridge || c->wall == waPetrifiedBridge) { if(c->land == laWhirlpool) return false; c->wall = waRed2; if(m == moDarkTroll) c->wall = waDeadwall; } else if(c->wall == waCavefloor) c->wall = waCavewall; else if(c->wall == waSea && c->land == laCaribbean) c->wall = waCIsland; else if(c->wall == waSea && c->land == laWhirlpool) return false; else if(c->wall == waSea) c->wall = waNone; else if(isWateryOrBoat(c) || c->wall == waFrozenLake) c->wall = waNone; else if(isWateryOrBoat(c) || c->wall == waFrozenLake) c->wall = waNone; else if(cellHalfvine(c)) { destroyHalfvine(c, waRed1); if(c->wall == waRed1 && m == moDarkTroll) c->wall = waDeadfloor2; } else return false; return true; } EX bool makeflame(cell *c, int timeout, bool checkonly) { if(!checkonly) destroyTrapsOn(c); if(itemBurns(c->item)) { if(checkonly) return true; if(c->cpdist <= 7) addMessage(XLAT("%The1 burns!", c->item)); c->item = itNone; } if(cellUnstable(c)) { if(checkonly) return true; doesFall(c); } else if(c->wall == waChasm || c->wall == waOpenGate || c->wall == waRed2 || c->wall == waRed3 || c->wall == waTower) return false; else if(c->wall == waBoat) { if(isPlayerOn(c) && markOrb(itOrbWinter)) { addMessage(XLAT("%the1 protects your boat!", itOrbWinter)); } if(checkonly) return true; if(c->cpdist <= 7) addMessage(XLAT("%The1 burns!", winf[c->wall].name)); drawFireParticles(c, 24); placeWater(c, c); if(isIcyLand(c)) HEAT(c) += 1; } else if(c->wall == waNone && c->land == laCocytus) { if(checkonly) return true; c->wall = waLake, HEAT(c) += 1; } else if(c->wall == waFireTrap) { if(checkonly) return true; if(c->wparam == 0) c->wparam = 1; } else if(c->wall == waFrozenLake) { if(checkonly) return true; drawParticles(c, MELTCOLOR, 8, 8); c->wall = waLake, HEAT(c) += 1; } else if(c->wall == waIcewall) { if(checkonly) return true; drawParticles(c, MELTCOLOR, 8, 8); c->wall = waNone; } else if(c->wall == waMineMine) { if(checkonly) return true; explodeMine(c); } else if(c->wall != waCTree && c->wall != waBigTree && c->wall != waSmallTree && c->wall != waVinePlant && !passable(c, NULL, P_MONSTER | P_MIRROR) && c->wall != waSaloon && c->wall != waRose) return false; // reptiles are able to use the water to put the fire off else if(c->wall == waReptileBridge) return false; else if(c->wall == waDock) { if(checkonly) return true; c->wall = waBurningDock; c->wparam = 3; return false; } else { eWall w = eternalFire(c) ? waEternalFire : waFire; if(!checkonly) drawFireParticles(c, 10); if(w == c->wall) return false; if(checkonly) return true; if(isReptile(c->wall)) kills[moReptile]++; destroyHalfvine(c); if(!isFire(c)) c->wparam = 0; c->wall = w; c->wparam = max(c->wparam, (char) timeout); if(c->land == laBrownian) c->landparam = 0; } return true; } EX void explosion(cell *c, int power, int central) { playSound(c, "explosion"); drawFireParticles(c, 30, 150); brownian::dissolve_brownian(c, 2); makeflame(c, central, false); forCellEx(c2, c) { destroyTrapsOn(c2); brownian::dissolve_brownian(c2, 1); if(c2->wall == waRed2 || c2->wall == waRed3) c2->wall = waRed1; else if(c2->wall == waDeadTroll || c2->wall == waDeadTroll2 || c2->wall == waPetrified || c2->wall == waGargoyle) { c2->wall = waNone; makeflame(c2, power/2, false); } else if(c2->wall == waPetrifiedBridge || c2->wall == waGargoyleBridge) { placeWater(c, c); } else if(c2->wall == waPalace || c2->wall == waOpenGate || c2->wall == waClosedGate || c2->wall == waSandstone || c2->wall == waMetal || c2->wall == waSaloon || c2->wall == waRuinWall) { c2->wall = waNone; makeflame(c2, power/2, false); } else if(c2->wall == waTower) c2->wall = waRubble; else if(c2->wall == waBarrowWall) c2->wall = waBarrowDig; else if(c2->wall == waBarrowDig) c2->wall = waNone; else if(c2->wall == waFireTrap) { if(c2->wparam == 0) c2->wparam = 1; } else if(c2->wall == waExplosiveBarrel) explodeBarrel(c2); else makeflame(c2, power, false); } } EX void explodeMine(cell *c) { if(c->wall != waMineMine) return; c->wall = waMineOpen; explosion(c, 20, 20); auto_teleport_charges(); } EX void explodeBarrel(cell *c) { if(c->wall != waExplosiveBarrel) return; c->wall = waNone; explosion(c, 20, 20); } EX bool mayExplodeMine(cell *c, eMonster who) { if(c->wall != waMineMine) return false; if(ignoresPlates(who)) return false; explodeMine(c); return true; } EX void stunMonster(cell *c2, eMonster killer, flagtype flags) { int newtime = ( c2->monst == moFatGuard ? 2 : c2->monst == moSkeleton && c2->land != laPalace && c2->land != laHalloween ? 7 : c2->monst == moTerraWarrior ? min(int(c2->stuntime + 8 - c2->hitpoints), 7) : isMetalBeast(c2->monst) ? 7 : c2->monst == moTortoise ? 7 : c2->monst == moReptile ? 7 : isPrincess(c2->monst) ? 6 : // spear stunning isBull(c2->monst) ? 3 : (c2->monst == moGreater || c2->monst == moGreaterM) ? 5 : c2->monst == moButterfly ? 2 : c2->monst == moDraugr ? 1 : c2->monst == moVizier ? 0 : c2->monst == moHedge ? 1 : c2->monst == moFlailer ? 1 : c2->monst == moSalamander ? 6 : c2->monst == moBrownBug ? 3 : 3); if(killer == moArrowTrap) newtime = min(newtime + 3, 7); if(!isMetalBeast(c2->monst) && !among(c2->monst, moSkeleton, moReptile, moSalamander, moTortoise, moBrownBug)) { c2->hitpoints--; if(c2->monst == moPrincess) playSound(c2, princessgender() ? "hit-princess" : "hit-prince"); } if(c2->stuntime < newtime) c2->stuntime = newtime; if(isBull(c2->monst)) c2->mondir = NODIR; checkStunKill(c2); } EX bool attackJustStuns(cell *c2, flagtype f, eMonster attacker) { if(f & AF_HORNS) return hornStuns(c2); else if(attacker == moArrowTrap && arrow_stuns(c2->monst)) return true; else if((f & AF_SWORD) && c2->monst == moSkeleton) return false; else if(f & (AF_CRUSH | AF_MAGIC | AF_FALL | AF_EAT | AF_GUN)) return false; else return isStunnable(c2->monst) && c2->hitpoints > 1; } EX void flameHalfvine(cell *c, int val) { if(itemBurns(c->item)) { addMessage(XLAT("%The1 burns!", c->item)); c->item = itNone; } c->wall = waPartialFire; c->wparam = val; } EX void minerEffect(cell *c) { eWall ow = c->wall; if(c->wall == waOpenGate || c->wall == waFrozenLake || c->wall == waBoat || c->wall == waStrandedBoat || c->wall == waCIsland || c->wall == waCIsland2 || c->wall == waTrapdoor || c->wall == waGiantRug) ; else if(c->wall == waMineUnknown || c->wall == waMineMine || c->wall == waMineOpen) c->wall = waMineOpen; else if(c->wall == waRed2) c->wall = waRed1; else if(c->wall == waRed3) c->wall = waRed2; else if(isReptile(c->wall)) c->wparam = 1; // wake up next turn else if(c->wall == waTempFloor) c->wall = waChasm; else if(c->wall == waTempBridge || c->wall == waPetrifiedBridge || c->wall == waTempBridgeBlocked) placeWater(c, NULL); else if(doesFall(c)) ow = waNone; else c->wall = waNone; if(c->wall != ow && ow) drawParticles(c, winf[ow].color, 16); } EX void killMutantIvy(cell *c, eMonster who) { if(checkOrb(who, itOrbStone)) petrify(c, waPetrified, moMutant); removeIvy(c); for(int i=0; itype; i++) if(c->move(i)->mondir == c->c.spin(i) && (isMutantIvy(c->move(i)) || c->move(i)->monst == moFriendlyIvy)) killMutantIvy(c->move(i), who); } EX void killMonster(cell *c, eMonster who, flagtype deathflags IS(0)) { eMonster m = c->monst; DEBBI(DF_TURN, ("killmonster ", dnameof(m))); if(!m) return; if(m == moKrakenH) return; if(m == moKrakenT) { if(c->hitpoints && m == moKrakenT) kills[moKrakenT]++; c->hitpoints = 0; c->stuntime = 1; cell *head = kraken::head(c); if(kraken::totalhp(head) == 0) kraken::kill(head, who); return; } if(isDragon(m)) { if(c->hitpoints && m != moDragonHead) kills[moDragonTail]++; c->hitpoints = 0; c->stuntime = 1; cell *head = dragon::findhead(c); if(dragon::totalhp(head) == 0) dragon::kill(head, who); } if(isWorm(c) && m != moTentacleGhost) return; bool fallanim = (deathflags & AF_FALL) && m != moMimic; int pcount = fallanim ? 0 : 16; if(m == moShadow) return; #if CAP_HISTORY if(!isBug(m) && !isAnyIvy(m)) { history::killhistory.push_back(make_pair(c,m)); } #endif if(m == moGolemMoved) m = moGolem; if(m == moKnightMoved) m = moKnight; if(m == moSlimeNextTurn) m = moSlime; if(m == moLesserM) m = moLesser; if(m == moGreater) m = moLesser; if(m == moGreaterM) m = moLesser; if(isPrincess(m)) m = moPrincess; if(m == moTentacleGhost) m = moGhost; if(m == moHunterGuard) m = moHunterDog; if(m == moHunterChanging) m = moHunterDog; if(m == moWolfMoved) m = moWolf; if(!isBulletType(m)) kills[m]++; if(saved_tortoise_on(c)) c->item = itNone; if(!c->item) if(m == moButterfly && (deathflags & AF_BULL)) c->item = itBull; if(isRatling(m) && c->wall == waBoat) { bool avenge = false; for(int i=0; itype; i++) if(!isWarpedType(c->move(i)->land)) avenge = true; if(avenge) { avengers += 2; } } if(m == moMirrorSpirit && who != moMimic && !(deathflags & (AF_MAGIC | AF_CRUSH))) { kills[m]--; mirrorspirits++; } if(isMutantIvy(m) || m == moFriendlyIvy) { pcount = 0; killMutantIvy(c, who); } if(m == moPrincess) { princess::info *i = princess::getPrincessInfo(c); if(i) { i->princess = NULL; if(i->bestdist == OUT_OF_PALACE) { items[itSavedPrincess]--; if(items[itSavedPrincess] < 0) { printf("princess below 0\n"); items[itSavedPrincess] = 0; } if(items[itSavedPrincess] == 0 && !inv::on) { items[itOrbLove] = 0; princess::reviveAt = gold(NO_LOVE) + 20; } } if(princess::challenge) showMissionScreen(); } } if(m == moGargoyle && c->wall != waMineMine) { bool connected = false; if(isGravityLand(c->land)) { for(int i=0; itype; i++) { cell *c2 = c->move(i); if(c2->wall == waPlatform || c2->wall == waGargoyle || c2->wall == waBarrier || c2->wall == waDeadTroll || c2->wall == waDeadTroll2 || c2->wall == waTrunk || c2->wall == waPetrified || isAlchAny(c2->wall)) connected = true; } } else { for(int i=0; itype; i++) { cell *c2 = c->move(i); if(!cellUnstableOrChasm(c2) && !isWatery(c2)) connected = true; } } if(connected) petrify(c, waGargoyle, m), pcount = 0; } if(m == moIceGolem) { if(petrify(c, waIcewall, m)) pcount = 0; heat::affect(c, -1); forCellEx(c2, c) heat::affect(c2, -.5); } if(m == moTroll) { petrify(c, waDeadTroll, m); pcount = 0; for(int i=0; itype; i++) if(c->move(i)) { c->move(i)->item = itNone; if(c->move(i)->wall == waDeadwall || c->move(i)->wall == waDeadfloor2) c->move(i)->wall = waCavewall; if(c->move(i)->wall == waDeadfloor) c->move(i)->wall = waCavefloor; } } if(m == moFjordTroll || m == moForestTroll || m == moStormTroll) { petrify(c, waDeadTroll2, m); } if(m == moMiner) { pcount = 32; playSound(c, "splash" + pick12()); destroyHalfvine(c); minerEffect(c); brownian::dissolve_brownian(c, 1); for(int i=0; itype; i++) if(passable(c->move(i), c, P_MONSTER | P_MIRROR | P_CLIMBUP | P_CLIMBDOWN)) { destroyHalfvine(c->move(i)); minerEffect(c->move(i)); brownian::dissolve_brownian(c->move(i), 1); if(c->move(i)->monst == moSlime || c->move(i)->monst == moSlimeNextTurn) killMonster(c->move(i), who); } } if(m == moOrangeDog) { if(pcount) for(int i=0; i<8; i++) { drawParticle(c, 0xFFFFFF); drawParticle(c, 0x202020); } pcount = 0; } if(m == moDraugr) { if(pcount) drawParticles(c, 0x804000, 8); pcount = 0; } if(m == moPalace) { if(pcount) { pcount = 4; for(int i=0; i<12; i++) drawParticle(c, 0x20C020); } } if(m == moPrincess) { playSound(c, princessgender() ? "die-princess" : "die-prince"); } if(m == moVineBeast) petrify(c, waVinePlant, m), pcount = 0; if(isBird(m)) moveEffect(c, c, moDeadBird, -1); if(m == moAcidBird) { playSound(c, "die-bomberbird"); pcount = 64; #if CAP_COMPLEX2 brownian::dissolve(c, 1); #endif } if(m == moBomberbird || m == moTameBomberbird) { pcount = 0; playSound(c, "die-bomberbird"); if(c->wall == waNone || c->wall == waMineUnknown || c->wall == waMineOpen || c->wall == waCavefloor || c->wall == waDeadfloor || c->wall == waDeadfloor2 || c->wall == waRubble || c->wall == waGargoyleFloor || c->wall == waCIsland || c->wall == waCIsland2 || c->wall == waStrandedBoat || c->wall == waRed1 || c->wall == waGiantRug) { c->wall = waMineMine; if(c->item) explodeMine(c); else if(c->land == laMinefield) c->landparam = 1; } else if(c->wall == waFrozenLake) c->wall = waLake; else if(c->wall == waGargoyleBridge) placeWater(c, c); else if(c->wall == waRed3 || c->wall == waRed2) { c->wall = waRed1; for(int i=0; itype; i++) if(c->move(i)->wall == waRed3) c->move(i)->wall = waRed2; c->item = itNone; } eWall w = c->wall; if(isFire(c) || c->wall == waRose || isReptile(c->wall)) { c->wall = waMineMine; explodeMine(c); if(isReptile(w)) kills[moReptile]++; if(w == waReptile) c->wall = waChasm; if(w == waReptileBridge) placeWater(c, NULL); } } if(m == moVineSpirit) { pcount = 32; playSound(c, "die-vinespirit"); destroyHalfvine(c); if(!isFire(c)) c->wall = waNone; } if(m == moRedTroll) { playSound(c, "die-troll"); if(doesFall(c)) fallingFloorAnimation(c, waRed1, m), pcount = 0; else if(snakepile(c, m)) pcount = 0; } if(m == moBrownBug) { if(doesFall(c)) ; else if(snakepile(c, m)) pcount = 0; } if(m == moDarkTroll) { playSound(c, "die-troll"); if(doesFall(c)) fallingFloorAnimation(c, waDeadwall, m), pcount = 0; else if(c->wall == waRed1 || c->wall == waRed2 || c->wall == waRed3) c->wall = waDeadwall, pcount = 0; else if(snakepile(c, m)) pcount = 0; } if(isWitch(m) && (isFire(c) || passable(c, NULL, P_MONSTER)) && !c->item) { if(m == moWitchFire) c->item = itOrbFire; if(m == moWitchFlash) c->item = itOrbFlash; if(m == moWitchGhost) c->item = itOrbAether; if(m == moWitchWinter) c->item = itOrbWinter; if(m == moWitchSpeed) c->item = itOrbSpeed; if(isFire(c) && itemBurns(c->item)) c->item = itNone; } if(checkOrb(who, itOrbStone)) petrify(c, waPetrified, m), pcount = 0; if(m == moFireFairy) { drawFireParticles(c, 16); pcount = 0; playSound(c, "die-fairy"); playSound(c, "fire"); makeflame(c, 50, false); } if(c->monst == moMetalBeast2 && !c->item && who == moLightningBolt && c->wall != waPetrified && c->wall != waChasm) c->item = itFulgurite; // this is actually redundant in many cases if(m == moPyroCultist && c->item == itNone && c->wall != waChasm && c->wall != waPetrified) { // a reward for killing him before he shoots! c->item = itOrbDragon; } if(m == moOutlaw && (c->item == itNone || c->item == itRevolver) && c->wall != waChasm) c->item = itBounty; // note: an Orb appears underwater! if(m == moWaterElemental && c->item == itNone) c->item = itOrbWater; if(m == moPirate && isOnCIsland(c) && c->item == itNone && ( eubinary || (c->master->alt && celldistAlt(c) <= 2-getDistLimit()) || isHaunted(c->land)) && geometry != gCrystal) { bool toomany = false; for(int i=0; itype; i++) { cell *c2 = c->move(i); if(c2 && c2->item == itCompass) toomany = true; if(c2 && BITRUNCATED) for(int j=0; jtype; j++) if(c2->move(j) && c2->move(j)->item == itCompass) toomany = true; } if(!toomany) c->item = itCompass; } if(m == moSlime) { pcount = 0; drawParticles(c, winf[c->wall].color, 80, 200); playSound(c, "splash" + pick12()); c->monst = moNone; spill(c, c->wall, 2); } // if(c->monst == moShark) c->heat += 1; // if(c->monst == moGreaterShark) c->heat += 10; if(isIcyLand(c)) { if(m == moCultist) HEAT(c) += 3; if(m == moPyroCultist) HEAT(c) += 6; if(m == moLesser) HEAT(c) += 10; } if(m == moLesser && !(kills[m] % 10)) degradeDemons(); if(isIvy(c)) { pcount = 0; eMonster m = c->monst; /*if((m == moIvyBranch || m == moIvyHead) && c->move(c->mondir)->monst == moIvyRoot) ivynext(c, moIvyNext); */ killIvy(c, who); if(m == moIvyBranch || m == moIvyHead || m == moIvyNext) { int qty = 0; cell *c2 = c->move(c->mondir); for(int i=0; itype; i++) if(c2->move(i)->monst == moIvyWait && c2->move(i)->mondir == c2->c.spin(i)) qty++; if(c->move(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->move(c->mondir)->monst = moIvyHead; } } } else if(c->monst == moTentacleGhost) c->monst = moTentacletail; else c->monst = moNone; if(m == moPair && c->move(c->mondir)->monst == moPair) killMonster(c->move(c->mondir), who, deathflags); if(isMagneticPole(m) && c->move(c->mondir)->monst == otherpole(m)) killMonster(c->move(c->mondir), who, deathflags); if(m == moEarthElemental) earthWall(c); if(m == moAlbatross && items[itOrbLuck]) useupOrb(itOrbLuck, items[itOrbLuck] / 2); if(m == moAirElemental) { airmap.clear(); for(int i=0; imonst == moAirElemental) airmap.push_back(make_pair(dcal[i],0)); buildAirmap(); } if(m == moMimic) { for(auto& m: mirror::mirrors) if(c == m.second.at) { drawParticles(c, mirrorcolor(m.second.mirrored), pcount); if(c->wall == waMirrorWall) drawParticles(c, mirrorcolor(!m.second.mirrored), pcount); } pcount = 0; } drawParticles(c, minf[m].color, pcount); if(fallanim) { fallingMonsterAnimation(c, m); } } EX void fightmessage(eMonster victim, eMonster attacker, bool stun, flagtype flags) { if(isBird(attacker)) { playSound(NULL, "hit-axe"+pick123()); addMessage(XLAT("%The1 claws %the2!", attacker, victim)); } else if(isGhost(attacker)) addMessage(XLAT("%The1 scares %the2!", attacker, victim)); else if(isSlimeMover(attacker) && !stun) { playSound(NULL, "hit-crush"+pick123()); addMessage(XLAT("%The1 eats %the2!", attacker, victim)); } else if(flags & AF_EAT) { playSound(NULL, "hit-crush"+pick123()); addMessage(XLAT("%The1 eats %the2!", attacker, victim)); } else if(attacker == moLancer) { playSound(NULL, "hit-rose"); addMessage(XLAT("%The1 pierces %the2!", attacker, victim)); } else if(attacker == moEarthElemental) { playSound(NULL, "hit-crush"+pick123()); addMessage(XLAT("%The1 punches %the2!", attacker, victim)); } else if(attacker == moPlayer) { if(flags & (AF_SWORD | AF_SWORD_INTO)) { playSound(NULL, "hit-axe"+pick123()); addMessage(XLAT("You slash %the1.", victim)); if(victim == moGoblin) achievement_gain("GOBLINSWORD"); } else if(victim == moKrakenT || victim == moDragonTail || victim == moDragonHead) { playSound(NULL, "hit-sword"+pick123()); addMessage(XLAT("You hit %the1.", victim)); // normal } else if(stun && victim == moVizier) { playSound(NULL, "hit-sword"+pick123()); addMessage(XLAT("You hit %the1.", victim)); // normal } else if(stun) { playSound(NULL, "hit-sword"+pick123()); addMessage(XLAT("You stun %the1.", victim)); // normal } else if(isNonliving(victim)) { playSound(NULL, "hit-sword"+pick123()); addMessage(XLAT("You destroy %the1.", victim)); // normal } else if(flags & AF_STAB) { playSound(NULL, "hit-axe"+pick123()); addMessage(XLAT("You stab %the1.", victim)); // normal } else if(flags & AF_APPROACH) { playSound(NULL, "hit-sword"+pick123()); if(victim == moLancer) addMessage(XLAT("You trick %the1.", victim)); // normal else addMessage(XLAT("You pierce %the1.", victim)); // normal } else if(!peace::on) { playSound(NULL, "hit-sword"+pick123()); addMessage(XLAT("You kill %the1.", victim)); // normal } } else { if(victim == moKrakenT || victim == moDragonTail || victim == moDragonHead) { playSound(NULL, "hit-crush"+pick123()); addMessage(XLAT("%The1 hits %the2.", attacker, victim)); // normal } else if(stun && victim == moVizier) { playSound(NULL, "hit-sword"+pick123()); addMessage(XLAT("%The1 hits %the2.", attacker, victim)); // normal } else if(stun) { playSound(NULL, "hit-sword"+pick123()); addMessage(XLAT("%The1 attacks %the2!", attacker, victim)); // normal } else if(isNonliving(victim)) { playSound(NULL, "hit-sword"+pick123()); addMessage(XLAT("%The1 destroys %the2!", attacker, victim)); // normal } else if(flags & AF_STAB) { playSound(NULL, "hit-axe"+pick123()); addMessage(XLAT("%The1 stabs %the2.", attacker, victim)); } else if(flags & AF_APPROACH) { playSound(NULL, "hit-sword"+pick123()); addMessage(XLAT("%The1 tricks %the2.", attacker, victim)); } else { playSound(NULL, "hit-sword"+pick123()); addMessage(XLAT("%The1 kills %the2!", attacker, victim)); } } } EX void fallMonster(cell *c, flagtype flags IS(0)) { attackMonster(c, flags, moNone); } EX bool notthateasy(eMonster m) { return isMultitile(m) || isStunnable(m) || m == moDraugr; } EX bool attackMonster(cell *c, flagtype flags, eMonster killer) { if((flags & AF_GETPLAYER) && isPlayerOn(c)) { killThePlayerAt(killer, c, flags); return true; } eMonster m = c->monst; int tk = tkills(); int tkt = killtypes(); bool dostun = attackJustStuns(c, flags, killer); if((flags & AF_BULL) && c->monst == moVizier && c->hitpoints > 1) { dostun = true; c->stuntime = 2; } bool eu = landUnlocked(laElementalWall); bool tu = landUnlocked(laTrollheim); if(flags & AF_MSG) fightmessage(m, killer, dostun, flags); if(dostun) stunMonster(c, killer, flags); else killMonster(c, killer, flags); if(peace::on) return false; int ntk = tkills(); int ntkt = killtypes(); if(tkt < R20 && ntkt >= R20 && in_full_game()) { addMessage(XLAT("You hear a distant roar!")); playSound(NULL, "message-roar"); } if(tk == 0 && ntk > 0 && in_full_game() && !cheater) { if(notthateasy(m)) addMessage(XLAT("Quite tough, for your first fight.")); else { bool more = false; forCellEx(c2, cwt.at) forCellEx(c3, c2) if(c3->monst) more = true; if(!more) addMessage(XLAT("That was easy, but groups could be dangerous.")); } } if(tk < 10 && ntk >= 10 && in_full_game() && !big_unlock) addMessage(XLAT("Good to know that your fighting skills serve you well in this strange world.")); if(tk < R100/2 && ntk >= R100/2 && in_full_game()) addMessage(XLAT("You wonder where all these monsters go, after their death...")); if(tk < R100 && ntk >= R100 && in_full_game()) addMessage(XLAT("You feel that the souls of slain enemies pull you to the Graveyard...")); if(!tu && landUnlocked(laTrollheim) && in_full_game()) { playSound(c, "message-troll"); addMessage(XLAT("%The1 says, \"I die, but my clan in Trollheim will avenge me!\"", m)); } if(!eu && landUnlocked(laElementalWall) && in_full_game()) addMessage(XLAT("After killing %the1, you feel able to reach the Elemental Planes!", m)); if(m == moVizier && c->monst != moVizier && kills[moVizier] == 1 && in_full_game()) { addMessage(XLAT("Hmm, he has been training in the Emerald Mine. Interesting...")); princess::forceMouse = true; } if(m == moIvyRoot && ntk>tk) achievement_gain("IVYSLAYER"); return ntk > tk; } EX void pushMonster(cell *ct, cell *cf, int direction_hint) { moveMonster(ct, cf, direction_hint); if(ct->monst == moBrownBug) { int t = snakelevel(ct) - snakelevel(cf); if(t > 0) ct->stuntime = min(ct->stuntime + 2 * t, 7); } if(isBull(ct->monst)) ct->monst = moRagingBull; } EX bool destroyHalfvine(cell *c, eWall newwall IS(waNone), int tval IS(6)) { if(cellHalfvine(c)) { for(int t=0; ttype; t++) if(c->move(t)->wall == c->wall) { if(newwall == waPartialFire) flameHalfvine(c->move(t), tval); else if(newwall == waRed1) c->move(t)->wall = waVinePlant; else c->move(t)->wall = newwall; } if(newwall == waPartialFire) flameHalfvine(c, tval); else c->wall = newwall; return true; } return false; } EX int coastvalEdge(cell *c) { return coastval(c, laIvoryTower); } EX int gravityLevel(cell *c) { if(c->land == laIvoryTower || c->land == laEndorian) return coastval(c, laIvoryTower); if(c->land == laDungeon) return -coastval(c, laIvoryTower); if(c->land == laMountain) return 1-celldistAlt(c); return 0; } EX int gravityLevelDiff(cell *c, cell *d) { if(c->land != laWestWall || d->land != laWestWall) return gravityLevel(c) - gravityLevel(d); if(shmup::on) return 0; int nid = neighborId(c, d); int id1 = parent_id(c, 1, coastvalEdge) + 1; int di1 = angledist(c->type, id1, nid); int id2 = parent_id(c, -1, coastvalEdge) - 1; int di2 = angledist(c->type, id2, nid); if(di1 < di2) return 1; if(di1 > di2) return -1; return 0; } EX bool canUnstable(eWall w, flagtype flags) { return w == waNone || w == waCanopy || w == waOpenPlate || w == waClosePlate || w == waOpenGate || ((flags & MF_STUNNED) && (w == waLadder || w == waTrunk || w == waSolidBranch || w == waWeakBranch || w == waBigBush || w == waSmallBush)); } EX bool cellEdgeUnstable(cell *c, flagtype flags IS(0)) { if(!isGravityLand(c->land) || !canUnstable(c->wall, flags)) return false; if(shmup::on && c->land == laWestWall) return false; forCellEx(c2, c) { if(isAnyIvy(c2->monst) && c->land == laMountain && !(flags & MF_IVY)) return false; int d = gravityLevelDiff(c, c2); if(d == gravity_zone_diff(c)) { if(againstWind(c2, c)) return false; if(!passable(c2, NULL, P_MONSTER | P_DEADLY)) return false; if(isWorm(c2)) return false; } if(WDIM == 3) { if(d == 0 && !passable(c2, NULL, P_MONSTER | P_DEADLY)) return false; if(d == -1 && !passable(c2, NULL, P_MONSTER | P_DEADLY)) forCellEx(c3, c2) if(c3 != c && gravityLevelDiff(c3, c) == 0) return false; } } return true; } // find worms and ivies EX void settemp(cell *c) { temps.push_back(c); tempval.push_back(c->monst); c->monst = moNone; } EX void findWormIvy(cell *c) { while(true) { if(c->monst == moWorm || c->monst == moTentacle || c->monst == moWormwait || c->monst == moTentaclewait || c->monst == moTentacleEscaping) { worms.push_back(c); settemp(c); break; } else if(c->monst == moHexSnake) { hexsnakes.push_back(c); settemp(c); } else if(c->monst == moWormtail || c->monst == moHexSnakeTail) { bool bug = true; for(int i=0; itype; i++) { cell* c2 = c->move(i); if(c2 && isWorm(c2) && c2->mondir != NODIR && c2->move(c2->mondir) == c) { settemp(c); c = c2; bug = false; } } if(bug) break; } else if(c->monst == moIvyWait) { cell* c2 = c->move(c->mondir); settemp(c); c=c2; } else if(c->monst == moIvyHead) { ivies.push_back(c); settemp(c); break; } else if(c->monst == moIvyBranch || c->monst == moIvyRoot) { bool bug = true; for(int i=0; itype; i++) { cell* c2 = c->move(i); if(c2 && (c2->monst == moIvyHead || c2->monst == moIvyBranch) && c2->move(c2->mondir) == c) { settemp(c); c = c2; bug = false; } } if(bug) break; } else break; } } bool bugsfighting; bool keepLightning = false; int statuecount; int tidalphase; int tidalsize, tide[200]; EX void calcTidalPhase() { if(!tidalsize) { for(int i=0; i<5; i++) tide[tidalsize++] = 1; for(int i=1; i<4; i++) for(int j=0; j<3; j++) tide[tidalsize++] = i; for(int i=4; i<7; i++) for(int j=0; j<2; j++) tide[tidalsize++] = i; for(int i=7; i<20; i++) tide[tidalsize++] = i; for(int i=20; i<23; i++) for(int j=0; j<2; j++) tide[tidalsize++] = i; for(int i=23; i<26; i++) for(int j=0; j<3; j++) tide[tidalsize++] = i; for(int i=0; i+iland == laOcean) { int t = c->landparam; if(chaosmode) { char& csd(c->SEADIST); if(csd == 0) csd = 7; char& cld(c->LANDDIST); if(cld == 0) cld = 7; int seadist=csd, landdist=cld; for(int i=0; itype; i++) { cell *c2 = c->move(i); if(!c2) continue; if(c2->land == laBarrier || c2->land == laOceanWall) ; else if(c2->land == laOcean) seadist = min(seadist, c2->SEADIST ? c2->SEADIST+1 : 7), landdist = min(landdist, c2->LANDDIST ? c2->LANDDIST+1 : 7); else if(isSealand(c2->land)) seadist = 1; else landdist = 1; } if(seadist < csd) csd = seadist, recalcTide = true; if(landdist < cld) cld = landdist, recalcTide = true; if(seadist == 1 && landdist == 1) t = 15; else t = c->CHAOSPARAM = 1 + (29 * (landdist-1)) / (seadist+landdist-2); } if(c->wall == waStrandedBoat || c->wall == waBoat) c->wall = t >= tidalphase ? waBoat : waStrandedBoat; if(c->wall == waSea || c->wall == waNone) c->wall = t >= tidalphase ? waSea : waNone; if(isFire(c) && t >= tidalphase) c->wall = waSea; } #if CAP_FIELD if(c->land == laVolcano) { int id = lavatide(c, 0); if(id < 96) { if(c->wall == waNone || isWateryOrBoat(c) || c->wall == waVinePlant || isAlch(c)) { if(isWateryOrBoat(c) || isAlch(c)) playSound(c, "steamhiss"); c->wall = waMagma; if(itemBurns(c->item)) { addMessage(XLAT("%The1 burns!", c->item)), c->item = itNone; playSound(c, "steamhiss", 30); } } } else if(c->wall == waMagma) c->wall = waNone; } #endif } EX void buildAirmap() { for(int k=0; ktype; i++) { cell *c2 = c->move(i); if(!c2) continue; if(!passable(c2, c, P_BLOW | P_MONSTER)) continue; if(!passable(c, c2, P_BLOW | P_MONSTER)) continue; airmap.push_back(make_pair(c2, d+1)); } } sort(airmap.begin(), airmap.end()); } map rosemap; // rosemap&3: // 0 - wave not reached // 1 - wave expanding // 2 - wave phase 1 // 3 - wave phase 2 EX int rosedist(cell *c) { if(!(havewhat&HF_ROSE)) return 0; int&r (rosemap[c]); if((r&7) == 7) return 0; if(r&3) return (r&3)-1; return 0; } EX bool againstRose(cell *cfrom, cell *cto) { if(rosedist(cfrom) != 1) return false; if(cto && rosedist(cto) == 2) return false; return true; } EX bool withRose(cell *cfrom, cell *cto) { if(rosedist(cfrom) != 1) return false; if(rosedist(cto) != 2) return false; return true; } EX void buildRosemap() { rosephase++; rosephase &= 7; if((havewhat&HF_ROSE) && !rosephase) { rosewave++; for(int k=0; kwall == waRose && c->cpdist <= gamerange() - 2) rosemap[c] = rosewave * 8 + 2; } } for(map::iterator it = rosemap.begin(); it != rosemap.end(); it++) { cell *c = it->first; int r = it->second; if(r < (rosewave) * 8) continue; if((r&7) == 2) if(c->wall == waRose || !isWall(c)) for(int i=0; itype; i++) { cell *c2 = c->move(i); if(!c2) continue; // if(snakelevel(c2) <= snakelevel(c) - 2) continue; if(!passable(c2, c, P_BLOW | P_MONSTER | P_ROSE)) continue; int& r2 = rosemap[c2]; if(r2 < r) r2 = r-1; } } for(map::iterator it = rosemap.begin(); it != rosemap.end(); it++) { int& r = it->second; if((r&7) == 1 || (r&7) == 2 || (r&7) == 3) r++; if(airdist(it->first) < 3 || whirlwind::cat(it->first)) r |= 7; if(it->first->land == laBlizzard) r |= 7; forCellEx(c2, it->first) if(airdist(c2) < 3) r |= 7; } } EX int getDistLimit() { return cgi.base_distlimit; } EX bool nogoSlow(cell *to, cell *from) { if(cellEdgeUnstable(to) && gravityLevelDiff(to, from) >= 0) return true; if(cellUnstable(to)) return true; return false; } // pathdist begin EX cell *pd_from; EX int pd_range; EX void onpath(cell *c, int d) { c->pathdist = d; pathq.push_back(c); } EX void onpath(cell *c, int d, int sp) { c->pathdist = d; pathq.push_back(c); reachedfrom.push_back(sp); } EX void clear_pathdata() { for(auto c: pathq) c->pathdist = PINFD; pathq.clear(); pathqm.clear(); reachedfrom.clear(); } EX int pathlock = 0; EX void compute_graphical_distance() { if(pathlock) { printf("path error: compute_graphical_distance\n"); } cell *c1 = centerover.at ? centerover.at : pd_from ? pd_from : cwt.at; int sr = get_sightrange_ambush(); if(pd_from == c1 && pd_range == sr) return; clear_pathdata(); pd_from = c1; pd_range = sr; c1->pathdist = 0; pathq.push_back(pd_from); for(int qb=0; qbpathdist == pd_range) break; if(qb == 0) forCellCM(c1, c) ; forCellEx(c1, c) if(c1->pathdist == PINFD) onpath(c1, c->pathdist + 1); } } EX void computePathdist(eMonster param) { for(cell *c: targets) onpath(c, isPlayerOn(c) ? 0 : 1, hrand(c->type)); int qtarg = isize(targets); int limit = gamerange(); for(int qb=0; qb < isize(pathq); qb++) { int fd = reachedfrom[qb] + 3; cell *c = pathq[qb]; if(c->monst && !isBug(c) && !(isFriendly(c) && !c->stuntime)) { pathqm.push_back(c); continue; // no paths going through monsters } if(isMounted(c) && !isPlayerOn(c)) { // don't treat the Worm you are riding as passable pathqm.push_back(c); continue; } if(c->cpdist > limit && !(c->land == laTrollheim && turncount < c->landparam)) continue; int d = c->pathdist; if(d == PINFD - 1) continue; for(int j=0; jtype; j++) { int i = (fd+j) % c->type; // printf("i=%d cd=%d\n", i, c->move(i)->cpdist); cell *c2 = c->move(i); if(c2 && c2->pathdist == PINFD && passable(c2, (qb= qtarg) { if(param == moTortoise && nogoSlow(c, c2)) continue; if(param == moIvyRoot && strictlyAgainstGravity(c, c2, false, MF_IVY)) continue; if(param == moWorm && (cellUnstable(c) || cellEdgeUnstable(c) || prairie::no_worms(c))) continue; if(items[itOrbLava] && c2->cpdist <= 5 && pseudohept(c) && makeflame(c2, 1, true)) continue; } onpath(c2, d+1, c->c.spin(i)); } } } } #if HDR struct pathdata { void checklock() { if(pd_from) pd_from = NULL, clear_pathdata(); if(pathlock) printf("path error\n"); pathlock++; } ~pathdata() { pathlock--; clear_pathdata(); } pathdata(eMonster m) { checklock(); computePathdist(m); } pathdata(int i) { checklock(); } }; #endif // pathdist end vector > butterflies; EX void addButterfly(cell *c) { for(int i=0; icpdist = INFD; worms.clear(); ivies.clear(); ghosts.clear(); golems.clear(); temps.clear(); tempval.clear(); targets.clear(); statuecount = 0; hexsnakes.clear(); hadwhat = havewhat; havewhat = 0; jiangshi_on_screen = 0; snaketypes.clear(); if(!(hadwhat & HF_WARP)) { avengers = 0; } if(!(hadwhat & HF_MIRROR)) { mirrorspirits = 0; } elec::havecharge = false; elec::afterOrb = false; elec::haveelec = false; airmap.clear(); if(!(hadwhat & HF_ROSE)) rosemap.clear(); dcal.clear(); reachedfrom.clear(); recalcTide = false; for(int i=0; icpdist == 0) continue; c->cpdist = 0; checkTide(c); dcal.push_back(c); reachedfrom.push_back(hrand(c->type)); if(!invismove) targets.push_back(c); } int distlimit = gamerange(); for(int i=0; imonst == moTentacle || c->monst == moTentaclewait || c->monst == moTentacleEscaping) worms.push_back(c); } int qb = 0; first7 = 0; while(true) { if(qb == isize(dcal)) break; int i, fd = reachedfrom[qb] + 3; cell *c = dcal[qb++]; int d = c->cpdist; if(WDIM == 2 && d == distlimit) { first7 = qb; break; } for(int j=0; jtype; j++) if(i = (fd+j) % c->type, c->move(i)) { // printf("i=%d cd=%d\n", i, c->move(i)->cpdist); cell *c2 = c->move(i); if(!c2) continue; if(isWarpedType(c2->land)) havewhat |= HF_WARP; if(c2->land == laMirror) havewhat |= HF_MIRROR; if((c->wall == waBoat || c->wall == waSea) && (c2->wall == waSulphur || c2->wall == waSulphurC)) c2->wall = waSea; if(c2 && signed(c2->cpdist) > d+1) { if(WDIM == 3 && !gmatrix.count(c2)) { if(!first7) first7 = qb; continue; } c2->cpdist = d+1; // remove treasures if(!peace::on && c2->item && c2->cpdist == distlimit && itemclass(c2->item) == IC_TREASURE && c2->item != itBabyTortoise && (items[c2->item] >= (chaosmode?10:20) + currentLocalTreasure || getGhostcount() >= 2)) { c2->item = itNone; if(c2->land == laMinefield) { c2->landparam &= ~3; } } if(c2->item == itBombEgg && c2->cpdist == distlimit && items[itBombEgg] >= c2->landparam) { c2->item = itNone; c2->landparam |= 2; c2->landparam &= ~1; if(!c2->monst) c2->monst = moBomberbird; } if(c2->item == itBarrow && c2->cpdist == distlimit && c2->wall != waBarrowDig) { c2->item = itNone; } if(c2->item == itLotus && c2->cpdist == distlimit && items[itLotus] >= getHauntedDepth(c2)) { c2->item = itNone; } if(c2->item == itMutant2 && timerghost) { bool rotten = true; for(int i=0; itype; i++) if(c2->move(i) && c2->move(i)->monst == moMutant) rotten = false; if(rotten) c2->item = itNone; } if(c2->item == itDragon && (shmup::on ? shmup::curtime-c2->landparam>300000 : turncount-c2->landparam > 500)) c2->item = itNone; if(c2->item == itTrollEgg && c2->cpdist == distlimit && !shmup::on && c2->landparam && turncount-c2->landparam > 650) c2->item = itNone; if(c2->item == itWest && c2->cpdist == distlimit && items[itWest] >= c2->landparam + 4) c2->item = itNone; if(c2->item == itMutant && c2->cpdist == distlimit && items[itMutant] >= c2->landparam) { c2->item = itNone; } if(c2->item == itIvory && c2->cpdist == distlimit && items[itIvory] >= c2->landparam) { c2->item = itNone; } if(c2->item == itAmethyst && c2->cpdist == distlimit && items[itAmethyst] >= -celldistAlt(c2)/5) { c2->item = itNone; } if(!keepLightning) c2->ligon = 0; dcal.push_back(c2); reachedfrom.push_back(c->c.spin(i)); checkTide(c2); if(c2->wall == waBigStatue && c2->land != laTemple) statuecount++; if(cellHalfvine(c2) && isWarped(c2)) { addMessage(XLAT("%The1 is destroyed!", c2->wall)); destroyHalfvine(c2); } if(c2->wall == waCharged) elec::havecharge = true; if(c2->land == laStorms) elec::haveelec = true; if(c2->land == laWhirlpool) havewhat |= HF_WHIRLPOOL; if(c2->land == laWhirlwind) havewhat |= HF_WHIRLWIND; if(c2->land == laWestWall) havewhat |= HF_WESTWALL; if(c2->land == laPrairie) havewhat |= HF_RIVER; if(c2->wall == waRose) havewhat |= HF_ROSE; if((hadwhat & HF_ROSE) && (rosemap[c2] & 3)) havewhat |= HF_ROSE; if(c2->monst) { if(isHaunted(c2->land) && c2->monst != moGhost && c2->monst != moZombie && c2->monst != moNecromancer) survivalist = false; if(c2->monst == moHexSnake || c2->monst == moHexSnakeTail) { havewhat |= HF_HEX; if(c2->mondir != NODIR) snaketypes.insert(snake_pair(c2)); if(c2->monst == moHexSnake) hexsnakes.push_back(c2); else findWormIvy(c2); } else if(c2->monst == moKrakenT || c2->monst == moKrakenH) { havewhat |= HF_KRAKEN; } else if(c2->monst == moDragonHead || c2->monst == moDragonTail) { havewhat |= HF_DRAGON; } else if(c2->monst == moWitchSpeed) havewhat |= HF_FAST; else if(c2->monst == moMutant) havewhat |= HF_MUTANT; else if(c2->monst == moJiangshi) jiangshi_on_screen++; else if(c2->monst == moOutlaw) havewhat |= HF_OUTLAW; else if(isGhostMover(c2->monst)) ghosts.push_back(c2); else if(isWorm(c2) || isIvy(c2)) findWormIvy(c2); else if(isBug(c2)) { havewhat |= HF_BUG; targets.push_back(c2); } else if(isFriendly(c2)) { if(c2->monst != moMouse && !markEmpathy(itOrbInvis) && !(isWatery(c2) && markEmpathy(itOrbFish)) && !c2->stuntime) targets.push_back(c2); if(c2->monst == moGolem) golems.push_back(c2); if(c2->monst == moFriendlyGhost) golems.push_back(c2); if(c2->monst == moKnight) golems.push_back(c2); if(c2->monst == moTameBomberbird) golems.push_back(c2); if(c2->monst == moMouse) { golems.push_back(c2); havewhat |= HF_MOUSE; } if(c2->monst == moPrincess || c2->monst == moPrincessArmed) golems.push_back(c2); if(c2->monst == moIllusion) { if(items[itOrbIllusion]) items[itOrbIllusion]--; else c2->monst = moNone; } } else if(c2->monst == moButterfly) { addButterfly(c2); } else if(isAngryBird(c2->monst)) { havewhat |= HF_BIRD; if(c2->monst == moBat) havewhat |= HF_BATS | HF_EAGLES; if(c2->monst == moEagle) havewhat |= HF_EAGLES; } else if(c2->monst == moReptile) havewhat |= HF_REPTILE; else if(isLeader(c2->monst)) havewhat |= HF_LEADER; else if(c2->monst == moEarthElemental) havewhat |= HF_EARTH; else if(c2->monst == moWaterElemental) havewhat |= HF_WATER; else if(c2->monst == moVoidBeast) havewhat |= HF_VOID; else if(c2->monst == moHunterDog) havewhat |= HF_HUNTER; else if(isMagneticPole(c2->monst)) havewhat |= HF_MAGNET; else if(c2->monst == moAltDemon) havewhat |= HF_ALT; else if(c2->monst == moHexDemon) havewhat |= HF_HEXD; else if(c2->monst == moMonk) havewhat |= HF_MONK; else if(c2->monst == moShark || c2->monst == moCShark) havewhat |= HF_SHARK; else if(c2->monst == moAirElemental) havewhat |= HF_AIR, airmap.push_back(make_pair(c2,0)); } // pheromones! if(c2->land == laHive && c2->landparam >= 50 && c2->wall != waWaxWall) havewhat |= HF_BUG; if(c2->wall == waThumperOn) targets.push_back(c2); } } } while(recalcTide) { recalcTide = false; for(int i=0; imonst = tempval[i]; buildAirmap(); } EX bool makeEmpty(cell *c) { if(c->monst != moPrincess) { if(isAnyIvy(c->monst)) killMonster(c, moPlayer, 0); else if(c->monst == moPair) { if(c->move(c->mondir)->monst == moPair) c->move(c->mondir)->monst = moNone; } else if(isWorm(c->monst)) { if(!items[itOrbDomination]) return false; } else c->monst = moNone; } if(c->land == laCocytus) c->wall = waFrozenLake; else if(c->land == laAlchemist || c->land == laCanvas) ; else if(c->land == laDual) ; else if(c->land == laCaves || c->land == laEmerald) c->wall = waCavefloor; else if(c->land == laDeadCaves) c->wall = waDeadfloor2; else if(c->land == laCaribbean || c->land == laOcean || c->land == laWhirlpool || c->land == laLivefjord || c->land == laWarpSea || c->land == laKraken) c->wall = waBoat; // , c->item = itOrbYendor; else if(c->land == laMinefield) c->wall = waMineOpen; else if(c->wall == waFan && bounded) ; else if(c->wall == waOpenPlate && bounded) ; else if(c->wall == waTrunk || c->wall == waSolidBranch || c->wall == waWeakBranch) ; else if(c->wall == waGiantRug) ; else if(c->wall == waInvisibleFloor) ; else if(c->wall == waDock) ; else if(c->land == laDocks) c->wall = waBoat; else if(c->wall == waFreshGrave && bounded) ; else if(c->wall == waBarrier && sphere && WDIM == 3) ; else if(isReptile(c->wall)) c->wparam = reptilemax(); else if(c->wall == waAncientGrave && bounded) ; else c->wall = waNone; if(c->item != itCompass) c->item = itNone; if(c->land == laWildWest) { forCellEx(c2, c) forCellEx(c3, c2) if(c3->wall != waBarrier) c3->wall = waNone; } return true; } int numgates = 0; EX void toggleGates(cell *c, eWall type, int rad) { if(!c) return; celllister cl(c, rad, 1000000, NULL); for(cell *ct: cl.lst) { if(ct->wall == waOpenGate && type == waClosePlate) { bool onWorm = false; if(isWorm(ct)) onWorm = true; for(int i=0; itype; i++) if(ct->move(i) && ct->move(i)->wall == waOpenGate && isWorm(ct->move(i))) onWorm = true; if(!onWorm) { ct->wall = waClosedGate, numgates++; if(ct->item) { playSound(ct, "hit-crush"+pick123()); addMessage(XLAT("%The1 is crushed!", ct->item)); ct->item = itNone; } toggleGates(ct, type, 1); } } if(ct->wall == waClosedGate && type == waOpenPlate) { ct->wall = waOpenGate, numgates++; toggleGates(ct, type, 1); } } } EX void toggleGates(cell *ct, eWall type) { playSound(ct, "click"); numgates = 0; if(type == waClosePlate && PURE) toggleGates(ct, type, 2); else toggleGates(ct, type, (GOLDBERG && !sphere && !a4) ? gp::dist_3() : 3); if(numgates && type == waClosePlate) playSound(ct, "closegate"); if(numgates && type == waOpenPlate) playSound(ct, "opengate"); } EX void destroyTrapsOn(cell *c) { if(c->wall == waArrowTrap) c->wall = waNone, destroyTrapsAround(c); } EX void destroyTrapsAround(cell *c) { forCellEx(c2, c) destroyTrapsOn(c2); } EX void destroyWeakBranch(cell *cf, cell *ct, eMonster who) { if(cf && ct && cf->wall == waWeakBranch && cellEdgeUnstable(ct) && gravityLevelDiff(ct, cf) >= 0 && !ignoresPlates(who)) { cf->wall = waCanopy; if(!cellEdgeUnstable(cf)) { cf->wall = waWeakBranch; return; } playSound(cf, "trapdoor"); drawParticles(cf, winf[waWeakBranch].color, 4); } if(cf && ct && cf->wall == waSmallBush && cellEdgeUnstable(ct) && gravityLevelDiff(ct, cf) >= 0 && !ignoresPlates(who)) { cf->wall = waNone; if(!cellEdgeUnstable(cf)) { cf->wall = waSmallBush; return; } playSound(cf, "trapdoor"); drawParticles(cf, winf[waWeakBranch].color, 4); } } EX bool isCentralTrap(cell *c) { if(c->wall != waArrowTrap) return false; int i = 0; forCellEx(c2, c) if(c2->wall == waArrowTrap) i++; return i == 2; } EX array traplimits(cell *c) { array res; int q = 0; res[2] = c; for(int d=0; dtype; d++) { cellwalker cw(c, d); cw += wstep; if(cw.at->wall != waArrowTrap) continue; res[1+q*2] = cw.at; cw += (cw.at->type/2); if((cw.at->type&1) && (cw+wstep).at->wall != waStone) cw += 1; cw += wstep; res[(q++)*4] = cw.at; } while(q<2) { res[q*4] = res[1+q*2] = NULL; q++; } return res; } EX void activateArrowTrap(cell *c) { if(c->wall == waArrowTrap && c->wparam == 0) { playSound(c, "click"); c->wparam = shmup::on ? 2 : 1; forCellEx(c2, c) activateArrowTrap(c2); if(shmup::on) shmup::activateArrow(c); } } // effect of moving monster m from cf to ct // this is called from moveMonster, or separately from moveIvy/moveWorm, // or when a dead bird falls (then m == moDeadBird) EX void moveEffect(cell *ct, cell *cf, eMonster m, int direction_hint) { if(cf) destroyWeakBranch(cf, ct, m); mayExplodeMine(ct, m); if(!isNonliving(m)) terracottaAround(ct); if(ct->wall == waMineUnknown && !ct->item && !ignoresPlates(m) && normal_gravity_at(ct)) ct->landparam |= 2; // mark as safe if((ct->wall == waClosePlate || ct->wall == waOpenPlate) && !ignoresPlates(m) && normal_gravity_at(ct)) toggleGates(ct, ct->wall); if(m == moDeadBird && cf == ct && cellUnstable(cf) && normal_gravity_at(ct)) { fallingFloorAnimation(cf); cf->wall = waChasm; } if(ct->wall == waArrowTrap && !ignoresPlates(m) && normal_gravity_at(ct)) activateArrowTrap(ct); if(ct->wall == waFireTrap && !ignoresPlates(m) && ct->wparam == 0 && normal_gravity_at(ct)) { playSound(ct, "click"); ct->wparam = 1; } if(cf && isPrincess(m)) princess::move(ct, cf); if(cf && m == moTortoise) { tortoise::emap[ct] = tortoise::getb(cf); tortoise::emap.erase(cf); } if(cf && ct->item == itBabyTortoise && !cf->item) { cf->item = itBabyTortoise; ct->item = itNone; animateMovement(ct, cf, LAYER_BOAT, direction_hint); tortoise::babymap[cf] = tortoise::babymap[ct]; tortoise::babymap.erase(ct); } } EX void updateHi(eItem it, int v) { if(!yendor::on) if(v > hiitems[modecode()][it]) hiitems[modecode()][it] = v; } EX void gainItem(eItem it) { int g = gold(); items[it]++; if(it != itLotus) updateHi(it, items[it]); if(it == itRevolver && items[it] > 6) items[it] = 6; achievement_collection(it, gold(), g); multi::treasures[multi::cpid]++; #if CAP_DAILY if(daily::on) achievement_final(false); #endif } EX string itemcounter(int qty) { string s = ""; s += " ("; s += its(qty); s += ")"; return s; } EX void gainShard(cell *c2, const char *msg) { invismove = false; string s = XLAT(msg); if(is_mirrorland(c2) && !peace::on) { gainItem(itShard); s += itemcounter(items[itShard]); collectMessage(c2, itShard); } addMessage(s); c2->wall = waNone; invismove = false; } EX void uncoverMinesFull(cell *c2) { int mineradius = bounded ? 3 : (items[itBombEgg] < 1 && !tactic::on) ? 0 : items[itBombEgg] < 20 ? 1 : items[itBombEgg] < 30 ? 2 : 3; bool nomine = !normal_gravity_at(c2); if(!nomine && uncoverMines(c2, mineradius, 0, true) && markOrb(itOrbAether)) nomine = true; if(!nomine) { uncoverMines(c2, mineradius, 0, false); mayExplodeMine(c2, moPlayer); } } EX void playerMoveEffects(cell *c1, cell *c2) { if(peace::on) items[itOrbSword] = c2->land == laBurial ? 100 : 0; sword::dir[multi::cpid] = sword::shift(c1, c2, sword::dir[multi::cpid]); destroyWeakBranch(c1, c2, moPlayer); uncoverMinesFull(c2); if((c2->wall == waClosePlate || c2->wall == waOpenPlate) && normal_gravity_at(c2) && !markOrb(itOrbAether)) toggleGates(c2, c2->wall); if(c2->wall == waArrowTrap && c2->wparam == 0 && normal_gravity_at(c2) && !markOrb(itOrbAether)) activateArrowTrap(c2); if(c2->wall == waFireTrap && c2->wparam == 0 && normal_gravity_at(c2) &&!markOrb(itOrbAether)) { playSound(c2, "click"); c2->wparam = 1; } princess::playernear(c2); if(c2->wall == waGlass && items[itOrbAether] > ORBBASE+1) { addMessage(XLAT("Your Aether powers are drained by %the1!", c2->wall)); drainOrb(itOrbAether, 2); } if(cellUnstable(c2) && !markOrb(itOrbAether)) doesFallSound(c2); if(c2->wall == waStrandedBoat && markOrb(itOrbWater)) c2->wall = waBoat; if(c2->land == laOcean && c2->wall == waBoat && c2->landparam < 30 && markOrb(itOrbWater)) c2->landparam = 40; if((c2->land == laHauntedWall || c2->land == laHaunted) && !hauntedWarning) { hauntedWarning = true; addMessage(XLAT("You become a bit nervous...")); addMessage(XLAT("Better not to let your greed make you stray from your path.")); playSound(c2, "nervous"); } } EX void beastcrash(cell *c, cell *beast) { if(c->wall == waPetrified || c->wall == waDeadTroll || c->wall == waDeadTroll2 || c->wall == waGargoyle) { addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->wall)); c->wall = waNone; } else if(c->wall == waDeadwall || c->wall == waCavewall || c->wall == waSandstone || c->wall == waVinePlant || c->wall == waIcewall || c->wall == waMirror || c->wall == waCloud || c->wall == waBigTree || c->wall == waSmallTree || c->wall == waGlass || c->wall == waClosedGate || c->wall == waStone || c->wall == waRuinWall) { addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->wall)); c->wall = waNone; } else if(cellHalfvine(c)) { addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->wall)); destroyHalfvine(c); } else if(c->wall == waThumperOff) { addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->wall)); c->wall = waThumperOn; c->wparam = 100; } else if(c->wall == waExplosiveBarrel) { addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->wall)); explodeBarrel(c); } else if(isBull(c->monst) || isSwitch(c->monst)) { addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->monst)); if(c->monst == moSleepBull) c->monst = moRagingBull, c->stuntime = 3; } } EX void stayEffect(cell *c) { eMonster m = c->monst; if(m == moAirElemental) airmap.push_back(make_pair(c, 0)); if(m == moRagingBull && c->mondir != NODIR) { playSound(NULL, "hit-axe"+pick123()); forCellIdEx(c2, d, c) { bool opposite = anglestraight(c, d, c->mondir); if(opposite) beastcrash(c2, c); } c->mondir = NODIR; c->stuntime = 3; } } EX void makeTrollFootprints(cell *c) { if(c->land != laTrollheim) return; if(c->item == itTrollEgg && c->landparam) return; c->landparam = turncount + 100; } EX void moveMonster(cell *ct, cell *cf, int direction_hint) { eMonster m = cf->monst; bool fri = isFriendly(cf); if(isDragon(m)) { printf("called for Dragon\n"); return; } if(m != moMimic) animateMovement(cf, ct, LAYER_SMALL, direction_hint); // the following line is necessary because otherwise plates disappear only inside the sight range if(cellUnstable(cf) && !ignoresPlates(m)) { fallingFloorAnimation(cf); cf->wall = waChasm; } moveEffect(ct, cf, m, direction_hint); if(ct->wall == waCamelotMoat && (m == moShark || m == moCShark || m == moGreaterShark)) achievement_gain("MOATSHARK"); if(m == moTentacleGhost) { cf->monst = moTentacletail; m = moGhost; } else cf->monst = moNone; if(ct->monst == moTentacletail && m == moGhost) { ct->monst = moTentacleGhost; } else { ct->monst = m; if(m == moWolf) ct->monst = moWolfMoved; if(m == moHunterChanging) ct->stuntime = 1; int d =neighborId(ct, cf); if(ct->monst != moTentacleGhost) ct->mondir = d; if(d >= 0) ct->monmirror = cf->monmirror ^ ct->c.mirror(d); } ct->hitpoints = cf->hitpoints; ct->stuntime = cf->stuntime; if(isMagneticPole(m) || m == moPair) { if(cf->mondir == 15) { ct->monst = moPirate; return; } cell *other_pole = cf->move(cf->mondir); if(other_pole) { ct->mondir = neighborId(ct, other_pole), other_pole->mondir = neighborId(other_pole, ct); } } if(fri || isBug(m) || items[itOrbDiscord]) stabbingAttack(cf, ct, m); if(isLeader(m)) { if(ct->wall == waBigStatue) { ct->wall = cf->wall; cf->wall = waBigStatue; animateMovement(ct, cf, LAYER_BOAT, revhint(cf, direction_hint)); } moveBoatIfUsingOne(ct, cf, revhint(cf, direction_hint)); } if(isTroll(m)) { makeTrollFootprints(ct); makeTrollFootprints(cf); } int inc = incline(cf, ct); if(m == moEarthElemental) { if(!passable(ct, cf, 0)) earthFloor(ct); earthMove(cf, neighborId(cf, ct)); } if(m == moWaterElemental) { placeWater(ct, cf); for(int i=0; itype; i++) { cell *c2 = ct->move(i); if(!c2) continue; if(c2->wall == waBoat && !(isPlayerOn(c2) && markOrb(itOrbWater))) { addMessage(XLAT("%The1 is washed away!", c2->wall, moWaterElemental)); placeWater(c2, ct); } else if(c2->wall == waStrandedBoat) { addMessage(XLAT("%The1 is washed away!", c2->wall, moWaterElemental)); c2->wall = waNone; } else if(c2->wall == waDeadTroll) { addMessage(XLAT("%The1 is washed away!", c2->wall, moWaterElemental)); c2->wall = waCavefloor; } else if(c2->wall == waDeadTroll2) { addMessage(XLAT("%The1 is washed away!", c2->wall, moWaterElemental)); c2->wall = waNone; } else if(isFire(c2) && c2->wall != waEternalFire) { addMessage(XLAT("%The1 is extinguished!", c2->wall, moWaterElemental)); if(c2->wall == waBurningDock) c2->wall = waDock; else c2->wall = waNone; } if(shmup::on && isWatery(c2)) shmup::destroyBoats(c2); } } if(m == moGreaterShark) for(int i=0; itype; i++) { cell *c3 = ct->move(i); if(c3 && c3->wall == waBoat) makeflame(c3, 5, false); } // lancers pierce our friends :( if(m == moLancer) { // printf("lancer stab?\n"); forCellEx(c3, ct) if(!logical_adjacent(cf, m, c3)) { if(canAttack(ct, moLancer, c3, c3->monst, AF_LANCE | AF_GETPLAYER)) { attackMonster(c3, AF_LANCE | AF_MSG | AF_GETPLAYER, m); } // this looks the same as effect graphically as exploding right away, // except that it does not kill the lancer if(c3->wall == waExplosiveBarrel) c3->wall = waFireTrap, c3->wparam = 2; } } if(m == moWitchFire) makeflame(cf, 10, false); if(m == moFireElemental) { makeflame(cf, 20, false); if(cf->wparam < 20) cf->wparam = 20; } bool adj = false; if(ct->cpdist == 1 && (items[itOrb37] || !nonAdjacent(cf,ct)) && markOrb(itOrbBeauty)) adj = true; if(!adj && items[itOrbEmpathy] && items[itOrbBeauty]) { for(int i=0; itype; i++) if(ct->move(i) && isFriendly(ct->move(i))) adj = true, markOrb(itOrbEmpathy), markOrb(itOrbBeauty); } if(adj && ct->stuntime == 0 && !isMimic(m)) { ct->stuntime = 2; checkStunKill(ct); } if(!cellEdgeUnstable(ct)) { if(isMetalBeast(m)) ct->stuntime += 2; if(m == moTortoise) ct->stuntime += 3; if(m == moDraugr && ct->land != laBurial && ct->land != laHalloween) ct->stuntime += 2; if(m == moBrownBug && snakelevel(ct) < snakelevel(cf)) ct->stuntime += 2; if(m == moBrownBug && snakelevel(ct) < snakelevel(cf) - 1) ct->stuntime += 2; if(m == moBrownBug && isWatery(ct) && !isWatery(cf)) ct->stuntime += 2; } if(isWitch(m) && ct->item == itOrbLife && passable(cf, NULL, P_MIRROR)) { // note that Fire Witches don't pick up Orbs of Life, addMessage(XLAT("%The1 picks up %the2!", moWitch, ct->item)); cf->monst = moEvilGolem; ct->item = itNone; } else if(m == moWitch) { bool pickup = false; if(ct->item == itOrbFlash) pickup = true, m = moWitchFlash; if(ct->item == itOrbWinter) pickup = true, m = moWitchWinter; if(ct->item == itOrbAether) pickup = true, m = moWitchGhost; if(ct->item == itOrbFire) pickup = true, m = moWitchFire; if(ct->item == itOrbSpeed) pickup = true, m = moWitchSpeed; if(ct->item == itOrbLife) pickup = true, cf->monst = moEvilGolem; if(pickup) { addMessage(XLAT("%The1 picks up %the2!", moWitch, ct->item)); ct->monst = m; ct->item = itNone; // speedwitches are stunned to prevent them from making a move // immediately if(m == moWitchSpeed) ct->stuntime = 1; } } if(m == moAirElemental) airmap.push_back(make_pair(ct, 0)); if(m == moWolf && ct->land == laVolcano) ct->monst = moLavaWolf; if(m == moLavaWolf && isIcyLand(ct)) ct->monst = moWolfMoved; if(m == moPair) ct->stuntime++; if(inc == -3 && ct->monst == moReptile) ct->stuntime =3; else if(inc == 2 && ct->monst == moReptile) ct->stuntime = 2; else if(inc == 3 && ct->monst == moReptile) ct->stuntime = 3; else if(inc == -3 && !survivesFall(ct->monst) && !passable(cf, ct, P_MONSTER)) { addMessage(XLAT("%The1 falls!", ct->monst)); fallMonster(ct, AF_FALL); } if(isThorny(ct->wall) && !survivesThorns(ct->monst)) { addMessage(XLAT("%The1 is killed by thorns!", ct->monst)); playSound(ct, "hit-rose"); if(isBull(ct->monst)) ct->wall = waNone; fallMonster(ct, AF_CRUSH); } if(sword::at(ct) && canAttack(NULL, moPlayer, ct, m, AF_SWORD_INTO)) { attackMonster(ct, AF_SWORD_INTO | AF_MSG, moPlayer); achievement_gain("GOSWORD"); } } EX bool cannotGo(eMonster m, cell *c) { if(m == moCrystalSage && (c->land != laCocytus || HEAT(c) > SAGEMELT || allPlayersInBoats())) return true; return false; } EX bool wantsToStay(eMonster m) { return m == moCrystalSage && allPlayersInBoats(); } EX bool batsAfraid(cell *c) { // bats for(int i=-1; imonst && invismove) continue; bool enear = false; forCellEx(c, targets[i]) forCellEx(c2, c) forCellEx(c3, c2) if(isActiveEnemy(c3, targets[i]->monst) && c3->monst != moBat && passable_for(c3->monst, c2, c3, 0) && passable_for(c3->monst, c, c2, 0) ) enear = true; if(!enear) return true; } return false; } EX int angledist(int t, int d1, int d2) { int dd = d1 - d2; while(dd<0) dd += t; while(dd>t/2) dd -= t; if(dd<0) dd = -dd; return dd; } EX int angledistButterfly(int t, int d1, int d2) { int dd = d1 - d2; while(dd<0) dd += t; return dd; } EX int angledist(cell *c, int d1, int d2) { return angledist(c->type, d1, d2); } EX bool anglestraight(cell *c, int d1, int d2) { return angledist(c->type, d1, d2) >= c->type / 2; } EX int bulldist(cell *c) { int low = 0; forCellEx(c2, c) if(c2->cpdist < c->cpdist) low++; return 8 * c->cpdist - low; } EX int bulldistance(cell *c, cell *d) { int low = 0; int cd = celldistance(c, d); forCellEx(c2, c) if(celldistance(c2, d) < cd) low++; return 8 * cd - low; } EX int landheattype(cell *c) { if(isIcyLand(c)) return 0; if(c->land == laVolcano) return 2; return 1; } // move value EX int moveval(cell *c1, cell *c2, int d, flagtype mf) { if(!c2) return -5000; eMonster m = c1->monst; // Angry Beasts can only go forward if(m == moRagingBull && c1->mondir != NODIR && !anglestraight(c1, c1->mondir, d)) return -1700; // never move against a rose if(againstRose(c1, c2) && !ignoresSmell(m)) return -1600; // worms cannot attack if they cannot move if(isWorm(m) && !passable_for(c1->monst, c2, c1, P_MONSTER)) return -1700; if(canAttack(c1, m, c2, c2->monst, AF_GETPLAYER | mf) && !(mf & MF_NOATTACKS)) { if(m == moRagingBull && c1->mondir != NODIR) return -1700; if(mf & MF_MOUNT) { if(c2 == dragon::target) return 3000; else if(isFriendlyOrBug(c2)) return 500; else return 2000; } if(isPlayerOn(c2)) return peace::on ? -1700 : 2500; else if(isFriendlyOrBug(c2)) return peace::on ? -1600 : 2000; else return 500; } if(!passable_for(c1->monst, c2, c1, 0)) return // never move into a wall (passable_for(c1->monst, c2, c1, P_DEADLY)) ? -1300 : -1700; // move impossible if(slowMover(m) && nogoSlow(c2, c1)) return -1300; if(isPlayerOn(c2)) return -1700; // probably shielded if((mf & MF_MOUNT) && c2 == dragon::target) return 3000; // crystal sages would die out of Cocytus if(cannotGo(m, c2)) return -600; // Rose Beauties keep to the Rose Garden if(m == moRoseBeauty && c2->land != laRose) return -600; if(wantsToStay(m)) return 750; if((m == moRatling || m == moRatlingAvenger) && lastmovetype == lmSkip) return 650; if(m == moLancer) { bool lancerok = true; forCellEx(c3, c2) if(c1 != c3 && !logical_adjacent(c1, m, c3)) if(canAttack(c2, moLancer, c3, c3->monst, AF_LANCE | AF_ONLY_ENEMY)) lancerok = false; if(!lancerok) return 750; } bool hunt = true; if(m == moLavaWolf) { // prefers to keep to volcano int clht = landheattype(c1); int dlht = landheattype(c2); if(dlht > clht) return 1510; if(dlht < clht) return 700; // will not hunt the player if these rules do not allow it bool onlava = false; for(cell *c: targets) { if(landheattype(c) >= clht) onlava = true; forCellEx(cc, c) if(landheattype(cc) >= clht) onlava = true; } if(!onlava) hunt = false; } if(m == moWolf) { int val = 1500; if(c2->land == laVolcano) return 1510; if(heat::absheat(c2) <= heat::absheat(c1)) return 900; for(int i=0; itype; i++) { cell *c3 = c1->move(i); if(heat::absheat(c3) > heat::absheat(c2)) val--; } return val; } if((mf & MF_MOUNT) && dragon::target) return 1500 + celldistance(c1, dragon::target) - celldistance(c2, dragon::target); // Goblins avoid getting near the Sword if(m == moGoblin && sword::isnear(c2)) return 790; if(m == moBat && batsAfraid(c2)) return 790; if(m == moButterfly) return 1500 + angledistButterfly(c1->type, c1->mondir, d); if(m == moRagingBull && c1->mondir != NODIR) return 1500 - bulldist(c2); // actually they just run away if(m == moHunterChanging && c2->pathdist > c1->pathdist) return 1600; if((mf & MF_PATHDIST) && !pathlock) printf("using MF_PATHDIST without path\n"); int bonus = 0; if(m == moBrownBug && snakelevel(c2) < snakelevel(c1)) bonus = -10; if(hunt && (mf & MF_PATHDIST) && c2->pathdist < c1->pathdist && !peace::on) return 1500 + bonus; // good move // prefer straight direction when wandering int dd = angledist(c1, c1->mondir, d); // goblins blocked by anglophobia prefer to move around than to stay if(m == moGoblin) { bool swn = false; forCellEx(c3, c1) if(sword::isnear(c3)) swn = true; if(swn) dd += 210; } return 800 + dd; } // stay value EX int stayval(cell *c, flagtype mf) { if(isShark(c->monst) && !isWatery(c)) return 525; if(againstRose(c, NULL) && !ignoresSmell(c->monst)) return -1500; if(!passable_for(c->monst, c, NULL, P_MONSTER | P_MIRROR)) return 525; if(cellEdgeUnstable(c)) return -1500; if(isRatling(c->monst) && lastmovetype != lmSkip) return 700; // Goblins avoid staying near the Sword (if there is no choice, movement is preferred) if(c->monst == moGoblin && sword::isnear(c)) return 780; // Vikings move in a roughly straight line even if they cannot detect you if(c->monst == moViking && c->wall == waBoat) return 750; // in peaceful, all monsters are wandering if(peace::on && c->monst != moTortoise) return 750; if(isWorm(c->monst)) return 550; if(c->monst == moRagingBull) return -1690; // worse than to stay in place if(c->monst == moBat && batsAfraid(c)) return 575; if(c->monst == moHunterGuard) return 1600; // prefers to stay in place // Lava Wolves will wander if not hunting if(c->monst == moLavaWolf) return 750; return 1000; } EX int totalbulldistance(cell *c, int k) { shpos.resize(SHSIZE); int tbd = 0; for(int p=0; p1; k++) { int pts[MAX_EDGE]; for(int d=0; dcmove(posdir[d]), k); int bestpts = 1000; for(int d=0; dtype & 1)) return 1; // irrelevant int d = c2->type / 2; bull += d; dirs[0] = positive = bull.spin; bull -= 2*d; dirs[1] = bull.spin; determinizeBull(c2, dirs, nc); if(dirs[0] == positive) return -1; return 1; } int posdir[MAX_EDGE], nc; EX int pickMoveDirection(cell *c, flagtype mf) { int bestval = stayval(c, mf); nc = 1; posdir[0] = -1; // printf("stayval [%p, %s]: %d\n", c, dnameof(c->monst), bestval); for(int d=0; dtype; d++) { cell *c2 = c->move(d); int val = moveval(c, c2, d, mf); // printf("[%d] %p: val=%5d pass=%d\n", d, c2, val, passable(c2,c,0)); if(val > bestval) nc = 0, bestval = val; if(val == bestval) posdir[nc++] = d; } if(c->monst == moRagingBull) determinizeBull(c, posdir, nc); if(!nc) return -1; return posdir[hrand(nc)]; } EX int pickDownDirection(cell *c, flagtype mf) { int downs[MAX_EDGE], qdowns = 0; int bestdif = -100; forCellIdEx(c2, i, c) { if(gravityLevelDiff(c2, c) < 0 && passable_for(c->monst, c2, c, P_MIRROR) && !isPlayerOn(c2)) { int cdif = i-c->mondir; if(cdif < 0) cdif += c->type; if(cdif > c->type/2) cdif = cdif - c->type; if(cdif < 0) cdif = -2*cdif+1; else cdif = 2*cdif; // printf("i=%d md=%d dif=%d\n", i, c->mondir, cdif); if(c2->wall == waClosePlate || c->wall == waClosePlate) cdif += 20; if(cdif > bestdif) bestdif = cdif, qdowns = 0; if(cdif == bestdif) downs[qdowns++] = i; } } if(!qdowns) return -1; return downs[hrand(qdowns)]; } EX vector reverse_directions(cell *c, int dir) { int d = c->degree(); switch(geometry) { case gBinary3: if(dir < 4) return {8}; else if(dir >= 8) return {0, 1, 2, 3}; else return {dir ^ 1}; case gHoroTris: if(dir < 4) return {7}; else if(dir == 4) return {5, 6}; else if(dir == 5) return {6, 4}; else if(dir == 6) return {4, 5}; else return {0, 1, 2, 3}; case gHoroRec: if(dir < 2) return {6}; else if(dir == 6) return {0, 1}; else return {dir^1}; case gKiteDart3: { if(dir < 4) return {dir ^ 2}; if(dir >= 6) return {4, 5}; vector res; for(int i=6; itype; i++) res.push_back(i); return res; } case gHoroHex: { if(dir < 6) return {12, 13}; if(dir >= 12) return {0, 1, 2, 3, 4, 5}; const int dt[] = {0,0,0,0,0,0,10,11,9,8,6,7,0,0}; return {dt[dir]}; } default: if(d & 1) return { gmod(dir + c->type/2, c->type), gmod(dir + (c->type+1)/2, c->type) }; else return { gmod(dir + c->type/2, c->type) }; } } template cell *determinePush(cellwalker who, cell *c2, int subdir, const T& valid, int& pushdir) { if(subdir != 1 && subdir != -1) { subdir = 1; static bool first = true; if(first) first = false, addMessage("bad push: " + its(subdir)); } cellwalker push = who; push += wstep; if(binarytiling) { auto rd = reverse_directions(push.at, push.spin); for(int i: rd) { push.spin = i; if(valid(push.cpeek())) return push.cpeek(); } return c2; } int pd = push.at->type/2; push += pd * -subdir; push += wstep; if(valid(push.at)) return push.at; if(c2->type&1) { push = push + wstep - subdir + wstep; pushdir = (push+wstep).spin; if(valid(push.at)) return push.at; } if(gravityLevelDiff(push.at, c2) < 0) { push = push + wstep + 1 + wstep; if(gravityLevelDiff(push.at, c2) < 0) { push = push + wstep - 2 + wstep; } if(gravityLevelDiff(push.at, c2) < 0) { push = push + wstep + 1 + wstep; } pushdir = (push+wstep).spin; if(valid(push.at)) return push.at; } return c2; } // Angry Beast attack // note: this is done both before and after movement EX void beastAttack(cell *c, bool player) { if(c->mondir == NODIR) return; forCellIdEx(c2, d, c) { bool opposite = anglestraight(c, d, c->mondir); int flags = AF_BULL; if(player) flags |= AF_GETPLAYER; if(!opposite) flags |= AF_ONLY_FBUG; if(canAttack(c, moRagingBull, c2, c2->monst, flags)) { attackMonster(c2, flags | AF_MSG, moRagingBull); if(c2->monst && c2->stuntime) { cellwalker bull (c, d); int subdir = determinizeBullPush(bull); int pushdir = NOHINT; cell *c3 = determinePush(bull, c2, subdir, [c2] (cell *c) { return passable(c, c2, P_BLOW); }, pushdir); if(c3 && c3 != c2) pushMonster(c3, c2, pushdir); } } if(c2->wall == waThumperOff) { playSound(c2, "click"); c2->wall = waThumperOn; c2->wparam = 100; } if(c2->wall == waExplosiveBarrel) { playSound(c2, "click"); explodeBarrel(c2); } if(c2->wall == waThumperOn) { cellwalker bull (c, d); int subdir = determinizeBullPush(bull); int pushdir = NOHINT; cell *c3 = determinePush(bull, c2, subdir, [c2] (cell *c) { return canPushThumperOn(c, c2, c); }, pushdir); if(c3 && c3 != c2) pushThumper(c2, c3); } } } bool quantum; EX cell *moveNormal(cell *c, flagtype mf) { eMonster m = c->monst; if(isPowerMonster(m) && !playerInPower()) return NULL; int d; if(c->stuntime) { if(cellEdgeUnstable(c, MF_STUNNED)) d = pickDownDirection(c, mf), nc = 1, posdir[0] = d; else return NULL; } else { // Angry Beasts attack all neighbors first if(m == moRagingBull) beastAttack(c, true); d = pickMoveDirection(c, mf); } if(d == -1) { stayEffect(c); return c; } if(!quantum) { cell *c2 = c->move(d); if(isPlayerOn(c2)) { if(m == moCrusher) { addMessage(XLAT("%The1 raises his weapon...", m)); crush_next.push_back(c2); c->stuntime = 7; return c2; } killThePlayerAt(m, c2, 0); return c2; } eMonster m2 = c2->monst; if(m2 && m == moCrusher) { addMessage(XLAT("%The1 raises his weapon...", m)); crush_next.push_back(c2); c->stuntime = 7; return c2; } else if(m2) { attackMonster(c2, AF_NORMAL | AF_MSG, m); animateAttack(c, c2, LAYER_SMALL, d); if(m == moFlailer && m2 == moIllusion) attackMonster(c, 0, m2); return c2; } moveMonster(c2, c, d); if(m == moRagingBull) beastAttack(c2, false); return c2; } else { bool attacking = false; for(int i=0; imove(posdir[i]); if(isPlayerOn(c2)) { killThePlayerAt(m, c2, 0); attacking = true; } else { eMonster m2 = c2->monst; if(m2) { attackMonster(c2, AF_NORMAL | AF_MSG, m); if(m == moFlailer && m2 == moIllusion) attackMonster(c, 0, m2); attacking = true; } } } if(!attacking) for(int i=0; imove(posdir[i]); if(!c->monst) c->monst = m; moveMonster(c2, c, posdir[i]); if(m == moRagingBull) beastAttack(c2, false); } return c->move(d); } } // for sandworms EX void explodeAround(cell *c) { for(int j=0; jtype; j++) { cell* c2 = c->move(j); if(c2) { if(isIcyLand(c2)) HEAT(c2) += 0.5; eWall ow = c2->wall; if((c2->wall == waDune || c2->wall == waIcewall || c2->wall == waAncientGrave || c2->wall == waFreshGrave || c2->wall == waColumn || c2->wall == waThumperOff || c2->wall == waThumperOn || (isFire(c2) && !eternalFire(c2)) || c2->wall == waBigTree || c2->wall == waSmallTree || c2->wall == waVinePlant || c2->wall == waVineHalfA || c2->wall == waVineHalfB)) { destroyHalfvine(c2); c2->wall = waNone; } if(c2->wall == waExplosiveBarrel) explodeBarrel(c2); if(c2->wall == waCavewall || c2->wall == waDeadTroll) c2->wall = waCavefloor; if(c2->wall == waDeadTroll2) c2->wall = waNone; if(c2->wall == waPetrified) c2->wall = waNone; if(c2->wall == waDeadfloor2) c2->wall = waDeadfloor; if(c2->wall == waGargoyleFloor) c2->wall = waChasm; if(c2->wall == waGargoyleBridge || c2->wall == waPetrifiedBridge) placeWater(c2, c2); if(c2->wall == waRubble) c2->wall = waNone; if(c2->wall == waPlatform) c2->wall = waNone; if(c2->wall == waStone) c2->wall = waNone, destroyTrapsAround(c2); if(c2->wall == waRose) c2->wall = waNone; if(c2->wall == waRuinWall) c2->wall = waNone; if(c2->wall == waLadder) c2->wall = waNone; if(c2->wall == waGargoyle) c2->wall = waNone; if(c2->wall == waSandstone) c2->wall = waNone; if(c2->wall == waSaloon) c2->wall = waNone; if(c2->wall == waDeadwall) c2->wall = waDeadfloor2; if(c2->wall == waBigStatue) c2->wall = waNone; if(c2->wall == waPalace || c2->wall == waOpenGate || c2->wall == waClosedGate) c2->wall = waNone; if(isAlch(c2) && isAlch(c)) c2->wall = c->wall; if(c2->wall != ow && ow) drawParticles(c2, winf[ow].color, 16); } } } EX void killHardcorePlayer(int id, flagtype flags) { charstyle& cs = getcs(id); cell *c = playerpos(id); if(flags & AF_FALL) fallingMonsterAnimation(c, moPlayer); else for(int i=0; i<6; i++) { drawParticle(c, cs.skincolor >> 8, 100); drawParticle(c, cs.haircolor >> 8, 125); if(cs.charid) drawParticle(c, cs.dresscolor >> 8, 150); else drawParticle(c, cs.skincolor >> 8, 150); if(cs.charid == 3) drawParticle(c, cs.dresscolor2 >> 8, 175); else drawParticle(c, cs.skincolor >> 8, 150); drawParticle(c, cs.swordcolor >> 8, 200); } if(multi::players > 1 && multi::activePlayers() > 1) { multi::leaveGame(id); } else { canmove = false; achievement_final(true); } } EX void killThePlayer(eMonster m, int id, flagtype flags) { if(markOrb(itOrbShield)) return; if(shmup::on) { multi::cpid = id; shmup::killThePlayer(m); } else if(items[itOrbDomination] && playerpos(id)->monst) { addMessage(XLAT("%The1 tries to dismount you!", m)); attackMonster(playerpos(id), AF_NORMAL, m); useupOrb(itOrbDomination, items[itOrbDomination]/2); } else if(items[itOrbShell] && !(flags & AF_EAT)) { addMessage(XLAT("%The1 attacks your shell!", m)); useupOrb(itOrbShell, 10); if(items[itOrbShell] < 1) items[itOrbShell] = 1, orbused[itOrbShell] = true; } else if(hardcore) { addMessage(XLAT("You are killed by %the1!", m)); killHardcorePlayer(id, flags); } else if(m == moLightningBolt && lastmovetype == lmAttack && isAlchAny(playerpos(id))) { addMessage(XLAT("You are killed by %the1!", m)); addMessage(XLAT("Don't play with slime and electricity next time, okay?")); kills[moPlayer]++; items[itOrbSafety] = 0; } else { // printf("confused!\n"); addMessage(XLAT("%The1 is confused!", m)); } } EX void killThePlayerAt(eMonster m, cell *c, flagtype flags) { for(int i=0; i 1) { multi::player[id].at = c; multi::player[id].spin = spin; multi::flipped[id] = fp; } else { cwt.at = c; cwt.spin = spin; flipplayer = fp; } afterplayermoved(); } EX void mountmove(cell *c, int spin, bool fp, cell *ppos) { for(int i=0; imonst == moWormwait) { c->monst = moWorm; return; } else if(c->monst == moTentaclewait) { c->monst = moTentacle; return; } else if(c->monst == moTentacleEscaping) { // explodeAround(c); forCellEx(c2, c) if(canAttack(c, c->monst, c2, c2->monst, mounted ? AF_ONLY_ENEMY : (AF_GETPLAYER | AF_ONLY_FBUG))) { attackMonster(c2, AF_NORMAL | AF_MSG | AF_GETPLAYER, c->monst); } cell *c2 = c; vector allcells; while(c2->mondir != NODIR) { allcells.push_back(c2); c2 = c2->move(c2->mondir); } allcells.push_back(c2); for(int i=isize(allcells)-2; i>=0; i--) { cell *cmt = allcells[i+1]; cell *cft = allcells[i]; if(cft->monst != moTentacleGhost && cmt->monst != moTentacleGhost) mountmove(cmt, cft->c.spin(cft->mondir), false, cft); animateMovement(cft, cmt, LAYER_BIG, cft->mondir); } c->monst = moNone; if(c->mondir != NODIR) c->move(c->mondir)->monst = moTentacleEscaping; return; } else if(c->monst != moWorm && c->monst != moTentacle) return; eMonster m = c->monst; int id = m - moWorm; int mf = MF_PATHDIST | AF_EAT; if(mounted) mf ^= (MF_MOUNT | MF_PATHDIST); // without this, in 3D geometries, Sandworms explode because no land around them is generated yet forCellCM(c2, c) setdist(c2, 8, c); int dir = pickMoveDirection(c, mf); if(c->wall == waRose) { addMessage(XLAT("%The1 eats %the2!", c->monst, c->wall)); c->wall = waNone; dir = -1; } if(dir == -1) { int spices = 0; if(id) { addMessage(XLAT("Cthulhu withdraws his tentacle!")); kills[moTentacle]++; c->monst = moTentacleEscaping; moveWorm(c); } else { kills[moWorm]++; spices = 3; } eItem loc = treasureType(c->land); bool spiceSeen = false; while(c->monst == moWorm || c->monst == moWormtail || c->monst == moTentacle || c->monst == moTentacletail) { // if(!id) explodeAround(c); drawParticles(c, minf[c->monst].color, 16); if(spices > 0 && c->land == laDesert) { if(notDippingForExtra(itSpice, loc)) { c->item = itSpice; if(c->cpdist <= 6) spiceSeen = true; } spices--; } c->monst = moNone; if(c->mondir != NODIR) c = c->move(c->mondir); } if(!id) { if(spiceSeen) addMessage(XLAT("The sandworm explodes in a cloud of Spice!")); else addMessage(XLAT("The sandworm explodes!")); playSound(NULL, "explosion"); if(geometry == gZebraQuotient) achievement_gain("ZEBRAWORM", rg::special_geometry); } return; } cell* goal = c->move(dir); if(isPlayerOn(goal) || goal->monst) attackMonster(goal, AF_EAT | AF_MSG | AF_GETPLAYER, c->monst); for(int j=0; jtype; j++) if(c->move(j) == goal) { goal->monst = eMonster(moWormwait + id); moveEffect(goal, NULL, eMonster(moWormwait + id), NOHINT); animateMovement(c, goal, LAYER_BIG, dir); c->monst = eMonster(moWormtail + id); goal->mondir = c->c.spin(j); goal->monmirror = c->monmirror ^ c->c.mirror(j); mountmove(goal, goal->mondir, true, c); if(id) { cell *c2 = c, *c3 = c2; while(c2->monst == moTentacletail || c2->monst == moTentacleGhost) { if(c2->mondir == NODIR) { // drawParticles(c2, (linf[c2->land].color & 0xF0F0F0), 16, 50); return; } c3 = c2, c2 = c3->move(c2->mondir); if(c3->monst != moTentacleGhost && c2->monst != moTentacleGhost) mountmove(c3, c3->mondir, true, c2); animateMovement(c2, c3, LAYER_BIG, c2->c.spin(c2->mondir)); } } cell *c2 = c, *c3 = c2; for(int a=0; amonst == moWormtail) { if(c2->mondir == NODIR) { drawParticles(c2, (linf[c2->land].color & 0xF0F0F0), 16); return; } c3 = c2, c2 = c3->move(c2->mondir); mountmove(c3, c3->mondir, true, c2); animateMovement(c2, c3, LAYER_BIG, revhint(c2, c2->mondir)); } } if(c2->monst == moWormtail) c2->monst = moNone, c3->mondir = NODIR; } } EX void ivynext(cell *c) { cellwalker cw(c, c->mondir, c->monmirror); // check the mirroring status cell *c2 = c; while(true) { if(c2->monst == moIvyRoot) break; if(!isIvy(c2->monst)) break; if(c2->c.mirror(c2->mondir)) cw.mirrored = !cw.mirrored; c2 = c2->move(c2->mondir); } cw.at->monst = moIvyWait; bool findleaf = false; while(true) { cw += 1; if(cw.spin == signed(cw.at->mondir)) { if(findleaf) { cw.at->monst = moIvyHead; break; } cw.at->monst = moIvyWait; cw += wstep; continue; } cw += wstep; if(cw.at->monst == moIvyWait && signed(cw.at->mondir) == cw.spin) { cw.at->monst = moIvyBranch; findleaf = true; continue; } cw += wstep; } } // this removes Ivy, but also potentially causes Vines to grow EX void removeIvy(cell *c) { eMonster m = c->monst; c->monst = moNone; // NEWYEARFIX for(int i=0; itype; i++) // note that semi-vines don't count if(c->move(i)->wall == waVinePlant) { destroyHalfvine(c); c->wall = waVinePlant; } if(c->wall != waVinePlant) { if(m == moIvyDead) m = moIvyWait; drawParticles(c, minf[m].color, 2); } } EX void moveivy() { if(isize(ivies) == 0) return; if(racing::on) return; pathdata pd(moIvyRoot); for(int i=0; imonst != moIvyHead) continue; ivynext(c); cell *mto = NULL; int pd = c->pathdist; int sp = 0; while(c->monst != moIvyRoot) { if(!isIvy(c->monst)) { raiseBuggyGeneration(c, "that's not an Ivy!"); } if(c->mondir == NODIR) raiseBuggyGeneration(c, "wrong mondir!"); for(int j=0; jtype; j++) { if(c->move(j) && canAttack(c, c->monst, c->move(j), c->move(j)->monst, AF_ONLY_FRIEND | AF_GETPLAYER)) { if(isPlayerOn(c->move(j))) killThePlayerAt(c->monst, c->move(j), 0); else { if(attackJustStuns(c->move(j), 0, c->monst)) addMessage(XLAT("The ivy attacks %the1!", c->move(j)->monst)); else if(isNonliving(c->move(j)->monst)) addMessage(XLAT("The ivy destroys %the1!", c->move(j)->monst)); else addMessage(XLAT("The ivy kills %the1!", c->move(j)->monst)); attackMonster(c->move(j), AF_NORMAL, c->monst); } continue; } if(c->move(j) && signed(c->move(j)->pathdist) < pd && passable(c->move(j), c, 0) && !strictlyAgainstGravity(c->move(j), c, false, MF_IVY)) mto = c->move(j), pd = mto->pathdist, sp = c->c.spin(j); } c = c->move(c->mondir); } if(mto && mto->cpdist) { animateMovement(mto->move(sp), mto, LAYER_BIG, mto->c.spin(sp)); mto->monst = moIvyWait, mto->mondir = sp; mto->monmirror = c->monmirror ^ c->c.mirror(sp); moveEffect(mto, NULL, moIvyWait, NOHINT); // if this is the only branch, we want to move the head immediately to mto instead if(mto->move(mto->mondir)->monst == moIvyHead) { mto->monst = moIvyHead; co->monst = moIvyBranch; } } else if(co->move(co->mondir)->monst != moIvyRoot) { // shrink useless branches, but do not remove them completely (at the root) if(co->monst == moIvyHead) co->move(co->mondir)->monst = moIvyHead; removeIvy(co); } } } EX bool earthMove(cell *from, int dir) { bool b = false; cell *c2 = from->move(dir); int d = from->c.spin(dir); b |= earthWall(from); if(c2) for(int u=2; u<=c2->type-2; u++) { cell *c3 = c2->modmove(d + u); if(c3) b |= earthFloor(c3); } return b; } vector gendfs; int targetcount; EX bool isTargetOrAdjacent(cell *c) { for(int i=0; ipathdist == 0) return; if(movtype == moKrakenH && isTargetOrAdjacent(from)) ; /* else if(passable_for(movtype, from, c, P_ONPLAYER | P_CHAIN | P_MONSTER)) ; else if(canAttack(c, movtype, from, from->monst, AF_GETPLAYER)) ; */ else if(passable_for(movtype, from, c, P_CHAIN | P_MONSTER)) ; else if(canAttack(c, movtype, from, from->monst, AF_GETPLAYER)) ; else if(isMagneticPole(movtype)) { // a special case here -- we have to ignore the illegality of // the 'second' move due to an adjacent opposite pole forCellIdEx(c2, d, c) if(c2->monst == movtype) { cell *c3 = c2->move(c2->mondir); eMonster m2 = c3->monst; c3->monst = moNone; bool ok = passable_for(movtype, from, c, P_CHAIN | P_MONSTER) && passable_for(movtype, c, c2, P_CHAIN | P_MONSTER); c3->monst = m2; if(ok) groupmove2(c2, c, d, movtype, mf); } } else return; if(from->monst) { if(mf & MF_MOUNT) { // don't go through friends if(isFriendlyOrBug(from)) return; } else { // go through the player (even mounted) if(isPlayerOn(from)) ; // go through the mounted dragon else if(isDragon(from->monst) && isFriendlyOrBug(from)) ; // but not through other worms else if(isWorm(from)) return; // go through other friends else if(isFriendlyOrBug(from)) ; else return; } } // Kraken movement if(movtype == moKrakenH && c->monst == moKrakenT && c->stuntime == 0) kraken::trymove(c); if(movegroup(c->monst) == movtype) { int af = AF_ONLY_FBUG | AF_GETPLAYER; if(mf & MF_MOUNT) af = 0; if(!passable_for(movtype, from, c, P_ONPLAYER | P_MONSTER)) return; if(!ignoresSmell(c->monst) && againstRose(c, from)) return; if((mf & MF_ONLYEAGLE) && c->monst != moEagle && c->monst != moBat) return; if((mf & MF_ONLYEAGLE) && bird_disruption(c) && markOrb(itOrbGravity)) return; // in the gravity lands, eagles cannot ascend in their second move if((mf & MF_ONLYEAGLE) && gravityLevelDiff(c, from) < 0) { onpath(c, 0); return; } if((mf & MF_NOFRIEND) && isFriendly(c)) return; if((mf & MF_MOUNT) && !isMounted(c)) return; if(isRatling(c->monst) && lastmovetype == lmSkip) return; if(c->stuntime) return; if(c->monst == moBat && batsAfraid(from)) return; // note: move from 'c' to 'from'! if(!(mf & MF_NOATTACKS)) for(int j=0; jtype; j++) if(c->move(j) && canAttack(c, c->monst, c->move(j), c->move(j)->monst, af)) { attackMonster(c->move(j), AF_NORMAL | AF_GETPLAYER | AF_MSG, c->monst); animateAttack(c, c->move(j), LAYER_SMALL, j); onpath(c, 0); // XLATC eagle return; } if(from->cpdist == 0 || from->monst) { onpath(c, 0); return; } if(movtype == moDragonHead) { dragon::move(from, c); return; } moveMonster(from, c, revhint(from, d)); onpath(from, 0); } onpath(c, 0); // MAXGCELL if(isize(gendfs) < 1000 || c->cpdist <= 6) gendfs.push_back(c); } EX void groupmove(eMonster movtype, flagtype mf) { pathdata pd(0); gendfs.clear(); if(mf & MF_MOUNT) { if(dragon::target) gendfs.push_back(dragon::target); if(movtype == moDragonHead) { for(int i=0; imonst) continue; if(isFriendlyOrBug(c)) continue; forCellIdEx(c2, d, c) if(c2->monst && isMounted(c2)) { groupmove2(c2,c,d,movtype,mf); } } } } else { if(!peace::on) for(int i=0; imove(t),c,t,movtype,mf); } if(movtype == moEagle && c->monst == moNone && !isPlayerOn(c) && !bird_disruption(c)) { cell *c2 = whirlwind::jumpFromWhereTo(c, false); groupmove2(c2, c, NODIR, movtype, mf); } } if(movtype != moDragonHead) for(int i=0; imonst != moEagle && c->monst != moBat) return; if(movegroup(c->monst) == movtype && c->pathdist != 0) { cell *c2 = moveNormal(c, mf); if(c2) onpath(c2, 0); } } } // Hex monsters vector hexdfs; // note: move from 'c' to 'from'! EX void moveHexSnake(cell *from, cell *c, int d, bool mounted) { if(from->wall == waBoat) from->wall = waSea; moveEffect(from, c, c->monst, revhint(from, d)); from->monst = c->monst; from->mondir = d; from->hitpoints = c->hitpoints; c->monst = moHexSnakeTail; preventbarriers(from); animateMovement(c, from, LAYER_BIG, revhint(from, d)); mountmove(from, from->mondir, true, c); cell *c2 = c, *c3=c2; for(int a=0;; a++) if(c2->monst == moHexSnakeTail) { if(a == ROCKSNAKELENGTH) { c2->monst = moNone, c3->mondir = NODIR; break; } if(c2->mondir == NODIR) break; mountmove(c2, c2->mondir, true, c2->move(c2->mondir)); animateMovement(c2->move(c2->mondir), c2, LAYER_BIG, revhint(c2, c2->mondir)); c3 = c2, c2 = c3->move(c2->mondir); } else break; } EX void snakeAttack(cell *c, bool mounted) { for(int j=0; jtype; j++) if(c->move(j) && canAttack(c, moHexSnake, c->move(j), c->move(j)->monst, mounted ? AF_ONLY_ENEMY : (AF_ONLY_FBUG | AF_GETPLAYER))) { eMonster m2 = c->move(j)->monst; attackMonster(c->move(j), AF_NORMAL | AF_GETPLAYER | AF_MSG, moHexSnake); produceGhost(c->move(j), moHexSnake, m2); } } EX bool goodmount(cell *c, bool mounted) { if(mounted) return isMounted(c); else return !isMounted(c); } EX int inpair(cell *c, int colorpair) { return (colorpair >> pattern_threecolor(c)) & 1; } EX int snake_pair(cell *c) { if(c->mondir == NODIR) return (1 << pattern_threecolor(c)); else return (1 << pattern_threecolor(c)) | (1 << pattern_threecolor(c->move(c->mondir))); } // note: move from 'c' to 'from'! EX void hexvisit(cell *c, cell *from, int d, bool mounted, int colorpair) { if(!c) return; if(cellUnstable(c) || cellEdgeUnstable(c)) return; if(c->pathdist == 0) return; if(cellUnstableOrChasm(c) || cellUnstableOrChasm(from)) return; /* if(c->monst == moHexSnake) printf("%p:%p %s %d\n", from, c, dnameof(from->monst), passable(from, c, true, false, false)); */ if(from->cpdist && (!passable(from, c, P_MONSTER|P_WIND|P_FISH))) return; if(c->monst == moHexSnake && snake_pair(c) == colorpair) { // printf("got snake\n"); if(!inpair(from, colorpair)) return; if(!goodmount(c, mounted)) return; if(canAttack(c, moHexSnake, from, from->monst, AF_EAT | (mounted ? AF_ONLY_ENEMY : AF_ONLY_FBUG | AF_GETPLAYER))) { attackMonster(from, AF_MSG | AF_EAT | AF_GETPLAYER, c->monst); } if(from->cpdist == 0 || from->monst) return; snakeAttack(c, mounted); moveHexSnake(from, c, d, mounted); } onpath(c, 0); // MAXGCELL if(isize(hexdfs) < 2000 || c->cpdist <= 6) hexdfs.push_back(c); } EX void movehex(bool mounted, int colorpair) { pathdata pd(3); hexdfs.clear(); if(mounted) { if(dragon::target && dragon::target->monst != moHexSnake) { hexdfs.push_back(dragon::target); onpath(dragon::target, 0); } } else for(cell *c: targets) { hexdfs.push_back(c); onpath(c, 0); } //hexdfs.push_back(cwt.at); for(int i=0; itype; t++) if(c->move(t) && inpair(c->move(t), colorpair)) dirtable[qdirtable++] = t; hrandom_shuffle(dirtable, qdirtable); while(qdirtable--) { int t = dirtable[qdirtable]; hexvisit(c->move(t), c, t, mounted, colorpair); } } } EX void movehex_rest(bool mounted) { for(int i=0; imonst == moHexSnake) { colorpair = snake_pair(c); if(!goodmount(c, mounted)) continue; int t[MAX_EDGE]; for(int i=0; itype; i++) t[i] = i; for(int j=1; jtype; j++) swap(t[j], t[hrand(j+1)]); for(int u=0; utype; u++) { createMov(c, t[u]); if(inpair(c->move(t[u]), colorpair)) hexvisit(c, c->move(t[u]), c->c.spin(t[u]), mounted, colorpair); } } if(c->monst == moHexSnake) { snakeAttack(c, mounted); kills[moHexSnake]++; playSound(c, "die-troll"); cell *c2 = c; while(c2->monst == moHexSnakeTail || c2->monst == moHexSnake) { if(c2->monst != moHexSnake && c2->mondir != NODIR) snakepile(c2, moHexSnake); snakepile(c2, moHexSnake); c2->monst = moNone; if(c2->mondir == NODIR) break; c2 = c2->move(c2->mondir); } } } } EX void movemutant() { vector young; for(cell *c: currentmap->allcells()) if(c->monst == moMutant && c->stuntime == mutantphase) young.push_back(c); for(int j=1; jmonst == moMutant) c->monst=moNone; continue; } for(int j=0; jtype; j++) { cell *c2 = c->move(j); if(!c2) continue; if(c2->monst != moMutant && canAttack(c, moMutant, c2, c2->monst, AF_ONLY_FBUG | AF_GETPLAYER)) { attackMonster(c2, AF_NORMAL | AF_MSG | AF_GETPLAYER, moMutant); continue; } if(isPlayerOn(c2)) continue; if((c2->land == laOvergrown || !pseudohept(c2)) && passable(c2, c, 0)) { if(c2->land == laClearing && !bounded && c2->mpdist > 7) continue; c2->monst = moMutant; c2->mondir = c->c.spin(j); c2->stuntime = mutantphase; animateMovement(c, c2, LAYER_BIG, j); } } } } EX vector> shpos; EX int cshpos = 0; EX cell *lastmountpos[MAXPLAYER]; EX void clearshadow() { shpos.resize(SHSIZE); for(int i=0; imonst == moShadow) { for(int j=0; jtype; j++) if(c->move(j) && canAttack(c, moShadow, c->move(j), c->move(j)->monst, AF_ONLY_FBUG | AF_GETPLAYER)) attackMonster(c->move(j), AF_NORMAL | AF_MSG | AF_GETPLAYER, c->monst); c->monst = moNone; shfrom = c; } shpos[p][cshpos] = playerpos(p); } cshpos = (cshpos+1) % SHSIZE; for(int p=0; pmonst == moNone && where->cpdist && where->land == laGraveyard && !sword::at(where)) { if(shfrom) animateMovement(shfrom, where, LAYER_SMALL, NOHINT); where->monst = moShadow; where->hitpoints = p; where->stuntime = 0; // the Shadow sets off the mines mayExplodeMine(where, moShadow); } } } EX void moveghosts() { if(invismove) return; for(int d=0; d<=MAX_EDGE; d++) movesofgood[d].clear(); for(int i=0; istuntime) continue; if(isPowerMonster(c) && !playerInPower()) continue; if(isGhostMover(c->monst)) { int goodmoves = 0; for(int k=0; ktype; k++) if(c->move(k) && c->move(k)->cpdist < c->cpdist) if(ghostmove(c->monst, c->move(k), c) && !isPlayerOn(c->move(k))) goodmoves++; movesofgood[goodmoves].push_back(c); } } for(int d=0; d<=MAX_EDGE; d++) for(int i=0; istuntime) continue; if(isPowerMonster(c) && !playerInPower()) continue; if(isGhostMover(c->monst) && c->cpdist >= 1) { int mdir[MAX_EDGE]; for(int j=0; jtype; j++) if(c->move(j) && canAttack(c, c->monst, c->move(j), c->move(j)->monst, AF_GETPLAYER | AF_ONLY_FBUG)) { // XLATC ghost/greater shark attackMonster(c->move(j), AF_NORMAL | AF_MSG | AF_GETPLAYER, c->monst); goto nextghost; } int qmpos = 0; for(int k=0; ktype; k++) if(c->move(k) && c->move(k)->cpdist < c->cpdist) if(ghostmove(c->monst, c->move(k), c)) mdir[qmpos++] = k; if(!qmpos) continue; int d = mdir[hrand(qmpos)]; cell *c2 = c->move(d); if(c2->monst == moTortoise && c2->stuntime > 1) { addMessage(XLAT("%The1 scares %the2 a bit!", c->monst, c2->monst)); c2->stuntime = 1; } else moveMonster(c2, c, d); } nextghost: ; } } int lastdouble = -3; EX void produceGhost(cell *c, eMonster victim, eMonster who) { if(who != moPlayer && !items[itOrbEmpathy]) return; if(markOrb(itOrbUndeath) && !c->monst && isGhostable(victim)) { c->monst = moFriendlyGhost, c->stuntime = 0; if(who != moPlayer) markOrb(itOrbEmpathy); } } EX bool swordAttack(cell *mt, eMonster who, cell *c, int bb) { eMonster m = c->monst; if(c->wall == waCavewall) markOrb(bb ? itOrbSword2: itOrbSword); if(c->wall == waSmallTree || c->wall == waBigTree || c->wall == waRose || c->wall == waCTree || c->wall == waVinePlant || thruVine(mt, c) || c->wall == waBigBush || c->wall == waSmallBush || c->wall == waSolidBranch || c->wall == waWeakBranch) { playSound(NULL, "hit-axe"+pick123()); markOrb(bb ? itOrbSword2: itOrbSword); drawParticles(c, winf[c->wall].color, 16); addMessage(XLAT("You chop down %the1.", c->wall)); destroyHalfvine(c); c->wall = waNone; } if(c->wall == waBarrowDig) { playSound(NULL, "hit-axe"+pick123()); markOrb(bb ? itOrbSword2: itOrbSword); drawParticles(c, winf[c->wall].color, 16); c->wall = waNone; } if(c->wall == waBarrowWall && items[itBarrow] >= 25) { playSound(NULL, "hit-axe"+pick123()); markOrb(bb ? itOrbSword2: itOrbSword); drawParticles(c, winf[c->wall].color, 16); c->wall = waNone; } if(c->wall == waExplosiveBarrel) explodeBarrel(c); if(!peace::on && canAttack(mt, who, c, m, AF_SWORD)) { markOrb(bb ? itOrbSword2: itOrbSword); int k = tkills(); attackMonster(c, AF_NORMAL | AF_MSG | AF_SWORD, who); if(c->monst == moShadow) c->monst = moNone; produceGhost(c, m, who); if(tkills() > k) return true; } return false; } EX void swordAttackStatic(int bb) { swordAttack(cwt.at, moPlayer, sword::pos(multi::cpid, bb), bb); } EX void swordAttackStatic() { for(int bb = 0; bb < 2; bb++) if(sword::orbcount(bb)) swordAttackStatic(bb); } EX void sideAttack(cell *mf, int dir, eMonster who, int bonus, eItem orb) { if(!items[orb]) return; if(who != moPlayer && !items[itOrbEmpathy]) return; for(int k: {-1, 1}) { cell *mt = mf->modmove(dir + k*bonus); eMonster m = mt->monst; flagtype f = AF_SIDE; if(items[itOrbSlaying]) f|= AF_CRUSH; if(canAttack(mf, who, mt, m, f)) { if((f & AF_CRUSH) && !canAttack(mf, who, mt, m, AF_SIDE | AF_MUSTKILL)) markOrb(itOrbSlaying); markOrb(orb); if(who != moPlayer) markOrb(itOrbEmpathy); if(attackMonster(mt, AF_NORMAL | AF_SIDE | AF_MSG, who)) produceGhost(mt, m, who); } else if(mt->wall == waBigTree) mt->wall = waSmallTree; else if(mt->wall == waSmallTree) mt->wall = waNone; else if(mt->wall == waExplosiveBarrel) explodeBarrel(mt); } } EX void sideAttack(cell *mf, int dir, eMonster who, int bonuskill) { int k = tkills(); sideAttack(mf, dir, who, 1, itOrbSide1); sideAttack(mf, dir, who, 2, itOrbSide2); sideAttack(mf, dir, who, 3, itOrbSide3); if(who == moPlayer) { int kills = tkills() - k + bonuskill; if(kills >= 5) achievement_gain("MELEE5"); } } template void do_swords(cell *mf, cell *mt, eMonster who, const T& f) { for(int bb=0; bb<2; bb++) if(who == moPlayer && sword::orbcount(bb)) { cell *sf = sword::pos(mf, sword::dir[multi::cpid], bb); cell *st = sword::pos(mt, sword::shift(mf, mt, sword::dir[multi::cpid]), bb); f(st, bb); if(sf != st && !isNeighbor(sf,st)) { // also attack the in-transit cell if(S3 == 3) { forCellEx(sb, sf) if(isNeighbor(sb, st) && sb != mf && sb != mt) f(sb, bb); } else { forCellEx(sb, mf) if(isNeighbor(sb, st) && sb != mt) f(sb, bb); forCellEx(sb, mt) if(isNeighbor(sb, sf) && sb != mf) f(sb, bb); } } } } EX eMonster do_we_stab_a_friend(cell *mf, cell *mt, eMonster who) { eMonster m = moNone; do_swords(mf, mt, who, [&] (cell *c, int bb) { if(!peace::on && canAttack(mt, who, c, c->monst, AF_SWORD) && c->monst && isFriendly(c)) m = c->monst; }); for(int t=0; ttype; t++) { cell *c = mf->move(t); if(!c) continue; bool stabthere = false; if(logical_adjacent(mt, who, c)) stabthere = true; if(stabthere && canAttack(mt,who,c,c->monst,AF_STAB) && isFriendly(c)) return c->monst; } return m; } EX void stabbingAttack(cell *mf, cell *mt, eMonster who, int bonuskill IS(0)) { int numsh = 0, numflail = 0, numlance = 0, numslash = 0, numbb[2]; numbb[0] = numbb[1] = 0; int backdir = neighborId(mt, mf); do_swords(mf, mt, who, [&] (cell *c, int bb) { if(swordAttack(mt, who, c, bb)) numbb[bb]++, numslash++; }); for(int bb=0; bb<2; bb++) achievement_count("SLASH", numbb[bb], 0); if(peace::on) return; for(int t=0; ttype; t++) { cell *c = mf->move(t); if(!c) continue; bool stabthere = false, away = true; if(logical_adjacent(mt, who, c)) stabthere = true, away = false; if(stabthere && c->wall == waExplosiveBarrel && markOrb(itOrbThorns)) explodeBarrel(c); if(stabthere && canAttack(mt,who,c,c->monst,AF_STAB)) { if(c->monst != moHedge) { markOrb(itOrbThorns); if(who != moPlayer) markOrb(itOrbEmpathy); } eMonster m = c->monst; int k = tkills(); if(attackMonster(c, AF_STAB | AF_MSG, who)) produceGhost(c, m, who); if(tkills() > k) numsh++; } if(away && c != mt && canAttack(mf,who,c,c->monst,AF_BACK)) { if(c->monst == moVizier && c->hitpoints > 1) { fightmessage(c->monst, who, true, AF_BACK); c->hitpoints--; // c->stuntime = 1; } else { eMonster m = c->monst; if(c->monst != moFlailer) { playSound(NULL, "hit-sword"+pick123()); fightmessage(c->monst, who, false, AF_BACK); } else { playSound(NULL, "hit-sword"+pick123()); if(who != moPlayer) addMessage(XLAT("%The1 tricks %the2.", who, c->monst)); else addMessage(XLAT("You trick %the1.", c->monst)); } if(c->monst == moFlailer && isPrincess(who) && isUnarmed(who)) achievement_gain("PRINCESS_PACIFIST"); if(attackMonster(c, 0, who)) numflail++; if(m == moVizier) produceGhost(c, m, who); } } } if(!isUnarmed(who)) forCellIdEx(c, t, mt) { if(!logical_adjacent(mt, who, c)) continue; eMonster mm = c->monst; int flag = AF_APPROACH; if(anglestraight(mt, backdir, t)) flag |= AF_HORNS; if(canAttack(mt,who,c,c->monst, flag)) { if(attackMonster(c, flag | AF_MSG, who)) numlance++; produceGhost(c, mm, who); } } if(who == moPlayer) { if(numsh) achievement_count("STAB", numsh, 0); if(numlance && numflail && numsh) achievement_gain("MELEE3"); if(numlance + numflail + numsh + numslash + bonuskill >= 5) achievement_gain("MELEE5"); if(numsh == 2) { if(lastdouble == turncount-1) achievement_count("STAB", 4, 0); lastdouble = turncount; } } } EX bool cellDangerous(cell *c) { return cellUnstableOrChasm(c) || isFire(c) || c->wall == waClosedGate; } // negative: die, attack friend, stay against rose, hit a wall, move against rose, hit the player EX bool hasPrincessWeapon(eMonster m) { return m == moPalace || m == moFatGuard; } EX int stayvalue(eMonster m, cell *c) { if(!passable_for(c->monst, c, NULL, P_MONSTER | P_MIRROR)) return -1501; if(cellEdgeUnstable(c)) return -1501; if(againstRose(c, NULL) && !ignoresSmell(c->monst)) return -1500; return 100; } // friendly version of moveval EX int movevalue(eMonster m, cell *c, cell *c2, flagtype flags) { int val = 0; if(isPlayerOn(c2)) val = -3000; else if(againstRose(c, c2) && !ignoresSmell(m)) return -1200; else if(m == moPrincess && c2->stuntime && hasPrincessWeapon(c2->monst) && !cellDangerous(c) && !cellDangerous(c2) && canAttack(c, m, c2, c2->monst, AF_IGNORE_UNARMED | flags)) { val = 15000; } else if(canAttack(c,m,c2,c2->monst,flags)) val = (!passable_for(c->monst, c, NULL, P_MONSTER | P_MIRROR)) ? 100 : (m == moPrincessArmed && isPrincess(c2->monst)) ? 14000 : // jealousy! isActiveEnemy(c2,m) ? 12000 : among(c2->monst, moSkeleton, moMetalBeast, moReptile, moTortoise, moSalamander, moTerraWarrior, moBrownBug) ? -400 : isIvy(c2) ? 8000 : isInactiveEnemy(c2,m) ? 1000 : -500; else if(monstersnear(c2, NULL, m, NULL, c)) val = 50; // linked with mouse suicide! else if(passable_for(m, c2, c, 0)) { if(mineMarked(c2) && !ignoresPlates(m)) val = 50; else val = 4000; } else if(passable_for(m, c2, c, P_DEADLY)) val = -1100; else val = -1750; if(c->monst == moGolem ) val -= c2->cpdist; else if(c->monst == moFriendlyGhost ) val += c2->cpdist - 40; else if(c->monst == moMouse) { int d; if(!euclid && (c2->land != laPalace || !c2->master->alt)) d = 200; else d = celldistAlt(c2); // first rule: suicide if the Princess is killed, // by monstersnear or jumping into a chasm princess::info *i = princess::getPrisonInfo(c); if(i && !i->princess) { if(val == 50 || c2->wall == waChasm) val = 20000; } // second rule: move randomly if the Princess is saved if(i && i->bestdist > 6) ; // third rule: do not get too far from the Princess else if(d > 150) val -= (700+d); // fourth rule: do not get too far from the Rogue // NOTE: since Mouse is not a target, we can use // the full pathfinding here instead of cpdist! else if(c2->pathdist > 3 && c2->pathdist <= 19) val -= (500+c2->pathdist * 10); else if(c2->pathdist > 19) val -= (700); // fifth rule: get close to the Princess, to point the way else val -= (250+d); /* // avoid stepping on trapdoors and plates // (REMOVED BECAUSE MICE NO LONGER ACTIVATE TRAPDOORS AND PLATES) // note that the Mouse will still step on the trapdoor // if it wants to get close to you and there is no other way if(c2->wall == waTrapdoor) val -= 5; */ } if(isPrincess(c->monst)) { int d = c2->cpdist; if(d <= 3) val -= d; else val -= 10 * d; // the Princess also avoids stepping on pressure plates if(c2->wall == waClosePlate || c2->wall == waOpenPlate || c2->wall == waTrapdoor) val -= 5; } if(c->monst == moTameBomberbird) { int d = c2->cpdist; if(d == 1 && c->cpdist > 1) d = 5; if(d == 2 && c->cpdist > 2) d = 4; val -= d; } if(c->monst == moKnight && (eubinary || c2->master->alt)) { val -= celldistAlt(c2); // don't go to external towers if(c2->wall == waTower && c2->wparam == 1 && !c2->monst) return 60; } return val; } #define STRONGWIND 99 EX void movegolems(flagtype flags) { if(items[itOrbEmpathy] && items[itOrbSlaying]) flags |= AF_CRUSH; pathdata pd(moMouse); int qg = 0; for(int i=0; imonst; if(c->stuntime) continue; if(m == moGolem || m == moKnight || m == moTameBomberbird || m == moPrincess || m == moPrincessArmed || m == moMouse || m == moFriendlyGhost) { if(m == moGolem) qg++; if(m == moFriendlyGhost) markOrb(itOrbUndeath); bool recorduse[ittypes]; for(int i=0; itype; k++) if(c->move(k)) { cell *c2 = c->move(k); int val = movevalue(m, c, c2, flags); if(val > bestv) bestv = val, bq = 0; if(val == bestv) bdirs[bq++] = k; } if(m == moTameBomberbird) { cell *c2 = whirlwind::jumpDestination(c); if(c2 && !c2->monst) { int val = movevalue(m, c, c2, flags); // printf("val = %d bestv = %d\n", if(val > bestv) bestv = val, bq = 0; if(val == bestv) bdirs[bq++] = STRONGWIND; } } for(int i=0; imove(dir) : whirlwind::jumpDestination(c); if(c2->monst) { bool revenge = (m == moPrincess); bool jealous = (isPrincess(c->monst) && isPrincess(c2->monst)); eMonster m2 = c2->monst; if(revenge) { playSound(c2, princessgender() ? "dzia-princess" : "dzia-prince"); addMessage(XLAT("%The1 takes %his1 revenge on %the2!", m, c2->monst)); } if(revenge || jealous) flags |= AF_CRUSH; else if((flags & AF_CRUSH) && !canAttack(c, m, c2, c2->monst, flags ^ AF_CRUSH ^ AF_MUSTKILL)) markOrb(itOrbEmpathy), markOrb(itOrbSlaying); attackMonster(c2, flags | AF_MSG, m); animateAttack(c, c2, LAYER_SMALL, dir); produceGhost(c2, m2, m); sideAttack(c, dir, m, 0); if(revenge) c->monst = m = moPrincessArmed; if(jealous) { playSound(c2, princessgender() ? "dzia-princess" : "dzia-prince"); addMessage("\"That should teach you to take me seriously!\""); } } else { passable_for(m, c2, c, P_DEADLY); DEBB(DF_TURN, ("move")); moveMonster(c2, c, dir); if(m != moTameBomberbird && m != moFriendlyGhost) moveBoatIfUsingOne(c2, c, dir); if(c2->monst == m) { if(m == moGolem) c2->monst = moGolemMoved; if(m == moMouse) c2->monst = moMouseMoved; if(m == moPrincess) c2->monst = moPrincessMoved; if(m == moPrincessArmed) c2->monst = moPrincessArmedMoved; if(m == moTameBomberbird) c2->monst = moTameBomberbirdMoved; if(m == moKnight) c2->monst = moKnightMoved; if(m == moFriendlyGhost) c2->stuntime = 1; } empathyMove(c, c2, dir); } DEBB(DF_TURN, ("other")); } } achievement_count("GOLEM", qg, 0); } EX void sageheat(cell *c, double v) { HEAT(c) += v; if(c->wall == waFrozenLake && HEAT(c) > .6) c->wall = waLake; } EX void activateFlashFrom(cell *cf, eMonster who, flagtype flags); bool sagefresh = true; EX int nearestPathPlayer(cell *c) { for(int i=0; ipathdist < c->pathdist) return nearestPathPlayer(c2); for(int i=0; imonst == moButterfly) { /* // don't move if under attack of a bull bool underattack = false; forCellEx(c3, c) if(c3->monst == moRagingBull && c3->mondir != NODIR && angledist(c3->type, c3->mondir, neighborId(c3, c)) == 3 && canAttack(c3, moRagingBull, c, c->monst, AF_BULL) ) underattack = true; if(underattack) continue; */ cell *c2 = moveNormal(c, 0); if(butterflies[i].second < 50 && c2) butterflies[j++] = make_pair(c2, butterflies[i].second+1); } } butterflies.resize(j); } // assume pathdist EX void specialMoves() { for(int i=0; istuntime) continue; eMonster m = c->monst; if(m == moHunterGuard && items[itHunting] >= 10) c->monst = moHunterChanging; if(m == moHunterDog && (havewhat & HF_FAILED_AMBUSH) && hyperbolic && !quotient) c->monst = moHunterChanging; if(m == moSleepBull && !peace::on) { bool wakeup = false; forCellEx(c2, c) if(c2->monst == moGadfly) { addMessage(XLAT("%The1 wakes up %the2.", c2->monst, m)); wakeup = true; } for(int i=0; imonst = m = moRagingBull; c->mondir = NODIR; } } if(m == moNecromancer) { pathdata pd(moNecromancer); int gravenum = 0, zombienum = 0; cell *gtab[8], *ztab[8]; for(int j=0; jtype; j++) if(c->move(j)) { if(c->move(j)->wall == waFreshGrave) gtab[gravenum++] = c->move(j); if(passable(c->move(j), c, 0) && c->move(j)->pathdist < c->pathdist) ztab[zombienum++] = c->move(j); } if(gravenum && zombienum) { cell *gr = gtab[hrand(gravenum)]; gr->wall = waAncientGrave; gr->monst = moGhost; gr->stuntime = 1; ztab[hrand(zombienum)]->monst = moZombie; ztab[hrand(zombienum)]->stuntime = 1; addMessage(XLAT("%The1 raises some undead!", m)); playSound(c, "necromancy"); } } else if(m == moOutlaw) { for(cell *c1: gun_targets(c)) if(canAttack(c, moOutlaw, c1, c1->monst, AF_GETPLAYER | AF_ONLY_FBUG | AF_GUN)) { attackMonster(c1, AF_GETPLAYER | AF_ONLY_FBUG | AF_GUN, moOutlaw); c->stuntime = 1; break; } } else if(m == moWitchFlash && flashWouldKill(c, AF_GETPLAYER | AF_ONLY_FBUG) && !flashWouldKill(c, false)) { addMessage(XLAT("%The1 activates her Flash spell!", m)); m = moWitch; activateFlashFrom(c, moWitchFlash, AF_MAGIC | AF_GETPLAYER | AF_MSG); c->stuntime = 1; } else if(m == moCrystalSage && c->cpdist <= 4 && isIcyLand(cwt.at) && cwt.at->wall != waBoat) { // only one sage attacks if(sagefresh) { sagefresh = false; if(sagephase == 0) { addMessage(XLAT("%The1 shows you two fingers.", m)); addMessage(XLAT("You wonder what does it mean?")); } else if(sagephase == 1) { addMessage(XLAT("%The1 shows you a finger.", m)); addMessage(XLAT("You think about possible meanings.")); } else { addMessage(XLAT("%The1 moves his finger downwards.", m)); addMessage(XLAT("Your brain is steaming.")); } sagephase++; for(int i=0; i 4) continue; sageheat(t, .0); for(int i=0; itype; i++) sageheat(t->move(i), .3); } } c->stuntime = 1; } else if(m == moPyroCultist && !peace::on) { bool shot = false; bool dont_approach = false; // smaller range on the sphere int firerange = (sphere || getDistLimit() < 5) ? 2 : 4; for(int i=0; imonst)); makeflame(t, 20, false); playSound(t, "fire"); c->monst = moCultist; shot = true; } if(celldistance(c,t) <= 3 && !sphere) dont_approach = true; } if(shot || dont_approach) c->stuntime = 1; } else if(m == moVampire) { for(int i=0; istuntime = 1; } } } } } EX void moveworms() { if(!isize(worms)) return; pathdata pd(moWorm); int wrm = isize(worms); for(int i=0; imonst == moGolemMoved) c->monst = moGolem; if(c->monst == moMouseMoved) c->monst = moMouse; if(c->monst == moPrincessMoved) c->monst = moPrincess; if(c->monst == moPrincessArmedMoved) c->monst = moPrincessArmed; if(c->monst == moKnightMoved) c->monst = moKnight; if(c->monst == moTameBomberbirdMoved) c->monst = moTameBomberbird; } EX bool saved_tortoise_on(cell *c) { return (c->monst == moTortoise && c->item == itBabyTortoise && !((tortoise::getb(c) ^ tortoise::babymap[c]) & tortoise::mask)); } EX bool normal_gravity_at(cell *c) { return !in_gravity_zone(c); } EX void moverefresh(bool turn IS(true)) { int dcs = isize(dcal); for(int i=0; imonst == moWolfMoved) c->monst = moWolf; if(c->monst == moIvyNext) { c->monst = moIvyHead; ivynext(c); } if(c->monst == moIvyDead) removeIvy(c); refreshFriend(c); if(c->monst == moSlimeNextTurn) c->monst = moSlime; if(c->monst == moLesser && !cellEdgeUnstable(c)) c->monst = moLesserM; else if(c->monst == moLesserM) c->monst = moLesser; if(c->monst == moGreater && !cellEdgeUnstable(c)) c->monst = moGreaterM; else if(c->monst == moGreaterM) c->monst = moGreater; if(c->monst == moPair && !c->stuntime) { cell *c2 = c->move(c->mondir); if(c2->monst != moPair) continue; if(true) for(int i: {-1, 1}) { cell *c3 = c->modmove(c->mondir + i); if(among(c3->wall, waRuinWall, waColumn, waStone, waVinePlant, waPalace)) { drawParticles(c3, winf[c3->wall].color, 30); c3->wall = waNone; } } } if(c->stuntime && !isMutantIvy(c)) { c->stuntime--; int breathrange = sphere ? 2 : 3; if(c->stuntime == 0 && c->monst == moDragonHead) { // if moDragonHead is renamed to "Dragon Head", we might need to change this eMonster subject = c->monst; if(!c->hitpoints) c->hitpoints = 1; else if(shmup::on && dragon::totalhp(c) > 2 && shmup::dragonbreath(c)) { c->hitpoints = 0; } else if(dragon::totalhp(c) <= 2) ; else if(isMounted(c)) { if(dragon::target && celldistance(c, dragon::target) <= breathrange && makeflame(dragon::target, 5, true)) { addMessage(XLAT("%The1 breathes fire!", subject)); makeflame(dragon::target, 5, false); playSound(dragon::target, "fire"); c->hitpoints = 0; } } else { for(int i=0; imonst) addMessage(XLAT("%The1 breathes fire at %the2!", subject, t->monst)); else addMessage(XLAT("%The1 breathes fire!", subject)); makeflame(t, 5, false); playSound(t, "fire"); c->hitpoints = 0; } } } } } // tortoises who have found their children no longer move if(saved_tortoise_on(c)) c->stuntime = 2; if(c->monst == moReptile) { if(c->wall == waChasm || cellUnstable(c)) { c->monst = moNone; c->wall = waReptile; c->wparam = reptilemax(); playSound(c, "click"); } else if(isChasmy(c) || isWatery(c)) { if(c->wall == waMercury) { fallMonster(c, AF_FALL); c->wall = waNone; } else { c->wall = waReptileBridge; c->wparam = reptilemax(); c->monst = moNone; } c->item = itNone; playSound(c, "click"); } } if(c->wall == waChasm) { if(c->land != laWhirlwind) c->item = itNone; if(c->monst && !survivesChasm(c->monst) && c->monst != moReptile && normal_gravity_at(c)) { if(c->monst != moRunDog && c->land == laMotion) achievement_gain("FALLDEATH1"); addMessage(XLAT("%The1 falls!", c->monst)); fallMonster(c, AF_FALL); } } else if(isReptile(c->wall) && turn) { if(c->monst || isPlayerOn(c)) c->wparam = -1; else if(c->cpdist <= 7) { c->wparam--; if(c->wparam == 0) { if(c->wall == waReptile) c->wall = waChasm; else placeWater(c, NULL); c->monst = moReptile; c->hitpoints = 3; c->stuntime = 0; int gooddirs[MAX_EDGE], qdirs = 0; // in the peace mode, a reptile will // prefer to walk on the ground, rather than the chasm for(int i=0; itype; i++) { int i0 = (i+3) % c->type; int i1 = (i+c->type-3) % c->type; if(c->move(i0) && passable(c->move(i0), c, 0)) if(c->move(i1) && passable(c->move(i1), c, 0)) gooddirs[qdirs++] = i; } if(qdirs) c->mondir = gooddirs[hrand(qdirs)]; playSound(c, "click"); } } } else if(isFire(c)) { if(c->monst == moSalamander) c->stuntime = max(c->stuntime, 1); else if(c->monst && !survivesFire(c->monst) && !isWorm(c->monst)) { addMessage(XLAT("%The1 burns!", c->monst)); if(isBull(c->monst)) { addMessage(XLAT("Fire is extinguished!")); c->wall = waNone; } fallMonster(c, AF_CRUSH); } if(c->item && itemBurns(c->item)) { addMessage(XLAT("%The1 burns!", c->item)); c->item = itNone; } } else if(isWatery(c)) { if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM) c->monst = moGreaterShark; if(c->monst && !survivesWater(c->monst) && normal_gravity_at(c)) { playSound(c, "splash"+pick12()); if(isNonliving(c->monst)) addMessage(XLAT("%The1 sinks!", c->monst)); else addMessage(XLAT("%The1 drowns!", c->monst)); if(isBull(c->monst)) { addMessage(XLAT("%The1 is filled!", c->wall)); c->wall = waNone; } fallMonster(c, AF_FALL); } } else if(c->wall == waSulphur || c->wall == waSulphurC || c->wall == waMercury) { if(c->monst && !survivesPoison(c->monst, c->wall) && normal_gravity_at(c)) { playSound(c, "splash"+pick12()); if(isNonliving(c->monst)) addMessage(XLAT("%The1 sinks!", c->monst)); else addMessage(XLAT("%The1 drowns!", c->monst)); if(isBull(c->monst)) { addMessage(XLAT("%The1 is filled!", c->wall)); c->wall = waNone; } fallMonster(c, AF_FALL); } } else if(c->wall == waMagma) { if(c->monst == moSalamander) c->stuntime = max(c->stuntime, 1); else if(c->monst && !survivesPoison(c->monst, c->wall) && normal_gravity_at(c)) { if(isNonliving(c->monst)) addMessage(XLAT("%The1 is destroyed by lava!", c->monst)); else addMessage(XLAT("%The1 is killed by lava!", c->monst)); playSound(c, "steamhiss", 70); fallMonster(c, AF_FALL); } } else if(!isWateryOrBoat(c)) { if(c->monst == moGreaterShark) c->monst = moGreaterM; else if(c->monst == moShark || c->monst == moCShark) { addMessage(XLAT("%The1 suffocates!", c->monst)); fallMonster(c, AF_CRUSH); } else if(c->monst == moKrakenH) { addMessage(XLAT("%The1 suffocates!", c->monst)); kraken::kill(c, moNone); } } if(c->monst == moVineSpirit && !cellHalfvine(c) && c->wall != waVinePlant) { addMessage(XLAT("%The1 is destroyed!", c->monst)); fallMonster(c, AF_CRUSH); } if(c->monst) mayExplodeMine(c, c->monst); if(c->monst && c->wall == waClosedGate && !survivesWall(c->monst)) { playSound(c, "hit-crush"+pick123()); addMessage(XLAT("%The1 is crushed!", c->monst)); fallMonster(c, AF_CRUSH); } if(c->monst && cellUnstable(c) && !ignoresPlates(c->monst) && !shmup::on) doesFallSound(c); } } EX void consMove(cell *c, eMonster param) { eMonster m = c->monst; if(movegroup(m) != moYeti) return; if(m == moWitchSpeed) havewhat |= HF_FAST; bool slow = slowMover(m); if(slow) havewhat |= HF_SLOW; if(param == moYeti && slow) return; if(param == moTortoise && !slow) return; if(param == moWitchSpeed && m != moWitchSpeed) return; if(isActiveEnemy(c, moPlayer)) { int goodmoves = 0; for(int t=0; ttype; t++) { cell *c2 = c->move(t); if(c2 && c2->pathdist < c->pathdist) goodmoves++; } movesofgood[goodmoves].push_back(c); } else movesofgood[0].push_back(c); } EX void moveNormals(eMonster param) { pathdata pd(param); for(int d=0; d<=MAX_EDGE; d++) movesofgood[d].clear(); for(int i=0; ipathdist == PINFD) consMove(c, param); } for(int d=0; d<=MAX_EDGE; d++) for(int i=0; imonst].mgroup == moYeti) { moveNormal(c, MF_PATHDIST); } } } EX void markAmbush(cell *c, manual_celllister& cl) { if(!cl.add(c)) return; forCellEx(c2, c) if(c2->cpdist < c->cpdist) markAmbush(c2, cl); } int ambush_distance; bool ambushed; EX void checkAmbushState() { if(havewhat & HF_HUNTER) { manual_celllister cl; for(cell *c: dcal) { if(c->monst == moHunterDog) { if(c->cpdist > ambush_distance) ambush_distance = c->cpdist; markAmbush(c, cl); } if(c->monst == moHunterGuard && c->cpdist <= 4) markAmbush(c, cl); } if(items[itHunting] > 5 && items[itHunting] <= 22) { int q = 0; for(int i=0; iitem == itOrbSafety) return; DEBB(DF_TURN, ("river")); if(havewhat & HF_RIVER) prairie::move(); /* DEBB(DF_TURN, ("magnet")); if(havewhat & HF_MAGNET) groupmove(moSouthPole, 0), groupmove(moNorthPole, 0); */ DEBB(DF_TURN, ("bugs")); if(havewhat & HF_HEXD) groupmove(moHexDemon, 0); if(havewhat & HF_ALT) groupmove(moAltDemon, 0); if(havewhat & HF_MONK) groupmove(moMonk, 0); DEBB(DF_TURN, ("worm")); cell *savepos[MAXPLAYER]; for(int i=0; i ORBBASE && cwt.at->monst) return false; int flags = 0; if(cwt.at->monst) { if(checkonly) return true; if(isMountable(cwt.at->monst)) addMessage(XLAT("You need to dismount %the1!", cwt.at->monst)); else addMessage(XLAT("You need to move to give space to %the1!", cwt.at->monst)); } else if(cwt.at->wall == waRoundTable) { if(markOrb2(itOrbAether)) return false; if(checkonly) return true; addMessage(XLAT("It would be impolite to land on the table!")); } else if(cwt.at->wall == waLake) { if(markOrb2(itOrbAether)) return false; if(markOrb2(itOrbFish)) return false; if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false; if(checkonly) return true; flags |= AF_FALL; addMessage(XLAT("Ice below you is melting! RUN!")); } else if(!attacking && cellEdgeUnstable(cwt.at)) { if(markOrb2(itOrbAether)) return false; if(checkonly) return true; if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false; addMessage(XLAT("Nothing to stand on here!")); } else if(cwt.at->wall == waSea || cwt.at->wall == waCamelotMoat) { if(markOrb(itOrbFish)) return false; if(markOrb2(itOrbAether)) return false; if(in_gravity_zone(cwt.at) && passable(cwt.at, NULL, P_ISPLAYER)) return false; if(checkonly) return true; addMessage(XLAT("You have to run away from the water!")); } else if(cwt.at->wall == waClosedGate) { if(markOrb2(itOrbAether)) return false; if(checkonly) return true; addMessage(XLAT("The gate is closing right on you! RUN!")); } else if(isFire(cwt.at) && !markOrb(itOrbWinter) && !markOrb2(itOrbShield)) { if(markOrb2(itOrbAether)) return false; if(checkonly) return true; addMessage(XLAT("This spot will be burning soon! RUN!")); } else if(cwt.at->wall == waMagma && !markOrb(itOrbWinter) && !markOrb2(itOrbShield)) { if(markOrb2(itOrbAether)) return false; if(in_gravity_zone(cwt.at) && passable(cwt.at, cwt.at, P_ISPLAYER)) return false; if(checkonly) return true; addMessage(XLAT("Run away from the magma!")); } else if(cwt.at->wall == waChasm) { if(markOrb2(itOrbAether)) return false; if(in_gravity_zone(cwt.at) && passable(cwt.at, cwt.at, P_ISPLAYER)) return false; if(checkonly) return true; flags |= AF_FALL; addMessage(XLAT("The floor has collapsed! RUN!")); } else if(items[itOrbAether] > ORBBASE && !passable(cwt.at, NULL, P_ISPLAYER | P_NOAETHER)) { if(markOrb2(itOrbAether)) return false; return true; } else if(!passable(cwt.at, NULL, P_ISPLAYER)) { if(isFire(cwt.at)) return false; // already checked: have Shield if(markOrb2(itOrbAether)) return false; if(checkonly) return true; addMessage(XLAT("Your Aether power has expired! RUN!")); } else return false; if(hardcore) killHardcorePlayer(multi::cpid, flags); return true; } EX int countMyGolems(eMonster m) { int g=0, dcs = isize(dcal); for(int i=0; imonst == m) g++; } return g; } EX int savePrincesses() { int g=0, dcs = isize(dcal); for(int i=0; imonst)) princess::save(c); } return g; } EX int countMyGolemsHP(eMonster m) { int g=0, dcs = isize(dcal); for(int i=0; imonst == m) g += c->hitpoints; } return g; } EX void restoreGolems(int qty, eMonster m, int hp IS(0)) { int dcs = isize(dcal); for(int i=1; qty && impdist >= 3 && passable(c, NULL, P_FLYING)) : passable(c, NULL, 0)) { c->hitpoints = hp / qty; c->monst = m, qty--, hp -= c->hitpoints; if(m == moPrincess || m == moPrincessArmed) princess::newFakeInfo(c); } } } EX void placeItems(int qty, eItem it) { int dcs = isize(dcal); for(int i=1; qty && imonst && !c->item && passable(c, NULL, 0)) c->item = it, qty--; } } EX cellwalker recallCell; EX bool activateRecall() { if(!recallCell.at) { addMessage("Error: no recall"); return false; } items[itOrbRecall] = 0; items[itOrbSafety] = 0; if(!makeEmpty(recallCell.at)) { addMessage(XLAT("Your Orb of Recall is blocked by something big!")); recallCell.at = NULL; return false; } killFriendlyIvy(); movecost(cwt.at, recallCell.at, 3); playerMoveEffects(cwt.at, recallCell.at); mirror::destroyAll(); sword::reset(); cwt = recallCell; recallCell.at = NULL; flipplayer = true; fullcenter(); makeEmpty(cwt.at); forCellEx(c2, cwt.at) if(c2->monst != moMutant) c2->stuntime = 4; if(shmup::on) shmup::recall(); if(multi::players > 1) multi::recall(); bfs(); checkmove(); drawSafety(); addMessage(XLAT("You are recalled!")); return true; } EX void saveRecall(cellwalker cw2) { if(!recallCell.at) recallCell = cw2; } EX void activateSafety(eLand l) { if(recallCell.at && activateRecall()) return; savePrincesses(); int gg = countMyGolems(moGolem); int gb = countMyGolems(moTameBomberbird); int gp1 = countMyGolems(moPrincess); int gp2 = countMyGolems(moPrincessArmed); int gph1 = countMyGolemsHP(moPrincess); int gph2 = countMyGolemsHP(moPrincessArmed); drawSafety(); addMessage(XLAT("You fall into a wormhole!")); eLand f = firstland; if(l == laTemple) l = laRlyeh; if(l == laClearing) l = laOvergrown; if(l == laWhirlpool) l = laOcean; if(l == laCrossroads5) l = laCrossroads2; // could not fit! if(l == laCamelot && !(tactic::on && specialland == laCamelot)) l = laCrossroads; firstland = l; safetyland = l; safetyseed = hrandpos(); clear_euland(firstland); safety = true; avengers = 0; clearMemory(); initcells(); initgame(); firstland = f; safety = false; restoreGolems(gg, moGolem); restoreGolems(gb, moTameBomberbird); restoreGolems(gp1, moPrincess, gph1); restoreGolems(gp2, moPrincessArmed, gph2); restartGraph(); } bool legalmoves[MAX_EDGE+1]; EX bool hasSafeOrb(cell *c) { return c->item == itOrbSafety || c->item == itOrbShield || c->item == itOrbShell || (c->item == itOrbYendor && yendor::state(c) == yendor::ysUnlocked); } EX void checkmove() { if(dual::state == 2) return; dynamicval gs(gravity_state, gravity_state); #if CAP_INV if(inv::on) inv::compute(); #endif if(multi::players > 1 && !multi::checkonly) return; if(hardcore) return; bool orbusedbak[ittypes]; // do not activate orbs! for(int i=0; itype; i++) if(movepcto(1, -1, true)) canmove = legalmoves[cwt.spin] = true; if(vid.mobilecompasssize || !canmove) for(int i=0; itype; i++) if(movepcto(1, 1, true)) canmove = legalmoves[cwt.spin] = true; if(kills[moPlayer]) canmove = false; #if CAP_INV if(inv::on && !canmove && !inv::incheck) { if(inv::remaining[itOrbSafety] || inv::remaining[itOrbFreedom]) canmove = true; else { inv::check(1); checkmove(); inv::check(-1); } if(canmove) pushScreen(inv::show); } #endif if(!canmove) { achievement_final(true); if(cmode & sm::NORMAL) showMissionScreen(); } if(canmove && timerstopped) { timerstart = time(NULL); timerstopped = false; } items[itWarning]-=2; for(int i=0; imonst == moFriendlyIvy) killMonster(on, moPlayer); if(on->monst) { addMessage(XLAT("There is no room for %the1!", m)); return; } if(passable(on, moveto, P_ISFRIEND | (m == moTameBomberbird ? P_FLYING : 0))) on->monst = m; else { on->monst = m; flagtype f = AF_CRUSH; if(isFire(on)) addMessage(XLAT("%The1 burns!", m)); else if(on->wall == waChasm) addMessage(XLAT("%The1 falls!", m)), f = AF_FALL; else if(isWatery(on) && isNonliving(m)) addMessage(XLAT("%The1 sinks!", m)), f = AF_FALL; else if(isWatery(on)) addMessage(XLAT("%The1 drowns!", m)), f = AF_FALL; else if(isWall(on)) addMessage(XLAT("%The1 is crushed!", m)); else if(m == moTameBomberbird && cwt.at->wall == waBoat) return; else addMessage(XLAT("%The1 is destroyed!", m)); printf("mondir = %d\n", on->mondir); fallMonster(cwt.at, f); } } EX bool multiRevival(cell *on, cell *moveto) { int fl = 0; if(items[itOrbAether]) fl |= P_AETHER; if(items[itOrbFish]) fl |= P_FISH; if(items[itOrbWinter]) fl |= P_WINTER; if(passable(on, moveto, fl)) { int id = multi::revive_queue[0]; for(int i=1; iland == laPower && to->land != laPower && (phase & 1)) { 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!")); } #if CAP_TOUR if(from->land != to->land && tour::on && (phase & 2)) tour::checkGoodLand(to->land); #endif if(to->land ==laCrossroads4 && !got_crossroads && !geometry && (phase & 2) && !cheater) { achievement_gain("CR4"); got_crossroads = true; chaosUnlocked = true; } if(isHaunted(from->land) && !isHaunted(to->land) && (phase & 2)) { updateHi(itLotus, truelotus = items[itLotus]); if(items[itLotus] >= 1) achievement_gain("LOTUS1"); if(items[itLotus] >= (big_unlock ? 25 : 10)) achievement_gain("LOTUS2"); if(items[itLotus] >= (big_unlock ? 50 : 25)) achievement_gain("LOTUS3"); if(items[itLotus] >= 50 && !big_unlock) achievement_gain("LOTUS4"); achievement_final(false); } if(geometry == gNormal && celldist(to) == 0 && !usedSafety && gold() >= 100 && (phase & 2)) achievement_gain("COMEBACK"); bool tortoiseOK = to->land == from->land || to->land == laTortoise || (to->land == laDragon && from->land != laTortoise) || chaosmode; if(tortoise::seek() && !from->item && !tortoiseOK && passable(from, NULL, 0) && (phase & 2)) { from->item = itBabyTortoise; tortoise::babymap[from] = tortoise::seekbits; addMessage(XLAT("You leave %the1.", itBabyTortoise)); items[itBabyTortoise]--; } } EX bool cantGetGrimoire(cell *c2, bool verbose IS(true)) { if(chaosmode) return false; if(!eubinary && !c2->master->alt) return false; if(c2->item == itGrimoire && items[itGrimoire] > celldistAlt(c2)/-TEMPLE_EACH) { if(verbose) addMessage(XLAT("You already have this Grimoire! Seek new tomes in the inner circles.")); return true; } return false; } EX void gainLife() { items[itOrbLife] ++; if(items[itOrbLife] > 5 && !shmup::on) items[itOrbLife] = 5; } EX void collectMessage(cell *c2, eItem which) { bool specialmode = yendor::on || princess::challenge || cheater || !in_full_game(); if(which == itDodeca && peace::on) return; if(which == itTreat) ; else if(isElementalShard(which)) { int tsh = items[itFireShard] + items[itAirShard] + items[itWaterShard] + items[itEarthShard] + items[itElemental]; if(tsh == 0) { addMessage(XLAT("Collect four different Elemental Shards!")); addMessage(XLAT("Unbalanced shards in your inventory are dangerous.")); } else { string t = XLAT("You collect %the1. (%2)", which, its(items[which]+1)); addMessage(t); } } else if(which == itKey) { addMessage(XLAT("You have found the Key! Now unlock this Orb of Yendor!")); } else if(which == itGreenStone && !items[itGreenStone]) addMessage(XLAT("This orb is dead...")); else if(which == itGreenStone) addMessage(XLAT("Another Dead Orb.")); else if(itemclass(which) != IC_TREASURE) { if(!inv::activating) addMessage(XLAT("You have found %the1!", which)); } else if(which == itBabyTortoise) { playSound(c2, playergender() ? "speak-princess" : "speak-prince"); addMessage(XLAT("Aww, poor %1... where is your family?", which)); } else if(gold() == 0 && !specialmode) addMessage(XLAT("Wow! %1! This trip should be worth it!", which)); else if(gold() == 1 && !specialmode) addMessage(XLAT("For now, collect as much treasure as possible...")); else if(gold() == 2 && !specialmode) addMessage(XLAT("Prove yourself here, then find new lands, with new quests...")); else if(!items[which] && itemclass(which) == IC_TREASURE) addMessage(XLAT("You collect your first %1!", which)); else if(items[which] == 4 && maxgold() == U5-1 && !specialmode) { addMessage(XLAT("You feel that %the2 become%s2 more dangerous.", which, c2->land)); addMessage(XLAT("With each %1 you collect...", which, c2->land)); } else if(items[which] == 9 && maxgold() == 9 && !specialmode) { if(inv::on) { addMessage(XLAT("The treasure gives your magical powers!", c2->land)); if(!ISMOBILE) addMessage(XLAT("Press 'i' to access your magical powers.", c2->land)); } else addMessage(XLAT("Are there any magical orbs in %the1?...", c2->land)); } else if(items[which] == R10 && maxgold() == R10 && !specialmode && !inv::on) { addMessage(XLAT("You feel that %the1 slowly become%s1 dangerous...", c2->land)); addMessage(XLAT("Better find some other place.")); } else if(which == itHunting && items[itHunting] == 4 && !specialmode && !ISMOBWEB) addMessage(XLAT("Hint: hold Alt to highlights enemies and other important features.")); else if(which == itSpice && items[itSpice] == U10*7/10 && !specialmode && isLandIngame(laHell)) addMessage(XLAT("You have a vision of the future, fighting demons in Hell...")); else if(which == itSpice && items[itSpice] == U10-1 && !specialmode && isLandIngame(laRedRock)) addMessage(XLAT("You will be fighting red rock snakes, too...")); else if(which == itKraken && items[itKraken] == U10-1 && !specialmode) addMessage(XLAT("You feel that a magical weapon is waiting for you...")); // else if(which == itFeather && items[itFeather] == 10) // addMessage(XLAT("There should be a Palace somewhere nearby...")); else if(which == itElixir && items[itElixir] == U5-1 && !specialmode) addMessage(XLAT("With this Elixir, your life should be long and prosperous...")); else if(which == itRuby && items[itRuby] == U5-1 && !specialmode && isLandIngame(laMountain)) { addMessage(XLAT("You feel something strange about gravity here...")); } else if(which == itPalace && items[itPalace] == U5-1 && !specialmode && isLandIngame(laDungeon)) { addMessage(XLAT("The rug depicts a man in a deep dungeon, unable to leave.")); } else if(which == itFeather && items[itFeather] == 25-1 && !specialmode && inv::on && !chaosmode) addMessage(XLAT("You feel the presence of free saves on the Crossroads.")); else if(which == itHell && items[itHell] == 25-1 && !specialmode && inv::on && !chaosmode) addMessage(XLAT("You feel the Orbs of Yendor nearby...")); else if(which == itHell && items[itHell] == 50-1 && !specialmode && inv::on && !chaosmode) addMessage(XLAT("You feel the Orbs of Yendor in the Crossroads...")); else if(which == itHell && items[itHell] == 100-1 && !specialmode && inv::on && !chaosmode) addMessage(XLAT("You feel the Orbs of Yendor everywhere...")); else if(which == itBone && items[itBone] % 25 == 24 && !specialmode && inv::on) addMessage(XLAT("You have gained an offensive power!")); else if(which == itHell && items[itHell] >= 100 && items[itHell] % 25 == 24 && !specialmode && inv::on) addMessage(XLAT("A small reward for braving the Hell.")); else if(which == itIvory && items[itIvory] == U5-1 && !specialmode && (isLandIngame(laMountain) || isLandIngame(laDungeon))) { addMessage(XLAT("You feel attuned to gravity, ready to face mountains and dungeons.")); } else if(which == itBone && items[itBone] == U5+1 && !specialmode && isLandIngame(laHell)) addMessage(XLAT("The Necromancer's Totem contains hellish incantations...")); else if(which == itStatue && items[itStatue] == U5+1 && !specialmode) addMessage(XLAT("The inscriptions on the Statue of Cthulhu point you toward your destiny...")); else if(which == itStatue && items[itStatue] == U5-1 && isLandIngame(laTemple)) addMessage(XLAT("There must be some temples of Cthulhu in R'Lyeh...")); else if(which == itDiamond && items[itDiamond] == U10-2 && !specialmode) addMessage(XLAT("Still, even greater treasures lie ahead...")); else if(which == itFernFlower && items[itFernFlower] == U5-1 && isLandIngame(laEmerald)) addMessage(XLAT("You overheard Hedgehog Warriors talking about emeralds...")); else if(which == itEmerald && items[itEmerald] == U5-1 && !specialmode && isLandIngame(laCamelot)) addMessage(XLAT("You overhear miners talking about a castle...")); else if(which == itEmerald && items[itEmerald] == U5 && !specialmode && isLandIngame(laCamelot)) addMessage(XLAT("A castle in the Crossroads...")); else if(which == itShard) ; else { int qty = (which == itBarrow) ? c2->landparam : 1; string t; if(which == itBarrow && items[which] < 25 && items[which] + qty >= 25) t = XLAT("Your energy swords get stronger!"); else if(maxgold() < 25 && items[which] + qty >= 25) t = XLAT("You feel even more attuned to the magic of this land!"); else t = XLAT("You collect %the1. (%2)", which, its(items[which]+qty)); addMessage(t); } } int ambushval; EX int ambushSize(cell *c, eItem what) { bool restricted = false; for(cell *c2: dcal) { if(c2->cpdist > 3) break; if(c2->monst && !isFriendly(c2) && !slowMover(c2) && !isMultitile(c2)) restricted = true; } int qty = items[itHunting]; if(ambushval) return ambushval; switch(what) { case itCompass: return 0; case itHunting: return min(min(qty, max(33-qty, 6)), 15); case itOrbSide3: return restricted ? 10 : 20; case itOrbFreedom: return restricted ? 10 : 60; case itOrbThorns: case itOrb37: return 20; case itOrbLava: return 20; case itOrbBeauty: return 35; case itOrbShell: return 35; case itOrbPsi: // return 40; -> no benefits return 20; case itOrbDash: case itOrbFrog: return 40; case itOrbAir: case itOrbDragon: return 50; case itOrbStunning: // return restricted ? 50 : 60; -> no benefits return 30; case itOrbBull: case itOrbSpeed: case itOrbShield: return 60; case itOrbInvis: return 80; case itOrbTeleport: return 300; case itGreenStone: case itOrbSafety: case itOrbYendor: return 0; case itKey: return 16; case itWarning: return qty; default: return restricted ? 6 : 10; break; // Flash can survive about 70, but this gives no benefits } } EX int ambush(cell *c, eItem what) { int maxdist = gamerange(); celllister cl(c, maxdist, 1000000, NULL); cell *c0 = c; int d = 0; int dogs0 = 0; for(cell *cx: cl.lst) { int dh = cl.getdist(cx); if(dh <= 2 && cx->monst == moHunterGuard) cx->monst = moHunterDog, dogs0++; if(dh > d) c0 = cx, d = dh; } if(sphere) { int dogs = ambushSize(c, what); for(int i = cl.lst.size()-1; i>0 && dogs; i--) if(!isPlayerOn(cl.lst[i]) && !cl.lst[i]->monst) cl.lst[i]->monst = moHunterDog, dogs--; } vector around; cell *clast = NULL; cell *ccur = c0; int v = VALENCE; if(v > 4) { for(cell *c: cl.lst) if(cl.getdist(c) == d) around.push_back(c); hrandom_shuffle(&around[0], isize(around)); } else { for(int tries=0; tries<10000; tries++) { cell *c2 = NULL; if(v == 3) { forCellEx(c1, ccur) if(c1 != clast && cl.listed(c1) && cl.getdist(c1) == d) c2 = c1; } if(v == 4) { for(int i=0; itype; i++) { cell *c1 = (cellwalker(ccur, i) + wstep + 1).peek(); if(!c1) continue; if(c1 != clast && cl.listed(c1) && cl.getdist(c1) == d) c2 = c1; } } if(!c2) break; if(c2->land == laHunting && c2->wall == waNone && c2->monst == moNone) around.push_back(c2); clast = ccur; ccur = c2; if(c2 == c0) break; } } int N = isize(around); int dogs = ambushSize(c, what); int gaps = dogs; if(!N) return dogs0; ambushed = true; int shift = hrand(N); dogs = min(dogs, N); gaps = min(gaps, N); for(int i=0; imonst = moHunterDog; nextdog->stuntime = 1; drawFlash(nextdog); } return dogs + dogs0; } EX bool cannotPickupItem(cell *c, bool telekinesis) { return itemHidden(c) && !telekinesis && !(isWatery(c) && markOrb(itOrbFish)); } EX bool canPickupItemWithMagnetism(cell *c, cell *from) { if(!c->item || c->item == itOrbYendor || isWall(c) || cannotPickupItem(c, false)) return false; if(c->item == itCompass && from->item) return false; return true; } EX bool doPickupItemsWithMagnetism(cell *c) { cell *csaf = NULL; if(items[itOrbMagnetism]) forCellEx(c3, c) if(canPickupItemWithMagnetism(c3, c)) { if(c3->item == itCompass) { if(!c->item) moveItem(c3, c, false); } else if(c3->item == itOrbSafety || c3->item == itBuggy || c3->item == itBuggy2) csaf = c3; else if(markOrb(itOrbMagnetism)) collectItem(c3, false); } if(csaf) return collectItem(csaf, false); return false; } EX void pickupMovedItems(cell *c) { if(!c->item) return; if(c->item == itOrbSafety) return; if(isPlayerOn(c)) collectItem(c, true); if(items[itOrbMagnetism]) forCellEx(c2, c) if(isPlayerOn(c2) && canPickupItemWithMagnetism(c, c2)) collectItem(c, true); } EX bool collectItem(cell *c2, bool telekinesis IS(false)) { int pg = gold(); bool dopickup = true; bool had_choice = false; if(cannotPickupItem(c2, telekinesis)) return false; /* if(c2->item == itHolyGrail && telekinesis) return false; */ if(c2->item) { invismove = false; if(shmup::on) shmup::visibleFor(2000); string s0 = ""; if(c2->item == itPalace && items[c2->item] == 12) princess::forceVizier = true; if(!cantGetGrimoire(c2, false)) collectMessage(c2, c2->item); if(c2->item == itDodeca && peace::on) peace::simon::extend(); } if(c2->land == laHunting && c2->item && !inv::activating) { int dogs = ambush(c2, c2->item); if(dogs) addMessage(XLAT("You are ambushed!")); } if(isRevivalOrb(c2->item) && multi::revive_queue.size()) { multiRevival(cwt.at, c2); } else if(isShmupLifeOrb(c2->item) && shmup::on) { playSound(c2, "pickup-orb"); // TODO summon gainLife(); } else if(orbcharges(c2->item)) { eItem it = c2->item; if(it == itOrbRecall && !dual::state) { cellwalker cw2 = cwt; cw2++; cw2.at = c2; saveRecall(cw2); } if(it == itOrbFire) playSound(c2, "fire"); else if(it == itOrbFire) playSound(c2, "fire"); else if(it == itOrbWinter) playSound(c2, "pickup-winter"); else if(it == itOrbSpeed) playSound(c2, "pickup-speed"); else if(it == itRevolver) playSound(c2, "pickup-key"); else playSound(c2, "pickup-orb"); if(items[itOrbChoice]) items[itOrbChoice] = 0, had_choice = true; int oc = orbcharges(it); if(dual::state && among(it, itOrbTeleport, itOrbFrog, itOrbPhasing, itOrbDash, itOrbRecall)) { oc = 10; it = itOrbSpeed; } if(c2->land == laAsteroids) oc = 10; if(markOrb(itOrbIntensity)) oc = oc * 6 / 5; if(!items[it]) items[it]++; items[it] += oc; } else if(c2->item == itOrbLife) { playSound(c2, "pickup-orb"); // TODO summon placeGolem(cwt.at, c2, moGolem); } else if(c2->item == itOrbFriend) { playSound(c2, "pickup-orb"); // TODO summon placeGolem(cwt.at, c2, moTameBomberbird); } #if CAP_TOUR else if(tour::on && (c2->item == itOrbSafety || c2->item == itOrbRecall)) { addMessage(XLAT("This Orb is not compatible with the Tutorial.")); return true; } #endif else if(c2->item == itOrbSafety) { playSound(c2, "pickup-orb"); // TODO safety if(!dual::state) items[c2->item] = 7; if(shmup::on) shmup::delayed_safety = true; else activateSafety(c2->land); return true; } else if(c2->item == itBabyTortoise) { using namespace tortoise; int bnew = babymap[c2]; babymap.erase(c2); int bold = seekbits; seekbits = bnew; tortoise::last = seekbits; if(seek()) { cell *c = passable(cwt.at, NULL, 0) ? cwt.at : c2; c->item = itBabyTortoise; if(c == c2) dopickup = false; babymap[c] = bold; } else items[itBabyTortoise]++; } else if(c2->item == itOrbYendor && peace::on) { if(!items[itDodeca]) { addMessage(XLAT("Collect as many Dodecahedra as you can, then return here!")); } else { addMessage(XLAT("Your score: %1", its(items[itDodeca]))); peace::simon::restore(); } dopickup = false; } else if(c2->item == itOrbYendor && yendor::state(c2) != yendor::ysUnlocked) { dopickup = false; } else if(c2->item == itOrbYendor) yendor::collected(c2); else if(c2->item == itHolyGrail) { playSound(c2, "tada"); int v = newRoundTableRadius() + 12; items[itOrbTeleport] += v; items[itOrbSpeed] += v; items[itHolyGrail]++; addMessage(XLAT("Congratulations! You have found the Holy Grail!")); if(!eubinary) c2->master->alt->emeraldval |= GRAIL_FOUND; achievement_collection(c2->item, pg, gold()); } else if(c2->item == itKey) { playSound(c2, "pickup-key"); for(int i=0; iitem == itCompass) { dopickup = false; } else if(c2->item == itBuggy || c2->item == itBuggy2) { items[itOrbSafety] += 7; if(shmup::on) shmup::delayed_safety = true; else { buggyGeneration = false; activateSafety(laCrossroads); } return true; } else if(c2->item == itTreat) { playSound(c2, "pickup-scroll"); halloween::getTreat(c2); } else { bool lhu = landUnlocked(laHell); if(c2->item == itBarrow) for(int i=0; ilandparam; i++) gainItem(c2->item); else if(c2->item) gainItem(c2->item); int g2 = gold(); if(c2->item) { char ch = iinf[c2->item].glyph; if(ch == '*') playSound(c2, "pickup-gem"); else if(ch == '$' || ch == 'x') playSound(c2, "pickup-gold"); else if(ch == '%' || ch == ';') playSound(c2, "pickup-potion"); else playSound(c2, "pickup-scroll"); } if(items[itFireShard] && items[itAirShard] && items[itWaterShard] && items[itEarthShard]) { items[itFireShard]--; items[itAirShard]--; items[itWaterShard]--; items[itEarthShard]--; gainItem(itElemental); gainItem(itElemental); gainItem(itElemental); gainItem(itElemental); addMessage(XLAT("You construct some Elemental Gems!", c2->item) + itemcounter(items[itElemental])); } if(c2->item == itBounty) items[itRevolver] = 6; if(c2->item == itHyperstone && items[itHyperstone] == 10) achievement_victory(true); if(chaosmode && gold() >= 300 && !chaosAchieved) { achievement_gain("CHAOS", rg::chaos); chaosAchieved = true; } #if ISMOBILE==1 if(pg < lastsafety + R30*3/2 && g2 >= lastsafety + R30*3/2) addMessage(XLAT("The Orb of Safety from the Land of Eternal Motion might save you.")); #endif #define IF(x) if(pg < (x) && g2 >= x && !peace::on) IF(R60/4) addMessage(XLAT("Collect treasure to access more different lands...")); IF(R30) addMessage(XLAT("You feel that you have enough treasure to access new lands!")); IF(R30*3/2) addMessage(XLAT("Collect more treasures, there are still more lands waiting...")); IF(R60) addMessage(XLAT("You feel that the stars are right, and you can access R'Lyeh!")); IF(R30*5/2) addMessage(XLAT("Kill monsters and collect treasures, and you may get access to Hell...")); IF(R10 * 9) addMessage(XLAT("To access Hell, collect %1 treasures each of 9 kinds...", its(R10))); if(landUnlocked(laHell) && !lhu) { addMessage(XLAT("Abandon all hope, the gates of Hell are opened!")); addMessage(XLAT("And the Orbs of Yendor await!")); } } if(dopickup && c2->item) { #if CAP_HISTORY // temporary variable to avoid the "cannot bind bitfield" problem in C++11 eItem dummy = c2->item; history::findhistory.emplace_back(c2, dummy); #endif if(c2->item == itBombEgg && c2->land == laMinefield) { c2->landparam |= 2; c2->landparam &= ~1; } if(!had_choice) c2->item = itNone; } // if(c2->land == laHive) // c2->heat = 1; int numOrb = 0; for(int i=0; i= princess::reviveAt && !inv::on) { princess::reviveAt = 0, items[itSavedPrincess] = 1; addMessage("You have enough treasure now to revive the Princess!"); } return false; } EX void glance_message() { 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.")); } EX void dropGreenStone(cell *c) { if(items[itGreenStone] && !passable(c, NULL, P_MONSTER)) { // NOTE: PL/CZ translations assume that itGreenStone is dropped to avoid extra forms! addMessage(XLAT("Cannot drop %the1 here!", itGreenStone)); return; } if(items[itGreenStone] && c->item == itNone) { items[itGreenStone]--; if(false) { c->item = itNone; spill(c, eWall(c->wall ^ waFloorA ^ waFloorB), 3); addMessage(XLAT("The slime reacts with %the1!", itGreenStone)); } else { c->item = itGreenStone; addMessage(XLAT("You drop %the1.", itGreenStone)); if(isHaunted(cwt.at->land)) survivalist = false; } } else { if(items[itGreenStone] && c->item == itGreenStone) addMessage(XLAT("You juggle the Dead Orbs.")); else if(items[itGreenStone] && c->item) addMessage(XLAT("You give %the1 a grim look.", c->item)); else if(items[itGreenStone]) { addMessage(XLAT("Cannot drop %the1 here!", itGreenStone)); return; } else glance_message(); } } EX void roundTableMessage(cell *c2) { if(!euclid && !cwt.at->master->alt) return; if(!euclid && !c2->master->alt) return; int dd = celldistAltRelative(c2) - celldistAltRelative(cwt.at); bool tooeasy = (roundTableRadius(c2) < newRoundTableRadius()); if(dd>0) { if(grailWasFound(cwt.at)) { addMessage(XLAT("The Knights congratulate you on your success!")); knighted = roundTableRadius(cwt.at); } else if(!tooeasy) addMessage(XLAT("The Knights laugh at your failure!")); } else { if(grailWasFound(cwt.at)) addMessage(XLAT("The Knights stare at you!")); else if(tooeasy) { if(!tactic::on) addMessage(XLAT("Come on, this is too easy... find a bigger castle!")); } else addMessage(XLAT("The Knights wish you luck!")); } } EX bool in_full_game() { if(tactic::on) return false; if(princess::challenge) return false; if(chaosmode) return true; if(euclid && isCrossroads(specialland)) return true; if(weirdhyperbolic && specialland == laCrossroads4) return true; if(geometry == gCrystal && isCrossroads(specialland)) return true; if(geometry == gNormal && !NONSTDVAR) return true; return false; } EX void knightFlavorMessage(cell *c2) { if(!eubinary && !c2->master->alt) { addMessage(XLAT("\"I am lost...\"")); return; } if(tactic::on) { addMessage(XLAT("\"The Knights of the Horocyclic Table salute you!\"")); return; } bool grailfound = grailWasFound(c2); int rad = roundTableRadius(c2); bool tooeasy = (rad < newRoundTableRadius()); static int msgid = 0; retry: if(msgid >= 32) msgid = 0; if(msgid == 0 && grailfound) { addMessage(XLAT("\"I would like to congratulate you again!\"")); } else if(msgid == 1 && !tooeasy) { addMessage(XLAT("\"Find the Holy Grail to become one of us!\"")); } else if(msgid == 2 && !tooeasy) { addMessage(XLAT("\"The Holy Grail is in the center of the Round Table.\"")); } #if CAP_CRYSTAL else if(msgid == 3 && geometry == gCrystal) { if(crystal::pure()) addMessage(XLAT("\"Each piece of the Round Table is exactly %1 steps away from the Holy Grail.\"", its(roundTableRadius(c2)))); else addMessage(XLAT("\"According to Merlin, the Round Table is a perfect Euclidean sphere in %1 dimensions.\"", its(ginf[gCrystal].sides/2))); } #endif else if(msgid == 3 && !peace::on && in_full_game()) { addMessage(XLAT("\"I enjoy watching the hyperbug battles.\"")); } else if(msgid == 4 && in_full_game()) { addMessage(XLAT("\"Have you visited a temple in R'Lyeh?\"")); } else if(msgid == 5 && in_full_game()) { addMessage(XLAT("\"Nice castle, eh?\"")); } else if(msgid == 6 && items[itSpice] < 10 && !peace::on && in_full_game()) { addMessage(XLAT("\"The Red Rock Valley is dangerous, but beautiful.\"")); } else if(msgid == 7 && items[itSpice] < 10 && !peace::on && in_full_game()) { addMessage(XLAT("\"Train in the Desert first!\"")); } else if(msgid == 8 && sizes_known() && !tactic::on) { string s = ""; if(0) ; #if CAP_CRYSTAL else if(geometry == gCrystal) s = crystal::get_table_boundary(); #endif else if(!quotient) s = expansion.get_descendants(rad).get_str(100); if(s == "") { msgid++; goto retry; } addMessage(XLAT("\"Our Table seats %1 Knights!\"", s)); } else if(msgid == 9 && sizes_known() && !tactic::on) { string s = ""; if(0); #if CAP_CRYSTAL else if(geometry == gCrystal) s = crystal::get_table_volume(); #endif else if(!quotient) s = expansion.get_descendants(rad-1, expansion.diskid).get_str(100); if(s == "") { msgid++; goto retry; } addMessage(XLAT("\"There are %1 floor tiles inside our Table!\"", s)); } else if(msgid == 10 && !items[itPirate] && !items[itWhirlpool] && !peace::on && in_full_game()) { addMessage(XLAT("\"Have you tried to take a boat and go into the Ocean? Try it!\"")); } else if(msgid == 11 && !princess::saved && in_full_game()) { addMessage(XLAT("\"When I visited the Palace, a mouse wanted me to go somewhere.\"")); } else if(msgid == 12 && !princess::saved && in_full_game()) { addMessage(XLAT("\"I wonder what was there...\"")); } else if(msgid == 13 && !peace::on && in_full_game()) { addMessage(XLAT("\"Be careful in the Rose Garden! It is beautiful, but very dangerous!\"")); } else if(msgid == 14) { addMessage(XLAT("\"There is no royal road to geometry.\"")); } else if(msgid == 15) { addMessage(XLAT("\"There is no branch of mathematics, however abstract, ")); addMessage(XLAT("which may not some day be applied to phenomena of the real world.\"")); } else if(msgid == 16) { addMessage(XLAT("\"It is not possession but the act of getting there, ")); addMessage(XLAT("which grants the greatest enjoyment.\"")); } else if(msgid == 17) { addMessage(XLAT("\"We live in a beautiful and orderly world, ")); addMessage(XLAT("and not in a chaos without norms.\"")); } else if(msgid == 25) { addMessage(XLAT("\"Thank you very much for talking, and have a great rest of your day!\"")); } else { msgid++; goto retry; } msgid++; } int mine_adjacency_rule = 0; map> adj_memo; EX bool geometry_has_alt_mine_rule() { if(WDIM == 2) return VALENCE > 3; if(WDIM == 3) return !among(geometry, gHoroHex, gCell5, gBitrunc3, gCell8, gECell8, gCell120, gECell120); return true; } EX vector adj_minefield_cells(cell *c) { vector res; if(mine_adjacency_rule == 0 || !geometry_has_alt_mine_rule()) forCellCM(c2, c) res.push_back(c2); else if(WDIM == 2) { cellwalker cw(c, 0); cw += wstep; cw++; cellwalker cw1 = cw; do { res.push_back(cw.at); cw += wstep; cw++; if(cw.cpeek() == c) cw++; } while(cw != cw1); } else if(adj_memo.count(c)) return adj_memo[c]; else { const vector vertices = currentmap->get_vertices(c); manual_celllister cl; cl.add(c); for(int i=0; irelative_matrix(c1->master, c->master); for(hyperpoint h: vertices) for(hyperpoint h2: vertices) if(hdist(h, T * h2) < 1e-6) shares = true; if(shares) res.push_back(c1); } if(shares || c == c1) forCellEx(c2, c1) cl.add(c2); } println(hlog, "adjacent to ", c, " = ", isize(res)); adj_memo[c] = res; } return res; } EX void auto_teleport_charges() { if(specialland == laMinefield && firstland == laMinefield && bounded) items[itOrbTeleport] = isFire(cwt.at->wall) ? 0 : 1; } EX bool uncoverMines(cell *c, int lev, int dist, bool just_checking) { bool b = false; if(c->wall == waMineMine && just_checking) return true; if(c->wall == waMineUnknown) { if(just_checking) return true; else { c->wall = waMineOpen; b = true; } } bool minesNearby = false; bool nominesNearby = false; bool mineopens = false; auto adj = adj_minefield_cells(c); for(cell *c2: adj) { if(c2->wall == waMineMine) minesNearby = true; if(c2->wall == waMineOpen) mineopens = true; if(c2->wall == waMineUnknown && !c2->item) nominesNearby = true; } if(lev && (nominesNearby || mineopens) && !minesNearby) for(cell *c2: adj) if(c2->wall == waMineUnknown || c2->wall == waMineOpen) { b |= uncoverMines(c2, lev-1, dist+1, just_checking); if(b && just_checking) return true; } if(minesNearby && !nominesNearby && dist == 0) { for(cell *c2: adj) if(c2->wall == waMineMine && c2->land == laMinefield) c2->landparam |= 1; } return b; } namespace orbbull { cell *prev[MAXPLAYER]; eLastmovetype prevtype[MAXPLAYER]; int count; bool is(cell *c1, cell *c2, cell *c3) { int lp = neighborId(c2, c1); int ln = neighborId(c2, c3); return lp >= 0 && ln >= 0 && anglestraight(c2, lp, ln); } void gainBullPowers() { items[itOrbShield]++; orbused[itOrbShield] = true; items[itOrbThorns]++; orbused[itOrbThorns] = true; items[itOrbHorns]++; orbused[itOrbHorns] = true; } void check() { int cp = multi::cpid; if(cp < 0 || cp >= MAXPLAYER) cp = 0; if(!items[itOrbBull]) { prev[cp] = NULL; return; } bool seq = false; if(prev[cp] && prevtype[cp] == lmMove && lastmovetype == lmMove) seq = is(prev[cp], lastmove, cwt.at); if(prev[cp] && prevtype[cp] == lmMove && lastmovetype == lmAttack) seq = is(prev[cp], cwt.at, lastmove); if(prev[cp] && prevtype[cp] == lmAttack && lastmovetype == lmAttack && count) seq = lastmove == prev[cp]; if(prev[cp] && prevtype[cp] == lmAttack && lastmovetype == lmMove && count) seq = cwt.at == prev[cp]; prev[cp] = lastmove; prevtype[cp] = lastmovetype; if(seq) { if(lastmovetype == lmMove) count++; gainBullPowers(); } else count = 0; } } // predictable or not static constexpr bool randterra = false; EX void terracotta(cell *c) { if(c->wall == waTerraWarrior && !c->monst && !racing::on) { bool live = false; if(randterra) { c->landparam++; if((c->landparam == 3 && hrand(3) == 0) || (c->landparam == 4 && hrand(2) == 0) || c->landparam == 5) live = true; } else { c->landparam--; live = !c->landparam; } if(live) c->monst = moTerraWarrior, c->hitpoints = 7, c->wall = waNone; } } EX void terracottaAround(cell *c) { forCellEx(c2, c) terracotta(c2); } EX void terracotta() { for(int i=0; iitem == itOrbSafety) { collectItem(playerpos(i), true); return; } if(playerInPower() && (phase2 || !items[itOrbSpeed]) && (havewhat & HF_FAST)) moveNormals(moWitchSpeed); if(phase2 && markOrb(itOrbEmpathy)) { bfs(); movegolems(AF_FAST); for(int i=0; imonst == moFriendlyGhost && dcal[i]->stuntime) dcal[i]->stuntime--; refreshFriend(dcal[i]); } } DEBB(DF_TURN, ("rop")); if(!dual::state) reduceOrbPowers(); int phase1 = (1 & items[itOrbSpeed]); if(dual::state && items[itOrbSpeed]) phase1 = !phase1; DEBB(DF_TURN, ("lc")); if(!phase1) livecaves(); if(!phase1) ca::simulate(); if(!phase1) heat::processfires(); for(cell *c: crush_now) { playSound(NULL, "closegate"); if(canAttack(c, moCrusher, c, c->monst, AF_GETPLAYER | AF_CRUSH)) { attackMonster(c, AF_MSG | AF_GETPLAYER | AF_CRUSH, moCrusher); } moveEffect(c, c, moDeadBird, -1); destroyBoats(c, NULL, true); explodeBarrel(c); } crush_now = move(crush_next); crush_next.clear(); DEBB(DF_TURN, ("heat")); heat::processheat(); // if(elec::havecharge) elec::drawcharges(); orbbull::check(); if(!phase1) terracotta(); if(items[itOrbFreedom]) for(int i=0; iwall; if(th->land == laAlchemist) th->wall = isAlch(cwt.at) ? cwt.at->wall : cto->wall; else th->wall = waNone; int explode = 0; if(cto->wall == waArrowTrap && w == waExplosiveBarrel ) explode = max(cto->wparam, 1); if(cto->wall == waFireTrap) { if(w == waExplosiveBarrel) explode = max(cto->wparam, 1); if(w == waThumperOn) explode = 2; } destroyTrapsOn(cto); if(cto->wall == waOpenPlate || cto->wall == waClosePlate) { toggleGates(cto, cto->wall); addMessage(XLAT("%The1 destroys %the2!", w, cto->wall)); } if(cellUnstable(cto) && cto->land == laMotion) { addMessage(XLAT("%The1 falls!", w)); doesFallSound(cto); } else if(cellUnstableOrChasm(cto)) { addMessage(XLAT("%The1 fills the hole!", w)); cto->wall = w == waThumperOn ? waTempFloor : waNone; cto->wparam = th->wparam; playSound(cto, "click"); } else if(isWatery(cto)) { addMessage(XLAT("%The1 fills the hole!", w)); cto->wall = w == waThumperOn ? waTempBridge : waNone; cto->wparam = th->wparam; playSound(cto, "splash"+pick12()); } else cto->wall = w; if(explode) cto->wall = waFireTrap, cto->wparam = explode; if(cto->wall == waThumperOn) cto->wparam = th->wparam; } EX bool canPushThumperOn(cell *tgt, cell *thumper, cell *player) { if(tgt->wall == waBoat || tgt->wall == waStrandedBoat) return false; if(isReptile(tgt->wall)) return false; if(isWatery(tgt) && !tgt->monst) return true; if(tgt->wall == waChasm && !tgt->monst) return true; return passable(tgt, thumper, P_MIRROR) && passable(tgt, player, P_MIRROR) && !tgt->item; } EX void activateActiv(cell *c, bool msg) { if(msg) addMessage(XLAT("You activate %the1.", c->wall)); if(c->wall == waThumperOff) { playSound(c, "click"); c->wall = waThumperOn; } if(c->wall == waBonfireOff) { playSound(c, "fire"); c->wall = waFire; } c->wparam = 100; } EX bool scentResistant() { return markOrb(itOrbBeauty) || markOrb(itOrbAether) || markOrb(itOrbShield); } EX void wouldkill(const char *msg) { if(who_kills_me == moWarning) addMessage(XLAT("This move appears dangerous -- are you sure?")); else if(who_kills_me == moFireball) addMessage(XLAT("Cannot move into the current location of another player!")); else if(who_kills_me == moAirball) addMessage(XLAT("Players cannot get that far away!")); else addMessage(XLAT(msg, who_kills_me)); } EX bool havePushConflict(cell *pushto, bool checkonly) { if(pushto && multi::activePlayers() > 1) { for(int i=0; imonst == moFriendlyIvy) killMonster(c2, moPlayer, 0); } EX bool monsterPushable(cell *c2) { return (c2->monst != moFatGuard && !(isMetalBeast(c2->monst) && c2->stuntime < 2) && c2->monst != moTortoise && c2->monst != moTerraWarrior && c2->monst != moVizier); } bool got_survivalist; EX bool should_switchplace(cell *c1, cell *c2) { if(isPrincess(c2->monst) || among(c2->monst, moGolem, moIllusion, moMouse, moFriendlyGhost)) return true; if(peace::on) return c2->monst && !isMultitile(c2->monst); return false; } EX bool warningprotection_hit(eMonster m) { if(m && warningprotection(XLAT("Are you sure you want to hit %the1?", m))) return true; return false; } EX bool switchplace_prevent(cell *c1, cell *c2, bool checkonly) { if(!should_switchplace(c1, c2)) return false; if(c1->monst && c1->monst != moFriendlyIvy) { if(!checkonly) addMessage(XLAT("There is no room for %the1!", c2->monst)); return true; } if(passable(c1, c2, P_ISFRIEND | (c2->monst == moTameBomberbird ? P_FLYING : 0))) return false; if(warningprotection_hit(c2->monst)) return true; return false; } EX void handle_switchplaces(cell *c1, cell *c2, bool& switchplaces) { if(should_switchplace(c1, c2)) { bool pswitch = false; if(c2->monst == moMouse) princess::mouseSqueak(c2); else if(isPrincess(c2->monst)) { princess::line(c2); princess::move(c1, c2); } else pswitch = true; c1->hitpoints = c2->hitpoints; c1->stuntime = c2->stuntime; placeGolem(c1, c2, c2->monst); if(cwt.at->monst != moNone && pswitch) addMessage(XLAT("You switch places with %the1.", c2->monst)); c2->monst = moNone; switchplaces = true; } } EX bool movepcto(int d, int subdir IS(1), bool checkonly IS(false)) { if(dual::state == 1) return dual::movepc(d, subdir, checkonly); if(d >= 0 && !checkonly && subdir != 1 && subdir != -1) printf("subdir = %d\n", subdir); global_pushto = NULL; bool switchplaces = false; if(d == MD_USE_ORB) return targetRangedOrb(multi::whereto[multi::cpid].tgt, roMultiGo); bool errormsgs = multi::players == 1 || multi::cpid == multi::players-1; if(hardcore && !canmove) return false; if(!checkonly && d >= 0) { flipplayer = false; if(multi::players > 1) multi::flipped[multi::cpid] = false; } DEBBI(checkonly ? 0 : DF_TURN, ("movepc")); int origd = d; if(d >= 0) { cwt += d; mirror::act(d, mirror::SPINSINGLE); d = cwt.spin; } if(d != -1 && !checkonly) playermoved = true; if(!checkonly) invismove = false; bool boatmove = false; if(multi::players > 1) lastmountpos[multi::cpid] = cwt.at; else lastmountpos[0] = cwt.at; if(againstRose(cwt.at, NULL) && d<0 && !scentResistant()) { if(checkonly) return false; addMessage("You just cannot stand in place, those roses smell too nicely."); return false; } gravity_state = gsNormal; bool fmsMove = forcedmovetype == fmSkip || forcedmovetype == fmMove; bool fmsAttack = forcedmovetype == fmSkip || forcedmovetype == fmAttack; bool fmsActivate = forcedmovetype == fmSkip || forcedmovetype == fmActivate; if(d >= 0) { cell *c2 = cwt.at->move(d); bool goodTortoise = c2->monst == moTortoise && tortoise::seek() && !tortoise::diff(tortoise::getb(c2)) && !c2->item; if(items[itOrbGravity]) { if(c2->monst && !should_switchplace(cwt.at, c2)) gravity_state = get_static_gravity(cwt.at); else gravity_state = get_move_gravity(cwt.at, c2); if(gravity_state) markOrb(itOrbGravity); } if(againstRose(cwt.at, c2) && !scentResistant()) { if(checkonly) return false; addMessage("Those roses smell too nicely. You have to come towards them."); return false; } bool try_instant = (forcedmovetype == fmInstant) || (forcedmovetype == fmSkip && !passable(c2, cwt.at, P_ISPLAYER | P_MIRROR | P_USEBOAT | P_FRIENDSWAP)); if(items[itOrbDomination] > ORBBASE && isMountable(c2->monst) && !monstersnear2() && fmsMove) { if(checkonly) { nextmovetype = lmMove; return true; } if(!isMountable(cwt.at->monst)) dragon::target = NULL; movecost(cwt.at, c2, 3); flipplayer = true; if(multi::players > 1) multi::flipped[multi::cpid] = true; invismove = (turncount >= noiseuntil) && items[itOrbInvis] > 0; killFriendlyIvy(); goto mountjump; } if(items[itOrbFlash] && try_instant) { if(checkonly) { nextmovetype = lmInstant; return true; } if(orbProtection(itOrbFlash)) return true; activateFlash(); bfs(); if(multi::players > 1) { multi::whereto[multi::cpid].d = MD_UNDECIDED; return false; } checkmove(); return true; } if(items[itOrbLightning] && try_instant) { if(checkonly) { nextmovetype = lmInstant; return true; } if(orbProtection(itOrbLightning)) return true; activateLightning(); keepLightning = true; bfs(); keepLightning = false; if(multi::players > 1) { multi::whereto[multi::cpid].d = MD_UNDECIDED; return false; } checkmove(); return true; } if(isActivable(c2) && fmsActivate) { if(checkonly) { nextmovetype = lmInstant; return true; } activateActiv(c2, true); bfs(); if(multi::players > 1) { multi::whereto[multi::cpid].d = MD_UNDECIDED; return false; } checkmove(); return true; } if(isPushable(c2->wall) && !c2->monst && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) { int pushdir; cell *c3 = determinePush(cwt, c2, subdir, [c2] (cell *c) { return canPushThumperOn(c, c2, cwt.at); }, pushdir); if(c3 == c2) { if(checkonly) return false; addMessage(XLAT("No room to push %the1.", c2->wall)); return false; } if(monstersnear(c2, NULL, moPlayer, NULL, cwt.at)) { if(!checkonly && errormsgs) wouldkill("%The1 would kill you there!"); return false; } global_pushto = c3; if(checkonly) { nextmovetype = lmMove; return true; } addMessage(XLAT("You push %the1.", c2->wall)); lastmovetype = lmPush; lastmove = cwt.at; pushThumper(c2, c3); } if(c2->item == itHolyGrail && roundTableRadius(c2) < newRoundTableRadius()) { if(checkonly) return false; addMessage(XLAT("That was not a challenge. Find a larger castle!")); return false; } if(c2->item == itOrbYendor && !checkonly && !peace::on && !itemHiddenFromSight(c2) && yendor::check(c2)) { return false; } if(isWatery(c2) && !nonAdjacentPlayer(cwt.at,c2) && !c2->monst && cwt.at->wall == waBoat && fmsMove) { if(havePushConflict(cwt.at, checkonly)) return false; if(againstWind(c2, cwt.at)) { if(!checkonly) addMessage(XLAT(airdist(c2) < 3 ? "The Air Elemental blows you away!" : "You cannot go against the wind!")); return false; } if(againstCurrent(c2, cwt.at) && !markOrb(itOrbWater)) { if(markOrb(itOrbFish) || markOrb(itOrbAether) || gravity_state) goto escape; if(!checkonly) addMessage(XLAT("You cannot go against the current!")); return false; } if(cwt.at->item == itOrbYendor) { addMessage(XLAT("The Orb of Yendor is locked in with powerful magic.")); return false; } if(monstersnear(c2, NULL, moPlayer, NULL, cwt.at)) { if(!checkonly && errormsgs) wouldkill("%The1 would kill you there!"); return false; } if(checkonly) { nextmovetype = lmMove; return true; } moveBoat(c2, cwt.at, d); boatmove = true; goto boatjump; } if(!c2->monst && cwt.at->wall == waBoat && boatGoesThrough(c2) && markOrb(itOrbWater) && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) { if(havePushConflict(cwt.at, checkonly)) return false; if(monstersnear(c2,NULL,moPlayer,NULL,cwt.at)) { if(!checkonly && errormsgs) wouldkill("%The1 would kill you there!"); return false; } if(checkonly) { nextmovetype = lmMove; return true; } if(c2->item && !cwt.at->item) moveItem(c2, cwt.at, false), boatmove = true; placeWater(c2, cwt.at); moveBoat(c2, cwt.at, d); c2->mondir = revhint(cwt.at, d); if(c2->item) boatmove = !boatmove; goto boatjump; } escape: if(c2->wall == waBigStatue && !c2->monst && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) { if(!canPushStatueOn(cwt.at)) { if(checkonly) return false; if(isFire(cwt.at)) addMessage(XLAT("You have to escape first!")); else addMessage(XLAT("There is not enough space!")); return false; } if(havePushConflict(cwt.at, checkonly)) return false; eWall save_c2 = c2->wall; eWall save_cw = cwt.at->wall; c2->wall = cwt.at->wall; if(doesnotFall(cwt.at)) cwt.at->wall = waBigStatue; if(monstersnear(c2,NULL,moPlayer,NULL,cwt.at)) { if(!checkonly && errormsgs) wouldkill("%The1 would kill you there!"); c2->wall = save_c2; cwt.at->wall = save_cw; return false; } if(checkonly) { c2->wall = save_c2; cwt.at->wall = save_cw; nextmovetype = lmMove; return true; } addMessage(XLAT("You push %the1 behind you!", waBigStatue)); animateMovement(c2, cwt.at, LAYER_BOAT, cwt.at->c.spin(d)); goto statuejump; } bool attackable; attackable = c2->wall == waBigTree || c2->wall == waSmallTree || c2->wall == waMirrorWall; if(attackable && markOrb(itOrbAether) && c2->wall != waMirrorWall) attackable = false; bool nm; nm = attackable; if(forcedmovetype == fmAttack) attackable = true; attackable = attackable && (!c2->monst || isFriendly(c2)); attackable = attackable && !nonAdjacentPlayer(cwt.at,c2); if(attackable && fmsAttack) { if(checkNeedMove(checkonly, true)) return false; if(monstersnear(cwt.at,c2,moPlayer,NULL,cwt.at)) { if(!checkonly && errormsgs) wouldkill("%The1 would get you!"); return false; } if(checkonly) { nextmovetype = nm ? lmAttack : lmSkip; return true; } if(c2->wall == waSmallTree) { drawParticles(c2, winf[c2->wall].color, 4); addMessage(XLAT("You chop down the tree.")); playSound(c2, "hit-axe" + pick123()); c2->wall = waNone; sideAttack(cwt.at, d, moPlayer, 0); animateAttack(cwt.at, c2, LAYER_SMALL, d); } else if(c2->wall == waBigTree) { drawParticles(c2, winf[c2->wall].color, 8); addMessage(XLAT("You start chopping down the tree.")); playSound(c2, "hit-axe" + pick123()); c2->wall = waSmallTree; sideAttack(cwt.at, d, moPlayer, 0); animateAttack(cwt.at, c2, LAYER_SMALL, d); } else { if(!peace::on) { if(c2->wall == waMirrorWall) addMessage(XLAT("You swing your sword at the mirror.")); else if(c2->wall) addMessage(XLAT("You swing your sword at %the1.", c2->wall)); else addMessage(XLAT("You swing your sword.")); swing: sideAttack(cwt.at, d, moPlayer, 0); animateAttack(cwt.at, c2, LAYER_SMALL, d); } } if(survivalist && isHaunted(c2->land)) survivalist = false; mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK); lastmovetype = lmTree; lastmove = c2; swordAttackStatic(); } else if(c2->monst == moKnight) { if(checkonly) return false; knightFlavorMessage(c2); return false; } else if(c2->monst && (!isFriendly(c2) || c2->monst == moTameBomberbird || isMountable(c2->monst)) && !(peace::on && !isMultitile(c2->monst) && !goodTortoise)) { if(!fmsAttack) return false; flagtype attackflags = AF_NORMAL; if(items[itOrbSpeed]&1) attackflags |= AF_FAST; if(items[itOrbSlaying]) attackflags |= AF_CRUSH; bool ca =canAttack(cwt.at, moPlayer, c2, c2->monst, attackflags); if(!ca) { if(forcedmovetype == fmAttack) { if(monstersnear(cwt.at,c2,moPlayer,NULL,cwt.at)) { if(!checkonly && errormsgs) wouldkill("%The1 would get you!"); return false; } if(checkonly) { nextmovetype = lmSkip; return true; } addMessage(XLAT("You swing your sword at %the1.", c2->monst)); goto swing; } if(checkonly) return false; if(c2->monst == moWorm || c2->monst == moWormtail || c2->monst == moWormwait) addMessage(XLAT("You cannot attack Sandworms directly!")); else if(c2->monst == moHexSnake || c2->monst == moHexSnakeTail) addMessage(XLAT("You cannot attack Rock Snakes directly!")); else if(nonAdjacent(c2, cwt.at)) addMessage(XLAT("You cannot attack diagonally!")); else if(thruVine(c2, cwt.at)) addMessage(XLAT("You cannot attack through the Vine!")); else if(c2->monst == moTentacle || c2->monst == moTentacletail || c2->monst == moTentaclewait || c2->monst == moTentacleEscaping) addMessage(XLAT("You cannot attack Tentacles directly!")); else if(c2->monst == moHedge && !markOrb(itOrbThorns)) { addMessage(XLAT("You cannot attack %the1 directly!", c2->monst)); addMessage(XLAT("Stab them by walking around them.")); } else if(c2->monst == moRoseBeauty || isBull(c2->monst) || c2->monst == moButterfly) addMessage(XLAT("You cannot attack %the1!", c2->monst)); else if(c2->monst == moFlailer && !c2->stuntime) { addMessage(XLAT("You cannot attack %the1 directly!", c2->monst)); addMessage(XLAT("Make him hit himself by walking away from him.")); } else if(c2->monst == moVizier && c2->hitpoints > 1 && !(attackflags & AF_FAST)) { addMessage(XLAT("You cannot attack %the1 directly!", c2->monst)); addMessage(XLAT("Hit him by walking away from him.")); } else if(c2->monst == moShadow) addMessage(XLAT("You cannot defeat the Shadow!")); else if(c2->monst == moGreater || c2->monst == moGreaterM) addMessage(XLAT("You cannot defeat the Greater Demon yet!")); else if(c2->monst == moDraugr) addMessage(XLAT("Your mundane weapon cannot hurt %the1!", c2->monst)); else if(isRaider(c2->monst)) addMessage(XLAT("You cannot attack Raiders directly!")); else if(isSwitch(c2->monst)) addMessage(XLAT("You cannot attack Jellies in their wall form!")); else addMessage(XLAT("For some reason... cannot attack!")); return false; } // pushto=c2 means that the monster is not killed and thus // still counts for lightning in monstersnear cell *pushto = NULL; int pushdir = 0; if(isStunnable(c2->monst) && c2->hitpoints > 1) { if(monsterPushable(c2)) pushto = determinePush(cwt, c2, subdir, [c2] (cell *c) { return passable(c, c2, P_BLOW); }, pushdir); else pushto = c2; } if(c2->monst == moTroll || c2->monst == moFjordTroll || c2->monst == moForestTroll || c2->monst == moStormTroll || c2->monst == moVineSpirit) pushto = c2; global_pushto = pushto; if(havePushConflict(pushto, checkonly)) return false; if(!(isWatery(cwt.at) && c2->monst == moWaterElemental) && checkNeedMove(checkonly, true)) return false; if(monstersnear(cwt.at, c2, moPlayer, pushto, cwt.at)) { if(errormsgs && !checkonly) wouldkill("You would be killed by %the1!"); return false; } if(c2->monst == moTameBomberbird && warningprotection_hit(moTameBomberbird)) return false; if(checkonly) { nextmovetype = lmAttack; return true; } /* if(c2->monst == moTortoise) { printf("seek=%d get=%d <%x/%x> item=%d\n", tortoise::seeking, !tortoise::diff(tortoise::getb(c2)), tortoise::getb(c2) & tortoise::mask, tortoise::seekbits & tortoise::mask, !c2->item); } */ mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK); int tk = tkills(); if(goodTortoise) { items[itBabyTortoise] += 4; updateHi(itBabyTortoise, items[itBabyTortoise]); c2->item = itBabyTortoise; tortoise::babymap[c2] = tortoise::seekbits; playSound(c2, playergender() ? "heal-princess" : "heal-prince"); addMessage(XLAT(playergender() == GEN_F ? "You are now a tortoise heroine!" : "You are now a tortoise hero!")); c2->stuntime = 2; achievement_collection(itBabyTortoise, 0, 0); } else { eMonster m = c2->monst; if(m) { if((attackflags & AF_CRUSH) && !canAttack(cwt.at, moPlayer, c2, c2->monst, attackflags ^ AF_CRUSH ^ AF_MUSTKILL)) markOrb(itOrbSlaying); if(c2->monst == moTerraWarrior && hrand(100) > 2 * items[itTerra]) { if(hrand(2 + jiangshi_on_screen) < 2) wandering_jiangshi++; } attackMonster(c2, attackflags | AF_MSG, moPlayer); // salamanders are stunned for longer time when pushed into a wall if(c2->monst == moSalamander && (pushto == c2 || !pushto)) c2->stuntime = 10; if(!c2->monst) produceGhost(c2, m, moPlayer); if(pushto && pushto != c2) pushMonster(pushto, c2, pushdir); animateAttack(cwt.at, c2, LAYER_SMALL, d); } } sideAttack(cwt.at, d, moPlayer, tkills() - tk); lastmovetype = lmAttack; lastmove = c2; swordAttackStatic(); } else if(!passable(c2, cwt.at, P_USEBOAT | P_ISPLAYER | P_MIRROR | P_MONSTER)) { if(checkonly) return false; if(nonAdjacent(cwt.at,c2)) addMessage(XLAT( geosupport_football() < 2 ? "You cannot move between the cells without dots here!" : "You cannot move between the triangular cells here!" )); else if(againstWind(c2, cwt.at)) addMessage(XLAT(airdist(c2) < 3 ? "The Air Elemental blows you away!" : "You cannot go against the wind!")); else if(isAlch(c2)) addMessage(XLAT("Wrong color!")); else if(c2->wall == waRoundTable) addMessage(XLAT("It would be impolite to land on the table!")); else if(snakelevel(cwt.at) >= 3 && snakelevel(c2) == 0) addMessage(XLAT("You would get hurt!", c2->wall)); else if(cellEdgeUnstable(cwt.at) && cellEdgeUnstable(c2)) { addMessage(XLAT("Gravity does not allow this!")); } else if(c2->wall == waChasm && c2->land == laDual) addMessage(XLAT("You cannot move there!")); else if(!c2->wall) addMessage(XLAT("You cannot move there!")); else { addMessage(XLAT("You cannot move through %the1!", c2->wall)); } return false; } else if(fmsMove) { if(mineMarked(c2) && !minesafe() && !checkonly && warningprotection(XLAT("Are you sure you want to step there?"))) return false; if(monstersnear(c2, NULL, moPlayer, NULL, cwt.at)) { if(checkonly) return false; if(items[itOrbFlash]) { if(checkonly) { nextmovetype = lmInstant; return true; } if(orbProtection(itOrbFlash)) return true; activateFlash(); checkmove(); return true; } if(items[itOrbLightning]) { if(checkonly) { nextmovetype = lmInstant; return true; } if(orbProtection(itOrbLightning)) return true; activateLightning(); checkmove(); return true; } if(who_kills_me == moOutlaw && items[itRevolver]) { for(int i=0; itype; i++) { cell *c3 = c2->move(i); if(c3) for(int i=0; itype; i++) { cell *c4 = c3->move(i); if(c4 && c4->monst == moOutlaw) { eItem i = targetRangedOrb(c4, roCheck); if(i == itRevolver) { targetRangedOrb(c4, roKeyboard); return false; } } } } } if(!checkonly && errormsgs) wouldkill("%The1 would kill you there!"); return false; } if(switchplace_prevent(cwt.at, c2, checkonly)) return false; if(!checkonly && warningprotection_hit(do_we_stab_a_friend(cwt.at, c2, moPlayer))) return false; if(checkonly) { nextmovetype = lmMove; return true; } boatjump: statuejump: flipplayer = true; if(multi::players > 1) multi::flipped[multi::cpid] = true; if(c2->item && isAlch(c2)) { if(cwt.at->wall == waBoat) c2->wall = waNone; else c2->wall = cwt.at->wall; } if(c2->wall == waRoundTable) { addMessage(XLAT("You jump over the table!")); } if(cwt.at->wall == waRoundTable) roundTableMessage(c2); invismove = (turncount >= noiseuntil) && items[itOrbInvis] > 0; if(items[itOrbFire]) { invismove = false; if(makeflame(cwt.at, 10, false)) markOrb(itOrbFire); } if(1) { bool haveIvy = false; forCellEx(c3, cwt.at) if(c3->monst == moFriendlyIvy) haveIvy = true; bool killIvy = haveIvy; if(items[itOrbNature]) { if(c2->monst != moFriendlyIvy && strictlyAgainstGravity(c2, cwt.at, false, MF_IVY)) { invismove = false; } else if(cwt.at->monst) invismove = false; else if(haveIvy || !cellEdgeUnstable(cwt.at, MF_IVY)) { cwt.at->monst = moFriendlyIvy; cwt.at->mondir = neighborId(cwt.at, c2); invismove = false; markOrb(itOrbNature); killIvy = false; } } if(killIvy) killFriendlyIvy(); } if(items[itOrbDigging]) { invismove = false; if(earthMove(cwt.at, d)) markOrb(itOrbDigging); } movecost(cwt.at, c2, 1); if(!boatmove && collectItem(c2)) return true; if(doPickupItemsWithMagnetism(c2)) return true; if(isIcyLand(cwt.at) && cwt.at->wall == waNone && markOrb(itOrbWinter)) { invismove = false; cwt.at->wall = waIcewall; } if(items[itOrbWinter]) forCellEx(c3, c2) if(c3->wall == waIcewall && c3->item) markOrb(itOrbWinter), collectItem(c3); movecost(cwt.at, c2, 2); handle_switchplaces(cwt.at, c2, switchplaces); mountjump: lastmovetype = lmMove; lastmove = cwt.at; stabbingAttack(cwt.at, c2, moPlayer); cell *c1 = cwt.at; int d = cwt.spin; cwt += wstep; if(switchplaces) animateReplacement(c1, cwt.at, LAYER_SMALL, d, cwt.spin); else animateMovement(c1, cwt.at, LAYER_SMALL, d); mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK | mirror::GO); playerMoveEffects(c1, c2); if(c2->monst == moFriendlyIvy) c2->monst = moNone; countLocalTreasure(); landvisited[cwt.at->land] = true; afterplayermoved(); } else return false; } else { if(items[itOrbGravity]) { gravity_state = get_static_gravity(cwt.at); if(gravity_state) markOrb(itOrbGravity); } lastmovetype = lmSkip; lastmove = NULL; if(checkNeedMove(checkonly, false)) return false; if(monstersnear(cwt.at, NULL, moPlayer, NULL, cwt.at)) { if(errormsgs && !checkonly) wouldkill("%The1 would get you!"); return false; } if(checkonly) { nextmovetype = lmSkip; return true; } swordAttackStatic(); if(d == -2) dropGreenStone(cwt.at); if(cellUnstable(cwt.at) && !markOrb(itOrbAether)) doesFallSound(cwt.at); if(last_gravity_state && !gravity_state) playerMoveEffects(cwt.at, cwt.at); } invisfish = false; if(items[itOrbFish]) { invisfish = true; for(int i=0; iland != laWhirlpool && !whirlpool::escaped) { whirlpool::escaped = true; achievement_gain("WHIRL1"); } if(items[itLotus] >= 25 && !isHaunted(cwt.at->land) && survivalist && !got_survivalist) { got_survivalist = true; achievement_gain("SURVIVAL"); } if(seenSevenMines && cwt.at->land != laMinefield) { seenSevenMines = false; achievement_gain("SEVENMINE"); } DEBB(DF_TURN, ("done")); return true; } #if HDR inline bool movepcto(const movedir& md) { return movepcto(md.d, md.subdir); } #endif /* bool isPsiTarget(cell *dst) { return dst->cpdist > 1 && dst->monst && !(isWorm(dst) || dst->monst == moShadow); } */ EX void moveItem1(cell *from, cell *to, bool activateYendor) { if(from->item == itOrbYendor) { bool xnew = true; for(int i=0; iitem == itKey) { for(int i=0; iitem == itBabyTortoise) { tortoise::babymap[to] = tortoise::babymap[from]; tortoise::babymap.erase(from); } eItem i = to->item; to->item = from->item; from->item = i; } EX void moveItem (cell *from, cell *to, bool activateYendor) { static cell dummy; dummy.item = itNone; moveItem1(from, &dummy, activateYendor); moveItem1(to, from, activateYendor); moveItem1(&dummy, to, activateYendor); } EX void fixWormBug(cell *c) { if(history::includeHistory) return; printf("worm bug!\n"); if(c->monst == moWormtail) c->monst = moWormwait; if(c->monst == moTentacletail || c->monst == moTentacleGhost) c->monst = moTentacle; if(c->monst == moHexSnakeTail) c->monst = moHexSnake; } EX cell *wormhead(cell *c) { // cell *cor = c; findhead: if(c->monst == moTentacle || c->monst == moWorm || c->monst == moHexSnake || c->monst == moWormwait || c->monst == moTentacleEscaping || c->monst == moTentaclewait || c->monst == moDragonHead) return c; for(int i=0; itype; i++) if(c->move(i) && isWorm(c->move(i)->monst) && c->move(i)->mondir == c->c.spin(i)) { c = c->move(i); goto findhead; } fixWormBug(c); return c; } EX int wormpos(cell *c) { // cell *cor = c; int cnt = 0; findhead: if(c->monst == moTentacle || c->monst == moWorm || c->monst == moHexSnake || c->monst == moWormwait || c->monst == moTentacleEscaping || c->monst == moTentaclewait || c->monst == moDragonHead) return cnt; for(int i=0; itype; i++) if(c->move(i) && isWorm(c->move(i)->monst) && c->move(i)->mondir == c->c.spin(i)) { c = c->move(i); cnt++; goto findhead; } fixWormBug(c); return cnt; } // currently works for worms only EX bool sameMonster(cell *c1, cell *c2) { if(!c1 || !c2) return false; if(c1 == c2) return true; if(isWorm(c1->monst) && isWorm(c2->monst)) return wormhead(c1) == wormhead(c2); if(isKraken(c1->monst) && isKraken(c2->monst)) return kraken::head(c1) == kraken::head(c2); return false; } EX eMonster haveMount() { for(int i=0; imonst; if(m) return m; } return moNone; } EX bool mightBeMine(cell *c) { return c->wall == waMineUnknown || c->wall == waMineMine; } hookset *hooks_mark; EX void performMarkCommand(cell *c) { if(!c) return; if(callhandlers(false, hooks_mark, c)) return; if(c->land == laCA && c->wall == waNone) c->wall = waFloorA; else if(c->land == laCA && c->wall == waFloorA) c->wall = waNone; if(c->land != laMinefield) return; if(c->item) return; if(!mightBeMine(c)) return; bool adj = false; forCellEx(c2, c) if(c2->wall == waMineOpen) adj = true; if(adj) c->landparam ^= 1; } EX bool mineMarked(cell *c) { if(!mightBeMine(c)) return false; if(c->item) return false; if(c->land != laMinefield) return true; return c->landparam & 1; } EX bool mineMarkedSafe(cell *c) { if(!mightBeMine(c)) return false; if(c->item) return true; if(c->land != laMinefield) return false; return c->landparam & 2; } EX bool minesafe() { return items[itOrbAether]; } EX bool warningprotection(const string& s) { if(hardcore) return false; if(multi::activePlayers() > 1) return false; if(items[itWarning]) return false; pushScreen([s] () { gamescreen(1); dialog::addBreak(250); dialog::init(XLAT("WARNING"), 0xFF0000, 150, 100); dialog::addBreak(500); dialog::addInfo(s); dialog::addBreak(500); dialog::addItem(XLAT("YES"), 'y'); dialog::lastItem().scale = 200; auto yes = [] () { items[itWarning] = 1; popScreen(); }; dialog::add_action(yes); dialog::add_key_action(SDLK_RETURN, yes); dialog::addItem(XLAT("NO"), 'n'); dialog::lastItem().scale = 200; dialog::add_action([] () { items[itWarning] = 0; popScreen(); }); dialog::display(); }); return true; } }