From 26fb19e7a87ee924b104ad3f9cd857d06255f7e0 Mon Sep 17 00:00:00 2001 From: Zeno Rogue Date: Sun, 8 Dec 2019 19:17:28 +0100 Subject: [PATCH] subdivided game.cpp; split movepcto into separate functions --- Makefile.simple | 2 +- attack.cpp | 1198 +++++++ basegraph.cpp | 2 +- bigstuff.cpp | 2 +- cell.cpp | 116 + celldrawer.cpp | 8 +- checkmove.cpp | 451 +++ complex2.cpp | 512 ++- control.cpp | 2 +- debug.cpp | 2 +- environment.cpp | 789 +++++ game.cpp | 8453 +---------------------------------------------- graph.cpp | 9 +- help.cpp | 2 +- hyper.cpp | 8 + hyper.h | 1 - items.cpp | 631 ++++ landgen.cpp | 8 +- landlock.cpp | 12 + mapeffects.cpp | 929 ++++++ monstergen.cpp | 6 +- monstermove.cpp | 2083 ++++++++++++ orbgen.cpp | 4 + orbs.cpp | 6 +- passable.cpp | 647 ++++ pcmove.cpp | 1267 +++++++ shmup.cpp | 6 +- sky.cpp | 2 +- system.cpp | 10 +- 29 files changed, 8689 insertions(+), 8479 deletions(-) create mode 100644 attack.cpp create mode 100644 checkmove.cpp create mode 100644 environment.cpp create mode 100644 items.cpp create mode 100644 mapeffects.cpp create mode 100644 monstermove.cpp create mode 100644 passable.cpp create mode 100644 pcmove.cpp 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;