diff --git a/LICENSE b/COPYING
similarity index 94%
rename from LICENSE
rename to COPYING
index 8cdb8451..d60c31a9 100644
--- a/LICENSE
+++ b/COPYING
@@ -1,12 +1,12 @@
- GNU GENERAL PUBLIC LICENSE
- Version 2, June 1991
+ GNU GENERAL PUBLIC LICENSE
+ Version 2, June 1991
- Copyright (C) 1989, 1991 Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
+ Copyright (C) 1989, 1991 Free Software Foundation, Inc.
+ 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
- Preamble
+ Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
@@ -15,7 +15,7 @@ software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
-the GNU Lesser General Public License instead.) You can apply it to
+the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
@@ -55,8 +55,8 @@ patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
-
- GNU GENERAL PUBLIC LICENSE
+
+ GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
@@ -110,7 +110,7 @@ above, provided that you also meet all of these conditions:
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
-
+
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
@@ -168,7 +168,7 @@ access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
-
+
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
@@ -225,7 +225,7 @@ impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
-
+
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
@@ -255,7 +255,7 @@ make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
- NO WARRANTY
+ NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
@@ -277,9 +277,9 @@ YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
- END OF TERMS AND CONDITIONS
-
- How to Apply These Terms to Your New Programs
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
@@ -290,8 +290,8 @@ to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
- {description}
- Copyright (C) {year} {fullname}
+
+ Copyright (C)
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
@@ -303,16 +303,17 @@ the "copyright" line and a pointer to where the full notice is found.
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
- You should have received a copy of the GNU General Public License along
- with this program; if not, write to the Free Software Foundation, Inc.,
- 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ You should have received a copy of the GNU General Public License
+ along with this program; if not, write to the Free Software
+ Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
+
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
- Gnomovision version 69, Copyright (C) year name of author
+ Gnomovision version 69, Copyright (C) year name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
@@ -329,12 +330,11 @@ necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
- {signature of Ty Coon}, 1 April 1989
+ , 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
-library. If this is what you want to do, use the GNU Lesser General
+library. If this is what you want to do, use the GNU Library General
Public License instead of this License.
-
diff --git a/DejaVuSans-Bold.ttf b/DejaVuSans-Bold.ttf
new file mode 100644
index 00000000..08695f23
Binary files /dev/null and b/DejaVuSans-Bold.ttf differ
diff --git a/LICENSE-ogg-vorbis.txt b/LICENSE-ogg-vorbis.txt
new file mode 100644
index 00000000..429b0591
--- /dev/null
+++ b/LICENSE-ogg-vorbis.txt
@@ -0,0 +1,32 @@
+The source code to this library used with SDL_mixer can be found here:
+http://www.libsdl.org/projects/SDL_mixer/libs/
+---
+
+Copyright (c) 2002-2008 Xiph.org Foundation
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions
+are met:
+
+- Redistributions of source code must retain the above copyright
+notice, this list of conditions and the following disclaimer.
+
+- Redistributions in binary form must reproduce the above copyright
+notice, this list of conditions and the following disclaimer in the
+documentation and/or other materials provided with the distribution.
+
+- Neither the name of the Xiph.org Foundation nor the names of its
+contributors may be used to endorse or promote products derived from
+this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION
+OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
diff --git a/LICENSE-sdl.txt b/LICENSE-sdl.txt
new file mode 100644
index 00000000..1669736c
--- /dev/null
+++ b/LICENSE-sdl.txt
@@ -0,0 +1,13 @@
+
+Please distribute this file with the SDL runtime environment:
+
+The Simple DirectMedia Layer (SDL for short) is a cross-platfrom library
+designed to make it easy to write multi-media software, such as games and
+emulators.
+
+The Simple DirectMedia Layer library source code is available from:
+http://www.libsdl.org/
+
+This library is distributed under the terms of the GNU LGPL license:
+http://www.gnu.org/copyleft/lesser.html
+
diff --git a/Makefile b/Makefile
new file mode 100644
index 00000000..74e64b1c
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,10 @@
+CXXFLAGS ?=
+
+hyper: hyper.cpp graph.cpp hyperpoint.cpp geometry.cpp cell.cpp heptagon.cpp game.cpp classes.cpp polygons.cpp language.cpp language-data.cpp achievement.cpp
+ g++ hyper.cpp -o hyper -lSDL -lSDL_ttf -lSDL_mixer -DFHS -Wall -lSDL_gfx ${CXXFLAGS} ${CPPFLAGS} ${LDFLAGS} -lGL -O3
+
+langen: langen.cpp language-cz.cpp language-pl.cpp language-tr.cpp language-ru.cpp
+ g++ langen.cpp -o langen
+
+language-data.cpp: langen
+ ./langen > language-data.cpp
diff --git a/Makefile.mac b/Makefile.mac
new file mode 100644
index 00000000..980a37f3
--- /dev/null
+++ b/Makefile.mac
@@ -0,0 +1,7 @@
+# note: this Makefile for Mac is based on this post. I have no resources to test it myself.
+# http://groups.google.com/group/rec.games.roguelike.development/browse_thread/thread/9c02e09c0195dc16/3cbde3dc4a0b7e4e
+#
+# You will also probably need to configure it yourself a bit to make it work.
+
+hyper: hyper.cpp graph.cpp hyperpoint.cpp geometry.cpp cell.cpp heptagon.cpp game.cpp polygons.cpp classes.cpp
+ g++ -m32 -I/sw/include hyper.cpp -L/sw/lib -lSDL -lSDLMain -lSDL_ttf -lSDL_gfx -framework AppKit -DMAC -O3
diff --git a/Makefile.mgw b/Makefile.mgw
new file mode 100644
index 00000000..aa76f970
--- /dev/null
+++ b/Makefile.mgw
@@ -0,0 +1,11 @@
+hyper.exe: hyper.cpp graph.cpp hyperpoint.cpp geometry.cpp cell.cpp heptagon.cpp game.cpp polygons.cpp classes.cpp hyper.res language-data.cpp
+ g++ -mwindows hyper.cpp hyper.res -o hyper.exe -lSDL -lSDL_mixer -lopengl32 SDL_ttf.dll SDL_gfx.dll -O3
+
+hyper.res: hyper.rc hr-icon.ico
+ windres hyper.rc -O coff -o hyper.res
+
+langen.exe: langen.cpp language-cz.cpp language-pl.cpp language-tr.cpp language-ru.cpp
+ g++ langen.cpp -o langen.exe
+
+language-data.cpp: langen.exe
+ ./langen.exe > language-data.cpp
diff --git a/SDL.dll b/SDL.dll
new file mode 100644
index 00000000..a5da7bbd
Binary files /dev/null and b/SDL.dll differ
diff --git a/SDL_gfx.dll b/SDL_gfx.dll
new file mode 100644
index 00000000..b202a1b7
Binary files /dev/null and b/SDL_gfx.dll differ
diff --git a/SDL_mixer.dll b/SDL_mixer.dll
new file mode 100644
index 00000000..89def763
Binary files /dev/null and b/SDL_mixer.dll differ
diff --git a/SDL_ttf.dll b/SDL_ttf.dll
new file mode 100644
index 00000000..0345453a
Binary files /dev/null and b/SDL_ttf.dll differ
diff --git a/achievement.cpp b/achievement.cpp
new file mode 100644
index 00000000..7cf87e59
--- /dev/null
+++ b/achievement.cpp
@@ -0,0 +1,323 @@
+#define NUMLEADER 26
+
+#define SCORE_UNKNOWN (-1)
+#define NO_SCORE_YET (-2)
+
+int currentscore[NUMLEADER];
+
+const char* leadernames[NUMLEADER] = {
+ "Score", "Diamonds", "Gold", "Spice", "Rubies", "Elixirs",
+ "Shards", "Totems", "Daisies", "Statues", "Feathers", "Sapphires",
+ "Hyperstones", "Time to Win", "Turns to Win",
+ "Time to 10 Hyperstones-62", "Turns to 10 Hyperstones-62", "Orbs of Yendor",
+ "Fern Flowers",
+ "Royal Jellies", "Powerstones", "Silver", "Wine", "Emeralds", "Grimoires",
+ "Holy Grails"
+ };
+
+bool haveLeaderboard(int id);
+
+void upload_score(int id, int v);
+
+string achievementMessage[3];
+int achievementTimer;
+// vector achievementsReceived;
+
+void achievement_log(const char* s, bool euclideanAchievement) {
+ if(cheater) return;
+ if(euclid != euclideanAchievement) return;
+
+ for(int i=0; i= 5)
+ achievement_gain("GOLEM2");
+ if(s == "GOLEM" && current >= 10)
+ achievement_gain("GOLEM3");
+ if(s == "STAB" && current >= 1)
+ achievement_gain("STABBER1");
+ if(s == "STAB" && current >= 2)
+ achievement_gain("STABBER2");
+ if(s == "STAB" && current >= 4)
+ achievement_gain("STABBER3");
+ if(s == "MIRRORKILL" && current-prev >= 1)
+ achievement_gain("MIRRORKILL1");
+ if(s == "MIRRORKILL" && current-prev >= 2)
+ achievement_gain("MIRRORKILL2");
+ if(s == "MIRRORKILL" && current-prev >= 3)
+ achievement_gain("MIRRORKILL3");
+ if(s == "FLASH" && current-prev >= 1)
+ achievement_gain("FLASH1");
+ if(s == "FLASH" && current-prev >= 5)
+ achievement_gain("FLASH2");
+ if(s == "FLASH" && current-prev >= 10)
+ achievement_gain("FLASH3");
+ if(s == "LIGHTNING" && current-prev >= 1)
+ achievement_gain("LIGHTNING1");
+ if(s == "LIGHTNING" && current-prev >= 5)
+ achievement_gain("LIGHTNING2");
+ if(s == "LIGHTNING" && current-prev >= 10)
+ achievement_gain("LIGHTNING3");
+ if(s == "MIRAGE" && current >= 35)
+ achievement_gain("MIRAGE", true);
+ if(s == "ORB" && current >= 10)
+ achievement_gain("ORB3");
+ if(s == "BUG" && current >= 1000)
+ achievement_gain("BUG3");
+ }
+
+int specific_improved = 0;
+int specific_what = 0;
+
+void improve_score(int i, eItem what) {
+#ifdef HAVE_ACHIEVEMENTS
+ if(items[what] && haveLeaderboard(i)) {
+ if(items[what] > currentscore[i] && currentscore[i] != SCORE_UNKNOWN) {
+ specific_improved++; specific_what = what;
+ currentscore[i] = items[what];
+ }
+
+ upload_score(i, items[what]);
+ }
+#endif
+ }
+
+void achievement_final(bool really_final) {
+#ifdef HAVE_ACHIEVEMENTS
+ if(cheater) return;
+ if(euclid) return;
+ int total_improved = 0;
+ specific_improved = 0;
+ specific_what = 0;
+
+ for(int i=1; i<=12; i++) improve_score(i, eItem(i));
+ improve_score(17, itOrbYendor);
+ improve_score(18, itFernFlower);
+ improve_score(19, itRoyalJelly);
+ improve_score(20, itPower);
+ improve_score(21, itSilver);
+ improve_score(22, itWine);
+ improve_score(23, itEmerald);
+ improve_score(24, itGrimoire);
+ improve_score(25, itHolyGrail);
+
+ int tg = gold();
+ if(tg && haveLeaderboard(0)) {
+ if(tg > currentscore[0] && currentscore[0] != SCORE_UNKNOWN) {
+ if(currentscore[0] < 0) total_improved += 2;
+ total_improved++; currentscore[0] = tg;
+ }
+ upload_score(0, tg);
+ }
+
+ if(total_improved >= 2) {
+ addMessage(XLAT("Your total treasure has been recorded in the "LEADERFULL"."));
+ addMessage(XLAT("Congratulations!"));
+ }
+ else if(total_improved && specific_improved >= 2)
+ addMessage(XLAT("You have improved your total high score and %1 specific high scores!", its(specific_improved)));
+ else if(total_improved && specific_improved)
+ addMessage(XLAT("You have improved your total and '%1' high score!", iinf[specific_what].name));
+ else if(total_improved)
+ addMessage(XLAT("You have improved your total high score on "LEADER". Congratulations!"));
+ else if(specific_improved >= 2)
+ addMessage(XLAT("You have improved %1 of your specific high scores!", its(specific_improved)));
+ else if(specific_improved)
+ addMessage(XLAT("You have improved your '%1' high score on "LEADER"!", iinf[specific_what].name));
+#endif
+ }
+
+void achievement_victory(bool hyper) {
+#ifdef HAVE_ACHIEVEMENTS
+ if(cheater) return;
+ if(euclid) return;
+
+ int t = savetime + time(NULL) - timerstart;
+
+ int ih1 = hyper ? 15 : 13;
+ int ih2 = hyper ? 16 : 14;
+
+ int improved = 0;
+ if(currentscore[ih1] == NO_SCORE_YET || currentscore[ih2] == NO_SCORE_YET)
+ improved += 4;
+
+ if(currentscore[ih1] < 0 || currentscore[ih1] > t) {
+ improved++; currentscore[ih1] = t;
+ }
+
+ if(currentscore[ih2] < 0 || currentscore[ih2] > turncount) {
+ improved+=2; currentscore[ih2] = turncount;
+ }
+
+ if(hyper)
+ addMessage(XLAT("You have collected 10 treasures of each type."));
+
+ if(improved) {
+ if(improved >= 4) {
+ if(!hyper) addMessage(XLAT("This is your first victory!"));
+ addMessage(XLAT("This has been recorded in the " LEADERFULL "."));
+ addMessage(XLAT("The faster you get here, the better you are!"));
+ }
+ else if(improved >= 3)
+ addMessage(XLAT("You have improved both your real time and turn count. Congratulations!"));
+ else if(improved == 1)
+ addMessage(XLAT("You have used less real time than ever before. Congratulations!"));
+ else if(improved == 2)
+ addMessage(XLAT("You have used less turns than ever before. Congratulations!"));
+ }
+
+ upload_score(ih1, t);
+ upload_score(ih2, turncount);
+#endif
+ }
+
+void achievement_pump();
+
+void achievement_display() {
+#ifdef HAVE_ACHIEVEMENTS
+ achievement_pump();
+ if(achievementTimer) {
+ int col = (ticks - achievementTimer);
+ if(col > 5000) { achievementTimer = 0; return; }
+ if(col > 2500) col = 5000 - col;
+ col /= 10; col *= 0x10101;
+ displayfr(vid.xres/2, vid.yres/4, 2, vid.fsize * 2, achievementMessage[0], col & 0xFFFF00, 8);
+ displayfr(vid.xres/2, vid.yres/4 + vid.fsize*2, 2, vid.fsize * 2, achievementMessage[1], col, 8);
+ displayfr(vid.xres/2, vid.yres/4 + vid.fsize*4, 2, vid.fsize, achievementMessage[2], col, 8);
+ }
+#endif
+ }
+
diff --git a/achievement.h b/achievement.h
new file mode 100644
index 00000000..339a3cba
--- /dev/null
+++ b/achievement.h
@@ -0,0 +1,30 @@
+// initialize the achievement system.
+void achievement_init();
+
+// close the achievement system.
+void achievement_close();
+
+// gain the achievement with the given name.
+// Only awarded if euclid equals euclideanAchievement.
+void achievement_gain(const char*, bool euclideanAchievement = false);
+
+// gain the achievement for collecting a number of 'it'.
+void achievement_collection(eItem it, int prevgold, int newgold);
+
+// this is used for 'counting' achievements, such as kill 10
+// monsters at the same time.
+void achievement_count(const string& s, int current, int prev);
+
+// gain the victory achievements. Set 'hyper' to true for
+// the Hyperstone victory, and false for the Orb of Yendor victory.
+void achievement_victory(bool hyper);
+
+// gain the final achievements. Called with really=false whenever the user
+// looks at their score, and really=true when the game really ends.
+void achievement_final(bool really);
+
+// display the last achievement gained.
+void achievement_display();
+
+// achievements received this game
+vector achievementsReceived;
diff --git a/cell.cpp b/cell.cpp
new file mode 100644
index 00000000..0c5fa128
--- /dev/null
+++ b/cell.cpp
@@ -0,0 +1,375 @@
+// Hyperbolic Rogue
+// Copyright (C) 2011-2012 Zeno Rogue, see 'hyper.cpp' for details
+
+// cells the game is played on
+
+int fix6(int a) { return (a+96)% 6; }
+
+struct cell : gcell {
+ char type; // 6 for hexagons, 7 for heptagons
+ unsigned char spn[7];
+ heptagon *master;
+ cell *mov[7]; // meaning very similar to heptagon::move
+ };
+
+int cellcount = 0;
+
+void initcell(cell *c); // from game.cpp
+
+cell *newCell(int type, heptagon *master) {
+ cell *c = new cell;
+ cellcount++;
+ c->type = type;
+ c->master = master;
+ for(int i=0; i<7; i++) c->mov[i] = NULL;
+ initcell(c);
+ return c;
+ }
+
+void merge(cell *c, int d, cell *c2, int d2) {
+ c->mov[d] = c2;
+ c->spn[d] = d2;
+ c2->mov[d2] = c;
+ c2->spn[d2] = d;
+ }
+
+typedef unsigned short eucoord;
+
+cell*& euclideanAtCreate(eucoord x, eucoord y);
+
+union heptacoder {
+ heptagon *h;
+ struct { eucoord x; eucoord y; } c;
+ };
+
+void decodeMaster(heptagon *h, eucoord& x, eucoord& y) {
+ heptacoder u;
+ u.h = h; x = u.c.x; y = u.c.y;
+ }
+
+heptagon* encodeMaster(eucoord x, eucoord y) {
+ heptacoder u;
+ u.c.x = x; u.c.y = y;
+ return u.h;
+ }
+
+// very similar to createMove in heptagon.cpp
+cell *createMov(cell *c, int d) {
+
+ if(euclid && !c->mov[d]) {
+ eucoord x, y;
+ decodeMaster(c->master, x, y);
+ for(int dx=-1; dx<=1; dx++)
+ for(int dy=-1; dy<=1; dy++)
+ euclideanAtCreate(x+dx, y+dy);
+ if(!c->mov[d]) { printf("fail!\n"); }
+ }
+
+ if(c->mov[d]) return c->mov[d];
+ else if(c->type == 7) {
+ cell *n = newCell(6, c->master);
+
+ c->mov[d] = n; n->mov[0] = c;
+ c->spn[d] = 0; n->spn[0] = d;
+
+ heptspin hs; hs.h = c->master; hs.spin = d;
+
+ heptspin hs2 = hsstep(hsspin(hs, 3), 3);
+
+ // merge(hs2.h->c7, hs2.spin, n, 2);
+
+ hs2.h->c7->mov[hs2.spin] = n; n->mov[2] = hs2.h->c7;
+ hs2.h->c7->spn[hs2.spin] = 2; n->spn[2] = hs2.spin;
+
+ hs2 = hsstep(hsspin(hs, 4), 4);
+ // merge(hs2.h->c7, hs2.spin, n, 4);
+ hs2.h->c7->mov[hs2.spin] = n; n->mov[4] = hs2.h->c7;
+ hs2.h->c7->spn[hs2.spin] = 4; n->spn[4] = hs2.spin;
+
+ }
+
+ else if(d == 5) {
+ int di = fixrot(c->spn[0]+1);
+ cell *c2 = createMov(c->mov[0], di);
+ merge(c, 5, c2, fix6(c->mov[0]->spn[di] + 1));
+
+ // c->mov[5] = c->mov[0]->mov[fixrot(c->spn[0]+1)];
+ // c->spn[5] = fix6(c->mov[0]->spn[fixrot(c->spn[0]+1)] + 1);
+ }
+
+ else if(d == 1) {
+ int di = fixrot(c->spn[0]-1);
+ cell *c2 = createMov(c->mov[0], di);
+ merge(c, 1, c2, fix6(c->mov[0]->spn[di] - 1));
+
+ // c->mov[1] = c->mov[0]->mov[fixrot(c->spn[0]-1)];
+ // c->spn[1] = fix6(c->mov[0]->spn[fixrot(c->spn[0]-1)] - 1);
+ }
+
+ else if(d == 3) {
+ int di = fixrot(c->spn[2]-1);
+ cell *c2 = createMov(c->mov[2], di);
+ merge(c, 3, c2, fix6(c->mov[2]->spn[di] - 1));
+ // c->mov[3] = c->mov[2]->mov[fixrot(c->spn[2]-1)];
+ // c->spn[3] = fix6(c->mov[2]->spn[fixrot(c->spn[2]-1)] - 1);
+ }
+ return c->mov[d];
+ }
+
+// similar to heptspin from heptagon.cpp
+struct cellwalker {
+ cell *c;
+ int spin;
+ cellwalker(cell *c, int spin) : c(c), spin(spin) {}
+ cellwalker() {}
+ };
+
+void cwspin(cellwalker& cw, int d) {
+ cw.spin = (cw.spin+d + 42) % cw.c->type;
+ }
+
+bool cwstepcreates(cellwalker& cw) {
+ return cw.c->mov[cw.spin] == NULL;
+ }
+
+void cwstep(cellwalker& cw) {
+ createMov(cw.c, cw.spin);
+ int nspin = cw.c->spn[cw.spin];
+ cw.c = cw.c->mov[cw.spin];
+ cw.spin = nspin;
+ }
+
+void eumerge(cell* c1, cell *c2, int s1, int s2) {
+ if(!c2) return;
+ c1->mov[s1] = c2; c1->spn[s1] = s2;
+ c2->mov[s2] = c1; c2->spn[s2] = s1;
+ }
+
+struct euclideanSlab {
+ cell* a[256][256];
+ euclideanSlab() {
+ for(int y=0; y<256; y++) for(int x=0; x<256; x++)
+ a[y][x] = NULL;
+ }
+ ~euclideanSlab() {
+ for(int y=0; y<256; y++) for(int x=0; x<256; x++)
+ if(a[y][x]) delete a[y][x];
+ }
+ };
+
+euclideanSlab* euclidean[256][256];
+
+// map, cell*> euclidean;
+
+cell*& euclideanAt(eucoord x, eucoord y) {
+ euclideanSlab*& slab(euclidean[y>>8][x>>8]);
+ if(!slab) slab = new euclideanSlab;
+ return slab->a[y&255][x&255];
+ }
+
+cell*& euclideanAtCreate(eucoord x, eucoord y) {
+ cell*& c ( euclideanAt(x,y) );
+ if(!c) {
+ c = newCell(6, &origin);
+ c->master = encodeMaster(x,y);
+ euclideanAt(x,y) = c;
+ eumerge(c, euclideanAt(x+1,y), 0, 3);
+ eumerge(c, euclideanAt(x,y+1), 1, 4);
+ eumerge(c, euclideanAt(x-1,y+1), 2, 5);
+ eumerge(c, euclideanAt(x-1,y), 3, 0);
+ eumerge(c, euclideanAt(x,y-1), 4, 1);
+ eumerge(c, euclideanAt(x+1,y-1), 5, 2);
+ }
+ return c;
+ }
+
+
+// initializer (also inits origin from heptagon.cpp)
+void initcells() {
+
+ origin.s = hsOrigin;
+ origin.fjordval = 98;
+ for(int i=0; i<7; i++) origin.move[i] = NULL;
+ origin.alt = NULL;
+ origin.distance = 0;
+ if(euclid)
+ origin.c7 = euclideanAtCreate(0,0);
+ else
+ origin.c7 = newCell(7, &origin);
+
+ // origin.fjordval =
+ }
+
+#define DEBMEM(x) // { x fflush(stdout); }
+
+void clearcell(cell *c) {
+ if(!c) return;
+ DEBMEM ( printf("c%d %p\n", c->type, c); )
+ for(int t=0; ttype; t++) if(c->mov[t]) {
+ DEBMEM ( printf("mov %p [%p] S%d\n", c->mov[t], c->mov[t]->mov[c->spn[t]], c->spn[t]); )
+ if(c->mov[t]->mov[c->spn[t]] != NULL &&
+ c->mov[t]->mov[c->spn[t]] != c) {
+ printf("cell error\n");
+ exit(1);
+ }
+ c->mov[t]->mov[c->spn[t]] = NULL;
+ }
+ DEBMEM ( printf("DEL %p\n", c); )
+ delete c;
+ }
+
+heptagon deletion_marker;
+
+#include
+void clearfrom(heptagon *at) {
+ queue q;
+ q.push(at);
+ at->alt = &deletion_marker;
+//int maxq = 0;
+ while(!q.empty()) {
+ at = q.front();
+// if(q.size() > maxq) maxq = q.size();
+ q.pop();
+ DEBMEM ( printf("from %p\n", at); )
+ for(int i=0; i<7; i++) if(at->move[i]) {
+ if(at->move[i]->alt != &deletion_marker)
+ q.push(at->move[i]);
+ at->move[i]->alt = &deletion_marker;
+ DEBMEM ( printf("!mov %p [%p]\n", at->move[i], at->move[i]->move[at->spin[i]]); )
+ if(at->move[i]->move[at->spin[i]] != NULL &&
+ at->move[i]->move[at->spin[i]] != at) {
+ printf("hept error\n");
+ exit(1);
+ }
+ at->move[i]->move[at->spin[i]] = NULL;
+ at->move[i] = NULL;
+ }
+ DEBMEM ( printf("at %p\n", at); )
+ if(at->c7) {
+ for(int i=0; i<7; i++)
+ clearcell(at->c7->mov[i]);
+ clearcell(at->c7);
+ }
+ DEBMEM ( printf("!DEL %p\n", at); )
+ if(at != &origin) delete at;
+ }
+//printf("maxq = %d\n", maxq);
+ }
+
+void verifycell(cell *c) {
+ int t = c->type;
+ for(int i=0; imov[i];
+ if(c2) {
+ if(t == 7) verifycell(c2);
+ if(c2->mov[c->spn[i]] && c2->mov[c->spn[i]] != c)
+ printf("cell error %p %p\n", c, c2);
+ }
+ }
+ }
+
+void verifycells(heptagon *at) {
+ for(int i=0; i<7; i++) if(at->move[i] && at->spin[i] == 0 && at->move[i] != &origin)
+ verifycells(at->move[i]);
+ for(int i=0; i<7; i++) if(at->move[i] && at->move[i]->move[at->spin[i]] && at->move[i]->move[at->spin[i]] != at) {
+ printf("hexmix error %p %p %p\n", at, at->move[i], at->move[i]->move[at->spin[i]]);
+ }
+ verifycell(at->c7);
+ }
+
+bool ishept(cell *c) {
+ // EUCLIDEAN
+ if(euclid) {
+ eucoord x, y;
+ decodeMaster(c->master, x, y);
+ return (short(y+2*x))%3 == 0;
+ }
+ else return c->type == 7;
+ }
+
+void clearMemory() {
+ extern void clearGameMemory();
+ clearGameMemory();
+ // EUCLIDEAN
+ if(euclid) {
+ for(int y=0; y<256; y++) for(int x=0; x<256; x++)
+ if(euclidean[y][x]) {
+ delete euclidean[y][x];
+ euclidean[y][x] = NULL;
+ }
+ }
+ else {
+ DEBMEM ( verifycells(&origin); )
+ clearfrom(&origin);
+ for(int i=0; itype == 7)
+ return c->master->fjordval >> 3;
+ else {
+ return fjord_hexagon(
+ fjordval(createMov(c,0)),
+ fjordval(createMov(c,2)),
+ fjordval(createMov(c,4))
+ );
+ }
+ }
+
+int eudist(short sx, short sy) {
+ int z0 = abs(sx);
+ int z1 = abs(sy);
+ int z2 = abs(sx+sy);
+ return max(max(z0,z1), z2);
+ }
+
+int celldist(cell *c) {
+ if(euclid) {
+ eucoord x, y;
+ decodeMaster(c->master, x, y);
+ return eudist(x, y);
+ }
+ if(c->type == 7) return c->master->distance;
+ int dx[3];
+ for(int u=0; u<3; u++)
+ dx[u] = createMov(c, u+u)->master->distance;
+ int mi = min(min(dx[0], dx[1]), dx[2]);
+ if(dx[0] > mi+2 || dx[1] > mi+2 || dx[2] > mi+2)
+ return -1; // { printf("cycle error!\n"); exit(1); }
+ if(dx[0] == mi+2 || dx[1] == mi+2 || dx[2] == mi+2)
+ return mi+1;
+ return mi;
+ }
+
+#define ALTDIST_BOUNDARY 99999
+#define ALTDIST_UNKNOWN 99998
+
+// defined in 'game'
+int euclidAlt(short x, short y);
+
+int celldistAlt(cell *c) {
+ if(euclid) {
+ eucoord x, y;
+ decodeMaster(c->master, x, y);
+ return euclidAlt(x, y);
+ }
+ if(c->type == 7) return c->master->alt->distance;
+ int dx[3];
+ for(int u=0; u<3; u++) if(createMov(c, u+u)->master->alt == NULL)
+ return ALTDIST_UNKNOWN;
+ for(int u=0; u<3; u++)
+ dx[u] = createMov(c, u+u)->master->alt->distance;
+ int mi = min(min(dx[0], dx[1]), dx[2]);
+ if(dx[0] > mi+2 || dx[1] > mi+2 || dx[2] > mi+2)
+ return ALTDIST_BOUNDARY; // { printf("cycle error!\n"); exit(1); }
+ if(dx[0] == mi+2 || dx[1] == mi+2 || dx[2] == mi+2)
+ return mi+1;
+ return mi;
+ }
+
+#define GRAIL_FOUND 0x4000
+#define GRAIL_RADIUS_MASK 0x3FFF
diff --git a/classes.cpp b/classes.cpp
new file mode 100644
index 00000000..2b871aee
--- /dev/null
+++ b/classes.cpp
@@ -0,0 +1,696 @@
+// --- help ---
+
+#define MC
+#define M
+
+const char *wormdes =
+MC"These huge monsters normally live below the sand, but your movements have "
+MC"disturbed them. They are too big to be slain with your "
+MC"weapons, but you can defeat them by making them unable to move. "
+M "This also produces some Spice. They move two times slower than you.";
+
+const char *cocytushelp =
+M "This frozen lake is a hellish version of the Icy Land. Now, your body heat melts the floor, not the walls.";
+
+const char *tentdes =
+MC"The tentacles of Cthulhu are like sandworms, but longer. "
+M "They also withdraw one cell at a time, instead of exploding instantly.";
+
+const char *gameboardhelp =
+MC"Ever wondered how some boardgame would look on the hyperbolic plane? "
+MC"I wondered about Go, so I have created this feature. Now you can try yourself!\n"
+MC"Enter = pick up an item (and score), space = clear an item\n"
+M "Other keys place orbs and terrain features of various kinds\n"
+ "In the periodic editor, press 0-4 to switch walls in different ways\n"
+ ;
+
+const char *ivydes =
+MC"A huge plant growing in the Jungle. Each Ivy has many branches, "
+MC"and one branch grows per each of your moves. Branches grow in a clockwise "
+M "order. The root itself is vulnerable.";
+
+const char *slimehelp =
+MC"The Alchemists produce magical potions from pools of blue and red slime. You "
+MC"can go through these pools, but you cannot move from a blue pool to a red "
+MC"pool, or vice versa. Pools containing items count as colorless, and "
+MC"they change color to the PC's previous color when the item is picked up. "
+MC"Slime beasts also have to keep to their own color, "
+MC"but when they are killed, they explode, destroying items and changing "
+M "the color of the slime and slime beasts around them.";
+
+const char *gdemonhelp =
+MC "These creatures are slow, but very powerful... more powerful than you. "
+MC "You need some more experience in demon fighting before you will be able to defeat them. "
+MC "Even then, you will be able to slay this one, but more powerful demons will come...\n\n"
+MC "Each 10 lesser demons you kill, you become powerful enough to kill all the greater "
+M "demons on the screen, effectively turning them into lesser demons.";
+
+const char *ldemonhelp =
+M "These creatures are slow, but they often appear in large numbers.";
+
+const char *trollhelp =
+MC"A big monster from the Living Caves. A dead Troll will be reunited "
+M "with the rocks, causing some walls to grow around its body.";
+
+const char *camelothelp =
+ "The Knights of the Round Table are the greatest warriors of these lands. "
+ "They are not very inventive with names though, as they call each of their "
+ "castles Camelot. "
+ "You are probably worth of joining them, but they will surely give you "
+ "some quest to prove yourself...\n\n"
+ "Each castle contains a single treasure, the Holy Grail, in the center. "
+ "The radius of the Round Table is usually 28, but after you find a Holy Grail "
+ "successfully, each new castle (and each Round Table) you find will be bigger.";
+
+const char *templehelp =
+ "The temple of Cthulhu consists of many concentric circles of columns. "
+ "You will surely encounter many Cultists there, who believe that a pilgrimage "
+ "to the inner circles will bring them closer to Cthulhu himself, and Grimoires "
+ "which surely contain many interesting secrets.\n\n"
+ "The circles in the temple of Cthulhu are actually horocycles. They are "
+ "infinite, and there is an infinite number of them.";
+
+const char *barrierhelp =
+M "Huge, impassable walls which separate various lands.";
+
+const char *cavehelp =
+MC"This cave contains walls which are somehow living. After each turn, each cell "
+MC"counts the number of living wall and living floor cells around it, and if it is "
+MC"currently of a different type than the majority of cells around it, it switches. "
+MC"Items count as three floor cells, and dead Trolls count as five wall cells. "
+M "Some foreign monsters also count as floor or wall cells.\n";
+
+const char *vinehelp =
+ "The Vineyard is filled with vines. A very dense pattern of straight lines here...\n\n"
+ "Vine Beasts and Vine Spirits change vine cells to grass, and vice versa.";
+
+const char *hvinehelp =
+ "A vine is growing here... but only on a half of the cell. How is that even possible?!"
+ "Most monsters cannot move from this cell to the cell containing the other half. "
+ "Vine spirits can move only to the adjacent cells which are also adjacent to the "
+ "other half.";
+
+const char *deadcavehelp =
+ "Somehow, this cave has not received the spark of Life yet.";
+
+const char *foresthelp =
+MC"This forest is quite dry. Beware the bushfires!\n"
+MC"Trees catch fire on the next turn. The temperature of the grass cells "
+MC"rises once per turn for each fire nearby, and becomes fire itself "
+MC"when its temperature has risen 10 times.\n"
+M "You can also cut down the trees. Big trees take two turns to cut down.";
+
+const char *hivehelp =
+ "The Hive is filled with Hyperbugs. They are huge insects which look a bit like "
+ "ants, a bit like bees, and a bit like roaches. "
+ "They live in circular nests, and an army of Hyperbugs will attack any intruder, "
+ "including you and enemy Hyperbugs. Will you manage to get to the "
+ "heart of such a nest, and get the precious Royal Jelly?";
+
+// --- monsters ---
+
+const int motypes = 67;
+
+struct monstertype {
+ char glyph;
+ int color;
+ const char *name;
+ const char *help;
+ };
+
+#define BUGCOLORS 3
+
+monstertype minf[motypes] = {
+ { 0, 0, "none" , NULL},
+ { 'Y', 0x4040FF, "Yeti" ,
+M "A big and quite intelligent monster living in the Icy Land."
+ },
+ { 'W', 0xD08040, "Icewolf" ,
+MC "A nasty predator from the Icy Land. Contrary to other monsters, "
+M "it tracks its prey by their heat."
+ },
+ { 'W', 0xD08040, "Icewolf" , ""},
+ { 'R', 0xFF8000, "Ranger" ,
+MC "Rangers take care of the magic mirrors in the Land of Mirrors. "
+MC "They know that rogues like to break these mirrors... so "
+M "they will attack you!"
+ },
+ { 'T', 0xD0D0D0, "Rock Troll", trollhelp},
+ { 'G', 0x20D020, "Goblin",
+MC "A nasty creature native to the Living Caves. They don't like you "
+M "for some reason."
+ },
+ { 'S', 0xE0E040, "Sand Worm", wormdes },
+ { 's', 0x808000, "Sand Worm Tail", wormdes },
+ { 'S', 0x808000, "Sand Worm W", wormdes },
+ { 'H', 0x80FF00, "Hedgehog Warrior",
+MC "These warriors of the Forest wield exotic weapons called hedgehog blades. "
+MC "These blades protect them from a frontal attack, but they still can be 'stabbed' "
+M "easily by moving from one place next to them to another."
+ },
+ { 'M', 0x806050, "Desert Man",
+M "A tribe of men native to the Desert. They have even tamed the huge Sandworms, who won't attack them."},
+ { 'C', 0x00FFFF, "Ivy Root", ivydes},
+ { 'C', 0xFFFF00, "Active Ivy", ivydes},
+ { 'C', 0x40FF00, "Ivy Branch", ivydes},
+ { 'C', 0x006030, "Dormant Ivy", ivydes},
+ { 'C', 0x800000, "Ivy N", ivydes},
+ { 'C', 0x800000, "Ivy D", ivydes},
+ { 'M', 0x804000, "Giant Ape",
+M "This giant ape thinks that you are an enemy."},
+ { 'B', 0x909000, "Slime Beast", slimehelp},
+ { '@', 0x8080FF, "Mirror Image",
+M "A magical being which copies your movements."
+ },
+ { '@', 0xFF8080, "Mirage",
+MC "A magical being which copies your movements. "
+M "You feel that it would be much more useful in an Euclidean space."
+ },
+ { '@', 0x509050, "Golem",
+M "You can summon these friendly constructs with a magical process."
+ },
+ { '@', 0x509050, "Golem",
+M "You can summon these friendly constructs with a magical process."
+ },
+ { 'E', 0xD09050, "Eagle",
+M "A majestic bird, who is able to fly very fast."
+ },
+ { 'S', 0xFF8080, "Seep",
+M "A monster who is able to live inside the living cave wall."
+ },
+ { 'Z', 0x804000, "Zombie",
+M "A typical Graveyard monster."
+ },
+ { 'G', 0xFFFFFF, "Ghost",
+M "A typical monster from the Graveyard, who moves through walls.\n\n"
+ "There are also wandering Ghosts. They will appear "
+ "if you do not explore any new places for a long time (about 100 turns). "
+ "They can appear anywhere in the game."
+ },
+ { 'N', 0x404040, "Necromancer",
+M "Necromancers can raise ghosts and zombies from fresh graves."
+ },
+ { 'S', 0x404040, "Shadow",
+M "A creepy monster who follows you everywhere in the Graveyard."
+ },
+ { 'T', 0x40E040, "Tentacle", tentdes },
+ { 't', 0x008000, "Tentacle Tail", tentdes },
+ { 'T', 0x008000, "Tentacle W", tentdes },
+ { 'z', 0xC00000, "Tentacle (withdrawing)", tentdes },
+ { 'P', 0xFF8000, "Cultist",
+M "People worshipping Cthulhu. They are very dangerous."
+ },
+ { 'P', 0xFFFF00, "Fire Cultist",
+MC "People worshipping Cthulhu. This one is especially dangerous, "
+M "as he is armed with a weapon which launches fire from afar."
+ },
+ { 'D', 0xFF0000, "Greater Demon", gdemonhelp},
+ { 'D', 0x800000, "Greater Demon", gdemonhelp},
+ { 'd', 0xFF2020, "Lesser Demon", ldemonhelp},
+ { 'd', 0x802020, "Lesser Demon", ldemonhelp},
+ { 'S', 0x2070C0, "Ice Shark",
+M "This dangerous predator has killed many people, and has been sent to Cocytus."
+ },
+ { 'W', 0xFFFFFF, "Running Dog",
+MC "This white dog is able to run all the time. It is the only creature "
+M "able to survive and breed in the Land of Eternal Motion."
+ },
+ { 'S', 0xC00040, "Demon Shark",
+MC "Demons of Hell do not drown when they fall into the lake in Cocytus. "
+M "They turn into demonic sharks, enveloped in a cloud of steam."
+ },
+ { 'S', 0xC00040, "Fire Fairy",
+MC "These fairies would rather burn the forest, than let you get some Fern Flowers. "
+MC "The forest is infinite, after all...\n\n"
+M "Fire Fairies transform into fires when they die."
+ },
+ { 'C', 0x4000C0, "Crystal Sage",
+MC "This being radiates an aura of wisdom. "
+MC "It is made of a beautiful crystal, you would love to take it home. "
+MC "But how is it going to defend itself? Better not to think of it, "
+MC "thinking causes your brain to go hot...\n\n"
+M "Crystal Sages melt at -30 °C, and they can rise the temperature around you from afar."
+ },
+ { 'P', 0x4040C0, "Pikeman",
+ "When Pikemen move, they attack all cells which are now adjacent to them. "
+ "Luckily, they can be killed in the same way.\n\n"
+ "They never move if this would attack their friends."
+ },
+ { 'F', 0xC04040, "Flail Guard",
+ "This guard of the Emerald Mine is wielding a huge flail. "
+ "You cannot attack him directly, as the flail would still hit you then. "
+ "Luckily, you have learned a trick: if you step away from him, "
+ "he will hit himself with the flail!"
+ },
+ { 'M', 0x404040, "Miner",
+ "Miners have special tools for dealing with the Living Cave. "
+ "When they die, these tools activate, destroying the living cave "
+ "around them."
+ },
+ { 'V', 0x421C52, "Vine Beast",
+ "A beast made of vines!\n\n"
+ "Vine Beasts turn into vines when they die."
+ },
+ { 'V', 0x003000, "Vine Spirit",
+ "A spirit living in the vines!\n\n"
+ "Vine Spirits destroy the vines when they die."
+ },
+ { 'T', 0x803030, "Dark Troll",
+ "A Troll without the power of Life."
+ },
+ { 'E', 0xFFFF40, "Earth Elemental",
+ "A rare unliving construct from the Dead Caves. "
+ "It instantly destroys cave walls next to its path, and also leaves "
+ "an impassable wall behind it. You suppose that this impassable wall helps it to "
+ "escape from some threats. You hope you won't meet these threats..."
+ },
+ { 'B', 0xC04040, "Red Hyperbug", hivehelp},
+ { 'B', 0x40C040, "Green Hyperbug", hivehelp},
+ { 'B', 0x4040C0, "Blue Hyperbug", hivehelp},
+ { 'W', 0x404040, "Witch Apprentice",
+ "A Witch without any special powers. But watch out! She will "
+ "pick up any basic Orbs on her path, and use their powers."
+ },
+ { 'W', 0xFF4040, "Speed Witch",
+ "A Witch with a Speed spell. She moves twice as fast as you. Unless you "
+ "have an Orb of Speed too, of course!"
+ },
+ { 'W', 0xFFFFFF, "Flash Witch",
+ "A Witch with a Flash spell. Very dangerous!\n\n"
+ "Luckily, she never uses the spell if it would kill her friends. "
+ "She could destroy an Evil Golem, though."
+ },
+ { 'W', 0xFF8000, "Fire Witch",
+ "A Witch with a Fire spell. She will leave a trail of fire behind her."
+ },
+ { 'W', 0x8080FF, "Winter Witch",
+ "A Witch with a Winter spell. She is able to move through fire."
+ },
+ { 'W', 0x808080, "Aether Witch",
+ "A Witch with an Aether spell. She is able to move through fire and walls."
+ },
+ { '@', 0x905050, "Evil Golem",
+ "Somebody has summoned these evil constructs with a magical process."
+ },
+ { '@', 0x8080FF, "Knight", camelothelp },
+ { 'P', 0xD10000, "Cult Leader",
+ "These Cultists can push the statues, just like you."
+ },
+ { 'B', 0x909000, "Slime Beast", slimehelp},
+ { '@', 0x8080FF, "Knight", camelothelp }, // knight moved
+ { '@', 0x8B4513, "Illusion",
+ "Illusions are targetted "
+ "by most monsters, just like yourself, Thumpers, and your friends."
+ },
+ };
+
+enum eMonster {
+ moNone,
+ moYeti, moWolf, moWolfMoved,
+ moRanger,
+ moTroll, moGoblin,
+ moWorm, moWormtail, moWormwait, moHedge,
+ moDesertman,
+ moIvyRoot, moIvyHead, moIvyBranch, moIvyWait, moIvyNext, moIvyDead,
+ moMonkey,
+ moSlime,
+ moMirror, moMirage, moGolem, moGolemMoved,
+ moEagle, moSeep,
+ moZombie, moGhost, moNecromancer, moShadow,
+ moTentacle, moTentacletail, moTentaclewait, moTentacleEscaping,
+ moCultist, moPyroCultist,
+ moGreater, moGreaterM, moLesser, moLesserM,
+ moShark, moRunDog, moGreaterShark, moFireFairy,
+ moCrystalSage, moLancer, moFlailer, moMiner,
+ moVineBeast, moVineSpirit, moDarkTroll, moEarthElemental,
+ moBug0, moBug1, moBug2,
+ moWitch, moWitchSpeed, moWitchFlash, moWitchFire, moWitchWinter, moWitchGhost,
+ moEvilGolem, moKnight, moCultistLeader, moSlimeNextTurn, moKnightMoved,
+ moIllusion,
+ // temporary
+ moDeadBug, moLightningBolt
+ };
+
+#define NUMWITCH 7
+
+// --- items ----
+
+const int ittypes = 40;
+
+struct itemtype {
+ char glyph;
+ int color;
+ const char *name;
+ const char *help;
+ };
+
+itemtype iinf[ittypes] = {
+ { 0, 0, "none", NULL},
+ { '*', 0xFFFFFF, "Ice Diamond",
+M "Cold white gems, found in the Icy Land."
+ },
+ { '$', 0xFFD700, "Gold",
+MC "An expensive metal from the Living Caves. For some reason "
+M "gold prevents the living walls from growing close to it."
+ },
+ { ';', 0xFF4000, "Spice",
+MC "A rare and expensive substance found in the Desert. "
+M "It is believed to extend life and raise special psychic powers."
+ },
+ { '*', 0xC00000, "Ruby",
+M "A beautiful gem from the Jungle."
+ },
+ { '!', 0xFFFF00, "Elixir of Life",
+MC "A wonderful beverage, apparently obtained by mixing red and blue slime. You definitely feel more "
+M "healthy after drinking it, but you still fell that one hit of a monster is enough to kill you."},
+ { '%', 0xFF00FF, "Shard",
+MC "A piece of a magic mirror, or a mirage cloud, that can be used for magical purposes. Only mirrors and clouds "
+M "in the Land of Mirrors leave these."},
+ { '/', 0xFF8000, "Necromancer's Totem",
+M "These sinister totems contain valuable gems."},
+ { '%', 0x00D000, "Demon Daisy",
+M "These star-shaped flowers native to Hell are a valuable alchemical component."},
+ { '/', 0x00FF00, "Statue of Cthulhu",
+M "This statue is made of materials which cannot be found in your world."},
+ { '*', 0xFF8000, "Phoenix Feather",
+M "One of few things that does not cause the floor in the Land of Eternal Motion to collapse. Obviously they are quite valuable."
+ },
+ { '*', 0x8080FF, "Ice Sapphire",
+M "Cold blue gems, found in the Cocytus."
+ },
+ { '*', 0xEEFF20, "Hyperstone",
+M "These bright yellow gems can be found only by those who have mastered the Crossroads."
+ },
+ { '[', 0x8080FF, "Key",
+MC "That's all you need to unlock the Orb of Yendor! Well... as long as you are able to return to the Orb that this key unlocks...\n\n"
+M "Each key unlocks only the Orb of Yendor which led you to it."
+ },
+ { 'o', 0x306030, "Dead Orb",
+MC "These orbs can be found in the Graveyard. You think that they were once powerful magical orbs, but by now, their "
+M "power is long gone. No way to use them, you could as well simply drop them...\n\n"
+ },
+ { 'o', 0xFF20FF, "Orb of Yendor",
+MC "This wonderful Orb can only be collected by those who have truly mastered this hyperbolic universe, "
+MC "as you need the right key to unlock it. Luckily, your psychic abilities will let you know "
+M "where the key is after you touch the Orb." },
+ { 'o', 0xFFFF00, "Orb of Storms",
+M "This orb can be used to invoke the lightning spell, which causes lightning bolts to shoot from you in all directions."},
+ { 'o', 0xFFFFFF, "Orb of Flash",
+M "This orb can be used to invoke a flash spell, which destroys almost everything in radius of 2."},
+ { 'o', 0x8080FF, "Orb of Winter",
+M "This orb can be used to invoke a wall of ice. It also protects you from fires."},
+ { 'o', 0xFF6060, "Orb of Speed",
+M "This orb can be used to move faster for some time."},
+ { 'o', 0x90B090, "Orb of Life",
+M "This orb can be used to summon friendly golems. It is used instantly when you pick it up."},
+ { 'o', 0x60D760, "Orb of Shielding",
+M "This orb can protect you from damage."},
+ { 'o', 0x606060, "Orb of Earth",
+M "This orb lets you go through living walls. It also has powers in some of the other lands."},
+ { 'o', 0x20FFFF, "Orb of Teleport",
+MC "This orb lets you instantly move to another location on the map. Just click a location which "
+M "is not next to you to teleport there. "
+ },
+ { 'o', 0xA0FF40, "Orb of Safety",
+MC "This orb lets you instantly move to a safe faraway location. Knowing the nature of this strange world, you doubt "
+MC "that you will ever find the way back...\n\n"
+MC "Your game will be saved if you quit the game while the Orb of Safety is still powered.\n\n"
+M "Technical note: as it is virtually impossible to return, this Orb recycles memory used for the world so far (even if you do not use it to save the game). "
+ },
+ { 'o', 0x40C000, "Orb of Thorns",
+M "This orb allows attacking Hedgehog Warriors directly, as well as stabbing other monsters.\n"
+ },
+ { '%', 0x0000FF, "Fern Flower",
+M "This flower brings fortune to the person who finds it.\n"
+ },
+ { '!', 0x900000, "Wine",
+M "Wine grown under hyperbolic sun would be extremely prized in your home location."
+ },
+ { 'o', 0x706070, "Orb of Aether",
+M "This orb allows one to pass through all kinds of walls and chasms."
+ },
+ { '$', 0xFFFFC0, "Silver",
+ "A precious metal from the Dead Caves."
+ },
+ { 'o', 0x005050, "Orb of the Mind",
+ "This orb allows you to instantly kill a non-adjacent enemy by clicking it. "
+ "Each use drains 30 charges."
+ },
+ { '!', 0xE2B227, "Royal Jelly",
+ "This is what Hyperbug Queens eat. Very tasty and healthy."
+ },
+ { '*', 0x60C060, "Emerald",
+ "A precious green gem from the Emerald Mines."
+ },
+ { 'o', 0x421C52, "Orb of Invisibility",
+ "When you have this Orb, most monsters won't see you, unless "
+ "you are standing still, attacking, or picking up items."
+ },
+ { '*', 0xFFFF00, "Powerstone",
+ "A Stone from the Land of Power. You are not sure what it is exactly, but "
+ "as the Powerstones are kept in crystal cabinets, they are surely valuable."
+ },
+ { 'o', 0xFF4000, "Orb of Fire",
+ "When you have this Orb, you will leave a trail of fire behind you."
+ },
+ { '!', 0xFFFF00, "Holy Grail", camelothelp },
+ { '?', 0x00FF00, "Grimoire",
+ "The Grimoires contain many secrets of the Great Old Ones. "
+ "Each new inner circle in the Temple of Cthulhu contains new Grimoires, with new secrets. "
+ "You hope to read them when you return home, and to learn many things. "
+ "The knowledge is valuable to you, but it is rather pointless to try to get "
+ "several copies of the same Grimoire..."
+ },
+ { 'o', 0xFF8000, "Orb of the Dragon",
+ "This Orb allows you to throw fire, just like the Fire Cultists.\n\n"
+ "Each fire drains 5 charges. You are not allowed to throw fire into adjacent cells."
+ },
+ { 'o', 0x8B4513, "Orb of Trickery",
+ "This Orb allows you to create illusions of yourself. Illusions are targetted "
+ "by most monsters, just like yourself, Thumpers, and your friends.\n\n"
+ "Each illusion takes 5 charges to create, and one extra charge "
+ "per turn. You can also click your illusion to take it away, restoring 4 charges.\n\n"
+ "If you have both Orb of Teleport and Orb of Trickery, Illusion is cast "
+ "first -- you can then teleport on your Illusion to switch places with it."
+ },
+ };
+
+enum eItem { itNone, itDiamond, itGold, itSpice, itRuby, itElixir, itShard, itBone, itHell, itStatue,
+ itFeather, itSapphire, itHyperstone, itKey,
+ itGreenStone, itOrbYendor,
+ itOrbLightning, itOrbFlash, itOrbWinter, itOrbSpeed, itOrbLife, itOrbShield, itOrbDigging,
+ itOrbTeleport, itOrbSafety,
+ itOrbThorns, itFernFlower,
+ itWine, itOrbGhost, itSilver, itOrbPsi,
+ itRoyalJelly, itEmerald, itOrbInvis, itPower, itOrbFire,
+ itHolyGrail, itGrimoire,
+ itOrbDragon, itOrbIllusion
+ };
+
+// --- wall types ---
+
+const int walltypes = 37;
+
+struct walltype {
+ char glyph;
+ int color;
+ const char *name;
+ const char *help;
+ };
+
+const char *lakeDesc = "Hell has these lakes everywhere... They are shaped like evil stars, and filled with burning sulphur.";
+
+walltype winf[walltypes] = {
+ { '.', 0xFF00FF, "none", NULL},
+ { '#', 0x8080FF, "ice wall",
+M "Ice Walls melt after some time has passed."
+ },
+ { '#', 0xC06000, "great wall", barrierhelp},
+ { '+', 0x900030, "red slime", slimehelp },
+ { '+', 0x300090, "blue slime", slimehelp },
+ { '#', 0xA0D0A0, "living wall", cavehelp},
+ { '.', 0x306060, "living floor",cavehelp},
+ { '#', 0xD03030, "dead troll", trollhelp},
+ { '#', 0xCDA98F, "sand dune",
+M "A natural terrain feature of the Desert."
+ },
+ { '%', 0xC0C0FF, "Magic Mirror",
+M "You can go inside the Magic Mirror, and produce some mirror images to help you."
+ },
+ { '%', 0xFFC0C0, "Cloud of Mirage",
+MC "Tiny droplets of magical water. You see images of yourself inside them. "
+M "Go inside the cloud, to make these images help you."},
+ { '^', 0x8D694F, "Thumper",
+M "A device that attracts sandworms and other enemies. You need to activate it."},
+ { '^', 0x804000, "Bonfire",
+M "A heap of wood that can be used to start a fire. Everything is already here, you just need to touch it to fire it."
+ },
+ { '+', 0xC0C0C0, "ancient grave",
+M "An ancient grave."
+ },
+ { '+', 0xD0D080, "fresh grave",
+M "A fresh grave. Necromancers like those."
+ },
+ { '#', 0x00FFFF, "column",
+M "A piece of architecture typical to R'Lyeh."
+ },
+ { '=', 0xFFFF00, "lake of sulphur", lakeDesc },
+ { '=', 0xFFFF00, "lake of sulphur", lakeDesc },
+ { '=', 0x000080, "lake",
+M "An impassable lake in Cocytus."
+ },
+ { '_', 0x000080, "frozen lake", cocytushelp },
+ { '>', 0x101010, "chasm",
+M "It was a floor... until something walked on it."
+ },
+ { '>', 0x101010, "chasmD",
+M "It was a floor... until something walked on it."
+ },
+ { '#', 0x60C000, "big tree", foresthelp},
+ { '#', 0x006000, "tree", foresthelp},
+ { '#', 0x421C52*2, "vine", vinehelp},
+ { ':', 0x006000, "vine", hvinehelp},
+ { ';', 0x006000, "vine", hvinehelp},
+ { '^', 0x804000, "partial fire", "This cell is partially on fire."},
+ { '#', 0xA07070, "dead wall", deadcavehelp},
+ { '.', 0x401010, "dead floor",deadcavehelp},
+ { '.', 0x905050, "rubble", "Dead floor, with some rubble."},
+ { '#', 0xD0D010, "weird rock",
+ "A weirdly colored rock. Hyperentomologists claim that the "
+ "Hyperbug armies use these rocks to navigate back home after a victorious battle."
+ },
+ { '#', 0x8080C0, "crystal cabinet",
+ "Witches use these crystal cabinets to protect Powerstones, as well as the more "
+ "expensive Orbs. They are partially protected from thieves: they are too strong "
+ "to be smashed by conventional attacks, and if you try to steal the item "
+ "using an Orb of Aether, your Aether power will be completely drained."
+ },
+ { '#', 0xC0C0C0, "wall of Camelot", camelothelp },
+ { '#', 0xA06000, "Round Table", camelothelp },
+ { '=', 0x0000A0, "moat of Camelot", camelothelp},
+ { '+', 0x606060, "big statue of Cthulhu",
+ "These statues of Cthulhu are too large to carry, and they don't look too "
+ "valuable anyway. Most monsters will never go through them... they probably have "
+ "their reasons. But you can go! When you move into the cell containing "
+ "a statue, you push the statue to the cell you left.\n"
+ }
+ };
+
+enum eWall { waNone, waIcewall, waBarrier, waFloorA, waFloorB, waCavewall, waCavefloor, waDeadTroll, waDune,
+ waMirror, waCloud, waThumper, waBonfire, waAncientGrave, waFreshGrave, waColumn, waSulphurC, waSulphur,
+ waLake, waFrozenLake, waChasm, waChasmD, waDryTree, waWetTree,
+ waVinePlant, waVineHalfA, waVineHalfB, waPartialFire,
+ waDeadwall, waDeadfloor, waDeadfloor2, waWaxWall, waGlass, waCamelot, waRoundTable,
+ waCamelotMoat,
+ waBigStatue,
+ // temporary walls for various purposes
+ waTemporary, waEarthD
+ };
+
+// --- land types ---
+
+const int numLands = 20;
+const int landtypes = numLands + 3;
+
+struct landtype {
+ int color;
+ const char *name;
+ const char *help;
+ };
+
+landtype linf[landtypes] = {
+ { 0xFF00FF, "???" , ""},
+ { 0xC06000, "Great Wall" , ""},
+ { 0xFF0000, "Crossroads" ,
+MC "This land is a quick gateway to other lands. It is very easy to find other lands "
+MC "from the Crossroads. Which means that you find monsters from most other lands here!\n\n"
+MC "As long as you have found enough treasure in their native lands, you can "
+MC "find magical items in the Crossroads. Mirror Land brings mirrors and clouds, "
+MC "and other land types bring magical orbs.\n\n"
+MC "A special treasure, Hyperstone, can be found on the Crossroads, but only "
+MC "after you have found 10 of every other treasure."
+ },
+ { 0xCDA98F, "Desert",
+M "A hot land, full of sand dunes, mysterious Spice, and huge and dangerous sand worms."
+ },
+ { 0x8080FF, "Icy Land",
+MC "A very cold land, full of ice walls. Your mere presence will cause these ice walls to "
+M "melt, even if you don't want it."
+ },
+ { 0x306060, "Living Cave", cavehelp},
+ { 0x00C000, "Jungle",
+M "A land filled with huge ivy plants and dangerous animals."
+ },
+ { 0x900090, "Alchemist Lab", slimehelp},
+ { 0x704070, "Mirror Land",
+M "A strange land which contains mirrors and mirages, protected by Mirror Rangers."},
+ { 0x404070, "Graveyard",
+MC "All the monsters you kill are carried to this strange land, and buried. "
+M "Careless Rogues are also carried here..."
+ },
+ { 0x00FF00, "R'Lyeh",
+M "An ancient sunken city which can be reached only when the stars are right.\n\n"
+ "You can find Temples of Cthulhu in R'Lyeh once you collect five Statues of Cthulhu."
+ },
+ { 0xC00000, "Hell",
+M "A land filled with demons and molten sulphur. Abandon all hope ye who enter here!"
+ },
+ { 0x00FF00, "Cocytus",
+ cocytushelp
+ },
+ { 0xFFFF00, "Land of Eternal Motion",
+MC "A land where you cannot stop, because every piece of floor is extremely unstable. Only monsters who "
+MC "can run forever are able to survive there, and only phoenix feathers are so light that they do not disturb "
+M "the floor.\n"
+ },
+ { 0x008000, "Dry Forest", foresthelp},
+ { 0x0000C0, "Emerald Mine",
+ "Evil people are mining for emeralds in this living cave. "
+ "It does not grow naturally, but it is dug out in a regular "
+ "pattern, which is optimal according to the evil engineers."
+ },
+ { 0x421C52, "Vineyard", foresthelp},
+ { 0x104040, "Dead Cave", deadcavehelp},
+ { 0x705020, "Hive", hivehelp},
+ { 0xFFFF00, "Land of Power",
+ "The Land of Power is filled with everburning fire, magical Orbs, and guarded by "
+ "witches and golems. There are basic orbs lying everywhere, and more prized ones "
+ "are kept in crystal cabinets.\n\n"
+ "Witches are allowed to use all the powers of the "
+ "basic orbs against intruders. These powers never expire, but a Witch "
+ "can use only one power at a time (not counting Orbs of Life).\n\n"
+ "Witches and Golems don't pursue you into other Lands. Also, most Orb powers"
+ "are drained when you leave the Land of Power."
+ },
+ { 0xD0D0D0, "Camelot", camelothelp },
+ { 0xD000D0, "Temple of Cthulhu", templehelp },
+ { 0xE08020, "Game Board", gameboardhelp}
+ };
+
+enum eLand { laNone, laBarrier, laCrossroads, laDesert, laIce, laCaves, laJungle, laAlchemist, laMirror, laGraveyard,
+ laRlyeh, laHell, laCocytus, laMotion, laDryForest, laFjord, laWineyard, laDeadCaves,
+ laHive, laPower, laCamelot, laTemple, laGameBoard };
+
+// cell information for the game
+
+struct gcell {
+ // 8 bits
+ eLand land : 5;
+ unsigned mondir : 3;
+ // 8 bits
+ eMonster monst : 7;
+ unsigned ligon : 1;
+ // 16 bits
+ eWall wall : 6;
+ eLand barleft : 5, barright : 5;
+ // 16 bits
+ eItem item : 6;
+ unsigned cpdist : 5, mpdist : 5;
+ // 14 bits
+ unsigned pathdist : 10, bardir : 4;
+ short tmp;
+ float heat;
+ };
+
+#define NODIR 7
+#define NOBARRIERS 8
diff --git a/fjordgen.cpp b/fjordgen.cpp
new file mode 100644
index 00000000..6b91de10
--- /dev/null
+++ b/fjordgen.cpp
@@ -0,0 +1,407 @@
+// rules for the fjordvalues of heptagons.
+int fjord_heptagon(int parent, int dir) {
+
+ // no fjordgen here
+ if(parent == 0) return 0;
+
+ #define RULE(t1,s1,d,t2,s2) \
+ if(parent == t1*8+s1 && dir == d) return t2*8+s2;
+
+ RULE(8,0,3,12,4)
+ RULE(8,0,4,12,0)
+ RULE(8,0,5,42,0)
+ RULE(8,1,3,40,0)
+ RULE(8,1,4,12,4)
+ RULE(8,1,5,12,0)
+ RULE(8,2,3,34,0)
+ RULE(8,2,4,40,0)
+ RULE(8,2,5,12,4)
+ RULE(8,3,3,10,0)
+ RULE(8,3,4,34,0)
+ RULE(8,3,5,40,0)
+ RULE(8,4,3,32,0)
+ RULE(8,4,4,10,0)
+ RULE(8,4,5,34,0)
+ RULE(8,5,3,42,0)
+ RULE(8,5,4,32,0)
+ RULE(8,5,5,10,0)
+ RULE(8,6,3,12,0)
+ RULE(8,6,4,42,0)
+ RULE(8,6,5,32,0)
+ RULE(9,0,3,13,4)
+ RULE(9,0,4,13,0)
+ RULE(9,0,5,43,0)
+ RULE(9,1,3,41,0)
+ RULE(9,1,4,13,4)
+ RULE(9,1,5,13,0)
+ RULE(9,2,3,35,0)
+ RULE(9,2,4,41,0)
+ RULE(9,2,5,13,4)
+ RULE(9,3,3,11,0)
+ RULE(9,3,4,35,0)
+ RULE(9,3,5,41,0)
+ RULE(9,4,3,33,0)
+ RULE(9,4,4,11,0)
+ RULE(9,4,5,35,0)
+ RULE(9,5,3,43,0)
+ RULE(9,5,4,33,0)
+ RULE(9,5,5,11,0)
+ RULE(9,6,3,13,0)
+ RULE(9,6,4,43,0)
+ RULE(9,6,5,33,0)
+ RULE(10,0,3,14,4)
+ RULE(10,0,4,14,0)
+ RULE(10,0,5,40,3)
+ RULE(10,1,3,42,4)
+ RULE(10,1,4,14,4)
+ RULE(10,1,5,14,0)
+ RULE(10,2,3,32,1)
+ RULE(10,2,4,42,4)
+ RULE(10,2,5,14,4)
+ RULE(10,3,3,8,0)
+ RULE(10,3,4,32,1)
+ RULE(10,3,5,42,4)
+ RULE(10,4,3,34,6)
+ RULE(10,4,4,8,0)
+ RULE(10,4,5,32,1)
+ RULE(10,5,3,40,3)
+ RULE(10,5,4,34,6)
+ RULE(10,5,5,8,0)
+ RULE(10,6,3,14,0)
+ RULE(10,6,4,40,3)
+ RULE(10,6,5,34,6)
+ RULE(11,0,3,15,4)
+ RULE(11,0,4,15,0)
+ RULE(11,0,5,41,3)
+ RULE(11,1,3,43,4)
+ RULE(11,1,4,15,4)
+ RULE(11,1,5,15,0)
+ RULE(11,2,3,33,1)
+ RULE(11,2,4,43,4)
+ RULE(11,2,5,15,4)
+ RULE(11,3,3,9,0)
+ RULE(11,3,4,33,1)
+ RULE(11,3,5,43,4)
+ RULE(11,4,3,35,6)
+ RULE(11,4,4,9,0)
+ RULE(11,4,5,33,1)
+ RULE(11,5,3,41,3)
+ RULE(11,5,4,35,6)
+ RULE(11,5,5,9,0)
+ RULE(11,6,3,15,0)
+ RULE(11,6,4,41,3)
+ RULE(11,6,5,35,6)
+ RULE(12,0,3,8,4)
+ RULE(12,0,4,40,1)
+ RULE(12,0,5,14,2)
+ RULE(12,1,3,12,6)
+ RULE(12,1,4,8,4)
+ RULE(12,1,5,40,1)
+ RULE(12,2,0,14,2)
+ RULE(12,2,1,42,6)
+ RULE(12,2,2,8,3)
+ RULE(12,2,3,12,5)
+ RULE(12,2,4,12,6)
+ RULE(12,2,5,8,4)
+ RULE(12,2,6,40,1)
+ RULE(12,3,3,8,3)
+ RULE(12,3,4,12,5)
+ RULE(12,3,5,12,6)
+ RULE(12,4,3,42,6)
+ RULE(12,4,4,8,3)
+ RULE(12,4,5,12,5)
+ RULE(12,5,3,14,2)
+ RULE(12,5,4,42,6)
+ RULE(12,5,5,8,3)
+ RULE(12,6,3,40,1)
+ RULE(12,6,4,14,2)
+ RULE(12,6,5,42,6)
+ RULE(13,0,3,9,4)
+ RULE(13,0,4,41,1)
+ RULE(13,0,5,15,2)
+ RULE(13,1,3,13,6)
+ RULE(13,1,4,9,4)
+ RULE(13,1,5,41,1)
+ RULE(13,2,3,13,5)
+ RULE(13,2,4,13,6)
+ RULE(13,2,5,9,4)
+ RULE(13,3,3,9,3)
+ RULE(13,3,4,13,5)
+ RULE(13,3,5,13,6)
+ RULE(13,4,3,43,6)
+ RULE(13,4,4,9,3)
+ RULE(13,4,5,13,5)
+ RULE(13,5,3,15,2)
+ RULE(13,5,4,43,6)
+ RULE(13,5,5,9,3)
+ RULE(13,6,3,41,1)
+ RULE(13,6,4,15,2)
+ RULE(13,6,5,43,6)
+ RULE(14,0,3,10,4)
+ RULE(14,0,4,42,5)
+ RULE(14,0,5,12,2)
+ RULE(14,1,3,14,6)
+ RULE(14,1,4,10,4)
+ RULE(14,1,5,42,5)
+ RULE(14,2,0,12,2)
+ RULE(14,2,3,14,5)
+ RULE(14,2,4,14,6)
+ RULE(14,2,5,10,4)
+ RULE(14,3,3,10,3)
+ RULE(14,3,4,14,5)
+ RULE(14,3,5,14,6)
+ RULE(14,4,3,40,2)
+ RULE(14,4,4,10,3)
+ RULE(14,4,5,14,5)
+ RULE(14,5,3,12,2)
+ RULE(14,5,4,40,2)
+ RULE(14,5,5,10,3)
+ RULE(14,6,3,42,5)
+ RULE(14,6,4,12,2)
+ RULE(14,6,5,40,2)
+ RULE(15,0,3,11,4)
+ RULE(15,0,4,43,5)
+ RULE(15,0,5,13,2)
+ RULE(15,1,3,15,6)
+ RULE(15,1,4,11,4)
+ RULE(15,1,5,43,5)
+ RULE(15,2,3,15,5)
+ RULE(15,2,4,15,6)
+ RULE(15,2,5,11,4)
+ RULE(15,3,3,11,3)
+ RULE(15,3,4,15,5)
+ RULE(15,3,5,15,6)
+ RULE(15,4,3,41,2)
+ RULE(15,4,4,11,3)
+ RULE(15,4,5,15,5)
+ RULE(15,5,3,13,2)
+ RULE(15,5,4,41,2)
+ RULE(15,5,5,11,3)
+ RULE(15,6,3,43,5)
+ RULE(15,6,4,13,2)
+ RULE(15,6,5,41,2)
+ RULE(32,0,3,43,2)
+ RULE(32,0,4,33,3)
+ RULE(32,0,5,42,3)
+ RULE(32,1,3,33,5)
+ RULE(32,1,4,43,2)
+ RULE(32,1,5,33,3)
+ RULE(32,2,3,42,1)
+ RULE(32,2,4,33,5)
+ RULE(32,2,5,43,2)
+ RULE(32,3,3,8,1)
+ RULE(32,3,4,42,1)
+ RULE(32,3,5,33,5)
+ RULE(32,4,3,10,6)
+ RULE(32,4,4,8,1)
+ RULE(32,4,5,42,1)
+ RULE(32,5,3,42,3)
+ RULE(32,5,4,10,6)
+ RULE(32,5,5,8,1)
+ RULE(32,6,3,33,3)
+ RULE(32,6,4,42,3)
+ RULE(32,6,5,10,6)
+ RULE(33,0,3,42,2)
+ RULE(33,0,4,32,3)
+ RULE(33,0,5,43,3)
+ RULE(33,1,3,32,5)
+ RULE(33,1,4,42,2)
+ RULE(33,1,5,32,3)
+ RULE(33,2,3,43,1)
+ RULE(33,2,4,32,5)
+ RULE(33,2,5,42,2)
+ RULE(33,3,3,9,1)
+ RULE(33,3,4,43,1)
+ RULE(33,3,5,32,5)
+ RULE(33,4,3,11,6)
+ RULE(33,4,4,9,1)
+ RULE(33,4,5,43,1)
+ RULE(33,5,3,43,3)
+ RULE(33,5,4,11,6)
+ RULE(33,5,5,9,1)
+ RULE(33,6,3,32,3)
+ RULE(33,6,4,43,3)
+ RULE(33,6,5,11,6)
+ RULE(34,0,3,35,4)
+ RULE(34,0,4,41,5)
+ RULE(34,0,5,35,2)
+ RULE(34,1,3,40,4)
+ RULE(34,1,4,35,4)
+ RULE(34,1,5,41,5)
+ RULE(34,2,3,10,1)
+ RULE(34,2,4,40,4)
+ RULE(34,2,5,35,4)
+ RULE(34,3,3,8,6)
+ RULE(34,3,4,10,1)
+ RULE(34,3,5,40,4)
+ RULE(34,4,3,40,6)
+ RULE(34,4,4,8,6)
+ RULE(34,4,5,10,1)
+ RULE(34,5,3,35,2)
+ RULE(34,5,4,40,6)
+ RULE(34,5,5,8,6)
+ RULE(34,6,3,41,5)
+ RULE(34,6,4,35,2)
+ RULE(34,6,5,40,6)
+ RULE(35,0,3,34,4)
+ RULE(35,0,4,40,5)
+ RULE(35,0,5,34,2)
+ RULE(35,1,3,41,4)
+ RULE(35,1,4,34,4)
+ RULE(35,1,5,40,5)
+ RULE(35,2,3,11,1)
+ RULE(35,2,4,41,4)
+ RULE(35,2,5,34,4)
+ RULE(35,3,3,9,6)
+ RULE(35,3,4,11,1)
+ RULE(35,3,5,41,4)
+ RULE(35,4,3,41,6)
+ RULE(35,4,4,9,6)
+ RULE(35,4,5,11,1)
+ RULE(35,5,3,34,2)
+ RULE(35,5,4,41,6)
+ RULE(35,5,5,9,6)
+ RULE(35,6,3,40,5)
+ RULE(35,6,4,34,2)
+ RULE(35,6,5,41,6)
+ RULE(40,0,3,34,5)
+ RULE(40,0,4,10,2)
+ RULE(40,0,5,14,1)
+ RULE(40,1,3,35,3)
+ RULE(40,1,4,34,5)
+ RULE(40,1,5,10,2)
+ RULE(40,2,3,34,1)
+ RULE(40,2,4,35,3)
+ RULE(40,2,5,34,5)
+ RULE(40,3,3,8,5)
+ RULE(40,3,4,34,1)
+ RULE(40,3,5,35,3)
+ RULE(40,4,3,12,3)
+ RULE(40,4,4,8,5)
+ RULE(40,4,5,34,1)
+ RULE(40,5,3,14,1)
+ RULE(40,5,4,12,3)
+ RULE(40,5,5,8,5)
+ RULE(40,6,3,10,2)
+ RULE(40,6,4,14,1)
+ RULE(40,6,5,12,3)
+ RULE(41,0,3,35,5)
+ RULE(41,0,4,11,2)
+ RULE(41,0,5,15,1)
+ RULE(41,1,3,34,3)
+ RULE(41,1,4,35,5)
+ RULE(41,1,5,11,2)
+ RULE(41,2,3,35,1)
+ RULE(41,2,4,34,3)
+ RULE(41,2,5,35,5)
+ RULE(41,3,3,9,5)
+ RULE(41,3,4,35,1)
+ RULE(41,3,5,34,3)
+ RULE(41,4,3,13,3)
+ RULE(41,4,4,9,5)
+ RULE(41,4,5,35,1)
+ RULE(41,5,3,15,1)
+ RULE(41,5,4,13,3)
+ RULE(41,5,5,9,5)
+ RULE(41,6,3,11,2)
+ RULE(41,6,4,15,1)
+ RULE(41,6,5,13,3)
+ RULE(42,0,3,10,5)
+ RULE(42,0,4,32,2)
+ RULE(42,0,5,33,4)
+ RULE(42,1,3,14,3)
+ RULE(42,1,4,10,5)
+ RULE(42,1,5,32,2)
+ RULE(42,2,3,12,1)
+ RULE(42,2,4,14,3)
+ RULE(42,2,5,10,5)
+ RULE(42,3,3,8,2)
+ RULE(42,3,4,12,1)
+ RULE(42,3,5,14,3)
+ RULE(42,4,3,32,6)
+ RULE(42,4,4,8,2)
+ RULE(42,4,5,12,1)
+ RULE(42,5,3,33,4)
+ RULE(42,5,4,32,6)
+ RULE(42,5,5,8,2)
+ RULE(42,6,3,32,2)
+ RULE(42,6,4,33,4)
+ RULE(42,6,5,32,6)
+ RULE(43,0,3,11,5)
+ RULE(43,0,4,33,2)
+ RULE(43,0,5,32,4)
+ RULE(43,1,3,15,3)
+ RULE(43,1,4,11,5)
+ RULE(43,1,5,33,2)
+ RULE(43,2,3,13,1)
+ RULE(43,2,4,15,3)
+ RULE(43,2,5,11,5)
+ RULE(43,3,3,9,2)
+ RULE(43,3,4,13,1)
+ RULE(43,3,5,15,3)
+ RULE(43,4,3,33,6)
+ RULE(43,4,4,9,2)
+ RULE(43,4,5,13,1)
+ RULE(43,5,3,32,4)
+ RULE(43,5,4,33,6)
+ RULE(43,5,5,9,2)
+ RULE(43,6,3,33,2)
+ RULE(43,6,4,32,4)
+ RULE(43,6,5,33,6)
+
+ printf("HEPTAGONAL RULE MISSING for (%d,%d)\n", parent,dir);
+ exit(1);
+ }
+
+// calculate the fjordvalue of a hexagonal cell,
+// based on the fjordvalues of the neighbor heptacells.
+int fjord_hexagon(int a, int b, int c) {
+ // pick the lexicographically smallest representation of the cycle
+ if(b <= a || c dcal; // queue for cpdist
+vector pathq; // queue for pathdist
+
+vector pathqm; // list of monsters to move (pathq restriced to monsters)
+
+vector targets; // list of monster targets
+
+// monsters of specific types to move
+vector worms, ivies, ghosts, golems, mirrors, mirrors2;
+
+vector temps; // temporary changes during bfs
+vector tempval; // restore temps
+
+// a bit nicer way of DFS
+vector reachedfrom;
+
+// additional direction information for BFS algorithms
+// it remembers from where we have got to this location
+// the opposite cell will be added to the queue first,
+// which helps the AI
+vector movesofgood[8];
+
+int first7; // the position of the first monster at distance 7 in dcal
+
+cellwalker cwt; // player character position
+
+bool isIcyLand(cell *c) {
+ return c->land == laIce || c->land == laCocytus;
+ }
+
+void initcell(cell *c) {
+ c->mpdist = INFD; // minimum distance from the player, ever
+ c->cpdist = INFD; // current distance from the player
+ c->pathdist = INFD; // current distance from the player, along paths (used by yetis)
+ c->heat = 0;
+ c->wall = waNone;
+ c->item = itNone;
+ c->monst = moNone;
+ c->bardir = NODIR;
+ c->land = laNone;
+ c->tmp = -1;
+ c->ligon = 0;
+ lastexplore = turncount;
+ }
+
+// 0 = basic treasure, 1 = something else, 2 = power orb
+#define IC_TREASURE 0
+#define IC_OTHER 1
+#define IC_ORB 2
+int itemclass(eItem i) {
+ if(i == 0) return -1;
+ if(i < itKey || i == itFernFlower ||
+ i == itWine || i == itSilver || i == itEmerald || i == itRoyalJelly || i == itPower ||
+ i == itGrimoire)
+ return IC_TREASURE;
+ if(i == itKey || i == itOrbYendor || i == itGreenStone || i == itHolyGrail)
+ return IC_OTHER;
+ return IC_ORB;
+ }
+
+int puregold() {
+ int i = items[itOrbYendor] * 50 + items[itHolyGrail] * 10;
+ for(int t=0; tland;
+ return treasureType(lastland);
+ }
+
+void countLocalTreasure() {
+ eItem i = localTreasureType();
+ currentLocalTreasure = i ? items[i] : 0;
+ }
+
+bool hellUnlocked() {
+ int i = 0;
+ for(int t=0; t= 10)
+ i++;
+ return i >= 9;
+ }
+
+bool hyperstonesUnlocked() {
+ for(int t=1; t mg)
+ mg = items[i];
+ return mg;
+ }
+
+int tkills() {
+ return
+ kills[moYeti] + kills[moWolf] +
+ kills[moRanger] + kills[moTroll] + kills[moGoblin] +
+ kills[moWorm] + kills[moDesertman] + kills[moIvyRoot] +
+ kills[moMonkey] + kills[moEagle] + kills[moSlime] + kills[moSeep] +
+ kills[moRunDog] +
+ kills[moCultist] + kills[moTentacle] + kills[moPyroCultist] +
+ kills[moLesser] + kills[moGreater] +
+ kills[moZombie] + kills[moGhost] + kills[moNecromancer] +
+ kills[moHedge] + kills[moFireFairy] +
+ kills[moCrystalSage] + kills[moShark] + kills[moGreaterShark] +
+ kills[moMiner] + kills[moFlailer] + kills[moLancer] +
+ kills[moVineSpirit] + kills[moVineBeast] +
+ kills[moBug0] + kills[moBug1] + kills[moBug2] +
+ kills[moDarkTroll] + kills[moEarthElemental] +
+ kills[moWitch] + kills[moEvilGolem] + kills[moWitchFlash] + kills[moWitchFire] +
+ kills[moWitchWinter] + kills[moWitchSpeed] +
+ kills[moCultistLeader];
+ }
+
+bool passable(cell *w, cell *from, bool monster_passable, bool mirror_passable) {
+ if(w->wall == waFloorA && from && from->wall == waFloorB && !w->item && !from->item)
+ return false;
+ if(w->wall == waFloorB && from && from->wall == waFloorA && !w->item && !from->item)
+ return false;
+ if(w->wall == waMirror || w->wall == waCloud) return mirror_passable;
+ if((w->wall == waVineHalfA || w->wall == waVineHalfB) && from && from->wall == w->wall)
+ return false;
+ if(w->wall == waNone || w->wall == waFloorA || w->wall == waFloorB ||
+ w->wall == waCavefloor || w->wall == waFrozenLake || w->wall == waVineHalfA ||
+ w->wall == waVineHalfB || w->wall == waDeadfloor || w->wall == waDeadfloor2) {
+ if(w->monst) return monster_passable;
+ return true;
+ }
+ return false;
+ }
+
+bool cellUnstable(cell *c) {
+ return c->land == laMotion && c->wall == waNone;
+ }
+
+bool cellUnstableOrChasm(cell *c) {
+ return c->land == laMotion && (c->wall == waNone || c->wall == waChasm);
+ }
+
+bool cellHalfvine(cell *c) {
+ return c->wall == waVineHalfA || c->wall == waVineHalfB;
+ }
+
+bool thruVine(cell *c, cell *c2) {
+ return cellHalfvine(c) && c2->wall == c->wall;
+ }
+
+bool itemBurns(eItem it) {
+ return it && it != itOrbDragon && it != itOrbFire;
+ }
+
+bool attackThruVine(eMonster m) {
+ return m == moGhost || m == moVineSpirit;
+ }
+
+// target, source
+bool attackingForbidden(cell *c, cell *c2) {
+ return thruVine(c, c2) && !attackThruVine(c2->monst) && !attackThruVine(c->monst);
+ }
+
+bool isFire(cell *w) {
+ return w->wall == waBonfire || w->wall == waPartialFire;
+ }
+
+// eagles can go through lakes, chasms, and slime
+// todo vines?
+bool eaglepassable(cell *w) {
+ if(w->monst) return false;
+ return
+ w->wall == waNone || w->wall == waFloorA || w->wall == waFloorB ||
+ w->wall == waCavefloor || w->wall == waFrozenLake || w->wall == waLake ||
+ w->wall == waDeadfloor || w->wall == waDeadfloor2 || w->wall == waCamelotMoat ||
+ w->wall == waSulphur || w->wall == waSulphurC || w->wall == waChasm;
+ }
+
+bool isActiv(cell *c) {
+ return c->wall == waThumper || c->wall == waBonfire || c->wall == waPartialFire;
+ }
+
+bool isMimic(eMonster m) {
+ return m == moMirror || m == moMirage;
+ }
+
+bool isMimic(cell *c) {
+ return isMimic(c->monst);
+ }
+
+bool isGolemOrKnight(cell *c) {
+ return
+ c->monst == moGolem || c->monst == moGolemMoved ||
+ c->monst == moKnight ||c->monst == moKnightMoved;
+ }
+
+bool isFriendly(cell *c) {
+ return isMimic(c->monst) || isGolemOrKnight(c) || c->monst == moIllusion;
+ }
+
+bool isBug(eMonster m) {
+ return m >= moBug0 && m < moBug0+BUGCOLORS;
+ }
+
+bool isBug(cell *c) {
+ return isBug(c->monst);
+ }
+
+bool isFriendlyOrBug(cell *c) {
+ return isFriendly(c) || isBug(c);
+ }
+
+bool isIvy(cell *c) {
+ return c->monst == moIvyRoot || c->monst == moIvyHead || c->monst == moIvyBranch || c->monst == moIvyWait ||
+ c->monst == moIvyNext || c->monst == moIvyDead;
+ }
+
+bool isDemon(cell *c) {
+ return c->monst == moLesser || c->monst == moLesserM ||
+ c->monst == moGreater || c->monst == moGreaterM;
+ }
+
+bool isWorm(cell *c) {
+ return c->monst == moWorm || c->monst == moWormtail || c->monst == moWormwait ||
+ c->monst == moTentacle || c->monst == moTentacletail || c->monst == moTentaclewait ||
+ c->monst == moTentacleEscaping;
+ }
+
+void useup(cell *c) {
+ c->tmp--;
+ if(c->tmp == 0) c->wall = waNone;
+ }
+
+bool isInactiveEnemy(cell *w) {
+ if(w->monst == moWormtail || w->monst == moWormwait || w->monst == moTentacletail || w->monst == moTentaclewait)
+ return true;
+ if(w->monst == moLesserM || w->monst == moGreaterM)
+ return true;
+ if(w->monst == moIvyRoot || w->monst == moIvyWait || w->monst == moIvyNext || w->monst == moIvyDead)
+ return true;
+ return false;
+ }
+
+bool isActiveEnemy(cell *w, cell *killed) {
+ if(w->monst == moNone || w == killed) return false;
+ if(isFriendly(w)) return false;
+ if(isInactiveEnemy(w)) return false;
+ if(w->monst == moIvyHead || w->monst == moIvyBranch) {
+ while(w != killed && w->mondir != NODIR) w = w->mov[w->mondir];
+ return w != killed;
+ }
+ return true;
+ }
+
+bool isArmedEnemy(cell *w, cell *killed) {
+ return w->monst != moCrystalSage && isActiveEnemy(w, killed);
+ }
+
+bool player_passable(cell *w, cell *from, bool mon) {
+ if(w->monst && !isFriendly(w)) return false;
+ if(isFire(w) && items[itOrbWinter]) return true;
+ if(w->wall == waRoundTable && from->wall != waRoundTable)
+ return true;
+ if(items[itOrbGhost] > 1) return true;
+ return passable(w, from, mon, true);
+ }
+
+bool isHive(eLand l) {
+ return l == laHive;
+ }
+
+bool isKillable(cell *c) {
+ return c->monst != moShadow && !isWorm(c) && c->monst != moGreater && c->monst != moGreaterM
+ && c->monst != moHedge && c->monst != moFlailer;
+ // && !isBug(c->monst);
+ }
+
+bool isKillableSomehow(cell *c) {
+ return isKillable(c)
+ || c->monst == moHedge || c->monst == moLancer || c->monst == moFlailer;
+ }
+
+bool isNeighbor(cell *c1, cell *c2) {
+ for(int i=0; itype; i++) if(c1->mov[i] == c2) return true;
+ return false;
+ }
+
+// how many monsters are near
+eMonster which;
+
+bool mirrorkill(cell *c) {
+ for(int t=0; ttype; t++)
+ if(c->mov[t] && isMimic(c->mov[t]) && c->mov[t]->mov[c->mov[t]->mondir] == c)
+ return true;
+ return false;
+ }
+
+// friendly==false: would the flash kill enemy monsters (i.e., allied with the witch)?
+// friendly==true: would the flash kill friendly monsters (or bugs)?
+bool flashWouldKill(cell *c, bool friendly) {
+ for(int t=0; ttype; t++) {
+ cell *c2 = c->mov[t];
+ for(int u=0; utype; u++) {
+ cell *c3 = c2->mov[u];
+ if(isWorm(c3)) continue; // immune to Flash
+ if(c3->monst == moEvilGolem) continue; // evil golems don't count
+ if(c3 != c && c3->monst) {
+ bool b = isFriendly(c3) || isBug(c3);
+ if(friendly ? b: !b) return true;
+ }
+ }
+ }
+ return false;
+ }
+
+int monstersnear(cell *c, cell *nocount = NULL, bool shielded = true) {
+ int res = 0;
+ bool fast = false;
+ if(shielded) {
+ if(items[itOrbShield] > 1) return 0;
+ fast = (items[itOrbSpeed] && !(items[itOrbSpeed] & 1));
+ }
+
+ for(int t=0; ttype; t++) {
+ cell *c2 = c->mov[t];
+
+ // consider monsters who attack from distance 2
+ if(c2)
+ if(!thruVine(c, c2)) for(int u=2; u<=c2->type-2; u++) {
+ cell *c3 = c2->mov[(c->spn[t]+u) % c2->type];
+ if(!c3) continue;
+ // only these monsters can attack from two spots...
+ if(c3->monst != moLancer && c3->monst != moWitchSpeed && c3->monst != moWitchFlash) continue;
+ // speedwitches can only attack not-fastened monsters,
+ // others can only attack if the move is not fastened
+ if(c3->monst == moWitchSpeed && items[itOrbSpeed]) continue;
+ if(c3->monst != moWitchSpeed && fast) continue;
+ // cannot attack if the immediate cell is impassable (except flashwitches)
+ if(c3->monst != moWitchFlash) {
+ if(!passable(c2, c3, !isArmedEnemy(c2, nocount), false)) continue;
+ if(c2 == cwt.c && items[itOrbFire]) continue;
+ }
+ // flashwitches cannot attack if it would kill another enemy
+ if(c3->monst == moWitchFlash && flashWouldKill(c3, false)) continue;
+ if(nocount && mirrorkill(c3)) continue;
+ res++, which = c3->monst;
+ }
+
+ // consider normal monsters
+ if(c2 && isArmedEnemy(c2, nocount) && c2->monst != moLancer) {
+ if(fast && c2->monst != moWitchSpeed) continue;
+ // they cannot attack through vines
+ if(attackingForbidden(c, c2))
+ continue;
+ // do not count if it would be killed by a mimic
+ if(nocount && mirrorkill(c2)) continue;
+ // do not include stabbed enemies
+ if(
+ (c2->monst == moHedge || (isKillable(c2) && items[itOrbThorns]))
+ && c2->cpdist == 1 && c != cwt.c)
+ continue;
+ res++, which = c2->monst;
+ }
+ }
+
+ return res;
+ }
+
+// reduce c->mpdist to d; also generate the landscape
+
+bool checkBarriersBack(cellwalker& bb, int q=5);
+
+bool checkBarriersFront(cellwalker& bb, int q=5) {
+ if(bb.c->mpdist < BARLEV) return false;
+ if(bb.c->mpdist == BUGLEV) return false;
+ if(bb.c->bardir != NODIR) return false;
+ if(bb.spin == 0) {q--; if(!q) return true; }
+
+ if(1) for(int i=0; i<7; i++) {
+ cellwalker bb2 = bb;
+ cwspin(bb2, i); cwstep(bb2);
+ if(bb2.c->bardir != NODIR) return false;
+ cwspin(bb2, 4); cwstep(bb2);
+ if(bb2.c->bardir != NODIR) return false;
+ }
+
+ cwstep(bb); cwspin(bb, 3); cwstep(bb); cwspin(bb, 3); cwstep(bb);
+ return checkBarriersBack(bb, q);
+ }
+
+bool checkBarriersBack(cellwalker& bb, int q) {
+ // printf("back, %p, s%d\n", bb.c, bb.spin);
+ if(bb.c->mpdist < BARLEV) return false;
+ if(bb.c->mpdist == BUGLEV) return false;
+ if(bb.c->bardir != NODIR) return false;
+ // if(bb.spin == 0 && bb.c->mpdist == INFD) return true;
+
+ if(1) for(int i=0; i<7; i++) {
+ cellwalker bb2 = bb;
+ cwspin(bb2, i); cwstep(bb2);
+ if(bb2.c->bardir != NODIR) return false;
+ cwspin(bb2, 4); cwstep(bb2);
+ if(bb2.c->bardir != NODIR) return false;
+ }
+
+ cwspin(bb, 3); cwstep(bb); cwspin(bb, 4);
+ // bool create = cwstepcreates(bb);
+ cwstep(bb); cwspin(bb, 3);
+ // if(create && bb.spin == 0) return true;
+ return checkBarriersFront(bb, q);
+ }
+
+void setbarrier(cell *c) {
+ c->wall = waBarrier;
+ c->land = laBarrier;
+/*if(isHive(c->barleft) && isHive(c->barright))
+ c->wall = waWaxWall, c->land = c->barleft; */
+ }
+
+void killIvy(cell *c) {
+ if(c->monst == moIvyDead) return;
+ for(int i=0; itype; i++) if(c->mov[i])
+ if(isIvy(c->mov[i]) && c->mov[i]->mondir == c->spn[i])
+ killIvy(c->mov[i]);
+ c->monst = moIvyDead;
+ }
+
+int buildIvy(cell *c, int children, int minleaf) {
+ c->mondir = NODIR;
+ c->monst = moIvyRoot;
+
+ cell *child = NULL;
+
+ int leaf = 0;
+ int leafchild = 0;
+ for(int i=0; itype; i++) {
+ createMov(c, i);
+ if(passable(c->mov[i], c, false, false)) {
+ if(children && !child)
+ child = c->mov[i], leafchild = buildIvy(c->mov[i], children-1, 5);
+ else
+ c->mov[i]->monst = (leaf++) ? moIvyWait : moIvyHead,
+ c->mov[i]->mondir = c->spn[i];
+ }
+ }
+
+ leaf += leafchild;
+ if(leaf < minleaf) {
+ if(child) killIvy(child);
+ killIvy(c);
+ return 0;
+ }
+ else return leaf;
+ }
+
+bool isIcyWall(cell *c) {
+ return c->wall == waNone || c->wall == waIcewall || c->wall == waFrozenLake || c->wall == waLake;
+ }
+
+bool destroyHalfvine(cell *c, eWall newwall = waNone, int tval = 6);
+
+void killMonster(cell *c);
+
+void prespill(cell* c, eWall t, int rad) {
+ // these monsters block spilling
+ if(c->monst == moSeep || c->monst == moVineSpirit || c->monst == moShark ||
+ c->monst == moGreaterShark)
+ return;
+ // turn statues into Slimes!
+ if(c->wall == waBigStatue && t != waNone) {
+ c->wall = waNone;
+ c->monst = moSlimeNextTurn;
+ }
+ // slimedeath spill
+ if((c->monst == moSlime || c->monst == moSlimeNextTurn) && t == waNone) {
+ c->wall = waNone; killMonster(c);
+ }
+ // these walls block spilling completely
+ if(c->wall == waIcewall || c->wall == waBarrier || c->wall == waDeadTroll ||
+ c->wall == waDune || c->wall == waAncientGrave || c->wall == waThumper ||
+ c->wall == waFreshGrave || c->wall == waColumn || c->wall == waPartialFire ||
+ c->wall == waDeadwall || c->wall == waWaxWall || c->wall == waCamelot || c->wall == waRoundTable ||
+ c->wall == waBigStatue)
+ return;
+ // these walls block further spilling
+ if(c->wall == waCavewall || cellUnstable(c) || c->wall == waSulphur ||
+ c->wall == waSulphurC || c->wall == waLake || c->wall == waChasm ||
+ c->wall == waDryTree || c->wall == waWetTree || c->wall == waTemporary ||
+ c->wall == waVinePlant || c->wall == waBonfire || c->wall == waVineHalfA || c->wall == waVineHalfB ||
+ c->wall == waCamelotMoat)
+ t = waTemporary;
+
+ if(c->wall == waSulphur) {
+ // remove the center as it would not look good
+ for(int i=0; itype; i++) if(c->mov[i] && c->mov[i]->wall == waSulphurC)
+ c->mov[i]->wall = waSulphur;
+ }
+
+ destroyHalfvine(c);
+ c->wall = t;
+ // destroy items...
+ c->item = itNone;
+ // block spill
+ if(t == waTemporary) return;
+ // cwt.c->item = itNone;
+ if(rad) for(int i=0; itype; i++) if(c->mov[i])
+ prespill(c->mov[i], t, rad-1);
+ }
+
+void spillfix(cell* c, eWall t, int rad) {
+ if(c->wall == waTemporary) c->wall = t;
+ if(rad) for(int i=0; itype; i++) if(c->mov[i])
+ spillfix(c->mov[i], t, rad-1);
+ }
+
+void spill(cell* c, eWall t, int rad) {
+ prespill(c,t,rad); spillfix(c,t,rad);
+ }
+
+void degradeDemons() {
+ addMessage(XLAT("You feel more experienced in demon fighting!"));
+ int dcs = size(dcal);
+ for(int i=0; imonst == moGreaterM || c->monst == moGreater)
+ achievement_gain("DEMONSLAYER");
+ if(c->monst == moGreaterM) c->monst = moLesserM;
+ if(c->monst == moGreater) c->monst = moLesser;
+ }
+ }
+
+void ivynext(cell *c);
+
+void earthFloor(cell *c) {
+ if(c->monst) return;
+ if(c->wall == waDeadwall) c->wall = waDeadfloor;
+ if(c->wall == waDune) c->wall = waNone;
+ if(c->wall == waAncientGrave || c->wall == waFreshGrave) c->wall = waNone;
+ }
+
+void earthWall(cell *c) {
+ if(c->wall == waDeadfloor || c->wall == waDeadfloor2 || c->wall == waEarthD) {
+ c->item = itNone;
+ c->wall = waDeadwall;
+ }
+ if(c->wall == waNone && c->land == laDesert) {
+ c->item = itNone;
+ c->wall = waDune;
+ }
+ }
+
+bool isWitch(eMonster m) {
+ // evil golems don't count
+ return m >= moWitch && m < moWitch+NUMWITCH-1;
+ }
+
+void killMonster(cell *c) {
+ DEB("killmonster");
+ eMonster m = c->monst;
+
+ if(!m) return;
+ if(isWorm(c)) return;
+ if(m == moShadow) return;
+ if(m == moGolemMoved) m = moGolem;
+ if(m == moKnightMoved) m = moKnight;
+ if(m == moSlimeNextTurn) m = moSlime;
+ if(m == moLesserM) m = moLesser;
+ if(m == moGreater) m = moLesser;
+ if(m == moGreaterM) m = moLesser;
+ kills[m]++;
+
+ if(m == moTroll) {
+ destroyHalfvine(c);
+ c->wall = cellUnstableOrChasm(c) ? waChasm : waDeadTroll;
+ c->item = itNone;
+ for(int i=0; itype; i++) if(c->mov[i]) {
+ c->mov[i]->item = itNone;
+ if(c->mov[i]->wall == waDeadwall || c->mov[i]->wall == waDeadfloor2) c->mov[i]->wall = waCavewall;
+ if(c->mov[i]->wall == waDeadfloor) c->mov[i]->wall = waCavefloor;
+ }
+ }
+ if(m == moMiner) {
+ destroyHalfvine(c);
+ c->wall = cellUnstableOrChasm(c) ? waChasm : waNone;
+ for(int i=0; itype; i++) if(passable(c->mov[i], c, true, true)) {
+ destroyHalfvine(c->mov[i]);
+ c->mov[i]->wall = cellUnstableOrChasm(c) ? waChasm : waNone;
+ if(c->mov[i]->monst == moSlime || c->mov[i]->monst == moSlimeNextTurn)
+ killMonster(c->mov[i]);
+ }
+ }
+ if(m == moVineBeast) {
+ destroyHalfvine(c);
+ c->wall = cellUnstableOrChasm(c) ? waChasm : waVinePlant;
+ c->item = itNone;
+ }
+ if(m == moVineSpirit) {
+ destroyHalfvine(c);
+ c->wall = waNone;
+ }
+ if(isWitch(m) && (c->wall == waBonfire || passable(c, NULL, true, false)) && !c->item) {
+ if(m == moWitchFire) c->item = itOrbFire;
+ if(m == moWitchFlash) c->item = itOrbFlash;
+ if(m == moWitchGhost) c->item = itOrbGhost;
+ if(m == moWitchWinter) c->item = itOrbWinter;
+ if(m == moWitchSpeed) c->item = itOrbSpeed;
+ if(c->wall == waBonfire && itemBurns(c->item))
+ c->item = itNone;
+ }
+ if(m == moFireFairy) {
+ c->wall = cellUnstableOrChasm(c) ? waChasm : waBonfire;
+ c->item = itNone, c->tmp = 50;
+ }
+ if(m == moPyroCultist && c->item == itNone && c->wall != waChasm) {
+ // a reward for killing him before he shoots!
+ c->item = itOrbDragon;
+ }
+ if(m == moSlime) { c->monst = moNone; spill(c, c->wall, 2); }
+ // if(c->monst == moShark) c->heat += 1;
+ // if(c->monst == moGreaterShark) c->heat += 10;
+ if(isIcyLand(c)) {
+ if(m == moCultist) c->heat += 3;
+ if(m == moPyroCultist) c->heat += 6;
+ if(m == moLesser) c->heat += 10;
+ }
+ if(m == moLesser && !(kills[m] % 10))
+ degradeDemons();
+ if(isIvy(c)) {
+ eMonster m = c->monst;
+ /*if((m == moIvyBranch || m == moIvyHead) && c->mov[c->mondir]->monst == moIvyRoot)
+ ivynext(c, moIvyNext); */
+ killIvy(c);
+ if(m == moIvyBranch || m == moIvyHead || m == moIvyNext) {
+ int qty = 0;
+ cell *c2 = c->mov[c->mondir];
+ for(int i=0; itype; i++)
+ if(c2->mov[i]->monst == moIvyWait && c2->mov[i]->mondir == c2->spn[i])
+ qty++;
+ if(c->mov[c->mondir]->monst == moIvyRoot || qty) {
+ c->monst = moIvyNext;
+ /* c->monst = moIvyHead;
+ ivynext(c);
+ if(c->monst == moIvyHead) c->monst = moIvyNext;
+ else c->monst = moNone; */
+ }
+ else {
+ c->mov[c->mondir]->monst = moIvyHead;
+ }
+ }
+ }
+ else c->monst = moNone;
+
+ if(m == moEarthElemental) earthWall(c);
+ }
+
+void burnMonstersAndItems(cell *c, int val, eWall firetype = waBonfire) {
+ if(itemBurns(c->item))
+ addMessage(XLAT("%The1 burns!", c->item)), c->item = itNone;
+ if(c->monst && c->monst != moGhost && !isWorm(c) && c->monst != moShadow) {
+ addMessage(XLAT("%The1 burns!", c->monst));
+ killMonster(c);
+ }
+ c->tmp = val;
+ c->wall = firetype;
+ }
+
+void flameHalfvine(cell *c, int val) {
+ burnMonstersAndItems(c, val);
+ c->wall = waPartialFire;
+ }
+
+bool destroyHalfvine(cell *c, eWall newwall, int tval) {
+ if(cellHalfvine(c)) {
+ for(int t=0; ttype; t++) if(c->mov[t]->wall == c->wall) {
+ if(newwall == waPartialFire) burnMonstersAndItems(c->mov[t], tval, newwall);
+ else c->mov[t]->wall = newwall;
+ }
+ if(newwall == waPartialFire) burnMonstersAndItems(c, tval, newwall);
+ else c->wall = newwall;
+ return true;
+ }
+ return false;
+ }
+
+bool orbChance(cell *c, eLand usual, int chthere, int chcross) {
+ if(c->land == usual) return chthere && rand() % chthere == 0;
+ if(chcross && c->land == laCrossroads) {
+ chcross = (chcross / 50) * (50 + items[itHyperstone]);
+ return rand() % chcross == 0;
+ }
+ return false;
+ }
+
+void buildBarrier(cell *c) {
+ // printf("build barrier at %p", c);
+ if(c->wall == waBarrier) {
+ // printf("-> ready\n");
+ return;
+ }
+// if(c->wall == waWaxWall) return;
+ if(c->mpdist > BARLEV) {
+ // printf("-> too far\n");
+ return; // == INFD) return;
+ }
+
+ cellwalker bb(c, c->bardir); setbarrier(bb.c);
+ cwstep(bb);
+ bb.c->barleft = c->barright;
+ bb.c->barright = c->barleft;
+ setbarrier(bb.c);
+ cwspin(bb, 2); cwstep(bb); bb.c->land = c->barleft; cwstep(bb);
+ cwspin(bb, 2); cwstep(bb); bb.c->land = c->barright; cwstep(bb);
+ cwspin(bb, 2);
+
+ cwspin(bb, 3); cwstep(bb);
+ bb.c->barleft = c->barright;
+ bb.c->barright = c->barleft;
+ setbarrier(bb.c);
+ cwspin(bb, 3); cwstep(bb);
+
+ bb.c->bardir = bb.spin;
+ bb.c->barleft = c->barright;
+ bb.c->barright = c->barleft;
+ // printf("#1\n");
+ buildBarrier(bb.c);
+
+ for(int a=-3; a<=3; a++) if(a) {
+ bb.c = c; bb.spin = c->bardir; cwspin(bb, a); cwstep(bb);
+ bb.c->land = a > 0 ? c->barright : c->barleft;
+ }
+
+ bb.c = c; bb.spin = c->bardir;
+ cwspin(bb, 3); cwstep(bb); cwspin(bb, 4); bb.c->land = c->barright; cwstep(bb); cwspin(bb, 3);
+ bb.c->bardir = bb.spin;
+ bb.c->barleft = c->barright;
+ bb.c->barright = c->barleft;
+ // printf("#2\n");
+ buildBarrier(bb.c);
+ }
+
+void chasmify(cell *c) {
+ c->wall = waChasm; c->item = itNone;
+ int q = 0;
+ cell *c2[10];
+ for(int i=0; itype; i++) if(c->mov[i] && c->mov[i]->mpdist > c->mpdist && cellUnstable(c->mov[i]))
+ c2[q++] = c->mov[i];
+ if(q) {
+ cell *c3 = c2[rand() % q];
+ c3->wall = waChasmD;
+ }
+ }
+
+void chasmifyEarth(cell *c) {
+ int q = 0;
+ int d2[10];
+ for(int i=2; i<=c->type-2; i++) {
+ int j = (i+c->mondir)%c->type;
+ cell *c2 = c->mov[j];
+ if(c2 && c2->mpdist > c->mpdist && (
+ c2->wall == waDeadfloor || c2->wall == waDeadwall ||
+ c2->wall == waDeadfloor2))
+ d2[q++] = j;
+ }
+ if(!q) printf("no further move!\n");
+ if(q) {
+ int d = d2[rand() % q];
+ cell *c3 = c->mov[d];
+ c3->wall = waEarthD;
+ for(int i=0; itype; i++) {
+ cell *c4 = createMov(c3, i);
+ earthFloor(c4);
+ }
+ c3->mondir = c->spn[d];
+ }
+ earthWall(c); c->item = itNone;
+ }
+
+eLand getNewLand(eLand old) {
+ eLand tab[32];
+ int cnt = 0;
+
+/* if(isHive(old) && rand() % 100 < 90) {
+ eLand n = old;
+ while(n == old) n = eLand(laHive0 + rand() % 3);
+ return n;
+ } */
+
+ // return (rand() % 2) ? laMotion : laJungle;
+
+ // the basic lands, always available
+ tab[cnt++] = laCrossroads;
+ tab[cnt++] = laIce;
+ tab[cnt++] = laDesert;
+ tab[cnt++] = laJungle;
+ if(old != laDeadCaves) tab[cnt++] = laCaves;
+
+ // the advanced lands
+ if(gold() >= 30) {
+ tab[cnt++] = laCrossroads;
+ tab[cnt++] = laAlchemist;
+ tab[cnt++] = laMirror;
+ tab[cnt++] = laMotion;
+ }
+
+ if(gold() >= 60) {
+ tab[cnt++] = laCrossroads;
+ tab[cnt++] = laRlyeh;
+ if(old != laDeadCaves && items[itGold] >= 5 && items[itFernFlower] >= 5) tab[cnt++] = laFjord;
+ if(old != laPower) tab[cnt++] = laDryForest;
+ if(old != laPower) tab[cnt++] = laWineyard;
+ if(old != laCaves && old != laFjord && items[itGold] >= 10) tab[cnt++] = laDeadCaves;
+ }
+
+ if(tkills() >= 100) {
+ tab[cnt++] = laGraveyard;
+ if(gold() >= 60) tab[cnt++] = laHive;
+ }
+
+ if(hellUnlocked()) {
+ tab[cnt++] = laCrossroads;
+ tab[cnt++] = laHell;
+ }
+
+ if(items[itHell] >= 10) {
+ tab[cnt++] = laCocytus;
+ if(old != laWineyard && old != laDryForest) tab[cnt++] = laPower;
+ }
+
+ eLand n = old;
+ while(n == old) n = tab[rand() % cnt];
+
+ return n;
+ }
+
+bool notDippingFor(eItem i) {
+ int v = items[i] - currentLocalTreasure;
+ if(v <= 10) return true;
+ if(v >= 20) return false;
+ return v >= rand() % 10 + 10;
+ }
+
+bool notDippingForExtra(eItem i, eItem x) {
+ int v = items[i] - min(items[x], currentLocalTreasure);
+ if(v <= 10) return true;
+ if(v >= 20) return false;
+ return v >= rand() % 10 + 10;
+ }
+
+eLand euland[65536];
+
+eLand switchable(eLand nearland, eLand farland, eucoord c) {
+ if(nearland == laCrossroads) {
+ if(rand() % 4 == 0 && (short(c)%3==0))
+ return laBarrier;
+ return laCrossroads;
+ }
+ else if(nearland == laBarrier) {
+ return getNewLand(farland);
+ }
+ else {
+ if(rand() % 20 == 0 && (short(c)%3==0))
+ return laBarrier;
+ return nearland;
+ }
+ }
+
+eLand getEuclidLand(eucoord c) {
+ if(euland[c]) return euland[c];
+ if(c == 0 || c == eucoord(-1) || c == 1)
+ return euland[c] = laCrossroads;
+ if(euland[eucoord(c-2)] && ! euland[eucoord(c-1)]) getEuclidLand(c-1);
+ if(euland[eucoord(c+2)] && ! euland[eucoord(c+1)]) getEuclidLand(c+1);
+ if(euland[eucoord(c-1)]) return
+ euland[c] = switchable(euland[c-1], euland[eucoord(c-2)], c);
+ if(euland[eucoord(c+1)]) return
+ euland[c] = switchable(euland[c+1], euland[eucoord(c+2)], c);
+ return euland[c] = laCrossroads;
+ }
+
+void createBugArmy(cell *c);
+
+int newRoundTableRadius() {
+ return 28 + 2 * items[itHolyGrail];
+ }
+
+int roundTableRadius(cell *c) {
+ if(euclid) return 28;
+ return c->master->alt->alt->fjordval & GRAIL_RADIUS_MASK;
+ }
+
+bool grailWasFound(cell *c) {
+ if(euclid) return items[itHolyGrail];
+ return c->master->alt->alt->fjordval & GRAIL_FOUND;
+ }
+
+int euclidAlt(short x, short y) {
+ if(euclidland == laTemple) {
+ return max(int(x), x+y);
+ }
+ else return eudist(x-20, y-10);
+ }
+
+int celldistAltRelative(cell *c) {
+ return celldistAlt(c) - roundTableRadius(c);
+ }
+
+heptagon *createAlternateMap(cell *c, int rad, hstate firststate);
+
+void generateAlts(heptagon *h);
+
+void buildCamelotWall(cell *c) {
+ c->wall = waCamelot;
+ for(int i=0; itype; i++) {
+ cell *c2 = createMov(c, i);
+ if(c2->wall == waNone && celldistAlt(c2) > celldistAlt(c) && c2->monst == moNone)
+ c2->wall = waCamelotMoat;
+ }
+ }
+
+// This function generates all lands. Warning: it's very long!
+void setdist(cell *c, int d, cell *from) {
+ DEB("setdist");
+ if(signed(c->mpdist) <= d) return;
+ c->mpdist = d;
+
+ if(d >= BARLEV) {
+ if(!c->land) c->land = from->land;
+ if(c->land == laTemple) c->land = laRlyeh;
+ if(c->land == laCamelot) c->land = laCrossroads;
+
+ if(euclid) {
+ c->land = euclidland;
+ if(euclidland == laCrossroads) {
+ eucoord x, y;
+ decodeMaster(c->master, x, y);
+ c->land = getEuclidLand(y+2*x);
+ }
+ }
+ }
+
+ if(d == BARLEV && !euclid) {
+
+ if(c->type == 7 && rand() % 10000 < (
+ showoff ? (cwt.c->mpdist > 7 ? 0 : 10000) :
+ c->land == laCrossroads ? 5000 : 50) &&
+ c->land != laGameBoard) {
+
+ int bd = 2 + (rand() % 2) * 3;
+
+ cellwalker bb(c, bd);
+ cellwalker bb2 = bb;
+
+ if(checkBarriersFront(bb) && checkBarriersBack(bb2)) {
+ c->bardir = bd;
+ eLand oldland = c->land;
+ eLand newland = getNewLand(oldland);
+ if(showoff) newland = showlist[(showid++) % 10];
+ landcount[newland]++;
+
+ if(bd == 5) c->barleft = oldland, c->barright = newland;
+ else c->barleft = newland, c->barright = oldland;
+ }
+ }
+
+ if(c->land == laCrossroads && c->type == 7 && rand() % 2000 < 200 && items[itEmerald] >= 5) {
+ int rtr = newRoundTableRadius();
+ heptagon *alt = createAlternateMap(c, rtr+14, hsOrigin);
+ if(alt) {
+ alt->fjordval = rtr;
+ }
+ }
+
+ if(c->land == laRlyeh && c->type == 7 && rand() % 2000 < 100 && items[itStatue] >= 5)
+ createAlternateMap(c, 2, hsA);
+
+ if(c->bardir != NODIR && c->bardir != NOBARRIERS)
+ buildBarrier(c);
+ }
+
+ if(d < 10) {
+ explore[d]++;
+ exploreland[d][c->land]++;
+
+ if(d < BARLEV)
+ for(int i=0; itype; i++) {
+ createMov(c, i);
+ setdist(c->mov[i], d+1, c);
+ }
+
+ if(d==8 && c->land == laFjord) {
+ if(euclid) {
+ eucoord x, y;
+ decodeMaster(c->master, x, y);
+ if(((y-2)&7) < 4) c->wall = waCavewall;
+ else c->wall = waCavefloor;
+ }
+ else {
+ int v = fjordval(c);
+ if((v&3) >= 2)
+ c->wall = waCavewall;
+ else c->wall = waCavefloor;
+ }
+ }
+
+ if(d==8 && c->land == laPower) {
+ int v;
+ if(euclid) {
+ eucoord x, y;
+ decodeMaster(c->master, x, y);
+ int y0 = ((short)y) % 6;
+ if(y0<0) y0+=6;
+ if(y0 == 3 || y0 == 4) v=24; else v=0;
+ }
+ else v = fjordval(c);
+ v &= ~3;
+ if((v == 24 || v == 32 || v == 56))
+ c->wall = waBonfire, c->tmp = 9;
+ else if(rand() % 100 < 10) {
+ c->wall = waGlass;
+ eItem protectedItems[17] = {
+ itPower, itPower, itPower, itPower, itPower, itPower,
+ itOrbLightning, itOrbLightning, itOrbThorns, itOrbThorns,
+ itOrbInvis, itOrbInvis,
+ itOrbShield, itOrbTeleport, itOrbPsi,
+ itOrbDragon, itOrbIllusion
+ };
+ c->item = protectedItems[rand() % 17];
+ }
+ }
+
+ if(d==8 && c->land == laWineyard) {
+ if(euclid) {
+ eucoord x, y;
+ decodeMaster(c->master, x, y);
+ int dy = ((short)y)%3; if(dy<0) dy += 3;
+ if(dy == 1) c->wall = waVinePlant;
+ }
+ else {
+ int v = fjordval(c);
+ int w = v / 4;
+ if(w == 9 || w == 10 || w == 7 || w == 8) {
+ c->wall = waVinePlant;
+ }
+ else if(v == 24 || v == 58 || v == 26 || v == 56)
+ c->wall = waVineHalfA;
+ else if(v == 25 || v == 59 || v == 27 || v == 57)
+ c->wall = waVineHalfB;
+ else c->wall = waNone;
+ }
+ }
+
+ if(d == 7 && cellHalfvine(c)) {
+ int i = -1;
+ for(int k=0; ktype; k++) if(c->mov[k] && c->mov[k]->wall == c->wall)
+ i = 0;
+ if(i == -1) c->wall = waNone;
+ }
+
+ // 24-58
+ // 26-56
+ if(d == 9) {
+
+ // if(c->land == laIce && ((celldist(c) % 10) + 10) % 10 == 5)
+ // c->wall = waColumn;
+
+ if(c->land == laIce) if(rand() % 100 < 5 && c->wall != waBarrier) {
+ c->wall = waIcewall;
+ for(int i=0; itype; i++) if(rand() % 100 < 50) {
+ createMov(c, i);
+ setdist(c->mov[i], d+1, c);
+ cell *c2 = c->mov[i];
+ if(c2->wall == waBarrier || c2->land != laIce) continue;
+ c2->wall = waIcewall;
+ for(int j=0; jtype; j++) if(rand() % 100 < 20) {
+ createMov(c2, j);
+ setdist(c->mov[i], d+2, c);
+ cell *c3 = c2->mov[j];
+ if(c3->wall == waBarrier || c3->land != laIce) continue;
+ c3->wall = waIcewall;
+ }
+ }
+ }
+
+ if(c->land == laIce || c->land == laCocytus) if(c->wall == waIcewall && items[itDiamond] >= 5 && rand() % 200 == 1)
+ c->wall = waBonfire, c->tmp = -1;;
+
+ if(c->land == laCaves)
+ c->wall = rand() % 100 < 55 ? waCavewall : waCavefloor;
+
+ if(c->land == laDeadCaves) {
+ int i = rand() % 100;
+ if(i<50) c->wall = waDeadwall;
+ else if(i<55) c->wall = waDeadfloor2;
+ else c->wall = waDeadfloor;
+ }
+
+ if(c->land == laAlchemist)
+ c->wall = rand() % 2 ? waFloorA : waFloorB;
+
+ if(c->land == laDryForest)
+ c->wall = rand() % 100 < 50 ? (rand() % 100 < 50 ? waDryTree : waWetTree) :
+ waNone;
+
+ if(c->land == laGraveyard && ishept(c))
+ c->wall = rand() % 5 ? waAncientGrave : waFreshGrave;
+
+ if(c->land == laRlyeh) {
+ if(rand() % 500 < 5) {
+ for(int i=0; itype; i++) {
+ createMov(c, i);
+ setdist(c->mov[i], d+1, c);
+ if(c->mov[i] && c->mov[i]->land == laRlyeh)
+ c->mov[i]->wall = waColumn;
+ }
+
+ for(int j=0; j<2; j++) {
+ int i = rand() % c->type;
+ if(c->mov[i] && c->mov[i]->land == laRlyeh)
+ c->mov[i]->wall = waNone;
+ }
+ }
+ if(ishept(c) && rand() % 2) c->wall = waColumn;
+ }
+
+ if(c->land == laHell) {
+ if(rand() % 100 < 4) {
+ for(int i=0; itype; i++) {
+ createMov(c, i);
+ setdist(c->mov[i], d+1, c);
+ if(c->mov[i] && c->mov[i]->land == laHell)
+ if(c->mov[i]->wall != waSulphurC)
+ c->mov[i]->wall = waSulphur;
+ }
+
+ c->wall = waSulphurC;
+ }
+ }
+
+ if(c->land == laCocytus) {
+ if(c->wall == waNone) c->wall = waFrozenLake;
+ if(rand() % 100 < 5) {
+ for(int i=0; itype; i++) {
+ createMov(c, i);
+ setdist(c->mov[i], d+1, c);
+ if(c->mov[i] && c->mov[i]->land == laCocytus)
+ c->mov[i]->wall = waLake;
+ }
+
+ c->wall = waLake;
+
+ if(rand() % 500 < 100 + 2 * (items[itSapphire]))
+ c->monst = moShark;
+ }
+ }
+
+ if(isHive(c->land) && rand() % 2000 < 2)
+ createBugArmy(c);
+
+ if((c->land == laCrossroads && !euclid) || c->land == laCamelot)
+ if(euclid || c->master->alt) {
+ int d = celldistAltRelative(c);
+ {
+ eucoord x,y;
+ decodeMaster(c->master, x, y);
+ }
+ if(d <= 14) {
+ if(!euclid) generateAlts(c->master);
+ c->bardir = NOBARRIERS;
+ int d = celldistAltRelative(c);
+ if(d == 10) {
+ if(ishept(c)) buildCamelotWall(c);
+ else {
+ if(!euclid) for(int i=0; i<7; i++) generateAlts(c->master->move[i]);
+ int q = 0;
+ for(int t=0; t<6; t++) {
+ createMov(c, t);
+ if(celldistAltRelative(c->mov[t]) == 10 && !ishept(c->mov[t])) q++;
+ }
+ if(q == 1) buildCamelotWall(c);
+ // towers of Camelot
+ if(q == 0) {
+ c->monst = moKnight;
+ for(int i=0; i<6; i++)
+ buildCamelotWall(c->mov[i]);
+ for(int i=0; itype; i++) if(celldistAltRelative(c->mov[i]) < d)
+ c->mondir = i;
+ }
+ }
+ }
+ if(d == 0) c->wall = waRoundTable;
+ if(celldistAlt(c) == 0) c->item = itHolyGrail;
+ if(d < 0 && rand() % 7000 <= 10 + items[itHolyGrail] * 5) {
+ eMonster m[3] = { moHedge, moLancer, moFlailer };
+ c->monst = m[rand() % 3];
+ }
+ if(d == 1) {
+ // roughly as many knights as table cells
+ if(rand() % 1720 < 1000)
+ c->monst = moKnight;
+ if(!euclid) for(int i=0; i<7; i++) generateAlts(c->master->move[i]);
+ for(int i=0; itype; i++)
+ if(c->mov[i] && celldistAltRelative(c->mov[i]) < d)
+ c->mondir = (i+3) % 6;
+ }
+ if(d <= 10) c->land = laCamelot;
+ if(d > 10 && !euclid) c->land = laCrossroads;
+ }
+ }
+
+ if((c->land == laRlyeh && !euclid) || c->land == laTemple) {
+ if(euclid || (c->master->alt && c->master->alt->distance <= 2)) {
+ if(!euclid) generateAlts(c->master);
+ c->bardir = NOBARRIERS;
+ int d = celldistAlt(c);
+ if(d <= 0) {
+ c->land = laTemple, c->wall = waNone, c->monst = moNone, c->item = itNone;
+ }
+ if(d % TEMPLE_EACH==0) {
+ if(ishept(c))
+ c->wall = waColumn;
+ else {
+ if(!euclid) for(int i=0; i<7; i++) generateAlts(c->master->move[i]);
+ int q = 0;
+ for(int t=0; t<6; t++) {
+ createMov(c, t);
+ if(celldistAlt(c->mov[t]) % TEMPLE_EACH == 0 && !ishept(c->mov[t])) q++;
+ }
+ if(q == 2) c->wall = waColumn;
+ }
+ }
+ }
+ }
+
+ if(isHive(c->land) && rand() % 2000 < 100 && !c->wall && !c->item && !c->monst) {
+ int nww = 0;
+ for(int i=0; itype; i++) if(c->mov[i] && c->mov[i]->wall == waWaxWall)
+ nww++;
+ if(nww == 0) {
+ c->wall = waWaxWall;
+ c->monst = moNone;
+ c->heat = rand() & 0xFFFFFF;
+ }
+ /* for(int i=0; itype; i++) {
+ if(rand() % 6 < 5) {
+ createMov(c,i);
+ cell *c2 = c->mov[i];
+ c2->wall = waWaxWall;
+ c2->monst = moNone;
+ }
+ } */
+ }
+
+ if(c->land == laDesert) {
+ if(rand() % 100 < 5) {
+ for(int i=0; itype; i++) {
+ createMov(c, i);
+ setdist(c->mov[i], d+1, c);
+ if(c->mov[i] && c->mov[i]->land == laDesert)
+ c->mov[i]->wall = waDune;
+ }
+
+ for(int j=0; j<2; j++) {
+ int i = rand() % c->type;
+ if(c->mov[i] && c->mov[i]->land == laDesert)
+ c->mov[i]->wall = waNone;
+ }
+ }
+
+ if(rand() % 300 == 1 && items[itSpice] >= 5)
+ c->wall = waThumper, c->tmp = -1;
+ }
+ }
+
+ if(d == 7 && c->land == laCaves && c->wall == waCavewall && rand() % 5000 < items[itGold])
+ c->monst = moSeep;
+
+ if(d == 7 && c->land == laFjord && c->wall == waCavewall && rand() % 5000 < items[itEmerald])
+ c->monst = moSeep;
+
+ if(d == 7 && c->land == laDeadCaves && c->wall == waDeadwall && rand() % 1000 < items[itSilver])
+ c->monst = moSeep;
+
+ if(d == 7 && c->wall == waVinePlant && rand() % 100 < 10)
+ c->monst = moVineSpirit;
+
+ // repair the buggy walls flowing in from another land, like ice walls flowing into the Caves
+ if(d == 7 && c->land == laCaves && c->wall != waCavewall && c->wall != waCavefloor)
+ c->wall = waCavefloor;
+
+ if(d == 7 && c->land == laDeadCaves && c->wall != waDeadwall &&
+ c->wall != waDeadfloor && c->wall != waDeadfloor2 && c->wall != waEarthD)
+ c->wall = waDeadfloor2;
+
+ if(d == 7 && c->land == laCocytus && c->wall != waFrozenLake && c->wall != waLake && c->wall != waIcewall)
+ c->wall = waFrozenLake;
+
+ if(d == 7 && c->land == laAlchemist && c->wall != waFloorA && c->wall != waFloorB)
+ c->wall = waFloorA;
+
+ if(d == 7 && c->wall == waIcewall && c->land != laIce && c->land != laCocytus)
+ c->wall = waNone;
+
+ if(d == 7 && c->wall == waChasmD) {
+ chasmify(c);
+ }
+
+ if(d == 7 && c->wall == waEarthD) {
+ chasmifyEarth(c);
+ }
+
+ // seal entrances to the Land of Power.
+ if(d == 7 && c->land == laPower && c->type == 7) {
+ bool onwall = false;
+ for(int i=0; i<7; i++) if(c->mov[i] && c->mov[i]->land != laPower)
+ onwall = true;
+ if(!onwall) for(int i=0; i<7; i++) {
+ cell *c2 = c->mov[i];
+ cell *c3 = c2->mov[(c->spn[i] + 3) % 6];
+ if(c3->land != laPower && c3->land != laBarrier)
+ if(c2->wall != waBonfire && c2->wall != waGlass) {
+ if(c->wall == waBonfire) c->monst = moWitchWinter;
+ else if(c->wall == waGlass) c->monst = moWitchGhost;
+ else c->monst = moEvilGolem;
+ }
+ }
+ }
+
+ if(d == 7 && passable(c, NULL, false, false)) {
+ if(c->land == laBarrier) c->wall = waBarrier;
+ }
+
+ if(d == 7 && passable(c, NULL, false, false) && !safety) {
+ int hard = items[itOrbYendor] * 5;
+
+ if(c->land == laIce) {
+ if(rand() % 5000 < 100 + 2 * (kills[moYeti] + kills[moWolf]) && notDippingFor(itDiamond))
+ c->item = itDiamond;
+ if(rand() % 8000 < 2 * (items[itDiamond] + hard))
+ c->monst = rand() % 2 ? moYeti : moWolf;
+ }
+ if(c->land == laCaves) {
+ if(rand() % 5000 < 100 + 2 * (kills[moTroll] + kills[moGoblin]) && notDippingFor(itGold))
+ c->item = itGold;
+ if(rand() % 8000 < 10 + 2 * (items[itGold] + hard))
+ c->monst = rand() % 2 ? moTroll : moGoblin;
+ }
+ if(c->land == laDeadCaves) {
+ if(rand() % 5000 < 100 + 2 * (kills[moDarkTroll] + kills[moEarthElemental]) && notDippingFor(itSilver))
+ c->item = itSilver;
+ if(rand() % 16000 < (items[itSilver] + hard)) {
+ c->monst = moEarthElemental;
+ for(int i=0; itype; i++) {
+ cell *c2 = c->mov[i];
+ earthFloor(c2);
+ }
+ for(int i=0; itype; i++) if(c->mov[i]->mpdist < c->mpdist) c->mondir = i;
+ chasmifyEarth(c); c->wall = waDeadfloor2;
+ }
+ else if(rand() % 8000 < 60 + 8 * (items[itSilver] + hard)) {
+ if(rand() % 100 < 25) {
+ }
+ else c->monst = rand() % 2 ? moDarkTroll : moGoblin;
+ }
+ }
+ if(c->land == laDesert) {
+ if(rand() % 5000 < 100 + 2 * (kills[moWorm] + kills[moDesertman]) && notDippingFor(itSpice))
+ c->item = itSpice;
+ if(rand() % 8000 < 10 + 2 * (items[itSpice] + hard))
+ c->monst = rand() % 2 ? moWorm : moDesertman,
+ c->mondir = NODIR;
+ }
+ if(c->land == laWineyard) {
+ if(rand() % 5000 < 100 + 2 * (kills[moVineBeast] + kills[moVineSpirit]) && notDippingFor(itWine))
+ c->item = itWine;
+ if(rand() % 8000 < 12 * (items[itWine] + hard))
+ c->monst = moVineBeast;
+ }
+ if(c->land == laFjord) {
+ if(rand() % 1000 < 100 + 2 * (kills[moMiner] + kills[moLancer] + kills[moFlailer]) && notDippingFor(itEmerald)) {
+ // do not destroy walls!
+ bool ok = true;
+ for(int i=0; itype; i++) if(c->mov[i]->wall == waCavewall) ok = false;
+ if(ok) c->item = itEmerald;
+ }
+ if(rand() % 8000 < 50 + 10 * (items[itEmerald] + hard)) {
+ static eMonster fjordmonsters[4] = { moHedge, moLancer, moFlailer, moMiner };
+ c->monst = fjordmonsters[rand() % 4];
+ }
+ }
+ if(c->land == laJungle) {
+ if(rand() % 5000 < 25 + 2 * (kills[moIvyRoot] + kills[moMonkey]) && notDippingFor(itRuby))
+ c->item = itRuby;
+ if(rand() % 15000 < 5 + 1 * (items[itRuby] + hard))
+ c->monst = moMonkey;
+ else if(rand() % 80000 < 5 + items[itRuby] + hard)
+ c->monst = moEagle;
+ else if(ishept(c) && rand() % 4000 < 300 + items[itRuby]) {
+ bool hard = rand() % 100 < 25;
+ if(hard ? buildIvy(c, 1, 9) : buildIvy(c, 0, c->type))
+ c->item = itRuby;
+ }
+ }
+ if(c->land == laAlchemist) {
+ if(rand() % 5000 < 25 + min(kills[moSlime], 200) && notDippingFor(itElixir))
+ c->item = itElixir;
+ else if(rand() % 3500 < 10 + items[itElixir] + hard)
+ c->monst = moSlime;
+ }
+ if(c->land == laPower) {
+ if(rand() % (5000+50*items[itPower]) < 1200) {
+ eItem powerorbs[6] = {
+ itOrbFlash, itOrbSpeed, itOrbFire, itOrbWinter, itOrbGhost, itOrbLife};
+ c->item = powerorbs[rand() % 6];
+ }
+ else if(c->type == 6 && rand() % 5000 < 10)
+ c->wall = rand() % 2 ? waMirror : waCloud;
+ else if(rand() % 1000 < 10 + (items[itPower] ? 10:0) + (items[itPower] + hard))
+ c->monst = eMonster(moWitch + rand() % NUMWITCH);
+ }
+ if(c->land == laCrossroads) {
+ if(c->type == 6 && rand() % 8000 < 120 && items[itShard] >= 10)
+ c->wall = rand() % 2 ? waMirror : waCloud;
+ else if(hyperstonesUnlocked() && rand() % 5000 < tkills() && notDippingFor(itHyperstone))
+ c->item = itHyperstone;
+ else if(rand() % 4000 < items[itHyperstone] + 2 * items[itHolyGrail]) {
+ // only interesting monsters here!
+ static eMonster m[14] = {
+ moWorm, moTroll, moEagle,
+ moLesser, moGreater, moPyroCultist, moGhost,
+ moFireFairy, moIvyRoot, moTentacle, moHedge,
+ moLancer, moFlailer, moVineBeast
+ };
+ eMonster cm = m[rand() % 14];
+ if(cm == moIvyRoot) buildIvy(c, 0, c->type);
+ else c->monst = cm;
+ if(cm == moWorm || cm == moTentacle)
+ c->mondir = NODIR;
+ }
+ }
+ if(c->land == laMirror) {
+ if(c->type == 6 && rand() % 5000 < 120 && notDippingFor(itShard))
+ c->wall = rand() % 2 ? waMirror : waCloud;
+ if(rand() % 12000 < 8 + items[itShard] + hard)
+ c->monst = moRanger;
+ else if(rand() % 60000 < 8 + items[itShard] + hard)
+ c->monst = moEagle;
+ }
+ if(c->land == laGraveyard) {
+ if(rand() % 5000 < 30 + 2 * (kills[moZombie] + kills[moGhost] + kills[moNecromancer]) && notDippingFor(itBone))
+ c->item = itBone;
+ if(rand() % 20000 < 10 + items[itBone] + hard + (kills[moZombie] + kills[moGhost] + kills[moNecromancer])/60) {
+ eMonster grm[6] = { moZombie, moZombie, moZombie, moGhost, moGhost, moNecromancer};
+ c->monst = grm[rand() % 6];
+ }
+ }
+ if(c->land == laRlyeh) {
+ if(rand() % 5000 < 30 + 2 * (kills[moCultist] + kills[moTentacle] + kills[moPyroCultist]) && notDippingFor(itStatue))
+ c->item = itStatue;
+ if(rand() % 8000 < 5 + items[itStatue] + hard)
+ c->monst = moTentacle, c->item = itStatue, c->mondir = NODIR;
+ else if(rand() % 12000 < 5 + items[itStatue] + hard)
+ c->monst = rand() % 3 ? ((rand() % 40 < items[itStatue]-25) ? moCultistLeader : moCultist) : moPyroCultist;
+ else if(rand() % 8000 < 5 + items[itStatue] + hard && c->type == 6) {
+ for(int t=0; ttype; t++) {
+ if(c->mov[t] && c->mov[t]->monst == moNone && (c->wall == waNone || c->wall == waColumn))
+ c->mov[t]->wall = ishept(c->mov[t]) ? waColumn : waNone;
+ if(c->mov[t]->wall == waColumn)
+ c->mov[t]->item = itNone;
+ }
+ if(buildIvy(c, 0, 3)) c->item = itStatue;
+ }
+ }
+ if(c->land == laTemple) {
+ // depth!
+ int d = celldistAlt(c);
+ // remember: d is negative
+ if(d % TEMPLE_EACH == 0) {
+ if(rand() % 5000 < 20 - 2*d)
+ c->monst = moTentacle, c->mondir = NODIR;
+ }
+ else {
+ int d0 = d % TEMPLE_EACH;
+ if(d0<0) d0=-d0;
+ if(rand() % 100 < 30) // && d0 != 1 && d0 != TEMPLE_EACH-1)
+ c->wall = waBigStatue;
+ else if(rand() % 20000 < -d)
+ c->monst = rand() % 3 ? moCultist : moPyroCultist;
+ else if(rand() % 100000 < -d)
+ c->monst = moCultistLeader;
+ else if(rand() % 5000 < 250)
+ c->item = itGrimoire;
+ else if(rand() % 5000 < 10 && -d > TEMPLE_EACH * 10)
+ c->item = itOrbDragon;
+ }
+ }
+ if(c->land == laDryForest) {
+ if(rand() % 5000 < 100 + 2 * (kills[moFireFairy]*2 + kills[moHedge]) && notDippingFor(itFernFlower))
+ c->item = itFernFlower;
+ if(rand() % 4000 < 40 + items[itFernFlower] + hard)
+ c->monst = moHedge;
+ else if(rand() % 8000 < 2 * items[itFernFlower] + hard)
+ c->monst = moFireFairy;
+ }
+ if(c->land == laHell) {
+ if(rand() % 1500 < 30 + (kills[moCultist] + kills[moTentacle]) && notDippingFor(itHell))
+ c->item = itHell;
+ if(rand() % 8000 < 40 + items[itHell] + hard)
+ c->monst = moLesser;
+ else if(rand() % 24000 < 40 + items[itHell] + hard)
+ c->monst = moGreater;
+ }
+ if(c->land == laCocytus) {
+ if(rand() % 5000 < 100 + 2 * (kills[moShark] + kills[moGreaterShark] + kills[moCrystalSage]) && notDippingFor(itSapphire))
+ c->item = itSapphire;
+ if(rand() % 5000 < 2 * (items[itSapphire] + hard)) {
+ eMonster ms[3] = { moYeti, moGreaterShark, moCrystalSage };
+ c->monst = ms[rand() % 3];
+ if(c->monst == moGreaterShark) c->wall = waLake;
+ }
+ }
+ if(c->land == laMotion) {
+ if(rand() % 1500 < 30 + (kills[moRunDog]) && notDippingFor(itFeather))
+ c->item = itFeather;
+ if(rand() % 20000 < 25 + items[itFeather] + hard) {
+ c->monst = moRunDog;
+ // preset the movement direction
+ // this will make the dog go in the direction of the center,
+ // if the player is unreachable/invisible
+ for(int d=0; dtype; d++) if(c->mov[d] == from) {
+ c->mondir = (d+3) % c->type;
+ }
+ chasmify(c);
+ }
+ }
+ if(c->land == laHive) {
+ if(isHive(c->land) && rand() % 6000 < (items[itRoyalJelly] + hard-15))
+ c->monst = eMonster(moBug0 + rand() % BUGCOLORS);
+
+ /* if(rand() % 1500 < 30 + (kills[moBug0] + kills[moBug1] + kills[moBug2]) && notDippingFor(itRoyalJelly))
+ c->item = itRoyalJelly; */
+ /* if(rand() % 2000 < 2)
+ c->monst = eMonster(moBug0 + rand() % 3); */
+ }
+ if(!c->item && c->wall != waCloud && c->wall != waMirror) {
+ if(orbChance(c, laJungle, 1200, 1500) && items[itRuby] >= 10)
+ c->item = itOrbLightning;
+ if(orbChance(c, laIce, 2000, 1500) && items[itDiamond] >= 10)
+ c->item = itOrbFlash;
+ if(orbChance(c, laCaves, 1800, 2000) && items[itGold] >= 10)
+ c->item = itOrbLife;
+ if(orbChance(c, laAlchemist, 600, 800) && items[itElixir] >= 10)
+ c->item = itOrbSpeed;
+ if(orbChance(c, laGraveyard, 200, 200) && items[itBone] >= 10)
+ c->item = itGreenStone;
+ if(orbChance(c, laDesert, 2500, 600) && items[itSpice] >= 10)
+ c->item = itOrbShield;
+ if(orbChance(c, laHell, 2000, 1000) && items[itHell] >= 10)
+ c->item = itOrbYendor;
+ if(orbChance(c, laRlyeh, 1500, 1000) && items[itStatue] >= 10)
+ c->item = itOrbTeleport;
+ if(orbChance(c, laMotion, 2000, 700) && items[itFeather] >= 10) {
+ c->item = itOrbSafety;
+ }
+
+ if(orbChance(c, laIce, 1500, 0) && items[itDiamond] >= 10)
+ c->item = itOrbWinter;
+ if(orbChance(c, laDryForest, 2500, 0) && items[itFernFlower] >= 10)
+ c->item = itOrbWinter;
+ if(orbChance(c, laCocytus, 1500, 1500) && items[itSapphire] >= 10)
+ c->item = itOrbWinter;
+ if(orbChance(c, laCaves, 1200, 0) && items[itGold] >= 10)
+ c->item = itOrbDigging;
+ if(orbChance(c, laDryForest, 500, 2000) && items[itFernFlower] >= 10)
+ c->item = itOrbThorns;
+
+ if(orbChance(c, laDeadCaves, 1800, 0) && items[itSilver] >= 10)
+ c->item = itGreenStone;
+ if(orbChance(c, laDeadCaves, 1800, 1000) && items[itSilver] >= 10)
+ c->item = itOrbDigging;
+ if(orbChance(c, laFjord, 1500, 2500) && items[itEmerald] >= 10)
+ c->item = itOrbPsi;
+ if(orbChance(c, laWineyard, 900, 1200) && items[itWine] >= 10)
+ c->item = itOrbGhost;
+ if(orbChance(c, laHive, 800, 1200) && items[itRoyalJelly] >= 10)
+ c->item = itOrbInvis;
+ if(orbChance(c, laPower, 0, 1500) && items[itPower] >= 10)
+ c->item = itOrbFire;
+ if(orbChance(c, laTemple, 0, 2500) && items[itGrimoire] >= 10)
+ c->item = itOrbDragon;
+ if(orbChance(c, laCamelot, 1000, 1500) && items[itHolyGrail])
+ c->item = itOrbIllusion;
+ }
+ }
+ }
+ }
+
+// find worms and ivies
+void settemp(cell *c) {
+ temps.push_back(c); tempval.push_back(c->monst); c->monst = moNone;
+ }
+
+void findWormIvy(cell *c) {
+ while(true) {
+ if(c->monst == moWorm || c->monst == moTentacle || c->monst == moWormwait || c->monst == moTentaclewait ||
+ c->monst == moTentacleEscaping) {
+ worms.push_back(c); settemp(c);
+ break;
+ }
+ else if(c->monst == moWormtail) {
+ bool bug = true;
+ for(int i=0; itype; i++) {
+ cell* c2 = c->mov[i];
+ if(c2 && isWorm(c2) && c2->mov[c2->mondir] == c) {
+ settemp(c);
+ c = c2;
+ bug = false;
+ }
+ }
+ if(bug) break;
+ }
+ else if(c->monst == moIvyWait) {
+ cell* c2 = c->mov[c->mondir];
+ settemp(c); c=c2;
+ }
+ else if(c->monst == moIvyHead) {
+ ivies.push_back(c); settemp(c);
+ break;
+ }
+ else if(c->monst == moIvyBranch || c->monst == moIvyRoot) {
+ bool bug = true;
+ for(int i=0; itype; i++) {
+ cell* c2 = c->mov[i];
+ if(c2 && (c2->monst == moIvyHead || c2->monst == moIvyBranch) && c2->mov[c2->mondir] == c) {
+ settemp(c);
+ c = c2;
+ bug = false;
+ }
+ }
+ if(bug) break;
+ }
+ else break;
+ }
+ }
+
+bool isGhostMover(eMonster m) {
+ return m == moGhost || m == moGreaterShark ||
+ (cwt.c->land == laPower && (m == moWitchGhost || m == moWitchWinter));
+ }
+
+bool havebugs, haveearth, haveeagles, haveleader;
+
+bool bugsfighting;
+
+// calculate cpdist and pathdist
+void bfs() {
+
+ int dcs = size(dcal);
+ for(int i=0; icpdist = INFD;
+ worms.clear(); ivies.clear(); ghosts.clear(); golems.clear(); mirrors.clear();
+ temps.clear(); tempval.clear(); targets.clear(); havebugs = false;
+ haveearth = false; haveeagles = false; haveleader = false;
+
+ dcal.clear(); dcal.push_back(cwt.c);
+ reachedfrom.clear(); reachedfrom.push_back(rand() % cwt.c->type);
+
+ int pqs = size(pathq);
+ for(int i=0; ipathdist = INFD;
+ pathq.clear(); if(!invismove) targets.push_back(cwt.c);
+
+ cwt.c->cpdist = 0;
+ int qb = 0;
+ while(true) {
+ int i, fd = reachedfrom[qb] + 3;
+ cell *c = dcal[qb++];
+ int d = c->cpdist;
+ if(d == 7) { first7 = qb; break; }
+ for(int j=0; jtype; j++) if(i = (fd+j) % c->type, c->mov[i]) {
+ // printf("i=%d cd=%d\n", i, c->mov[i]->cpdist);
+ cell *c2 = c->mov[i];
+ if(c2 && signed(c2->cpdist) > d+1) {
+ c2->cpdist = d+1;
+
+ // remove treasures
+ if(c2->item && c2->cpdist == 7 && itemclass(c2->item) == IC_TREASURE &&
+ items[c2->item] >= 20 + currentLocalTreasure)
+ c2->item = itNone;
+
+ c2->ligon = 0;
+ dcal.push_back(c2);
+ reachedfrom.push_back(c->spn[i]);
+ if(c2->monst) {
+ if(isWorm(c2) || isIvy(c2)) findWormIvy(c2);
+ else if(isGhostMover(c2->monst))
+ ghosts.push_back(c2);
+ else if(isBug(c2)) {
+ havebugs = true;
+ targets.push_back(c2);
+ }
+ else if(isFriendly(c2)) {
+ targets.push_back(c2);
+ if(c2->monst == moGolem) golems.push_back(c2);
+ if(c2->monst == moKnight) golems.push_back(c2);
+ if(c2->monst == moIllusion) {
+ if(items[itOrbIllusion]) items[itOrbIllusion]--;
+ else c2->monst = moNone;
+ }
+ if(isMimic(c2)) mirrors.push_back(c2);
+ }
+ else if(c2->monst == moEagle) haveeagles = true;
+ else if(c2->monst == moEarthElemental) haveearth = true;
+ else if(c2->monst == moCultistLeader) haveleader = true;
+ }
+ // pheromones!
+ if(c2->land == laHive && c2->heat >= 50 && c2->wall != waWaxWall)
+ havebugs = true;
+ if(c2->wall == waThumper && c2->tmp > 0) {
+ useup(c2);
+ targets.push_back(c2);
+ }
+ }
+ }
+ }
+
+ for(int i=0; ipathdist = (targets[i] == cwt.c) ? 0 : 1;
+ pathq.push_back(targets[i]);
+ }
+
+ int qtemp = size(temps);
+ for(int i=0; imonst = tempval[i];
+
+ pathqm.clear();
+ reachedfrom.clear(); reachedfrom.push_back(rand() % cwt.c->type);
+
+ qb = 0;
+ for(qb=0; qb < size(pathq); qb++) {
+ int fd = reachedfrom[qb] + 3;
+ cell *c = pathq[qb];
+ eucoord x, y;
+ decodeMaster(c->master, x, y);
+ if(c->monst && !isBug(c) && !isFriendly(c)) {
+ pathqm.push_back(c);
+ continue; // no paths going through monsters
+ }
+ if(c->cpdist > 7) continue;
+ int d = c->pathdist;
+ for(int j=0; jtype; j++) {
+ int i = (fd+j) % c->type;
+ // printf("i=%d cd=%d\n", i, c->mov[i]->cpdist);
+ if(c->mov[i] && c->mov[i]->pathdist == INFD && !thruVine(c, c->mov[i]) &&
+ (c->mov[i]->monst || passable(c->mov[i], (d==0)?NULL:c, true, false))) {
+ c->mov[i]->pathdist = d+1;
+ pathq.push_back(c->mov[i]); reachedfrom.push_back(c->spn[i]);
+ }
+ }
+ }
+ }
+
+extern void cleargraphmemory();
+
+// initialize the game
+void initgame() {
+ cwt.c = origin.c7; cwt.spin = 0;
+ cwt.c->land = euclid ? euclidland : firstland;
+ createMov(cwt.c, 0);
+
+ for(int i=0; i<65536; i++) euland[i] = laNone;
+
+ // extern int sightrange; sightrange = 9;
+ // cwt.c->land = laHell; items[itHell] = 10;
+ for(int i=9; i>=0; i--) {
+ setdist(cwt.c, i, NULL);
+ verifycells(&origin);
+ }
+ if(cwt.c->land == laCocytus)
+ cwt.c->wall = waFrozenLake;
+ else if(cwt.c->land == laAlchemist || cwt.c->land == laGameBoard)
+ ;
+ else if(cwt.c->land == laCaves || cwt.c->land == laFjord)
+ cwt.c->wall = waCavefloor;
+ else if(cwt.c->land == laDeadCaves)
+ cwt.c->wall = waDeadfloor2;
+ else
+ cwt.c->wall = waNone;
+ cwt.c->item = itNone;
+ cwt.c->monst = moNone;
+
+ cleargraphmemory();
+
+ if(!safety) {
+ timerstart = time(NULL); turncount = 0; sagephase = 0;
+ timerstopped = false;
+ savecount = 0; savetime = 0;
+ cheater = 0;
+ if(firstland != laIce) cheater++;
+ }
+ else safety = false;
+
+ // items[itGreenStone] = 100;
+ // items[itOrbTeleport] = 100;
+
+ /* items[itGold] = 10;
+ items[itDiamond] = 10;
+ items[itSpice] = 10;
+ items[itElixir] = 10;
+ items[itRuby] = 10;
+ items[itShard] = 10;
+ items[itSilver] = 10;
+ items[itWine] = 10;
+ items[itRoyalJelly] = 10;
+ items[itEmerald] = 10;
+ items[itFeather] = 10;
+ kills[moYeti] = 90; */
+
+ /*
+ items[itGold] = 20;
+ items[itDiamond] = 20;
+ items[itSpice] = 20;
+ items[itRuby] = 20;
+ items[itElixir] = 20;
+ */
+
+ /*
+ items[itOrbShield] = 100;
+ items[itOrbSpeed] = 100;
+ items[itOrbWinter] = 100;
+ items[itOrbLightning] = 100;
+ */
+
+ // items[itOrbLightning] = 100;
+ // items[itRuby] = 100;
+
+ // items[itOrbWinter] = 1000;
+
+ bfs();
+ }
+
+void firetrail(cell *c) {
+ if(!passable(c, NULL, true, true)) return;
+ destroyHalfvine(c);
+ c->item = itNone;
+ if(c->wall == waFrozenLake)
+ c->wall = waLake;
+ if(cellUnstableOrChasm(c))
+ return;
+ c->wall = waBonfire, c->tmp = 10;
+ }
+
+void moveNormal(cell *c) {
+ bool repeat = true;
+ eMonster m = c->monst;
+
+ again:
+
+ for(int j=0; jtype; j++)
+ if(c->mov[j] && isFriendlyOrBug(c->mov[j]) && repeat && !attackingForbidden(c->mov[j], c)) {
+ // XLATC enemy destroys a friend
+ addMessage(XLAT("%The1 destroys %the2!", m, c->mov[j]->monst));
+ c->mov[j]->monst = moNone;
+ return;
+ }
+
+ int posdir[10], nc = 0;
+
+ for(int j=0; jtype; j++) {
+ cell *c2 = c->mov[j];
+ if(!c2) continue;
+ if(c2->pathdist >= c->pathdist) continue;
+ if(!passable(c2, c, false, false)) continue;
+ // crystal sages can't stand out of Cocytus
+ if(m == moCrystalSage && (c2->land != laCocytus || c2->heat > SAGEMELT))
+ continue;
+ // lancers don't move next to other monsters
+ if(c->monst == moLancer) {
+ bool lancerok = true;
+ for(int u=2; u<=c2->type-2; u++) {
+ cell *c3 = c2->mov[(c->spn[j]+u)%c2->type];
+ if(c3->monst && !isFriendlyOrBug(c3) && isKillableSomehow(c3))
+ lancerok = false;
+ }
+ if(!lancerok) continue;
+ }
+
+
+// if(m == moNecromancer ? c2->land == laGraveyard : true)
+// it is more fun when demons step into the Land of Eternal Motion, IMO
+// if((m == moLesser || m == moGreater) ? c2->land != laMotion : true)
+ if(c2->cpdist > 0) {
+ posdir[nc] = j;
+ nc++;
+ }
+ }
+
+ if(nc == 0 && !passable(c, NULL, true, true)) {
+ // running dogs run away!
+ // they prefer a straight direction
+ int sgn = rand() % 2 ? 1 : -1;
+ for(int b=3; b>=0; b--) for(int s=-1; s<=1; s+=2) if(!nc) {
+ int d = (c->mondir + b*s*sgn) % c->type;
+ d += c->type; d %= c->type;
+ cell *c2 = c->mov[d];
+ if(passable(c2, c, false, false))
+ posdir[nc++] = d;
+ }
+ }
+
+/*
+ // Eagles can fly through chasms/slime if needed
+ if(m == moEagle && nc == 0) {
+ printf("no eagle no fly\n");
+ for(int j=0; jtype; j++) printf("%d from %d\n", c->mov[j]->cpdist, c->cpdist);
+ for(int j=0; jtype; j++)
+ if(c->mov[j] && c->mov[j]->cpdist < c->cpdist &&
+ !c->mov[j]->monst &&
+ (c->mov[j]->land == laMotion || c->mov[j]->land == laAlchemist) && c->wall != waDeadTroll)
+ if(c->mov[j]->cpdist > 0)
+ posdest[nc++] = c->mov[j];
+ printf("nc=%d\n", nc);
+ } */
+
+ if(!nc) return;
+ nc = rand() % nc;
+ cell *c2 = c->mov[posdir[nc]];
+ c2->monst = m, c->monst = moNone;
+ c2->mondir = c->spn[posdir[nc]];
+ if(m == moEagle && repeat && c->pathdist > 1) { repeat = false; c = c2; goto again; }
+
+ // lancers pierce our friends :(
+ if(m == moLancer) {
+ // printf("lancer stab?\n");
+ for(int u=2; u<=c2->type-2; u++) {
+ cell *c3 = c2->mov[(c->spn[posdir[nc]]+u)%c2->type];
+ if(c3->monst && isKillableSomehow(c3)) {
+ addMessage(XLAT("%The1 pierces %the2!", m, c3->monst));
+ killMonster(c3);
+ }
+ }
+ }
+
+ if(m == moWitchFire) firetrail(c);
+ if(isWitch(m) && c2->item == itOrbLife && passable(c, NULL, true, true)) {
+ // note that Fire Witches don't pick up Orbs of Life,
+ addMessage(XLAT("%The1 picks up %the2!", moWitch, c2->item));
+ c->monst = moEvilGolem; c2->item = itNone;
+ }
+ else if(m == moWitch) {
+ bool pickup = false;
+ if(c2->item == itOrbFlash)
+ pickup = true, m = moWitchFlash;
+ if(c2->item == itOrbWinter)
+ pickup = true, m = moWitchWinter;
+ if(c2->item == itOrbGhost)
+ pickup = true, m = moWitchGhost;
+ if(c2->item == itOrbFire)
+ pickup = true, m = moWitchFire;
+ // Orbs of Speed are a special case here, because we don't want
+ // the witch to move immediately
+ if(c2->item == itOrbLife)
+ pickup = true, c->monst = moEvilGolem;
+ if(pickup) {
+ addMessage(XLAT("%The1 picks up %the2!", moWitch, c2->item));
+ c2->monst = m; c2->item = itNone;
+ }
+ }
+ }
+
+void explodeAround(cell *c) {
+ for(int j=0; jtype; j++) {
+ cell* c2 = c->mov[j];
+ if(c2) {
+ if(isIcyLand(c2)) c2->heat += 0.5;
+ if((c2->wall == waDune || c2->wall == waIcewall ||
+ c2->wall == waAncientGrave || c2->wall == waFreshGrave ||
+ c2->wall == waColumn || c2->wall == waThumper || c2->wall == waBonfire ||
+ c2->wall == waDryTree || c2->wall == waWetTree ||
+ c2->wall == waVinePlant || c2->wall == waVineHalfA || c2->wall == waVineHalfB)) {
+ destroyHalfvine(c2); c2->wall = waNone;
+ }
+ if(c2->wall == waCavewall || c2->wall == waDeadTroll) c2->wall = waCavefloor;
+ if(c2->wall == waDeadfloor2) c2->wall = waDeadfloor;
+ if(c2->wall == waDeadwall) c2->wall = waDeadfloor2;
+ if(c2->wall == waBigStatue) c2->wall = waNone;
+ if(c2->wall == waFloorA || c2->wall == waFloorB)
+ if(c->wall == waFloorA || c->wall == waFloorB)
+ c2->wall = c->wall;
+ }
+ }
+ }
+
+void moveWorm(cell *c) {
+
+ if(c->monst == moWormwait) { c->monst = moWorm; return; }
+ else if(c->monst == moTentaclewait) { c->monst = moTentacle; return; }
+ else if(c->monst == moTentacleEscaping) {
+ // explodeAround(c);
+ c->monst = moNone;
+ if(c->mondir != NODIR) c->mov[c->mondir]->monst = moTentacleEscaping;
+ return;
+ }
+ else if(c->monst != moWorm && c->monst != moTentacle) return;
+
+ int ncg = 0, ncb = 0;
+ cell *gmov[7], *bmov[7];
+
+ int id = c->monst - moWorm;
+
+ for(int j=0; jtype; j++) {
+ if(c->mov[j] && isFriendlyOrBug(c->mov[j]) && !attackingForbidden(c->mov[j], c)) {
+ addMessage(XLAT("%The1 eats %the2!", c->monst, c->mov[j]->monst));
+ ncg = 1; gmov[0] = c->mov[j];
+ break;
+ }
+ if(c->mov[j] && passable(c->mov[j], c, false, false) && !cellUnstable(c->mov[j]) && c->mov[j] != cwt.c) {
+ if(c->mov[j]->pathdist < c->pathdist) gmov[ncg++] = c->mov[j]; else bmov[ncb++] = c->mov[j];
+ }
+ }
+
+ if(ncg == 0 && ncb == 0) {
+ int spices = 0;
+ if(id) {
+ addMessage(XLAT("Cthulhu withdraws his tentacle!"));
+ kills[moTentacle]++;
+ c->monst = moTentacleEscaping;
+ moveWorm(c);
+ }
+ else {
+ addMessage(XLAT("The sandworm explodes in a cloud of Spice!"));
+ kills[moWorm]++;
+ spices = 3;
+ }
+ eItem loc = treasureType(c->land);
+ while(c->monst == moWorm || c->monst == moWormtail || c->monst == moTentacle || c->monst == moTentacletail) {
+ // if(!id)
+ explodeAround(c);
+ if(spices > 0 && c->land == laDesert) {
+ if(notDippingForExtra(itSpice, loc))
+ c->item = itSpice;
+ spices--;
+ }
+ c->monst = moNone;
+ if(c->mondir != NODIR) c = c->mov[c->mondir];
+ }
+ return;
+ }
+
+ cell* goal;
+ if(ncg) goal = gmov[rand() % ncg];
+ else goal = bmov[rand() % ncb];
+
+ for(int j=0; jtype; j++) if(c->mov[j] == goal) {
+ goal->monst = eMonster(moWormwait + id);
+
+ c->monst = eMonster(moWormtail + id);
+ goal->mondir = c->spn[j];
+
+ if(id) break;
+
+ cell *c2 = c, *c3 = c2;
+ for(int a=0; a<15; a++)
+ if(c2->monst == moWormtail) {
+ if(c2->mondir == NODIR) return;
+ c3 = c2, c2 = c3->mov[c2->mondir];
+ }
+
+ if(c2->monst == moWormtail) c2->monst = moNone, c3->mondir = NODIR;
+ }
+
+ }
+
+void ivynext(cell *c) {
+ cellwalker cw(c, c->mondir);
+ cw.c->monst = moIvyWait;
+ bool findleaf = false;
+ while(true) {
+ cwspin(cw, 1);
+ if(cw.spin == signed(cw.c->mondir)) {
+ if(findleaf) {
+ cw.c->monst = moIvyHead; break;
+ }
+ cw.c->monst = moIvyWait;
+ cwstep(cw);
+ continue;
+ }
+ cwstep(cw);
+ if(cw.c->monst == moIvyWait && signed(cw.c->mondir) == cw.spin) {
+ cw.c->monst = moIvyBranch;
+ findleaf = true; continue;
+ }
+ cwstep(cw);
+ }
+ }
+
+// this removes Ivy, but also potentially causes Vines to grow
+void removeIvy(cell *c) {
+ c->monst = moNone;
+ for(int i=0; itype; i++)
+ // note that semi-vines don't count
+ if(c->mov[i]->wall == waVinePlant) {
+ destroyHalfvine(c);
+ c->wall = waVinePlant;
+ }
+ }
+
+void moveivy() {
+ for(int i=0; imonst != moIvyHead) continue;
+ ivynext(c);
+
+ cell *mto = NULL;
+ int pd = c->pathdist;
+ int sp = 0;
+
+ while(c->monst != moIvyRoot) {
+ for(int j=0; jtype; j++) {
+ if(c->mov[j] && isFriendly(c->mov[j])) {
+ addMessage(XLAT("The ivy destroys %the1!", c->mov[j]->monst));
+ c->mov[j]->monst = moNone;
+ continue;
+ }
+ if(c->mov[j] && signed(c->mov[j]->pathdist) < pd && passable(c->mov[j], c, false, false))
+ mto = c->mov[j], pd = mto->pathdist, sp = c->spn[j];
+ }
+ c = c->mov[c->mondir];
+ }
+
+ if(mto && mto->cpdist) {
+ mto->monst = moIvyWait, mto->mondir = sp;
+ // if this is the only branch, we want to move the head immediately to mto instead
+ if(mto->mov[mto->mondir]->monst == moIvyHead) {
+ mto->monst = moIvyHead; co->monst = moIvyBranch;
+ }
+ }
+ else if(co->mov[co->mondir]->monst != moIvyRoot) {
+ // shrink useless branches, but do not remove them completely (at the root)
+ if(co->monst == moIvyHead) co->mov[co->mondir]->monst = moIvyHead;
+ removeIvy(co);
+ }
+ }
+ }
+
+// move slimes, and also seeps
+
+int sval = 1;
+vector slimedfs;
+
+bool isSlimeMover(cell *c) {
+ return
+ c->monst == moSlime || c->monst == moSeep || c->monst == moShark ||
+ c->monst == moVineSpirit;
+ }
+
+void slimevisit(cell *c, cell *from, int u) {
+ if(!c) return;
+ if(eq(c->tmp, sval)) return;
+ if(c->wall != waCavewall && c->wall != waLake && c->wall != waDeadwall &&
+ c->wall != waFloorA && c->wall != waFloorB && c->wall != waVinePlant &&
+ c->wall != waVineHalfB && c->wall != waVineHalfA
+ ) return;
+ bool hv = false;
+ // cavewalls/deadwalls are the same for seeps
+ if(c->wall == waCavewall && from->wall == waDeadwall) hv++;
+ if(c->wall == waDeadwall && from->wall == waCavewall) hv++;
+ // only travel to halfvines correctly
+ if(cellHalfvine(c) && from != cwt.c) {
+ int i=0;
+ for(int t=0; ttype; t++) if(c->mov[t] && c->mov[t]->wall == c->wall) i=t;
+ int z = i-u; if(z<0) z=-z; z%=6;
+ if(z>1) return;
+ hv=true;
+ }
+ // only travel from halfvines correctly
+ if(cellHalfvine(from) && from != cwt.c) {
+ int i=0;
+ for(int t=0; ttype; t++) if(from->mov[t] && from->mov[t]->wall == from->wall) i=t;
+ int z = i-c->spn[u]; if(z<0) z=-z; z%=6;
+ if(z>1) return;
+ hv=true;
+ }
+ if(c->wall != from->wall && from != cwt.c && !hv) return;
+ if(c->item) return;
+ // if(c->wall == waThumper || c->wall == waBonfire) return;
+ c->tmp = sval;
+ if(size(slimedfs) < 1000) slimedfs.push_back(c), reachedfrom.push_back(u);
+ if(!isWorm(c) && !isIvy(c) && !isMimic(c))
+ for(int i=0; itype; i++) if(c->mov[i] == from)
+ c->mondir = i;
+ if(isSlimeMover(c)) {
+
+ for(int j=0; jtype; j++)
+ if(c->mov[j] && isFriendlyOrBug(c->mov[j])) {
+ // XLATC slime/seep/shark
+ addMessage(XLAT("%The1 eats %the2!", c->monst, c->mov[j]->monst));
+ c->mov[j]->monst = moNone;
+ return;
+ }
+
+ if(from->cpdist == 0 || from->monst) return;
+
+ from->monst = c->monst, c->monst = moNone;
+ }
+ }
+
+void moveslimes() {
+ sval++;
+ slimedfs.clear(); reachedfrom.clear();
+ for(int i=0; itype);
+ }
+ for(int i=0; itype; t++) {
+ int u = (j+t) % c->type;
+ slimevisit(c->mov[u], c, c->spn[u]);
+ }
+ }
+ }
+
+// move eagles
+
+vector eagledfs;
+
+void eaglevisit(cell *c, cell *from, int id) {
+ if(!c) return;
+ if(eq(c->tmp, sval)) return;
+ if(c->monst == moEagle) {
+ if(id == 1) for(int j=0; jtype; j++)
+ if(c->mov[j] && isFriendlyOrBug(c->mov[j]) && !attackingForbidden(c->mov[j], c)) {
+ // XLATC eagle
+ addMessage(XLAT("%The1 claws %the2!", c->monst, c->mov[j]->monst));
+ c->mov[j]->monst = moNone;
+ return;
+ }
+
+ if(from->cpdist == 0 || from->monst) return;
+
+ from->monst = c->monst, c->monst = moNone;
+ }
+ if(c->wall == waThumper || isFire(c)) return;
+ c->tmp = sval;
+ if(!eaglepassable(c)) return;
+ if(size(eagledfs) < 1000) eagledfs.push_back(c);
+ }
+
+void moveeagles(int id) {
+ if(invismove) return;
+ sval++;
+ eagledfs.clear();
+ for(int i=0; itype; t++)
+ eaglevisit(c->mov[t], c, id);
+ }
+ }
+
+vector earthdfs;
+
+bool earthpassable(cell *c, cell *from) {
+ // cannot go through Living Caves...
+ if(c->wall == waCavefloor) return false;
+ // but can dig through...
+ if(c->wall == waDeadwall || c->wall == waDune)
+ return true;
+ return passable(c, from, true, false);
+ }
+
+void earthMove(cell *from, int dir) {
+ cell *c2 = from->mov[dir];
+ int d = from->spn[dir];
+ earthWall(from);
+ if(c2) for(int u=2; u<=c2->type-2; u++) {
+ cell *c3 = c2->mov[(d + u)% c2->type];
+ earthFloor(c3);
+ }
+ }
+
+void earthvisit(cell *c, cell *from, int d) {
+ if(!c) return;
+ if(eq(c->tmp, sval)) return;
+
+ if(!earthpassable(c, from)) return;
+
+ if(c->monst == moEarthElemental) {
+ // note: move from 'c' to 'from'!
+ for(int j=0; jtype; j++)
+ if(c->mov[j] && isFriendlyOrBug(c->mov[j]) && !attackingForbidden(c->mov[j], c)) {
+ // XLATC eagle
+ addMessage(XLAT("%The1 punches %the2!", c->monst, c->mov[j]->monst));
+ c->mov[j]->monst = moNone;
+ return;
+ }
+
+ if(!passable(from, c, false, false)) {
+ earthFloor(from);
+ return;
+ }
+ if(from->cpdist == 0 || from->monst) return;
+
+ earthMove(c, from->spn[d]);
+
+ from->monst = c->monst, c->monst = moNone;
+ }
+ if(c->wall == waThumper || isFire(c)) return;
+ c->tmp = sval;
+ if(size(earthdfs) < 1000) earthdfs.push_back(c);
+ }
+
+void moveearth() {
+ if(invismove) return;
+ sval++;
+ earthdfs.clear();
+ for(int i=0; itype; t++)
+ earthvisit(c->mov[t], c, t);
+ }
+ }
+
+vector leaderdfs;
+
+bool canPushStatueOn(cell *c) {
+ return passable(c, NULL, true, false);
+ }
+
+// note: move from 'c' to 'from'!
+void leadervisit(cell *c, cell *from, int d) {
+ if(!c) return;
+ if(eq(c->tmp, sval)) return;
+
+ if(c->monst == moCultistLeader) {
+ // he cannot push big statues into bonfires, just like the player
+ if(c->wall == waBonfire && from->wall == waBigStatue)
+ return;
+ // note: move from 'c' to 'from'!
+ for(int j=0; jtype; j++)
+ if(c->mov[j] && isFriendlyOrBug(c->mov[j]) && !attackingForbidden(c->mov[j], c)) {
+ // XLATC eagle
+ addMessage(XLAT("%The1 punches %the2!", c->monst, c->mov[j]->monst));
+ c->mov[j]->monst = moNone;
+ return;
+ }
+
+ if(from->wall != waBigStatue && !passable(from, c, false, false)) {
+ return;
+ }
+ if(from->cpdist == 0 || from->monst) return;
+
+ if(from->wall == waBigStatue) {
+ from->wall = c->wall;
+ c->wall = waBigStatue;
+ }
+ // earthMove(c, from->spn[d]);
+
+ from->monst = c->monst, c->monst = moNone;
+ }
+
+ else if(c->wall == waBigStatue ? !canPushStatueOn(from) : !passable(c, from, false, false))
+ return;
+
+ if(c->wall == waThumper || isFire(c)) return;
+ c->tmp = sval;
+
+ if(size(leaderdfs) < 1000) leaderdfs.push_back(c);
+ }
+
+void moveleader() {
+ if(invismove) return;
+ sval++;
+ leaderdfs.clear();
+ for(int i=0; itype; t++)
+ leadervisit(c->mov[t], c, t);
+ }
+ }
+
+#define SHSIZE 16
+
+cell *shpos[SHSIZE];
+int cshpos = 0;
+
+void clearshadow() {
+ for(int i=0; imonst == moShadow)
+ shpos[cshpos]->monst = moNone;
+ shpos[cshpos] = cwt.c;
+ cshpos = (cshpos+1) % SHSIZE;
+ if(shpos[cshpos] && shpos[cshpos]->monst == moNone && shpos[cshpos]->cpdist && shpos[cshpos]->land == laGraveyard)
+ shpos[cshpos]->monst = moShadow;
+ }
+
+// from-to
+bool ghostmove(cell* c1, cell* c2) {
+ if(c1->monst == moGhost || c1->monst == moWitchGhost) return true;
+ if(c1->monst == moGreaterShark) return c2->wall == waLake;
+ if(c1->monst == moWitchWinter)
+ return c2->wall == waBonfire || passable(c2, c1, false, false);
+ return false;
+ }
+
+void moveghosts() {
+
+ if(invismove) return;
+ for(int d=0; d<8; d++) movesofgood[d].clear();
+
+ for(int i=0; imonst) && c->cpdist > 1) {
+ int goodmoves = 0;
+
+ for(int k=0; ktype; k++) if(c->mov[k] && !c->mov[k]->monst && c->mov[k]->cpdist < c->cpdist)
+ if(ghostmove(c, c->mov[k]))
+ goodmoves++;
+
+ movesofgood[goodmoves].push_back(c);
+ }
+ }
+
+ for(int d=0; d<8; d++) for(int i=0; imonst) && c->cpdist > 1) {
+
+ int mdir[7];
+
+ for(int j=0; jtype; j++)
+ if(c->mov[j] && isFriendlyOrBug(c->mov[j])) {
+ // XLATC ghost/greater shark
+ addMessage(XLAT("%The1 scares %the2!", c->monst, c->mov[j]->monst));
+ c->mov[j]->monst = moNone;
+ return;
+ }
+
+ int qmpos = 0;
+ for(int k=0; ktype; k++) if(c->mov[k] && !c->mov[k]->monst && c->mov[k]->cpdist < c->cpdist)
+ if(ghostmove(c, c->mov[k]))
+ mdir[qmpos++] = k;
+ if(!qmpos) continue;
+ int d = mdir[rand() % qmpos];
+ cell *c2 = c->mov[d];
+ c2->monst = c->monst; c->monst = moNone;
+ }
+ }
+ }
+
+int lastdouble = -3;
+
+void stabbingAttack(cell *mf, cell *mt, eMonster who = moNone) {
+ int numsh = 0, numflail = 0, numlance = 0;
+ for(int t=0; ttype; t++) {
+ cell *c = mf->mov[t];
+ if(c->monst == moHedge || (!who && items[itOrbThorns] && c->monst && isKillable(c))) {
+ for(int u=0; utype; u++) {
+ if(c->mov[u] == mt) {
+ if(who)
+ addMessage(XLAT("%The1 stabs %the2.", who, c->monst));
+ else
+ addMessage(XLAT("You stab %the1.", c->monst));
+ int k = tkills();
+ killMonster(c);
+ if(tkills() > k) numsh++;
+ }
+ }
+ }
+ if(c->monst == moFlailer) {
+ bool away = true;
+ for(int u=0; utype; u++) if(c->mov[u] == mt) away = false;
+ if(away) {
+ if(who)
+ addMessage(XLAT("%The1 tricks %the2.", who, c->monst));
+ else
+ addMessage(XLAT("You trick %the1.", c->monst));
+ int k = tkills();
+ killMonster(c);
+ if(tkills() > k) numflail++;
+ }
+ }
+ }
+
+ for(int t=0; ttype; t++) {
+ cell *c = mt->mov[t];
+ if(c->monst == moLancer) {
+ if(who)
+ addMessage(XLAT("%The1 tricks %the2.", who, c->monst));
+ else
+ addMessage(XLAT("You trick %the1.", c->monst));
+ int k = tkills();
+ killMonster(c);
+ if(tkills() > k) numlance++;
+ }
+ }
+
+ if(numsh) achievement_count("STAB", numsh, 0);
+
+ if(numlance && numflail && numsh) achievement_gain("MELEE3");
+
+ if(numlance + numflail + numsh >= 5) achievement_gain("MELEE5");
+
+ if(numsh == 2) {
+ if(lastdouble == turncount-1) achievement_count("STAB", 4, 0);
+ lastdouble = turncount;
+ }
+ }
+
+void movegolems() {
+ int qg = 0;
+ for(int i=0; imonst;
+ if(m == moGolem || m == moKnight) {
+ if(m == moGolem) qg++;
+ int bestv = 100, bq = 0, bdirs[7];
+ for(int k=0; ktype; k++) if(c->mov[k]) {
+ int val;
+ if(c->mov[k] == cwt.c) val = 0;
+ else if(isActiveEnemy(c->mov[k], NULL) && isKillable(c->mov[k]) &&
+ !attackingForbidden(c->mov[k], c))
+ val = 12000;
+ else if(isInactiveEnemy(c->mov[k]) && isKillable(c->mov[k]) &&
+ !attackingForbidden(c->mov[k], c))
+ val = 10000;
+ else if(isIvy(c->mov[k]) && !attackingForbidden(c->mov[k], c)) val = 8000;
+ else if(monstersnear(c->mov[k], NULL, false)) val = 0;
+ else if(passable(c->mov[k], c, false, false)) val = 4000;
+ else val = 0;
+ if(c->monst == moGolem)
+ val -= c->mov[k]->cpdist;
+ if(c->monst == moKnight && c->mov[k]->master->alt)
+ val -= celldistAlt(c->mov[k]);
+ if(val > bestv) bestv = val, bq = 0;
+ if(val == bestv) bdirs[bq++] = k;
+ }
+ if(bestv <= 100) continue;
+ int dir = bdirs[rand() % bq];
+ cell *c2 = c->mov[dir];
+ if(c2->monst) {
+ addMessage(XLAT("%The1 destroys %the2!", c->monst, c2->monst));
+ killMonster(c2);
+ }
+ else {
+ stabbingAttack(c, c2, m);
+ c2->monst = m == moGolem ? moGolemMoved : moKnightMoved;
+ c2->mondir = c->spn[dir];
+ c->monst = moNone;
+ }
+ }
+ }
+ achievement_count("GOLEM", qg, 0);
+ }
+
+bool wchance(int a, int of) {
+ of *= 10;
+ a += items[itOrbYendor] * 5 + items[itHolyGrail] + 1;
+ if(cwt.c->land == laCrossroads) a+= items[itHyperstone] * 10;
+ for(int i=0; i 40) seepcount = (t-40 + rand() % 20) / 20;
+ int ghostcount = 0;
+ if(t > 80) ghostcount = (t-80 + rand() % 20) / 20;
+
+ while(first7 < size(dcal)) {
+ int i = first7 + rand() % (size(dcal) - first7);
+ cell *c = dcal[i];
+
+ // wandering seeps & ghosts
+ if(seepcount && c->wall == waCavewall && !c->monst && eq(c->tmp, sval)) {
+ c->monst = moSeep;
+ seepcount--;
+ continue;
+ }
+
+ if(ghostcount && !c->monst && cwt.c->type != laCaves) {
+ c->monst = moGhost;
+ ghostcount--;
+ continue;
+ }
+
+ if((c->wall == waCavewall || c->wall == waDeadwall) && !c->monst && eq(c->tmp, sval) &&
+ wchance(items[treasureType(c->land)], 10)) {
+ c->monst = moSeep;
+ continue;
+ }
+
+ else if(c->monst || c->pathdist == INFD) break;
+
+ else if(c->land == laIce && wchance(items[itDiamond], 10))
+ c->monst = rand() % 2 ? moWolf : moYeti;
+
+ else if(c->land == laDesert && wchance(items[itSpice], 10))
+ c->monst = rand() % 10 ? moDesertman : moWorm;
+
+ else if(c->land == laCaves && wchance(items[itGold], 5))
+ c->monst = rand() % 3 ? moTroll : moGoblin;
+
+ else if(c->land == laDeadCaves && wchance(items[itSilver], 5))
+ c->monst = rand() % 20 ? (rand() % 3 ? moDarkTroll : moGoblin) : moEarthElemental;
+
+ else if(c->land == laJungle && wchance(items[itRuby], 40))
+ c->monst = rand() % 10 ? moMonkey : moEagle;
+
+ else if(c->land == laMirror && wchance(items[itShard], 15))
+ c->monst = rand() % 10 ? moRanger : moEagle;
+
+ else if(c->land == laHell && wchance(items[itHell], 20))
+ c->monst = rand() % 3 ? moLesser : moGreater;
+
+ else if(c->land == laRlyeh && wchance(items[itStatue], 15))
+ c->monst = rand() % 3 ? moPyroCultist :
+ (rand() % 40 < items[itStatue]-25) ? moCultistLeader : moCultist;
+
+ else if(c->land == laGraveyard && wchance(items[itBone], 15))
+ c->monst = rand() % 5 ? moGhost : moNecromancer;
+
+ else if(c->land == laDryForest && wchance(items[itFernFlower], 5))
+ c->monst = rand() % 5 ? moHedge : moFireFairy;
+
+ else if(c->land == laCocytus && wchance(items[itSapphire], 45))
+ c->monst = moCrystalSage;
+
+ else if(c->land == laAlchemist && wchance(items[itElixir], 3) && eq(c->tmp, sval) && c->item == itNone)
+ c->monst = moSlime;
+
+ else if(c->land == laFjord && wchance(items[itEmerald], 5)) {
+ static eMonster m[4] = {moHedge, moLancer, moMiner, moFlailer};
+ c->monst = m[rand() % 4];
+ }
+
+ else if(c->land == laWineyard && wchance(items[itWine], 10)) {
+ c->monst = moVineBeast;
+ }
+
+ else if(c->land == laPower && wchance(items[itPower], 10)) {
+ c->monst = eMonster(moWitch + rand() % NUMWITCH);
+ }
+
+ else if(c->land == laCamelot && rand() % 30 == 0 && celldistAltRelative(c) < 0) {
+ eMonster m[3] = { moHedge, moLancer, moFlailer };
+ c->monst = m[rand() % 3];
+ }
+
+ else if(c->land == laCrossroads && items[itHyperstone] && wchance(items[itHyperstone], 20)) {
+ // only interesting monsters here!
+ static eMonster m[12] = {
+ moWorm, moTroll, moEagle,
+ moLesser, moGreater, moPyroCultist, moGhost,
+ moFireFairy, moHedge,
+ moLancer, moFlailer, moVineBeast
+ };
+ c->monst = m[rand() % 9];
+ }
+
+ else break;
+
+ if(c->monst == moWorm) c->mondir = NODIR;
+
+ // laMotion -> no respawn!
+ }
+ }
+
+void sageheat(cell *c, double v) {
+ c->heat += v;
+ if(c->wall == waFrozenLake && c->heat > .6) c->wall = waLake;
+ }
+
+bool normalMover(eMonster m) {
+ return
+ m == moYeti || m == moRanger || m == moGoblin || m == moTroll || m == moDesertman ||
+ m == moMonkey || m == moZombie || m == moNecromancer || m == moCultist ||
+ m == moLesser || m == moGreater || m == moRunDog || m == moPyroCultist ||
+ m == moFireFairy || m == moCrystalSage || m == moHedge ||
+ m == moVineBeast || m == moLancer || m == moFlailer ||
+ m == moMiner || m == moDarkTroll ||
+ (cwt.c->land == laPower && (
+ (isWitch(m) && m != moWitchGhost && m != moWitchWinter) || m == moEvilGolem
+ ));
+ }
+
+struct buginfo_t {
+ cell *where;
+ short dist[BUGCOLORS];
+ };
+
+vector buginfo;
+
+vector bugqueue[BUGCOLORS];
+vector bugqueue4[BUGCOLORS];
+
+struct bugtomove_t {
+ int dist, moves, index;
+ bugtomove_t(int d, int m, int i) { dist=d; moves=m; index=i; }
+ };
+
+bool operator < (const bugtomove_t& m1, const bugtomove_t& m2) {
+ if(m1.dist != m2.dist) return m1.dist < m2.dist;
+ if(m1.moves != m2.moves) return m1.moves < m2.moves;
+ return false;
+ }
+
+vector bugtomove;
+vector deadbug;
+vector bugcellq;
+
+int bugcount[BUGCOLORS];
+
+bool isBugEnemy(cell *c, int k) {
+ if(c == cwt.c && !invismove) return true;
+ if(!c->monst) return false;
+ if(c->monst == moBug0+k) return false;
+ if(isIvy(c)) return false;
+ return (isBug(c) || isKillableSomehow(c));
+ }
+
+// list bugs and targets for each color
+#define BUGINF 29999
+
+void bugQueueInsert(int k, int i, int d) {
+ if(buginfo[i].dist[k] > d) {
+ if(buginfo[i].dist[k] != BUGINF) {
+ printf("%d -> %d\n", buginfo[i].dist[k], d);
+ }
+ buginfo[i].dist[k] = d;
+ bugqueue[k].push_back(i);
+ }
+ }
+
+void bugcell(cell *c) {
+ if(isActiv(c) || !passable(c, NULL, true, false))
+ return;
+ short& i(c->tmp);
+ if(i >= 0 && i < size(buginfo) && buginfo[i].where == c)
+ return;
+ i = size(buginfo);
+ buginfo.resize(i+1);
+ buginfo_t& b(buginfo[i]);
+ b.where = c;
+ for(int k=0; ktype; dir++) {
+ cell *c2 = c->mov[dir];
+ if(c2 && isBugEnemy(c2,k) && !attackingForbidden(c2, c)) {
+ if(isBug(c2)) havebug = true;
+ else haveother = true;
+ }
+ }
+ if(havebug) bugQueueInsert(k, i, 0);
+ else if(haveother) bugqueue4[k].push_back(i);
+ }
+/*// bugs communicate if the distance is at most 2
+ // also all nearby cells are inserted to the buginfo structure
+ if(size(buginfo) < 30000) {
+ for(int dir=0; dirtype; dir++) {
+ cell *c2 = c->mov[dir];
+ if(c2) {
+ // if(isBug(c)) bugcellq.push_back(c2); => does not help...
+ for(int t=0; ttype; t++)
+ if(c2->mov[t] && isBug(c2->mov[t]))
+ bugcellq.push_back(c2),
+ bugcellq.push_back(c2->mov[t]);
+ }
+ }
+ }*/
+
+ // use pheromones!
+ if(c->land == laHive && c->heat > 1) {
+ c->heat -= 1;
+ for(int dir=0; dirtype; dir++) {
+ cell *c2 = c->mov[dir];
+ if(c2) {
+ for(int t=0; ttype; t++)
+ if(c2->mov[t])
+ bugcellq.push_back(c2),
+ bugcellq.push_back(c2->mov[t]);
+ }
+ }
+ }
+ }
+
+int last_d = -1;
+
+void handleBugQueue(int k, int t) {
+ int i = bugqueue[k][t];
+ buginfo_t& b(buginfo[i]);
+ cell *c = b.where;
+ int d = b.dist[k];
+ last_d = d;
+ int goodmoves = 0;
+ for(int dir=0; dirtype; dir++) {
+ cell *c2 = c->mov[dir];
+ if(!c2) continue;
+ if(c2->tmp < 0 || c2->tmp >= size(buginfo)) continue;
+ if(!passable(c, c2, true, false)) continue;
+ int j = c2->tmp;
+ if(buginfo[j].where != c2) continue;
+ if(buginfo[j].dist[k] < d) goodmoves++;
+ bugQueueInsert(k, j, d+1);
+ }
+ if(isBug(c) && c->monst == moBug0+k) {
+ bugcount[c->monst - moBug0]++;
+ bugtomove.push_back(bugtomove_t(d,goodmoves,i));
+ }
+ }
+
+#include
+
+void movebugs() {
+ buginfo.clear();
+ for(int k=0; k check;
+ for(int t=0; tmonst;
+ int k = (m - moBug0) % BUGCOLORS;
+ int gmoves[8], q=0, bqual = -1;
+
+ for(int dir=0; dirtype; dir++) {
+ cell *c2 = c->mov[dir];
+ int qual = -10;
+ if(!c2) continue;
+ else if(isBugEnemy(c2, k) && c2->monst != moDeadBug)
+ qual = c2 != cwt.c ? 2 : -5;
+ else {
+ if(c2->monst) continue;
+ if(!passable(c2, c, false, false)) continue;
+ if(c2->tmp < 0 || c2->tmp >= size(buginfo)) continue;
+ if(buginfo[c2->tmp].where != c2) continue;
+ if(buginfo[c2->tmp].dist[k] < b.dist[k])
+ qual = 1;
+ else if(buginfo[c2->tmp].dist[k] == b.dist[k])
+ qual = 0;
+ }
+ // printf("%d->#%d %d: %d\n", i, dir, c2->tmp, qual);
+ if(qual > bqual) bqual = qual, q=0;
+ if(qual == bqual) gmoves[q++] = dir;
+ }
+
+ if(!q) { c->heat += 3; continue; }
+ int d = gmoves[rand() % q];
+ cell *c2 = c->mov[d];
+ if(c2->monst) {
+ eMonster killed = c2->monst;
+ if(isBug(killed)) battlecount++;
+ else addMessage(XLAT("%The1 fights with %the2!", c->monst, c2->monst));
+ killMonster(c2);
+ // killMonster(c);
+ if(isBug(killed)) {
+ c2->monst = moDeadBug, deadbug.push_back(c2);
+ bugcount[killed - moBug0]--;
+ }
+// c->monst = moDeadBug, deadbug.push_back(c);
+ }
+ else {
+ c2->monst = c->monst;
+ c2->mondir = c->spn[d];
+ c->monst = moNone;
+ // pheromones!
+ if(c->heat < 90) c->heat += 5;
+ if(c2->heat < 90) c2->heat += 5;
+ // if(isHive(c2->land)) c2->land = eLand(laHive0+k);
+/* if(c2->item == itRoyalJelly && !isQueen(m)) {
+ // advance!
+ c2->monst = eMonster(m+BUGCOLORS);
+ c2->item = itNone;
+ } */
+ }
+ }
+
+ // cleanup
+ for(int i=0; imonst = moNone;
+ if(battlecount)
+ addMessage(XLAT("The Hyperbugs are fighting!"));
+
+ int maxbug = 0;
+ for(int k=0; k maxbug) maxbug = bugcount[k];
+
+ achievement_count("BUG", maxbug, 0);
+ }
+
+void bugcitycell(cell *c, int d) {
+ if(isActiv(c) || !passable(c, NULL, true, false)) return;
+ short& i = c->tmp;
+ if(i >= 0 && i < size(buginfo) && buginfo[i].where == c)
+ return;
+ i = size(buginfo);
+ buginfo_t b;
+ b.where = c;
+ b.dist[0] = d;
+ buginfo.push_back(b);
+ }
+
+void createBugArmy(cell *c) {
+ int k = rand() % BUGCOLORS;
+ int minbugs = 50, maxbugs = 50;
+ int var = 5 + items[itRoyalJelly];
+ if(var>25) var=25;
+ // minbugs += 100; maxbugs += 100;
+ minbugs -= var; maxbugs += var;
+ maxbugs += items[itRoyalJelly];
+ int numbugs = minbugs + rand() % (maxbugs - minbugs + 1);
+
+ /* int i = items[itRoyalJelly];
+ int chance = 20 + 25 * i + 9000;
+ // i=0: 16%
+ // i=10: 73%
+ // i=50: 1270 vs 6000
+ eMonster m = eMonster(moBug0 + rand() % BUGCOLORS);
+ if(c->wall) return;
+ for(int i=0; itype; i++) {
+ cell *c2 = createMov(c,i);
+ if(rand() % (100+chance) < chance) {
+ if(!c2->wall) c2->monst = m;
+ for(int j=2; j<=c2->type-2; j++) {
+ int jj = (j+c->spn[i]) % c2->type;
+ cell *c3 = createMov(c2, jj);
+ if(rand() % (6000+chance) < chance && !c3->wall)
+ c3->monst = m;
+ }
+ }
+ }
+ c->monst = eMonster(m + BUGCOLORS); */
+
+ int gdir = -1;
+ for(int i=0; itype; i++) {
+ if(c->mov[i] && c->mov[i]->mpdist < c->mpdist) gdir = i;
+ }
+ if(!gdir) return;
+ cellwalker bf(c, gdir);
+ for(int i=0; i<7; i++) {
+ if(bf.c->type == 6)
+ cwspin(bf, 3);
+ else
+ cwspin(bf, 3 + rand() % 2);
+ cwstep(bf);
+ }
+ cell *citycenter = bf.c;
+ buginfo.clear();
+
+ // mark the area with BFS
+ bugcitycell(citycenter, 0);
+ for(int i=0; iland != laHive && c->land != laNone) return;
+ if(c->bardir != NODIR) return;
+ if(c->heat >= 100) return;
+ // bfs
+ if(d < 9) for(int t=0; ttype; t++)
+ bugcitycell(createMov(c,t), d+1);
+ }
+
+ // place everything
+ for(int i=0; iwall == waNone)
+ c->item = itRoyalJelly;
+ c->bardir = NOBARRIERS;
+ if(d == 9 || d == 6 || d == 3)
+ c->barleft = eLand(d/3),
+ c->barright = eLand(k);
+ else
+ c->barleft = laNone;
+ if(numbugs && c->wall == waNone)
+ c->monst = eMonster(moBug0 + k), numbugs--;
+ c->land = laHive;
+ // prevent barriers
+ if(c->mpdist == INFD) c->mpdist = BUGLEV;
+ }
+ }
+
+void moveFastMonsters() {
+ for(int i=0; imonst == moWitchSpeed) {
+
+ if(c->pathdist == 1 && c->monst != moGhost) {
+ if(items[itOrbShield]) continue;
+ addMessage(XLAT("%The1 is confused!", c->monst));
+ break;
+ }
+
+ int goodmoves = 0;
+ for(int t=0; ttype; t++) {
+ cell *c2 = c->mov[t];
+ if(c2 && c2->pathdist < c->pathdist)
+ goodmoves++;
+ }
+ movesofgood[goodmoves].push_back(c);
+ }
+ }
+
+ for(int d=0; d<8; d++) for(int i=0; imonst != moNone)
+ moveNormal(c);
+ }
+ }
+
+void activateFlashFrom(cell *cf);
+
+vector nonmovers;
+
+bool sagefresh = true;
+
+void considerEnemyMove(cell *c) {
+ eMonster m = c->monst;
+
+ if(isActiveEnemy(c, NULL)) {
+
+ if(c->pathdist == 1 && c->monst != moGhost) {
+ // c->iswall = true; c->ismon = false;
+ if(items[itOrbShield] || c->monst == moCrystalSage) return;
+
+ addMessage(XLAT("%The1 is confused!", m));
+ // playerdead = true;
+ return;
+ }
+
+ if(c->monst == moWitch && c->item == itOrbSpeed) {
+ addMessage(XLAT("%The1 picks up %the2!", moWitch, c->item));
+ c->monst = moWitchSpeed; c->item = itNone;
+ }
+
+ if(c->monst == moNecromancer) {
+ int gravenum = 0, zombienum = 0;
+ cell *gtab[8], *ztab[8];
+ for(int j=0; jtype; j++) if(c->mov[j]) {
+ if(c->mov[j]->wall == waFreshGrave) gtab[gravenum++] = c->mov[j];
+ if(passable(c->mov[j], c, false, false) && c->mov[j]->pathdist < c->pathdist)
+ ztab[zombienum++] = c->mov[j];
+ }
+ if(gravenum && zombienum) {
+ cell *gr = gtab[rand() % gravenum];
+ gr->wall = waAncientGrave;
+ gr->monst = moGhost;
+ ztab[rand() % zombienum]->monst = moZombie;
+ addMessage(XLAT("%The1 raises some undead!", c->monst));
+ return;
+ }
+ }
+
+ if(c->monst == moWolf) {
+ int bhd = NODIR;
+ ld besth = c->heat;
+ for(int j=0; jtype; j++) if(c->mov[j]->heat > besth && passable(c->mov[j], c, false, false))
+ besth = c->mov[j]->heat, bhd = j;
+ if(bhd != NODIR) {
+ // printf("wolf moved from %Lf (%p) to %Lf (%p)\n", c->heat, c, besth, c->mov[bhd]);
+ c->mov[bhd]->monst = moWolfMoved, c->monst = moNone;
+ }
+ }
+
+ else if(c->monst == moPyroCultist && c->cpdist <= 4 && cwt.c->wall == waNone && !cellUnstable(cwt.c)) {
+ addMessage(XLAT("%The1 throws fire at you!", c->monst));
+ if(itemBurns(cwt.c->item))
+ addMessage(XLAT("%The1 burns!", cwt.c->item)), cwt.c->item = itNone;
+ cwt.c->wall = waBonfire;
+ cwt.c->tmp = 20;
+ c->monst = moCultist;
+ }
+
+ else if(c->monst == moWitchFlash && flashWouldKill(c, true) && !flashWouldKill(c, false)) {
+ addMessage(XLAT("%The1 activates her Flash spell!", c->monst));
+ c->monst = moWitch;
+ activateFlashFrom(c);
+ }
+
+ else if(c->monst == moCrystalSage && c->cpdist <= 4 && isIcyLand(cwt.c)) {
+ // only one sage attacks
+ if(sagefresh) {
+ sagefresh = false;
+ if(sagephase == 0) {
+ addMessage(XLAT("%The1 shows you two fingers.", c->monst));
+ addMessage(XLAT("You wonder what does it mean?"));
+ }
+ else if(sagephase == 1) {
+ addMessage(XLAT("%The1 shows you a finger.", c->monst));
+ addMessage(XLAT("You think about possible meanings."));
+ }
+ else {
+ addMessage(XLAT("%The1 moves his finger downwards.", c->monst));
+ addMessage(XLAT("Your brain is steaming."));
+ }
+ sagephase++;
+ sageheat(cwt.c, .0);
+ for(int i=0; itype; i++)
+ sageheat(cwt.c->mov[i], .3);
+ }
+ }
+
+ else if(normalMover(m)) {
+ int goodmoves = 0;
+ for(int t=0; ttype; t++) {
+ cell *c2 = c->mov[t];
+ if(c2 && c2->pathdist < c->pathdist)
+ goodmoves++;
+ }
+ movesofgood[goodmoves].push_back(c);
+ }
+ }
+ }
+
+void movemonsters() {
+
+ sagefresh = true;
+ turncount++;
+
+ DEBT("ghosts");
+ moveghosts();
+
+ DEBT("normal");
+
+ for(int d=0; d<8; d++) movesofgood[d].clear();
+
+ for(int i=0; ipathdist == INFD)
+ considerEnemyMove(c);
+ }
+
+ for(int d=0; d<8; d++) for(int i=0; imonst))
+ moveNormal(c);
+ }
+
+ if(sagefresh) sagephase = 0;
+
+ DEBT("worm");
+ int wrm = size(worms);
+ for(int i=0; imonst == moWolfMoved) c->monst = moWolf;
+ if(c->monst == moIvyNext) {
+ c->monst = moIvyHead; ivynext(c);
+ }
+ if(c->monst == moIvyDead)
+ removeIvy(c);
+ if(c->monst == moGolemMoved) c->monst = moGolem;
+ if(c->monst == moKnightMoved) c->monst = moKnight;
+ if(c->monst == moSlimeNextTurn) c->monst = moSlime;
+ if(c->monst == moLesser) c->monst = moLesserM;
+ else if(c->monst == moLesserM) c->monst = moLesser;
+ if(c->monst == moGreater) c->monst = moGreaterM;
+ else if(c->monst == moGreaterM) c->monst = moGreater;
+
+ if(c->wall == waChasm) {
+ c->item = itNone;
+ if(c->monst && c->monst != moGhost && c->monst != moEagle) {
+ if(c->monst != moRunDog) achievement_gain("FALLDEATH1");
+ killMonster(c);
+ }
+ }
+
+ if(c->wall == waBonfire) {
+ if(c->monst && c->monst != moGhost && c->monst != moWitchWinter && c->monst != moWitchGhost) {
+ addMessage(XLAT("%The1 burns!", c->monst));
+ killMonster(c);
+ }
+ }
+
+ if(c->wall == waLake) {
+ c->item = itNone;
+ if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM)
+ c->monst = moGreaterShark;
+ if(c->monst && c->monst != moShark && c->monst != moGreaterShark &&
+ c->monst != moGhost && c->monst != moEagle) killMonster(c);
+ }
+
+ if(c->monst && cellUnstable(c) && c->monst != moGhost && c->monst != moEagle) {
+ c->wall = waChasm;
+ }
+ }
+
+ DEBT("shadow");
+ moveshadow();
+
+ DEBT("wandering");
+ wandering();
+ }
+
+// move heat
+
+vector vinefires;
+
+void heat() {
+ double rate = items[itOrbSpeed] ? .5 : 1;
+ int oldmelt = kills[0];
+
+ /* if(cwt.c->heat > .5) cwt.c->heat += .3;
+ if(cwt.c->heat > 1.) cwt.c->heat += .3;
+ if(cwt.c->heat > 1.4) cwt.c->heat += .5; */
+ if(isIcyLand(cwt.c))
+ cwt.c->heat += (items[itOrbWinter] ? -1.2 : 1.2) * rate;
+
+ vinefires.clear();
+
+ int dcs = size(dcal);
+ for(int i=0; icpdist > 8) break;
+ if(isFire(c) && c->tmp > 0) {
+ if(c->land != laPower) useup(c);
+ if(c->wall == waBonfire) for(int i=0; itype; i++) {
+ cell *c2 = c->mov[i];
+ if(c2 && c2->wall == waVinePlant)
+ vinefires.push_back(c2);
+ // both halfvines have to be near fire at once
+ if(c2 && cellHalfvine(c2) && c->mov[(i+1)%c->type]->wall == c2->wall)
+ vinefires.push_back(c2);
+ }
+
+ // two semifires are required to spread
+ if(c->wall == waPartialFire) for(int i=0; itype; i++) {
+ cell *c2 = c->mov[i];
+ if(c2 && (c2->wall == waVinePlant)) {
+ for(int j=0; jtype; j++) if(c2->mov[j] && c2->mov[j]->wall == waPartialFire &&
+ c2->mov[j] != c)
+ vinefires.push_back(c2);
+ }
+ }
+ }
+ if(!isIcyLand(c)) continue;
+ if(c->monst == moRanger) c->heat += 3 * rate;
+ if(c->monst == moDesertman) c->heat += 4 * rate;
+ if(c->monst == moMonkey) c->heat += rate;
+ if(c->wall == waDeadTroll) c->heat -= 2 * rate;
+ if(c->wall == waBigStatue) c->heat -= 3 * rate;
+ if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM)
+ c->heat += (c->land == laCocytus ? 1.5 : 10) * rate;
+ if(c->monst == moGreaterShark)
+ c->heat += 2 * rate;
+ if(c->monst == moCultist) c->heat += 3 * rate;
+ if(c->monst == moCultistLeader) c->heat += 4 * rate;
+ if(c->monst == moPyroCultist) c->heat += 6 * rate;
+ if(c->monst == moGhost) c->heat -= rate;
+ if(c->wall == waBonfire && c->tmp > 0) c->heat += 4 * rate;
+
+ ld hmod = 0;
+
+ for(int j=0; jtype; j++) if(c->mov[j]) {
+ if(!isIcyLand(c->mov[j])) {
+ // make sure that we can still enter Cocytus,
+ // it won't heat up right away even without Orb of Winter or Orb of Speed
+ if(c->mov[j] == cwt.c && (c->land == laIce || items[itOrbWinter]))
+ hmod += rate * (items[itOrbWinter] ? -1.2 : 1.2) / 4;
+ continue;
+ }
+ ld hdiff = c->mov[j]->heat - c->heat;
+ hdiff /= 10;
+ if(c->mov[j]->cpdist <= 8)
+ c->mov[j]->heat -= hdiff;
+ else
+ hdiff = -c->heat / 250;
+ hmod += hdiff;
+ }
+
+ c->heat += hmod * rate;
+ if(c->monst == moCrystalSage && c->heat >= SAGEMELT) {
+ addMessage(XLAT("%The1 melts away!", c->monst));
+ killMonster(c);
+ }
+ }
+
+ for(int i=0; iwall == waIcewall && c->heat > .4) c->wall = waNone, kills[0]++;
+ if(c->wall == waFrozenLake && c->heat > .6) c->wall = waLake, kills[0]++;
+ if(c->wall == waLake && c->heat < -.4 && c->monst != moGreaterShark) {
+ c->wall = waFrozenLake;
+ if(c->monst == moShark) {
+ addMessage(XLAT("%The1 is frozen!", c->monst));
+ killMonster(c);
+ }
+ }
+ }
+
+ for(int i=0; iwall == waVinePlant) {
+ if(c->monst == moVineBeast) killMonster(c);
+ c->wall = waBonfire, c->tmp = 6;
+ }
+ else if(cellHalfvine(c)) destroyHalfvine(c, waPartialFire);
+ }
+
+ if(kills[0] != oldmelt) bfs();
+ }
+
+bool gardener = false;
+
+void livecaves() {
+ int dcs = size(dcal);
+
+ vector bringlife;
+
+ for(int i=0; icpdist > 8) break;
+
+ if(c->wall != waCavefloor && c->wall != waCavewall) continue;
+
+ // if(c->wall == waThumper || c->wall == waBonfire) continue;
+ c->tmp = 0;
+ if(c->monst == moDarkTroll) c->monst = moTroll;
+ if(c->item || c->monst || c->cpdist == 0) continue;
+ for(int j=0; jtype; j++) if(c->mov[j]) {
+ if(c->mov[j]->wall == waDeadfloor) c->tmp++, bringlife.push_back(c->mov[j]);
+ else if(c->mov[j]->wall == waDeadwall || c->mov[j]->wall == waDeadfloor2) c->tmp--, bringlife.push_back(c->mov[j]);
+ else if(c->mov[j]->wall == waCavefloor) c->tmp++;
+ else if(c->mov[j]->wall == waCavewall) c->tmp--;
+ else if(c->mov[j]->wall == waDeadTroll) c->tmp -= 5;
+ else if(c->mov[j]->wall == waVinePlant) c->tmp--;
+ else if(c->mov[j]->wall != waBarrier) c->tmp += 5;
+ if(c->mov[j]->cpdist == 0 && items[itOrbDigging]) c->tmp+=100;
+ if(c->mov[j]->wall == waThumper && c->mov[j]->tmp > 0) c->tmp+=100;
+ if(c->mov[j]->wall == waBonfire) c->tmp+=100;
+ if(c->mov[j]->wall == waBigStatue) c->tmp-=100;
+ if(c->mov[j]->item) c->tmp+=2;
+ if(c->mov[j]->monst == moZombie) c->tmp += 10;
+ if(c->mov[j]->monst == moGhost) c->tmp += 10;
+ if(c->mov[j]->monst == moNecromancer) c->tmp += 10;
+ if(c->mov[j]->monst == moWormtail) c->tmp++;
+ if(c->mov[j]->monst == moTentacletail) c->tmp-=2;
+ if(isIvy(c->mov[j])) c->tmp--;
+ if(isDemon(c->mov[j])) c->tmp-=3;
+ // if(c->mov[j]->monst) c->tmp++;
+ // if(c->mov[j]->monst == moTroll) c->tmp -= 3;
+ }
+ }
+
+ for(int i=0; icpdist > 8) break;
+ if(c->wall != waCavefloor && c->wall != waCavewall) continue;
+// if(c->land != laCaves) continue;
+// if(c->wall == waThumper || c->wall == waBonfire) continue;
+
+ if(c->tmp > 0) c->wall = waCavefloor;
+ if(c->tmp < 0) {
+ c->wall = waCavewall;
+ if(c->land != laCaves && c->land != laDeadCaves && c->land != laFjord && !gardener) {
+ gardener = true;
+ achievement_gain("GARDENER");
+ }
+ }
+ }
+
+ for(int i=0; iwall == waDeadfloor) c->wall = waCavefloor;
+ if(c->wall == waDeadfloor2) c->wall = waCavewall;
+ if(c->wall == waDeadwall) c->wall = waCavewall;
+ if(c->wall == waCavewall && c->item) c->wall = waCavefloor;
+ if(c->land == laDeadCaves) c->land = laCaves;
+ if(c->item == itSilver) c->item = itGold;
+ if(c->item == itGreenStone) c->item = itOrbLife;
+ if(c->monst == moEarthElemental) {
+ addMessage(XLAT("%The1 is destroyed by the forces of Life!", c->monst));
+ killMonster(c);
+ c->item = itOrbDigging;
+ }
+ }
+ }
+
+void dryforest() {
+ int dcs = size(dcal);
+ for(int i=0; icpdist > 8) break;
+ if(c->land != laDryForest) continue;
+ if(c->wall == waThumper) continue;
+ if(c->wall == waBonfire)
+ c->heat = 0;
+
+ for(int j=0; jtype; j++) if(c->mov[j]) {
+ if(c->mov[j]->wall == waBonfire) c->heat++;
+ }
+ }
+
+ for(int i=0; icpdist > 8) break;
+ if(c->land != laDryForest) continue;
+ if(c->wall == waNone && c->heat >= 10)
+ burnMonstersAndItems(c, 50);
+ if((c->wall == waDryTree || c->wall == waWetTree || c->wall == waBonfire) && c->heat >= 1)
+ c->wall = waBonfire, c->tmp = 50;
+ }
+
+/*
+ for(int i=0; itmp = 0;
+ c->heat = 0;
+ if(c->cpdist > 8) break;
+ if(c->land != laDryForest) continue;
+
+ for(int j=0; jtype; j++) if(c->mov[j]) {
+ if(c->mov[j]->wall == waWetTree)
+ c->tmp++;
+ if(c->mov[j]->wall == waDryTree)
+ c->heat++;
+ }
+ }
+
+ for(int i=0; itype - c->tmp - int(c->heat);
+ if(c->tmp > a && c->tmp > c->heat)
+ c->wall = waWetTree;
+ else if(c->heat > a && c->heat > c->tmp)
+ c->wall = waDryTree;
+ else if(a > c->heat && a > c->tmp)
+ c->wall = waNone;
+ } */
+ }
+
+// mirror management
+
+bool cellMirrorable(cell *c) {
+ return
+ c->wall == waNone || c->wall == waCavefloor || c->wall == waFloorA || c->wall == waFloorB ||
+ c->wall == waFrozenLake || c->wall == waDeadfloor || c->wall == waDeadfloor2;
+ }
+
+void castLightningBolt(cellwalker lig);
+
+void createMM(cellwalker& cw, eMonster type) {
+ if(type == moLightningBolt)
+ castLightningBolt(cw);
+ else if(cw.c->monst == moNone && cellMirrorable(cw.c) && cw.c != cwt.c) {
+ cw.c->monst = type;
+ cw.c->mondir = cw.spin;
+ }
+ }
+
+void createMirrors(cell *c, int dir, eMonster type) {
+ cellwalker C(c, dir);
+
+ if(type == moMirror) type = moMirage;
+ else if(type == moMirage) type = moMirror;
+
+ for(int i=0; i<6; i++) {
+ cwstep(C);
+ if(C.c->type == 6) {
+ cwspin(C, i);
+ createMM(C, type);
+ cwspin(C, -i);
+ }
+ cwstep(C);
+ cwspin(C, 1);
+ }
+ }
+
+void createMirages(cell *c, int dir, eMonster type) {
+ cellwalker C(c, dir);
+ for(int i=0; i<6; i++) {
+ cwstep(C);
+ if(C.c->type == 6) {
+ cwspin(C, 2);
+ cwstep(C);
+ cwspin(C, 4-i);
+ createMM(C, type);
+ cwspin(C, 6-4+i);
+ cwstep(C);
+ cwspin(C, 2);
+ cwstep(C);
+ cwspin(C, 2-i);
+ createMM(C, type);
+ cwspin(C, 6-2+i);
+ cwstep(C);
+ cwspin(C, 2);
+ }
+ cwstep(C);
+ cwspin(C, 1);
+ }
+ }
+
+void spinmirrors(int d) {
+
+ for(int i=0; imonst == moMirror)
+ mirrors[i]->mondir = (mirrors[i]->mondir - d + 42) % mirrors[i]->type;
+ if(c->monst == moMirage)
+ mirrors[i]->mondir = (mirrors[i]->mondir + d + 42) % mirrors[i]->type;
+ }
+
+ }
+
+void destroyMirrors() {
+ for(int i=0; imonst;
+ if(isMimic(m)) c->monst = moNone;
+ }
+ mirrors.clear();
+ }
+
+void destroyStrayMirrors() {
+ for(int i=0; icpdist > 7 && isMimic(c)) {
+ c->monst = moNone;
+ }
+ }
+ }
+
+void gomirrors(bool go) {
+ int tk = tkills();
+ int nummirage = 0;
+ mirrors2.clear();
+ for(int i=0; imonst;
+ if(isMimic(m)) {
+ if(m == moMirage) nummirage++;
+ cell *c2 = c->mov[c->mondir];
+ if(c2 && c2->monst != moNone && !isMimic(c2) && isKillable(c2)) {
+ addMessage(XLAT("%The1 destroys %the2!", m, c2->monst));
+ killMonster(c2);
+ }
+ if(!go) continue;
+ c->monst = moNone;
+ if(!c2) continue;
+ stabbingAttack(c, c2, m);
+ if(!passable(c2, c, true, true)) continue;
+ if(isWorm(c2)) continue;
+ if(c2->monst == moGreater) {
+ c2->monst = moLesser; continue;
+ }
+ if(c2->monst == moGreaterM) {
+ c2->monst = moLesserM; continue;
+ }
+ if(c2 == cwt.c) {
+ addMessage(XLAT("You join %the1.", m));
+ continue;
+ }
+ if(isMimic(c2)) {
+ addMessage(XLAT("Two of your images crash and disappear!"));
+ c2->monst = moNone;
+ continue;
+ }
+ if(isIvy(c2)) {
+ // killIvy(c2);
+ continue;
+ }
+ c2->monst = m;
+ c2->mondir = c->spn[c->mondir];
+ mirrors2.push_back(c2);
+ }
+ }
+ for(int i=0; imonst;
+ if(c->wall == waMirror) {
+ addMessage(XLAT("%The1 breaks the mirror!", m));
+ createMirrors(c, c->mondir, m);
+ c->wall = waNone;
+ }
+ if(c->wall == waCloud) {
+ addMessage(XLAT("%The1 disperses the cloud!", m));
+ createMirages(c, c->mondir, m);
+ c->wall = waNone;
+ }
+ }
+ achievement_count("MIRRORKILL", tkills(), tk);
+ achievement_count("MIRAGE", nummirage, 0);
+ }
+
+void reduceOrbPowers() {
+ if(items[itOrbLightning]) items[itOrbLightning]--;
+ if(items[itOrbSpeed]) items[itOrbSpeed]--;
+ if(items[itOrbFlash]) items[itOrbFlash]--;
+ if(items[itOrbShield]) items[itOrbShield]--;
+ if(items[itOrbWinter]) items[itOrbWinter]--;
+ if(items[itOrbFire]) items[itOrbFire]--;
+ if(items[itOrbIllusion]) items[itOrbIllusion]--;
+ if(items[itOrbDragon]) items[itOrbDragon]--;
+ if(items[itOrbPsi]) items[itOrbPsi]--;
+ if(items[itOrbInvis]) items[itOrbInvis]--;
+ if(items[itOrbGhost]) items[itOrbGhost]--;
+ if(items[itOrbDigging]) items[itOrbDigging]--;
+ if(items[itOrbTeleport]) items[itOrbTeleport]--;
+ if(items[itOrbSafety]) items[itOrbSafety]--;
+ if(items[itOrbThorns]) items[itOrbThorns]--;
+ }
+
+void flashAlchemist(cell *c) {
+ if(c->wall == waFloorA || c->wall == waFloorB) {
+ if(cwt.c->wall == waFloorA || cwt.c->wall == waFloorB)
+ c->wall = cwt.c->wall;
+ else
+ c->wall = eWall(c->wall ^ waFloorB ^ waFloorA);
+ }
+ }
+
+void flashCell(cell *c, bool msg) {
+ flashAlchemist(c);
+ if(msg && c->monst && !isWorm(c) && c->monst != moShadow)
+ addMessage(XLAT("%The1 is destroyed by the Flash.", c->monst));
+ killMonster(c);
+ if(isIcyLand(c))
+ c->heat += 2;
+ if(c->land == laDryForest)
+ c->heat += 2;
+ if(c->wall == waCavewall) c->wall = waCavefloor;
+ if(c->wall == waDeadTroll) c->wall = waCavefloor;
+ if(c->wall == waDeadfloor2) c->wall = waDeadfloor;
+ if(c->wall == waDeadwall) c->wall = waDeadfloor2;
+ if(c->wall == waMirror) c->wall = waNone;
+ if(c->wall == waCloud) c->wall = waNone;
+ if(c->wall == waDune) c->wall = waNone;
+ if(c->wall == waAncientGrave) c->wall = waNone;
+ if(c->wall == waFreshGrave) c->wall = waNone;
+ if(c->wall == waColumn) c->wall = waNone;
+ if(c->wall == waGlass) c->wall = waNone;
+ if(c->wall == waDryTree || c->wall == waWetTree) c->wall = waNone;
+ if(c->wall == waBigStatue) c->wall = waNone;
+ if(isActiv(c)) c->tmp = 77;
+ }
+
+extern void drawFlash(cell* c);
+
+void activateFlashFrom(cell *cf) {
+ drawFlash(cf);
+ for(int i=0; itype; t++)
+ for(int u=0; utype; u++)
+ if(c->mov[t] == cf->mov[u] && c->mov[t] != NULL) {
+ flashCell(c, true);
+ }
+ }
+ }
+
+void activateFlash() {
+ int tk = tkills();
+ drawFlash(cwt.c);
+ addMessage(XLAT("You activate the Flash spell!"));
+ items[itOrbFlash] = 0;
+ for(int i=0; icpdist > 2) break;
+ flashCell(c, false);
+ }
+ achievement_count("FLASH", tkills(), tk);
+ }
+
+bool barrierAt(cellwalker& c, int d) {
+ if(d >= 7) return true;
+ if(d <= -7) return true;
+ d = c.spin + d + 42;
+ d%=c.c->type;
+ if(!c.c->mov[d]) return true;
+ if(c.c->mov[d]->wall == waBarrier) return true;
+ return false;
+ }
+
+void castLightningBolt(cellwalker lig) {
+ int bnc = 0;
+ while(true) {
+ // printf("at: %p i=%d d=%d\n", lig.c, i, lig.spin);
+ if(lig.c->mov[lig.spin] == 0) break;
+ cwstep(lig);
+
+ cell *c = lig.c;
+
+ flashAlchemist(c);
+ killMonster(c);
+ if(isIcyLand(c) || c->land == laDryForest) c->heat += 2;
+ c->ligon = 1;
+
+ bool brk = false, spin = false;
+
+ if(c->wall == waCavewall) c->wall = waCavefloor, brk = true;
+ if(c->wall == waDeadTroll) c->wall = waCavefloor, brk = true;
+ if(c->wall == waDeadfloor2)c->wall = waDeadfloor;
+ if(c->wall == waDeadwall) c->wall = waDeadfloor2, brk = true;
+ if(c->wall == waGlass) c->wall = waNone, spin = true;
+ if(c->wall == waDune) c->wall = waNone, brk = true;
+ if(c->wall == waIcewall) c->wall = waNone, brk = true;
+ if(c->wall == waAncientGrave) c->wall = waNone, spin = true;
+ if(c->wall == waFreshGrave) c->wall = waNone, spin = true;
+ if(c->wall == waBigStatue) c->wall = waNone, spin = true;
+ if(c->wall == waColumn) c->wall = waNone, spin = true;
+ if(isActiv(c)) c->tmp = 77;
+ if(c->wall == waDryTree || c->wall == waWetTree || c->wall == waVinePlant) {
+ burnMonstersAndItems(c, 4);
+ brk = true;
+ }
+ if(cellHalfvine(c) && c->mov[lig.spin] && c->wall == c->mov[lig.spin]->wall) {
+ destroyHalfvine(c, waPartialFire, 4);
+ brk = true;
+ }
+
+ if(c == cwt.c) {bnc++; if(bnc > 10) break; }
+ if(spin) cwspin(lig, rand() % lig.c->type);
+
+ if(brk) break;
+
+ if(c->wall == waBarrier || c->wall == waCamelot) {
+ int left = -1;
+ int right = 1;
+ while(barrierAt(lig, left)) left--;
+ while(barrierAt(lig, right)) right++;
+ cwspin(lig, -(right + left));
+ bnc++; if(bnc > 10) break;
+ }
+ else {
+ cwspin(lig, 3);
+ if(c->type == 7) cwspin(lig, rand() % 2);
+ }
+
+ if(c->wall == waCloud) {
+ c->wall = waNone;
+ createMirages(c, lig.spin, moLightningBolt);
+ }
+
+ if(c->wall == waMirror) {
+ c->wall = waNone;
+ createMirrors(c, lig.spin, moLightningBolt);
+ break;
+ }
+ }
+ }
+
+void activateLightning() {
+ int tk = tkills();
+ extern void drawLightning();
+ drawLightning();
+ addMessage(XLAT("You activate the Lightning spell!"));
+ items[itOrbLightning] = 0;
+ for(int i=0; itype; i++)
+ castLightningBolt(cellwalker(cwt.c, i));
+ achievement_count("LIGHTNING", tkills(), tk);
+ }
+
+// move the PC in direction d (or stay in place for d == -1)
+
+bool canmove = true;
+
+bool checkNeedMove(bool checkonly) {
+ if(items[itOrbGhost] > 1) return false;
+ if(cwt.c->wall == waRoundTable) {
+ if(checkonly) return true;
+ addMessage(XLAT("It would be impolite to land on the table!"));
+ }
+ else if(cwt.c->wall == waLake) {
+ if(checkonly) return true;
+ addMessage(XLAT("Ice below you is melting! RUN!"));
+ }
+ else if(isFire(cwt.c) && items[itOrbShield] < 2 && !items[itOrbWinter]) {
+ if(checkonly) return true;
+ addMessage(XLAT("This spot will be burning soon! RUN!"));
+ }
+ else if(items[itOrbGhost] == 1 && !player_passable(cwt.c, NULL, false)) {
+ if(checkonly) return true;
+ addMessage(XLAT("Your Aether power has expired! RUN!"));
+ }
+ else if(cwt.c->wall == waChasm) {
+ if(checkonly) return true;
+ addMessage(XLAT("The floor has collapsed! RUN!"));
+ }
+ else return false;
+ return true;
+ }
+
+#define YDIST 101
+
+struct yendorinfo {
+ cell *path[YDIST];
+ bool found;
+ };
+
+vector yi;
+
+int yii = 0;
+
+bool checkYendor(cell *yendor, bool checkonly) {
+ int byi = size(yi);
+ for(int i=0; itype);
+
+ cell *prev = yendor;
+
+ for(int i=0; itype == 7) cwspin(lig, rand() % 2);
+
+ setdist(lig.c, 10, prev);
+ setdist(lig.c, 9, prev);
+ }
+
+ nyi.path[YDIST-1] = lig.c;
+ nyi.found = false;
+
+ cell *key = lig.c;
+
+ for(int b=10; b>=7; b--) setdist(key, b, prev);
+
+ for(int i=-1; itype; i++) {
+ cell *c2 = i >= 0 ? key->mov[i] : key;
+ c2->monst = moNone; c2->item = itNone;
+ if(!passable(c2, NULL, true, true)) {
+ if(c2->wall == waCavewall) c2->wall = waCavefloor;
+ else if(c2->wall == waDeadwall) c2->wall = waDeadfloor2;
+ else if(c2->wall == waLake) c2->wall = waFrozenLake;
+ else c2->wall = waNone;
+ }
+ }
+ key->item = itKey;
+
+ yi.push_back(nyi);
+ }
+ yii = byi;
+ addMessage(XLAT("You need to find the right Key to unlock this Orb of Yendor!"));
+ achievement_gain("YENDOR1");
+ return false;
+ }
+
+int countMyGolems() {
+ int g=0, dcs = size(dcal);
+ for(int i=0; imonst == moGolem) g++;
+ }
+ return g;
+ }
+
+void restoreGolems(int qty) {
+ int dcs = size(dcal);
+ for(int i=1; qty && imonst = moGolem, qty--;
+ }
+ }
+
+void activateSafety(eLand l) {
+ extern void drawSafety();
+ int g = countMyGolems();
+ drawSafety();
+ addMessage(XLAT("You fall into a wormhole!"));
+ eLand f = firstland;
+ if(l == laTemple) l = laRlyeh;
+ if(l == laCamelot) l = laCrossroads;
+ firstland = l;
+ for(int i=0; i<65536; i++) euland[i] = laNone;
+ euland[0] = euland[1] = firstland;
+ safety = true;
+ clearMemory();
+ initcells();
+ initgame();
+ firstland = f;
+ safety = false;
+ restoreGolems(g);
+ extern void restartGraph();
+ restartGraph();
+ }
+
+bool hasSafeOrb(cell *c) {
+ return
+ c->item == itOrbSafety ||
+ c->item == itOrbShield ||
+ c->item == itOrbYendor;
+ }
+
+
+bool movepcto(int d, bool checkonly = false);
+
+void checkmove() {
+ canmove = false;
+ if(movepcto(-1, true)) canmove = true;
+ for(int i=0; itype; i++)
+ if(movepcto(1, true)) canmove = true;
+ if(!canmove)
+ achievement_final(true);
+ if(canmove && timerstopped) {
+ timerstart = time(NULL);
+ timerstopped = false;
+ }
+ }
+
+// move the PC. Warning: a very long function! todo: refactor
+
+void placeGolem(cell *on, cell *moveto, eMonster m) {
+ if(passable(on, moveto, false, false))
+ cwt.c->monst = m;
+ else if(on->wall == waBonfire)
+ addMessage(XLAT("%The1 burns!", m));
+ }
+
+void movecost(cell* from, cell *to) {
+ if(from->land == laPower && to->land != laPower) {
+ int n=0;
+ for(int i=0; i= 2 && i != itOrbFire)
+ items[i] = 2, n++;
+ if(n)
+ addMessage(XLAT("As you leave, your powers are drained!"));
+ }
+ }
+
+enum orbAction { roMouse, roKeyboard, roCheck, roMouseForce };
+
+// roCheck: return orb type if successful, 0 otherwise
+// roMouse/roKeyboard:
+// return orb type if successful, eItem(-1) if do nothing, 0 otherwise
+eItem targetRangedOrb(cell *c, orbAction a);
+
+bool haveRangedOrb() {
+ return
+ items[itOrbPsi] || items[itOrbDragon] || items[itOrbTeleport] ||
+ items[itOrbIllusion];
+ }
+
+bool isRangedOrb(eItem i) {
+ return i == itOrbPsi || i == itOrbDragon || i == itOrbTeleport || i == itOrbIllusion;
+ }
+
+bool haveRangedTarget() {
+ if(!haveRangedOrb())
+ return false;
+ for(int i=0; i= 0) {
+ cwspin(cwt, d);
+ spinmirrors(d);
+ d = cwt.spin;
+ }
+ if(d != -1 && !checkonly) playermoved = true;
+ if(!checkonly) invismove = false;
+ if(d >= 0) {
+ cell *c2 = cwt.c->mov[d];
+
+ if(!player_passable(c2, cwt.c, false) && items[itOrbFlash]) {
+ if(checkonly) return true;
+ activateFlash();
+ checkmove();
+ return true;
+ }
+
+ if(!player_passable(c2, cwt.c, false) && items[itOrbLightning]) {
+ if(checkonly) return true;
+ activateLightning();
+ checkmove();
+ return true;
+ }
+
+ if(isActiv(c2) && c2->tmp == -1) {
+ if(checkonly) return true;
+ addMessage(XLAT("You activate %the1.", c2->wall));
+ c2->tmp = 100;
+ checkmove();
+ return true;
+ }
+
+ if((c2->wall == waThumper/* || (c2->wall == waBigStatue && c2->type == 6)*/) && !monstersnear(c2) && !c2->monst) {
+ eWall w = c2->wall;
+ cellwalker push = cwt;
+ cwstep(push);
+ cwspin(push, 3);
+ cwstep(push);
+/* if(w == waBigStatue && push.c->type == 7) {
+ if(checkonly) return false;
+ addMessage(XLAT("%The1 is too heavy to put it back on the pedestal.", c2->wall));
+ return false;
+ } */
+ if((!passable(push.c, c2, false, true) || !passable(push.c, cwt.c, false, true) || push.c->item) && c2->type == 7) {
+ cwstep(push);
+ cwspin(push, 1);
+ cwstep(push);
+ }
+ if(!passable(push.c, c2, false, true) || !passable(push.c, cwt.c, false, true) || push.c->item) {
+ if(checkonly) return false;
+ addMessage(XLAT("No room to push %the1.", c2->wall));
+ return false;
+ }
+ if(checkonly) return true;
+ addMessage(XLAT("You push %the1.", c2->wall));
+ push.c->tmp = c2->tmp;
+ if(c2->land == laAlchemist)
+ c2->wall = (cwt.c->wall == waFloorB || cwt.c->wall == waFloorA) ? cwt.c->wall : push.c->wall;
+ else c2->wall = waNone;
+ push.c->wall = w;
+ }
+
+/* if((c2->wall == waBigStatue) && c2->type == 7 && !monstersnear(c2)) {
+ int q = 0;
+ for(int i=3; i<=4; i++) {
+ cellwalker push = cwt;
+ cwstep(push);
+ cwspin(push, i);
+ cwstep(push);
+ if(passable(push.c, c2, false, true)) q++;
+ }
+ if(!q) {
+ if(checkonly) return false;
+ addMessage(XLAT("No room to push %the1.", c2->wall));
+ return false;
+ }
+ if(checkonly) return true;
+ addMessage(XLAT("You push %the1.", c2->wall));
+ c2->wall = waNone;
+ for(int i=3; i<=4; i++) {
+ cellwalker push = cwt;
+ cwstep(push);
+ cwspin(push, i);
+ cwstep(push);
+ if(passable(push.c, c2, false, true))
+ push.c->wall = waBigStatue;
+ }
+ } */
+
+ if(c2->wall == waBigStatue && !monstersnear(c2) && !c2->monst) {
+ if(!canPushStatueOn(cwt.c)) {
+ if(checkonly) return false;
+ if(cwt.c->wall == waBonfire)
+ addMessage(XLAT("You have to escape first!"));
+ else
+ addMessage(XLAT("There is not enough space!"));
+ return false;
+ }
+
+ if(checkonly) return true;
+ addMessage(XLAT("You push %the1 behind you!", c2->wall));
+ c2->wall = cwt.c->wall;
+ if(cellUnstable(cwt.c))
+ cwt.c->wall = waChasm;
+ else
+ cwt.c->wall = waBigStatue;
+ }
+
+ if(c2->wall == waDryTree && !monstersnear(cwt.c)) {
+ if(checkonly) return true;
+ addMessage(XLAT("You start cutting down the tree."));
+ c2->wall = waWetTree;
+ }
+ else if(c2->wall == waWetTree && !monstersnear(cwt.c)) {
+ if(checkonly) return true;
+ addMessage(XLAT("You cut down the tree."));
+ c2->wall = waNone;
+ }
+ else if(c2->monst == moKnight) {
+ if(checkonly) return false;
+
+ bool tooeasy = (roundTableRadius(c2) < newRoundTableRadius());
+ if(grailWasFound(cwt.c) && rand() % 5 == 0) {
+ addMessage(XLAT("\"I would like to congratulate you again!\""));
+ }
+ else if(rand() % 5 != 0 && !tooeasy) {
+ static int i;
+ i++;
+ if(i%2)
+ addMessage(XLAT("\"Find the Holy Grail to become one of us!\""));
+ else
+ addMessage(XLAT("\"The Holy Grail is in the center of the Round Table.\""));
+ }
+ else {
+ int i = rand() % 3;
+ if(i == 0)
+ addMessage(XLAT("\"I enjoy watching the hyperbug battles.\""));
+ if(i == 1)
+ addMessage(XLAT("\"Have you visited a temple in R'Lyeh?\""));
+ if(i == 2)
+ addMessage(XLAT("\"Nice castle, eh?\""));
+ }
+ return false;
+ }
+ else if(c2->monst && !isFriendly(c2)) {
+ if(c2->monst == moWorm || c2->monst == moWormtail || c2->monst == moWormwait) {
+ if(checkonly) return false;
+ addMessage(XLAT("You cannot attack Sandworms directly!"));
+ return false;
+ }
+
+ if(attackingForbidden(c2, cwt.c)) {
+ if(checkonly) return false;
+ addMessage(XLAT("You cannot attack through the Vine!"));
+ return false;
+ }
+
+ if(c2->monst == moTentacle || c2->monst == moTentacletail || c2->monst == moTentaclewait) {
+ if(checkonly) return false;
+ addMessage(XLAT("You cannot attack Tentacles directly!"));
+ return false;
+ }
+
+/* if(isBug(c2)) {
+ if(checkonly) return false;
+ addMessage(XLAT("You cannot win with %the1!", c2->monst));
+ return false;
+ }
+*/
+ if(c2->monst == moHedge && !items[itOrbThorns]) {
+ if(checkonly) return false;
+ addMessage(XLAT("You cannot attack %the1 directly!", c2->monst));
+ addMessage(XLAT("Stab them by walking around them."));
+ return false;
+ }
+
+ if(c2->monst == moFlailer) {
+ if(checkonly) return false;
+ addMessage(XLAT("You cannot attack %the1 directly!", c2->monst));
+ addMessage(XLAT("Make him hit himself by walking away from him."));
+ return false;
+ }
+
+ if(c2->monst == moShadow) {
+ if(checkonly) return false;
+ addMessage(XLAT("You cannot defeat the Shadow!"));
+ return false;
+ }
+
+ if(c2->monst == moGreater || c2->monst == moGreaterM) {
+ if(checkonly) return false;
+ addMessage(XLAT("You cannot defeat the Greater Demon yet!"));
+ return false;
+ }
+
+ if(monstersnear(cwt.c, c2)) {
+ if(checkonly) return false;
+ addMessage(XLAT("You would be killed by %the1!", which));
+ return false;
+ }
+
+ if(checkNeedMove(checkonly))
+ return false;
+
+ if(checkonly) return true;
+ addMessage(XLAT("You kill %the1.", c2->monst));
+
+ int mt = c2->monst;
+ int tk = tkills();
+ killMonster(c2);
+
+ int ntk = tkills();
+
+ if(tk == 0 && ntk > 0)
+ addMessage(XLAT("That was easy, but groups could be dangerous."));
+
+ if(tk < 10 && ntk >= 10)
+ addMessage(XLAT("Good to know that your fighting skills serve you well in this strange world."));
+
+ if(tk < 50 && ntk >= 50)
+ addMessage(XLAT("You wonder where all these monsters go, after their death..."));
+
+ if(tk < 100 && ntk >= 100)
+ addMessage(XLAT("You feel that the souls of slain enemies pull you to the Graveyard..."));
+
+ if(mt == moIvyRoot && ntk>tk)
+ achievement_gain("IVYSLAYER");
+
+ gomirrors(0);
+ }
+ else if(!player_passable(c2, cwt.c, true)) {
+ if(checkonly) return false;
+ if(c2->wall == waFloorA || c2->wall == waFloorB)
+ addMessage(XLAT("Wrong color!"));
+ else if(c2->wall == waRoundTable)
+ addMessage(XLAT("It would be impolite to land on the table!"));
+ else
+ addMessage(XLAT("You cannot move through %the1!", c2->wall));
+ return false;
+ }
+ else if(c2->land == laGameBoard) {
+ // do not pick up!
+ if(checkonly) return true;
+ flipplayer = true;
+ cwstep(cwt);
+ setdist(cwt.c, 0, NULL);
+ bfs();
+ checkmove();
+ return true;
+ }
+ else {
+ if(c2->item == itOrbYendor && !checkYendor(c2, checkonly)) {
+ return false;
+ }
+ if(c2->item == itHolyGrail) {
+ if(roundTableRadius(c2) < newRoundTableRadius()) {
+ if(checkonly) return false;
+ addMessage(XLAT("That was not a challenge. Find a larger castle!"));
+ return false;
+ }
+ }
+ if(!hasSafeOrb(c2) && monstersnear(c2)) {
+ if(checkonly) return false;
+
+ if(items[itOrbFlash]) {
+ if(checkonly) return true;
+ activateFlash();
+ checkmove();
+ return true;
+ }
+
+ if(items[itOrbLightning]) {
+ if(checkonly) return true;
+ activateLightning();
+ checkmove();
+ return true;
+ }
+
+ addMessage(XLAT("%The1 would kill you there!", which));
+ return false;
+ }
+ if(checkonly) return true;
+ flipplayer = true;
+ if(c2->item && c2->land == laAlchemist) c2->wall = cwt.c->wall;
+ if(c2->wall == waRoundTable) {
+ addMessage(XLAT("You jump over the table!"));
+ }
+
+ if(cwt.c->wall == waRoundTable) {
+ int dd = celldistAltRelative(c2) - celldistAltRelative(cwt.c);
+
+ bool tooeasy = (roundTableRadius(c2) < newRoundTableRadius());
+
+ if(dd>0) {
+ if(grailWasFound(cwt.c)) {
+ addMessage(XLAT("The Knights congratulate you on your success!"));
+ knighted = roundTableRadius(cwt.c);
+ }
+ else if(!tooeasy)
+ addMessage(XLAT("The Knights laugh at your failure!"));
+ }
+ else {
+ if(grailWasFound(cwt.c))
+ addMessage(XLAT("The Knights stare at you!"));
+ else if(tooeasy)
+ addMessage(XLAT("Come on, this is too easy... find a bigger castle!"));
+ else
+ addMessage(XLAT("The Knights wish you luck!"));
+ }
+ }
+
+ int pg = gold();
+ bool dopickup = true;
+
+ invismove = items[itOrbInvis] > 0;
+ if(items[itOrbFire]) {
+ invismove = false;
+ firetrail(cwt.c);
+ }
+
+ if(items[itOrbDigging]) {
+ invismove = false;
+ earthMove(cwt.c, d);
+ }
+
+ if(c2->item) {
+ invismove = false;
+ string s0 = "";
+ if(0) ;
+ if(gold() == 0)
+ addMessage(XLAT("Wow! %1! This trip should be worth it!", c2->item));
+ else if(gold() == 1)
+ addMessage(XLAT("For now, collect as much treasure as possible..."));
+ else if(gold() == 2)
+ addMessage(XLAT("Prove yourself here, then find new lands, with new quests..."));
+ else if(!items[c2->item] && itemclass(c2->item) == IC_TREASURE)
+ addMessage(XLAT("You collect your first %1!", c2->item));
+ else if(c2->item == itKey)
+ addMessage(XLAT("You have found the Key! Now unlock this Orb of Yendor!"));
+ else if(c2->item == itGreenStone && !items[itGreenStone])
+ addMessage(XLAT("This orb is dead..."));
+ else if(c2->item == itGreenStone)
+ addMessage(XLAT("Another Dead Orb."));
+ else if(itemclass(c2->item) != IC_TREASURE)
+ addMessage(XLAT("You have found %the1!", c2->item));
+ else if(items[c2->item] == 4 && maxgold() == 4) {
+ addMessage(XLAT("You feel that %the2 become%s2 more dangerous.", c2->item, c2->land));
+ addMessage(XLAT("With each %1 you collect...", c2->item, c2->land));
+ }
+ else if(items[c2->item] == 9 && maxgold() == 9)
+ addMessage(XLAT("Are there any magical orbs in %the1?...", c2->land));
+ else if(items[c2->item] == 10 && maxgold() == 10) {
+ addMessage(XLAT("You feel that %the1 slowly become%s1 dangerous...", c2->land));
+ addMessage(XLAT("Better find some other place."));
+ }
+ else if(c2->item == itSpice && items[itSpice] == 7)
+ addMessage(XLAT("You have a vision of the future, fighting demons in Hell..."));
+ else if(c2->item == itElixir && items[itElixir] == 4)
+ addMessage(XLAT("With this Elixir, your life should be long and prosperous..."));
+ else if(c2->item == itBone && items[itBone] == 6)
+ addMessage(XLAT("The Necromancer's Totem contains hellish incantations..."));
+ else if(c2->item == itStatue && items[itStatue] == 6)
+ addMessage(XLAT("The inscriptions on the Statue of Cthulhu point you toward your destiny..."));
+ else if(c2->item == itStatue && items[itStatue] == 4)
+ addMessage(XLAT("There must be some temples of Cthulhu in R'Lyeh..."));
+ else if(c2->item == itDiamond && items[itDiamond] == 8)
+ addMessage(XLAT("Still, even greater treasures lie ahead..."));
+ else if(c2->item == itFernFlower && items[itFernFlower] == 4)
+ addMessage(XLAT("You overheard Hedgehog Warriors talking about emeralds..."));
+ else if(c2->item == itEmerald && items[itEmerald] == 4)
+ addMessage(XLAT("You overhear miners talking about a castle..."));
+ else if(c2->item == itEmerald && items[itEmerald] == 5)
+ addMessage(XLAT("A castle in the Crossroads..."));
+ else {
+ string t = XLAT("You collect %the1.", c2->item);
+ addMessage(t);
+ }
+ }
+
+ if(c2->item == itOrbSpeed) {
+ items[c2->item] += 31;
+ if(items[c2->item] > 67) items[c2->item] = 67;
+ }
+ else if(c2->item == itOrbLife) {
+ placeGolem(cwt.c, c2, moGolem);
+ }
+ else if(c2->item == itOrbSafety) {
+ items[c2->item] += 7;
+ activateSafety(c2->land);
+ return true;
+ }
+ else if(c2->item == itOrbLightning) {
+ items[c2->item] += 78;
+ if(items[c2->item] > 777) items[c2->item] = 777;
+ }
+ else if(c2->item == itOrbThorns) {
+ items[c2->item] += 78;
+ if(items[c2->item] > 151) items[c2->item] = 151;
+ }
+ else if(c2->item == itOrbFlash) {
+ items[c2->item] += 78;
+ if(items[c2->item] > 777) items[c2->item] = 777;
+ }
+ else if(c2->item == itOrbShield) {
+ items[c2->item] += 16;
+ if(items[c2->item] > 77) items[c2->item] = 77;
+ }
+ else if(c2->item == itOrbWinter) {
+ items[c2->item] += 31;
+ if(items[c2->item] > 77) items[c2->item] = 77;
+ }
+ else if(c2->item == itOrbFire) {
+ items[c2->item] += 31;
+ if(items[c2->item] > 77) items[c2->item] = 77;
+ }
+ else if(c2->item == itOrbDragon) {
+ items[c2->item] += 78;
+ if(items[c2->item] > 111) items[c2->item] = 111;
+ }
+ else if(c2->item == itOrbIllusion) {
+ items[c2->item] += 78;
+ if(items[c2->item] > 111) items[c2->item] = 111;
+ }
+ else if(c2->item == itOrbPsi) {
+ items[c2->item] += 78;
+ if(items[c2->item] > 111) items[c2->item] = 111;
+ }
+ else if(c2->item == itOrbInvis) {
+ items[c2->item] += 31;
+ if(items[c2->item] > 77) items[c2->item] = 77;
+ }
+ else if(c2->item == itOrbGhost) {
+ items[c2->item] += 31;
+ if(items[c2->item] > 77) items[c2->item] = 77;
+ }
+ else if(c2->item == itOrbDigging) {
+ items[c2->item] += 78;
+ if(items[c2->item] > 101) items[c2->item] = 101;
+ }
+ else if(c2->item == itOrbTeleport) {
+ items[c2->item] += 78;
+ if(items[c2->item] > 201) items[c2->item] = 201;
+ }
+ else if(c2->item == itOrbYendor) {
+ for(int i=0; i<4; i++) switch(rand() % 12) {
+ case 0: items[itOrbSpeed] += 31; break;
+ case 1: items[itOrbLightning] += 78; break;
+ case 2: items[itOrbFlash] += 78; break;
+ case 3: items[itOrbShield] += 31; break;
+ case 4: items[itOrbWinter] += 151; break;
+ case 5: items[itOrbDigging] += 151; break;
+ case 6: items[itOrbTeleport] += 151; break;
+ case 7: items[itOrbThorns] += 151; break;
+ case 8: items[itOrbInvis] += 151; break;
+ case 9: items[itOrbPsi] += 151; break;
+ case 10: items[itOrbGhost] += 151; break;
+ case 11: items[itOrbFire] += 151; break;
+ }
+ items[itOrbYendor]++;
+ items[itKey]--;
+ addMessage(XLAT("CONGRATULATIONS!"));
+ achievement_collection(itOrbYendor, pg, gold());
+ achievement_victory(false);
+ }
+ else if(c2->item == itHolyGrail) {
+ int v = newRoundTableRadius() + 12;
+ items[itOrbTeleport] += v;
+ items[itOrbSpeed] += v;
+ items[itHolyGrail]++;
+ addMessage(XLAT("Congratulations! You have found the Holy Grail!"));
+ if(!euclid) c2->master->alt->fjordval |= GRAIL_FOUND;
+ achievement_collection(c2->item, pg, gold());
+ }
+ else if(c2->item == itKey) {
+ for(int i=0; iitem == itGrimoire && items[itGrimoire] > celldistAlt(c2)/-TEMPLE_EACH) {
+ addMessage(XLAT("You already have this Grimoire! Seek new tomes in the inner circles."));
+ dopickup = false;
+ }
+ else {
+ bool lhu = hellUnlocked();
+ if(c2->item) items[c2->item]++;
+ int g2 = gold();
+
+ if(c2->item == itHyperstone && items[itHyperstone] == 10)
+ achievement_victory(true);
+
+ achievement_collection(c2->item, pg, g2);
+
+ if(pg < 15 && g2 >= 15)
+ addMessage(XLAT("Collect treasure to access more different lands..."));
+ if(pg < 30 && g2 >= 30)
+ addMessage(XLAT("You feel that you have enough treasure to access new lands!"));
+ if(pg < 45 && g2 >= 45)
+ addMessage(XLAT("Collect more treasures, there are still more lands waiting..."));
+ if(pg < 60 && g2 >= 60)
+ addMessage(XLAT("You feel that the stars are right, and you can access R'Lyeh!"));
+ if(pg < 75 && g2 >= 75)
+ addMessage(XLAT("Kill monsters and collect treasures, and you may get access to Hell..."));
+ if(pg < 90 && g2 >= 90)
+ addMessage(XLAT("To access Hell, collect 10 treasures each of 9 kinds..."));
+ if(hellUnlocked() && !lhu) {
+ addMessage(XLAT("Abandon all hope, the gates of Hell are opened!"));
+ addMessage(XLAT("And the Orbs of Yendor await!"));
+ }
+ }
+
+ if(dopickup) c2->item = itNone;
+// if(c2->land == laHive)
+// c2->heat = 1;
+
+ int numOrb = 0;
+ for(int i=0; iwall == waNone)
+ cwt.c->wall = waIcewall;
+ }
+
+ movecost(cwt.c, c2);
+
+ if(c2->monst == moGolem || c2->monst == moIllusion) {
+ addMessage(XLAT("You switch places with %the1.", c2->monst));
+ placeGolem(cwt.c, c2, c2->monst);
+ c2->monst = moNone;
+ }
+ else if(c2->monst) {
+ addMessage(XLAT("You rejoin %the1.", c2->monst));
+ killMonster(c2);
+ }
+
+ stabbingAttack(cwt.c, c2);
+ cwstep(cwt);
+
+ gomirrors(1);
+
+ if(c2->wall == waMirror) {
+ invismove = false;
+ addMessage(XLAT("The mirror shatters!"));
+ if(c2->land == laMirror) {
+ int g = gold();
+ items[itShard]++;
+ achievement_collection(itShard, g+1, g);
+ }
+ c2->wall = waNone;
+ createMirrors(cwt.c, cwt.spin, moMirage);
+ }
+
+ if(c2->wall == waGlass && items[itOrbGhost] > 2) {
+ addMessage(XLAT("Your Aether powers are drained by %the1!", c2->wall));
+ items[itOrbGhost] = 2;
+ }
+
+ if(c2->wall == waCloud) {
+ invismove = false;
+ addMessage(XLAT("The cloud turns into a bunch of images!"));
+ if(c2->land == laMirror) {
+ int g = gold();
+ items[itShard]++;
+ achievement_collection(itShard, g+1, g);
+ }
+ c2->wall = waNone;
+ createMirages(cwt.c, cwt.spin, moMirage);
+ }
+
+ if(cellUnstable(c2) && !items[itOrbGhost])
+ c2->wall = waChasm;
+
+ countLocalTreasure();
+ landvisited[cwt.c->land] = true;
+ setdist(cwt.c, 0, NULL);
+ }
+ }
+ else {
+ if(checkNeedMove(checkonly))
+ return false;
+ if(monstersnear(cwt.c)) {
+ if(checkonly) return false;
+ addMessage(XLAT("%The1 would get you!", which));
+ return false;
+ }
+ if(checkonly) return true;
+ if(d == -2 && items[itGreenStone] && cwt.c->item == itNone) {
+ items[itGreenStone]--;
+ if(false) {
+ cwt.c->item = itNone;
+ spill(cwt.c, eWall(cwt.c->wall ^ waFloorA ^ waFloorB), 3);
+ addMessage(XLAT("The slime reacts with %the1!", itGreenStone));
+ }
+ else {
+ cwt.c->item = itGreenStone;
+ addMessage(XLAT("You drop %the1.", itGreenStone));
+ }
+ }
+ else if(d == -2) {
+ if(gold() >= 300)
+ addMessage(XLAT("You feel great, like a true treasure hunter."));
+ else if(gold() >= 200)
+ addMessage(XLAT("Your eyes shine like gems."));
+ else if(gold() >= 100)
+ addMessage(XLAT("Your eyes shine as you glance at your precious treasures."));
+ else if(gold() >= 50)
+ addMessage(XLAT("You glance at your great treasures."));
+ else if(gold() >= 10)
+ addMessage(XLAT("You glance at your precious treasures."));
+ else if(gold() > 0)
+ addMessage(XLAT("You glance at your precious treasure."));
+ else
+ addMessage(XLAT("Your inventory is empty."));
+ }
+ }
+ DEBT("bfs");
+ bfs();
+ destroyStrayMirrors();
+ DEBT("heat");
+ heat();
+ DEBT("rop");
+ int phase1 = (1 & items[itOrbSpeed]);
+ reduceOrbPowers();
+ DEBT("mmo");
+ int phase2 = (1 & items[itOrbSpeed]);
+ if(!phase2) movemonsters();
+ if(cwt.c->land == laPower && !phase1) { bfs(); moveFastMonsters(); }
+ DEBT("lc");
+ if(!phase1) livecaves();
+ if(!phase1) dryforest();
+ DEBT("check");
+ checkmove();
+ DEBT("done");
+ return true;
+ }
+
+/* bool isPsiTarget(cell *dst) {
+ return
+ dst->cpdist > 1 &&
+ dst->monst &&
+ !(isWorm(dst) || dst->monst == moShadow);
+ } */
+
+void teleportTo(cell *dest) {
+ cwt.c->monst = dest->monst;
+ dest->monst = moNone;
+ movecost(cwt.c, dest);
+ cwt.c = dest; cwt.spin = rand() % dest->type; flipplayer = !!(rand() % 2);
+ items[itOrbTeleport] = 0;
+
+ addMessage(XLAT("You teleport to a new location!"));
+ destroyMirrors();
+
+ for(int i=9; i>=0; i--)
+ setdist(cwt.c, i, NULL);
+
+ bfs();
+ checkmove();
+ }
+
+void psi_attack(cell *dest) {
+ addMessage(XLAT("You kill %the1 with a mental blast!", dest->monst));
+ killMonster(dest);
+ items[itOrbPsi] -= 30;
+ if(items[itOrbPsi]<0) items[itOrbPsi] = 0;
+ checkmove();
+ }
+
+bool flammable(cell *c) {
+ return
+ c->wall == waNone || c->wall == waFloorA || c->wall == waFloorB ||
+ c->wall == waCavefloor || c->wall == waDeadfloor ||
+ c->wall == waDryTree || c->wall == waWetTree ||
+ c->wall == waVinePlant || c->wall == waVineHalfA || c->wall == waVineHalfB ||
+ c->wall == waIcewall;
+ }
+
+void placeDragonfire(cell *c) {
+ if(cellUnstable(c))
+ c->wall = waChasm;
+ else if(c->wall == waNone && c->land == laCocytus)
+ c->wall = waLake, c->heat += 20;
+ else if(cellHalfvine(c))
+ flameHalfvine(c, 20);
+ else {
+ c->wall = waBonfire;
+ c->tmp = 20;
+ }
+ if(itemBurns(c->item))
+ addMessage(XLAT("%The1 burns!", c->item)), c->item = itNone;
+ addMessage(XLAT("You throw fire!"));
+ items[itOrbDragon] -= 5;
+ if(items[itOrbDragon]<0) items[itOrbDragon] = 0;
+ checkmove();
+ }
+
+void placeIllusion(cell *c) {
+ c->monst = moIllusion;
+ items[itOrbIllusion] -= 5;
+ if(items[itOrbIllusion]<0) items[itOrbIllusion] = 0;
+ addMessage(XLAT("You create an Illusion!"));
+ checkmove();
+ }
+
+eItem targetRangedOrb(cell *c, orbAction a) {
+ if(!haveRangedOrb()) return itNone;
+ if(c == cwt.c || isNeighbor(cwt.c, c)) {
+ if(a == roKeyboard || a == roMouseForce )
+ addMessage(XLAT("You cannot target that close!"));
+ return itNone;
+ }
+ if(c->cpdist > 7) {
+ if(a != roCheck)
+ addMessage(XLAT("You cannot target that far away!"));
+ return itNone;
+ }
+
+ // (1) switch with an illusion
+ if(items[itOrbTeleport] && c->monst == moIllusion) {
+ if(a != roCheck) teleportTo(c);
+ return itOrbTeleport;
+ }
+
+ // (2) place illusion
+ if(items[itOrbIllusion] && c->monst == moNone && c->item == itNone && passable(c, NULL, false, true)) {
+ if(a != roCheck) placeIllusion(c);
+ return itOrbIllusion;
+ }
+
+ // (3) teleport
+ if(items[itOrbTeleport] && c->monst == moNone && c->item == itNone && passable(c, NULL, false, true)) {
+ if(a != roCheck) teleportTo(c);
+ return itOrbTeleport;
+ }
+
+ // (4) remove an illusion
+ if(items[itOrbIllusion] && c->monst == moIllusion) {
+ if(a != roCheck) {
+ addMessage(XLAT("You take the Illusion away."));
+ items[itOrbIllusion] += 4;
+ c->monst = moNone;
+ }
+ return itOrbIllusion;
+ }
+
+ // (5) psi blast
+ if(items[itOrbPsi] && c->monst && !isWorm(c) && c->monst != moShadow) {
+ if(a != roCheck) psi_attack(c);
+ return itOrbPsi;
+ }
+
+ // (6) place fire
+ if(items[itOrbDragon] && flammable(c)) {
+ if(a != roCheck) placeDragonfire(c);
+ return itOrbDragon;
+ }
+
+ if(a == roCheck) return itNone;
+
+ if(items[itOrbPsi] && c->monst) {
+ addMessage(XLAT("%The1 is immune to mental blasts!", c->monst));
+ }
+ else if(items[itOrbTeleport] && c->monst) {
+ addMessage(XLAT("Cannot teleport on a monster!"));
+ }
+ else if(items[itOrbIllusion] && c->item)
+ addMessage(XLAT("Cannot cast illusion on an item!"));
+ else if(items[itOrbIllusion] && c->monst)
+ addMessage(XLAT("Cannot cast illusion on a monster!"));
+ else if(items[itOrbIllusion] && !passable(c, NULL, false, true))
+ addMessage(XLAT("Cannot cast illusion here!"));
+ else if(items[itOrbTeleport] && c->item) {
+ addMessage(XLAT("Cannot teleport on an item!"));
+ }
+ else if(items[itOrbTeleport] && !passable(c, NULL, false, true)) {
+ addMessage(XLAT("Cannot teleport here!"));
+ }
+ else if(items[itOrbDragon] && !flammable(c)) {
+ addMessage(XLAT("Cannot throw fire there!"));
+ }
+ else return eItem(0);
+
+ return eItem(-1);
+ }
+
+#define MAXBOX 120
+#define POSSCORE 107 // update this when new boxes are added!
+
+struct score {
+ string ver;
+ int box[MAXBOX];
+ };
+
+int savebox[MAXBOX], boxid;
+bool saving;
+
+string boxname[MAXBOX];
+bool fakebox[MAXBOX];
+
+void applyBox(int& t) {
+ if(saving) savebox[boxid++] = t;
+ else t = savebox[boxid++];
+ }
+
+void applyBoxNum(int& i, string name = "") {
+ fakebox[boxid] = (name == "");
+ boxname[boxid] = name;
+ applyBox(i);
+ }
+
+// just skips the value when loading
+void applyBoxSave(int i, string name = "") {
+ fakebox[boxid] = (name == "");
+ boxname[boxid] = name;
+ applyBox(i);
+ }
+
+int applyBoxLoad(string name = "") {
+ fakebox[boxid] = (name == "");
+ boxname[boxid] = name;
+ int i=0; applyBox(i);
+ return i;
+ }
+
+void applyBoxI(eItem it, bool f = false) {
+ boxname[boxid] = iinf[it].name;
+ fakebox[boxid] = f;
+ applyBox(items[it]);
+ }
+
+void applyBoxM(eMonster m, bool f = false) {
+ fakebox[boxid] = f;
+ boxname[boxid] = minf[m].name;
+ applyBox(kills[m]);
+ }
+
+void killbox(eMonster m, int& val) {
+ if(saving) kills[m] = val;
+ else { val = kills[m]; kills[m] = 0; }
+ }
+
+void applyBoxes() {
+
+ applyBoxSave(timerstart, "time elapsed");
+ time_t timer = time(NULL);
+ applyBoxSave(timer, "date");
+ applyBoxSave(gold(), "treasure collected");
+ applyBoxSave(tkills(), "total kills");
+ applyBoxNum(turncount, "turn count");
+ applyBoxNum(cellcount, "cells generated");
+
+ if(!saving) timerstart = time(NULL);
+
+ for(int i=0; iland);
+ else firstland = eLand(applyBoxLoad());
+
+ for(int i=itOrbLightning; i<25; i++) applyBoxI(eItem(i), true);
+
+ applyBoxI(itRoyalJelly);
+ applyBoxI(itWine);
+ applyBoxI(itSilver);
+ applyBoxI(itEmerald);
+ applyBoxI(itPower);
+ applyBoxI(itOrbFire, true);
+ applyBoxI(itOrbInvis, true);
+ applyBoxI(itOrbGhost, true);
+ applyBoxI(itOrbPsi, true);
+ applyBoxM(moBug0);
+ applyBoxM(moBug1);
+ applyBoxM(moBug2);
+ applyBoxM(moVineBeast);
+ applyBoxM(moVineSpirit);
+ applyBoxM(moLancer);
+ applyBoxM(moFlailer);
+ applyBoxM(moEarthElemental);
+ applyBoxM(moDarkTroll);
+ applyBoxM(moWitch);
+ applyBoxM(moWitchFire);
+ applyBoxM(moWitchFlash);
+ applyBoxM(moWitchGhost);
+ applyBoxM(moWitchSpeed);
+ applyBoxM(moEvilGolem);
+ applyBoxM(moWitchWinter);
+ applyBoxI(itHolyGrail);
+ applyBoxI(itGrimoire);
+ applyBoxM(moKnight);
+ applyBoxM(moCultistLeader);
+ }
+
+void saveBox() {
+ boxid = 0; saving = true; applyBoxes();
+ }
+
+void loadBox() {
+ // have boxid
+ boxid = 0; saving = false; applyBoxes();
+ }
+
+// certify that saves and achievements were received
+// in an official version of HyperRogue
+#ifdef CERTIFY
+#include "certify.cpp"
+#else
+bool tampered;
+
+void saveCertificate(FILE *f) {}
+bool loadCertificate(FILE *f, score& sc) {return true; }
+
+int achievement_certify(const char *s, int a, int b, int c) { return 0; }
+
+#endif
+
+void saveStats() {
+ if(euclid) return;
+#ifndef ANDROID
+
+ FILE *f = fopen(scorefile, "at");
+ if(!f) {
+ printf("Could not open the score file '%s'!\n", scorefile);
+ addMessage(XLAT("Could not open the score file: ", scorefile));
+ return;
+ }
+
+ if(showoff) return;
+
+ time_t timer;
+ timer = time(NULL);
+ char sbuf[128]; strftime(sbuf, 128, "%c", localtime(&timerstart));
+ char buf[128]; strftime(buf, 128, "%c", localtime(&timer));
+
+ fprintf(f, "HyperRogue: game statistics (version "VER")\n");
+ if(cheater)
+ fprintf(f, "CHEATER! (cheated %d times)\n", cheater);
+ if(true) {
+
+ fprintf(f, VER);
+ saveBox();
+
+ for(int i=0; i\n", kills[i], minf[i].name, i);
+
+ fprintf(f, "\n\n\n");
+
+ printf("Game statistics saved to %s\n", scorefile);
+ addMessage(XLAT("Game statistics saved to %1", scorefile));
+ fclose(f);
+#endif
+ }
+
+bool havesave = true;
+
+#ifndef ANDROID
+// load the save
+void loadsave() {
+
+ printf("Trying to load a save.\n");
+ FILE *f = fopen(scorefile, "rt");
+ havesave = f;
+ if(!f) return;
+ score sc;
+ bool ok = false;
+ bool tamper = false;
+ while(!feof(f)) {
+ char buf[120];
+ if(fgets(buf, 120, f) == NULL) break;
+ if(buf[0] == 'H' && buf[1] == 'y') {
+ if(fscanf(f, "%s", buf) <= 0) break; sc.ver = buf;
+ if(sc.ver < "4.4" || sc.ver == "CHEATER!") continue;
+ ok = true;
+ for(int i=0; iitem]++;
+ c->item = itNone;
+ }
+ if(u == ' ')
+ c->item = itNone;
+
+ extern void movepckeydir(int);
+ if(sym == 'd' || sym == SDLK_KP6) movepckeydir(0);
+ if( sym == SDLK_KP3) movepckeydir(1);
+ if(sym == 'x' || sym == SDLK_KP2) movepckeydir(2);
+ if( sym == SDLK_KP1) movepckeydir(3);
+ if(sym == 'a' || sym == SDLK_KP4) movepckeydir(4);
+ if( sym == SDLK_KP7) movepckeydir(5);
+ if(sym == 'w' || sym == SDLK_KP8) movepckeydir(6);
+ if( sym == SDLK_KP9) movepckeydir(7);
+
+ if(u == 'g')
+ c->item = itGreenStone;
+ if(u == 'r')
+ c->item = itOrbSpeed;
+ if(u == 't')
+ c->item = itOrbFlash;
+ if(u == 'o')
+ c->item = itOrbShield;
+ if(u == 'i')
+ c->item = itOrbTeleport;
+ if(u == 'p')
+ c->item = itOrbLightning;
+ if(u == 'h')
+ c->item = itDiamond;
+ if(u == 'j')
+ c->item = itHell;
+ if(u == 'k')
+ c->item = itFernFlower;
+ if(u == 'l')
+ c->item = itSapphire;
+
+ if(boardmode == 'f') {
+ int fv = fjordval(c);
+ if(u == '0' || u == '1' || u == '2' || u == '3' || u == '4') fjordwalled[fv] = !fjordwalled[fv];
+ if(u == '1' || u == '4') fjordwalled[fv^1] = !fjordwalled[fv^1];
+ if(u == '2' || u == '4') fjordwalled[fv^2] = !fjordwalled[fv^2];
+ if(u == '3' || u == '4') fjordwalled[fv^3] = !fjordwalled[fv^3];
+ return;
+ }
+
+ if(u == '0')
+ c->wall = waNone;
+ if(u == '1')
+ c->wall = waFloorA;
+ if(u == '2')
+ c->wall = waFloorB;
+ if(u == '3')
+ c->wall = waFrozenLake;
+ if(u == '4')
+ c->wall = waCavefloor;
+ if(u == '5')
+ c->wall = waLake;
+ if(u == '6')
+ c->wall = waCavewall;
+ if(u == '7')
+ c->wall = waIcewall;
+ if(u == '8')
+ c->wall = waDryTree;
+ if(u == '9')
+ c->wall = waColumn;
+ }
+#endif
+
+static int orbid = 0;
+
+eItem nextOrb() {
+ orbid++;
+ eItem i = eItem(orbid % ittypes);
+ if(itemclass(i) == IC_ORB) return i;
+ else return nextOrb();
+ }
+
+eItem randomTreasure() {
+ eItem i = eItem(rand() % ittypes);
+ if(itemclass(i) == IC_TREASURE) return i;
+ else return randomTreasure();
+ }
+
+eItem randomTreasure2(int cv) {
+ int bq = 60000, cq = 0;
+ eItem best = itDiamond;
+ eItem lt = localTreasureType();
+ for(int a=1; aland == laCrossroads) q -= 5;
+ if(q < bq) bq = q, cq = 0;
+ if(q == bq) { cq++; if(rand() % cq == 0) best = i; }
+ }
+ return best;
+ }
+
+extern int webdisplay;
+
+void applyCheat(char u, cell *c = NULL) {
+ if(u == 'M' && cwt.c->type == 6) {
+ addMessage(XLAT("You summon some Mirages!"));
+ cheater++;
+ createMirrors(cwt.c, cwt.spin, moMirage),
+ createMirages(cwt.c, cwt.spin, moMirage);
+ }
+ if(u == 'G') {
+ addMessage(XLAT("You summon a golem!"));
+ cheater++;
+ int i = cwt.spin;
+ if(passable(cwt.c->mov[i], NULL, false, false))
+ cwt.c->mov[i]->monst = moGolem;
+ }
+ if(u == 'L') {
+ do {
+ firstland = eLand(firstland+1);
+ if(firstland == landtypes) firstland = eLand(2);
+ }
+ while(firstland == laGameBoard || firstland == laCamelot || firstland == laTemple);
+ euclidland = firstland;
+ cheater++; addMessage(XLAT("You will now start your games in %1", firstland));
+ }
+ if(u == 'C') {
+ cheater++;
+ activateSafety(laCrossroads);
+ addMessage(XLAT("Activated the Hyperstone Quest!"));
+ for(int i=0; itype; i++)
+ if(passable(cwt.c->mov[i], NULL, false, false)) {
+ eItem it = nextOrb();
+ cwt.c->mov[i]->item = it;
+ }
+ }
+ if(u == 'F') {
+ items[itOrbFlash] += 1;
+ items[itOrbTeleport] += 1;
+ items[itOrbLightning] += 1;
+ items[itOrbSpeed] += 1;
+ items[itOrbShield] += 1;
+ cheater++; addMessage(XLAT("Orb power gained!"));
+ }
+ if(u == 'D') {
+ items[itGreenStone] += 10;
+ cheater++; addMessage(XLAT("Dead orbs gained!"));
+ }
+ if(u == 'Y') {
+ items[itOrbYendor] ++;
+ cheater++; addMessage(XLAT("Orb of Yendor gained!"));
+ }
+ if(u == 'T') {
+ items[randomTreasure2(10)] += 10;
+ cheater++; addMessage(XLAT("Treasure gained!"));
+ }
+ if(u == 'T'-64) {
+ items[randomTreasure2(100)] += 100;
+ cheater++; addMessage(XLAT("Lots of treasure gained!"));
+ }
+ if(u == 'W') {
+ addMessage(XLAT("You summon a sandworm!"));
+ cheater++;
+ int i = cwt.spin;
+ if(passable(cwt.c->mov[i], NULL, false, false))
+ cwt.c->mov[i]->monst = moWorm,
+ cwt.c->mov[i]->mondir = NODIR;
+ }
+ if(u == 'I') {
+ addMessage(XLAT("You summon an Ivy!"));
+ cheater++;
+ int i = cwt.spin;
+ int j = cwt.c->spn[i];
+ cell* c = cwt.c->mov[i]->mov[(j+3)%cwt.c->mov[i]->type];
+ if(passable(c, NULL, false, false)) buildIvy(c, 0, 1);
+ }
+ if(u == 'E') {
+ addMessage(XLAT("You summon a monster!"));
+ cheater++;
+ int i = cwt.spin;
+ if(cwt.c->mov[i]->wall == waChasm)
+ cwt.c->mov[i]->wall = waNone;
+ if(passable(cwt.c->mov[i], NULL, true, false)) {
+ eMonster mo[7] = { moEagle, moPyroCultist, moGhost, moTroll, moKnight, moMiner, moVineBeast };
+ cwt.c->mov[i]->monst = mo[rand() % 7];
+ }
+ }
+ if(u == 'H') {
+ addMessage(XLAT("You summon some Thumpers!"));
+ cheater++;
+ for(int i=0; itype; i++)
+ if(passable(cwt.c->mov[i], NULL, false, false))
+ cwt.c->mov[i]->wall = rand() % 2 ? waThumper : waBigStatue, cwt.c->mov[i]->tmp = -1;
+ }
+ if(u == 'B') {
+ addMessage(XLAT("You summon a bonfire!"));
+ cheater++;
+ int i = cwt.spin;
+ if(passable(cwt.c->mov[i], NULL, false, false))
+ cwt.c->mov[i]->wall = waBonfire, cwt.c->mov[i]->tmp = -1;
+ }
+ if(u == 'Z') {
+ cwt.spin++; flipplayer = false;
+ cwt.spin %= cwt.c->type;
+ }
+ if(u == 'J') {
+ if(items[localTreasureType()] > 0)
+ items[localTreasureType()] = 0;
+ else for(int i=1; iland);
+ items[itOrbSafety] += 3;
+ cheater++; addMessage(XLAT("Activated Orb of Safety!"));
+ }
+ if(u == 'U') {
+ activateSafety(firstland);
+ cheater++; addMessage(XLAT("Teleported to %1!", firstland));
+ }
+ if(u == 'W'-64) {
+ webdisplay++;
+ cheater++; addMessage(XLAT("Cheat-changed the display.", firstland));
+ }
+ }
+
+void generateAlts(heptagon *h) {
+ if(!h->alt) return;
+ h->c7->bardir = NOBARRIERS;
+ for(int i=0; i<7; i++) if(h->c7->mov[i])
+ h->c7->mov[i]->bardir = NOBARRIERS;
+ for(int i=0; i<7; i++)
+ createStep(h->alt, i)->alt = h->alt->alt;
+ int relspin = -4; // for horocycles it must go the other way
+ for(int i=0; i<7; i++) for(int j=0; j<7; j++) {
+ createStep(h, i);
+ if(h->move[i]->alt == h->alt->move[j])
+ relspin = (i-j+7) % 7;
+ }
+ if(relspin == -4) {
+ if(h->alt != h->alt->alt) {
+ printf("relspin {%p:%p}\n", h->alt, h->alt->alt);
+ for(int i=0; i<7; i++) printf("%p ", h->alt->move[i]); printf(" ALT\n");
+ for(int i=0; i<7; i++) printf("%p ", h->move[i]); printf(" REAL\n");
+ for(int i=0; i<7; i++) printf("%p ", h->move[i]->alt); printf(" REAL ALT\n");
+ }
+ relspin = 3;
+ }
+ // h[relspin] matches alt[0]
+//printf("{%d~%d}\n", h->distance, h->alt->distance);
+ for(int i=0; i<7; i++) {
+ int ir = (7+i-relspin)%7;
+ heptagon *hm = h->alt->move[ir];
+ heptagon *ho = createStep(h, i);
+// printf("[%p:%d ~ %p:%d] %p ~ %p\n",
+// h, i, h->alt, ir,
+// ho, hm);
+ if(ho->alt && ho->alt != hm) {
+ if(ho->alt->alt == hm->alt) {
+ printf("ERROR: alt cross! [%d -> %d]\n", ho->alt->distance, hm->distance);
+ // exit(1);
+ }
+ continue;
+ }
+ ho->alt = hm;
+ }
+ }
+
+heptagon *createAlternateMap(cell *c, int rad, hstate firststate) {
+
+ // check for direction
+ int gdir = -1;
+ for(int i=0; itype; i++) {
+ if(c->mov[i] && c->mov[i]->mpdist < c->mpdist) gdir = i;
+ }
+ if(!gdir) return NULL;
+
+ // check for non-crossing
+ int bd = 2;
+ cellwalker bb(c, bd);
+ cellwalker bb2 = bb;
+ if(!(checkBarriersFront(bb) && checkBarriersBack(bb2))) {
+ return NULL;
+ }
+
+ // okay, let's go then!
+ cellwalker bf(c, gdir);
+ cell *cx[rad+1];
+ for(int i=0; itype == 6)
+ cwspin(bf, 3);
+ else
+ cwspin(bf, 3 + rand() % 2);
+ cwstep(bf);
+ }
+ cx[rad] = bf.c;
+ heptagon *h = bf.c->master;
+
+ heptagon *alt = new heptagon;
+ allAlts.push_back(alt);
+//printf("new alt {%p}\n", alt);
+ alt->s = firststate;
+ alt->fjordval = 0;
+ for(int i=0; i<7; i++) alt->move[i] = NULL;
+ alt->distance = 0;
+ alt->c7 = NULL;
+ alt->alt = alt;
+ h->alt = alt;
+
+ for(int d=rad; d>=0; d--) {
+ generateAlts(cx[d]->master);
+ cx[d]->bardir = NOBARRIERS;
+ }
+
+ return alt;
+//for(int d=rad; d>=0; d--) printf("%3d. %p {%d}\n", d, cx[d]->master, cx[d]->master->alt->distance);
+ }
diff --git a/geometry.cpp b/geometry.cpp
new file mode 100644
index 00000000..c81a2abb
--- /dev/null
+++ b/geometry.cpp
@@ -0,0 +1,124 @@
+// Hyperbolic Rogue
+// Copyright (C) 2011-2012 Zeno Rogue, see 'hyper.cpp' for details
+
+// geometrical constants
+
+ld tessf, crossf, hexf;
+
+#define ALPHA (M_PI*2/7)
+
+hyperpoint Crad[42];
+
+transmatrix heptmove[7], hexmove[7];
+
+void precalc() {
+
+ ld fmin = 1, fmax = 2;
+
+ for(int p=0; p<100; p++) {
+ ld f = (fmin+fmax) / 2;
+ hyperpoint H = xpush(f) * C0;
+ ld v1 = intval(H, C0), v2 = intval(H, spin(2*M_PI/7)*H);
+ if(v1 > v2) fmin = f; else fmax = f;
+ }
+ tessf = fmin;
+
+ fmin = 0, fmax = 2;
+ for(int p=0; p<100; p++) {
+ ld f = (fmin+fmax) / 2;
+ hyperpoint H = spin(M_PI/7) * xpush(f) * C0;
+ ld v1 = intval(H, C0), v2 = intval(H, xpush(tessf) * C0);
+ if(v1 < v2) fmin = f; else fmax = f;
+ }
+ crossf = fmin;
+
+ fmin = 0, fmax = tessf;
+ for(int p=0; p<100; p++) {
+ ld f = (fmin+fmax) / 2;
+ hyperpoint H = xpush(f) * C0;
+ hyperpoint H1 = spin(2*M_PI/7) * H;
+ hyperpoint H2 = xpush(tessf-f) * C0;
+ ld v1 = intval(H, H1), v2 = intval(H, H2);
+ if(v1 < v2) fmin = f; else fmax = f;
+ }
+ hexf = fmin;
+
+ for(int i=0; i<42; i++)
+ Crad[i] = spin(2*M_PI*i/42) * xpush(.4) * C0;
+ for(int d=0; d<7; d++)
+ heptmove[d] = spin(-d * ALPHA) * xpush(tessf) * spin(M_PI);
+ for(int d=0; d<7; d++)
+ hexmove[d] = spin(-d * ALPHA) * xpush(-crossf)* spin(M_PI);
+
+ }
+
+transmatrix ddi(ld dir, ld dist) {
+ // EUCLIDEAN
+ if(euclid)
+ return eupush(cos(M_PI*dir/42) * dist, -sin(M_PI*dir/42) * dist);
+ else
+ return spin(M_PI*dir/42) * xpush(dist) * spin(-M_PI*dir/42);
+ }
+
+// tesselation drawing
+
+#define NUMFACE 500
+transmatrix tess[NUMFACE];
+
+void genTesselation() {
+ int N = 1;
+ tess[0] = Id;
+ for(int i=0; i %d\n", i,t, N);
+ tess[N] = T; N++;
+ if(N == NUMFACE) return;
+ nextt: ;
+ }
+ }
+ }
+
+struct ltd {
+ hyperpoint P1;
+ hyperpoint P2;
+ int col;
+ };
+
+vector lines;
+
+void addline(hyperpoint P1, hyperpoint P2, int col) {
+ ltd L;
+ L.P1 = P1; L.P2 = P2; L.col = col;
+ lines.push_back(L);
+ }
+
+void addlines() {
+
+ // change the if(0) conditions to see the underlying structure
+
+ if(0) for(int t =0; t
+
+#ifdef AUDIO
+#include
+#endif
+bool audio;
+int audiovolume = 60;
+
+#ifndef MAC
+#undef main
+#endif
+
+#include
+
+#ifdef GFX
+#include
+#endif
+#endif
+
+#ifndef MOBILE
+#ifdef GL
+#ifdef MAC
+#include
+#include
+#else
+#include
+#include
+#endif
+
+#ifdef MAC
+#include
+#else
+#include
+#endif
+#endif
+#endif
+
+#ifdef ANDROID
+#ifndef FAKE
+#ifdef GL
+#include
+#include
+#include
+#endif
+#endif
+#endif
+
+
+#ifndef MOBILE
+
+// x resolutions
+
+#define NUMMODES 7
+
+SDL_Surface *s;
+TTF_Font *font[256];
+SDL_Joystick *stick, *panstick;
+
+#endif
+
+int webdisplay = 0;
+
+// R:239, G:208, B:207
+
+struct videopar {
+ ld scale, eye, alpha, aspeed;
+ bool full;
+ bool goteyes;
+ bool quick;
+ bool darkhepta;
+ bool shifttarget;
+
+ int xres, yres, framelimit;
+
+ int xscr, yscr;
+
+ // paramaters calculated from the above
+ int xcenter, ycenter;
+ int radius;
+ ld alphax, beta;
+
+ int fsize;
+ int flashtime;
+
+ int wallmode, monmode, axes;
+
+ // for OpenGL
+ float scrdist;
+
+ bool usingGL;
+ bool usingAA;
+
+ int joyvalue, joyvalue2, joypanthreshold;
+ float joypanspeed;
+
+ bool female;
+ int language;
+ int boardmode;
+
+ int skincolor, haircolor, dresscolor, swordcolor;
+ int killreduction;
+ };
+
+int skincolors[] = { 7, 0xD0D0D0FF, 0xEFD0C9FF, 0xC77A58FF, 0xA58869FF, 0x602010FF, 0xFFDCB1FF, 0xEDE4C8FF };
+int haircolors[] = { 8, 0x686868FF, 0x8C684AFF, 0xF2E1AEFF, 0xB55239FF, 0xFFFFFFFF, 0x804000FF, 0x502810FF, 0x301800FF };
+int dresscolors[] = { 6, 0xC00000FF, 0x00C000FF, 0x0000C0FF, 0xC0C000FF, 0xC0C0C0FF, 0x202020FF };
+int swordcolors[] = { 6, 0xC0C0C0FF, 0xFFFFFFFF, 0xFFC0C0FF, 0xC0C0FFFF, 0x808080FF, 0x202020FF };
+
+// is the player using mouse? (used for auto-cross)
+bool mousing = true;
+
+// is the mouse button pressed?
+bool mousepressed = false;
+
+//
+int axestate;
+
+int ticks;
+
+hyperpoint ccenter; ld crad;
+
+videopar vid;
+int default_language;
+
+int playergender() { return vid.female ? GEN_F : GEN_M; }
+int lang() {
+ if(vid.language >= 0)
+ return vid.language;
+ return default_language;
+ }
+
+int sightrange = 7;
+
+cell *mouseover, *lmouseover, *centerover, *lcenterover; ld modist, centdist;
+string mouseovers;
+
+int mousex, mousey, mousedist, mousedest, joyx, joyy, joydir, panjoyx, panjoyy;
+bool autojoy = true;
+hyperpoint mouseh;
+
+bool leftclick, rightclick, targetclick, hiliteclick;
+bool gtouched;
+bool revcontrol;
+
+bool doHighlight() {
+ return hiliteclick ? vid.monmode == 2 : vid.monmode == 3;
+ }
+
+int getcstat; ld getcshift;
+
+int ZZ;
+
+string help;
+
+enum emtype {emNormal, emHelp, emVisual1, emVisual2,
+ emChangeMode, emCustomizeChar,
+ emQuit, emDraw, emScores, emPickEuclidean, emPickScores} cmode;
+
+int andmode = 0;
+
+int darken = 0;
+
+#ifndef MOBILE
+int& qpixel(SDL_Surface *surf, int x, int y) {
+ if(x<0 || y<0 || x >= surf->w || y >= surf->h) return ZZ;
+ char *p = (char*) surf->pixels;
+ p += y * surf->pitch;
+ int *pi = (int*) (p);
+ return pi[x];
+ }
+
+int qpixel3(SDL_Surface *surf, int x, int y) {
+ if(x<0 || y<0 || x >= surf->w || y >= surf->h) return ZZ;
+ char *p = (char*) surf->pixels;
+ p += y * surf->pitch;
+ p += x;
+ int *pi = (int*) (p);
+ return pi[0];
+ }
+
+void loadfont(int siz) {
+ if(!font[siz]) {
+ font[siz] = TTF_OpenFont("DejaVuSans-Bold.ttf", siz);
+ if (font[siz] == NULL) {
+ printf("error: Font file not found\n");
+ exit(1);
+ }
+ }
+ }
+
+int textwidth(int siz, const string &str) {
+ if(size(str) == 0) return 0;
+
+ loadfont(siz);
+
+ int w, h;
+ TTF_SizeUTF8(font[siz], str.c_str(), &w, &h);
+ // printf("width = %d [%d]\n", w, size(str));
+ return w;
+ }
+#endif
+
+#ifdef IOS
+
+int textwidth(int siz, const string &str) {
+ return mainfont->getSize(str, siz / 36.0).width;
+ }
+
+#endif
+
+int darkened(int c) {
+ for(int i=0; i>= 1;
+ return c;
+ }
+
+int darkenedby(int c, int lev) {
+ for(int i=0; i>= 1;
+ return c;
+ }
+
+int darkena(int c, int lev, int a) {
+ for(int i=0; i>= 1;
+ return (c << 8) + a;
+ }
+
+#ifdef GL
+
+int lalpha = 0xFF;
+
+void glcolor(int color) {
+ unsigned char *c = (unsigned char*) (&color);
+ glColor4f(c[2] / 255.0, c[1] / 255.0, c[0]/255.0, lalpha / 255.0); // c[3]/255.0);
+ }
+
+void selectEyeGL(int ed) {
+ float ve = ed*vid.eye;
+ ve *= 2; // vid.xres; ve /= vid.radius;
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+
+ float lowdepth = 0.1;
+ float hidepth = 10000;
+
+ // simulate glFrustum
+ GLfloat frustum[16] = {
+ vid.yres * 1./vid.xres, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, -(hidepth+lowdepth)/(hidepth-lowdepth), -1,
+ 0, 0, -2*lowdepth*hidepth/(hidepth-lowdepth), 0};
+
+ if(ve)
+ glTranslatef(-(ve * vid.radius) * (vid.alpha - (vid.radius*1./vid.xres) * vid.eye) / vid.xres, 0, 0);
+
+ glTranslatef((vid.xcenter*2.)/vid.xres - 1, 1 - (vid.ycenter*2.)/vid.yres, 0);
+
+ glMultMatrixf(frustum);
+
+ glMatrixMode(GL_MODELVIEW);
+ glLoadIdentity();
+
+ float sc = vid.radius / (vid.yres/2.);
+ GLfloat mat[16] = {sc,0,0,0, 0,-sc,0,0, 0,0,-1,0, 0,0,-vid.alpha,1};
+ glMultMatrixf(mat);
+
+ if(ve) glTranslatef(ve, 0, vid.eye);
+
+ vid.scrdist = -vid.alpha + vid.yres * sc / 2;
+ }
+
+void selectEyeMask(int ed) {
+ if(ed == 0) {
+ glColorMask( GL_TRUE,GL_TRUE,GL_TRUE,GL_TRUE );
+ }
+ else if(ed == 1) {
+ glColorMask( GL_TRUE,GL_FALSE,GL_FALSE,GL_TRUE );
+ }
+ else if(ed == -1) {
+ glColorMask( GL_FALSE,GL_TRUE,GL_TRUE,GL_TRUE );
+ }
+ }
+
+void setGLProjection() {
+
+ glMatrixMode(GL_PROJECTION);
+ glLoadIdentity();
+
+ glClearColor(0,0,0,1);
+ glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
+
+ glEnable(GL_BLEND);
+ glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
+
+ selectEyeGL(0);
+ }
+
+GLfloat glcoords[1500][3];
+int qglcoords;
+
+bool GL_initialized = false;
+void buildpolys();
+
+#ifndef MOBILE
+
+struct glfont_t {
+ GLuint * textures; // Holds The Texture Id's
+//GLuint list_base; // Holds The First Display List ID
+ int widths[128+NUMEXTRA];
+ int heights[128+NUMEXTRA];
+ float tx[128+NUMEXTRA];
+ float ty[128+NUMEXTRA];
+ };
+
+glfont_t *glfont[256];
+
+void init(const char * fname, unsigned int h);
+
+inline int next_p2 (int a )
+{
+ int rval=1;
+ // rval<<=1 Is A Prettier Way Of Writing rval*=2;
+ while(rvalw );
+ int theight = next_p2( txt->h );
+
+ Uint16 expanded_data[twidth * theight];
+
+ for(int j=0; j =txt->w || j>=txt->h) ? 0 : ((qpixel(txt, i, j)>>24)&0xFF) * 0x101;
+ }
+
+// printf("b\n");
+ f.widths[ch] = txt->w;
+ f.heights[ch] = txt->h;
+
+ glBindTexture( GL_TEXTURE_2D, f.textures[ch]);
+ glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MAG_FILTER,GL_LINEAR);
+ glTexParameteri(GL_TEXTURE_2D,GL_TEXTURE_MIN_FILTER,GL_LINEAR);
+
+ glTexImage2D( GL_TEXTURE_2D, 0, GL_RGBA, twidth, theight, 0,
+ GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, expanded_data );
+
+// printf("c\n");
+
+ float x=(float)txt->w / (float)twidth;
+ float y=(float)txt->h / (float)theight;
+ f.tx[ch] = x;
+ f.ty[ch] = y;
+
+/* glNewList(f.list_base+ch,GL_COMPILE);
+ glBindTexture(GL_TEXTURE_2D, f.textures[ch]);
+
+ //glPushMatrix();
+
+ // glTranslatef(bitmap_glyph->left,0,0);
+ // glTranslatef(0,bitmap_glyph->top-bitmap.rows,0);
+
+// printf("d\n");
+
+ glBegin(GL_QUADS);
+ float eps=0;
+ glTexCoord2d(eps,eps); glVertex2f(0, txt->h);
+ glTexCoord2d(eps,y-0); glVertex2f(0, 0);
+ glTexCoord2d(x-eps,y-0); glVertex2f(txt->w, 0);
+ glTexCoord2d(x-eps,0); glVertex2f(txt->w, txt->h);
+ glEnd();
+ glEndList(); */
+ //glPopMatrix();
+
+ SDL_FreeSurface(txt);
+ }
+
+//printf("init size=%d ok\n", size);
+ GLERR("initfont");
+ }
+
+int getnext(const char* s, int& i) {
+ for(int k=0; k= x && mousey <= y && mousex <= x+tsize && mousey >= y-f.heights[32]);
+
+ for(int i=0; s[i];) {
+
+ // glListBase(f.list_base);
+ // glCallList(s[i]); // s[i]);
+
+ int tabid = getnext(s,i);
+ float fx=f.tx[tabid];
+ float fy=f.ty[tabid];
+ int wi = f.widths[tabid];
+ int hi = f.heights[tabid];
+
+ for(int ed = (vid.goteyes && shift)?-1:0; ed<2; ed+=2) {
+ glPushMatrix();
+ glTranslatef(x-ed*shift-vid.xcenter,y-vid.ycenter, vid.scrdist);
+ selectEyeMask(ed);
+ glBindTexture(GL_TEXTURE_2D, f.textures[tabid]);
+ glBegin(GL_QUADS);
+ glTexCoord2d(0,0); glVertex2f(0, -hi);
+ glTexCoord2d(0,fy); glVertex2f(0, 0);
+ glTexCoord2d(fx,fy); glVertex2f(wi, 0);
+ glTexCoord2d(fx,0); glVertex2f(wi, -hi);
+ glEnd();
+ glPopMatrix();
+ }
+
+ if(vid.goteyes) selectEyeMask(0);
+
+ GLERR("print");
+
+ // int tabid = s[i];
+ x += f.widths[tabid];
+
+/*
+ printf("point %d,%d\n", x, y);
+ glBegin(GL_POINTS);
+ glVertex3f(rand() % 100 - rand() % 100, rand() % 100 - rand() % 100, 100);
+ glEnd(); */
+
+ }
+
+ glDisable(GL_TEXTURE_2D);
+
+ return clicked;
+ // printf("gl_print ok\n");
+ }
+#endif
+
+void resetGL() {
+ GL_initialized = false;
+#ifndef MOBILE
+ for(int i=0; i<128; i++) if(glfont[i]) {
+ delete glfont[i];
+ glfont[i] = NULL;
+ }
+#endif
+ buildpolys();
+ }
+
+#endif
+
+#ifndef MOBILE
+bool displaystr(int x, int y, int shift, int size, const char *str, int color, int align) {
+
+ if(strlen(str) == 0) return false;
+
+ if(size <= 0 || size > 255) {
+ // printf("size = %d\n", size);
+ return false;
+ }
+
+#ifdef GL
+ if(vid.usingGL) return gl_print(x, y, shift, size, str, color, align);
+#endif
+
+ SDL_Color col;
+ col.r = (color >> 16) & 255;
+ col.g = (color >> 8 ) & 255;
+ col.b = (color >> 0 ) & 255;
+
+ col.r >>= darken; col.g >>= darken; col.b >>= darken;
+
+ loadfont(size);
+
+ SDL_Surface *txt = (vid.usingAA?TTF_RenderText_Blended:TTF_RenderText_Solid)(font[size], str, col);
+
+ if(txt == NULL) return false;
+
+ SDL_Rect rect;
+
+ rect.w = txt->w;
+ rect.h = txt->h;
+
+ rect.x = x - rect.w * align / 16;
+ rect.y = y - rect.h/2;
+
+ bool clicked = (mousex >= rect.x && mousey >= rect.y && mousex <= rect.x+rect.w && mousey <= rect.y+rect.h);
+
+ if(shift) {
+ SDL_Surface* txt2 = SDL_DisplayFormat(txt);
+ SDL_LockSurface(txt2);
+ SDL_LockSurface(s);
+ int c0 = qpixel(txt2, 0, 0);
+ for(int yy=0; yy graphdata;
+
+void gdpush(int t) {
+ graphdata.push_back(t);
+ }
+
+bool displaychr(int x, int y, int shift, int size, char chr, int col) {
+ gdpush(2); gdpush(x); gdpush(y); gdpush(8);
+ gdpush(col); gdpush(size); gdpush(0);
+ gdpush(1); gdpush(chr);
+ return false;
+ }
+
+void gdpush_utf8(const string& s) {
+ int g = graphdata.size(), q = 0;
+ gdpush((int) s.size()); for(int i=0; i= 192 && uch < 224) {
+ int u = ((s[i] - 192)&31) << 6;
+ i++;
+ u += (s[i] - 128) & 63;
+ gdpush(u); q++;
+ }
+ else {
+ gdpush(s[i]); q++;
+ }
+ }
+ graphdata[g] = q;
+ }
+
+bool displayfr(int x, int y, int b, int size, const string &s, int color, int align) {
+ gdpush(2); gdpush(x); gdpush(y); gdpush(align);
+ gdpush(color); gdpush(size); gdpush(b);
+ gdpush_utf8(s);
+ int mx = x - mousex;
+ int my = y - mousey;
+ return
+ mx >= -3*size && mx <= +3*size &&
+ my >= -size*3/4 && my <= +size*3/4;
+ }
+
+bool displaystr(int x, int y, int shift, int size, const string &s, int color, int align) {
+ gdpush(2); gdpush(x); gdpush(y); gdpush(align);
+ gdpush(color); gdpush(size); gdpush(0);
+ gdpush_utf8(s);
+ int mx = x - mousex;
+ int my = y - mousey;
+ return
+ mx >= -3*size && mx <= +3*size &&
+ my >= -size*3/4 && my <= +size*3/4;
+ }
+
+#endif
+
+bool displaynum(int x, int y, int shift, int size, int col, int val, string title) {
+ char buf[64];
+ sprintf(buf, "%d", val);
+ bool b1 = displayfr(x-8, y, 1, size, buf, col, 16);
+ bool b2 = displayfr(x, y, 1, size, title, col, 0);
+ if((b1 || b2) && gtouched) {
+ col ^= 0x00FFFF;
+ displayfr(x-8, y, 1, size, buf, col, 16);
+ displayfr(x, y, 1, size, title, col, 0);
+ }
+ return b1 || b2;
+ }
+
+struct msginfo {
+ int stamp;
+ char flashout;
+ char spamtype;
+ string msg;
+ };
+
+vector msgs;
+
+vector gamelog;
+
+void flashMessages() {
+ for(int i=0; i .9999) return Hypc;
+
+ // hz*hz-(hx/(hz+alpha))^2 - (hy/(hz+alpha))^2 =
+
+ // hz*hz-hr*(hz+alpha)^2 == 1
+ // hz*hz - hr*hr*hz*Hz
+
+ ld A = 1-hr;
+ ld B = 2*hr*vid.alphax;
+ ld C = 1 + hr*vid.alphax*vid.alphax;
+
+ // Az^2 - Bz = C
+ B /= A; C /= A;
+
+ // z^2 - Bz = C
+ // z^2 - Bz + (B^2/4) = C + (B^2/4)
+ // z = (B/2) + sqrt(C + B^2/4)
+
+ ld hz = B / 2 + sqrt(C + B*B/4);
+ hyperpoint H;
+ H[0] = hx * (hz+vid.alphax);
+ H[1] = hy * (hz+vid.alphax);
+ H[2] = hz;
+
+ return H;
+ }
+
+void getcoord(const hyperpoint& H, int& x, int& y, int &shift) {
+
+ if(H[2] < 0.999) {
+ printf("error: %s\n", display(H));
+ // exit(1);
+ }
+ ld tz = euclid ? (EUCSCALE+vid.alphax) : vid.alphax+H[2];
+ if(tz < 1e-3 && tz > -1e-3) tz = 1000;
+ x = vid.xcenter + int(vid.radius * H[0] / tz);
+ y = vid.ycenter + int(vid.radius * H[1] / tz);
+#ifndef MOBILE
+ shift = vid.goteyes ? int(vid.eye * vid.radius * (1 - vid.beta / tz)) : 0;
+#endif
+ }
+
+int dlit;
+
+int polyi;
+
+#define POLYMAX 60000
+
+#ifdef MOBILE
+short polyx[POLYMAX], polyy[POLYMAX];
+#else
+Sint16 polyx[POLYMAX], polyxr[POLYMAX], polyy[POLYMAX];
+#endif
+
+void drawline(const hyperpoint& H1, int x1, int y1, int s1, const hyperpoint& H2, int x2, int y2, int col) {
+ dlit++; if(dlit>500) return;
+
+ int dst = (x1-x2)*(x1-x2)+(y1-y2)*(y1-y2);
+
+ #ifdef GL
+ if(vid.usingGL && dst <= (ISMOBILE ? 100 : 20)) {
+ if(euclid) {
+ for(int i=0; i<2; i++) glcoords[qglcoords][i] = H1[i]; glcoords[qglcoords][2] = EUCSCALE;
+ }
+ else {
+ for(int i=0; i<3; i++) glcoords[qglcoords][i] = H1[i];
+ }
+ qglcoords++;
+ return;
+ }
+ #endif
+
+ #ifdef MOBILE
+ if(dst <= 400 && !vid.usingGL) {
+ if(polyi >= POLYMAX) return;
+ polyx[polyi] = x1;
+ polyy[polyi] = y1;
+ polyi++;
+ return;
+ }
+ #else
+ #ifdef GFX
+ if(dst <= 20 && !vid.usingGL) {
+ if(col == -1) {
+ if(polyi >= POLYMAX) return;
+ polyx[polyi] = x1-s1;
+ polyxr[polyi] = x1+s1;
+ polyy[polyi] = y1;
+ polyi++;
+ }
+ else (vid.usingAA?aalineColor:lineColor) (s, x1, y1, x2, y2, col);
+ return;
+ }
+ #endif
+ if(dst <= 2) {
+ return;
+ }
+ #endif
+
+ hyperpoint H3 = mid(H1, H2);
+ int x3, y3, s3; getcoord(H3, x3, y3, s3);
+ #ifndef GFX
+ if(!vid.usingGL) {
+ qpixel(s, x3-s3, y3) |= col & 0xFF0000;
+ qpixel(s, x3+s3, y3) |= col & 0x00FFFF;
+ }
+ #endif
+
+ drawline(H1, x1, y1, s1, H3, x3, y3, col);
+ drawline(H3, x3, y3, s3, H2, x2, y2, col);
+ }
+
+void drawline(const hyperpoint& H1, const hyperpoint& H2, int col) {
+ if(vid.usingGL) {
+ qglcoords = 0;
+ }
+
+ // printf("line\n");
+ if(col != -1) {
+ col = (col << 8) + lalpha;
+ if(col == -1) col = -2;
+ polyi = 0;
+ #ifndef GFX
+ if(!vid.usingGL) {
+ SDL_LockSurface(s);
+ col >>= 8;
+ }
+ #endif
+ }
+
+ dlit = 0;
+ int x1, y1, s1; getcoord(H1, x1, y1, s1);
+ int x2, y2, s2; getcoord(H2, x2, y2, s2);
+ drawline(H1, x1, y1, s1, H2, x2, y2, col);
+
+ if(vid.usingGL) {
+
+ // EUCLIDEAN
+ if(euclid) {
+ for(int i=0; i<2; i++) glcoords[qglcoords][i] = H2[i]; glcoords[qglcoords][2] = EUCSCALE;
+ }
+ else {
+ for(int i=0; i<3; i++) glcoords[qglcoords][i] = H2[i];
+ }
+ qglcoords++;
+
+ glVertexPointer(3, GL_FLOAT, 0, glcoords);
+ glEnableClientState(GL_VERTEX_ARRAY);
+ glcolor(col >> 8);
+
+ if(vid.goteyes) {
+ selectEyeGL(-1);
+ selectEyeMask(-1);
+ glDrawArrays(GL_LINE_STRIP, 0, qglcoords);
+ selectEyeGL(+1);
+ selectEyeMask(+1);
+ glDrawArrays(GL_LINE_STRIP, 0, qglcoords);
+ selectEyeGL(0);
+ selectEyeMask(0);
+ }
+ else glDrawArrays(GL_LINE_STRIP, 0, qglcoords);
+ }
+
+ #ifdef MOBILE
+ else if(col != -1) {
+ gdpush(3); gdpush(col);
+ gdpush(polyi+1);
+ for(int i=0; itype;
+ }
+
+int gradient(int c0, int c1, ld v0, ld v, ld v1) {
+ int vv = int(256 * ((v-v0) / (v1-v0)));
+ int c = 0;
+ for(int a=0; a<3; a++) {
+ int p0 = (c0 >> (8*a)) & 255;
+ int p1 = (c1 >> (8*a)) & 255;
+ int p = (p0*(256-vv) + p1*vv + 127) >> 8;
+ c += p << (8*a);
+ }
+ return c;
+ }
+
+// cloak color
+int cloakcolor(int rtr) {
+ rtr -= 28;
+ rtr /= 2;
+ rtr %= 10;
+ if(rtr < 0) rtr += 10;
+ // rtr = time(NULL) % 10;
+ int cc[10] = {
+ 0x8080FF, 0x80FFFF, 0x80FF80, 0xFF8080, 0xFF80FF, 0xFFFF80,
+ 0xFFFFC0, 0xFFD500, 0x421C52, 0
+ };
+ return cc[rtr];
+ }
+
+int firecolor(int phase) {
+ return gradient(0xFFFF00, 0xFF0000, -1, sin((phase + ticks)/100.0), 1);
+ }
+
+int fc(int ph, int col) {
+ if(items[itOrbFire]) col = darkena(firecolor(ph), 0, 0xFF);
+ if(items[itOrbGhost]) col = (col &~0XFF) | (col&0xFF) / 2;
+ if(invismove) col = (col &~0XFF) | (int((col&0xFF) * (100+100*sin(ticks / 500.)))/200);
+ return col;
+ }
+
+int flashat, lightat, safetyat;
+cell *flashcell;
+
+void drawFlash(cell *c) { flashat = ticks; flashcell = c; }
+void drawLightning() { lightat = ticks; }
+void drawSafety() { safetyat = ticks; }
+
+#include "polygons.cpp"
+
+bool drawplayer = true;
+
+transmatrix playertrans;
+
+double eurad = 0.52;
+
+bool outofmap(hyperpoint h) {
+ if(euclid)
+ return h[0] * h[0] + h[1] * h[1] > 15 * eurad;
+ else
+ return h[2] < .5;
+ }
+
+void drawShield(const transmatrix& V) {
+ float ds = ticks / 300.;
+ int col = darkened(iinf[itOrbShield].color);
+ for(int a=0; a<84*5; a++)
+ drawline(V*ddi(a, hexf + sin(ds + M_PI*2*a/20)*.1)*C0, V*ddi((a+1), hexf + sin(ds + M_PI*2*(a+1)/20)*.1)*C0, col);
+ }
+
+void drawSpeed(const transmatrix& V) {
+ ld ds = ticks / 10.;
+ int col = darkened(iinf[itOrbSpeed].color);
+ for(int b=0; b<84; b+=14)
+ for(int a=0; a<84; a++)
+ drawline(V*ddi(ds+b+a, hexf*a/84)*C0, V*ddi(ds+b+(a+1), hexf*(a+1)/84)*C0, col);
+ }
+
+void drawSafety(const transmatrix& V, int ct) {
+ ld ds = ticks / 50.;
+ int col = darkened(iinf[itOrbSafety].color);
+ for(int a=0; atype;
+ else
+ return 42 - d * 84 / c->type;
+ }
+
+bool drawMonster(const transmatrix& V, int ct, cell *c, int col) {
+
+ if(doHighlight())
+ poly_outline =
+ (c->cpdist == 0 || isFriendly(c)) ? 0x00FF00FF : 0xFF0000FF;
+
+ eMonster m = c->monst;
+
+ if(c->cpdist == 0) {
+
+ if(items[itOrbShield] > 1) drawShield(V);
+
+ if(items[itOrbSpeed]) drawSpeed(V);
+
+ int ct = c->type;
+
+ if(items[itOrbSafety]) drawSafety(V, ct);
+
+ if(items[itOrbFlash]) drawFlash(V);
+
+ if(items[itOrbWinter])
+ drawWinter(V, displaydir(c, cwt.spin));
+
+ if(items[itOrbLightning] > 1) drawLightning(V);
+
+ if(safetyat > 0) {
+ int tim = ticks - safetyat;
+ if(tim > 2500) safetyat = 0;
+ for(int u=tim; u<=2500; u++) {
+ if((u-tim)%250) continue;
+ ld rad = hexf * u / 250;
+ int col = iinf[itOrbSafety].color;
+ for(int a=0; a<84; a++)
+ drawline(V*ddi(a, rad)*C0, V*ddi(a+1, rad)*C0, col);
+ }
+ }
+
+ transmatrix cV2 = c == cwt.c ? cwtV : V;
+ // if(flipplayer) cV2 = cV2 * spin(M_PI);
+ if(flipplayer) cV2 = cV2 * spin(M_PI);
+
+ if(vid.monmode < 2) return true;
+
+ bool havus = shUser[0][2].s;
+ playertrans = cV2;
+
+ for(int i=0; i<8; i++) if(shUser[i][2].s)
+ queuepoly(cV2, ct, shUser[i][2], 0xFFFFFFFF);
+
+ if(drawplayer && !havus) {
+
+ if(c->monst == moGolem) {
+ queuepoly(cV2, ct, shPBody, darkena(col, 0, 0XC0));
+ queuepoly(cV2, ct, shGolemhead, darkena(col, 1, 0XFF));
+ }
+ else {
+ queuepoly(cV2, ct, vid.female ? shFemaleBody : shPBody, fc(0, vid.skincolor));
+
+ if(items[itOrbThorns])
+ queuepoly(cV2, ct, shHedgehogBladePlayer, 0x00FF00FF);
+ else
+ queuepoly(cV2, ct, shPSword, fc(314, vid.swordcolor));
+
+ if(cheater) {
+ queuepoly(cV2, ct, shDemon, darkena(0xFFFF00, 0, 0xFF));
+ // queuepoly(cV2, ct, shHood, darkena(0xFF00, 1, 0xFF));
+ }
+ else {
+ queuepoly(cV2, ct, shPFace, fc(500, vid.skincolor));
+ queuepoly(cV2, ct, vid.female ? shFemaleHair : shPHead, fc(150, vid.haircolor));
+ }
+ if(vid.female)
+ queuepoly(cV2, ct, shFemaleDress, fc(500, vid.dresscolor));
+
+ if(knighted)
+ queuepoly(cV2, ct, shKnightCloak, darkena(cloakcolor(knighted), 1, 0xFF));
+ }
+ }
+ }
+
+ if(isIvy(c) || isWorm(c)) {
+
+ transmatrix V2 = V;
+
+ if(c->mondir != NODIR) {
+ int hdir = displaydir(c, c->mondir);
+
+ if(vid.monmode > 1) {
+ V2 = V2 * spin(hdir * M_PI / 42);
+ if(isIvy(c))
+ queuepoly(V2, ct, shIBranch, (col << 8) + 0xFF);
+ else if(c->monst < moTentacle) {
+ queuepoly(V2, ct, shTentacleX, 0xFF);
+ queuepoly(V2, ct, shTentacle, (col << 8) + 0xFF);
+ }
+ else {
+ queuepoly(V2, ct, shTentacleX, 0xFFFFFFFF);
+ queuepoly(V2, ct, shTentacle, (col << 8) + 0xFF);
+ }
+ }
+
+ else for(int u=-1; u<=1; u++)
+ drawline(V*ddi(hdir+21, u*crossf/5)*C0, V*ddi(hdir, crossf)*ddi(hdir+21, u*crossf/5)*C0, 0x606020 >> darken);
+ }
+
+ if(vid.monmode > 1) {
+ if(isIvy(c))
+ queuepoly(V, ct, shILeaf[ct-6], darkena(col, 0, 0xFF));
+ else if(m == moWorm || m == moWormwait) {
+ queuepoly(V2 * spin(M_PI), ct, shWormHead, darkena(col, 0, 0xFF));
+ queuepoly(V2 * spin(M_PI), ct, shEyes, 0xFF);
+ }
+ else if(m == moTentacle || m == moTentaclewait || m == moTentacleEscaping)
+ queuepoly(V2 * spin(M_PI), ct, shTentHead, darkena(col, 0, 0xFF));
+ else
+ queuepoly(V2, ct, shJoint, darkena(col, 0, 0xFF));
+ }
+
+ return vid.monmode < 2;
+ }
+
+ else if(isMimic(c)) {
+
+ int hdir = displaydir(c, c->mondir);
+
+ transmatrix mirrortrans = Id;
+ if(c->monst == moMirror) mirrortrans[1][1] = -1;
+ transmatrix V2 = V * spin(M_PI*hdir/42) * mirrortrans;
+
+ if(vid.monmode > 1) {
+ if(flipplayer) V2 = V2 * spin(M_PI);
+ queuepoly(V2, ct, vid.female ? shFemaleBody : shPBody, darkena(col, 0, 0X80));
+ queuepoly(V2, ct, shPSword, darkena(col, 0, 0XC0));
+ queuepoly(V2, ct, vid.female ? shFemaleHair : shPHead, darkena(col, 1, 0XC0));
+ queuepoly(V2, ct, shPFace, darkena(col, 0, 0XC0));
+ if(vid.female)
+ queuepoly(V2, ct, shFemaleDress, darkena(col, 1, 0XC0));
+ if(flipplayer) V2 = V2 * spin(M_PI);
+ }
+
+ if(!outofmap(mouseh)) {
+ hyperpoint P2 = V2 * inverse(cwtV) * mouseh;
+ int xc, yc, sc;
+ getcoord(P2, xc, yc, sc);
+ displaychr(xc, yc, sc, 10, 'x', 0xFF00);
+ }
+
+ return vid.monmode < 2;
+ }
+
+ else if(c->monst == moIllusion) {
+ if(vid.monmode > 1) {
+ queuepoly(V, ct, vid.female ? shFemaleBody : shPBody, darkena(col, 0, 0X80));
+ queuepoly(V, ct, shPSword, darkena(col, 0, 0XC0));
+ queuepoly(V, ct, vid.female ? shFemaleHair : shPHead, darkena(col, 1, 0XC0));
+ queuepoly(V, ct, shPFace, darkena(col, 0, 0XC0));
+ if(vid.female)
+ queuepoly(V, ct, shFemaleDress, darkena(col, 1, 0XC0));
+ }
+ return vid.monmode < 2;
+ }
+
+ else if(c->monst && vid.monmode < 2) return true;
+
+ else if(isFriendly(c)) {
+ // golems and knights don't face player
+ int hdir = displaydir(c, c->mondir) + 42;
+ transmatrix V2 = V * spin(hdir * M_PI / 42) ;
+ if(m == moKnight) {
+ queuepoly(V2, ct, shPBody, darkena(0xC0C0A0, 0, 0xC0));
+ queuepoly(V2, ct, shPSword, darkena(0xFFFF00, 0, 0xFF));
+ queuepoly(V2, ct, shKnightArmor, darkena(0xD0D0D0, 1, 0xFF));
+ int col;
+ if(c->master->alt)
+ col = cloakcolor(roundTableRadius(c));
+ else
+ col = cloakcolor(newRoundTableRadius());
+ queuepoly(V2, ct, shKnightCloak, darkena(col, 1, 0xFF));
+ queuepoly(V2, ct, shPHead, darkena(0x703800, 1, 0XFF));
+ queuepoly(V2, ct, shPFace, darkena(0xC0C0A0, 0, 0XFF));
+ }
+ else {
+ queuepoly(V2, ct, shPBody, darkena(col, 0, 0XC0));
+ queuepoly(V2, ct, shGolemhead, darkena(col, 1, 0XFF));
+ }
+ }
+
+ else if(c->monst) {
+
+ // face the player
+ transmatrix VL;
+
+ if(false) {
+ // hyperpoint V0 = cwtV * C0;
+ hyperpoint V1 = V * C0;
+ VL = V * spin(hypot(V1[0], V1[1]));
+ }
+ else {
+ hyperpoint V0 = inverse(cwtV) * V * C0;
+ hyperpoint V1 = spintox(V0) * V0;
+ VL = cwtV * rspintox(V0) * rpushxto0(V1) * spin(M_PI);
+ }
+
+ char xch = minf[m].glyph;
+
+ if(m == moWolf) {
+ queuepoly(VL, ct, shWolfLegs, darkena(col, 0, 0xFF));
+ queuepoly(VL, ct, shWolfBody, darkena(col, 0, 0xFF));
+ queuepoly(VL, ct, shWolfHead, darkena(col, 0, 0xFF));
+ queuepoly(VL, ct, shWolfEyes, darkena(col, 3, 0xFF));
+ }
+ else if(m == moVineBeast) {
+ queuepoly(VL, ct, shWolfLegs, 0x00FF00FF);
+ queuepoly(VL, ct, shWolfBody, darkena(col, 1, 0xFF));
+ queuepoly(VL, ct, shWolfHead, darkena(col, 0, 0xFF));
+ queuepoly(VL, ct, shWolfEyes, 0xFF0000FF);
+ }
+ else if(isBug(m)) {
+
+ int hdir = displaydir(c, c->mondir) + 42;
+
+ transmatrix V2 = V * spin(hdir * M_PI / 42) ;
+
+ queuepoly(V2, ct, shBugBody, darkena(col, 0, 0xFF));
+ queuepoly(V2, ct, shBugArmor, darkena(col, 1, 0xFF));
+ }
+ else if(m == moRunDog) {
+ queuepoly(VL, ct, shWolf, darkena(col, 0, 0xFF));
+ }
+ else if(m == moShark || m == moGreaterShark)
+ queuepoly(VL, ct, shShark, darkena(col, 0, 0xFF));
+ else if(m == moEagle)
+ queuepoly(VL, ct, shEagle, darkena(col, 0, 0xFF));
+ else if(m == moZombie)
+ queuepoly(VL, ct, shPBody, darkena(col, 0, 0xFF));
+ else if(m == moDesertman) {
+ queuepoly(VL, ct, shPBody, darkena(col, 0, 0xC0));
+ queuepoly(VL, ct, shPSword, 0xFFFF00FF);
+ queuepoly(VL, ct, shHood, 0xD0D000C0);
+ }
+ else if(m == moCrystalSage) {
+ queuepoly(VL, ct, shPBody, 0xFFFFFFFF);
+ queuepoly(VL, ct, shPHead, 0xFFFFFFFF);
+ queuepoly(VL, ct, shPFace, 0xFFFFFFFF);
+ }
+ else if(m == moHedge) {
+ queuepoly(VL, ct, shPBody, darkena(col, 0, 0xFF));
+ queuepoly(VL, ct, shHedgehogBlade, 0xC0C0C0FF);
+ queuepoly(VL, ct, shPHead, 0x804000FF);
+ queuepoly(VL, ct, shPFace, 0xF09000FF);
+ }
+ else if(m == moYeti || m == moMonkey) {
+ queuepoly(VL, ct, shYeti, darkena(col, 0, 0xC0));
+ queuepoly(VL, ct, shPHead, darkena(col, 0, 0xFF));
+ }
+ else if(m == moShadow) {
+ queuepoly(VL, ct, shPBody, darkena(col, 0, 0X80));
+ queuepoly(VL, ct, shPSword, darkena(col, 0, 0XC0));
+ queuepoly(VL, ct, shPHead, darkena(col, 1, 0XC0));
+ queuepoly(VL, ct, shPFace, darkena(col, 0, 0XC0));
+ }
+ else if(m == moRanger) {
+ queuepoly(VL, ct, shPBody, darkena(col, 0, 0xC0));
+ queuepoly(VL, ct, shPSword, darkena(col, 0, 0xFF));
+ queuepoly(VL, ct, shArmor, darkena(col, 1, 0xFF));
+ }
+ else if(m == moGhost || m == moSeep) {
+ queuepoly(VL, ct, shGhost, darkena(col, 0, 0x80));
+ queuepoly(VL, ct, shEyes, 0xFF);
+ }
+ else if(m == moVineSpirit) {
+ queuepoly(VL, ct, shGhost, 0xD0D0D0C0);
+ queuepoly(VL, ct, shEyes, 0xFF0000FF);
+ }
+ else if(m == moFireFairy) {
+ col = firecolor(0);
+ queuepoly(VL, ct, shFemaleBody, darkena(col, 0, 0XC0));
+// queuepoly(cV2, ct, shPSword, darkena(col, 0, 0XFF));
+ queuepoly(VL, ct, shFemaleHair, darkena(col, 1, 0xFF));
+ queuepoly(VL, ct, shPFace, darkena(col, 0, 0XFF));
+// queuepoly(cV2, ct, shFemaleDress, 0xC00000FF);
+ }
+ else if(m == moSlime) {
+ queuepoly(VL, ct, shSlime, darkena(col, 0, 0x80));
+ queuepoly(VL, ct, shEyes, 0xFF);
+ }
+ else if(m == moCultist || m == moPyroCultist || m == moCultistLeader) {
+ queuepoly(VL, ct, shPBody, darkena(col, 0, 0xC0));
+ queuepoly(VL, ct, shPSword, darkena(col, 2, 0xFF));
+ queuepoly(VL, ct, shHood, darkena(col, 1, 0xFF));
+ }
+ else if(m == moNecromancer) {
+ queuepoly(VL, ct, shPBody, 0xC00000C0);
+ queuepoly(VL, ct, shHood, darkena(col, 1, 0xFF));
+ }
+ else if(m == moGoblin) {
+ queuepoly(VL, ct, shYeti, darkena(col, 0, 0xC0));
+ queuepoly(VL, ct, shArmor, darkena(col, 1, 0XFF));
+ }
+ else if(m == moLancer || m == moFlailer || m == moMiner) {
+ if(m == moLancer)
+ VL = VL * spin(c->type == 6 ? -M_PI/3 : -M_PI/2 );
+ queuepoly(VL, ct, shPBody, darkena(col, 0, 0xC0));
+ queuepoly(VL, ct, m == moFlailer ? shArmor : shHood, darkena(col, 1, 0XFF));
+ if(m == moMiner)
+ queuepoly(VL, ct, shPickAxe, darkena(0xC0C0C0, 0, 0XFF));
+ if(m == moLancer)
+ queuepoly(VL, ct, shPike, darkena(col, 0, 0XFF));
+ if(m == moFlailer) {
+ queuepoly(VL, ct, shFlailBall, darkena(col, 0, 0XFF));
+ queuepoly(VL, ct, shFlailChain, darkena(col, 1, 0XFF));
+ queuepoly(VL, ct, shFlailTrunk, darkena(col, 0, 0XFF));
+ }
+ }
+ else if(m == moTroll) {
+ queuepoly(VL, ct, shYeti, darkena(col, 0, 0xC0));
+ queuepoly(VL, ct, shPHead, darkena(col, 1, 0XFF));
+ queuepoly(VL, ct, shPFace, darkena(col, 2, 0XFF));
+ }
+ else if(m == moDarkTroll) {
+ queuepoly(VL, ct, shYeti, darkena(col, 0, 0xC0));
+ queuepoly(VL, ct, shPHead, darkena(col, 0, 0XFF));
+ queuepoly(VL, ct, shPFace, 0xFFFFFF80);
+ }
+ else if(m == moEarthElemental) {
+ queuepoly(VL, ct, shYeti, darkena(col, 0, 0xC0));
+ queuepoly(VL, ct, shPHead, darkena(col, 0, 0XFF));
+ queuepoly(VL, ct, shPFace, 0xF0000080);
+ }
+ else if(xch == 'd' || xch == 'D') {
+ queuepoly(VL, ct, shPBody, darkena(col, 1, 0xC0));
+ int acol = col;
+ if(xch == 'D') acol = 0xD0D0D0;
+ queuepoly(VL, ct, shDemon, darkena(acol, 0, 0xFF));
+ }
+ else if(m == moEvilGolem) {
+ queuepoly(VL, ct, shPBody, darkena(col, 0, 0XC0));
+ queuepoly(VL, ct, shGolemhead, darkena(col, 1, 0XFF));
+ }
+ else if(isWitch(m)) {
+ int c = 0xFF;
+ if(m == moWitchGhost) c = 0x85 + 120 * sin(ticks / 160.0);
+ if(m == moWitchWinter) drawWinter(VL, 42);
+ if(m == moWitchFlash) drawFlash(VL);
+ if(m == moWitchSpeed) drawSpeed(VL);
+ if(m == moWitchFire) col = firecolor(0);
+ queuepoly(VL, ct, shFemaleBody, darkena(col, 0, c));
+// queuepoly(cV2, ct, shPSword, darkena(col, 0, 0XFF));
+// queuepoly(VL, ct, shHood, darkena(col, 0, 0XC0));
+ if(m == moWitchFire) col = firecolor(100);
+ queuepoly(VL, ct, shFemaleHair, darkena(col, 1, c));
+ if(m == moWitchFire) col = firecolor(200);
+ queuepoly(VL, ct, shPFace, darkena(col, 0, c));
+ if(m == moWitchFire) col = firecolor(300);
+ queuepoly(VL, ct, shWitchDress, darkena(col, 1, 0XC0));
+ }
+ else return true;
+ }
+
+ return false;
+ }
+
+cell *keycell;
+int keycelldist;
+
+void drawCircle(int x, int y, int size, int color) {
+ #ifdef GL
+ if(vid.usingGL) {
+ qglcoords = 0;
+ glcolor(color);
+ x -= vid.xcenter; y -= vid.ycenter;
+ int pts = size * 4;
+ if(pts > 1500) pts = 1500;
+ if(ISMOBILE && pts > 72) pts = 72;
+ for(int r=0; r 1500) pts = 1500;
+ for(int r=0; rtype; t++) if(c->mov[t] && bugsNearby(c->mov[t], dist-1)) return true;
+ return false;
+ }
+
+void drawcell(cell *c, transmatrix V, int spinv) {
+
+ // todo: fix when scrolling
+ if(c->land != laGameBoard && sightrange < 10) {
+ // not yet created
+ if(c->mpdist > 7) return;
+ // (incorrect comment) too far, no bugs nearby
+ if(playermoved && c->cpdist > sightrange) return;
+ }
+
+ if(!euclid) {
+ // draw a web-like map
+ if(webdisplay & 1) {
+ if(c->type == 6) {
+ for(int a=0; a<3; a++)
+ drawline(V*Crad[a*7], V*Crad[a*7+21], 0xd0d0 >> darken);
+ }
+ else {
+ for(int a=0; a<7; a++)
+ drawline(V*C0, V*Crad[(21+a*6)%42], 0xd0d0 >> darken);
+ }
+ }
+
+ if(webdisplay & 2) if(c->type == 7) {
+ drawline(V*C0, V*xpush(tessf)*C0, 0xd0d0 >> darken);
+ }
+
+ if(webdisplay & 4) if(c->type == 7 && c->master->alt) {
+ for(int i=0; i<7; i++)
+ if(c->master->move[i]->alt == c->master->alt->move[0])
+ drawline(V*C0, V*spin(-2*M_PI*i/7)*xpush(tessf)*C0, 0xd000d0 >> darken);
+ }
+ }
+
+ // save the player's view center
+ if(c == cwt.c) {
+ playerfound = true;
+
+/* if(euclid)
+ return d * 84 / c->type;
+ else
+ return 42 - d * 84 / c->type;
+ cwtV = V * spin(-cwt.spin * 2*M_PI/c->type) * spin(M_PI); */
+
+ cwtV = V * spin(displaydir(c, cwt.spin) * M_PI/42);
+ }
+
+ if(1) {
+
+ hyperpoint VC0 = V*C0;
+
+ if(intval(mouseh, VC0) < modist) {
+ modist = intval(mouseh, VC0);
+ mouseover = c;
+ }
+
+ double dfc = euclid ? intval(VC0, C0) : VC0[2];
+
+ if(dfc < centdist) {
+ centdist = dfc;
+ centerover = c;
+ }
+
+ int xc, yc, sc, xs, ys, ss;
+ getcoord(VC0, xc, yc, sc);
+ getcoord(V*xpush(.5)*C0, xs, ys, ss);
+ // int col = 0xFFFFFF - 0x20 * c->maxdist - 0x2000 * c->cpdist;
+
+ if(c->mpdist > 8) return; // not yet generated
+
+ char ch = winf[c->wall].glyph;
+ int col = winf[c->wall].color;
+
+ if(c->land == laAlchemist && c->wall == waNone) col = 0x202020;
+
+ if(c->land == laCrossroads && c->wall == waNone) col = (vid.goteyes ? 0xFF3030 : 0xFF0000);
+ if(c->land == laDesert && c->wall == waNone) col = 0xEDC9AF;
+ if(c->land == laCaves && c->wall == waNone) col = 0x202020;
+ if(c->land == laFjord && c->wall == waNone) col = 0x202020;
+ if(c->land == laDeadCaves && c->wall == waNone) col = 0x202020;
+ if(isHive(c->land)) {
+ col = linf[c->land].color;
+ if(c->wall == waWaxWall)
+ col = int(c->heat) & 0xFFFFFF;
+ }
+ if(c->land == laJungle && c->wall == waNone) col = (vid.goteyes ? 0x408040 : 0x008000);
+ if(c->land == laPower && c->wall == waNone)
+ col = linf[c->land].color;
+/* if(c->land == laFjord && c->wall == waNone) {
+ col = 0x50A020;
+ }
+ if(c->land == laFjord && c->wall == waLake) {
+ col = 0x202080;
+ int i = 0;
+ for(int k=0; ktype; k++) if(c->mov[k] && c->mov[k]->wall != waLake)
+ i++;
+ if(i > 0) {
+ col = gradient(col, 0xFFFFFF, 0, i-fabs(sin(ticks/1500.0)), 7);
+ }
+ } */
+ if(c->land == laWineyard && c->wall == waNone) {
+ col = 0x006000;
+ }
+ if(c->land == laDryForest && c->wall == waNone) {
+ if(c->wall == waDryTree)
+ col = (vid.goteyes ? 0xC0C060 : 0xC0C000);
+ else if(c->wall == waWetTree)
+ col = (vid.goteyes ? 0x60C060 : 0x00C000);
+ else if(c->wall == waNone) {
+ col = gradient(0x008000, 0x800000, 0, c->heat, 10);
+ }
+ }
+ if(c->land == laMirror && c->wall == waNone) col = 0x808080;
+ if(c->land == laMotion && c->wall == waNone) col = 0xF0F000;
+ if(c->land == laGraveyard && c->wall == waNone) col = 0x107010;
+ if(c->land == laCamelot && c->wall == waNone) {
+ int d = showoff ? 0 : celldistAltRelative(c);
+ if(d < 0)
+ col = 0xA0A0A0;
+ else {
+ // a nice floor pattern
+ int v = fjordval(c);
+ int v0 = (v&~3);
+ bool sw = (v&1);
+ if(v0 == 8 || v0 == 12 || v0 == 20 || v0 == 40 || v0 == 36 || v0 == 24)
+ sw = !sw;
+ if(sw)
+ col = 0xC0C0C0;
+ else
+ col = 0xA0A0A0;
+ }
+ }
+ if(c->land == laRlyeh && c->wall == waNone) col = (vid.goteyes ? 0x4080C0 : 0x004080);
+ if(c->land == laTemple) {
+ int d = showoff ? 0 : celldistAlt(c);
+ if(d % TEMPLE_EACH == 0)
+ col = c->wall == waColumn ? winf[waColumn].color :
+ gradient(0x304080, winf[waColumn].color, 0, 0.5, 1);
+// else if(c->type == 7)
+// col = 0x707070;
+ else if(d% 2 == -1)
+ col = 0x304080;
+ else
+ col = 0x405090;
+ }
+ if(c->land == laHell && c->wall == waNone) col = (vid.goteyes ? 0xC03030 : 0xC00000);
+ if(c->land == laGameBoard) {
+ col = linf[c->land].color;
+ if(c->wall != waNone)
+ col = winf[c->wall].color;
+ if(c->wall == waLake) col = 0x202020;
+ if(c->wall == waFrozenLake) col = 0xC0C0C0;
+
+ if(c->type == 7 && vid.boardmode == '3') col = 0;
+ if(c->type == 6 && vid.boardmode == '7') col = 0;
+ }
+
+ if(isIcyLand(c) && isIcyWall(c)) {
+ if(c->heat < -0.4)
+ col = gradient(0x4040FF, 0x0000FF, -0.4, c->heat, -1);
+ else if(c->heat < 0)
+ col = gradient(0x8080FF, 0x4040FF, 0, c->heat, -0.4);
+ else if(c->heat < 0.2)
+ col = gradient(0x8080FF, 0xFFFFFF, 0, c->heat, 0.2);
+ // else if(c->heat < 0.4)
+ // col = gradient(0xFFFFFF, 0xFFFF00, 0.2, c->heat, 0.4);
+ else if(c->heat < 0.6)
+ col = gradient(0xFFFFFF, 0xFF0000, 0.2, c->heat, 0.6);
+ else if(c->heat < 0.8)
+ col = gradient(0xFF0000, 0xFFFF00, 0.6, c->heat, 0.8);
+ else
+ col = 0xFFFF00;
+ if(c->wall == waNone)
+ col = (col & 0xFEFEFE) >> 1;
+ if(c->wall == waLake)
+ col = (col & 0xFCFCFC) >> 2;
+ }
+
+ if(c->wall == waBonfire && c->tmp == 0)
+ col = 0x404040;
+
+ if(isFire(c) && c->tmp > 0)
+ col = firecolor(100);
+
+ if(c->wall == waThumper && c->tmp == 0)
+ col = 0xEDC9AF;
+
+ if(c->wall == waThumper && c->tmp > 0) {
+ int ds = ticks;
+ for(int u=0; u<5; u++) {
+ ld rad = hexf * (.3 * u + (ds%1000) * .0003);
+ int col = gradient(0xFFFFFF, 0, 0, rad, 1.5 * hexf);
+ for(int a=0; a<84; a++)
+ drawline(V*ddi(a, rad)*C0, V*ddi(a+1, rad)*C0, col);
+ }
+ }
+
+ if(c->land == laFjord && c->wall == waCavefloor) {
+ col = gradient(col, 0xFF00, 0, 0.5, 1);
+ // col |= 0xFF00; // col += 0x300060; // col += 0x2F18; col -= 0x100000;
+ }
+
+ if(c->land == laFjord && c->wall == waCavewall) {
+ col = 0xC0FFC0;
+ // col |= 0xFF00; // col += 0x300060; // col += 0x2F18; col -= 0x100000;
+ }
+
+ if(c->land == laHive && items[itOrbInvis] && c->wall == waNone && c->heat)
+ col = gradient(col, 0xFF0000, 0, c->heat, 100);
+
+ if(vid.darkhepta && ishept(c))
+ col = gradient(0, col, 0, 0.75, 1);
+
+ int ycol = col;
+
+ if(c->land == laHive && c->bardir == NOBARRIERS && c->barleft) {
+ col = minf[moBug0+c->barright].color;
+ }
+
+ int xcol = col;
+
+ if(c->item)
+ ch = iinf[c->item].glyph, col = iinf[c->item].color;
+
+ int icol = col;
+
+ if(c->item && c->land == laAlchemist)
+ xcol = col;
+
+ if(c->monst)
+ ch = minf[c->monst].glyph, col = minf[c->monst].color;
+
+ if(c->cpdist == 0 && drawplayer) { ch = '@'; col = cheater ? 0xFF3030 : 0xD0D0D0; }
+
+ if(c->monst == moSlime) {
+ col = winf[c->wall].color;
+ col |= (col>>1);
+ }
+
+ if(c->ligon) {
+ int tim = ticks - lightat;
+ if(tim > 1000) tim = 800;
+ for(int t=0; t<7; t++) if(c->mov[t] && c->mov[t]->ligon) {
+ int hdir = displaydir(c, t);
+ int col = gradient(iinf[itOrbLightning].color, 0, 0, tim, 1100);
+ drawline(V*ddi(ticks, hexf/2)*C0, V*ddi(hdir, crossf)*C0, col);
+ }
+ }
+
+ int ct = c->type;
+
+ bool error = false;
+
+ if(vid.wallmode) {
+
+ poly_outline = 0x000000FF;
+
+ // floor
+
+ int fd =
+ c->land == laAlchemist || c->land == laIce || c->land == laGraveyard ||
+ c->land == laRlyeh || c->land == laTemple ? 1 : 2;
+
+ if(shUser[0][ct-6].s) {
+ for(int i=0; i<8; i++) if(shUser[i][ct-6].s)
+ queuepoly(V, ct, shUser[i][ct-6], darkena(xcol, 2, 0xC0));
+ }
+
+ else if(c->wall == waChasm)
+ ;
+
+ else if(c->land == laWineyard && (c->wall == waVineHalfA || c-> wall == waVineHalfB)) {
+
+ int i =-1;
+ for(int t=0;t<6; t++) if(c->mov[t] && c->mov[t]->wall == c->wall)
+ i = t;
+
+ int hdir = 14 + displaydir(c, i);
+
+ transmatrix V2 = V * spin(M_PI*hdir/42);
+
+ hpcshape *shar = shSemiFeatherFloor;
+
+ if(vid.wallmode == 1) shar = shSemiBFloor;
+ if(vid.wallmode == 2) shar = shSemiFloor;
+
+ int dk = vid.wallmode == 1 ? 0 : vid.wallmode == 2 ? 2 : 1;
+
+ queuepoly(V2, ct, shar[0], darkena(winf[waVinePlant].color, dk, 0xFF));
+ queuepoly(V2, ct, shar[1], darkena(xcol, dk, 0xFF));
+ }
+
+ else if(vid.wallmode == 1 && c->land == laAlchemist)
+ queuepoly(V, ct, shFloor[ct-6], darkena(xcol, 1, 0xFF));
+
+ else if(vid.wallmode == 1 && c->land != laGameBoard)
+ queuepoly(V, ct, shBFloor[ct-6], darkena(xcol, 0, 0xFF));
+
+ else if(vid.wallmode == 2 && c->land != laGameBoard) {
+ queuepoly(V, ct, shFloor[ct-6], darkena(xcol, fd, 0xFF));
+ }
+
+ else if(c->land == laWineyard) {
+ queuepoly(V, ct, (euclid ? shStarFloor : shFeatherFloor)[ct-6], darkena(xcol, 1, 0xFF));
+ }
+
+ else if(c->land == laRlyeh)
+ queuepoly(V, ct, (euclid ? shFloor: shTriFloor)[ct-6], darkena(xcol, 1, 0xFF));
+
+ else if(c->land == laTemple)
+ queuepoly(V, ct, (euclid ? shFloor: shTriFloor)[ct-6], darkena(xcol, 1, 0xFF));
+
+ else if(c->land == laAlchemist)
+ queuepoly(V, ct, shCloudFloor[ct-6], darkena(xcol, 1, 0xFF));
+
+ else if(c->land == laJungle)
+ queuepoly(V, ct, (euclid ? shStarFloor : shFeatherFloor)[ct-6], darkena(xcol, 2, 0xFF));
+
+ else if(c->land == laGraveyard)
+ queuepoly(V, ct, (euclid ? shFloor : shCrossFloor)[ct-6], darkena(xcol, 1, 0xFF));
+
+ else if(c->land == laFjord) {
+ queuepoly(V, ct, (euclid ? shFloor : shCaveFloor)[ct-6], darkena(xcol, 2, 0xFF));
+ }
+
+ else if(c->land == laDeadCaves) {
+ queuepoly(V, ct, (euclid ? shFloor : shCaveFloor)[ct-6], darkena(xcol, 1, 0xFF));
+ }
+
+ else if(c->land == laMotion)
+ queuepoly(V, ct, shMFloor[ct-6], darkena(xcol, 2, 0xFF));
+
+ else if(c->land == laHell)
+ queuepoly(V, ct, (euclid ? shStarFloor : shDemonFloor)[ct-6], darkena(xcol, 2, 0xFF));
+
+ else if(c->land == laIce)
+// queuepoly(V, ct, shFloor[ct-6], darkena(xcol, 2, 0xFF));
+ queuepoly(V, ct, shStarFloor[ct-6], darkena(xcol, 1, 0xFF));
+
+ else if(c->land == laCocytus)
+ queuepoly(V, ct, (euclid ? shCloudFloor : shDesertFloor)[ct-6], darkena(xcol, 1, 0xFF));
+
+ else if(c->land == laPower)
+ queuepoly(V, ct, (euclid ? shCloudFloor : shPowerFloor)[ct-6], darkena(xcol, 1, 0xFF));
+
+ else if(c->land == laHive) {
+ queuepoly(V, ct, shFloor[ct-6], darkena(xcol, 1, 0xFF));
+ queuepoly(V, ct, shMFloor[ct-6], darkena(xcol, 2, 0xFF));
+ if(c->wall != waWaxWall && c->wall != waDeadTroll && c->wall != waVinePlant)
+ queuepoly(V, ct, shMFloor2[ct-6], darkena(xcol, xcol==ycol ? 1 : 2, 0xFF));
+ }
+
+ else if(c->land == laCaves)
+ queuepoly(V, ct, (euclid ? shCloudFloor : shCaveFloor)[ct-6], darkena(xcol, 2, 0xFF));
+
+ else if(c->land == laGameBoard) {
+ if(c->type == 7 && vid.boardmode == '7')
+ queuepoly(V, ct, vid.wallmode == 1 ? shBFloor[ct-6] : shBigHepta, darkena(xcol, 2, 0xFF));
+
+ if(c->type == 6 && vid.boardmode == '3')
+ queuepoly(V, ct, vid.wallmode == 1 ? shBFloor[ct-6] : shBigTriangle, darkena(xcol, 2, 0xFF));
+
+ if(vid.boardmode == 'h')
+ queuepoly(V, ct, vid.wallmode == 1 ? shBFloor[ct-6] : shFloor[c->type - 6], darkena(xcol, 2, 0xFF));
+
+ if(vid.boardmode == 'f') {
+ static int fcol[4] = { 0x404040FF, 0xFF000080, 0x008000FF, 0x000080FF };
+ int fv = fjordval(c);
+ col = fcol[fv&3];
+ queuepoly(V, ct, vid.wallmode == 1 ? shBFloor[ct-6] : shFloor[c->type - 6], col);
+ if(fjordwalled[fv])
+ queuepoly(V, ct, shWall[ct-6], col | 0xFF3F3F);
+ int siz = int(sqrt(squar(xc-xs)+squar(yc-ys))) / 5;
+ displaystr(xc, yc, sc, siz, its(fv), 0xFFFFFFFF, 8);
+ }
+ }
+
+ else if(c->land == laDesert)
+ queuepoly(V, ct, (euclid ? shCloudFloor : shDesertFloor)[ct-6], darkena(xcol, 2, 0xFF));
+
+ else if(c->land == laDryForest)
+ queuepoly(V, ct, (euclid ? shStarFloor : shDesertFloor)[ct-6], darkena(xcol, 2, 0xFF));
+
+ else
+ queuepoly(V, ct, shFloor[ct-6], darkena(xcol, 2, 0xFF));
+ // walls
+
+ char xch = winf[c->wall].glyph;
+
+ if(c->wall == waBigStatue)
+ queuepoly(V, ct, shStatue,
+ darkena(winf[c->wall].color, 0, 0xFF)
+ );
+
+ else if(c->wall == waSulphurC)
+ queuepoly(V, ct, shGiantStar[ct-6], darkena(xcol, 0, 0xFF));
+
+ else if(c->wall == waFrozenLake || c->wall == waLake || c->wall == waCamelotMoat ||
+ c->wall == waRoundTable)
+ ;
+
+ else if(xch == '#') {
+ if(c->wall == waVinePlant)
+ xcol = 0x60C000;
+ queuepoly(V, ct, shWall[ct-6], darkena(xcol, 0, 0xFF));
+ }
+
+ else if(xch == '%')
+ queuepoly(V, ct, shMirror, darkena(xcol, 0, 0xC0));
+
+ else if(isActiv(c)) {
+ ld sp = c->tmp > 0 ? ticks / 500. : 0;
+ queuepoly(V * spin(sp), ct, shStar, darkena(col, 0, 0xF0));
+ }
+
+ else if(xch == '+' && c->land == laGraveyard && c->wall != waFloorB && c->wall != waFloorA)
+ queuepoly(V, ct, shCross, darkena(xcol, 0, 0xFF));
+
+ else if(xch != '.' && xch != '+' && xch != '>' && xch != ':' && xch != ';' && c->wall != waSulphur)
+ error = true;
+
+/* if(c->master->alt) {
+ int d = celldistAlt(c);
+ int siz = int(sqrt(squar(xc-xs)+squar(yc-ys))) / 5;
+ if(d != ALTDIST_UNKNOWN && d != ALTDIST_BOUNDARY)
+ displaystr(xc, yc, sc, siz, its(d), 0xFFFFFFFF, 8);
+ } */
+ }
+ else if(!(c->item || c->monst || c->cpdist == 0)) error = true;
+
+ // treasure
+
+ char xch = iinf[c->item].glyph;
+ hpcshape *xsh =
+ c->item == itHolyGrail ? &shGrail :
+ xch == '*' ? &shGem[ct-6] : xch == '%' ? &shDaisy : xch == '$' ? &shStar : xch == ';' ? &shTriangle :
+ xch == '!' ? &shTriangle : c->item == itBone ? &shNecro : c->item == itStatue ? &shStatue :
+ xch == '?' ? &shBookCover :
+ c->item == itKey ? &shKey : NULL;
+
+ if(doHighlight()) {
+ int k = itemclass(c->item);
+ if(k == IC_TREASURE)
+ poly_outline = 0xFFFF00FF;
+ else if(k == IC_ORB)
+ poly_outline = 0xFF8000FF;
+ else
+ poly_outline = 0xFFFFFFFF;
+ }
+
+ if(vid.monmode == 0 && c->item)
+ error = true;
+
+ else if(xsh)
+ queuepoly(V * spin(ticks / 1500.), ct, *xsh, darkena(icol, 0, 0xF0));
+
+ else if(xch == 'o') {
+ if(c->item == itOrbFire) icol = firecolor(100);
+ queuepoly(V, ct, shDisk, darkena(icol, 0, 0xC0));
+ if(c->item == itOrbFire) icol = firecolor(200);
+ queuepoly(V, ct, shRing, darkena(icol, 0, int(0x80 + 0x70 * sin(ticks / 300.))));
+ }
+
+ else if(c->item) error = true;
+
+ if(xsh == &shBookCover)
+ queuepoly(V * spin(ticks / 1500.), ct, shBook, 0x805020FF);
+
+ // monsters
+
+ if(flashat > 0 && c == flashcell) {
+ int tim = ticks - flashat;
+ if(tim > 1000) flashat = 0;
+ for(int u=0; u<=tim; u++) {
+ if((u-tim)%50) continue;
+ if(u < tim-150) continue;
+ ld rad = u * 3 / 1000.;
+ rad = rad * (5-rad) / 2;
+ rad *= hexf;
+ int col = iinf[itOrbFlash].color;
+ if(u > 500) col = gradient(col, 0, 500, u, 1100);
+ for(int a=0; a<84; a++)
+ drawline(V*ddi(a, rad)*C0, V*ddi(a+1, rad)*C0, col);
+ }
+ }
+
+ error |= drawMonster(V, ct, c, col);
+
+/* if(ch == '.') {
+ col = darkened(col);
+ for(int t=0; t= 2) {
+ displaychr(xc-2, yc, sc, siz, ch, 0);
+ displaychr(xc+2, yc, sc, siz, ch, 0);
+ displaychr(xc, yc-2, sc, siz, ch, 0);
+ displaychr(xc, yc+2, sc, siz, ch, 0);
+ }
+ displaychr(xc, yc, sc, siz, ch, col);
+ }
+
+ if(c == keycell) {
+ displaychr(xc, yc, sc, 2*vid.fsize, 'X', 0x10101 * int(128 + 100 * sin(ticks / 150.)));
+ displaystr(xc, yc, sc, vid.fsize, its(keycelldist), 0x10101 * int(128 - 100 * sin(ticks / 150.)), 8);
+ }
+
+#if defined(ANDROID) || defined(PANDORA) || defined(IOS)
+ if(c == lmouseover && (mousepressed || ISANDROID || ISMOBILE)) {
+ drawCircle(xc, yc, int(sqrt(squar(xc-xs)+squar(yc-ys)) * .8), c->cpdist > 1 ? 0x00FFFF : 0xFF0000);
+ }
+#endif
+
+ if(c == lmouseover && c->land == laGameBoard) {
+ drawCircle(xc, yc, int(sqrt(squar(xc-xs)+squar(yc-ys)) * .8), 0x00FFFF);
+ }
+
+ if(joydir >= 0 && c == cwt.c->mov[(joydir+cwt.spin) % cwt.c->type])
+ drawCircle(xc, yc, int(sqrt(squar(xc-xs)+squar(yc-ys)) * (.78 - .02 * sin(ticks/199.0))), 0x00FF00);
+
+#ifndef MOBILE
+ if(c == lcenterover && !playermoved)
+ drawCircle(xc, yc, int(sqrt(squar(xc-xs)+squar(yc-ys)) * (.70 - .06 * sin(ticks/200.0))), int(175 + 25 * sin(ticks / 200.0)));
+#endif
+
+ if(cmode == emDraw && cwt.c->type == 6 && ct == 6) for(int a=0; arots; a++) {
+
+ transmatrix V2 = V * spin(M_PI + 2*M_PI*a/dsCur->rots);
+
+ if(outofmap(mouseh)) break;
+
+ hyperpoint P2 = V2 * inverse(cwtV) * mouseh;
+
+ int xc, yc, sc;
+ getcoord(P2, xc, yc, sc);
+ displaychr(xc, yc, sc, 10, 'x', 0xFF);
+
+ if(crad > 0 && c->cpdist <= 3) {
+ lalpha = 0x80;
+ transmatrix movtocc = V2 * inverse(cwtV) * rgpushxto0(ccenter);
+ for(int d=0; d<84; d++)
+ drawline(movtocc * ddi(d+1, crad) * C0, movtocc * ddi(d, crad) * C0, 0xC00000);
+ lalpha = 0xFF;
+ }
+ }
+
+ // process mouse
+
+ for(int i=-1; itype; i++) if(i == -1 ? cwt.c == c : cwt.c->mov[i % cwt.c->type] == c) {
+ int mx = mousex, my = mousey;
+ if(revcontrol) mx = vid.xcenter*2-mx, my = vid.ycenter*2-my;
+
+ int ndist = (xc-mx) * (xc-mx) + (yc-my) * (yc-my);
+ if(ndist < mousedist) mousedist = ndist, mousedest = i;
+ }
+
+ // drawline(V*C0, V*Crad[0], 0xC00000);
+ if(c->bardir != NODIR && c->bardir != NOBARRIERS) {
+ drawline(V*C0, V*heptmove[c->bardir]*C0, 0x505050 >> darken);
+ drawline(V*C0, V*hexmove[c->bardir]*C0, 0x505050 >> darken);
+ }
+
+ }
+ }
+
+string buildCredits();
+
+string buildHelpText() {
+ string h;
+ h += XLAT("Welcome to HyperRogue");
+#ifdef ANDROID
+ h += XLAT(" for Android");
+#endif
+#ifdef IOS
+ h += XLAT(" for iOS");
+#endif
+ h += XLAT("! (version %1)\n\n", VER);
+
+ h += XLAT(
+MC "You have been trapped in a strange, non-Euclidean world. Collect as much treasure as possible "
+MC "before being caught by monsters. The more treasure you collect, the more "
+MC "monsters come to hunt you, as long as you are in the same land type. The "
+M "Orbs of Yendor are the ultimate treasure; get at least one of them to win the game!"
+ );
+ h += XLAT(" (press ESC for some hints about it).");
+ h += "\n\n";
+
+ h += XLAT(
+MC "You can fight most monsters by moving into their location. "
+MC "The monster could also kill you by moving into your location, but the game "
+M "automatically cancels all moves which result in that.\n\n"
+ );
+#ifdef MOBILE
+ h += XLAT(
+MC "Usually, you move by touching somewhere on the map; you can also touch one "
+MC "of the four buttons on the map corners to change this (to scroll the map "
+MC "or get information about map objects). You can also touch the "
+M "numbers displayed to get their meanings.\n"
+ );
+#else
+ h += XLAT(
+MC "Move with mouse, num pad, qweadzxc, or hjklyubn. Wait by pressing 's' or '.'. Spin the world with arrows, PageUp/Down, and Home/Space. "
+MC "To save the game you need an Orb of Safety. Press 'v' for config, ESC for the quest status and menu.\n\n"
+ );
+ h += XLAT(
+ "You can right click any element to get more information about it.\n\n"
+ );
+#endif
+ h += XLAT("See more on the website: ")
+ + "http//roguetemple.com/z/hyper.php\n\n";
+
+#ifdef MOBILE
+ h += buildCredits();
+#else
+ h += XLAT("Press 'c' for credits.");
+#endif
+ return h;
+ }
+
+string musiclicense;
+
+string buildCredits() {
+ string h;
+ h += XLAT("game design, programming, texts and graphics by Zeno Rogue \n\n");
+ if(lang() != 0)
+ h += XLAT("add credits for your translation here");
+#ifndef NOLICENSE
+ h += XLAT(
+MC "released under GNU General Public License version 2 and thus "
+M "comes with absolutely no warranty; see COPYING for details\n\n"
+ );
+#endif
+ h += XLAT(
+M "special thanks to the following people for their bug reports, feature requests, porting, and other help:\n\n%1\n\n",
+ "Konstantin Stupnik, ortoslon, chrysn, Adam Borowski, Damyan Ivanov, Ryan Farnsley, mcobit, Darren Grey, tricosahedron, Maciej Chojecki, Marek Čtrnáct"
+ );
+#ifdef EXTRALICENSE
+ h += EXTRALICENSE;
+#endif
+ if(musiclicense != "") h += musiclicense;
+ return h;
+ }
+
+string generateHelpForItem(eItem it) {
+ string help = XLAT(iinf[it].help);
+#ifdef ANDROID
+ if(it == itOrbSafety)
+ help += XLAT("This might be useful for Android devices with limited memory.");
+ if(it == itGreenStone)
+ help += XLAT("You can touch the Dead Orb in your inventory to drop it.");
+#else
+ if(it == itOrbSafety)
+ help += XLAT("Thus, it is potentially useful for extremely long games, which would eat all the memory on your system otherwise.\n");
+ if(isRangedOrb(it))
+ help += XLAT("You can also scroll to the desired location and then press 't'.");
+ if(it == itGreenStone)
+ help += XLAT("You can press 'g' or click them in the list to drop a Dead Orb.");
+#endif
+ return help;
+ }
+
+void describeMouseover() {
+ cell *c = mousing ? mouseover : playermoved ? NULL : centerover;
+ string out = mouseovers;
+ if(!c) { }
+ else if(cmode == emNormal) {
+ out = XLAT1(linf[c->land].name);
+ help = XLAT(linf[c->land].help);
+
+ // if(c->land == laIce) out = "Icy Lands (" + fts(60 * (c->heat - .4)) + " C)";
+ if(c->land == laIce) out += " (" + fts(60 * (c->heat-.4)) + " °C)";
+ if(c->land == laCocytus) out += " (" + fts(60 * (c->heat-.8)) + " °C)";
+ if(c->land == laDryForest && c->heat > .5) out += " (" + its(int(c->heat+.1))+"/10)";
+
+/* // Hive debug
+ if(c->land == laHive) {
+ out += " [" + its(c->tmp) + " H" + its(int(c->heat));
+ if(c->tmp >= 0 && c->tmp < size(buginfo) && buginfo[c->tmp].where == c) {
+ buginfo_t b(buginfo[c->tmp]);
+ for(int k=0; k<3; k++) out += ":" + its(b.dist[k]);
+ for(int k=0; k<3; k++)
+ for(int i=0; itmp)
+ out += " B"+its(k)+":"+its(i);
+ }
+ out += "]";
+ } */
+
+ if(c->wall && !((c->wall == waFloorA || c->wall == waFloorB) && c->item)) {
+ out += ", "; out += XLAT1(winf[c->wall].name);
+ if(!((c->wall == waCavefloor || c->wall == waCavewall) && c->land == laFjord))
+ help = XLAT(winf[c->wall].help);
+ }
+
+ if(isActiv(c)) {
+ if(c->tmp < 0) out += XLAT(" (touch to activate)");
+ if(c->tmp == 0) out += XLAT(" (expired)");
+ if(c->tmp > 0 && !(c->land == laPower && c->wall == waBonfire))
+ out += XLAT(" [%1 turns]", its(c->tmp));
+ }
+
+ if(c->monst) {out += ", "; out += XLAT1(minf[c->monst].name); help = XLAT(minf[c->monst].help);}
+
+ if(c->item) {
+ out += ", ";
+ out += XLAT1(iinf[c->item].name);
+ if(!c->monst) help = generateHelpForItem(c->item);
+ }
+
+ if(!c->cpdist) out += XLAT(", you");
+ }
+ else if(cmode == emVisual1) {
+ if(getcstat == 'p') {
+ out = XLAT("0 = Klein model, 1 = Poincaré model");
+ if(vid.alpha < -0.5)
+ out = XLAT("you are looking through it!");
+ }
+ else if(getcstat == 'r') {
+ out = XLAT("simply resize the window to change resolution");
+ }
+ else if(getcstat == 'f') {
+ out = XLAT("[+] keep the window size, [-] use the screen resolution");
+ }
+ else if(getcstat == 'a' && vid.aspeed > -4.99)
+ out = XLAT("+5 = center instantly, -5 = do not center the map");
+ else if(getcstat == 'a')
+ out = XLAT("press Space or Home to center on the PC");
+ else if(getcstat == 'w')
+ out = XLAT("also hold Alt during the game to toggle high contrast");
+ else if(getcstat == 'w' || getcstat == 'm')
+ out = XLAT("You can choose one of the several modes");
+ else if(getcstat == 'c')
+ out = XLAT("The axes help with keyboard movement");
+ else if(getcstat == 'g')
+ out = XLAT("Affects looks and grammar");
+#ifndef MOBILE
+ else if(getcstat == 's')
+ out = XLAT("Config file: %1", conffile);
+#endif
+ else out = "";
+ }
+ else if(cmode == emVisual2) {
+ if(getcstat == 'p') { // stick) {
+ if(autojoy)
+ out = XLAT("joystick mode: automatic (release the joystick to move)");
+ if(!autojoy)
+ out = XLAT("joystick mode: manual (press a button to move)");
+ }
+ else if(getcstat == 'e')
+ out = XLAT("You need special glasses to view the game in 3D");
+ else if(getcstat == 'f')
+ out = XLAT("Reduce the framerate limit to conserve CPU energy");
+ }
+
+ mouseovers = out;
+ #ifndef MOBILE
+ if(cmode != emPickScores)
+ displayfr(vid.xres/2, vid.fsize, 2, vid.fsize, out, linf[cwt.c->land].color, 8);
+ if(mousey < vid.fsize * 3/2) getcstat = SDLK_F1;
+ #endif
+ }
+
+void drawrec(const heptspin& hs, int lev, hstate s, transmatrix V) {
+
+ cell *c = hs.h->c7;
+
+ drawcell(c, V * spin(hs.spin*2*M_PI/7), hs.spin);
+
+ if(lev <= 0) return;
+
+ for(int d=0; d<7; d++) {
+ int ds = fixrot(hs.spin + d);
+ // createMov(c, ds);
+ if(c->mov[ds] && c->spn[ds] == 0)
+ drawcell(c->mov[ds], V * hexmove[d], 0);
+ }
+
+ if(lev <= 1) return;
+
+ for(int d=0; d<7; d++) {
+ hstate s2 = transition(s, d);
+ if(s2 == hsError) continue;
+ heptspin hs2 = hsstep(hsspin(hs, d), 0);
+ drawrec(hs2, lev-2, s2, V * heptmove[d]);
+ }
+
+ }
+
+int mindx=-7, mindy=-7, maxdx=7, maxdy=7;
+
+
+void drawEuclidean() {
+ eucoord px, py;
+ if(!lcenterover) lcenterover = cwt.c;
+ printf("centerover = %p player = %p [%d,%d]-[%d,%d]\n", lcenterover, cwt.c,
+ mindx, mindy, maxdx, maxdy);
+ decodeMaster(lcenterover->master, px, py);
+
+ int minsx = mindx-1, maxsx=maxdx+1, minsy=mindy-1, maxsy=maxdy+1;
+ mindx=maxdx=mindy=maxdy=0;
+
+ for(int dx=minsx; dx<=maxsx; dx++)
+ for(int dy=minsy; dy<=maxsy; dy++) {
+ eucoord x = dx+px;
+ eucoord y = dy+py;
+ cell *c = euclideanAt(x,y);
+ if(!c) continue;
+ transmatrix Mat = Id;
+ Mat[2][2] = 1;
+ Mat[0][2] += (x + y * .5) * eurad;
+ double q3 = sqrt(double(3));
+ Mat[1][2] += y * q3 /2 * eurad;
+ while(Mat[0][2] <= -16384 * eurad) Mat[0][2] += 32768 * eurad;
+ while(Mat[0][2] >= 16384 * eurad) Mat[0][2] -= 32768 * eurad;
+ while(Mat[1][2] <= -16384 * q3 * eurad) Mat[1][2] += 32768 * q3 * eurad;
+ while(Mat[1][2] >= 16384 * q3 * eurad) Mat[1][2] -= 32768 * q3 * eurad;
+ Mat = View * Mat;
+
+ // Mat[0][0] = -1;
+ // Mat[1][1] = -1;
+
+ // Mat[2][0] = x*x/10;
+ // Mat[2][1] = y*y/10;
+ // Mat = Mat * xpush(x-30) * ypush(y-30);
+
+ int cx, cy, shift;
+ getcoord(Mat * C0, cx, cy, shift);
+ if(cx >= 0 && cy >= 0 && cx < vid.xres && cy < vid.yres) {
+ if(dx < mindx) mindx = dx;
+ if(dy < mindy) mindy = dy;
+ if(dx > maxdx) maxdx = dx;
+ if(dy > maxdy) maxdy = dy;
+ }
+
+ drawcell(c, Mat, 0);
+ }
+ }
+
+void drawthemap() {
+
+ keycell = NULL;
+
+ if(yii < size(yi)) {
+ if(!yi[yii].found) for(int i=0; icpdist <= sightrange) {
+ keycell = yi[yii].path[i];
+ keycelldist = YDIST - i;
+ }
+ }
+
+ #ifndef MOBILE
+ lmouseover = mouseover;
+ #endif
+ mousedist = 1000000;
+ mousedest = -1;
+ modist = 1e20; mouseover = NULL; mouseovers = XLAT("Press F1 or right click for help");
+ centdist = 1e20; lcenterover = centerover; centerover = NULL;
+ #ifdef MOBILE
+ mouseovers = XLAT("No info about this...");
+ #endif
+ if(outofmap(mouseh))
+ modist = -5;
+ playerfound = false;
+
+ if(euclid)
+ drawEuclidean();
+ else
+ drawrec(viewctr,
+ (!playermoved) ? sightrange+1 : sightrange + 4,
+ hsOrigin, View);
+ }
+
+void centerpc(ld aspd) {
+ hyperpoint H = cwtV * C0;
+ ld R = sqrt(H[0] * H[0] + H[1] * H[1]);
+ if(R < 1e-9) {
+ return;
+ }
+
+ if(euclid) {
+ // Euclidean
+ aspd *= (2+3*R*R);
+ if(aspd > R) aspd = R;
+
+ View[0][2] -= cwtV[0][2] * aspd / R;
+ View[1][2] -= cwtV[1][2] * aspd / R;
+ }
+
+ else {
+ aspd *= (1+R);
+
+ if(R < aspd) {
+ View = gpushxto0(H) * View;
+ }
+ else
+ View = rspintox(H) * xpush(-aspd) * spintox(H) * View;
+ }
+ }
+
+void drawmovestar() {
+
+ if(!playerfound) return;
+
+ if(vid.axes == 0 || (vid.axes == 1 && mousing)) return;
+
+ hyperpoint H = cwtV * C0;
+ ld R = sqrt(H[0] * H[0] + H[1] * H[1]);
+ transmatrix Centered = Id;
+
+ if(euclid)
+ Centered = eupush(H[0], H[1]);
+ else if(R > 1e-9) Centered = rgpushxto0(H);
+
+ int starcol = (vid.goteyes? 0xE08060 : 0xC00000);
+
+ if(vid.axes == 3 || (vid.wallmode == 2 && vid.axes == 1))
+ queuepoly(Centered, 7, shMovestar, darkena(starcol, 0, 0xFF));
+
+ else for(int d=0; d<8; d++) {
+ int col = starcol;
+#ifdef PANDORA
+ if(leftclick && (d == 2 || d == 6 || d == 1 || d == 7)) col >>= 2;
+ if(rightclick && (d == 2 || d == 6 || d == 3 || d == 5)) col >>= 2;
+ if(!leftclick && !rightclick && (d&1)) col >>= 2;
+#endif
+// EUCLIDEAN
+ if(euclid)
+ drawline(Centered * C0, Centered * ddi(d * 10.5, 0.5) * C0, col >> darken);
+ else
+ drawline(Centered * C0, Centered * spin(M_PI*d/4)* xpush(.5) * C0, col >> darken);
+ }
+ }
+
+void optimizeview() {
+
+ int turn = 0;
+ ld best = INF;
+
+ transmatrix TB;
+
+ for(int i=-1; i<7; i++) {
+
+ ld trot = -i * M_PI * 2 / 7.0;
+ transmatrix T = i < 0 ? Id : spin(trot) * xpush(tessf) * spin(M_PI);
+ hyperpoint H = View * T * C0;
+ if(H[2] < best) best = H[2], turn = i, TB = T;
+ }
+
+ if(turn >= 0) {
+ View = View * TB;
+ fixmatrix(View);
+ viewctr = hsspin(viewctr, turn);
+ viewctr = hsstep(viewctr, 0);
+ }
+ }
+
+int vectodir(const hyperpoint& P) {
+ hyperpoint H = cwtV * C0;
+ ld R = sqrt(H[0] * H[0] + H[1] * H[1]);
+ transmatrix Centered = cwtV;
+ if(!euclid)
+ Centered = gpushxto0(H) * Centered;
+ else if(R > 1e-9)
+ Centered = eupush(-H[0], -H[1]) * Centered;
+ int bdir = -1;
+ ld binv = 99;
+ for(int i=0; itype; i++) {
+ ld inv = intval(Centered * spin(-i * 2 * M_PI /cwt.c->type) * xpush(1) * C0, P);
+ if(inv < binv) binv = inv, bdir = i;
+ }
+ // if(euclid) bdir = (bdir + 3) % 6;
+ return bdir;
+ }
+
+void movepckeydir(int d) {
+ // EUCLIDEAN
+ if(euclid)
+ movepcto(vectodir(spin(-d * M_PI/4) * eupush(1, 0) * C0));
+ else
+ movepcto(vectodir(spin(-d * M_PI/4) * xpush(1) * C0));
+ }
+
+void checkjoy() {
+ ld joyvalue1 = vid.joyvalue * vid.joyvalue;
+ ld joyvalue2 = vid.joyvalue2 * vid.joyvalue2;
+
+ ld jx = joyx;
+ ld jy = joyy;
+ ld sq = jx*jx+jy*jy;
+
+ if(autojoy) {
+ if(sq < joyvalue1) { if(joydir >= 0) movepcto(joydir); joydir = -1; return; }
+ if(sq < joyvalue2 && joydir == -1) return;
+ }
+ else {
+ if(sq < joyvalue1) { joydir = -1; return; }
+ }
+
+ joydir = vectodir(hpxy(jx, jy));
+ }
+
+void checkpanjoy(double t) {
+
+ if(panjoyx * panjoyx + panjoyy * panjoyy < vid.joypanthreshold * vid.joypanthreshold)
+ return;
+
+ ld jx = panjoyx * t * vid.joypanspeed;
+ ld jy = panjoyy * t * vid.joypanspeed;
+
+ playermoved = false;
+ View = gpushxto0(hpxy(jx, jy)) * View;
+ }
+
+void calcparam() {
+ vid.xcenter = vid.xres / 2;
+ vid.ycenter = vid.yres / 2;
+ vid.radius = int(vid.scale * vid.ycenter) - (ISANDROID ? 2 : ISIOS ? 40 : 40);
+
+ if(vid.xres < vid.yres) {
+ vid.radius = int(vid.scale * vid.xcenter) - (ISIOS ? 10 : 2);
+ vid.ycenter = vid.yres - vid.radius - vid.fsize - (ISIOS ? 10 : 0);
+ }
+
+ vid.beta = 1 + vid.alpha + vid.eye;
+ vid.alphax = vid.alpha + vid.eye;
+ vid.goteyes = vid.eye > 0.001 || vid.eye < -0.001;
+ }
+
+#ifndef MOBILE
+void displayStat(int y, const string& name, const string& val, char mkey) {
+
+ int dy = vid.fsize * y + vid.yres/4;
+ int dx = vid.xres/2 - 100;
+
+ bool xthis = (mousey >= dy-vid.fsize/2 && mousey <= dy + vid.fsize/2);
+ int xcol = 0x808080;
+
+ if(xthis) {
+ getcstat = mkey; getcshift = 0;
+ int mx = mousex - dx;
+ if(mx >= 0 && mx <= 100) {
+ if(mx < 20) getcshift = -1 , xcol = 0xFF0000;
+ else if(mx < 40) getcshift = -0.1 , xcol = 0x0000FF;
+ else if(mx < 50) getcshift = -0.01, xcol = 0x00FF00;
+ if(mx > 80) getcshift = +1 , xcol = 0xFF0000;
+ else if(mx > 60) getcshift = +0.1 , xcol = 0x0000FF;
+ else if(mx > 50) getcshift = +0.01, xcol = 0x00FF00;
+ }
+ }
+
+ if(val != "") {
+ displaystr(dx, dy, 0, vid.fsize, val, xthis ? 0xFFFF00 : 0x808080, 16);
+ displaystr(dx+25, dy, 0, vid.fsize, "-", xthis && getcshift < 0 ? xcol : 0x808080, 8);
+ displaystr(dx+75, dy, 0, vid.fsize, "+", xthis && getcshift > 0 ? xcol : 0x808080, 8);
+ }
+
+ displaystr(dx+100, dy, 0, vid.fsize, s0 + mkey, xthis ? 0xFFFF00 : 0xC0F0C0, 0);
+
+ displaystr(dx+125, dy, 0, vid.fsize, name, xthis ? 0xFFFF00 : 0x808080, 0);
+ }
+
+void displayStatHelp(int y, string name) {
+
+ int dy = vid.fsize * y + vid.yres/4;
+ int dx = vid.xres/2 - 100;
+
+ displaystr(dx+100, dy, 0, vid.fsize, name, 0xC0C0C0, 0);
+ }
+
+void displayButton(int x, int y, const string& name, int key, int align, int rad = 0) {
+ if(displayfr(x, y, rad, vid.fsize, name, 0x808080, align)) {
+ displayfr(x, y, rad, vid.fsize, name, 0xFFFF00, align);
+ getcstat = key;
+ }
+ }
+
+void quitOrAgain() {
+ int y = vid.yres * (618) / 1000;
+ displayButton(vid.xres/2, y + vid.fsize*1/2,
+ (items[itOrbSafety] && havesave) ?
+ XLAT("Press Enter or F10 to save") :
+ XLAT("Press Enter or F10 to quit"),
+ SDLK_RETURN, 8, 2);
+ displayButton(vid.xres/2, y + vid.fsize*2, XLAT("or 'r' or F5 to restart"), 'r', 8, 2);
+ displayButton(vid.xres/2, y + vid.fsize*7/2, XLAT("or 't' to see the top scores"), 't', 8, 2);
+ if(canmove) displayButton(vid.xres/2, y + vid.fsize*10/2, XLAT("or another key to continue"), ' ', 8, 2);
+ else displayButton(vid.xres/2, y + vid.fsize*10/2, XLAT("or ESC to see how it ended"), ' ', 8, 2);
+ }
+#endif
+
+int calcfps() {
+ #define CFPS 30
+ static int last[CFPS], lidx = 0;
+ int ct = ticks;
+ int ret = ct - last[lidx];
+ last[lidx] = ct;
+ lidx++; lidx %= CFPS;
+ if(ret == 0) return 0;
+ return (1000 * CFPS) / ret;
+ }
+
+int msgscroll = 0;
+
+string timeline() {
+ int timespent = savetime + (timerstopped ? 0 : (time(NULL) - timerstart));
+ char buf[20];
+ sprintf(buf, "%d:%02d", timespent/60, timespent % 60);
+ return XLAT("%1 turns (%2)", its(turncount), buf);
+ }
+
+void showGameover() {
+ int y = vid.yres * (1000-618) / 1000 - vid.fsize * 7/2;
+ displayfr(vid.xres/2, y, 4, vid.fsize*2,
+ cheater ? XLAT("It is a shame to cheat!") :
+ showoff ? XLAT("Showoff mode") :
+ canmove ? XLAT("Quest status") :
+ XLAT("GAME OVER"), 0xC00000, 8
+ );
+ displayfr(vid.xres/2, y + vid.fsize*2, 2, vid.fsize, XLAT("Your score: %1", its(gold())), 0xD0D0D0, 8);
+ displayfr(vid.xres/2, y + vid.fsize*3, 2, vid.fsize, XLAT("Enemies killed: %1", its(tkills())), 0xD0D0D0, 8);
+
+ if(!timerstopped && !canmove) {
+ savetime += time(NULL) - timerstart;
+ timerstopped = true;
+ }
+ if(canmove && !timerstart)
+ timerstart = time(NULL);
+
+ if(items[itOrbYendor]) {
+ displayfr(vid.xres/2, y + vid.fsize*4, 2, vid.fsize, XLAT("Orbs of Yendor found: %1", its(items[itOrbYendor])), 0xFF00FF, 8);
+ displayfr(vid.xres/2, y + vid.fsize*5, 2, vid.fsize, XLAT("CONGRATULATIONS!"), 0xFFFF00, 8);
+ }
+ else {
+ if(gold() < 30)
+ displayfr(vid.xres/2, y+vid.fsize*5, 2, vid.fsize, XLAT("Collect 30 $$$ to access more worlds"), 0xC0C0C0, 8);
+ else if(gold() < 60)
+ displayfr(vid.xres/2, y+vid.fsize*5, 2, vid.fsize, XLAT("Collect 60 $$$ to access even more lands"), 0xC0C0C0, 8);
+ else if(!hellUnlocked())
+ displayfr(vid.xres/2, y+vid.fsize*5, 2, vid.fsize, XLAT("Collect at least 10 treasures in each of 9 types to access Hell"), 0xC0C0C0, 8);
+ else if(items[itHell] < 10)
+ displayfr(vid.xres/2, y+vid.fsize*5, 2, vid.fsize, XLAT("Collect at least 10 Demon Daisies to find the Orbs of Yendor"), 0xC0C0C0, 8);
+ else if(size(yi) == 0)
+ displayfr(vid.xres/2, y+vid.fsize*5, 2, vid.fsize, XLAT("Look for the Orbs of Yendor in Hell or in the Crossroads!"), 0xC0C0C0, 8);
+ else
+ displayfr(vid.xres/2, y+vid.fsize*5, 2, vid.fsize, XLAT("Unlock the Orb of Yendor!"), 0xC0C0C0, 8);
+ }
+ if(tkills() < 100)
+ displayfr(vid.xres/2, y+vid.fsize*6, 2, vid.fsize, XLAT("Defeat 100 enemies to access the Graveyard"), 0xC0C0C0, 8);
+ else if(hellUnlocked()) {
+ #define NUMSEQ 19
+ eLand seq[NUMSEQ] = {
+ laHell, laCocytus, laGraveyard,
+ laWineyard, laFjord, laHive, laDeadCaves, laPower,
+ laRlyeh, laTemple, laDryForest, laIce, laCaves,
+ laJungle, laDesert, laAlchemist, laMotion, laMirror, laCrossroads
+ };
+ bool b = true;
+ for(int i=0; i=0) {
+ msginfo m;
+ m.spamtype = 0;
+ m.flashout = true;
+ m.stamp = ticks-128*vid.flashtime-128*(gls-i);
+ m.msg = gamelog[i];
+ mnum++,
+ msgs.push_back(m);
+ }
+
+ if(mnum)
+ displayfr(vid.xres/2, vid.yres-vid.fsize*(mnum+1), 2, vid.fsize/2, XLAT("last messages:"), 0xC0C0C0, 8);
+
+ #ifndef MOBILE
+ quitOrAgain();
+ #endif
+ }
+
+void displayabutton(int px, int py, string s, int col) {
+ // TMP
+ int siz = vid.yres > vid.xres ? vid.fsize*2 : vid.fsize * 3/2;
+ int x = vid.xcenter + px * (vid.radius);
+ int y = vid.ycenter + py * (vid.radius - siz/2);
+ if(gtouched && !mouseover
+ && abs(mousex - vid.xcenter) < vid.radius
+ && abs(mousey - vid.ycenter) < vid.radius
+ && hypot(mousex-vid.xcenter, mousey-vid.ycenter) > vid.radius
+ && px == (mousex > vid.xcenter ? 1 : -1)
+ && py == (mousey > vid.ycenter ? 1 : -1)
+ ) col = 0xFF0000;
+ displayfr(x, y, 0, siz, s, col, 8+8*px);
+ }
+
+vector scores;
+
+int scoresort = 2;
+int scoredisplay = 1;
+int scorefrom = 0;
+bool scorerev = false;
+
+bool scorecompare(const score& s1, const score &s2) {
+ return s1.box[scoresort] > s2.box[scoresort];
+ }
+
+bool fakescore() {
+ return fakebox[scoredisplay];
+ }
+
+string displayfor(score* S) {
+ // printf("S=%p, scoredisplay = %d\n", S, scoredisplay);
+ if(S == NULL) {
+ return XLATN(boxname[scoredisplay]);
+ }
+ if(scoredisplay == 0) {
+ char buf[10];
+ snprintf(buf, 10, "%d:%02d", S->box[0]/60, S->box[0]%60);
+ return buf;
+ }
+ if(scoredisplay == 1) {
+ time_t tim = S->box[1];
+ char buf[128]; strftime(buf, 128, "%c", localtime(&tim));
+ return buf;
+ }
+ return its(S->box[scoredisplay]);
+ }
+
+#ifndef ANDROID
+void loadScores() {
+ scores.clear();
+ FILE *f = fopen(scorefile, "rt");
+ if(!f) {
+ printf("Could not open the score file '%s'!\n", scorefile);
+ addMessage(s0 + "Could not open the score file: " + scorefile);
+ return;
+ }
+ while(!feof(f)) {
+ char buf[120];
+ if(fgets(buf, 120, f) == NULL) break;
+ if(buf[0] == 'H' && buf[1] == 'y') {
+ score sc; bool ok = true;
+ if(fscanf(f, "%s", buf) <= 0) break; sc.ver = buf;
+
+ for(int i=0; i= "4.4") {
+ sc.box[0] = sc.box[65];
+ // the first executable on Steam included a corruption
+ if(sc.box[65] > 1420000000 && sc.box[65] < 1430000000) {
+ sc.box[0] = sc.box[65] - sc.box[1];
+ sc.box[65] = sc.box[0];
+ }
+ // do not include saves
+ if(sc.box[65 + 4 + itOrbSafety - itOrbLightning]) ok = false;
+ }
+ else
+ sc.box[0] = sc.box[1] - sc.box[0]; // could not save then
+ if(ok && boxid > 20) scores.push_back(sc);
+ }
+ }
+ fclose(f);
+ addMessage(its(size(scores))+" games have been recorded in "+scorefile);
+ cmode = emScores;
+ scoresort = 2; reverse(scores.begin(), scores.end());
+ scorefrom = 0;
+ stable_sort(scores.begin(), scores.end(), scorecompare);
+ }
+
+bool notgl = false;
+
+void showPickScores() {
+
+ int d = scoredisplay;
+
+ vector > v;
+ for(int i=0; i= size(scores)) break;
+ score& S(scores[id]);
+ char buf[16];
+
+ sprintf(buf, "%d", id+1);
+ displaystr(bx*4, y, 0, vid.fsize, buf, 0xC0C0C0, 16);
+
+ sprintf(buf, "%d", S.box[2]);
+ displaystr(bx*8, y, 0, vid.fsize, buf, 0xC0C0C0, 16);
+
+ sprintf(buf, "%d", S.box[3]);
+ displaystr(bx*12, y, 0, vid.fsize, buf, 0xC0C0C0, 16);
+
+ sprintf(buf, "%d:%02d", S.box[0]/60, S.box[0] % 60);
+ displaystr(bx*18, y, 0, vid.fsize, buf, 0xC0C0C0, 16);
+
+ displaystr(bx*22, y, 0, vid.fsize, S.ver, 0xC0C0C0, 16);
+
+ displaystr(bx*23, y, 0, vid.fsize, displayfor(&S), 0xC0C0C0, 0);
+
+ y += vid.fsize*5/4; id++;
+ }
+
+#ifdef IOS
+ displayabutton(-1, +1, XLAT("SORT"), BTON);
+ displayabutton(+1, +1, XLAT("PLAY"), BTON);
+#endif
+ }
+
+void sortScores() {
+ if(scorerev) reverse(scores.begin(), scores.end());
+ else {
+ scorerev = true;
+ scoresort = scoredisplay;
+ stable_sort(scores.begin(), scores.end(), scorecompare);
+ }
+ }
+
+void shiftScoreDisplay(int delta) {
+ scoredisplay = (scoredisplay + POSSCORE + delta) % POSSCORE, scorerev = false;
+ if(fakescore()) shiftScoreDisplay(delta);
+ }
+#endif
+
+#ifndef MOBILE
+void handleScoreKeys(int sym, SDL_Event& ev) {
+ if(sym == SDLK_LEFT || sym == SDLK_KP4 || sym == 'h' || sym == 'a')
+ shiftScoreDisplay(-1);
+ else if(sym == SDLK_RIGHT || sym == SDLK_KP6 || sym == 'l' || sym == 'd')
+ shiftScoreDisplay(1);
+ else if(sym == 't') cmode = emPickScores;
+ else if(sym == SDLK_UP || sym == 'k' || sym == 'w')
+ scorefrom -= 5;
+ else if(sym == SDLK_DOWN || sym == 'j' || sym == 'x')
+ scorefrom += 5;
+ else if(sym == 's') sortScores();
+ else if(sym != 0 || ev.type == SDL_MOUSEBUTTONDOWN) cmode = emNormal;
+ }
+
+void handlePickScoreKeys(int uni, SDL_Event& ev) {
+ if(uni != 0) {
+ int k = uni - '!';
+ if(k >= 0 && k < POSSCORE) scoredisplay = k;
+ cmode = emScores;
+ scorerev = false;
+ }
+ }
+
+#endif
+
+string ONOFF(bool b) {
+ return XLAT(b ? "ON" : "OFF");
+ }
+
+void drawStats() {
+
+ DEB("stats");
+
+#ifdef IOS
+ if(cmode != emNormal && cmode != emQuit)
+ return;
+#endif
+
+ int vx, vy;
+
+ if(vid.xres < vid.yres) {
+ vx = vid.fsize * 3;
+ vy = vid.fsize * 2;
+ }
+ else {
+ vx = vid.xres - vid.fsize * 3;
+ vy = vid.fsize;
+ }
+
+ #define ADV(z) \
+ if(vid.xres < vid.yres) { \
+ vx += vid.fsize*4; \
+ if(vx > vid.xres - vid.fsize*2) vx = vid.fsize * 3, vy += vid.fsize; \
+ } \
+ else { \
+ vy += vid.fsize * z/2; \
+ if(vy > vid.yres) vx += (vx > vid.xres/2 ? -5:5) * vid.fsize, vy = vid.fsize * 5/2; \
+ }
+
+ DEB("$$$");
+ if(displaynum(vx, vy, 0, vid.fsize, 0xFFFFFF, gold(), "$$$")) {
+ mouseovers = XLAT("Your total wealth"),
+ help = XLAT(
+MC "The total value of the treasure you have collected.\n\n"
+MC "Every world type contains a specific type of treasure, worth 1 $$$; "
+MC "your goal is to collect as much treasure as possible, but every treasure you find "
+MC "causes more enemies to hunt you in its native land.\n\n"
+M "Orbs of Yendor are worth 50 $$$ each.\n\n"
+ );
+ }
+
+ DEB("$$$Z");
+ ADV(3);
+
+ int oldz = 0;
+
+ for(int z=0; z<3; z++) for(int i=0; i= 3) {
+ int kvx, kvy;
+ if(vid.xres >= vid.yres)
+ kvx = vid.fsize * 8, kvy = vid.fsize;
+ else { ADV(2); kvx=vx, kvy=vy; }
+ if(displaynum(kvx, kvy, 0, vid.fsize, 0xFFFFFF, tkills(), "XX"))
+ mouseovers = XLAT("Your total kills"),
+ help = XLAT(
+ "In most lands, more treasures are generated with each enemy native to this land you kill. "
+ "Moreover, 100 kills is a requirement to enter the Graveyard and the Hive.\n\n"
+ "Friendly creatures and parts of monsters (such as the Ivy) do appear in the list, "
+ "but are not counted in the total kill count.");
+ }
+
+ ADV(3);
+
+ int s = vid.fsize;
+ if(vid.xres >= vid.yres)
+ vid.fsize = vid.fsize - vid.killreduction;
+
+ for(int i=1; i vid.fsize * 3 && vid.xres >= vid.yres && vid.xres < vid.yres * 5/3)
+ vid.killreduction++;
+
+ DEB("stats OK");
+ achievement_display();
+ }
+
+void drawscreen() {
+
+#ifdef GL
+ if(vid.usingGL) setGLProjection();
+#endif
+
+ calcparam();
+ if(cmode != emHelp) help = "@";
+
+ #ifndef MOBILE
+ // SDL_LockSurface(s);
+ // unsigned char *b = (unsigned char*) s->pixels;
+ // int n = vid.xres * vid.yres * 4;
+ // while(n) *b >>= 1, b++, n--;
+ // memset(s->pixels, 0, vid.xres * vid.yres * 4);
+ if(!vid.usingGL) SDL_FillRect(s, NULL, 0);
+ #endif
+
+ if(!canmove) darken = 1;
+ if(cmode != emNormal && cmode != emDraw && cmode != emCustomizeChar) darken = 2;
+ if(cmode == emQuit && !canmove) darken = 0;
+
+ if(!vid.goteyes && !euclid)
+ drawCircle(vid.xcenter, vid.ycenter, vid.radius, 0xFF);
+
+ if(vid.wallmode < 2 && !euclid && !(vid.boardmode != 'h' && cwt.c->land == laGameBoard)) {
+ int ls = size(lines);
+ if(ISMOBILE) ls /= 10;
+ for(int t=0; t> darken);
+ }
+
+ drawqueue();
+ ptds.clear();
+
+ DEB("dmap");
+
+ drawthemap();
+
+ DEB("mstar");
+ #ifndef MOBILE
+ if(cmode == emNormal) drawmovestar();
+ #endif
+
+ if(cmode == emDraw) {
+ lalpha = 0x20;
+ for(int d=0; d<84; d++)
+ drawline(C0, spin(M_PI*d/42)* xpush(crossf) * C0, 0xC0C0C0);
+ for(int d=0; d<84; d++) for(int u=2; u<=20; u++)
+ drawline(spin(M_PI*d/42)* xpush(crossf*u/20) * C0, spin(M_PI*(d+1)/42)* xpush(crossf*u/20) * C0, 0xC0C0C0);
+
+ lalpha = 0x80;
+ if(crad > 0) {
+ transmatrix movtocc = rgpushxto0(ccenter);
+ for(int d=0; d<84; d++)
+ drawline(movtocc * ddi(d+1, crad) * C0, movtocc * ddi(d, crad) * C0, 0x00C000);
+ }
+ // spin(M_PI*(d+1)/42) * xpush(crad) * spin(-M_PI*(d+1)/42) * ccenter, spin(M_PI*d/42) * xpush(crad) * spin(-M_PI*d/42) * ccenter, 0xC0C0C0);
+ lalpha = 0xFF;
+ }
+
+ getcstat = 0;
+
+ if(cmode == emNormal || cmode == emQuit) drawStats();
+
+ #ifdef MOBILE
+
+ if(cmode == emNormal) {
+ displayabutton(-1, -1, XLAT("MOVE"), andmode == 0 ? BTON : BTOFF);
+ displayabutton(+1, -1, XLAT(andmode == 1 ? "BACK" : "DRAG"), andmode == 1 ? BTON : BTOFF);
+ displayabutton(-1, +1, XLAT("INFO"), andmode == 2 ? BTON : BTOFF);
+ displayabutton(+1, +1, XLAT(ISIOS ? "MENU" : andmode == 3 ? "QUEST" : "HELP"),
+ andmode == 3 ? (ISIOS ? BTON : 0xFF00FF) : BTOFF);
+ }
+
+ if(cmode == emQuit) {
+ displayabutton(-1, +1, XLAT("NEW"), BTON);
+ displayabutton(+1, +1, XLAT(canmove ? "PLAY" : ISIOS ? " " : "SHARE"), BTON);
+ }
+ #endif
+
+ if(cmode == emDraw) {
+ mouseovers =
+ floordraw ? (cwt.c->type == 6 ? "hex floor" : "hepta floor") : "character";
+
+ mouseovers =
+ XLAT("Drawing %1 (layer %2), F1 for help", XLAT(mouseovers), its(dslayer));
+ }
+
+ // displaynum(vx,100, 0, 24, 0xc0c0c0, celldist(cwt.c), ":");
+
+ darken = 0;
+ drawmessages();
+
+ DEB("msgs1");
+ if(cmode == emNormal) {
+ #ifdef MOBILE
+ if(!canmove) cmode = emQuit;
+ #endif
+ if(!canmove) showGameover();
+ #ifndef MOBILE
+// if(!canmove)
+// displayButton(vid.xres-8, vid.yres-vid.fsize*2, XLAT("ESC for menu/quest"), SDLK_ESCAPE, 16);
+ #endif
+ }
+
+ #ifndef ANDROID
+ if(cmode == emScores)
+ showScores();
+
+ if(cmode == emPickScores)
+ showPickScores();
+ #endif
+
+ #ifndef MOBILE
+ if(cmode == emChangeMode) {
+ displayStat(2, XLAT("vector graphics editor"), "", 'g');
+ displayStat(3, XLAT("Euclidean mode"), "", 'e');
+ displayStat(4, XLAT("cheat mode"), "", 'c');
+ displayStat(6, XLAT("heptagonal game board"), "", '7');
+ displayStat(7, XLAT("triangular game board"), "", '3');
+ displayStat(8, XLAT("HyperRogue game board"), "", 'h');
+ displayStat(9, XLAT("Periodic Editor"), "", 'f');
+
+ displayStat(18, XLAT("first page [Space]"), "", ' ');
+ displayStat(19, XLAT("exit configuration"), "", 'v');
+ }
+
+ if(cmode == emCustomizeChar) {
+ displayStatHelp(0, XLAT("Customize character"));
+
+ displayStat(2, XLAT("gender"), XLAT(vid.female ? "female" : "male"), 'g');
+ displayStat(3, XLAT("skin color"), "?", 's');
+ displayStat(4, XLAT("weapon color"), "?", 'w');
+ displayStat(5, XLAT("hair color"), "?", 'h');
+ if(vid.female)
+ displayStat(6, XLAT("dress color"), "?", 'd');
+
+ displayStatHelp(16, XLAT("Shift=random, Ctrl=mix"));
+
+ displayStat(19, XLAT("exit configuration"), "", 'v');
+ }
+
+ if(cmode == emPickEuclidean) {
+ int s = vid.fsize;
+ vid.fsize = vid.fsize * 4/5;
+ displayStatHelp(0, XLAT("Euclidean mode"));
+ int lt = landtypes;
+ landvisited[laCrossroads] = true;
+ landvisited[laIce] = true;
+ landvisited[laMirror] = true;
+ // for(int i=2; i= 500) siz = (siz * 9)/10;
+ else if(ISIOS) siz = (siz * 3+1)/2;
+ else if(size(help) >= 500) siz = siz * 2/3;
+
+ int vy = vid.fsize * 4;
+
+ int xs = vid.xres * 618/1000;
+ int xo = vid.xres * 186/1000;
+
+ for(int i=0; i<=size(help); i++) {
+ int ls = 0;
+ int prev = last;
+ if(help[i] == ' ') lastspace = i;
+ if(textwidth(siz, help.substr(last, i-last)) > xs) {
+ if(lastspace == last) ls = i-1, last = i-1;
+ else ls = lastspace, last = ls+1;
+ }
+ if(help[i] == 10 || i == size(help)) ls = i, last = i+1;
+ if(ls) {
+ displayfr(xo, vy, 2, siz, help.substr(prev, ls-prev), 0xC0C0C0, 0);
+ if(ls == prev) vy += siz/2;
+ else vy += siz;
+ lastspace = last;
+ }
+ }
+ }
+#endif
+
+#ifndef MOBILE
+ if(mouseover && targetclick) {
+ eItem i = targetRangedOrb(mouseover, roCheck);
+ if(i)
+ displaychr(mousex, mousey, 0, vid.fsize, '@', iinf[i].color);
+ }
+#endif
+
+ #ifndef MOBILE
+ DEB("msgs3");
+ // SDL_UnlockSurface(s);
+
+#ifdef GL
+ if(vid.usingGL) SDL_GL_SwapBuffers(); else
+#endif
+ SDL_UpdateRect(s, 0, 0, vid.xres, vid.yres);
+
+ #endif
+
+ if(playermoved && vid.aspeed > 4.99) {
+ centerpc(1000);
+ playermoved = false;
+ return;
+ }
+
+ }
+
+#ifndef MOBILE
+bool setfsize = false;
+
+void setvideomode() {
+
+ if(!vid.full) {
+ if(vid.xres > vid.xscr) vid.xres = vid.xscr * 9/10, setfsize = true;
+ if(vid.yres > vid.yscr) vid.yres = vid.yscr * 9/10, setfsize = true;
+ }
+
+ if(setfsize) vid.fsize = min(vid.yres / 32, vid.xres / 48), setfsize = false;
+
+ int flags = 0;
+
+#ifdef GL
+ if(vid.usingGL) {
+ flags = SDL_OPENGL | SDL_HWSURFACE | SDL_GL_DOUBLEBUFFER;
+ SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 1);
+ }
+#endif
+
+ int sizeflag = (vid.full ? SDL_FULLSCREEN : SDL_RESIZABLE);
+
+ s= SDL_SetVideoMode(vid.xres, vid.yres, 32, flags | sizeflag);
+
+ if(vid.full && !s) {
+ vid.xres = vid.xscr;
+ vid.yres = vid.yscr;
+ vid.fsize = min(vid.yres / 32, vid.xres / 48);
+ s = SDL_SetVideoMode(vid.xres, vid.yres, 32, flags | SDL_FULLSCREEN);
+ }
+
+ if(!s) {
+ addMessage("Failed to set the graphical mode: "+its(vid.xres)+"x"+its(vid.yres)+(vid.full ? " fullscreen" : " windowed"));
+ vid.xres = 640;
+ vid.yres = 480;
+ s = SDL_SetVideoMode(vid.xres, vid.yres, 32, flags | SDL_RESIZABLE);
+ }
+
+#ifdef GL
+ glViewport(0, 0, vid.xres, vid.yres);
+ resetGL();
+#endif
+ }
+#endif
+
+void restartGraph() {
+ if(euclid) {
+ centerover = euclideanAtCreate(0,0);
+ printf("centerover = %p\n", centerover);
+ }
+ else {
+ viewctr.h = &origin;
+ viewctr.spin = 0;
+ }
+ View = Id;
+ webdisplay = 0;
+ }
+
+#ifndef ANDROID
+void saveConfig() {
+ FILE *f = fopen(conffile, "wt");
+ if(!f) {
+ addMessage(s0 + "Could not open the config file: " + conffile);
+ return;
+ }
+ fprintf(f, "%d %d %d %d\n", vid.xres, vid.yres, vid.full, vid.fsize);
+ fprintf(f, "%f %f %f %f\n", float(vid.scale), float(vid.eye), float(vid.alpha), float(vid.aspeed));
+ fprintf(f, "%d %d %d %d %d %d %d\n", vid.wallmode, vid.monmode, vid.axes, audiovolume, vid.framelimit, vid.usingGL, vid.usingAA);
+ fprintf(f, "%d %d %d %f %d %d\n", vid.joyvalue, vid.joyvalue2, vid.joypanthreshold, vid.joypanspeed, autojoy, vid.flashtime);
+ fprintf(f, "%d %d %08x %08x %08x %08x\n",
+ vid.female?1:0, vid.language, vid.skincolor, vid.haircolor, vid.swordcolor, vid.dresscolor);
+
+ fprintf(f, "%d %d\n", vid.darkhepta, vid.shifttarget);
+
+ fprintf(f, "\n\nThis is a configuration file for HyperRogue (version "VER")\n");
+ fprintf(f, "\n\nThe numbers are:\n");
+ fprintf(f, "screen width & height, fullscreen mode (0=windowed, 1=fullscreen), font size\n");
+ fprintf(f, "scale, eye distance, parameter, animation speed\n");
+ fprintf(f, "wallmode, monster mode, cross mode, audiovolume, framerate limit, usingGL, usingAA\n");
+ fprintf(f, "calibrate first joystick (threshold A, threshold B), calibrate second joystick (pan threshold, pan speed), joy mode\n");
+ fprintf(f, "gender (1=female), language, skin color, hair color, sword color, dress color\n");
+ fprintf(f, "darken hepta, shift target\n");
+
+ fclose(f);
+ addMessage(s0 + "Configuration saved to: " + conffile);
+ }
+
+void loadConfig() {
+ vid.xres = 9999; vid.yres = 9999; vid.framelimit = 300;
+ FILE *f = fopen(conffile, "rt");
+ if(f) {
+ int fs, gl=1, aa=1, bb=1;
+ int err;
+ err=fscanf(f, "%d%d%d%d", &vid.xres, &vid.yres, &fs, &vid.fsize);
+ vid.full = fs;
+ float a, b, c, d;
+ err=fscanf(f, "%f%f%f%f\n", &a, &b, &c, &d);
+ if(err == 4) {
+ vid.scale = a; vid.eye = b; vid.alpha = c; vid.aspeed = d;
+ }
+ err=fscanf(f, "%d%d%d%d%d%d%d", &vid.wallmode, &vid.monmode, &vid.axes, &audiovolume, &vid.framelimit, &gl, &aa);
+ vid.usingGL = gl; vid.usingAA = aa;
+ err=fscanf(f, "%d%d%d%f%d%d", &vid.joyvalue, &vid.joyvalue2, &vid.joypanthreshold, &vid.joypanspeed, &aa, &vid.flashtime);
+ autojoy = aa; aa = 0;
+ err=fscanf(f, "%d%d%x%x%x%x", &aa, &vid.language, &vid.skincolor, &vid.haircolor, &vid.swordcolor, &vid.dresscolor);
+ vid.female = aa;
+ aa=0; bb=0;
+ err=fscanf(f, "%d%d", &aa, &bb);
+ vid.darkhepta = aa; vid.shifttarget = bb;
+
+ fclose(f);
+ printf("Loaded configuration: %s\n", conffile);
+ }
+
+#ifndef MOBILE
+ if(clWidth) vid.xres = clWidth;
+ if(clHeight) vid.yres = clHeight;
+ if(clFont) vid.fsize = clFont;
+
+ for(int k=0; k 0) {
+ for(int i=0; buf[i]; i++) if(buf[i] == 10 || buf[i] == 13) buf[i] = 0;
+ if(buf[0] == '[' && buf[3] == ']') {
+ int id = (buf[1] - '0') * 10 + buf[2] - '0';
+ if(id >= 0 && id < landtypes) {
+ if(buf[5] == '*' && buf[6] == '/') musfname[id] = dir2 + (buf+7);
+ else musfname[id] = buf+5;
+ }
+ else {
+ fprintf(stderr, "warning: bad soundtrack id, use the following format:\n");
+ fprintf(stderr, "[##] */filename\n");
+ fprintf(stderr, "where ## are two digits, and */ is optional and replaced by path to the music\n");
+ fprintf(stderr, "alternatively LAST = reuse the last track instead of having a special one");
+ }
+ // printf("[%2d] %s\n", id, buf);
+ }
+ else if(buf[0] == '#') {
+ }
+ else {
+ musiclicense += buf;
+ musiclicense += "\n";
+ }
+ }
+ fclose(f);
+ return true;
+ }
+ return false;
+ }
+#endif
+
+void initgraph() {
+
+ vid.usingGL = true;
+ vid.usingAA = true;
+ vid.flashtime = 8;
+ vid.scale = 1;
+ vid.alpha = 1;
+ vid.aspeed = 0;
+ vid.eye = 0;
+ vid.full = false;
+ vid.quick = true;
+#ifdef ANDROID
+ vid.wallmode = 2;
+#else
+ vid.wallmode = 3;
+#endif
+
+ vid.joyvalue = 4800;
+ vid.joyvalue2 = 5600;
+ vid.joypanthreshold = 2500;
+ vid.joypanspeed = 0.0001;
+
+ vid.framelimit = 75;
+ vid.monmode = 2;
+ vid.axes = 1;
+
+ vid.skincolor = 0xD0D0D0FF;
+ vid.haircolor = 0x686868FF;
+ vid.dresscolor= 0xC00000FF;
+ vid.swordcolor= 0xD0D0D0FF;
+
+ vid.killreduction = 0;
+
+ vid.female = false;
+ vid.language = -1;
+ vid.boardmode = 'h';
+
+ joyx = joyy = 0; joydir = -1;
+
+ restartGraph();
+
+ initgeo();
+
+ buildpolys();
+ for(int i=0; i<8; i++)
+ dsUser[i][0].rots = 6,
+ dsUser[i][1].rots = 7,
+ dsUser[i][2].rots = 1, dsUser[i][2].sym = true;
+
+ for(int i=0; i<8; i++) for(int k=0; k<3; k++)
+ dsUser[i][k].shift = C0, dsUser[i][k].spin = Cx1;
+
+ #ifndef MOBILE
+ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) == -1)
+ {
+ printf("Failed to initialize video.\n");
+ exit(2);
+ }
+ const SDL_VideoInfo *inf = SDL_GetVideoInfo();
+ vid.xscr = vid.xres = inf->current_w;
+ vid.yscr = vid.yres = inf->current_h;
+
+ SDL_WM_SetCaption("HyperRogue " VER, "HyperRogue "VER);
+
+ loadConfig();
+
+ setvideomode();
+ if(!s) {
+ printf("Failed to initialize graphics.\n");
+ exit(2);
+ }
+
+ SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
+ SDL_EnableUNICODE(1);
+
+ if(TTF_Init() != 0) {
+ printf("Failed to initialize TTF.\n");
+ exit(2);
+ }
+
+ if(SDL_NumJoysticks()) stick = SDL_JoystickOpen(0);
+ if(SDL_NumJoysticks() >= 2) panstick = SDL_JoystickOpen(1);
+
+ #ifdef AUDIO
+
+ audio =
+ loadMusicInfo(musicfile)
+ || loadMusicInfo("./hyperrogue-music.txt")
+ || loadMusicInfo("music/hyperrogue-music.txt")
+#ifdef FHS
+ || loadMusicInfo("/usr/share/hyperrogue/hyperrogue-music.txt")
+ || loadMusicInfo(s0 + getenv("HOME") + "/.hyperrogue-music.txt")
+#endif
+ ;
+
+ if(audio) {
+ if(Mix_OpenAudio(MIX_DEFAULT_FREQUENCY, MIX_DEFAULT_FORMAT, 2, 4096) != 0) {
+ fprintf(stderr, "Unable to initialize audio: %s\n", Mix_GetError());
+ audio = false;
+ }
+ else {
+ audio = true;
+ Mix_AllocateChannels(4);
+ }
+ }
+ #endif
+
+ #endif
+ }
+
+int frames;
+bool outoffocus = false;
+
+#ifdef AUDIO
+
+bool loaded[landtypes];
+Mix_Music* music[landtypes];
+int musicpos[landtypes];
+int musstart;
+int musfadeval = 2000;
+
+eLand cid = laNone;
+
+void handlemusic() {
+ if(audio && audiovolume) {
+ eLand id = cwt.c->land;
+ if(outoffocus) id = eLand(0);
+ if(musfname[id] == "LAST") id = cid;
+ if(!loaded[id]) {
+ loaded[id] = true;
+ // printf("loading (%d)> %s\n", id, musfname[id].c_str());
+ if(musfname[id] != "") {
+ music[id] = Mix_LoadMUS(musfname[id].c_str());
+ if(!music[id]) {
+ printf("Mix_LoadMUS: %s\n", Mix_GetError());
+ }
+ }
+ }
+ if(cid != id && !musfadeval) {
+ musicpos[cid] = SDL_GetTicks() - musstart;
+ musfadeval = musicpos[id] ? 500 : 2000;
+ Mix_FadeOutMusic(musfadeval);
+ // printf("fadeout %d, pos %d\n", musfadeval, musicpos[cid]);
+ }
+ if(music[id] && !Mix_PlayingMusic()) {
+ cid = id;
+ Mix_VolumeMusic(audiovolume);
+ Mix_FadeInMusicPos(music[id], -1, musfadeval, musicpos[id] / 1000.0);
+ // printf("fadein %d, pos %d\n", musfadeval, musicpos[cid]);
+ musstart = SDL_GetTicks() - musicpos[id];
+ musicpos[id] = 0;
+ musfadeval = 0;
+ }
+ }
+ }
+
+
+void resetmusic() {
+ if(audio && audiovolume) {
+ Mix_FadeOutMusic(3000);
+ cid = laNone;
+ for(int i=0; i> 8, cs[rand() % q] >> 8, 0, rand() % 101, 100) << 8) + 0xFF;
+ else
+ c = cs[(id+1) % q];
+ }
+
+const char* drawhelp =
+ "In this mode you can draw your own player character and floors. "
+ "Mostly for the development purposes, but you can have fun too.\n\n"
+ "f - floor, p - player (repeat 'p' for layers)\n\n"
+ "n - new shape, u - copy the 'player body' shape\n\n"
+ "1-9 - rotational symmetries, 0 - toggle axial symmetry\n\n"
+ "point with mouse and: a - add point, m - move nearest point, d - delete nearest point, c - nearest point again, b - add after nearest\n\n"
+ "s - save in C++ format (but cannot be loaded yet without editing source)\n\n"
+ "z - zoom, o - Poincaré model\n";
+
+#ifndef MOBILE
+
+// Warning: a very long function! todo: refactor
+
+void loadShape(hpcshape& sh, int d, int layer) {
+ dsCur = &(dsUser[layer][2]);
+ dsCur->list.clear();
+ dsCur->sym = d==2;
+ for(int i=sh.s; i < sh.s + (sh.e-sh.s)/d; i++)
+ dsCur->list.push_back(hpc[i]);
+ }
+
+void mainloop() {
+ int lastt = 0;
+ while(true) {
+
+ #ifndef GFX
+ #ifndef GL
+ vid.wallmode = 0;
+ vid.monmode = 0;
+ #endif
+ #endif
+
+ DEB("screen");
+ optimizeview();
+ ticks = SDL_GetTicks();
+
+ dslayer %= 8;
+ dsCur = &(dsUser[dslayer][floordraw ? cwt.c->type-6 : 2]);
+
+ int cframelimit = vid.framelimit;
+ if((cmode == emVisual1 || cmode == emVisual2 || cmode == emHelp || cmode == emQuit) && cframelimit > 15)
+ cframelimit = 15;
+ if(outoffocus && cframelimit > 10) cframelimit = 10;
+
+ int timetowait = lastt + 1000 / cframelimit - ticks;
+ if(timetowait > 0)
+ SDL_Delay(timetowait);
+ else {
+ if(playermoved && vid.aspeed > -4.99 && !outoffocus)
+ centerpc((ticks - lastt) / 1000.0 * exp(vid.aspeed));
+ if(panstick || panjoyx || panjoyy)
+ checkpanjoy((ticks - lastt) / 1000.0);
+ lastt = ticks;
+ frames++;
+ if(!outoffocus) drawscreen();
+ }
+
+ Uint8 *keystate = SDL_GetKeyState(NULL);
+ rightclick = keystate[SDLK_RCTRL];
+ leftclick = keystate[SDLK_RSHIFT];
+ hiliteclick = keystate[SDLK_LALT] | keystate[SDLK_RALT];
+
+ if(vid.shifttarget) {
+ leftclick = false;
+ targetclick = keystate[SDLK_RSHIFT] | keystate[SDLK_LSHIFT];
+ }
+ else {
+ leftclick = keystate[SDLK_RSHIFT];
+ targetclick = true;
+ }
+
+#ifdef AUDIO
+ if(audio) handlemusic();
+#endif
+ SDL_Event ev;
+ DEB("react");
+
+ while(SDL_PollEvent(&ev)) {
+ int sym = 0;
+ int uni = 0;
+ ld shift = 1;
+
+ if(ev.type == SDL_ACTIVEEVENT) {
+ if(ev.active.state & SDL_APPINPUTFOCUS) {
+ if(ev.active.gain) {
+ outoffocus = false;
+ }
+ else {
+ outoffocus = true;
+ }
+ }
+ }
+
+ if(ev.type == SDL_VIDEORESIZE) {
+ vid.xres = ev.resize.w;
+ vid.yres = ev.resize.h;
+ vid.killreduction = 0;
+ setfsize = true;
+ setvideomode();
+#ifdef GL
+ glViewport(0, 0, vid.xres, vid.yres);
+#endif
+ }
+
+ if(ev.type == SDL_VIDEOEXPOSE) {
+ drawscreen();
+ }
+
+ if(ev.type == SDL_JOYAXISMOTION) {
+ if(ev.jaxis.value != 0 &&
+ ev.jaxis.axis >= 2)
+ printf("which = %d axis = %d value = %d\n",
+ ev.jaxis.which,
+ ev.jaxis.axis,
+ ev.jaxis.value);
+ if(ev.jaxis.which == 0) {
+ if(ev.jaxis.axis == 0)
+ joyx = ev.jaxis.value;
+ else if(ev.jaxis.axis == 1)
+ joyy = ev.jaxis.value;
+ else if(ev.jaxis.axis == 3)
+ panjoyx = ev.jaxis.value;
+ else if(ev.jaxis.axis == 4)
+ panjoyy = ev.jaxis.value;
+ checkjoy();
+ printf("panjoy = %d,%d\n", panjoyx, panjoyy);
+ }
+ else {
+ if(ev.jaxis.axis == 0)
+ panjoyx = ev.jaxis.value;
+ else
+ panjoyy = ev.jaxis.value;
+ }
+ }
+
+ if(ev.type == SDL_JOYBUTTONDOWN) {
+ movepcto(joydir);
+ checkjoy();
+ }
+
+ if(ev.type == SDL_KEYDOWN) {
+ flashMessages();
+ mousing = false;
+ sym = ev.key.keysym.sym;
+ uni = ev.key.keysym.unicode;
+ if(ev.key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) shift = -1;
+ if(ev.key.keysym.mod & (KMOD_LCTRL | KMOD_RCTRL)) shift /= 10;
+ }
+
+ if(ev.type == SDL_MOUSEBUTTONDOWN) {
+ flashMessages();
+ mousepressed = true;
+ mousing = true;
+ if(ev.button.button==SDL_BUTTON_RIGHT || leftclick)
+ sym = SDLK_F1;
+ else if(ev.button.button==SDL_BUTTON_MIDDLE || rightclick)
+ sym = 1;
+ else if(ev.button.button==SDL_BUTTON_WHEELUP) {
+ ld jx = (mousex - vid.xcenter - .0) / vid.radius / 10;
+ ld jy = (mousey - vid.ycenter - .0) / vid.radius / 10;
+ playermoved = false;
+ View = gpushxto0(hpxy(jx, jy)) * View;
+ sym = 1;
+ }
+ else {
+ sym = getcstat, uni = getcstat, shift = getcshift;
+ }
+ }
+
+ if(ev.type == SDL_MOUSEBUTTONUP)
+ mousepressed = false;
+
+ if(cmode != emScores) {
+#ifndef PANDORA
+ if(sym == SDLK_RIGHT) View = xpush(-0.2*shift) * View, playermoved = false;
+ if(sym == SDLK_LEFT) View = xpush(+0.2*shift) * View, playermoved = false;
+ if(sym == SDLK_UP) View = ypush(+0.2*shift) * View, playermoved = false;
+ if(sym == SDLK_DOWN) View = ypush(-0.2*shift) * View, playermoved = false;
+#endif
+ if(sym == SDLK_PAGEUP) View = spin(M_PI/21*shift) * View;
+ if(sym == SDLK_PAGEDOWN) View = spin(-M_PI/21*shift) * View;
+ }
+
+ if(ev.type == SDL_MOUSEMOTION) {
+ hyperpoint mouseoh = mouseh;
+
+ mousing = true;
+ mousex = ev.motion.x;
+ mousey = ev.motion.y;
+ mouseh = gethyper(mousex, mousey);
+
+ if((rightclick || (SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_MMASK)) &&
+ !outofmap(mouseh) && !outofmap(mouseoh) &&
+ mouseh[2] < 50 && mouseoh[2] < 50) {
+ panning(mouseoh, mouseh);
+ }
+
+#ifdef SIMULATE_JOYSTICK
+ // pretend that both joysticks are present
+ stick = panstick = (SDL_Joystick*) (&vid);
+ panjoyx = 20 * (mousex - vid.xcenter);
+ panjoyy = 20 * (mousey - vid.ycenter);
+ checkjoy();
+#endif
+ }
+
+ DEB("r1");
+ if(sym == SDLK_F7) {
+
+ time_t timer;
+ timer = time(NULL);
+ char buf[128]; strftime(buf, 128, "shot-%y%m%d-%H%M%S.bmp", localtime(&timer));
+
+ SDL_SaveBMP(s, buf);
+ addMessage(XLAT("Screenshot saved to %1", buf));
+ }
+
+ DEB("r2");
+
+ if(cmode == emDraw) {
+
+ hyperpoint mh = inverse(playertrans) * mouseh;
+ // inverse(playertrans) *
+ // mouseh;
+
+ // spin(-M_PI/2) * mouseh;
+
+ if(uni == 'n') {
+ dsCur->list.clear();
+ dsCur->list.push_back(mh);
+#ifdef GL
+ GL_initialized = false;
+#endif
+ }
+
+ if(uni == 'a') {
+ dsCur->list.push_back(mh);
+#ifdef GL
+ GL_initialized = false;
+#endif
+ }
+
+ if(uni == 'u') {
+ if(!vid.female) loadShape(shPBody, 2, 0);
+ else loadShape(shFemaleBody, 2, 0);
+
+ loadShape(shPSword, 1, 1);
+
+ if(vid.female)
+ loadShape(shFemaleDress, 2, 2);
+
+ if(vid.female)
+ loadShape(shFemaleHair, 2, 3);
+ else
+ loadShape(shPHead, 2, 3);
+
+ loadShape(shPFace, 2, 4);
+
+#ifdef GL
+ GL_initialized = false;
+#endif
+ }
+
+ if(uni == 'm' || uni == 'd' || uni == 'c' || uni == 'b') {
+ int i = 0;
+ if(size(dsCur->list) < 2) continue;
+ for(int j=1; jlist); j++)
+ if(intval(mh, dsCur->list[j]) < intval(mh, dsCur->list[i]))
+ i = j;
+ if(uni == 'm')
+ dsCur->list[i] = mh;
+ if(uni == 'd')
+ dsCur->list.erase(dsCur->list.begin() + i);
+ if(uni == 'c')
+ dsCur->list.push_back(dsCur->list[i]);
+ if(uni == 'b') {
+ while(i) {
+ dsCur->list.push_back(dsCur->list[0]);
+ dsCur->list.erase(dsCur->list.begin());
+ i--;
+ }
+ }
+#ifdef GL
+ GL_initialized = false;
+#endif
+ }
+
+ if(uni == 'f') {
+ if(floordraw) dslayer++; else floordraw = true;
+ }
+ if(uni == 'p') {
+ if(!floordraw) dslayer++; else floordraw = false;
+ }
+ if(uni == 'g') ccenter = mouseh, crad += shift / 20;
+
+ if(uni == '1') dsCur->rots = 1;
+ if(uni == '2') dsCur->rots = 2;
+ if(uni == '3') dsCur->rots = 3;
+ if(uni == '4') dsCur->rots = 4;
+ if(uni == '5') dsCur->rots = 5;
+ if(uni == '6') dsCur->rots = 6;
+ if(uni == '7') dsCur->rots = 7;
+ if(uni == '8') dsCur->rots = 8;
+ if(uni == '9') dsCur->rots = 21;
+ if(uni == '0') dsCur->sym = !dsCur->sym;
+
+ if(uni == 't') dsCur->shift = mh;
+ if(uni == 'y') dsCur->spin = mh;
+
+#ifdef GL
+ if(uni == 't' || uni == 'y') GL_initialized = false;
+#endif
+
+
+ if(uni == 's') {
+ for(int i=prehpc; i < qhpc; i++) {
+ for(int k=0; k<8; k++)
+ for(int j=0; j<3; j++) if(i == shUser[k][j].s)
+ printf("\n // group %d layer %d\n\n", j, k);
+ printf(" hpcpush(hpxyz(%f,%f,%f));\n", double(hpc[i][0]), double(hpc[i][1]), double(hpc[i][2]));
+ }
+ }
+
+ if(uni == 'z') vid.alpha = -.5;
+ if(uni == 'o') vid.alpha = 1;
+
+ saveImages();
+
+ if(sym == SDLK_F6) {
+ cmode = emNormal;
+ sym = 0;
+ }
+
+ if(sym == SDLK_ESCAPE) cmode = emNormal;
+
+ if(sym == SDLK_F1) {
+ cmode = emHelp;
+ sym = 0;
+ help = XLAT(drawhelp);
+ }
+
+ if(sym == SDLK_F2) {
+ cmode = emVisual1;
+ sym = 0;
+ }
+
+ if(sym == SDLK_F10) cmode = emNormal;
+ }
+
+ if(cmode == emNormal) {
+
+ if(!(uni >= 'A' && uni <= 'Z') && cwt.c->land != laGameBoard) {
+ if(sym == 'l' || sym == 'd' || sym == SDLK_KP6) movepckeydir(0);
+ if(sym == 'n' || sym == 'c' || sym == SDLK_KP3) movepckeydir(1);
+ if(sym == 'j' || sym == 'x' || sym == SDLK_KP2) movepckeydir(2);
+ if(sym == 'b' || sym == 'z' || sym == SDLK_KP1) movepckeydir(3);
+ if(sym == 'h' || sym == 'a' || sym == SDLK_KP4) movepckeydir(4);
+ if(sym == 'y' || sym == 'q' || sym == SDLK_KP7) movepckeydir(5);
+ if(sym == 'k' || sym == 'w' || sym == SDLK_KP8) movepckeydir(6);
+ if(sym == 'u' || sym == 'e' || sym == SDLK_KP9) movepckeydir(7);
+ }
+
+#ifdef PANDORA
+ if(sym == SDLK_RIGHT) movepckeydir(0);
+ if(sym == SDLK_LEFT) movepckeydir(4);
+ if(sym == SDLK_DOWN) movepckeydir(2 + (leftclick?1:0) - (rightclick?1:0));
+ if(sym == SDLK_UP) movepckeydir(6 - (leftclick?1:0) + (rightclick?1:0));
+#endif
+
+ if(cheater)
+ applyCheat(uni, mouseover);
+
+ if(cwt.c->land == laGameBoard)
+ applyGameBoard(uni, sym, mouseover, vid.boardmode);
+ else {
+ if(sym == '.' || sym == 's') movepcto(-1);
+ if((sym == SDLK_DELETE || sym == SDLK_KP_PERIOD || sym == 'g') && uni != 'G' && uni != 'G'-64)
+ movepcto(-2);
+ if(sym == 't' && uni != 'T' && uni != 'T'-64) {
+ targetRangedOrb(centerover, roKeyboard);
+ }
+ }
+
+ if(sym == SDLK_KP5) movepcto(-1);
+
+ // if(sym == SDLK_F4) restartGameSwitchEuclid();
+
+ if(sym == SDLK_F5) restartGame();
+ if(sym == SDLK_ESCAPE) {
+ cmode = emQuit;
+ achievement_final(false);
+ if(!canmove) {
+ addMessage(XLAT("GAME OVER"));
+ addMessage(timeline());
+ }
+ msgscroll = 0;
+ }
+ if(sym == SDLK_F10) return;
+
+ if(!canmove) {
+ if(sym == SDLK_RETURN) return;
+ else if(uni == 'r') restartGame();
+ else if(uni == 't') {
+ restartGame();
+ loadScores();
+ }
+ }
+
+ if(sym == SDLK_HOME || sym == SDLK_F3 || sym == ' ') {
+ if(playerfound) centerpc(INF);
+ else {
+ View = Id;
+ // EUCLIDEAN
+ if(!euclid) viewctr.h = cwt.c->master;
+ else centerover = cwt.c;
+ // SDL_LockSurface(s);
+ drawthemap();
+ // SDL_UnlockSurface(s);
+ centerpc(INF);
+ }
+ playermoved = true;
+ }
+
+ if(sym == SDLK_F6) {
+ View = spin(M_PI/2) * inverse(cwtV) * View;
+ if(flipplayer) View = spin(M_PI) * View;
+ cmode = emDraw;
+ }
+
+ if(sym == 'v' || sym == SDLK_F2) {
+ cmode = emVisual1;
+ }
+
+#ifdef PANDORA
+ if(ev.type == SDL_MOUSEBUTTONUP && sym == 0 && !rightclick) {
+#else
+ if(ev.type == SDL_MOUSEBUTTONDOWN && sym == 0 && !rightclick) {
+#endif
+
+ bool forcetarget = (keystate[SDLK_RSHIFT] | keystate[SDLK_LSHIFT]);
+
+ if(mouseover && targetclick && targetRangedOrb(mouseover, forcetarget ? roMouseForce : roMouse))
+ ;
+ else if(forcetarget)
+ ;
+ else if(mousedest == -1)
+ movepcto(mousedest);
+ else
+ movepcto((mousedest + 42 - cwt.spin)%42);
+ }
+
+ if(sym == SDLK_F1) {
+ cmode = emHelp;
+ }
+ }
+
+ else if(cmode == emVisual1) {
+
+ char xuni = uni | 96;
+
+ if(uni >= 32 && uni < 64) xuni = uni;
+
+ if(uni == ' ') cmode = emVisual2;
+
+ if(xuni == 'p') vid.alpha += shift * 0.1;
+ if(xuni == 'z') vid.scale += shift * 0.1;
+ if(xuni == 'a') vid.aspeed += shift;
+ if(xuni == 'f') {
+ vid.full = !vid.full;
+ if(shift > 0) {
+ vid.xres = vid.full ? vid.xscr : 9999;
+ vid.yres = vid.full ? vid.yscr : 9999;
+ setfsize = true;
+ }
+ setvideomode();
+ }
+
+ if(xuni == 'v' || sym == SDLK_F2) cmode = emNormal;
+ if(xuni == 's') saveConfig();
+
+ if(xuni == '7') { vid.darkhepta = !vid.darkhepta; }
+ if(xuni == 'w') { vid.wallmode += 60 + (shift > 0 ? 1 : -1); vid.wallmode %= 4; }
+ if(xuni == 'm') { vid.monmode += 60 + (shift > 0 ? 1 : -1); vid.monmode %= 4; }
+ if(xuni == 'c') { vid.axes += 60 + (shift > 0 ? 1 : -1); vid.axes %= 4; }
+ if(xuni == 'b') {
+ audiovolume += int(10.5 * shift);
+ if(audiovolume < 0) audiovolume = 0;
+ if(audiovolume > MIX_MAX_VOLUME) audiovolume = MIX_MAX_VOLUME;
+ Mix_VolumeMusic(audiovolume);
+ }
+
+ if(sym == SDLK_ESCAPE) cmode = emNormal;
+
+#ifdef SHOWOFF
+ process_showoff(sym);
+#endif
+
+ if(sym == SDLK_F1 || sym == 'h') {
+ cmode = emHelp;
+ }
+
+ if(xuni == 'l') {
+ vid.language++;
+ vid.language %= NUMLAN;
+ printf("lan = %d\n", vid.language);
+ }
+
+ if(xuni == 'g') {
+ vid.female = !vid.female;
+ switchcolor(vid.skincolor, skincolors, 0);
+ switchcolor(vid.haircolor, haircolors, 0);
+ switchcolor(vid.dresscolor, dresscolors, 0);
+ switchcolor(vid.swordcolor, swordcolors, 0);
+ cmode = emCustomizeChar;
+ }
+
+ }
+
+ else if(cmode == emCustomizeChar) {
+ char xuni = uni | 96;
+ int mod = ev.key.keysym.mod;
+ if(shift < -.5)
+ mod = 1;
+ else if(shift > -.2 && shift < .2)
+ mod = 2;
+ else mod = 0;
+ if(xuni == 'g') vid.female = !vid.female;
+ if(xuni == 's') switchcolor(vid.skincolor, skincolors, mod);
+ if(xuni == 'h') switchcolor(vid.haircolor, haircolors, mod);
+ if(xuni == 'w') switchcolor(vid.swordcolor, swordcolors, mod);
+ if(xuni == 'd') switchcolor(vid.dresscolor, dresscolors, mod);
+ if(xuni == 'v' || sym == SDLK_F2 || sym == SDLK_ESCAPE) cmode = emNormal;
+ }
+
+ else if(cmode == emVisual2) {
+ char xuni = uni | 96;
+
+#ifdef GL
+ if(xuni == 'v' || sym == SDLK_F2 || sym == SDLK_ESCAPE) cmode = emNormal;
+
+ if(xuni == 'o' && shift > 0) {
+ vid.usingGL = !vid.usingGL;
+ if(vid.usingGL) addMessage(XLAT("openGL mode enabled"));
+ if(!vid.usingGL) addMessage(XLAT("openGL mode disabled"));
+ setvideomode();
+ }
+
+ if(xuni == 'o' && shift < 0 && !vid.usingGL) {
+ vid.usingAA = !vid.usingAA;
+ if(vid.usingAA) addMessage(XLAT("anti-aliasing enabled"));
+ if(!vid.usingAA) addMessage(XLAT("anti-aliasing disabled"));
+ }
+#endif
+
+ if(xuni == 'f') {
+ vid.framelimit += int(10.5 * shift);
+ if(vid.framelimit < 5) vid.framelimit = 5;
+ }
+
+ if(xuni == 'a') vid.joyvalue += int(shift * 100);
+ if(xuni == 'b') vid.joyvalue2 += int(shift * 100);
+ if(xuni == 'c') vid.joypanthreshold += int(shift * 100);
+ if(xuni == 'd') vid.joypanspeed += shift / 50000;
+ if(xuni == 'e') vid.eye += shift * 0.01;
+ if(xuni == 't') vid.flashtime += shift>0?1:-1;
+
+ if(xuni == 'p') autojoy = !autojoy;
+ if(xuni == 's') { vid.shifttarget = !vid.shifttarget; }
+
+ if(uni == ' ') cmode = emChangeMode;
+ }
+
+ else if(cmode == emChangeMode) {
+
+ char xuni = uni | 96;
+ if(xuni == 'v' || sym == SDLK_F2 || sym == SDLK_ESCAPE) cmode = emNormal;
+ if(uni == ' ') cmode = emVisual1;
+
+ if(sym == 'c') {
+ cheater++;
+ addMessage(XLAT("You activate your demonic powers!"));
+ addMessage(XLAT("Shift+F, Shift+O, Shift+T, Shift+L, Shift+U, etc."));
+ cmode = emNormal;
+ }
+
+ if(xuni == 'g') cmode = emDraw;
+ if(xuni == 'e') cmode = emPickEuclidean;
+
+ if(uni == 'h' || uni == '3' || uni == '7' || uni == 'f') {
+ vid.boardmode = uni;
+ cheater = 1;
+ showid = 0; firstland = laGameBoard;
+ for(int i=0; i= 'a' && xuni < 'a' + landtypes) {
+ euclidland = eLand(2 + (xuni - 'a'));
+ if(landvisited[euclidland]) {
+ if(euclid) restartGame();
+ else restartGameSwitchEuclid();
+ cmode = emNormal;
+ }
+ else euclidland = laIce;
+ }
+ }
+
+ else if(cmode == emHelp) {
+ if(sym == SDLK_F1 && help != "@")
+ help = "@";
+ else if(uni == 'c')
+ help = buildCredits();
+ else if((sym != 0 && sym != SDLK_F12) || ev.type == SDL_MOUSEBUTTONDOWN)
+ cmode = (help == XLAT(drawhelp) ? emDraw : emNormal);
+ }
+
+ else if(cmode == emQuit) {
+ if(sym == SDLK_RETURN || sym == SDLK_F10) return;
+ else if(uni == 'r' || sym == SDLK_F5) {
+ restartGame(), cmode = emNormal;
+ msgs.clear();
+ }
+ else if(sym == SDLK_UP || sym == SDLK_KP8) msgscroll++;
+ else if(sym == SDLK_DOWN || sym == SDLK_KP2) msgscroll--;
+ else if(sym == SDLK_PAGEUP || sym == SDLK_KP9) msgscroll+=5;
+ else if(sym == SDLK_PAGEDOWN || sym == SDLK_KP3) msgscroll-=5;
+ else if(uni == 't') {
+ if(!canmove) restartGame();
+ loadScores();
+ msgs.clear();
+ }
+
+ else if((sym != 0 && sym != SDLK_F12) || ev.type == SDL_MOUSEBUTTONDOWN) {
+ cmode = emNormal;
+ msgs.clear();
+ }
+ }
+
+ else if(cmode == emScores)
+ handleScoreKeys(sym, ev);
+
+ else if(cmode == emPickScores)
+ handlePickScoreKeys(uni, ev);
+
+ if(ev.type == SDL_QUIT)
+ return;
+
+ DEB("r3");
+ }
+
+ if(playerdead) break;
+ }
+
+ }
+#endif
+
+#ifndef MOBILE
+void cleargraph() {
+ for(int i=0; i<256; i++) if(font[i]) TTF_CloseFont(font[i]);
+ for(int i=0; i<128; i++) if(glfont[i]) delete glfont[i];
+#ifndef SIMULATE_JOYSTICK
+ if(stick) SDL_JoystickClose(stick);
+ if(panstick) SDL_JoystickClose(panstick);
+#endif
+ SDL_Quit();
+ }
+#endif
+
+void cleargraphmemory() {
+ mouseover = centerover = lmouseover = NULL;
+ }
+
diff --git a/heptagon.cpp b/heptagon.cpp
new file mode 100644
index 00000000..22f805b5
--- /dev/null
+++ b/heptagon.cpp
@@ -0,0 +1,158 @@
+// Hyperbolic Rogue
+// Copyright (C) 2011-2012 Zeno Rogue, see 'hyper.cpp' for details
+
+// heptagon here refers to underlying heptagonal tesselation
+// (which you can see by changing the conditions in graph.cpp)
+
+// automaton state
+enum hstate { hsOrigin, hsA, hsB, hsError };
+
+int fixrot(int a) { return (a+98)% 7; }
+int fix42(int a) { return (a+420)% 42; }
+
+struct heptagon;
+
+struct cell;
+cell *newCell(int type, heptagon *master);
+
+struct heptagon {
+ // automaton state
+ hstate s : 8;
+ // we are spin[i]-th neighbor of move[i]
+ unsigned char spin[7];
+ // neighbors; move[0] always goes towards origin,
+ // and then we go clockwise
+ heptagon* move[7];
+ // distance from the origin
+ short distance;
+ // fjord/wineyard generator
+ short fjordval;
+ heptagon*& modmove(int i) { return move[fixrot(i)]; }
+ unsigned char& gspin(int i) { return spin[fixrot(i)]; }
+ cell *c7;
+ // associated generator of alternate structure, for Camelot and horocycles
+ heptagon *alt;
+ };
+
+// the automaton is used to generate each heptagon in an unique way
+// (you can see the tree obtained by changing the conditions in graph.cpp)
+// from the origin we can go further in any direction, and from other heptagons
+// we can go in directions 3 and 4 (0 is back to origin, so 3 and 4 go forward),
+// and sometimes in direction 5
+
+hstate transition(hstate s, int dir) {
+ if(s == hsOrigin) return hsA;
+ if(s == hsA && dir >= 3 && dir <= 4) return hsA;
+ if(s == hsA && dir == 5) return hsB;
+ if(s == hsB && dir == 4) return hsB;
+ if(s == hsB && dir == 3) return hsA;
+ return hsError;
+ }
+
+heptagon origin;
+vector allAlts;
+
+// create h->move[d] if not created yet
+heptagon *createStep(heptagon *h, int d);
+
+// create a new heptagon
+heptagon *buildHeptagon(heptagon *parent, int d, hstate s, int pard = 0) {
+ heptagon *h = new heptagon;
+ h->alt = NULL;
+ h->s = s;
+ for(int i=0; i<7; i++) h->move[i] = NULL;
+ h->move[pard] = parent; h->spin[pard] = d;
+ parent->move[d] = h; parent->spin[d] = pard;
+ if(parent->c7) {
+ h->c7 = newCell(7, h);
+ h->fjordval = fjord_heptagon(parent->fjordval, d);
+ }
+ else {
+ h->c7 = NULL;
+ h->fjordval = 0;
+ }
+//generateFjordval(parent);
+//generateFjordval(h);
+ if(pard == 0) {
+ if(parent->s == hsOrigin) h->distance = 2;
+ else if(h->spin[0] == 5)
+ h->distance = parent->distance + 1;
+ else if(h->spin[0] == 4 && h->move[0]->s == hsB)
+ h->distance = createStep(h->move[0], (h->spin[0]+2)%7)->distance + 3;
+ else h->distance = parent->distance + 2;
+ }
+ else h->distance = parent->distance - 2;
+ return h;
+ }
+
+void addSpin(heptagon *h, int d, heptagon *from, int rot, int spin) {
+ rot = fixrot(rot);
+ createStep(from, rot);
+ h->move[d] = from->move[rot];
+ h->spin[d] = fixrot(from->spin[rot] + spin);
+ h->move[d]->move[fixrot(from->spin[rot] + spin)] = h;
+ h->move[d]->spin[fixrot(from->spin[rot] + spin)] = d;
+//generateFjordval(h->move[d]); generateFjordval(h);
+ }
+
+heptagon *createStep(heptagon *h, int d) {
+ d = fixrot(d);
+ if(h->s != hsOrigin && !h->move[0]) {
+ buildHeptagon(h, 0, hsA, 4);
+ }
+ if(h->move[d]) return h->move[d];
+ if(h->s == hsOrigin) {
+ buildHeptagon(h, d, hsA);
+ }
+ else if(d == 1) {
+ addSpin(h, d, h->move[0], h->spin[0]-1, -1);
+ }
+ else if(d == 6) {
+ addSpin(h, d, h->move[0], h->spin[0]+1, +1);
+ }
+ else if(d == 2) {
+ createStep(h->move[0], h->spin[0]-1);
+ addSpin(h, d, h->move[0]->modmove(h->spin[0]-1), 5 + h->move[0]->gspin(h->spin[0]-1), -1);
+ }
+ else if(d == 5 && h->s == hsB) {
+ createStep(h->move[0], h->spin[0]+1);
+ addSpin(h, d, h->move[0]->modmove(h->spin[0]+1), 2 + h->move[0]->gspin(h->spin[0]+1), +1);
+ }
+ else
+ buildHeptagon(h, d, (d == 5 || (h->s == hsB && d == 4)) ? hsB : hsA);
+ return h->move[d];
+ }
+
+// a structure used to walk on the heptagonal tesselation
+// (remembers not only the heptagon, but also direction)
+struct heptspin {
+ heptagon *h;
+ int spin;
+ };
+
+heptspin hsstep(const heptspin &hs, int spin) {
+ createStep(hs.h, hs.spin);
+ heptspin res;
+ res.h = hs.h->move[hs.spin];
+ res.spin = fixrot(hs.h->spin[hs.spin] + spin);
+ return res;
+ }
+
+heptspin hsspin(const heptspin &hs, int val) {
+ heptspin res;
+ res.h = hs.h;
+ res.spin = fixrot(hs.spin + val);
+ return res;
+ }
+
+// display the coordinates of the heptagon
+void backtrace(heptagon *pos) {
+ if(pos == &origin) return;
+ backtrace(pos->move[0]);
+ printf(" %d", pos->spin[0]);
+ }
+
+void hsshow(const heptspin& t) {
+ printf("ORIGIN"); backtrace(t.h); printf(" (spin %d)\n", t.spin);
+ }
+
diff --git a/hr-icon.ico b/hr-icon.ico
new file mode 100644
index 00000000..c647ba5c
Binary files /dev/null and b/hr-icon.ico differ
diff --git a/hr3-caves.ogg b/hr3-caves.ogg
new file mode 100644
index 00000000..ea5bdbcb
Binary files /dev/null and b/hr3-caves.ogg differ
diff --git a/hr3-crossroads.ogg b/hr3-crossroads.ogg
new file mode 100644
index 00000000..064f6839
Binary files /dev/null and b/hr3-crossroads.ogg differ
diff --git a/hr3-desert.ogg b/hr3-desert.ogg
new file mode 100644
index 00000000..35d967a9
Binary files /dev/null and b/hr3-desert.ogg differ
diff --git a/hr3-graveyard.ogg b/hr3-graveyard.ogg
new file mode 100644
index 00000000..b7db8191
Binary files /dev/null and b/hr3-graveyard.ogg differ
diff --git a/hr3-hell.ogg b/hr3-hell.ogg
new file mode 100644
index 00000000..9de328e0
Binary files /dev/null and b/hr3-hell.ogg differ
diff --git a/hr3-icyland.ogg b/hr3-icyland.ogg
new file mode 100644
index 00000000..97fbcd80
Binary files /dev/null and b/hr3-icyland.ogg differ
diff --git a/hr3-jungle.ogg b/hr3-jungle.ogg
new file mode 100644
index 00000000..49a5429f
Binary files /dev/null and b/hr3-jungle.ogg differ
diff --git a/hr3-laboratory.ogg b/hr3-laboratory.ogg
new file mode 100644
index 00000000..551202ad
Binary files /dev/null and b/hr3-laboratory.ogg differ
diff --git a/hr3-mirror.ogg b/hr3-mirror.ogg
new file mode 100644
index 00000000..9c67fb73
Binary files /dev/null and b/hr3-mirror.ogg differ
diff --git a/hr3-motion.ogg b/hr3-motion.ogg
new file mode 100644
index 00000000..d4386d83
Binary files /dev/null and b/hr3-motion.ogg differ
diff --git a/hr3-rlyeh.ogg b/hr3-rlyeh.ogg
new file mode 100644
index 00000000..76444018
Binary files /dev/null and b/hr3-rlyeh.ogg differ
diff --git a/hyper.cpp b/hyper.cpp
new file mode 100644
index 00000000..baec6040
--- /dev/null
+++ b/hyper.cpp
@@ -0,0 +1,200 @@
+// Hyperbolic Rogue
+// Copyright (C) 2011 Zeno Rogue
+
+// This program is free software; you can redistribute it and/or
+// modify it under the terms of the GNU General Public License
+// as published by the Free Software Foundation; either version 2
+// of the License, or (at your option) any later version.
+
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU General Public License for more details.
+
+// You should have received a copy of the GNU General Public License
+// along with this program; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+
+// disable for the Android version
+#define ISANDROID 0
+#define ISMOBILE 0
+#define ISIOS 0
+#define VER "6.6"
+#define VERNUM 6600
+#define VERNUM_HEX 0x6600
+
+#include
+
+#ifndef MAC
+#undef main
+#endif
+
+#include
+#include
+#include
+#include
+#include
+#include
+
+using namespace std;
+
+const char *scorefile = "hyperrogue.log";
+const char *conffile = "hyperrogue.ini";
+const char *musicfile = "";
+
+typedef long double ld;
+
+template int size(T& x) {return x.size(); }
+string its(int i) { char buf[64]; sprintf(buf, "%d", i); return buf; }
+string fts(float x) { char buf[64]; sprintf(buf, "%4.2f", x); return buf; }
+
+#undef DEBT
+void DEBT(const char *buf) {
+ printf("%4d %s\n", SDL_GetTicks(), buf);
+ }
+
+string s0;
+void addMessage(string s, char spamtype = 0);
+
+int clWidth, clHeight, clFont;
+string commandline;
+
+#include "hyperpoint.cpp"
+#include "fjordgen.cpp"
+#include "heptagon.cpp"
+#include "classes.cpp"
+#include "language.cpp"
+
+#ifdef STEAM
+#define NOLICENSE
+#endif
+
+#include "achievement.h"
+
+#include "cell.cpp"
+#include "game.cpp"
+#include "graph.cpp"
+
+#include "achievement.cpp"
+
+#include
+
+
+bool switchEuclid = false;
+
+int main(int argc, char **argv) {
+
+ printf("HyperRogue by Zeno Rogue , version "VER"\n");
+#ifndef NOLICENSE
+ printf("released under GNU General Public License version 2 and thus\n");
+ printf("comes with absolutely no warranty; see COPYING for details\n");
+#endif
+
+ achievement_init();
+
+ // printf("cell size = %d\n", int(sizeof(cell)));
+ srand(time(NULL));
+
+ #ifdef FHS
+ char sbuf[640], cbuf[640];
+ if(getenv("HOME")) {
+ snprintf(sbuf, 640, "%s/.%s", getenv("HOME"), scorefile); scorefile = sbuf;
+ snprintf(cbuf, 640, "%s/.%s", getenv("HOME"), conffile); conffile = cbuf;
+ }
+ #endif
+
+ for(int i=1; i= 0; uu--) {
+ printf("uu=%d\n", uu);
+ initgame(uu);
+ restartGame();
+ } */
+
+ eLand f = firstland;
+
+ loadsave();
+ initgame();
+
+ restoreGolems(items[itOrbLife]); items[itOrbLife] = 0;
+
+ firstland = f;
+ // exit(1);
+
+ initgraph();
+
+ int t1 = SDL_GetTicks();
+
+ if(switchEuclid) restartGameSwitchEuclid();
+
+ mainloop();
+
+ achievement_final(!items[itOrbSafety]);
+ SDL_Quit();
+
+ saveStats();
+ int msec = SDL_GetTicks() - t1;
+ printf("frame : %f ms (%f fps)\n", 1.*msec/frames, 1000.*frames/msec);
+ clearMemory();
+ cleargraph();
+
+ achievement_close();
+
+ return 0;
+ }
diff --git a/hyper.exe b/hyper.exe
new file mode 100755
index 00000000..407cb24b
Binary files /dev/null and b/hyper.exe differ
diff --git a/hyper.rc b/hyper.rc
new file mode 100644
index 00000000..71d7a111
--- /dev/null
+++ b/hyper.rc
@@ -0,0 +1,26 @@
+id ICON "hr-icon.ico"
+
+1 VERSIONINFO
+FILEVERSION 5,5,1,0
+PRODUCTVERSION 5,5,1,0
+BEGIN
+ BLOCK "StringFileInfo"
+ BEGIN
+ BLOCK "040904E4"
+ BEGIN
+ VALUE "CompanyName", "Zeno Rogue"
+ VALUE "FileDescription", "A roguelike in non-euclidean space"
+ VALUE "FileVersion", "55a"
+ VALUE "InternalName", "hyper"
+ VALUE "LegalCopyright", "Zeno Rogue"
+ VALUE "OriginalFilename", "hyper.exe"
+ VALUE "ProductName", "HyperRogue"
+ VALUE "ProductVersion", "5.5a"
+ END
+ END
+
+ BLOCK "VarFileInfo"
+ BEGIN
+ VALUE "Translation", 0x409, 1252
+ END
+END
diff --git a/hyperpoint.cpp b/hyperpoint.cpp
new file mode 100644
index 00000000..8c65c76f
--- /dev/null
+++ b/hyperpoint.cpp
@@ -0,0 +1,250 @@
+// Hyperbolic Rogue
+// Copyright (C) 2011-2012 Zeno Rogue, see 'hyper.cpp' for details
+
+// for the Euclidean mode...
+bool euclid = false;
+
+// hyperbolic points and matrices
+
+// basic functions and types
+//===========================
+
+ld sinh(ld alpha) { return (exp(alpha) - exp(-alpha)) / 2; }
+ld cosh(ld alpha) { return (exp(alpha) + exp(-alpha)) / 2; }
+
+ld squar(ld x) { return x*x; }
+
+int sig(int z) { return z<2?1:-1; }
+
+// hyperbolic point:
+//===================
+
+// we represent the points on the hyperbolic plane
+// by points in 3D space (Minkowski space) such that x^2+y^2-z^2 == -1, z > 0
+// (this is analogous to representing a sphere with points such that x^2+y^2+z^2 == 1)
+
+struct hyperpoint {
+ ld tab[3];
+ ld& operator [] (int i) { return tab[i]; }
+ const ld& operator [] (int i) const { return tab[i]; }
+ };
+
+hyperpoint hpxyz(ld x, ld y, ld z) {
+ // EUCLIDEAN
+ hyperpoint r; r[0] = x; r[1] = y; r[2] = z; return r;
+ }
+
+hyperpoint hpxy(ld x, ld y) {
+ // EUCLIDEAN
+ return hpxyz(x,y, euclid ? 1 : 1+x*x+y*y);
+ }
+
+// center of the pseudosphere
+hyperpoint Hypc = { {0,0,0} };
+
+// origin of the hyperbolic plane
+hyperpoint C0 = { {0,0,1} };
+
+// a point (I hope this number needs no comments ;) )
+hyperpoint Cx1 = { {1,0,1.41421356237} };
+
+// this function returns approximate square of distance between two points
+// (in the spherical analogy, this would be the distance in the 3D space,
+// through the interior, not on the surface)
+// also used to verify whether a point h1 is on the hyperbolic plane by using Hypc for h2
+
+ld intval(const hyperpoint &h1, const hyperpoint &h2) {
+ return squar(h1[0]-h2[0]) + squar(h1[1]-h2[1]) - squar(h1[2]-h2[2]);
+ }
+
+// display a hyperbolic point
+char *display(const hyperpoint& H) {
+ static char buf[100];
+ sprintf(buf, "%8.4f:%8.4f:%8.4f", double(H[0]), double(H[1]), double(H[2]));
+ return buf;
+ }
+
+// get the center of the line segment from H1 to H2
+hyperpoint mid(const hyperpoint& H1, const hyperpoint& H2) {
+
+ hyperpoint H3;
+ H3[0] = H1[0] + H2[0];
+ H3[1] = H1[1] + H2[1];
+ H3[2] = H1[2] + H2[2];
+
+ ld Z = 2;
+
+ if(!euclid) {
+ Z = intval(H3, Hypc);
+ Z = sqrt(-Z);
+ }
+
+ for(int c=0; c<3; c++) H3[c] /= Z;
+
+ return H3;
+ }
+
+// matrices
+//==========
+
+// matrices represent isometries of the hyperbolic plane
+// (just like isometries of the sphere are represented by rotation matrices)
+
+struct transmatrix {
+ ld tab[3][3];
+ ld * operator [] (int i) { return tab[i]; }
+ const ld * operator [] (int i) const { return tab[i]; }
+ };
+
+// identity matrix
+transmatrix Id = {{{1,0,0}, {0,1,0}, {0,0,1}}};
+
+hyperpoint operator * (const transmatrix& T, const hyperpoint& H) {
+ hyperpoint z;
+ for(int i=0; i<3; i++) {
+ z[i] = 0;
+ for(int j=0; j<3; j++) z[i] += T[i][j] * H[j];
+ }
+ return z;
+ }
+
+transmatrix operator * (const transmatrix& T, const transmatrix& U) {
+ transmatrix R;
+ for(int i=0; i<3; i++) for(int j=0; j<3; j++) R[i][j] = 0;
+ for(int i=0; i<3; i++) for(int j=0; j<3; j++) for(int k=0; k<3; k++)
+ R[i][j] += T[i][k] * U[k][j];
+ return R;
+ }
+
+// rotate by alpha degrees
+transmatrix spin(ld alpha) {
+ transmatrix T = Id;
+ T[0][0] = +cos(alpha); T[0][1] = +sin(alpha);
+ T[1][0] = -sin(alpha); T[1][1] = +cos(alpha);
+ T[2][2] = 1;
+ return T;
+ }
+
+transmatrix eupush(ld x, ld y) {
+ transmatrix T = Id;
+ T[0][2] = x;
+ T[1][2] = y;
+ return T;
+ }
+
+// push alpha units to the right
+transmatrix xpush(ld alpha) {
+ if(euclid) return eupush(alpha, 0);
+ transmatrix T = Id;
+ T[0][0] = +cosh(alpha); T[0][2] = +sinh(alpha);
+ T[2][0] = +sinh(alpha); T[2][2] = +cosh(alpha);
+ return T;
+ }
+
+// push alpha units vertically
+transmatrix ypush(ld alpha) {
+ if(euclid) return eupush(0, alpha);
+ transmatrix T = Id;
+ T[1][1] = +cosh(alpha); T[1][2] = +sinh(alpha);
+ T[2][1] = +sinh(alpha); T[2][2] = +cosh(alpha);
+ return T;
+ }
+
+// rotate the hyperplane around C0 such that H[1] == 0 and H[0] >= 0
+transmatrix spintox(hyperpoint H) {
+ transmatrix T = Id;
+ ld R = sqrt(H[0] * H[0] + H[1] * H[1]);
+ if(R >= 1e-12) {
+ T[0][0] = +H[0]/R; T[0][1] = +H[1]/R;
+ T[1][0] = -H[1]/R; T[1][1] = +H[0]/R;
+ }
+ return T;
+ }
+
+// reverse of spintox(H)
+transmatrix rspintox(hyperpoint H) {
+ transmatrix T = Id;
+ ld R = sqrt(H[0] * H[0] + H[1] * H[1]);
+ if(R >= 1e-12) {
+ T[0][0] = +H[0]/R; T[0][1] = -H[1]/R;
+ T[1][0] = +H[1]/R; T[1][1] = +H[0]/R;
+ }
+ return T;
+ }
+
+// for H such that H[1] == 0, this matrix pushes H to C0
+transmatrix pushxto0(hyperpoint H) {
+ if(euclid) return eupush(-H[0], -H[1]);
+ transmatrix T = Id;
+ T[0][0] = +H[2]; T[0][2] = -H[0];
+ T[2][0] = -H[0]; T[2][2] = +H[2];
+ return T;
+ }
+
+// reverse of pushxto0(H)
+transmatrix rpushxto0(hyperpoint H) {
+ if(euclid) return eupush(H[0], H[1]);
+ transmatrix T = Id;
+ T[0][0] = +H[2]; T[0][2] = +H[0];
+ T[2][0] = +H[0]; T[2][2] = +H[2];
+ return T;
+ }
+
+// generalization: H[1] can be non-zero
+transmatrix gpushxto0(hyperpoint H) {
+ hyperpoint H2 = spintox(H) * H;
+ return rspintox(H) * pushxto0(H2) * spintox(H);
+ }
+
+transmatrix rgpushxto0(hyperpoint H) {
+ hyperpoint H2 = spintox(H) * H;
+ return rspintox(H) * rpushxto0(H2) * spintox(H);
+ }
+
+
+// fix the matrix T so that it is indeed an isometry
+// (without using this, imprecision could accumulate)
+
+void fixmatrix(transmatrix& T) {
+ for(int x=0; x<3; x++) for(int y=0; y<=x; y++) {
+ ld dp = 0;
+ for(int z=0; z<3; z++) dp += T[z][x] * T[z][y] * sig(z);
+
+ if(y == x) dp = 1 - sqrt(sig(x)/dp);
+
+ for(int z=0; z<3; z++) T[z][x] -= dp * T[z][y];
+ }
+ }
+
+// show the matrix on screen
+
+void display(const transmatrix& T) {
+ for(int y=0; y<3; y++) {
+ for(int x=0; x<3; x++) printf("%10.7f", double(T[y][x]));
+ printf(" -> %10.7f\n", double(squar(T[y][0]) + squar(T[y][1]) - squar(T[y][2])));
+ // printf("\n");
+ }
+ for(int x=0; x<3; x++) printf("%10.7f", double(squar(T[0][x]) + squar(T[1][x]) - squar(T[2][x]))); printf("\n");
+ for(int x=0; x<3; x++) {
+ int y = (x+1) % 3;
+ printf("%10.7f", double(T[0][x]*T[0][y] + T[1][x]*T[1][y] - T[2][x]*T[2][y]));
+ }
+ printf("\n\n");
+ }
+
+transmatrix inverse(transmatrix T) {
+ ld det = 0;
+ for(int i=0; i<3; i++)
+ det += T[0][i] * T[1][(i+1)%3] * T[2][(i+2)%3];
+ for(int i=0; i<3; i++)
+ det -= T[0][i] * T[1][(i+2)%3] * T[2][(i+1)%3];
+
+ transmatrix T2;
+ if(det == 0) return T2;
+
+ for(int i=0; i<3; i++)
+ for(int j=0; j<3; j++)
+ T2[j][i] = (T[(i+1)%3][(j+1)%3] * T[(i+2)%3][(j+2)%3] - T[(i+1)%3][(j+2)%3] * T[(i+2)%3][(j+1)%3]) / det;
+
+ return T2;
+ }
diff --git a/hyperrogue-music.txt b/hyperrogue-music.txt
new file mode 100644
index 00000000..8940cd45
--- /dev/null
+++ b/hyperrogue-music.txt
@@ -0,0 +1,33 @@
+# Crossroads [02]
+[02] */hr3-crossroads.ogg
+# Desert [03]
+[03] */hr3-desert.ogg
+# Icy Lands [04]
+[04] */hr3-icyland.ogg
+# Living Caves [05]
+[05] */hr3-caves.ogg
+# Jungle [06]
+[06] */hr3-jungle.ogg
+# Alchemist's Lab [07]
+[07] */hr3-laboratory.ogg
+# Mirror [08]
+[08] */hr3-mirror.ogg
+# Graveyard [09]
+[09] */hr3-graveyard.ogg
+# R'Lyeh [10]
+[10] */hr3-rlyeh.ogg
+# Hell [11]
+[11] */hr3-hell.ogg
+# Cheaterland [12]
+[12] */hr3-icyland.ogg
+# Motion [13]
+[13] */hr3-motion.ogg
+# Dry Forest [14]
+[14] */hr3-jungle.ogg
+# Game Board [15]
+[15] */hr3-laboratory.ogg
+# Barrier [01] (useless)
+# None [00] (used when the window is out of focus)
+
+
+HyperRogue soundtrack by Shawn Parrotte (http://www.shawnparrotte.com), under the Creative Commons BY-SA 3.0 license, http://creativecommons.org/licenses/by-sa/3.0/
diff --git a/hyperrogue.html b/hyperrogue.html
new file mode 100644
index 00000000..ed9030b1
--- /dev/null
+++ b/hyperrogue.html
@@ -0,0 +1,107 @@
+
+
+
+HyperRogue
+
+
+
+
| | | | | | | | | | | | | | |