diff --git a/Makefile.simple b/Makefile.simple index 95046ed7..32eeeb4e 100644 --- a/Makefile.simple +++ b/Makefile.simple @@ -159,7 +159,7 @@ makeh$(EXE_EXTENSION): makeh.cpp $(CXX) makeh.cpp -o $@ autohdr.h: makeh$(EXE_EXTENSION) *.cpp - ./makeh locations.cpp hyperpoint.cpp geometry.cpp goldberg.cpp init.cpp floorshapes.cpp cell.cpp multi.cpp shmup.cpp pattern2.cpp mapeditor.cpp graph.cpp textures.cpp hprint.cpp language.cpp *.cpp > autohdr.h + ./makeh locations.cpp hyperpoint.cpp geometry.cpp goldberg.cpp init.cpp floorshapes.cpp cell.cpp multi.cpp shmup.cpp pattern2.cpp mapeditor.cpp graph.cpp textures.cpp hprint.cpp language.cpp complex.cpp *.cpp > autohdr.h language-data.cpp: langen$(EXE_EXTENSION) ./langen > language-data.cpp diff --git a/attack.cpp b/attack.cpp new file mode 100644 index 00000000..f1760002 --- /dev/null +++ b/attack.cpp @@ -0,0 +1,1198 @@ +// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details + +/** \file gameutil.cpp + * \brief routines related to attacking and killing + */ + +#include "hyper.h" + +namespace hr { + +/** how many instances of each monster type has been killed */ +EX array kills; + +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; + } + + +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; + } + +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 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 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(movei(c, FALL), moDeadBird); + 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)) && !cryst) { + 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 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(const movei& mi) { + moveMonster(mi); + auto& cf = mi.s; + auto& ct = mi.t; + 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 void killFriendlyIvy() { + forCellEx(c2, cwt.at) if(c2->monst == 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); + } + +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 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(match(c2, c1)); + } + 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 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> 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 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); + } + } + } + } +#endif + +int lastdouble = -3; + +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; + } + } + } + +} diff --git a/basegraph.cpp b/basegraph.cpp index 7fb2d588..34a1f47f 100644 --- a/basegraph.cpp +++ b/basegraph.cpp @@ -79,7 +79,7 @@ int utfsize(char c) { } EX int get_sightrange() { return getDistLimit() + sightrange_bonus; } -EX int get_sightrange_ambush() { return max(get_sightrange(), ambush_distance); } +EX int get_sightrange_ambush() { return max(get_sightrange(), ambush::distance); } bool display_data::in_anaglyph() { return vid.stereo_mode == sAnaglyph; } bool display_data::stereo_active() { return vid.stereo_mode != sOFF; } diff --git a/bigstuff.cpp b/bigstuff.cpp index d01a540a..e3d8cf14 100644 --- a/bigstuff.cpp +++ b/bigstuff.cpp @@ -23,7 +23,7 @@ EX int newRoundTableRadius() { EX int getAnthraxData(cell *c, bool b) { int d = celldistAlt(c); - int rad = 28 + 3 * anthraxBonus; + int rad = 28 + 3 * camelot::anthraxBonus; while(d < -rad) { d += rad + 12; rad += 3; diff --git a/cell.cpp b/cell.cpp index 6c0aa6e1..3e87b056 100644 --- a/cell.cpp +++ b/cell.cpp @@ -1120,4 +1120,120 @@ EX void clearCellMemory() { auto cellhooks = addHook(clearmemory, 500, clearCellMemory); +EX bool isNeighbor(cell *c1, cell *c2) { + for(int i=0; itype; 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; + } + +EX int mine_adjacency_rule = 0; + +EX 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, C0); + 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 vector reverse_directions(cell *c, int dir) { + if(PURE) return reverse_directions(c->master, dir); + int d = c->degree(); + 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) }; + } + +EX vector reverse_directions(heptagon *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) }; + } + } + } diff --git a/celldrawer.cpp b/celldrawer.cpp index fdb23071..2d0551cd 100644 --- a/celldrawer.cpp +++ b/celldrawer.cpp @@ -143,7 +143,7 @@ void celldrawer::setcolors() { fcol = 0x404040; for(int a=0; a<21; a++) if((b >> a) & 1) - fcol += variant_features[a].color_change; + fcol += variant::features[a].color_change; if(c->wall == waAncientGrave) wcol = 0x080808; else if(c->wall == waFreshGrave) @@ -506,9 +506,9 @@ void celldrawer::setcolors() { break; case waMineUnknown: case waMineMine: - if(mineMarkedSafe(c)) + if(mine::marked_safe(c)) fcol = wcol = gradient(wcol, 0x40FF40, 0, 0.2, 1); - else if(mineMarked(c)) + else if(mine::marked_mine(c)) fcol = wcol = gradient(wcol, 0xFF4040, -1, sintick(100), 1); // fallthrough @@ -1255,7 +1255,7 @@ void celldrawer::draw_features() { } case waTerraWarrior: - drawTerraWarrior(V, randterra ? (c->landparam & 7) : (5 - (c->landparam & 7)), 7, 0); + drawTerraWarrior(V, terracotta::randterra ? (c->landparam & 7) : (5 - (c->landparam & 7)), 7, 0); break; case waBoat: case waStrandedBoat: diff --git a/checkmove.cpp b/checkmove.cpp new file mode 100644 index 00000000..a541f019 --- /dev/null +++ b/checkmove.cpp @@ -0,0 +1,451 @@ +// Hyperbolic Rogue - checkmate rule +// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details + +/** \file checkmove.cpp + * \brief Check the validity of move (checkmate rule) + */ + +#include "hyper.h" + +namespace hr { + +#if HDR +#define PUREHARDCORE_LEVEL 10 +#endif + +/** are we in the hardcore mode */ +EX bool hardcore = false; +/** when did we switch to the hardcore mode */ +EX int hardcoreAt; + +EX bool pureHardcore() { return hardcore && hardcoreAt < PUREHARDCORE_LEVEL; } + +/** can we still move? */ +EX bool canmove = true; + +// how many monsters are near +EX eMonster who_kills_me; + +EX int lastkills; + +EX 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); + } + +#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; fitem == itOrbFish && c->wall == waBoat) || + (c->item == itOrbAether && c->wall == waBoat); + } + +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(sm.who == moPlayer && sm.moveto->item == itOrbSpeed && !items[itOrbSpeed]) fast = true; + } + + 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; + } + +EX bool monstersnear2(); + +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 namespace stalemate { + EX vector moves; + EX bool nextturn; + + EX bool isMoveto(cell *c) { + for(int i=0; iwall == waBoat) || (cf->wall == waBoat && c->wall == waSea); + } + +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; + } + +EX namespace multi { + EX bool checkonly = false; + EX bool aftermove; + EX } + +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 void checkmove() { + + if(dual::state == 2) return; + if(shmup::on) 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; i variant_features; +extern array features; #endif #define VF [] (cell *c) -array variant_features {{ - variant_feature{(color_t)(-0x202020), 5, moNecromancer, VF { +array features {{ + feature{(color_t)(-0x202020), 5, moNecromancer, VF { if(c->wall == waNone && hrand(1500) < 20) c->wall = waFreshGrave; if(hrand(20000) < 10 + items[itVarTreasure]) c->monst = moNecromancer; @@ -322,6 +323,509 @@ array variant_features {{ {0x100708, 1, moRatling, VF { if(c->wall == waNone && !c->monst && hrand(50000) < 25 + items[itVarTreasure]) c->monst = moRatling; }} }}; #undef VF +EX } + +EX namespace camelot { +/** number of Grails collected, to show you as a knight */ +EX int knighted = 0; + +/** this value is used when using Orb of Safety in the Camelot in Pure Tactics Mode */ +EX int anthraxBonus = 0; + +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 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 && cryst) { + 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(cryst) + 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(cryst) + 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++; + } +EX } + +EX namespace mine { + +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; + } + +EX bool mightBeMine(cell *c) { + return c->wall == waMineUnknown || c->wall == waMineMine; + } + +EX 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 marked_mine(cell *c) { + if(!mightBeMine(c)) return false; + if(c->item) return false; + if(c->land != laMinefield) return true; + return c->landparam & 1; + } + +EX bool marked_safe(cell *c) { + if(!mightBeMine(c)) return false; + if(c->item) return true; + if(c->land != laMinefield) return false; + return c->landparam & 2; + } + +EX bool safe() { + return items[itOrbAether]; + } + +EX void uncover_full(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 auto_teleport_charges() { + if(specialland == laMinefield && firstland == laMinefield && bounded) + items[itOrbTeleport] = isFire(cwt.at->wall) ? 0 : 1; + } + +EX } + +EX namespace terracotta { +#if HDR +// predictable or not +static constexpr bool randterra = false; +#endif + +EX void check(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 check_around(cell *c) { + forCellEx(c2, c) + check(c2); + } + +EX void check() { + for(int i=0; icpdist < c->cpdist) + mark(c2, cl); + } + +EX int distance; +EX bool ambushed; + +EX void check_state() { + if(havewhat & HF_HUNTER) { + manual_celllister cl; + for(cell *c: dcal) { + if(c->monst == moHunterDog) { + if(c->cpdist > distance) + distance = c->cpdist; + mark(c, cl); + } + if(c->monst == moHunterGuard && c->cpdist <= 4) + mark(c, cl); + } + if(items[itHunting] > 5 && items[itHunting] <= 22) { + int q = 0; + for(int i=0; icpdist > 3) break; + if(c2->monst && !isFriendly(c2) && !slowMover(c2) && !isMultitile(c2)) restricted = true; + } + + int qty = items[itHunting]; + if(fixed_size) + return fixed_size; + 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 = size(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 = size(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 } } #endif diff --git a/control.cpp b/control.cpp index ac5445ca..f88c810c 100644 --- a/control.cpp +++ b/control.cpp @@ -426,7 +426,7 @@ EX void handleKeyNormal(int sym, int uni) { if(DEFAULTNOR(sym)) { gmodekeys(sym, uni); if(uni == 'm' && canmove && (centerover == cwt.at ? mouseover : centerover)) - performMarkCommand(mouseover); + mine::performMarkCommand(mouseover); } if(DEFAULTCONTROL) { diff --git a/debug.cpp b/debug.cpp index 8cabbaa5..29b5628e 100644 --- a/debug.cpp +++ b/debug.cpp @@ -664,7 +664,7 @@ int read_cheat_args() { // make all ambushes use the given number of dogs // example: hyper -W Hunt -IP Shield 1 -ambush 60 PHASE(3) cheat(); - shift(); ambushval = argi(); + shift(); ambush::fixed_size = argi(); } else if(argis("-testdistances")) { PHASE(3); shift(); test_distances(argi()); diff --git a/environment.cpp b/environment.cpp new file mode 100644 index 00000000..04a636df --- /dev/null +++ b/environment.cpp @@ -0,0 +1,789 @@ +// Hyperbolic Rogue - environment +// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details + +/** \file environment.cpp + * \brief game environment: routines related to the game that affect all the map. Monsters to move are detected here, but their moves are implemented in monstermove.cpp + */ + +#include "hyper.h" + +namespace hr { + +#if HDR +#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) +#endif + +EX flagtype havewhat, hadwhat; + +/** monsters of specific types to move */ +EX vector worms, ivies, ghosts, golems, hexsnakes; + +/** temporary changes during bfs */ +vector> tempmonsters; + +/** 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. + **/ +EX vector reachedfrom; + +/** The position of the first cell in dcal in distance 7. New wandering monsters can be generated in dcal[first7..]. */ +EX int first7; + +/** the list of all nearby cells, according to cpdist */ +EX vector dcal; +/** the list of all nearby cells, according to current pathdist */ +EX vector pathq; + +/** the number of big statues -- they increase monster generation */ +EX int statuecount; + +/** list of monsters to move (pathq restriced to monsters) */ +EX vector pathqm; + +/** which hex snakes are there */ +EX set snaketypes; + +EX int gamerange_bonus = 0; +EX int gamerange() { return getDistLimit() + gamerange_bonus; } + +// 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 ? centerover : 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++) { + cell *c = pathq[qb]; + int fd = reachedfrom[qb] + c->type/2; + 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 + +/** calculate cpdist, 'have' flags, and do general fixings */ +EX void bfs() { + + calcTidalPhase(); + + yendor::onpath(); + + int dcs = isize(dcal); + for(int i=0; icpdist = INFD; + worms.clear(); ivies.clear(); ghosts.clear(); golems.clear(); + tempmonsters.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 = t.second; + + buildAirmap(); + } + +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); + } + } + +// find worms and ivies +EX void settemp(cell *c) { + tempmonsters.emplace_back(c, (eMonster) 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; + } + } + +EX void monstersTurn() { + checkSwitch(); + mirror::breakAll(); + DEBB(DF_TURN, ("bfs")); + bfs(); + DEBB(DF_TURN, ("charge")); + if(elec::havecharge) elec::act(); + DEBB(DF_TURN, ("mmo")); + int phase2 = (1 & items[itOrbSpeed]); + if(!phase2) movemonsters(); + 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(movei(c, FALL), moDeadBird); + 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::check(); + + if(items[itOrbFreedom]) + for(int i=0; i snaketypes; - -#if HDR -#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) -#endif - -EX flagtype havewhat, hadwhat; - -EX bool seenSevenMines = false; - -EX bool pureHardcore() { return hardcore && hardcoreAt < PUREHARDCORE_LEVEL; } - -EX bool canmove = true; - -EX int sagephase = 0; - -/** number of Grails collected, to show you as a knight */ -EX int knighted = 0; - -EX bool usedSafety = false; -EX eLand safetyland; -EX int safetyseed; - -int showid = 0; - -/** last move was invisible */ -EX bool invismove = false; -/** last move was invisible due to Orb of Fish (thus Fish still see you)*/ -EX bool invisfish = false; - -EX int noiseuntil; // noise until the given turn - -EX void createNoise(int t) { - noiseuntil = max(noiseuntil, turncount+t); - invismove = false; - if(shmup::on) shmup::visibleFor(100 * t); - } - -EX int currentLocalTreasure; - -EX bool landvisited[landtypes]; - -bool eq(short a, short b) { return a==b; } - -/** for treasures, the number collected; for orbs, the number of charges */ -EX array items; -/** how many instances of each monster type has been killed */ -EX array kills; - -EX int explore[10], exploreland[10][landtypes], landcount[landtypes]; -EX map > hiitems; -EX bool orbused[ittypes], lastorbused[ittypes]; -/** should we center the screen on the PC? */ -EX bool playermoved = true; -/** if false, make the PC look in direction cwt.spin (after attack); otherwise, make them look the other direction (after move) */ -EX bool flipplayer = true; -/** did the player cheat? how many times? */ -EX int cheater = 0; - -/** this value is used when using Orb of Safety in the Camelot in Pure Tactics Mode */ -EX int anthraxBonus = 0; - -/** the list of all nearby cells, according to cpdist */ -EX vector dcal; -/** the list of all nearby cells, according to current pathdist */ -EX vector pathq; - -/** offscreen cells to take care off */ -EX vector offscreen; - -/** list of monsters to move (pathq restriced to monsters) */ -EX vector pathqm; - -/** list of cells that the monsters are targetting (PCs, allies, Thumpers, etc.) */ -EX vector targets; - -/** monsters of specific types to move */ -vector worms, ivies, ghosts, golems, hexsnakes; - -/** temporary changes during bfs */ -vector> tempmonsters; - -/** 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. - **/ -EX vector reachedfrom; - -/** monsters to move, ordered by the number of possible good moves */ -vector movesofgood[MAX_EDGE+1]; - -/** The position of the first cell in dcal in distance 7. New wandering monsters can be generated in dcal[first7..]. */ -EX int first7; - -/** Cellwalker describing the single player. Also used temporarily in shmup and multiplayer modes. */ -EX cellwalker cwt; - -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. @@ -218,873 +59,11 @@ EX int hrandstate() { return r2() & HRANDMAX; } -EX void initcell(cell *c) { - c->mpdist = 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 int lastsafety; -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; - -EX 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, flagtype 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; - } - -EX 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(to->move(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(const movei& mi) { - eWall x = mi.t->wall; mi.t->wall = mi.s->wall; mi.s->wall = x; - mi.t->mondir = mi.rev_dir_or(NODIR); - moveItem(mi.s, mi.t, false); - animateMovement(mi, LAYER_BOAT); - } - -EX void moveBoatIfUsingOne(const movei& mi) { - if(mi.s->wall == waBoat && isWatery(mi.t)) moveBoat(mi); - else if(mi.s->wall == waBoat && boatGoesThrough(mi.t) && isFriendly(mi.t) && markEmpathy(itOrbWater)) { - placeWater(mi.t, mi.s); - moveBoat(mi); - } - } - -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 || c->land == laBrownian) - 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; - } +EX bool usedSafety = false; +EX eLand safetyland; +EX int safetyseed; EX bool childbug = false; @@ -1110,4950 +89,16 @@ EX bool isChild(cell *w, cell *killed) { 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); } EX 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(sm.who == moPlayer && sm.moveto->item == itOrbSpeed && !items[itOrbSpeed]) fast = true; - } - - 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; - } - -EX bool monstersnear2(); - -EX 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; - } - -EX namespace multi { - EX bool checkonly = false; - EX bool aftermove; - EX } - -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(movei(c, FALL), moDeadBird); - 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)) && !cryst) { - 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(const movei& mi) { - moveMonster(mi); - auto& cf = mi.s; - auto& ct = mi.t; - 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) { - tempmonsters.emplace_back(c, (eMonster) 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; - -EX bool keepLightning = false; - -EX int statuecount; - -int tidalphase; - -EX 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()); - } - -/** current state of the rose scent - * rosemap[c] &3 can be: - * 0 - wave not reached - * 1 - wave expanding - * 2 - wave phase 1 - * 3 - wave phase 2 - */ -EX map rosemap; - -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 ? centerover : 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++) { - cell *c = pathq[qb]; - int fd = reachedfrom[qb] + c->type/2; - 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 - -EX vector > butterflies; - -EX void addButterfly(cell *c) { - for(int i=0; icpdist = INFD; - worms.clear(); ivies.clear(); ghosts.clear(); golems.clear(); - tempmonsters.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 = t.second; - - 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->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->wall == waLadder) - ; - 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->land == laBrownian && c->wall == waNone && c->landparam == 0) - c->landparam = 1; - - 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(const movei& mi, eMonster m) { - - auto& cf = mi.s; - auto& ct = mi.t; - 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(mi); - - 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(mi.rev(), LAYER_BOAT); - 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(); - bool lhu = landUnlocked(laHell); - items[it]++; if(it != itLotus) updateHi(it, items[it]); - if(it == itRevolver && items[it] > 6) items[it] = 6; - achievement_collection(it); - multi::treasures[multi::cpid]++; -#if CAP_DAILY - if(daily::on) achievement_final(false); -#endif - - int g2 = gold(); - if(items[itFireShard] && items[itAirShard] && items[itWaterShard] && items[itEarthShard]) { - items[itFireShard]--; - items[itAirShard]--; - items[itWaterShard]--; - items[itEarthShard]--; - gainItem(itElemental); - gainItem(itElemental); - gainItem(itElemental); - gainItem(itElemental); - addMessage(XLAT("You construct some Elemental Gems!", it) + itemcounter(items[itElemental])); - } - - if(it == itBounty) - items[itRevolver] = 6; - - if(it == itHyperstone && items[itHyperstone] == 10) - achievement_victory(true); - - if(chaosmode && gold() >= 300 && !chaosAchieved) { - achievement_gain("CHAOS", rg::chaos); - chaosAchieved = true; - } - -#if ISMOBILE==1 - if(g < 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(g < (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!")); - } - } - -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) { - collectMessage(c2, itShard); - gainItem(itShard); - s += itemcounter(items[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(const movei& mi) { - auto& cf = mi.s; - auto& ct = mi.t; - eMonster m = cf->monst; - bool fri = isFriendly(cf); - if(isDragon(m)) { - printf("called for Dragon\n"); - return; - } - if(m != moMimic) animateMovement(mi, LAYER_SMALL); - // 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(mi, m); - 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(mi.rev(), LAYER_BOAT); - } - - moveBoatIfUsingOne(mi); - } - - if(isTroll(m)) { makeTrollFootprints(ct); makeTrollFootprints(cf); } - - int inc = incline(cf, ct); - - if(m == moEarthElemental) { - if(!passable(ct, cf, 0)) earthFloor(ct); - earthMove(mi); - } - - 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) && !isFriendly(ct)) - adj = true; - - if(!adj && items[itOrbEmpathy] && items[itOrbBeauty] && !isFriendly(ct)) { - 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; - } - -/** for the monster at c1, evaluation of the move to c2 - * @param mf what moves are allowed - */ - -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) { - if(PURE) return reverse_directions(c->master, dir); - int d = c->degree(); - 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) }; - } - -EX vector reverse_directions(heptagon *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) }; - } - } - -#if HDR -template -movei determinePush(cellwalker who, int subdir, const T& valid) { - 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; - cell *c2 = push.at; - if(binarytiling) { - auto rd = reverse_directions(push.at, push.spin); - for(int i: rd) { - push.spin = i; - if(valid(push.cpeek())) return movei(push.at, push.spin); - } - return movei(c2, NO_SPACE); - } - int pd = push.at->type/2; - push += pd * -subdir; - push += wstep; - if(valid(push.at)) return movei(c2, (push+wstep).spin); - if(c2->type&1) { - push = push + wstep - subdir + wstep; - if(valid(push.at)) return movei(c2, (push+wstep).spin); - } - 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; - } - if(valid(push.at)) return movei(c2, (push+wstep).spin); - } - return movei(c2, NO_SPACE); - } -#endif - -// Angry Beast attack -// note: this is done both before and after movement -EX void beastAttack(cell *c, bool player, bool targetdir) { - if(c->mondir == NODIR) return; - forCellIdEx(c2, d, c) { - bool opposite = targetdir ? (d==c->mondir) : 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); - auto mi = determinePush(bull, subdir, [c2] (cell *c) { return passable(c, c2, P_BLOW); }); - if(mi.proper()) - pushMonster(mi); - } - } - 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); - auto mi = determinePush(bull, subdir, [c2] (cell *c) { return canPushThumperOn(c, c2, c); }); - if(mi.proper()) - pushThumper(mi); - } - } - } - -EX 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, false); - d = pickMoveDirection(c, mf); - } - if(d == -1) { - stayEffect(c); - return c; - } - - if(!quantum) { - movei mi(c, d); - auto& c2 = mi.t; - 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(mi, LAYER_SMALL); - if(m == moFlailer && m2 == moIllusion) - attackMonster(c, 0, m2); - return c2; - } - - moveMonster(mi); - if(m == moRagingBull) beastAttack(c2, false, 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; imonst) c->monst = m; - moveMonster(mi); - if(m == moRagingBull) beastAttack(mi.t, false, 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[i].at = mi.t; - multi::player[i].spin = mi.rev_dir(); - multi::flipped[i] = fp; - } - else { - cwt.at = mi.t; - cwt.spin = mi.rev_dir(); - flipplayer = fp; - } - afterplayermoved(); - } - if(lastmountpos[i] == mi.s && mi.s) { - lastmountpos[i] = mi.t; - } - else if(lastmountpos[i] == mi.t) { - lastmountpos[i] = NULL; - } - } - } - -EX void moveWorm(cell *c) { - - bool mounted = isMounted(c); - - if(c->monst == moWormwait) { c->monst = moWorm; return; } - else if(c->monst == moTentaclewait) { c->monst = moTentacle; return; } - else if(c->monst == moTentacleEscaping) { - // explodeAround(c); - 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]; - auto mi = moveimon(cft); - if(cft->monst != moTentacleGhost && cmt->monst != moTentacleGhost) - mountmove(mi, false); - animateMovement(mi, LAYER_BIG); - } - 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; - } - - movei mi(c, dir); - auto& goal = mi.t; - - if(isPlayerOn(goal) || goal->monst) - attackMonster(goal, AF_EAT | AF_MSG | AF_GETPLAYER, c->monst); - - if(1) { - goal->monst = eMonster(moWormwait + id); - moveEffect(mi, eMonster(moWormwait + id)); - - animateMovement(mi, LAYER_BIG); - c->monst = eMonster(moWormtail + id); - goal->mondir = mi.rev_dir_or(NODIR); - goal->monmirror = c->monmirror ^ c->c.mirror(dir); - - mountmove(mi, true); - - if(id) { - cell *c2 = c, *c3 = c2; - while(c2->monst == moTentacletail || c2->monst == moTentacleGhost) { - auto mim = moveimon(c2).rev(); - if(!mim.proper()) return; - c3 = c2, c2 = mim.s; - if(c3->monst != moTentacleGhost && c2->monst != moTentacleGhost) - mountmove(mim, true); - animateMovement(mim, LAYER_BIG); - } - } - - cell *c2 = c, *c3 = c2; - for(int a=0; amonst == moWormtail) { - movei mim = moveimon(c2).rev(); - if(!mim.proper()) { - drawParticles(c2, (linf[c2->land].color & 0xF0F0F0), 16); - return; - } - c3 = c2, c2 = mim.s; - mountmove(mim, true); - animateMovement(mim, LAYER_BIG); - } - } - - 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); - - int pd = c->pathdist; - - movei mi(nullptr, nullptr, NODIR); - - while(c->monst != moIvyRoot) { - if(!isIvy(c->monst)) { - raiseBuggyGeneration(c, "that's not an Ivy!"); - } - if(c->mondir == NODIR) - raiseBuggyGeneration(c, "wrong mondir!"); - - forCellIdEx(c2, j, c) { - if(canAttack(c, c->monst, c2, c2->monst, AF_ONLY_FRIEND | AF_GETPLAYER)) { - if(isPlayerOn(c2)) - killThePlayerAt(c->monst, c2, 0); - else { - if(attackJustStuns(c2, 0, c->monst)) - addMessage(XLAT("The ivy attacks %the1!", c2->monst)); - else if(isNonliving(c2->monst)) - addMessage(XLAT("The ivy destroys %the1!", c2->monst)); - else - addMessage(XLAT("The ivy kills %the1!", c2->monst)); - attackMonster(c2, AF_NORMAL, c->monst); - } - continue; - } - if(c2 && c2->pathdist < pd && passable(c2, c, 0) && !strictlyAgainstGravity(c2, c, false, MF_IVY)) - mi = movei(c, j), pd = c2->pathdist; - } - c = c->move(c->mondir); - } - - auto& mto = mi.t; - - if(mto && mto->cpdist) { - animateMovement(mi, LAYER_BIG); - mto->monst = moIvyWait, mto->mondir = mi.rev_dir_or(NODIR); - mto->monmirror = mi.s->monmirror ^ mi.mirror(); - moveEffect(mi, moIvyWait); - // if this is the only branch, we want to move the head immediately to mto instead - if(mi.s->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(const movei& mi) { - auto& from = mi.s; - bool b = false; - cell *c2 = mi.t; - b |= earthWall(from); - if(!mi.proper()) return b; - int d = mi.rev_dir_or(0); - 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(from->wall == waThumperOn) ; - else if(passable_for(movtype, from, c, P_CHAIN | P_MONSTER)) ; - else if(canAttack(c, movtype, from, from->monst, AF_GETPLAYER | AF_NOSHIELD)) ; - 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(movei(c, d).rev(), 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(movei(c, j), LAYER_SMALL); - onpath(c, 0); - // XLATC eagle - return; - } - - if(from->cpdist == 0 || from->monst) { onpath(c, 0); return; } - - if(movtype == moDragonHead) { - dragon::move(mi); - return; - } - - moveMonster(mi); - 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(movei(c,d).rev(),movtype,mf); - } - } - } - } - else { - if(!peace::on) for(int i=0; imonst == moNone && !isPlayerOn(c) && !bird_disruption(c)) { - cell *c2 = whirlwind::jumpFromWhereTo(c, false); - groupmove2(movei(c2, c, STRONGWIND), 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; - -EX void moveHexSnake(const movei& mi, bool mounted) { - // note: move from 'c' to 'from'! - auto& from = mi.t; - auto& c = mi.s; - if(from->wall == waBoat) from->wall = waSea; - moveEffect(mi, c->monst); - from->monst = c->monst; from->mondir = mi.rev_dir_or(NODIR); from->hitpoints = c->hitpoints; - c->monst = moHexSnakeTail; - preventbarriers(from); - - animateMovement(mi, LAYER_BIG); - mountmove(mi, true); - - cell *c2 = c, *c3=c2; - for(int a=0;; a++) if(c2->monst == moHexSnakeTail) { - if(a == ROCKSNAKELENGTH) { c2->monst = moNone, c3->mondir = NODIR; break; } - auto mim = moveimon(c2).rev(); - if(!mim.proper()) break; - mountmove(mim, true); - animateMovement(mim, LAYER_BIG); - c3 = c2, c2 = mim.s; - } - 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(movei(from, d).rev(), 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++) { - movei mi(c, j); - auto& c2 = mi.t; - 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(mi, LAYER_BIG); - } - } - } - } - -#if HDR -#define SHSIZE 16 -#endif - -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(match(shfrom, where), LAYER_SMALL); - 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(movei(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; - } - -EX bool hasPrincessWeapon(eMonster m) { - return m == moPalace || m == moFatGuard; - } - -/** for an ally m at c, evaluate staying in place */ -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; - } - -/** for an ally m at c, evaluate moving to c2 */ -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; - } - -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; imonst) { - 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(movei(c, dir), LAYER_SMALL); - 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(mi); - if(m != moTameBomberbird && m != moFriendlyGhost) - moveBoatIfUsingOne(mi); - - 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(mi); - } - 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 && @@ -6064,523 +109,6 @@ 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); - } - -EX int ambush_distance; -EX 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 && !c->item && passable(c, NULL, 0)) - c->item = it, qty--; - } - } - EX cellwalker recallCell; EX display_data recallDisplay; @@ -6722,81 +241,6 @@ EX void activateSafety(eLand l) { restartGraph(); } -EX 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; - if(shmup::on) 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); @@ -6850,1047 +294,12 @@ EX bool multiRevival(cell *on, cell *moveto) { return false; } -bool got_crossroads; - -EX void movecost(cell* from, cell *to, int phase) { - if(from->land == 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 && !inv::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); - } - } - -EX 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)) { - - 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 = intensify(oc); - 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); - } - 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 { - if(c2->item == itBarrow) - for(int i=0; ilandparam; i++) gainItem(c2->item); - else if(c2->item) gainItem(c2->item); - - 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(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(cryst && isCrossroads(specialland)) return true; - if((in_s2xe() || nonisotropic || (hybri && hybrid::under_class() != gcSphere)) && 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 && cryst) { - 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(cryst) - 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(cryst) - 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++; - } - -EX int mine_adjacency_rule = 0; - -EX 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, C0); - 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; - } - -EX 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); - } - - EX 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; - } - EX } - -#if HDR -// predictable or not -static constexpr bool randterra = false; -#endif - -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(movei(c, FALL), moDeadBird); - 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; iwparam = 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); - } - -EX 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(match(c2, c1)); - } - 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) { - movei mi(cwt.at, d); - cell *& c2 = mi.t; - 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) { - auto mip = determinePush(cwt, subdir, [c2] (cell *c) { return canPushThumperOn(c, c2, cwt.at); }); - if(mip.d == NO_SPACE) { - 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 = mip.t; - if(checkonly) { nextmovetype = lmMove; return true; } - addMessage(XLAT("You push %the1.", c2->wall)); - lastmovetype = lmPush; lastmove = cwt.at; - pushThumper(mip); - } - - 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) { - if(markOrb(itOrbFish) || markOrb(itOrbAether) || gravity_state) goto escape; - if(!checkonly) 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(mi); - boatmove = true; - goto boatjump; - } - - if(!c2->monst && cwt.at->wall == waBoat && cwt.at->item != itOrbYendor && 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(mi); - 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(mi.rev(), LAYER_BOAT); - 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(mi, LAYER_SMALL); - } - 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(mi, LAYER_SMALL); - } - 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(mi, LAYER_SMALL); - } - } - 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 destroyed and thus - // still counts for lightning in monstersnear - - movei mip(c2, nullptr, NO_SPACE); - - cell *pushto = NULL; - if(isStunnable(c2->monst) && c2->hitpoints > 1) { - if(monsterPushable(c2)) - mip = determinePush(cwt, subdir, [c2] (cell *c) { return passable(c, c2, P_BLOW); }); - else - mip.t = c2; - } - if(c2->monst == moTroll || c2->monst == moFjordTroll || - c2->monst == moForestTroll || c2->monst == moStormTroll || c2->monst == moVineSpirit) - mip.t = c2; - - global_pushto = mip.t; - - if(havePushConflict(mip.t, 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); - } - 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(mip.proper()) pushMonster(mip); - animateAttack(mi, LAYER_SMALL); - } - } - - 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(snakelevel(c2) <= snakelevel(cwt.at)-2) { - bool can_leave = false; - forCellEx(c3, c2) if(passable(c3, c2, P_ISPLAYER | P_MONSTER)) can_leave = true; - if(!can_leave && !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(mi)) 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; - cwt += wstep; - if(switchplaces) { - indAnimateMovement(mi, LAYER_SMALL); - indAnimateMovement(mi.rev(), LAYER_SMALL); - commitAnimations(LAYER_SMALL); - } - else - animateMovement(mi, LAYER_SMALL); - current_display->which_copy = current_display->which_copy * adj(mi); - - 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 && @@ -8717,42 +376,6 @@ inline bool movepcto(const movedir& md) { return movepcto(md.d, md.subdir); } !(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"); @@ -8809,67 +432,9 @@ EX eMonster haveMount() { return moNone; } -EX bool mightBeMine(cell *c) { - return c->wall == waMineUnknown || c->wall == waMineMine; - } - -EX 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; +EX eMonster otherpole(eMonster m) { + return eMonster(m ^ moNorthPole ^ moSouthPole); } + } diff --git a/graph.cpp b/graph.cpp index 21af36bc..8830e3e0 100644 --- a/graph.cpp +++ b/graph.cpp @@ -976,6 +976,7 @@ EX void drawTerraWarrior(const transmatrix& V, int t, int hp, double footphase) void drawPlayer(eMonster m, cell *where, const transmatrix& V, color_t col, double footphase, bool stop = false) { charstyle& cs = getcs(); + auto& knighted = camelot::knighted; if(mapeditor::drawplayer && !mapeditor::drawUserShape(V, mapeditor::sgPlayer, cs.charid, cs.skincolor, where)) { @@ -1219,7 +1220,7 @@ void drawMimic(eMonster m, cell *where, const transmatrix& V, color_t col, doubl else if(!where || shmup::curtime >= shmup::getPlayer()->nextshot) queuepoly(VBODY * VBS, cgi.shPKnife, darkena(col, 0, 0XC0)); - if(knighted) + if(camelot::knighted) queuepoly(VBODY3 * VBS, cgi.shKnightCloak, darkena(col, 1, 0xC0)); queuepoly(VHEAD1, (cs.charid&1) ? cgi.shFemaleHair : cgi.shPHead, darkena(col, 1, 0XC0)); @@ -3263,7 +3264,7 @@ EX bool placeSidewall(cell *c, int i, int sidepar, const transmatrix& V, color_t #endif bool openorsafe(cell *c) { - return c->wall == waMineOpen || mineMarkedSafe(c); + return c->wall == waMineOpen || mine::marked_safe(c); } #define Dark(x) darkena(x,0,0xFF) @@ -3989,8 +3990,8 @@ EX void drawMarkers() { queuecircleat(lmouseover, .8, darkena(lmouseover->cpdist > 1 ? 0x00FFFF : 0xFF0000, 0, 0xFF)); } - if(global_pushto && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON && WDIM == 2) { - queuecircleat(global_pushto, .6, darkena(0xFFD500, 0, 0xFF)); + if(pcm.mip.t && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON && WDIM == 2) { + queuecircleat(pcm.mip.t, .6, darkena(0xFFD500, 0, 0xFF)); } #endif diff --git a/help.cpp b/help.cpp index 2e1a8078..d179f722 100644 --- a/help.cpp +++ b/help.cpp @@ -885,7 +885,7 @@ EX void describeMouseover() { out += XLAT1(iinf[c->item].name); if(c->item == itBarrow) out += " (x" + its(c->landparam) + ")"; if(c->land == laHunting) { - int i = ambushSize(c, c->item); + int i = ambush::size(c, c->item); if(i) out += " (" + XLAT("ambush:") + " " + its(i) + ")"; } if(c->item == itBabyTortoise && tortoise::seek()) diff --git a/hyper.cpp b/hyper.cpp index 7c923cbd..b19e470d 100644 --- a/hyper.cpp +++ b/hyper.cpp @@ -55,6 +55,14 @@ #include "complex2.cpp" #include "savemem.cpp" #include "game.cpp" +#include "passable.cpp" +#include "checkmove.cpp" +#include "pcmove.cpp" +#include "environment.cpp" +#include "monstermove.cpp" +#include "mapeffects.cpp" +#include "attack.cpp" +#include "items.cpp" #include "orbgen.cpp" #include "monstergen.cpp" #include "landlock.cpp" diff --git a/hyper.h b/hyper.h index 8b50723e..1da0acb1 100644 --- a/hyper.h +++ b/hyper.h @@ -410,7 +410,6 @@ typedef function cellfunction; #define PT(x, y) ((tactic::on || quotient == 2 || daily::on) ? (y) : inv::on ? min(2*(y),x) : (x)) #define ROCKSNAKELENGTH 50 #define WORMLENGTH 15 -#define PUREHARDCORE_LEVEL 10 #define PRIZEMUL 7 #define INF 9999 diff --git a/items.cpp b/items.cpp new file mode 100644 index 00000000..ed33ea84 --- /dev/null +++ b/items.cpp @@ -0,0 +1,631 @@ +// Hyperbolic Rogue - items +// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details + +/** \file item.cpp + * \brief routines related to items + */ + +#include "hyper.h" + +namespace hr { + +EX int currentLocalTreasure; + +/** for treasures, the number collected; for orbs, the number of charges */ +EX array items; + +EX map > hiitems; + +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)) { + + 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::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 = intensify(oc); + 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); + } + 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 { + if(c2->item == itBarrow) + for(int i=0; ilandparam; i++) gainItem(c2->item); + else if(c2->item) gainItem(c2->item); + + 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(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 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 bool itemHidden(cell *c) { + return isWatery(c) && !(shmup::on && shmup::boatAt(c)); + } + +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; + } + +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(); + bool lhu = landUnlocked(laHell); + items[it]++; if(it != itLotus) updateHi(it, items[it]); + if(it == itRevolver && items[it] > 6) items[it] = 6; + achievement_collection(it); + multi::treasures[multi::cpid]++; +#if CAP_DAILY + if(daily::on) achievement_final(false); +#endif + + int g2 = gold(); + if(items[itFireShard] && items[itAirShard] && items[itWaterShard] && items[itEarthShard]) { + items[itFireShard]--; + items[itAirShard]--; + items[itWaterShard]--; + items[itEarthShard]--; + gainItem(itElemental); + gainItem(itElemental); + gainItem(itElemental); + gainItem(itElemental); + addMessage(XLAT("You construct some Elemental Gems!", it) + itemcounter(items[itElemental])); + } + + if(it == itBounty) + items[itRevolver] = 6; + + if(it == itHyperstone && items[itHyperstone] == 10) + achievement_victory(true); + + if(chaosmode && gold() >= 300 && !chaosAchieved) { + achievement_gain("CHAOS", rg::chaos); + chaosAchieved = true; + } + +#if ISMOBILE==1 + if(g < 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(g < (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!")); + } + } + +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) { + collectMessage(c2, itShard); + gainItem(itShard); + s += itemcounter(items[itShard]); + } + addMessage(s); + c2->wall = waNone; + invismove = false; + } + +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 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 && !inv::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); + } + } + +EX bool itemHiddenFromSight(cell *c) { + return isWatery(c) && !items[itOrbInvis] && !(items[itOrbFish] && playerInWater()) + && !(shmup::on && shmup::boatAt(c)); + } + +} diff --git a/landgen.cpp b/landgen.cpp index 776b15e2..8a493ae4 100644 --- a/landgen.cpp +++ b/landgen.cpp @@ -12,6 +12,8 @@ namespace hr { // land generation routines +EX int explore[10], exploreland[10][landtypes], landcount[landtypes]; + EX bool safety = false; EX eLand lastland; @@ -1118,7 +1120,7 @@ EX void giantLandSwitch(cell *c, int d, cell *from) { createArrowTrapAt(c, laTerracotta); if(pseudohept(c) && hrand(100) < 40 && c->wall == waNone && !racing::on) { c->wall = waTerraWarrior; - c->landparam = randterra ? 0 : 3 + hrand(3); + c->landparam = terracotta::randterra ? 0 : 3 + hrand(3); if(hrand(100) < items[itTerra]-10) c->landparam--; if(hrand(100) < items[itTerra]-10) @@ -2430,8 +2432,8 @@ EX void giantLandSwitch(cell *c, int d, cell *from) { if(fargen) { int treasure_rate = 2; for(int i=0; i<21; i++) if((b>>i) & 1) { - treasure_rate += variant_features[i].rate_change; - variant_features[i].build(c); + treasure_rate += variant::features[i].rate_change; + variant::features[i].build(c); } if(hrand(2000 - PT(kills[moVariantWarrior] * 5, 250)) < treasure_rate && !c->wall && !c->monst) c->item = itVarTreasure; diff --git a/landlock.cpp b/landlock.cpp index 1644f010..a08b0984 100644 --- a/landlock.cpp +++ b/landlock.cpp @@ -8,6 +8,18 @@ #include "hyper.h" namespace hr { +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(cryst && isCrossroads(specialland)) return true; + if((in_s2xe() || nonisotropic || (hybri && hybrid::under_class() != gcSphere)) && isCrossroads(specialland)) return true; + if(geometry == gNormal && !NONSTDVAR) return true; + return false; + } + EX bool nodisplay(eMonster m) { return m == moIvyDead || diff --git a/mapeffects.cpp b/mapeffects.cpp new file mode 100644 index 00000000..66d97d3c --- /dev/null +++ b/mapeffects.cpp @@ -0,0 +1,929 @@ +// Hyperbolic Rogue - Map effects +// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details + +/** \file mapeffects.cpp + * \brief Routines handling the map effects + */ + +#include "hyper.h" +namespace hr { + +/** offscreen cells to take care off */ +EX vector offscreen; + +EX void initcell(cell *c) { + c->mpdist = 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 void destroyBoats(cell *c, cell *c2, bool strandedToo) { + if(c->wall == waBoat) placeWater(c, c2); + if(strandedToo && c->wall == waStrandedBoat) c->wall = waNone; + shmup::destroyBoats(c); + } + +#if HDR +enum eGravity { gsNormal, gsLevitation, gsAnti }; +#endif +EX eGravity gravity_state, last_gravity_state; + +EX bool bird_disruption(cell *c) { + return c->cpdist <= 5 && items[itOrbGravity]; + } + +EX bool in_gravity_zone(cell *c) { + return gravity_state && c->cpdist <= 5; + } + +EX 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; + } + +EX bool isJWall(cell *c) { + return isWall(c) || c->monst == passive_switch; + } + +EX 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; + } + +EX 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); + } + +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 || c->land == laBrownian) + placeWater(c, c); + else { + c->wall = c->land == laCaribbean ? waCIsland2 : waNone; + } + } + } + +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); + mine::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 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 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; + } + +int tidalphase; + +EX 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 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->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->wall == waLadder) + ; + 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->land == laBrownian && c->wall == waNone && c->landparam == 0) + c->landparam = 1; + + 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); + } + } + +#if HDR +template +movei determinePush(cellwalker who, int subdir, const T& valid) { + 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; + cell *c2 = push.at; + if(binarytiling) { + auto rd = reverse_directions(push.at, push.spin); + for(int i: rd) { + push.spin = i; + if(valid(push.cpeek())) return movei(push.at, push.spin); + } + return movei(c2, NO_SPACE); + } + int pd = push.at->type/2; + push += pd * -subdir; + push += wstep; + if(valid(push.at)) return movei(c2, (push+wstep).spin); + if(c2->type&1) { + push = push + wstep - subdir + wstep; + if(valid(push.at)) return movei(c2, (push+wstep).spin); + } + 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; + } + if(valid(push.at)) return movei(c2, (push+wstep).spin); + } + return movei(c2, NO_SPACE); + } +#endif + +// 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 bool earthMove(const movei& mi) { + auto& from = mi.s; + bool b = false; + cell *c2 = mi.t; + b |= earthWall(from); + if(!mi.proper()) return b; + int d = mi.rev_dir_or(0); + if(c2) for(int u=2; u<=c2->type-2; u++) { + cell *c3 = c2->modmove(d + u); + if(c3) b |= earthFloor(c3); + } + return b; + } + +EX bool cellDangerous(cell *c) { + return cellUnstableOrChasm(c) || isFire(c) || c->wall == waClosedGate; + } + + +} diff --git a/monstergen.cpp b/monstergen.cpp index 042572d8..afcbafc2 100644 --- a/monstergen.cpp +++ b/monstergen.cpp @@ -8,6 +8,8 @@ #include "hyper.h" namespace hr { +EX int avengers, mirrorspirits, wandering_jiangshi, jiangshi_on_screen; + EX bool timerghost = true; EX bool gen_wandering = true; @@ -312,7 +314,7 @@ EX void wandering() { kills[moBomberbird] = 0; kills[moTameBomberbird] = 0; for(cell *c: currentmap->allcells()) if(c->wall == waMineUnknown) kills[moBomberbird]++; - for(cell *c: currentmap->allcells()) if(among(c->wall, waMineMine, waMineUnknown) && mineMarked(c)) kills[moTameBomberbird]++; + for(cell *c: currentmap->allcells()) if(among(c->wall, waMineMine, waMineUnknown) && mine::marked_mine(c)) kills[moTameBomberbird]++; return; } if(!canmove) return; @@ -440,7 +442,7 @@ EX void wandering() { else if(c->land == laVariant && wchance(items[itVarTreasure], 50)) { int i = hrand(21); if(getBits(c) & (1>>i)) { - eMonster m = variant_features[i].wanderer; + eMonster m = variant::features[i].wanderer; if(m) c->monst = m, c->hitpoints = 3; } continue; diff --git a/monstermove.cpp b/monstermove.cpp new file mode 100644 index 00000000..c896b365 --- /dev/null +++ b/monstermove.cpp @@ -0,0 +1,2083 @@ +// Hyperbolic Rogue - monster movement +// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details + +/** \file monstermove.cpp + * \brief monster movement + */ + +#include "hyper.h" + +namespace hr { + +EX int turncount; + +EX int mutantphase; + +EX int sagephase = 0; + +/** list of cells that the monsters are targetting (PCs, allies, Thumpers, etc.) */ +EX vector targets; + +/** monsters to move, ordered by the number of possible good moves */ +vector movesofgood[MAX_EDGE+1]; + +EX vector > butterflies; + +EX void addButterfly(cell *c) { + for(int i=0; iland != laTrollheim) return; + if(c->item == itTrollEgg && c->landparam) return; + c->landparam = turncount + 100; + } + +EX bool hasPrincessWeapon(eMonster m) { + return m == moPalace || m == moFatGuard; + } + +EX void sageheat(cell *c, double v) { + HEAT(c) += v; + if(c->wall == waFrozenLake && HEAT(c) > .6) c->wall = waLake; + } + +bool sagefresh = true; + +/** 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(const movei& mi, eMonster m) { + + auto& cf = mi.s; + auto& ct = mi.t; + if(cf) destroyWeakBranch(cf, ct, m); + + mayExplodeMine(ct, m); + + if(!isNonliving(m)) terracotta::check_around(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(mi); + + 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(mi.rev(), LAYER_BOAT); + tortoise::babymap[cf] = tortoise::babymap[ct]; + tortoise::babymap.erase(ct); + } + } + +EX void moveMonster(const movei& mi) { + auto& cf = mi.s; + auto& ct = mi.t; + eMonster m = cf->monst; + bool fri = isFriendly(cf); + if(isDragon(m)) { + printf("called for Dragon\n"); + return; + } + if(m != moMimic) animateMovement(mi, LAYER_SMALL); + // 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(mi, m); + 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(mi.rev(), LAYER_BOAT); + } + + moveBoatIfUsingOne(mi); + } + + if(isTroll(m)) { makeTrollFootprints(ct); makeTrollFootprints(cf); } + + int inc = incline(cf, ct); + + if(m == moEarthElemental) { + if(!passable(ct, cf, 0)) earthFloor(ct); + earthMove(mi); + } + + 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) && !isFriendly(ct)) + adj = true; + + if(!adj && items[itOrbEmpathy] && items[itOrbBeauty] && !isFriendly(ct)) { + 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; + } + +/** for the monster at c1, evaluation of the move to c2 + * @param mf what moves are allowed + */ + +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)]; + } + +// Angry Beast attack +// note: this is done both before and after movement +EX void beastAttack(cell *c, bool player, bool targetdir) { + if(c->mondir == NODIR) return; + forCellIdEx(c2, d, c) { + bool opposite = targetdir ? (d==c->mondir) : 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); + auto mi = determinePush(bull, subdir, [c2] (cell *c) { return passable(c, c2, P_BLOW); }); + if(mi.proper()) + pushMonster(mi); + } + } + 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); + auto mi = determinePush(bull, subdir, [c2] (cell *c) { return canPushThumperOn(c, c2, c); }); + if(mi.proper()) + pushThumper(mi); + } + } + } + +EX 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, false); + d = pickMoveDirection(c, mf); + } + if(d == -1) { + stayEffect(c); + return c; + } + + if(!quantum) { + movei mi(c, d); + auto& c2 = mi.t; + 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(mi, LAYER_SMALL); + if(m == moFlailer && m2 == moIllusion) + attackMonster(c, 0, m2); + return c2; + } + + moveMonster(mi); + if(m == moRagingBull) beastAttack(c2, false, 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; imonst) c->monst = m; + moveMonster(mi); + if(m == moRagingBull) beastAttack(mi.t, false, false); + } + return c->move(d); + } + } + +EX void mountmove(const movei& mi, bool fp) { + for(int i=0; i 1) { + multi::player[i].at = mi.t; + multi::player[i].spin = mi.rev_dir(); + multi::flipped[i] = fp; + } + else { + cwt.at = mi.t; + cwt.spin = mi.rev_dir(); + flipplayer = fp; + } + afterplayermoved(); + } + if(lastmountpos[i] == mi.s && mi.s) { + lastmountpos[i] = mi.t; + } + else if(lastmountpos[i] == mi.t) { + lastmountpos[i] = NULL; + } + } + } + +EX void moveWorm(cell *c) { + + bool mounted = isMounted(c); + + if(c->monst == moWormwait) { c->monst = moWorm; return; } + else if(c->monst == moTentaclewait) { c->monst = moTentacle; return; } + else if(c->monst == moTentacleEscaping) { + // explodeAround(c); + 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]; + auto mi = moveimon(cft); + if(cft->monst != moTentacleGhost && cmt->monst != moTentacleGhost) + mountmove(mi, false); + animateMovement(mi, LAYER_BIG); + } + 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; + } + + movei mi(c, dir); + auto& goal = mi.t; + + if(isPlayerOn(goal) || goal->monst) + attackMonster(goal, AF_EAT | AF_MSG | AF_GETPLAYER, c->monst); + + if(1) { + goal->monst = eMonster(moWormwait + id); + moveEffect(mi, eMonster(moWormwait + id)); + + animateMovement(mi, LAYER_BIG); + c->monst = eMonster(moWormtail + id); + goal->mondir = mi.rev_dir_or(NODIR); + goal->monmirror = c->monmirror ^ c->c.mirror(dir); + + mountmove(mi, true); + + if(id) { + cell *c2 = c, *c3 = c2; + while(c2->monst == moTentacletail || c2->monst == moTentacleGhost) { + auto mim = moveimon(c2).rev(); + if(!mim.proper()) return; + c3 = c2, c2 = mim.s; + if(c3->monst != moTentacleGhost && c2->monst != moTentacleGhost) + mountmove(mim, true); + animateMovement(mim, LAYER_BIG); + } + } + + cell *c2 = c, *c3 = c2; + for(int a=0; amonst == moWormtail) { + movei mim = moveimon(c2).rev(); + if(!mim.proper()) { + drawParticles(c2, (linf[c2->land].color & 0xF0F0F0), 16); + return; + } + c3 = c2, c2 = mim.s; + mountmove(mim, true); + animateMovement(mim, LAYER_BIG); + } + } + + 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); + + int pd = c->pathdist; + + movei mi(nullptr, nullptr, NODIR); + + while(c->monst != moIvyRoot) { + if(!isIvy(c->monst)) { + raiseBuggyGeneration(c, "that's not an Ivy!"); + } + if(c->mondir == NODIR) + raiseBuggyGeneration(c, "wrong mondir!"); + + forCellIdEx(c2, j, c) { + if(canAttack(c, c->monst, c2, c2->monst, AF_ONLY_FRIEND | AF_GETPLAYER)) { + if(isPlayerOn(c2)) + killThePlayerAt(c->monst, c2, 0); + else { + if(attackJustStuns(c2, 0, c->monst)) + addMessage(XLAT("The ivy attacks %the1!", c2->monst)); + else if(isNonliving(c2->monst)) + addMessage(XLAT("The ivy destroys %the1!", c2->monst)); + else + addMessage(XLAT("The ivy kills %the1!", c2->monst)); + attackMonster(c2, AF_NORMAL, c->monst); + } + continue; + } + if(c2 && c2->pathdist < pd && passable(c2, c, 0) && !strictlyAgainstGravity(c2, c, false, MF_IVY)) + mi = movei(c, j), pd = c2->pathdist; + } + c = c->move(c->mondir); + } + + auto& mto = mi.t; + + if(mto && mto->cpdist) { + animateMovement(mi, LAYER_BIG); + mto->monst = moIvyWait, mto->mondir = mi.rev_dir_or(NODIR); + mto->monmirror = mi.s->monmirror ^ mi.mirror(); + moveEffect(mi, moIvyWait); + // if this is the only branch, we want to move the head immediately to mto instead + if(mi.s->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); + } + } + } + +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(from->wall == waThumperOn) ; + else if(passable_for(movtype, from, c, P_CHAIN | P_MONSTER)) ; + else if(canAttack(c, movtype, from, from->monst, AF_GETPLAYER | AF_NOSHIELD)) ; + 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(movei(c, d).rev(), 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(movei(c, j), LAYER_SMALL); + onpath(c, 0); + // XLATC eagle + return; + } + + if(from->cpdist == 0 || from->monst) { onpath(c, 0); return; } + + if(movtype == moDragonHead) { + dragon::move(mi); + return; + } + + moveMonster(mi); + 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(movei(c,d).rev(),movtype,mf); + } + } + } + } + else { + if(!peace::on) for(int i=0; imonst == moNone && !isPlayerOn(c) && !bird_disruption(c)) { + cell *c2 = whirlwind::jumpFromWhereTo(c, false); + groupmove2(movei(c2, c, STRONGWIND), 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; + +EX void moveHexSnake(const movei& mi, bool mounted) { + // note: move from 'c' to 'from'! + auto& from = mi.t; + auto& c = mi.s; + if(from->wall == waBoat) from->wall = waSea; + moveEffect(mi, c->monst); + from->monst = c->monst; from->mondir = mi.rev_dir_or(NODIR); from->hitpoints = c->hitpoints; + c->monst = moHexSnakeTail; + preventbarriers(from); + + animateMovement(mi, LAYER_BIG); + mountmove(mi, true); + + cell *c2 = c, *c3=c2; + for(int a=0;; a++) if(c2->monst == moHexSnakeTail) { + if(a == ROCKSNAKELENGTH) { c2->monst = moNone, c3->mondir = NODIR; break; } + auto mim = moveimon(c2).rev(); + if(!mim.proper()) break; + mountmove(mim, true); + animateMovement(mim, LAYER_BIG); + c3 = c2, c2 = mim.s; + } + 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(movei(from, d).rev(), 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++) { + movei mi(c, j); + auto& c2 = mi.t; + 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(mi, LAYER_BIG); + } + } + } + } + +#if HDR +#define SHSIZE 16 +#endif + +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(match(shfrom, where), LAYER_SMALL); + 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(movei(c, d)); + + } + nextghost: ; + } + } + +/** for an ally m at c, evaluate staying in place */ +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; + } + +/** for an ally m at c, evaluate moving to c2 */ +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(mine::marked_mine(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; + } + +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; imonst) { + 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(movei(c, dir), LAYER_SMALL); + 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(mi); + if(m != moTameBomberbird && m != moFriendlyGhost) + moveBoatIfUsingOne(mi); + + 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(mi); + } + DEBB(DF_TURN, ("other")); + } + } + achievement_count("GOLEM", qg, 0); + } + +/** note: butterflies don't use moveNormal for two reasons: + * 1) to make sure that they move AFTER bulls + * 2) to make sure that they move offscreen + */ +EX void moveButterflies() { + int j = 0; + 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 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 movehex_all() { + for(int i: snaketypes) { + movehex(false, i); + if(!shmup::on && haveMount()) movehex(true, i); + } + movehex_rest(false); + movehex_rest(true); + } + +EX void movemonsters() { + ambush::distance = 0; + + DEBB(DF_TURN, ("lava1")); + orboflava(1); + + ambush::check_state(); + + sagefresh = true; + turncount++; + + specialMoves(); + + DEBB(DF_TURN, ("ghosts")); + moveghosts(); + + DEBB(DF_TURN, ("butterflies")); + moveButterflies(); + + DEBB(DF_TURN, ("normal")); + moveNormals(moYeti); + + DEBB(DF_TURN, ("slow")); + if(havewhat & HF_SLOW) moveNormals(moTortoise); + + if(sagefresh) sagephase = 0; + + DEBB(DF_TURN, ("ivy")); + moveivy(); + DEBB(DF_TURN, ("slimes")); + groupmove(moSlime, 0); + DEBB(DF_TURN, ("sharks")); + if(havewhat & HF_SHARK) groupmove(moShark, 0); + DEBB(DF_TURN, ("eagles")); + if(havewhat & HF_BIRD) groupmove(moEagle, 0); + if(havewhat & HF_EAGLES) groupmove(moEagle, MF_NOATTACKS | MF_ONLYEAGLE); + DEBB(DF_TURN, ("eagles")); + if(havewhat & HF_REPTILE) groupmove(moReptile, 0); + DEBB(DF_TURN, ("air")); + if(havewhat & HF_AIR) { + airmap.clear(); + groupmove(moAirElemental, 0); + buildAirmap(); + } + DEBB(DF_TURN, ("earth")); + if(havewhat & HF_EARTH) groupmove(moEarthElemental, 0); + DEBB(DF_TURN, ("water")); + if(havewhat & HF_WATER) groupmove(moWaterElemental, 0); + DEBB(DF_TURN, ("void")); + if(havewhat & HF_VOID) groupmove(moVoidBeast, 0); + DEBB(DF_TURN, ("leader")); + if(havewhat & HF_LEADER) groupmove(moPirate, 0); + DEBB(DF_TURN, ("mutant")); + if((havewhat & HF_MUTANT) || (bounded && among(specialland, laOvergrown, laClearing))) movemutant(); + DEBB(DF_TURN, ("bugs")); + if(havewhat & HF_BUG) hive::movebugs(); + DEBB(DF_TURN, ("whirlpool")); + if(havewhat & HF_WHIRLPOOL) whirlpool::move(); + DEBB(DF_TURN, ("whirlwind")); + if(havewhat & HF_WHIRLWIND) whirlwind::move(); + #if CAP_COMPLEX2 + DEBB(DF_TURN, ("westwall")); + if(havewhat & HF_WESTWALL) westwall::move(); + #endif + 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= 0) return true; + if(cellUnstable(to)) return true; + return false; + } + +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 int realstuntime(cell *c) { + if(isMutantIvy(c)) return (c->stuntime - mutantphase) & 15; + return c->stuntime; + } + + +} diff --git a/orbgen.cpp b/orbgen.cpp index 93f4c14c..c69deff3 100644 --- a/orbgen.cpp +++ b/orbgen.cpp @@ -413,6 +413,10 @@ EX bool buildPrizeMirror(cell *c, int freq) { return mirror::build(c); } +#if HDR +extern cellwalker cwt; +#endif + EX eLand getPrizeLand(cell *c IS(cwt.at)) { eLand l = c->land; if(isElemental(l)) l = laElementalWall; diff --git a/orbs.cpp b/orbs.cpp index 2f0ecfa7..089f09e3 100644 --- a/orbs.cpp +++ b/orbs.cpp @@ -8,6 +8,8 @@ #include "hyper.h" namespace hr { +EX bool orbused[ittypes], lastorbused[ittypes]; + EX bool markOrb(eItem it) { if(!items[it]) return false; orbused[it] = true; @@ -85,7 +87,7 @@ EX bool reduceOrbPower(eItem it, int cap) { return true; } if(items[it] > cap && timerghost) items[it] = cap; - auto_teleport_charges(); + mine::auto_teleport_charges(); return false; } @@ -579,7 +581,7 @@ void teleportTo(cell *dest) { checkmoveO(); movecost(from, dest, 2); - auto_teleport_charges(); + mine::auto_teleport_charges(); } EX void jumpTo(cell *dest, eItem byWhat, int bonuskill IS(0), eMonster dashmon IS(moNone)) { diff --git a/passable.cpp b/passable.cpp new file mode 100644 index 00000000..7b0f5162 --- /dev/null +++ b/passable.cpp @@ -0,0 +1,647 @@ +// Hyperbolic Rogue - passability +// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details + +/** \file checkmove.cpp + * \brief check whether monster/PC can move in the given direction + */ + +#include "hyper.h" + +namespace hr { + +// === 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, flagtype 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; + } + +EX 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; + } + +EX 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(to->move(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(const movei& mi) { + eWall x = mi.t->wall; mi.t->wall = mi.s->wall; mi.s->wall = x; + mi.t->mondir = mi.rev_dir_or(NODIR); + moveItem(mi.s, mi.t, false); + animateMovement(mi, LAYER_BOAT); + } + +EX void moveBoatIfUsingOne(const movei& mi) { + if(mi.s->wall == waBoat && isWatery(mi.t)) moveBoat(mi); + else if(mi.s->wall == waBoat && boatGoesThrough(mi.t) && isFriendly(mi.t) && markEmpathy(itOrbWater)) { + placeWater(mi.t, mi.s); + moveBoat(mi); + } + } + +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 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 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()); + } + +EX int rosewave, rosephase; + +/** current state of the rose scent + * rosemap[c] &3 can be: + * 0 - wave not reached + * 1 - wave expanding + * 2 - wave phase 1 + * 3 - wave phase 2 + */ +EX map rosemap; + +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 bool scentResistant() { + return markOrb(itOrbBeauty) || markOrb(itOrbAether) || markOrb(itOrbShield); + } + + +} diff --git a/pcmove.cpp b/pcmove.cpp new file mode 100644 index 00000000..41d9a880 --- /dev/null +++ b/pcmove.cpp @@ -0,0 +1,1267 @@ +// Hyperbolic Rogue - PC movement +// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details + +/** \file checkmove.cpp + * \brief PC movements + */ + +#include "hyper.h" + +namespace hr { + +EX bool keepLightning = false; + +EX bool seenSevenMines = false; + +/** have we been warned about the Haunted Woods? */ +EX bool hauntedWarning; + +/** is the Survivalist achievement still valid? have we received it? */ +EX bool survivalist, got_survivalist; + +/** last move was invisible */ +EX bool invismove = false; +/** last move was invisible due to Orb of Fish (thus Fish still see you)*/ +EX bool invisfish = false; + +/** if false, make the PC look in direction cwt.spin (after attack); otherwise, make them look the other direction (after move) */ +EX bool flipplayer = true; + +/** Cellwalker describing the single player. Also used temporarily in shmup and multiplayer modes. */ +EX cellwalker cwt; + +EX cell*& singlepos() { return cwt.at; } +EX inline bool singleused() { return !(shmup::on || multi::players > 1); } + +/** should we center the screen on the PC? */ +EX bool playermoved = true; + +/** did the player cheat? how many times? */ +EX int cheater = 0; + +/** lands visited -- unblock some modes */ +EX bool landvisited[landtypes]; + +EX int noiseuntil; // noise until the given turn + +EX void createNoise(int t) { + noiseuntil = max(noiseuntil, turncount+t); + invismove = false; + if(shmup::on) shmup::visibleFor(100 * t); + } + +#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 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); + } + + EX void gainBullPowers() { + items[itOrbShield]++; orbused[itOrbShield] = true; + items[itOrbThorns]++; orbused[itOrbThorns] = true; + items[itOrbHorns]++; orbused[itOrbHorns] = true; + } + + EX 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; + } +EX } + +EX bool checkNeedMove(bool checkonly, bool attacking) { + if(items[itOrbDomination] > 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 cell *lastmove; +eLastmovetype lastmovetype, nextmovetype; +eForcemovetype forcedmovetype; + +#if HDR +struct pcmove { + bool switchplaces; + bool checkonly; + bool errormsgs; + int origd; + bool fmsMove, fmsAttack, fmsActivate; + int d; + int subdir; + bool boatmove; + bool good_tortoise; + flagtype attackflags; + + bool movepcto(); + bool actual_move(); + bool stay(); + bool after_instant(bool kl); + + bool perform_actual_move(); + bool after_move(); + bool perform_move_or_jump(); + bool swing(); + bool boat_move(); + bool after_escape(); + bool move_if_okay(); + bool attack(); + + void tell_why_cannot_attack(); + void tell_why_impassable(); + void handle_friendly_ivy(); + + movei mi, mip; + pcmove() : mi(nullptr, nullptr, 0), mip(nullptr, nullptr, 0) {} + }; +#endif + +EX pcmove pcm; + +EX bool movepcto(int d, int subdir IS(1), bool checkonly IS(false)) { + pcm.checkonly = checkonly; + pcm.d = d; pcm.subdir = subdir; + return pcm.movepcto(); + } + +bool pcmove::movepcto() { + if(dual::state == 1) return dual::movepc(d, subdir, checkonly); + if(d >= 0 && !checkonly && subdir != 1 && subdir != -1) printf("subdir = %d\n", subdir); + mip.t = NULL; + switchplaces = false; + + if(d == MD_USE_ORB) + return targetRangedOrb(multi::whereto[multi::cpid].tgt, roMultiGo); + + 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")); + if(!checkonly) invismove = false; + 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) addMessage("You just cannot stand in place, those roses smell too nicely."); + return false; + } + + gravity_state = gsNormal; + + fmsMove = forcedmovetype == fmSkip || forcedmovetype == fmMove; + fmsAttack = forcedmovetype == fmSkip || forcedmovetype == fmAttack; + fmsActivate = forcedmovetype == fmSkip || forcedmovetype == fmActivate; + + return (d >= 0) ? actual_move() : stay(); + } + +bool pcmove::after_move() { + if(checkonly) return true; + + 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; + } + +bool pcmove::swing() { + sideAttack(cwt.at, d, moPlayer, 0); + animateAttack(mi, LAYER_SMALL); + + if(survivalist && isHaunted(mi.t->land)) + survivalist = false; + mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK); + lastmovetype = lmTree; lastmove = mi.t; + swordAttackStatic(); + + return after_move(); + } + +bool pcmove::after_instant(bool kl) { + keepLightning = kl; + bfs(); + keepLightning = false; + if(multi::players > 1) { multi::whereto[multi::cpid].d = MD_UNDECIDED; return false; } + checkmove(); + return true; + } + +bool pcmove::actual_move() { + origd = d; + if(d >= 0) { + cwt += d; + mirror::act(d, mirror::SPINSINGLE); + d = cwt.spin; + } + if(d != -1 && !checkonly) playermoved = true; + + mi = movei(cwt.at, d); + cell *& c2 = mi.t; + good_tortoise = 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) 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(); + return perform_move_or_jump(); + } + + if(items[itOrbFlash] && try_instant) { + if(checkonly) { nextmovetype = lmInstant; return true; } + if(orbProtection(itOrbFlash)) return true; + activateFlash(); + return after_instant(false); + } + + if(items[itOrbLightning] && try_instant) { + if(checkonly) { nextmovetype = lmInstant; return true; } + if(orbProtection(itOrbLightning)) return true; + activateLightning(); + return after_instant(true); + } + + if(isActivable(c2) && fmsActivate) { + if(checkonly) { nextmovetype = lmInstant; return true; } + activateActiv(c2, true); + return after_instant(false); + } + + if(isPushable(c2->wall) && !c2->monst && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) { + mip = determinePush(cwt, subdir, [c2] (cell *c) { return canPushThumperOn(c, c2, cwt.at); }); + if(mip.d == NO_SPACE) { + if(!checkonly) 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; + } + if(checkonly) { nextmovetype = lmMove; return true; } + addMessage(XLAT("You push %the1.", c2->wall)); + lastmovetype = lmPush; lastmove = cwt.at; + pushThumper(mip); + return perform_actual_move(); + } + + if(c2->item == itHolyGrail && roundTableRadius(c2) < newRoundTableRadius()) { + if(!checkonly) + 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) + return boat_move(); + + if(!c2->monst && cwt.at->wall == waBoat && cwt.at->item != itOrbYendor && 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(mi); + c2->mondir = revhint(cwt.at, d); + if(c2->item) boatmove = !boatmove; + return perform_actual_move(); + } + + return after_escape(); + } + +bool pcmove::boat_move() { + + cell *& c2 = mi.t; + 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) + return after_escape(); + if(!checkonly) + addMessage(XLAT("You cannot go against the current!")); + return false; + } + + if(cwt.at->item == itOrbYendor) { + if(markOrb(itOrbFish) || markOrb(itOrbAether) || gravity_state) + return after_escape(); + if(!checkonly) + 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(mi); + boatmove = true; + return perform_actual_move(); + } + +void pcmove::tell_why_cannot_attack() { + cell *& c2 = mi.t; + 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!")); + } + +bool pcmove::after_escape() { + cell*& c2 = mi.t; + + if(c2->wall == waBigStatue && !c2->monst && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) { + if(!canPushStatueOn(cwt.at)) { + if(!checkonly) { + 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(mi.rev(), LAYER_BOAT); + return perform_actual_move(); + } + + 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; + return swing(); + } + 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; + return swing(); + } + 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.")); + return swing(); + } + return false; + } + else if(c2->monst == moKnight) { + if(!checkonly) camelot::knightFlavorMessage(c2); + return false; + } + else if(c2->monst && (!isFriendly(c2) || c2->monst == moTameBomberbird || isMountable(c2->monst)) + && !(peace::on && !isMultitile(c2->monst) && !good_tortoise)) + return attack(); + else if(!passable(c2, cwt.at, P_USEBOAT | P_ISPLAYER | P_MIRROR | P_MONSTER)) { + if(!checkonly) tell_why_impassable(); + return false; + } + else if(fmsMove) + return move_if_okay(); + + else return false; + } + +bool pcmove::move_if_okay() { + cell*& c2 = mi.t; + if(mine::marked_mine(c2) && !mine::safe() && !checkonly && warningprotection(XLAT("Are you sure you want to step there?"))) + return false; + + if(snakelevel(c2) <= snakelevel(cwt.at)-2) { + bool can_leave = false; + forCellEx(c3, c2) if(passable(c3, c2, P_ISPLAYER | P_MONSTER)) can_leave = true; + if(!can_leave && !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; } + + return perform_actual_move(); + } + +void pcmove::tell_why_impassable() { + cell*& c2 = mi.t; + 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)); + } + } + +bool pcmove::attack() { + auto& c2 = mi.t; + if(!fmsAttack) return false; + + 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)); + return swing(); + } + if(!checkonly) tell_why_cannot_attack(); + return false; + } + + // mip.t=c2 means that the monster is not destroyed and thus + // still counts for lightning in monstersnear + + mip = movei(c2, nullptr, NO_SPACE); + + if(isStunnable(c2->monst) && c2->hitpoints > 1) { + if(monsterPushable(c2)) + mip = determinePush(cwt, subdir, [c2] (cell *c) { return passable(c, c2, P_BLOW); }); + else + mip.t = c2; + } + if(c2->monst == moTroll || c2->monst == moFjordTroll || + c2->monst == moForestTroll || c2->monst == moStormTroll || c2->monst == moVineSpirit) + mip.t = c2; + + if(havePushConflict(mip.t, checkonly)) return false; + + if(!(isWatery(cwt.at) && c2->monst == moWaterElemental) && checkNeedMove(checkonly, true)) + return false; + + if(monstersnear(cwt.at, c2, moPlayer, mip.t, 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; } + + mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK); + + int tk = tkills(); + + if(good_tortoise) { + 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); + } + 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 && (mip.t == c2 || !mip.t)) c2->stuntime = 10; + if(!c2->monst) produceGhost(c2, m, moPlayer); + if(mip.proper()) pushMonster(mip); + animateAttack(mi, LAYER_SMALL); + } + } + + sideAttack(cwt.at, d, moPlayer, tkills() - tk); + lastmovetype = lmAttack; lastmove = c2; + swordAttackStatic(); + return after_move(); + } + +bool pcmove::perform_actual_move() { + cell*& c2 = mi.t; + 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) + camelot::roundTableMessage(c2); + + invismove = (turncount >= noiseuntil) && items[itOrbInvis] > 0; + + if(items[itOrbFire]) { + invismove = false; + if(makeflame(cwt.at, 10, false)) markOrb(itOrbFire); + } + + handle_friendly_ivy(); + + if(items[itOrbDigging]) { + invismove = false; + if(earthMove(mi)) 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); + + return perform_move_or_jump(); + } + +void pcmove::handle_friendly_ivy() { + cell*& c2 = mi.t; + 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(); + } + +bool pcmove::perform_move_or_jump() { + lastmovetype = lmMove; lastmove = cwt.at; + + stabbingAttack(cwt.at, mi.t, moPlayer); + cell *c1 = cwt.at; + cwt += wstep; + if(switchplaces) { + indAnimateMovement(mi, LAYER_SMALL); + indAnimateMovement(mi.rev(), LAYER_SMALL); + commitAnimations(LAYER_SMALL); + } + else + animateMovement(mi, LAYER_SMALL); + current_display->which_copy = current_display->which_copy * adj(mi); + + mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK | mirror::GO); + + playerMoveEffects(c1, mi.t); + + if(mi.t->monst == moFriendlyIvy) mi.t->monst = moNone; + + countLocalTreasure(); + landvisited[cwt.at->land] = true; + afterplayermoved(); + + return after_move(); + } + +bool pcmove::stay() { + 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); + + return after_move(); + } + +#if HDR +inline bool movepcto(const movedir& md) { return movepcto(md.d, md.subdir); } +#endif + + +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; + } + +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 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; iland == laPower || singlepos()->land == laHalloween; + for(int i=0; iland == laPower || playerpos(i)->land == laHalloween)) + return true; + return false; + } + +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); + + mine::uncover_full(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 afterplayermoved() { + pregen(); + if(!racing::on) + setdist(cwt.at, 7 - getDistLimit() - genrange_bonus, NULL); + prairie::treasures(); + if(generatingEquidistant) { + printf("Warning: generatingEquidistant set to true\n"); + generatingEquidistant = false; + } + } + +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"); + } + } + +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 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; 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]--; + } + } + +} \ No newline at end of file diff --git a/shmup.cpp b/shmup.cpp index e7b51e7d..ebcdd659 100644 --- a/shmup.cpp +++ b/shmup.cpp @@ -1155,7 +1155,7 @@ void movePlayer(monster *m, int delta) { cwt.at = c2; afterplayermoved(); if(c2->item && c2->land == laAlchemist) c2->wall = m->base->wall; if(m->base->wall == waRoundTable) - roundTableMessage(c2); + camelot::roundTableMessage(c2); if(c2->wall == waCloud || c2->wall == waMirror) { visibleFor(500); cellwalker cw(c2, 0, false); @@ -1176,7 +1176,7 @@ void movePlayer(monster *m, int delta) { items[itOrbLife] = 0; m->dead = true; } - uncoverMinesFull(c2); + mine::uncover_full(c2); if(isWatery(c2) && isWatery(m->base) && m->inBoat) moveItem(m->base, c2, true); @@ -2622,7 +2622,7 @@ EX void turn(int delta) { #if CAP_INV if(inv::on) inv::compute(); #endif - terracotta(); + terracotta::check(); heat::processfires(); if(havewhat&HF_WHIRLPOOL) whirlpool::move(); if(havewhat&HF_WHIRLWIND) whirlwind::move(); diff --git a/sky.cpp b/sky.cpp index 9c519f4a..217dfacb 100644 --- a/sky.cpp +++ b/sky.cpp @@ -182,7 +182,7 @@ void celldrawer::draw_ceiling() { col = 0x404040; for(int a=0; a<21; a++) if((b >> a) & 1) - col += variant_features[a].color_change; + col += variant::features[a].color_change; col = col & 0x00FF00; break; } diff --git a/system.cpp b/system.cpp index 01de63d0..960384bf 100644 --- a/system.cpp +++ b/system.cpp @@ -203,7 +203,7 @@ EX void initgame() { } if((tactic::on || yendor::on || peace::on) && isCyclic(firstland)) { - anthraxBonus = items[itHolyGrail]; + camelot::anthraxBonus = items[itHolyGrail]; cwt.at->move(0)->land = firstland; if(firstland == laWhirlpool) cwt.at->move(0)->wall = waSea; @@ -358,7 +358,7 @@ EX void initgame() { #if CAP_INV if(inv::on) inv::init(); #endif - auto_teleport_charges(); + mine::auto_teleport_charges(); if(!use_special_land) { if(firstland != (princess::challenge ? laPalace : laIce)) cheater++; } @@ -1125,10 +1125,10 @@ EX void loadsave() { // printf("boxid = %d\n", boxid); if(items[itHolyGrail]) { items[itHolyGrail]--; - knighted = newRoundTableRadius(); + camelot::knighted = newRoundTableRadius(); items[itHolyGrail]++; } - else knighted = 0; + else camelot::knighted = 0; safety = true; if(items[itSavedPrincess] < 0) items[itSavedPrincess] = 0; addMessage(XLAT("Game loaded.")); @@ -1169,7 +1169,7 @@ EX void stop_game() { princess::reviveAt = 0; princess::forceVizier = false; princess::forceMouse = false; - knighted = 0; + camelot::knighted = 0; // items[itGreenStone] = 100; clearMemory(); game_active = false;