diff --git a/achievement.cpp b/achievement.cpp index 3d208d74..e0b9a34c 100644 --- a/achievement.cpp +++ b/achievement.cpp @@ -1,7 +1,7 @@ // Hyperbolic Rogue -- achievements // Copyright (C) 2011-2016 Zeno Rogue, see 'hyper.cpp' for details -#define NUMLEADER 57 +#define NUMLEADER 69 #define SCORE_UNKNOWN (-1) #define NO_SCORE_YET (-2) @@ -16,7 +16,7 @@ const char* leadernames[NUMLEADER] = { "Score", "Diamonds", "Gold", "Spice", "Rubies", "Elixirs", "Shards", "Totems", "Daisies", "Statues", "Feathers", "Sapphires", "Hyperstones", "Time to Win-71", "Turns to Win-71", - "Time to 10 Hyperstones-83", "Turns to 10 Hyperstones-83", "Orbs of Yendor", + "Time to 10 Hyperstones-94", "Turns to 10 Hyperstones-94", "Orbs of Yendor", "Fern Flowers", "Royal Jellies", "Powerstones", "Silver", "Wine", "Emeralds", "Grimoires", "Holy Grails", "Red Gems", "Pirate Treasures", @@ -47,8 +47,23 @@ const char* leadernames[NUMLEADER] = { "Tortoise points", // 54 "Dragon Scales", // 55 "Apples", // 56 + "Heptagonal Mode", // 57 + "Sunken Treasures", // 58 + "Ancient Jewelry", // 59 + "Golden Eggs", // 60 + "Multiplayer Score", // 61 + "Statistics", // 62 + "Halloween", // 63 + "Amethysts", // 64 + "Slime Molds", // 65 + "Dodecahedra", // 66 + "Green Grass", // 67 + "Spinel" // 68 }; +#define LB_STATISTICS 62 +#define LB_HALLOWEEN 63 + bool haveLeaderboard(int id); void upload_score(int id, int v); @@ -59,13 +74,18 @@ int achievementTimer; bool wrongMode(char flags) { if(cheater) return true; + if(flags == 'x') return false; if(purehepta != (flags == '7')) return true; if(euclid != (flags == 'e')) return true; + if(sphere != (flags == 'E')) return true; + if((quotient == 1) != (flags == 'q')) return true; + if((quotient == 2) != (flags == 'Q')) return true; if(shmup::on != (flags == 's')) return true; if(randomPatternsMode) return true; if(yendor::on) return true; if(tactic::on) return true; if(chaosmode != (flags == 'C')) return true; + if((numplayers() > 1) != (flags == 'm')) return true; return false; } @@ -80,10 +100,13 @@ void achievement_log(const char* s, char flags) { for(int i=0; i= 2) achievement_gain("STABBER2"); + if(s == "SLASH" && current >= 2) + achievement_gain("SLASH2"); if(s == "STAB" && current >= 4) achievement_gain("STABBER3"); if(s == "MIRRORKILL" && current-prev >= 1) @@ -374,6 +451,9 @@ void achievement_score(int cat, int number) { #ifdef HAVE_ACHIEVEMENTS if(cheater) return; if(euclid) return; + if(sphere && cat != LB_HALLOWEEN) return; + if(quotient) return; + if(elliptic && cat != LB_HALLOWEEN) return; if(purehepta) return; if(randomPatternsMode) return; if(shmup::on && cat != LB_PURE_TACTICS_SHMUP && cat != LB_PURE_TACTICS_COOP) return; @@ -403,7 +483,7 @@ void improveItemScores() { improve_score(34, itPalace); improve_score(35, itFjord); - improve_score(37, itEdge); + improve_score(37, itIvory); improve_score(38, itElemental); improve_score(39, itZebra); @@ -420,14 +500,34 @@ void improveItemScores() { improve_score(54, itBabyTortoise); improve_score(55, itDragon); improve_score(56, itApple); + + improve_score(58, itKraken); + improve_score(59, itBarrow); + improve_score(60, itTrollEgg); + + improve_score(64, itAmethyst); + improve_score(65, itSlime); + improve_score(66, itDodeca); + + improve_score(67, itGreenGrass); + improve_score(68, itBull); } void achievement_final(bool really_final) { if(offlineMode) return; #ifdef HAVE_ACHIEVEMENTS + upload_score(LB_STATISTICS, time(NULL)); if(cheater) return; + + if(sphere && euclidland == laHalloween) { + if(shmup::on || chaosmode || purehepta || numplayers() > 1 || tactic::on || randomPatternsMode) + return; + achievement_score(LB_HALLOWEEN, items[itTreat]); + } + if(euclid) return; - if(purehepta) return; + if(sphere) return; + if(elliptic) return; if(randomPatternsMode) return; if(tactic::on) { @@ -439,15 +539,24 @@ void achievement_final(bool really_final) { if(yendor::on) return; - if(shmup::on && chaosmode) return; + // no leaderboards for two special modes at once + int specials = 0; + if(shmup::on) specials++; + if(chaosmode) specials++; + if(purehepta) specials++; + if(specials > 1) return; + + if(numplayers() > 1 && chaosmode) return; + if(numplayers() > 1 && purehepta) return; int total_improved = 0; specific_improved = 0; specific_what = 0; - if(!shmup::on && !chaosmode) improveItemScores(); + if(!shmup::on && !chaosmode && !purehepta && numplayers() == 1) improveItemScores(); - int sid = chaosmode ? 53 : shmup::on ? (numplayers() > 1 ? 44 : 28) : 0; + int sid = purehepta ? 57 : chaosmode ? 53 : shmup::on ? (numplayers() > 1 ? 44 : 28) : + (numplayers() > 1 ? 61 : 0); int tg = gold(); if(tg && haveLeaderboard(sid)) { @@ -488,6 +597,8 @@ void achievement_victory(bool hyper) { #ifdef HAVE_ACHIEVEMENTS if(cheater) return; if(euclid) return; + if(sphere) return; + if(quotient) return; if(purehepta) return; if(randomPatternsMode) return; if(hyper && shmup::on) return; @@ -572,3 +683,7 @@ void achievement_display() { #endif } +bool isAscending(int i) { + return i == 13 || i == 14 || i == 15 || i == 16 || i == 29 || i == 30 || i == 45; + }; + diff --git a/cell.cpp b/cell.cpp index 3fcbb9a0..1798fda0 100644 --- a/cell.cpp +++ b/cell.cpp @@ -1,10 +1,11 @@ + // Hyperbolic Rogue -- cells // Copyright (C) 2011-2016 Zeno Rogue, see 'hyper.cpp' for details // cells the game is played on int fix6(int a) { return (a+96)% 6; } -int fix7(int a) { return (a+84)% 7; } +int fix7(int a) { return (a+420)%S7; } int dirdiff(int dd, int t) { dd %= t; @@ -15,7 +16,20 @@ int dirdiff(int dd, int t) { struct cell : gcell { char type; // 6 for hexagons, 7 for heptagons - unsigned char spn[7]; + + // wall parameter, used for remaining power of Bonfires and Thumpers + char wparam; + + // 'tmp' is used for: + // pathfinding algorithm used by monsters with atypical movement (which do not use pathdist) + // bugs' pathfinding algorithm + short aitmp; + + uint32_t spintable; + int spin(int d) { return tspin(spintable, d); } + int spn(int d) { return tspin(spintable, d); } + int mirror(int d) { return tmirror(spintable, d); } + heptagon *master; cell *mov[7]; // meaning very similar to heptagon::move }; @@ -36,11 +50,11 @@ cell *newCell(int type, heptagon *master) { return c; } -void merge(cell *c, int d, cell *c2, int d2) { +void merge(cell *c, int d, cell *c2, int d2, bool mirrored = false) { c->mov[d] = c2; - c->spn[d] = d2; + tsetspin(c->spintable, d, d2 + (mirrored?8:0)); c2->mov[d2] = c; - c2->spn[d2] = d; + tsetspin(c2->spintable, d2, d + (mirrored?8:0)); } typedef unsigned short eucoord; @@ -80,55 +94,54 @@ cell *createMov(cell *c, int d) { if(c->mov[d]) return c->mov[d]; else if(purehepta) { heptagon *h2 = createStep(c->master, d); - c->mov[d] = h2->c7; - c->spn[d] = c->master->spin[d]; - h2->c7->mov[c->spn[d]] = c; - h2->c7->spn[c->spn[d]] = d; + merge(c,d,h2->c7,c->master->spin(d),false); } - else if(c->type == 7) { + else if(c->type != 6) { cell *n = newCell(6, c->master); - c->mov[d] = n; n->mov[0] = c; - c->spn[d] = 0; n->spn[0] = d; + merge(c,d,n,0,false); - heptspin hs; hs.h = c->master; hs.spin = d; + heptspin hs; hs.h = c->master; hs.spin = d; hs.mirrored = false; - heptspin hs2 = hsstep(hsspin(hs, 3), 3); + int a3 = c->type/2; + int a4 = a3+1; - // merge(hs2.h->c7, hs2.spin, n, 2); + heptspin hs2 = hsstep(hsspin(hs, a3), a3); + merge(hs2.h->c7, hs2.spin, n, 2, hs2.mirrored); - 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; + heptspin hs3 = hsstep(hsspin(hs, a4), a4); + merge(hs3.h->c7, hs3.spin, n, 4, hs3.mirrored); + extern void verifycell(cell *c); + verifycell(n); } else if(d == 5) { - int di = fixrot(c->spn[0]+1); + int di = fixrot(c->spin(0)+1); cell *c2 = createMov(c->mov[0], di); - merge(c, 5, c2, fix6(c->mov[0]->spn[di] + 1)); + bool mirr = c->mov[0]->mirror(di); + merge(c, 5, c2, fix6(c->mov[0]->spn(di) + (mirr?-1:1)), mirr); // 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); + 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)); + bool mirr = c->mov[0]->mirror(di); + merge(c, 1, c2, fix6(c->mov[0]->spn(di) - (mirr?-1:1)), mirr); // 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); + bool mirr = c->mirror(2); + int di = fixrot(c->spn(2)-(mirr?-1:1)); cell *c2 = createMov(c->mov[2], di); - merge(c, 3, c2, fix6(c->mov[2]->spn[di] - 1)); + bool nmirr = mirr ^ c->mov[2]->mirror(di); + merge(c, 3, c2, fix6(c->mov[2]->spn(di) - (nmirr?-1:1)), nmirr); // 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); } @@ -136,12 +149,12 @@ cell *createMov(cell *c, int d) { } cell *createMovR(cell *c, int d) { - d %= 42; d += 42; d %= c->type; + d %= 420; d += 420; d %= c->type; return createMov(c, d); } cell *getMovR(cell *c, int d) { - d %= 42; d += 42; d %= c->type; + d %= 420; d += 420; d %= c->type; return c->mov[d]; } @@ -149,12 +162,13 @@ cell *getMovR(cell *c, int d) { struct cellwalker { cell *c; int spin; - cellwalker(cell *c, int spin) : c(c), spin(spin) {} - cellwalker() {} + bool mirrored; + cellwalker(cell *c, int spin) : c(c), spin(spin) { mirrored = false; } + cellwalker() { mirrored = false; } }; void cwspin(cellwalker& cw, int d) { - cw.spin = (cw.spin+d + 42) % cw.c->type; + cw.spin = (cw.spin+(MIRR(cw)?-d:d) + 420) % cw.c->type; } bool cwstepcreates(cellwalker& cw) { @@ -162,20 +176,21 @@ bool cwstepcreates(cellwalker& cw) { } cell *cwpeek(cellwalker cw, int dir) { - return createMov(cw.c, (cw.spin+42+dir) % cw.c->type); + return createMov(cw.c, (cw.spin+420+dir) % cw.c->type); } void cwstep(cellwalker& cw) { createMov(cw.c, cw.spin); - int nspin = cw.c->spn[cw.spin]; + int nspin = cw.c->spn(cw.spin); + if(cw.c->mirror(cw.spin)) cw.mirrored = !cw.mirrored; 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; + c1->mov[s1] = c2; tsetspin(c1->spintable, s1, s2); + c2->mov[s2] = c1; tsetspin(c2->spintable, s2, s1); } struct euclideanSlab { @@ -216,28 +231,99 @@ cell*& euclideanAtCreate(eucoord x, eucoord y) { return c; } +int spherecells() { + if(S7 == 5) return (elliptic?6:12); + if(S7 == 4) return (elliptic?3:6); + if(S7 == 3) return 4; + if(S7 == 2) return (elliptic?1:2); + if(S7 == 1) return 1; + return 12; + } // initializer (also inits origin from heptagon.cpp) void initcells() { DEBB(DF_INIT, (debugfile,"initcells\n")); - - origin.s = hsOrigin; - origin.emeraldval = 98; - origin.zebraval = 40; - origin.fiftyval = 0; -#ifdef CDATA - origin.rval0 = origin.rval1 = 0; - origin.cdata = NULL; -#endif - - 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); + if(sphere) { + for(int i=0; itype, 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) { + 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; + c->mov[t]->mov[c->spn(t)] = NULL; } DEBMEM ( printf("DEL %p\n", c); ) delete c; @@ -261,6 +347,14 @@ void clearcell(cell *c) { heptagon deletion_marker; +void clearHexes(heptagon *at) { + if(at->c7) { + if(!purehepta) for(int i=0; i<7; i++) + clearcell(at->c7->mov[i]); + clearcell(at->c7); + } + } + #include void clearfrom(heptagon *at) { queue q; @@ -276,22 +370,16 @@ void clearfrom(heptagon *at) { 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) { + 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]->move[at->spin(i)] = NULL; at->move[i] = NULL; } - DEBMEM ( printf("at %p\n", at); ) - if(at->c7) { - if(!purehepta) for(int i=0; i<7; i++) - clearcell(at->c7->mov[i]); - clearcell(at->c7); - } - DEBMEM ( printf("!DEL %p\n", at); ) + clearHexes(at); if(at != &origin) delete at; } //printf("maxq = %d\n", maxq); @@ -302,46 +390,49 @@ void verifycell(cell *c) { for(int i=0; imov[i]; if(c2) { - if(t == 7 && !purehepta) verifycell(c2); - if(c2->mov[c->spn[i]] && c2->mov[c->spn[i]] != c) - printf("cell error %p %p\n", c, c2); + if(t != 6 && !purehepta) verifycell(c2); + if(c2->mov[c->spn(i)] && c2->mov[c->spn(i)] != c) { + printf("cell error %p:%d [%d] %p:%d [%d]\n", c, i, c->type, c2, c->spn(i), c2->type); + exit(1); + } } } } 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]]); + 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 [%d s=%d] %p %p\n", at, i, at->spin(i), at->move[i], at->move[i]->move[at->spin(i)]); } + if(!sphere && !quotient) for(int i=0; i<7; i++) if(at->move[i] && at->spin(i) == 0 && at->move[i] != &origin) + verifycells(at->move[i]); verifycell(at->c7); } +int eupattern(cell *c) { + eucoord x, y; + decodeMaster(c->master, x, y); + short z = (short(y+2*x))%3; + z %= 3; + if(z<0) z += 3; + return z; + } + + 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; + if(euclid) return eupattern(c) == 0; + else return c->type != 6; } bool ishex1(cell *c) { // EUCLIDEAN - if(euclid) { - eucoord x, y; - decodeMaster(c->master, x, y); - short z = (short(y+2*x))%3; - if(z<0) z += 3; - return z == 1; - } + if(euclid) return eupattern(c) == 1; else return c->type == 7; } int emeraldval(cell *c) { - if(euclid) return 0; + if(euclid) return eupattern(c); + if(sphere) return 0; if(c->type == 7) return c->master->emeraldval >> 3; else { @@ -360,6 +451,17 @@ int eudist(short sx, short sy) { return max(max(z0,z1), z2); } +int compdist(int dx[3]) { + 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; + if((dx[0] == mi+1) + (dx[1] == mi+1) + (dx[2] == mi+1) >= 2) + return mi+1; + return mi; + } + int celldist(cell *c) { if(euclid) { eucoord x, y; @@ -370,12 +472,7 @@ int celldist(cell *c) { 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; + return compdist(dx); } #define ALTDIST_BOUNDARY 99999 @@ -392,6 +489,7 @@ int celldistAlt(cell *c) { decodeMaster(c->master, x, y); return euclidAlt(x, y); } + if(!c->master->alt) return 0; 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) @@ -420,8 +518,23 @@ unsigned bitmajority(unsigned a, unsigned b, unsigned c) { return (a&b) | ((a^b)&c); } +int eufifty(cell *c) { + eucoord x, y; + decodeMaster(c->master, x, y); + int ix = short(x) + 99999 + short(y); + int iy = short(y) + 99999; + if(c->land == laWildWest) + return (ix + iy * 26 + 28) % 37; + else { + ix += (iy/3) * 3; + iy %= 3; ix %= 9; + return iy * 9 + ix; + } + } + int fiftyval(cell *c) { - if(euclid) return 0; + if(euclid) return eufifty(c) * 32; + if(sphere) return 0; if(c->type == 7) return c->master->fiftyval; else { @@ -433,26 +546,11 @@ int fiftyval(cell *c) { } int cdist50(cell *c) { + if(sphere) return 0; if(euclid) { - eucoord x, y; - decodeMaster(c->master, x, y); - int ix = short(x) + 99999 + short(y); - int iy = short(y) + 99999; - if(c->land == laPalace) { - char palacemap[3][10] = { - "012333321", - "112322232", - "222321123" - }; - ix += (iy/3) * 3; - iy %= 3; ix %= 9; - return palacemap[iy][ix] - '0'; - } - else { - const char *westmap = "0123333332112332223322233211233333322"; - int id = ix + iy * 26 + 28; - return westmap[id % 37] - '0'; - } + if(c->land == laWildWest) + return "0123333332112332223322233211233333322"[eufifty(c)] - '0'; + else return "012333321112322232222321123"[eufifty(c)] - '0'; } if(c->type == 7) return cdist50(fiftyval(c)); int a0 = cdist50(createMov(c,0)); @@ -464,6 +562,7 @@ int cdist50(cell *c) { int land50(cell *c) { if(c->type == 7) return land50(fiftyval(c)); + else if(sphere || euclid) return 0; else { if(cdist50(createMov(c,0)) < 3) return land50(createMov(c,0)); if(cdist50(createMov(c,2)) < 3) return land50(createMov(c,2)); @@ -474,6 +573,7 @@ int land50(cell *c) { int polara50(cell *c) { if(c->type == 7) return polara50(fiftyval(c)); + else if(sphere || euclid) return 0; else { if(cdist50(createMov(c,0)) < 3) return polara50(createMov(c,0)); if(cdist50(createMov(c,2)) < 3) return polara50(createMov(c,2)); @@ -485,6 +585,7 @@ int polara50(cell *c) { int polarb50(cell *c) { if(euclid) return true; if(c->type == 7) return polarb50(fiftyval(c)); + else if(sphere || euclid) return true; else { if(cdist50(createMov(c,0)) < 3) return polarb50(createMov(c,0)); if(cdist50(createMov(c,2)) < 3) return polarb50(createMov(c,2)); @@ -498,7 +599,8 @@ int elhextable[28][3] = { }; int fiftyval049(cell *c) { - if(c->type == 7) return fiftyval(c) / 32; + if(c->type == 7 || euclid) return fiftyval(c) / 32; + else if(sphere) return 0; else { int a[3], qa=0; int pa = polara50(c), pb = polarb50(c); @@ -536,7 +638,9 @@ int fiftyval049(cell *c) { int zebra40(cell *c) { if(c->type == 7) return (c->master->zebraval/10); - else { + else if(sphere) return 0; + else if(euclid) return eupattern(c); + else { int ii[3], z; ii[0] = (c->mov[0]->master->zebraval/10); ii[1] = (c->mov[2]->master->zebraval/10); @@ -558,6 +662,7 @@ int zebra40(cell *c) { int zebra3(cell *c) { if(c->type == 7) return (c->master->zebraval/10)/4; + else if(sphere) return 0; else { int ii[3]; ii[0] = (c->mov[0]->master->zebraval/10)/4; @@ -696,8 +801,6 @@ bool randpatternMajority(cell *c, int ival, int iterations) { return memo; } -#ifdef CDATA - #include map spins; @@ -732,6 +835,8 @@ void setHeptagonRval(heptagon *h) { cdata *getHeptagonCdata(heptagon *h) { if(h->cdata) return h->cdata; + if(sphere || quotient) h = &origin; + if(h == &origin) { return h->cdata = new cdata(orig_cdata); } @@ -876,8 +981,108 @@ eLand getCLand(cell *c) { return land_scape[b & 31]; } +// list all cells in distance at most maxdist, or until when maxcount cells are reached + +struct celllister { + vector lst; + vector tmps; + vector dists; + + bool listed(cell *c) { + return c->aitmp >= 0 && c->aitmp < size(lst) && lst[c->aitmp] == c; + } + + void add(cell *c, int d) { + if(listed(c)) return; + c->aitmp = size(lst); + tmps.push_back(c->aitmp); + lst.push_back(c); + dists.push_back(d); + } + + int getdist(cell *c) { return dists[c->aitmp]; } + + ~celllister() { + for(int i=0; iaitmp = tmps[i]; + } + + celllister(cell *orig, int maxdist, int maxcount, cell *breakon) { + lst.clear(); + tmps.clear(); + dists.clear(); + add(orig, 0); + cell *last = orig; + for(int i=0; i= maxcount || dists[i]+1 == maxdist) break; + last = lst[size(lst)-1]; + maxdist--; + } + } + } + }; + +cell *heptatdir(cell *c, int d) { + if(d&1) { + cell *c2 = createMov(c, d); + int s = c->spin(d); + s += 3; s %= 6; + return createMov(c2, s); + } + else return createMov(c, d); + } + +namespace fieldpattern { + +pair fieldval(cell *c) { + if(c->type == 7) return make_pair(c->master->fieldval, false); + else return make_pair(btspin(c->master->fieldval, c->spin(0)), true); + } + +int subpathid = fp43.matcode[fp43.strtomatrix("RRRPRRRRRPRRRP")]; +int subpathorder = fp43.order(fp43.matrices[subpathid]); + +pair subval(cell *c, int _subpathid = subpathid, int _subpathorder = subpathorder) { + if(c->type == 6) + return min(min(subval(createMov(c, 0)),subval(createMov(c, 2))), subval(createMov(c, 4))); + else { + pair pbest, pcur; + pcur.first = c->master->fieldval; + pcur.second = 0; + pbest = pcur; + for(int i=0; i<_subpathorder; i++) { + pcur.first = fp43.gmul(pcur.first, _subpathid); + pcur.second++; + if(pcur < pbest) pbest = pcur; + } + return pbest; + } + } + +} int celldistance(cell *c1, cell *c2) { int d = 0; + + if(euclid) { + eucoord x1, y1, x2, y2; + decodeMaster(c1->master, x1, y1); + decodeMaster(c2->master, x2, y2); + return eudist(x1-x2, y1-y2); + } + + if(sphere || quotient == 1) { + celllister cl(c1, 64, 1000, c2); + return cl.getdist(c2); + } + + if(quotient == 2) + return fp43.getdist(fieldpattern::fieldval(c1), fieldpattern::fieldval(c2)); + cell *cl1=c1, *cr1=c1, *cl2=c2, *cr2=c2; while(true) { if(cl1 == cl2) return d; @@ -912,16 +1117,20 @@ int celldistance(cell *c1, cell *c2) { } } -void clearMemory() { - extern void clearGameMemory(); - clearGameMemory(); - if(shmup::on) shmup::clearMemory(); - cleargraphmemory(); -#ifndef MOBILE - mapeditor::clearModelCells(); -#endif +void clearHyperbolicMemory() { + DEBMEM ( verifycells(&origin); ) + clearfrom(&origin); + for(int i=0; itype != 6) { + int id = c->master->fiftyval; + int hemitable[3][12] = { + { 6, 3, 3, 3, 3, 3,-6,-3,-3,-3,-3,-3}, + { 6, 3, 6, 3, 0, 0,-6,-3,-6,-3, 0, 0}, + {-3, 0, 3, 0,-6,-6, 3, 0,-3, 0, 6, 6} + }; + return hemitable[which][id]; + } + else { + int score = 0; + for(int i=0; i<6; i+=2) + score += getHemisphere(c->mov[i], which); + return score/3; + } + } + +namespace quotientspace { + + vector allcells; + + struct code { + int c[8]; + }; + + bool operator == (const code& c1, const code &c2) { + for(int i=0; i<8; i++) if(c1.c[i] != c2.c[i]) return false; + return true; + } + + bool operator < (const code& c1, const code &c2) { + for(int i=0; i<8; i++) if(c1.c[i] != c2.c[i]) return c1.c[i] < c2.c[i]; + return false; + } + + map reachable; + vector bfsq; + + int cod(heptagon *h) { + return zebra40(h->c7); + } + + code get(heptspin hs) { + code res; + res.c[0] = cod(hs.h); + for(int i=1; i<8; i++) { + res.c[i] = cod(hsstep(hs, 0).h); + hs = hsspin(hs, 1); + } + return res; + } + + vector connections; + + int rvadd = 0, rvdir = 1; + + int rv(int x) { return (rvadd+x*rvdir) % 7; } // if(x) return 7-x; else return x; } + + void add(const heptspin& hs) { + code g = get(hs); + if(!reachable.count(g)) { + reachable[g] = bfsq.size(); + bfsq.push_back(hs); + add(hsspin(hs, 1)); + } + } + + vector allh; + + void clear() { + clearfrom(origin.alt); + for(int i=0; imove[i] = NULL; + origin.alt->c7 = newCell(7, origin.alt); + + for(int i=0; ialt = NULL; + h->s = hsOrigin; + h->emeraldval = 0; + h->zebraval = 0; + h->fiftyval = 0; + h->fieldval = 7*i; + h->rval0 = h->rval1 = 0; h->cdata = NULL; + h->distance = 0; + h->c7 = newCell(7, h); + } + for(int j=0; j<7; j++) { + h->move[rv(j)] = allh[connections[i*7+j]/7]; + h->setspin(rv(j), rv(connections[i*7+j]%7)); + } + } + + for(int i=0; iemeraldval = allh[i]->alt->emeraldval; + allh[i]->zebraval = allh[i]->alt->zebraval; + allh[i]->fiftyval = allh[i]->alt->fiftyval; + allh[i]->distance = allh[i]->alt->distance; + /* for(int j=0; j<7; j++) + allh[i]->move[j]->alt = createStep(allh[i]->alt, j); */ + } + + celllister cl(origin.c7, 100, 100000000, NULL); + allcells = cl.lst; + } + } + diff --git a/classes.cpp b/classes.cpp index df645e0d..7d0d167f 100644 --- a/classes.cpp +++ b/classes.cpp @@ -1,11 +1,6 @@ // Hyperbolic Rogue -- items, monsters, walls, lands, descriptions, etc. // Copyright (C) 2011-2016 Zeno Rogue, see 'hyper.cpp' for details -#define GEN_M 0 -#define GEN_F 1 -#define GEN_N 2 -#define GEN_O 3 - // --- help --- const char *wormdes = @@ -110,7 +105,7 @@ const char *foresthelp = "Trees catch fire on the next turn. The temperature of the grass cells " "rises once per turn for each fire nearby, and becomes fire itself " "when its temperature has risen 10 times.\n" - "You can also cut down the trees. Big trees take two turns to cut down."; + "You can also chop down the trees. Big trees take two turns to chop down."; const char *hivehelp = "The Hive is filled with Hyperbugs. They are huge insects which look a bit like " @@ -216,13 +211,36 @@ const char *winddesc = "outcoming wind. However, you can move two cells with the wind in a single turn, " "and so can the birds."; +const char *warningdesc = + "Warnings are issued when you try to do something that appears dangerous, " + "like stepping on a known mine, or getting your boat destroyed by " + "a Kraken without having Orb of the Fish. In some cases the action " + "might actually be safe -- so you can ignore the warning and do it anyway, " + "simply by repeating the action."; + const char *hauntdesc = "A dark forest filled with ghosts and graves. But there is also treasure hidden " "deep within... But don't let greed make you stray from your path, as " "you can get lost!\n\n" "The Haunted Woods are bounded by a single equidistant curve. It is not a circle or horocycle.\n\n"; +const char *bulldashdesc = + "Butterflies don't pursue you -- unless you get next to them, they just spin around the obstacles. " + "They cannot be killed conventionally, but you get treasure when a Raging Bull crashes into a Butterfly. "; + +const char *prairiedesc = + "You can find safety in some places in the Prairie, but if you want treasures, " + "they can be found only on the other side of a giant herd of bulls."; + +const char *cadesc = + "A land for people wanting to experiment with cellular automata in the HyperRogue grid. " + "Rules can be given on the command line; the default rules are:\n" + "-c07 00100000 -c06 0010000 -c17 00011000 -c16 0001100 -caprob 0.3\n" + "(-c0 or -c1 can be given if the same rule is to be used for hexagonal " + "and heptagonal cells)."; + const char *NODESC = "No description yet."; +const char *NODESCYET = "No description yet."; const char *GENDERSWITCH = NODESC; // --- monsters --- @@ -259,8 +277,8 @@ const char *dragondesc = "The head will regenerate on the " "turns the Dragon is not moving, so you will usually have to hit it with " "your last attack; otherwise, if the head is healthy, it may breathe " - "fire (at range 3), losing the hitpoint. Killing the Dragon gives you " - "treasure."; + "fire (at range 3), losing the hitpoint. Killing the Dragon " + "while still in the Dragon Chasms gives you treasure."; const char *tortoisedesc = "Galápagos is the land of Tortoises. " @@ -275,8 +293,49 @@ const char *tortoisedesc = "Bringing back a Baby Tortoise counts as 5 $$$. The more factors agree in " "the given location of Galápagos, the brighter it is shown on your screen."; +const char *krakendesc = + "There are Krakens in your homeland too... huge sea monsters which " + "could easily destroy ships. The geometry of this strange world " + "prevents quick movement of huge objects, " + "so there are no large ships, only small boats, and " + "hyperbolic Krakens are relatively small too. Still, you suppose they might be " + "the widest creatures which could still move at considerable speed...\n\n" + + "Kraken heads can move only on hexagons. You need to attack all the tentacles to " + "kill the Kraken. A tentacle cannot attack if it has been attacked on the " + "same turn. When a Kraken attacks you while you are in a boat, it " + "destroys the boat, but does not kill you."; -const int motypes = 125; +const char *halloweendesc = + "Halloween is a special land, that is available only in the spherical " + "or elliptic geometry (press 'o' to switch). You play on the surface of " + "a jack-o'-lantern, " + "and have to collect as many Treats as possible. Each Treat you collect " + "brings new monsters to fight, and new magical powers for you. You " + "have to fight the monsters while effectively managing your limited " + "resources."; + +const char *reptiledesc = + "These reptiles are quite strange creatures. They " + "spend most of their lives sleeping as floors " + "that other creatures can walk on. " + "Sometimes they wake up to hunt their prey, " + "but they will happily go back to sleep if they " + "happen to move into a hole on their way. " + "Your attacks do not kill the Reptiles, but " + "you can push and stun them."; + +const char *naturedesc = + "This Orb allows you to grow like an Ivy. " + "The Ivy is always rooted in your current location; " + "moving among the Ivy cells will move the root. " + "Moving to a new location will cause the Ivy to grow " + ", if an Ivy could make that movement " + "(otherwise it breaks). " + "You can also target one of the cells adjacent to your ivy " + "(not to you) to grow or attack there."; + +const int motypes = 139; struct monstertype { char glyph; @@ -474,7 +533,7 @@ monstertype minf[motypes] = { { 'B', 0x909000, "Slime Beast", slimehelp}, { '@', 0x8080FF, "Knight", camelothelp }, // knight moved { '@', 0x8B4513, "Illusion", - "Illusions are targetted " + "Illusions are targeted " "by most monsters, just like yourself, Thumpers, and your friends." }, { 'P', 0xD00000, "Pirate", @@ -526,7 +585,7 @@ monstertype minf[motypes] = { { 'P', 0xFF80FF, "Princess", princessdesc}, { 'P', 0xFF80FF, "Prince", princessdesc}, { 'P', 0xFF80FF, "Princess", princessdesc}, - { 'S', 0xC0C0C0, "Servant", "A simple servant of the master of the Ivory Tower."}, + { 'F', 0xD03000, "Familiar", "A simple servant of the master of the Ivory Tower."}, { 'B', 0x707070, "Gargoyle", gargdesc}, { 'E', 0xFF0000, "Fire Elemental", "This monster leaves a trail of fire behind."}, @@ -573,7 +632,7 @@ monstertype minf[motypes] = { { 'T', 0x487830, "Tortoise", tortoisedesc}, { 'D', 0xC03000, "Dragon", dragondesc}, { 'd', 0xC03000, "Dragon", dragondesc}, - { 'N', 0x303030, "Nighthawk", NODESC}, + { 'F', 0x909090, "Gadfly", "Annoying insects. They can awaken Sleeping Bulls."}, { 'Y', 0xFF8000, "Yendorian Researcher", "These people study gravity and infinite trees. " "They have no special features, other than wearing a strange hat." @@ -581,6 +640,39 @@ monstertype minf[motypes] = { { 'K', 0xA8A8A8, "Sparrowhawk", "A bird who hunts in the treetops of Yendorian Forest." }, + { 'K', 0xD0A0A0, "Kraken", krakendesc}, + { 'K', 0xC07070, "Kraken Tentacle", krakendesc}, + { 'D', 0xF09090, "Draugr", + "Animated corpses of ancient Viking warriors. They are immune to mundane weapons, " + "but they can be destroyed by your Orb of the Sword." + }, + { 'C', 0xC08000, "Friendly Ivy", naturedesc }, + { 'V', 0xC000C0, "Vampire Bat", + "Vampire Bats don't attack normally, but they drain your magical powers if " + "they are at distance at most 2 from you." + }, + { 'B', 0x404040, "Bat", + "Someone has told you that one can get battle experience safely by " + "killing tons of essentially harmless creatures, such as Bats. But " + "does this make any sense?...\n\n" + "It does not. Bats cannot hurt you, but may block your movement, or " + "toggle switches if they fall on them." }, + { 'R', 0x8080C0, "Reptile", reptiledesc }, + { 'B', 0x606020, "Herd Bull", + "Herds of these Bulls are running long distances for some reason. They become Raging Bulls if something stops them." }, + { 'B', 0xA03000, "Raging Bull", + "Raging Bulls charge in a straight line: on heptagons, when they can choose one of two possible directions, " + "they choose one closer to your current location. In the case of a tie, the cell where more neighbors is " + "closer to your current location is chosen; if still a tie, past locations are considered. " + "They can attack you in any direction, and monsters on their way are attacked even if friendly. " + "When they crash into something, the obstacle is usually destroyed, and they are stunned for three turns, after " + "which they charge at you again (in any direction). " + "Raging Bulls cannot be killed or stunned conventionally." + }, + { 'B', 0xB07000, "Sleeping Bull", + "Sleeping bulls wake up when you get into distance of two cells from them." + }, + { 'S', 0xFFD500, "Butterfly", bulldashdesc}, // shmup specials { '@', 0xC0C0C0, "Rogue", "In the Shoot'em Up mode, you are armed with thrown Knives."}, @@ -593,6 +685,9 @@ monstertype minf[motypes] = { { '?', 0x00C000, "dead bug", NODESC}, { '?', 0xFFFF00, "electric discharge", NODESC}, // appears as 'killed by electrocution' { '?', 0xE06000, "dead bird", NODESC}, + { '?', 0xE06000, "Energy Sword", NODESC}, + { '!', 0xFF0000, "Warning", warningdesc}, + { '*', 0, "vertex", "A vertex from rogueviz."} }; enum eMonster { @@ -626,18 +721,23 @@ enum eMonster { moMouse, moMouseMoved, moPrincess, moPrincessMoved, moPrincessArmed, moPrincessArmedMoved, - moEdgeMonkey, moGargoyle, moFireElemental, moAirElemental, + moFamiliar, moGargoyle, moFireElemental, moAirElemental, moOrangeDog, moTentacleGhost, moMetalBeast, moMetalBeast2, moOutlaw, moMutant, moStormTroll, moForestTroll, moRedFox, moWindCrow, moFriendlyGhost, moRatling, moFalsePrincess, moRoseLady, moRoseBeauty, moRatlingAvenger, moTortoise, moDragonHead, moDragonTail, - moNighthawk, moLemur, moKestrel, + moGadfly, moResearcher, moSparrowhawk, + moKrakenH, moKrakenT, moDraugr, moFriendlyIvy, + moVampire, moBat, moReptile, + moHerdBull, moRagingBull, moSleepBull, + moButterfly, // shmup specials moPlayer, moBullet, moFlailBullet, moFireball, moTongue, moAirball, // temporary - moDeadBug, moLightningBolt, moDeadBird + moDeadBug, moLightningBolt, moDeadBird, moEnergySword, moWarning, + moRogueviz }; struct genderswitch_t { @@ -667,7 +767,7 @@ genderswitch_t genderswitch[NUM_GS] = { // --- items --- -const int ittypes = 92; +const int ittypes = 110; struct itemtype { char glyph; @@ -694,7 +794,7 @@ itemtype iinf[ittypes] = { }, { '!', 0xFFFF00, "Elixir of Life", "A wonderful beverage, apparently obtained by mixing red and blue slime. You definitely feel more " - "healthy after drinking it, but you still fell that one hit of a monster is enough to kill you."}, + "healthy after drinking it, but you still feel that one hit of a monster is enough to kill you."}, { '%', 0xFF00FF, "Shard", "A piece of a magic mirror, or a mirage cloud, that can be used for magical purposes. Only mirrors and clouds " "in the Land of Mirrors leave these."}, @@ -798,7 +898,7 @@ itemtype iinf[ittypes] = { "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 " + "This Orb allows you to create illusions of yourself. Illusions are targeted " "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" @@ -991,7 +1091,67 @@ itemtype iinf[ittypes] = { "you have to dismount this turn -- be very careful to make this possible, " "as your mount could attack you immediately!\n\n" "While riding, " "click on a location to order your mount to move or attack there.", - } + }, + { 'o', 0xFFFF80, "Orb of the Sword", + "This Orb gives you a weapon made of pure magical energy. You do not hold " + "it, it simply floats in the air next to you. When you go, the energy sword moves " + "with you, pointing at the same relative angle it pointed before -- you cannot " + "move or rotate it otherwise. Most monsters can be killed by moving the sword into them, " + "and won't move into the spot with the sword." + }, + { 'x', 0x4040FF, "Sunken Treasure", + "Cargo of a ship which was once destroyed by a Kraken." }, + { 'o', 0xFF8040, "Orb of the Sword II", + "An alternative version of Orb of the Sword. If you have both of them, " + "you have two energy swords, facing in opposite directions." + }, + { '*', 0xFFFF80, "Ancient Jewelry", + "Precious belongings of ancient Viking heroes. Your Orb of the Sword can be " + "used to dig these treasures out of the barrows." + }, + { '!', 0xFFD700, "Golden Egg", + "Trolls of Trollheim are descendants of a bridge Troll, who collected " + "payments from people crossing the bridge. One of them paid with " + "golden eggs. The bridge Troll found the eggs beautiful, but he quickly lost them. " + "Golden eggs are still revered by Trolls, and you can find them in their " + "caves." + }, + { '!', 0xFF0000, "Warning", warningdesc + }, + { 'o', 0x808080, "Orb of the Stone", + "Trolls turn into stone walls when they die. When you have this Orb, " + "this happens to every monster you defeat. Statues created from this Orb " + "have slightly different properties than Trolls who petrify naturally." + }, + { 'o', 0xC08000, "Orb of Nature", naturedesc }, + { '%', 0x800080, "Treat", halloweendesc }, + { '%', 0x30A030, "Slime Mold", + "A very interesting species of slime mold." + }, + { '*', 0xFF00FF, "Amethyst", "A beatiful purple gem from the Lost Mountain." }, + { 'o', 0xC00040, "Orb of Recall", + "When the charges on this Orb expire, " + "you will be automatically returned to the place where you have found it. " + "Extra Orbs of Recall delay this without changing the recall location. " + "Pick up an Orb of Safety causes an immediate recall."}, + { ']', 0x8080FF, "Dodecahedron", + "These dodecahedra made of a mysterious material are the Reptiles' favorite toy." + }, + { 'o', 0x8080FF, "Orb of Vaulting", + "This Orb allows you to jump over an adjacent monster, killing or stunning it. " + "You can only vault in a roughly straight line. " + "Target a cell on the other side to use it." + }, + { '$', 0x80FF80, "Green Grass", prairiedesc }, + { 'o', 0x8080FF, "Orb of Horns", + "After you move while having this Orb, you immediately attack the next cell in the straight line " + "(or two cells, when moving on a heptagon). This attack is slightly stronger than your normal " + "attack: it can stun some of the monsters which cannot be killed or stunned normally." + }, + { 'o', 0x8080FF, "Orb of the Bull", + "You get the powers of Shield, Horns, and Thorns after you move two moves in a straight line " + "with this Orb." }, + { '$', 0xC060C0, "Spinel", bulldashdesc }, }; enum eItem { itNone, itDiamond, itGold, itSpice, itRuby, itElixir, itShard, itBone, itHell, itStatue, @@ -1000,32 +1160,43 @@ enum eItem { itNone, itDiamond, itGold, itSpice, itRuby, itElixir, itShard, itBo itOrbLightning, itOrbFlash, itOrbWinter, itOrbSpeed, itOrbLife, itOrbShield, itOrbDigging, itOrbTeleport, itOrbSafety, itOrbThorns, itFernFlower, - itWine, itOrbGhost, itSilver, itOrbPsi, + itWine, itOrbAether, itSilver, itOrbPsi, itRoyalJelly, itEmerald, itOrbInvis, itPower, itOrbFire, itHolyGrail, itGrimoire, itOrbDragon, itOrbIllusion, itPirate, itCompass, - itRedGem, itOrbPreserve, itOrbTelekinesis, + itRedGem, itOrbTime, itOrbSpace, itBombEgg, itCoast, itWhirlpool, itOrbFriend, itOrbWater, itOrbAir, itPalace, itOrbFrog, itFjord, itOrbFish, itOrbDiscord, itSavedPrincess, itOrbLove, - itEdge, itZebra, + itIvory, itZebra, itFireShard, itAirShard, itEarthShard, itWaterShard, itElemental, itOrbSummon, itOrbMatter, itBounty, itRevolver, itFulgurite, itMutant, itOrbStunning, itOrbLuck, itMutant2, itOrbFreedom, itLotus, itOrbUndeath, itWindstone, itOrbEmpathy, itStrongWind, itBuggy, itBuggy2, - itRose, itCoral, itOrbSkunk, itOrb37, itOrbEnergy, - itBabyTortoise, itOrbShell, itApple, itDragon, itOrbDomination + itRose, itCoral, itOrbBeauty, itOrb37, itOrbEnergy, + itBabyTortoise, itOrbShell, itApple, itDragon, itOrbDomination, + itOrbSword, + itKraken, itOrbSword2, itBarrow, + itTrollEgg, itWarning, + itOrbStone, itOrbNature, itTreat, + itSlime, itAmethyst, + itOrbRecall, itDodeca, + itOrbDash, + itGreenGrass, + itOrbHorns, + itOrbBull, + itBull }; // --- wall types --- -const int walltypes = 88; +const int walltypes = 96; struct walltype { char glyph; @@ -1105,7 +1276,7 @@ walltype winf[walltypes] = { "using an Orb of Aether, your Aether power will be completely drained." }, { '#', 0xC0C0C0, "wall of Camelot", camelothelp }, - { '#', 0xA06000, "Round Table", 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 " @@ -1168,7 +1339,7 @@ walltype winf[walltypes] = { { '#', 0x3030FF, "charged wall", elecdesc}, { '#', 0xFF3030, "grounded wall", elecdesc}, { '#', 0xA0A060, "sandstone wall", elecdesc}, - { '+', 0x704000, "saloon wall", wildwestdesc}, + { '#', 0x704000, "saloon wall", wildwestdesc}, { '#', 0x90C0C0, "metal wall", elecdesc}, { '#', 0x607030, "dead troll", trollhelpX}, { '+', 0xC0C0FF, "fan", winddesc}, @@ -1182,13 +1353,28 @@ walltype winf[walltypes] = { { '#', 0xC0C000, "warp gate", "This gate separates the warped area from the normal land."}, { '+', 0x804000, "trunk", "The skeleton of a tree."}, - { '+', 0x804000, "solid branch", "Branches here could bear your weight easily."}, - { '+', 0x804000, "weak branch", + { '-', 0x402000, "solid branch", "Branches here could bear your weight easily."}, + { ':', 0x804000, "weak branch", "Branches here will bear you weight, but if you use them to move (not fall) to an unstable place, they will break."}, { '+', 0x60C060, "canopy", "Only thin twigs and leaves here. They may bear fruits, but for you, these cells count " "as unstable." - } + }, + { '#', 0xD0C060, "barrow wall", "This wall is quite strong. You will need another way in."}, + { '#', 0x90A060, "barrow", "Your Orb of the Sword can be used to dig here."}, + { '#', 0xE0E0E0, "stone statue", "A petrified creature."}, + { '.', 0xE8E8E8, "tower of Camelot", camelothelp}, + { '-', 0x402000, "big bush", + "You can hold this bush to climb the Lost Mountain. " + "Bushes block the movement of birds." + }, + { ':', 0x804000, "small bush", + "You can hold this bush to climb the Lost Mountain, " + "but it is not very strong -- it will get destroyed " + "if you climb from it into an unstable location. " + "Bushes block the movement of birds."}, + { '.', 0xFFFF00, "Reptile floor", reptiledesc}, + { '.', 0xFFFF00, "Reptile bridge", reptiledesc}, }; enum eWall { waNone, waIcewall, waBarrier, waFloorA, waFloorB, waCavewall, waCavefloor, waDeadTroll, waDune, @@ -1212,12 +1398,16 @@ enum eWall { waNone, waIcewall, waBarrier, waFloorA, waFloorB, waCavewall, waCav waDeadTroll2, waFan, waTemporary, waEarthD, waElementalTmp, waElementalD, waFloorC, waFloorD, waRose, waWarpGate, - waTrunk, waSolidBranch, waWeakBranch, waCanopy + waTrunk, waSolidBranch, waWeakBranch, waCanopy, + waBarrowWall, waBarrowDig, + waPetrified, waTower, + waBigBush, waSmallBush, + waReptile, waReptileBridge }; // --- land types --- -const int landtypes = 56; +const int landtypes = 67; struct landtype { int color; @@ -1355,6 +1545,42 @@ landtype linf[landtypes] = { }, { 0x487830, "Galápagos", tortoisedesc}, { 0xD04000, "Dragon Chasms", dragondesc}, + { 0xD04000, "Kraken Depths", + "A long time ago, this was a trade route. But then, Krakens have risen out of the " + "depths. Many trading ships sank here. Legend says that you can uncover the secret " + "of a magical weapon spell somewhere in the depths...\n\n" + + "You can find Sunken Treasures here, but they won't appear until you have killed " + "a Kraken. You will also need Orb of the Fish to get the treasures, luckily you can " + "steal one from the Viking treasure hunters." + }, + { 0x804020, "Burial Grounds", + "Ancient Viking heroes were buried here. Their graves have barrows raised over " + "them, and are guarded by Draugar, animated corpses who are immune to mundane weapons. " + "You will need to use a magical weapon spell to defeat them, and to rob the " + "ancient jewelry buried in the graves." + }, + { 0x90A548, "Trollheim", + "Many clans of Trolls spend their lives in this kingdom. You can find many " + "statues of Trolls here. You suppose that they are not actually statues, but simply " + "elderly Trolls, who have petrified upon death. Or maybe you have killed " + "these Trolls yourself?" + }, + { 0xFF7518, "Halloween", halloweendesc}, + { 0x605040, "Dungeon", + "The result of a collaboration of the Great Vizier and the Wizard of the Ivory Tower." + }, + { 0x603000, "Lost Mountain", + "Gravitational anomalies in the Jungle create mountains " + "overgrown with ivies and bushes. " + "Will you dare to climb the ivies to get the amethysts hidden above?\n\n" + "Cells adjacent to Ivies count as stable (but Ivies " + "cannot climb themselves or other Ivies)."}, + { 0xFFFF00, "Reptiles", reptiledesc}, + { 0x0000D0, "Prairie", prairiedesc}, + { 0x800080, "Bull Dash", bulldashdesc}, + { 0xC000C0, "Crossroads V", "Extremely narrow Crossroads layout.\n"}, + { 0xC0C0C0, "Cellular Automaton", cadesc} }; enum eLand { laNone, laBarrier, laCrossroads, laDesert, laIce, laCaves, laJungle, laAlchemist, laMirror, laGraveyard, @@ -1367,8 +1593,11 @@ enum eLand { laNone, laBarrier, laCrossroads, laDesert, laIce, laCaves, laJungle laCanvas, laPrincessQuest, laWildWest, laStorms, laOvergrown, laClearing, laHaunted, laHauntedWall, laHauntedBorder, - laWhirlwind, laRose, laGridCoast, laGridSea, laCrossroads4, - laEndorian, laTortoise, laDragon + laWhirlwind, laRose, laWarpCoast, laWarpSea, laCrossroads4, + laEndorian, laTortoise, laDragon, + laKraken, laBurial, laTrollheim, + laHalloween, laDungeon, laMountain, laReptile, + laPrairie, laBull, laCrossroads5, laCA }; // cell information for the game @@ -1387,69 +1616,104 @@ struct gcell { unsigned ligon : 1; // is it sparkling with lightning? unsigned - pathdist : 5, // player distance wrt usual movement + pathdist : 7, // player distance wrt usual movement cpdist : 5, mpdist : 5; // current/minimum player distance unsigned mondir : 3, // monster direction, for multi-tile monsters and graphics bardir : 4, // barrier direction stuntime : 4, // stun time left (for Palace Guards and Skeletons) - hitpoints : 3, // hitpoints left (for Palace Guards) - landflags : 2; // extra flags for land + hitpoints : 3; // hitpoints left (for Palace Guards, also reused as cpid for mirrors) + + unsigned landflags : 8; // extra flags for land - char wparam; // wall parameter, used for remaining power of Bonfires and Thumpers - - // 'tmp' is used for: - // pathfinding algorithm used by monsters with atypical movement (which do not use pathdist) - // bugs' pathfinding algorithm - short aitmp; - // 'landparam' is used for: // heat in Icy/Cocytus; // heat in Dry (0..10); // CR2 structure; // hive Weird Rock color / pheromones; - // Ocean/coast depth - union { int32_t landpar; float heat; char bytes[4]; } LHU; + // Ocean/coast depth; + // Bomberbird Egg hatch time / mine marking; + // number of Ancient Jewelry; + // improved tracking in Trollheim + union { + int32_t landpar; + float heat; + char bytes[4]; + struct fieldinfo { + uint16_t fieldval; + unsigned rval : 4; + unsigned flowerdist : 4; + unsigned walldist : 4; + unsigned walldist2 : 4; + } fi; + + } LHU; }; #define landparam LHU.landpar +#define fval LHU.fi.fieldval + #define NODIR 7 #define NOBARRIERS 8 -#define LAND_OVER 44 -eLand land_over[LAND_OVER] = { +#define LAND_OVER 53 +#define LAND_OVERX 55 + +eLand land_over[LAND_OVERX] = { laIce, laCaves, laDesert, laMotion, laJungle, laAlchemist, laCrossroads, - laMirror, laMinefield, laZebra, laPalace, laPrincessQuest, - laOcean, laLivefjord, laGridCoast, laCaribbean, laWhirlpool, laRlyeh, laTemple, + laMirror, laMinefield, laPalace, laPrincessQuest, laZebra, laReptile, + laOcean, laWarpCoast, laLivefjord, laKraken, laCaribbean, laWhirlpool, laRlyeh, laTemple, + laIvoryTower, laEndorian, laDungeon, laMountain, laCrossroads2, laDryForest, laWineyard, laDeadCaves, laGraveyard, laHaunted, laHive, laRedRock, - laIvoryTower, laEndorian, laDragon, laTortoise, - laOvergrown, laClearing, laStorms, laWhirlwind, laRose, - laEmerald, laCamelot, laElementalWall, - laHell, laCrossroads3, laCocytus, laPower, laCrossroads4 + laOvergrown, laClearing, laStorms, laWhirlwind, laRose, laBurial, + laEmerald, laCamelot, + laPrairie, laBull, + laElementalWall, laTrollheim, + laHell, laCrossroads3, laCocytus, laPower, laCrossroads4, + laCrossroads5, + // EXTRA + laWildWest, laHalloween }; -#define LAND_EUC 42 +#define LAND_EUC 49 eLand land_euc[LAND_EUC] = { laIce, laCaves, laDesert, laMotion, laJungle, laCrossroads, laMirror, laMinefield, laAlchemist, laZebra, laPalace, laPrincessQuest, - laOcean, laLivefjord, laGridCoast, laCaribbean, laWhirlpool, laRlyeh, laTemple, - laElementalWall, + laOcean, laLivefjord, laWarpCoast, laCaribbean, laKraken, laWhirlpool, laRlyeh, laTemple, + laElementalWall, laTrollheim, laDryForest, laWineyard, laDeadCaves, laGraveyard, laHive, laRedRock, laIvoryTower, - laOvergrown, laClearing, laStorms, laWhirlwind, laRose, + laOvergrown, laClearing, laStorms, laWhirlwind, laRose, laBurial, laEmerald, laCamelot, laDragon, laTortoise, laHell, laCrossroads3, laCocytus, laPower, laCrossroads4, - laWildWest + laWildWest, + laReptile, laMountain, laBull, laPrairie }; // MISSING: laCrossroads2 -#define LAND_HYP 39 +#define LAND_SPH 39 +eLand land_sph[LAND_SPH] = { + laHalloween, + laIce, laCaves, laDesert, laMotion, laJungle, + laCrossroads, + laMirror, laMinefield, laAlchemist, + laLivefjord, laWarpCoast, laKraken, laRlyeh, + laTrollheim, + laDryForest, laDeadCaves, laGraveyard, laHive, laRedRock, + laOvergrown, laStorms, laWhirlwind, laRose, laBurial, + laEmerald, laDragon, laTortoise, + laHell, laCrossroads3, laCocytus, laPower, laElementalWall, + laCrossroads4, + laWildWest, laPalace, laBull, laPrairie, laCA + }; + +#define LAND_HYP 47 eLand land_hyp[LAND_HYP] = { laHell, laCocytus, laGraveyard, laWineyard, laDryForest, laCaves, @@ -1458,8 +1722,14 @@ eLand land_hyp[LAND_HYP] = { laDesert, laRedRock, laWhirlpool, laOvergrown, laClearing, laStorms, laCaribbean, laJungle, laAlchemist, laMotion, laMirror, laMinefield, - laZebra, laElementalWall, laIvoryTower, laHaunted, laWhirlwind, laCrossroads, - laGridCoast, laRose, laDragon, laEndorian, laTortoise + laZebra, laElementalWall, laIvoryTower, laHaunted, laWhirlwind, + laWarpCoast, laRose, laDragon, laEndorian, + laReptile, laDungeon, laMountain, + laTortoise, + laKraken, laBurial, laTrollheim, + laPrairie, laBull, + // always must be last + laCrossroads }; #define LAND_SCAPE 32 @@ -1472,29 +1742,32 @@ eLand land_scape[LAND_SCAPE] = { laOvergrown, laStorms, laJungle, laAlchemist, laMotion, laMirror, laMinefield, laZebra, laWhirlwind, laCrossroads, - laGridCoast, laRose, + laWarpCoast, laRose, laCrossroads, laCrossroads2, laCrossroads3 }; -#define LAND_TAC 44 +#define LAND_TAC 50 struct landtacinfo { eLand l; int tries, multiplier; }; landtacinfo land_tac[LAND_TAC] = { {laIce, 10, 1}, {laDesert, 10, 1}, {laMotion, 10, 1}, {laCaves, 10, 1}, {laAlchemist, 10, 1}, {laJungle, 10, 1}, {laMirror, 10, 1}, {laZebra, 10, 1}, {laPalace, 10, 1}, - {laOcean, 10, 1}, {laLivefjord, 10, 1}, {laGridCoast, 10, 1}, {laRlyeh, 10, 1}, {laHell, 10, 1}, - {laElementalWall, 10, 1}, {laDryForest, 10, 1}, {laWineyard, 10, 1}, + {laOcean, 10, 1}, {laLivefjord, 10, 1}, {laWarpCoast, 10, 1}, {laRlyeh, 10, 1}, {laHell, 10, 1}, + {laDryForest, 10, 1}, {laWineyard, 10, 1}, {laReptile, 10, 1}, {laDeadCaves, 10, 1}, {laGraveyard, 10, 1}, {laHaunted, 10, 1}, - {laIvoryTower, 10, 1}, {laEndorian, 10, 1}, + {laIvoryTower, 10, 1}, {laEndorian, 10, 1}, {laMountain, 5, 2}, {laDungeon, 5, 2}, {laEmerald, 10, 1}, {laCocytus, 10, 1}, - {laCaribbean, 5, 2}, {laWhirlpool, 5, 2}, {laTemple, 5, 2}, {laMinefield, 5, 2}, + {laCaribbean, 5, 2}, {laWhirlpool, 5, 2}, {laKraken, 5, 2}, + {laTemple, 5, 2}, {laMinefield, 5, 2}, {laPower, 5, 2}, {laHive, 5, 2}, {laRedRock, 5, 2}, {laStorms, 5, 2}, {laOvergrown, 5, 2}, {laClearing, 5, 2}, {laWhirlwind, 5, 2}, {laRose, 5, 2}, {laDragon, 2, 5}, {laTortoise, 1, 10}, + {laBurial, 5, 2}, + {laElementalWall, 10, 1}, {laTrollheim, 5, 2}, {laCrossroads, 10, 1}, {laCrossroads2, 10, 1}, {laCrossroads3, 10, 1}, {laCrossroads4, 10, 1}, @@ -1506,6 +1779,6 @@ landtacinfo land_tac[LAND_TAC] = { eLand randlands[RANDLANDS] = { laIce, laDesert, laCaves, laAlchemist, laGraveyard, laPower, laLivefjord, laZebra, laRlyeh, laDryForest, laEmerald, laWineyard, laDeadCaves, laRedRock, - laOvergrown, laWildWest, laGridCoast + laOvergrown, laWildWest, laWarpCoast }; diff --git a/complex.cpp b/complex.cpp index 3747d8ee..fb35df3f 100644 --- a/complex.cpp +++ b/complex.cpp @@ -15,6 +15,7 @@ namespace whirlwind { decodeMaster(c->master, x, y); return 1+((((signed short)(y)+int(50000))/3)%3); } + if(sphere) return getHemisphere(c, 0) > 0 ? 1 : 2; return zebra3(c); } @@ -126,10 +127,12 @@ namespace whirlwind { } for(int i=0; iitem) + animateMovement(whirlline[i+1], whirlline[i], LAYER_BOAT); } for(int i=0; iitem) - collectItem(cwt.c, true); + if(isPlayerOn(whirlline[i]) && whirlline[i]->item) + collectItem(whirlline[i], true); } void move() { @@ -174,7 +177,7 @@ namespace whirlwind { namespace elec { - bool havecharge, havethunder; + bool havecharge, haveelec, havethunder; bool afterOrb; // extra charge from the Orb of Lightning enum eCharge { @@ -193,7 +196,7 @@ namespace elec { if(c->wall == waCharged) return ecCharged; if(c->wall == waSea || c->wall == waGrounded) return ecGrounded; if(c->wall == waSandstone || c->wall == waDeadTroll || - c->wall == waDeadTroll2 || + c->wall == waDeadTroll2 || c->wall == waVinePlant || c->wall == waMetal || isAlchAny(c)) return c->land == laStorms ? ecConductor : ecGrounded; @@ -202,20 +205,18 @@ namespace elec { if(c->wall == waChasm) return ecIsolator; - if(shmup::on ? isPlayerOn(c) : (c == cwt.c || c == stalemate::moveto || (items[itOrbEmpathy] && isFriendly(c)))) { + if(shmup::on ? isPlayerOn(c) : (isPlayerOn(c) || stalemate::isMoveto(c) || (items[itOrbEmpathy] && isFriendly(c)))) { if(items[itOrbShield]) return ecIsolator; - if(ao) return ecIsolator; - if(!items[itOrbGhost]) return c->land == laStorms ? ecConductor : ecGrounded; + if(afterOrb) return ecIsolator; + if(!items[itOrbAether]) return c->land == laStorms ? ecConductor : ecGrounded; } // if(c->monst && stalemate::moveto) printf("%p: isKilled = %d\n", c, stalemate::isKilled(c)); - if( - ( - c->monst || (shmup::on ? isPlayerOn(c) : c == cwt.c) || - c == stalemate::moveto || c == stalemate::pushto) + else if( + (c->monst || stalemate::isPushto(c)) && - (c == stalemate::pushto || !stalemate::isKilled(c)) + (stalemate::isPushto(c) || !stalemate::isKilled(c)) && c->monst != moGhost && c->monst != moIvyDead && c->monst != moIvyNext && !(isDragon(c->monst) && !c->hitpoints) @@ -323,15 +324,17 @@ namespace elec { if(c->monst) { if(c->monst == moMetalBeast2 && !c->item) c->item = itFulgurite; - killMonster(c); + killMonster(c, moLightningBolt); } if(isPlayerOn(c)) { killThePlayerAt(moLightningBolt, c, 0); } if(c->wall == waSandstone) - c->wall = waNone, c->item = itFulgurite; + c->wall = waNone, c->item = itFulgurite, + drawParticles(c, winf[waSandstone].color, 16); if(c->wall == waDeadTroll) c->wall = waCavefloor; if(c->wall == waDeadTroll2 || isAlchAny(c) || c->wall == waVinePlant) + drawParticles(c, winf[c->wall].color, 16), c->wall = waNone; /* if(c->wall == waCharged) c->wall = waMetal; */ @@ -357,6 +360,7 @@ namespace elec { void init() { chargecells.clear(); + if(!haveelec && !afterOrb) return; sval++; for(int i=0; iland == laStorms && !afterOrb) - markOrb(itOrbShield), markOrb(itOrbGhost); + for(int i=0; iland == laStorms && !afterOrb) + markOrb(itOrbShield), markOrb(itOrbAether); builder b; fire(); if(!afterOrb) @@ -444,8 +450,9 @@ namespace elec { } if(lightningfast > 1) return; builder b; - if(elec::affected(cwt.c)) - lightningfast = 1; + for(int i=0; iland != laPalace) return OUT_OF_PALACE; else if(euclid) return celldistAlt(c); else if(!c->master->alt) return OUT_OF_PRISON; + else if(quotient || sphere) return OUT_OF_PRISON; else return celldistAlt(c); } @@ -551,6 +561,7 @@ namespace princess { c->stuntime = 0; c->hitpoints = palaceHP(); drawFlash(c); + playSound(c, princessgender() ? "heal-princess" : "heal-prince"); info *inf = NULL; for(int i=0; ibestdist >= 6) @@ -642,6 +654,7 @@ namespace princess { static int msgid = 0; + playSound(c, princessgender() ? "speak-princess" : "speak-prince"); retry: if(msgid >= 32) msgid = 0; @@ -870,7 +883,7 @@ namespace whirlpool { if(i == c->type) return NULL; if(d>d2) next = -next; for(int j=1; jtype; j++) { - cell *c2 = c->mov[(i+42+next*j) % c->type]; + cell *c2 = c->mov[(i+420+next*j) % c->type]; if(celldistAlt(c2) == d) return c2; } return NULL; @@ -917,13 +930,15 @@ namespace whirlpool { void whirlMove(cell *wto, cell *wfrom) { // monsters don't move - if(wfrom && (wfrom == cwt.c || wfrom->monst)) + if(wfrom && (isPlayerOn(wfrom) || wfrom->monst)) return; // disappear if(!wto) { wfrom->wall = waSea; wfrom->item = itNone; } if(wfrom && wto && wfrom->wall == waBoat && wto->wall == waSea && !wto->monst) { wfrom->wall = waSea; wto->wall = waBoat; + wto->mondir = neighborId(wto, wfrom); + animateMovement(wfrom, wto, LAYER_BOAT); } if(wfrom && wto && wfrom->item && !wto->item && wfrom->wall != waBoat) { @@ -992,23 +1007,32 @@ namespace mirror { void createMM(cellwalker& cw, eMonster type) { if(type == moLightningBolt) castLightningBolt(cw); - else if(cw.c->monst == moNone && cellMirrorable(cw.c) && cw.c != cwt.c) { + else if(cw.c->monst == moNone && cellMirrorable(cw.c) && !isPlayerOn(cw.c)) { cw.c->monst = type; cw.c->mondir = cw.spin; + cw.c->hitpoints = multi::cpid; } } + inline eMonster switchtype(eMonster m) { + return (m == moMirror) ? moMirage : moMirror; + } + + inline eMonster switchtypeif(eMonster m, bool b) { + if(!b) return m; + return (m == moMirror) ? moMirage : moMirror; + } + void createMirrors(cell *c, int dir, eMonster type) { cellwalker C(c, dir); - if(type == moMirror) type = moMirage; - else if(type == moMirage) type = moMirror; + type = switchtype(type); for(int i=0; itype; i++) { cwstep(C); if(C.c->type == c->type) { cwspin(C, i); - createMM(C, type); + createMM(C, switchtypeif(type, C.mirrored)); cwspin(C, -i); } cwstep(C); @@ -1056,13 +1080,13 @@ namespace mirror { } void spin(int d) { - for(int i=0; ihitpoints != multi::cpid) continue; if(c->monst == moMirror) - mirrors[i]->mondir = (mirrors[i]->mondir - d + 42) % mirrors[i]->type; + mirrors[i]->mondir = (mirrors[i]->mondir - d + 420) % mirrors[i]->type; if(c->monst == moMirage) - mirrors[i]->mondir = (mirrors[i]->mondir + d + 42) % mirrors[i]->type; + mirrors[i]->mondir = (mirrors[i]->mondir + d + 420) % mirrors[i]->type; } } @@ -1091,13 +1115,14 @@ namespace mirror { mirrors2.clear(); for(int i=0; ihitpoints != multi::cpid) continue; eMonster m = c->monst; if(isMimic(m)) { if(m == moMirage) nummirage++; int dir = c->mondir; cell *c2 = c->mov[dir]; if(c2 && !isMimic(c2) && canAttack(c,m,c2,c2->monst, 0)) - killWithMessage(c2, true, m); + attackMonster(c2, AF_MSG | AF_ORSTUN, m); if(c2->wall == waBigTree) c2->wall = waSmallTree; else if(c2->wall == waSmallTree) @@ -1113,12 +1138,14 @@ namespace mirror { if(c2->monst == moGreaterM) { c2->monst = moLesserM; continue; } - if(c2 == cwt.c) { + if(isPlayerOn(c2)) { addMessage(XLAT("You join %the1.", m)); + playSound(c2, "click"); continue; } if(isMimic(c2)) { addMessage(XLAT("Two of your images crash and disappear!")); + playSound(c2, "click"); c2->monst = moNone; continue; } @@ -1128,18 +1155,25 @@ namespace mirror { } c->monst = m; moveMonster(c2, c); empathyMove(c, c2, dir); - mirrors2.push_back(c2); + mirrors2.push_back(c2); + if(c->mirror(dir) && isMimic(c2->monst)) + c2->monst = switchtype(c2->monst); } } for(int i=0; ihitpoints != multi::cpid) continue; eMonster m = c->monst; if(c->wall == waMirror) { addMessage(XLAT("%The1 breaks the mirror!", m)); + drawParticles(c, winf[c->wall].color, 16); + playSound(c, "pickup-mirror", 50); createMirrors(c, c->mondir, m); c->wall = waNone; } if(c->wall == waCloud) { + playSound(c, "pickup-mirror", 50); + drawParticles(c, winf[c->wall].color, 16); addMessage(XLAT("%The1 disperses the cloud!", m)); createMirages(c, c->mondir, m); c->wall = waNone; @@ -1149,6 +1183,11 @@ namespace mirror { achievement_count("MIRAGE", nummirage, 0); } + void spingo(int d, bool fwd) { + if(multi::players > 1) spin(d); + go(fwd); + } + } namespace hive { @@ -1181,7 +1220,7 @@ namespace hive { int bugcount[BUGCOLORS]; bool isBugEnemy(cell *c, int k) { - if(c == cwt.c && !invismove) return true; + if(isPlayerOn(c) && !invismove) return true; if(!c->monst) return false; if(c->monst == moBug0+k) return false; if(isIvy(c)) return false; @@ -1367,7 +1406,7 @@ namespace hive { if(isBug(killed)) battlecount++; else if(!fightspam(c2)) addMessage(XLAT("%The1 fights with %the2!", c->monst, c2->monst)); - killOrStunMonster(c2); + attackMonster(c2, AF_ORSTUN, c->monst); // killMonster(c); if(isBug(killed)) { c2->monst = moDeadBug, deadbug.push_back(c2); @@ -1483,7 +1522,7 @@ namespace hive { int d = b.dist[0]; if(d <= 1 && c->wall == waNone) c->item = itRoyalJelly; - c->bardir = NOBARRIERS; + preventbarriers(c); if(d == 9 || d == 6 || d == 3) c->barleft = eLand(d/3), c->barright = eLand(k); @@ -1513,6 +1552,9 @@ namespace heat { double celsius(cell *c) { return absheat(c) * 60; } + // adjust to the improved heat transfer algorithm in 9.4 + const float FIX94 = 1.5; + void processheat(double rate = 1, bool tick = true) { if(markOrb(itOrbSpeed)) rate /= 2; int oldmelt = kills[0]; @@ -1547,22 +1589,30 @@ namespace heat { for(int i=0; iland == laCocytus && shmup::on) ? rate/3 : rate; if(purehepta) xrate *= 1.7; + if(!shmup::on) xrate /= FIX94; if(isIcyLand(c)) HEAT(c) += (markOrb(itOrbWinter) ? -1.2 : 1.2) * xrate; } vinefires.clear(); rosefires.clear(); + + vector& allcells = quotient ? quotientspace::allcells : dcal; + + int dcs = size(allcells); + + vector hmods(dcs, 0); - int dcs = size(dcal); for(int i=0; iland == laCocytus && shmup::on) ? rate/3 : rate; if(purehepta) xrate *= 1.7; - if(c->cpdist > 8) break; + if(!shmup::on) xrate /= FIX94; + if(c->cpdist > 7 && !quotient) break; if(hasTimeout(c)) { if(tick) useup(c); @@ -1584,7 +1634,8 @@ namespace heat { vinefires.push_back(c2); if(c2 && c2->wall == waSmallTree && c2->land != laDryForest) vinefires.push_back(c2); - if(c2 && (c2->wall == waWeakBranch || c2->wall == waCanopy || c2->wall == waTrunk || c2->wall == waSolidBranch)) + if(c2 && (c2->wall == waWeakBranch || c2->wall == waCanopy || c2->wall == waTrunk || c2->wall == waSolidBranch || + c2->wall == waBigBush || c2->wall == waSmallBush)) vinefires.push_back(c2); if(c2 && c2->wall == waBonfireOff) activateActiv(c2, false); // both halfvines have to be near fire at once @@ -1603,37 +1654,41 @@ namespace heat { } } if(isIcyLand(c)) { - if(c->monst == moRanger) HEAT(c) += 3 * xrate; - if(c->monst == moDesertman) HEAT(c) += 4 * xrate; - if(c->monst == moMonkey) HEAT(c) += xrate; - if(c->wall == waDeadTroll) HEAT(c) -= 2 * xrate; - if(c->wall == waDeadTroll2) HEAT(c) -= 1.5 * xrate; - if(c->wall == waBigStatue) HEAT(c) -= .5 * xrate; - if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM) - HEAT(c) += (c->land == laCocytus ? 1.5 : 10) * xrate; - if(c->monst == moGreaterShark) - HEAT(c) += 2 * xrate; - if(c->monst == moCultist) HEAT(c) += 3 * xrate; - if(c->monst == moCultistLeader) HEAT(c) += 4 * xrate; - if(c->monst == moPyroCultist) HEAT(c) += 6 * xrate; - if(c->monst == moFireFairy) HEAT(c) += 6 * xrate; - if(c->monst == moFireElemental) HEAT(c) += 8 * xrate; - if(isDragon(c->monst)) HEAT(c) += 2 * xrate; - if(c->monst == moGhost) HEAT(c) -= xrate; - if(c->monst == moWaterElemental) HEAT(c) -= xrate; - if(isFire(c)) HEAT(c) += 4 * xrate; - if(isPrincess(c->monst)) HEAT(c) += (markEmpathy(itOrbWinter) ? -1.2 : 1.2) * xrate; - - forCellEx(ct, c) if(!isIcyLand(ct) && isFire(ct)) - HEAT(c) += xrate*.1; - ld hmod = 0; + if(c->monst == moRanger) hmod += 3 * xrate; + if(c->monst == moDesertman) hmod += 4 * xrate; + if(c->monst == moMonkey) hmod += xrate; + if(c->wall == waDeadTroll) hmod -= 2 * xrate; + if(c->wall == waDeadTroll2) hmod -= 1.5 * xrate; + if(c->wall == waBigStatue) hmod -= .5 * xrate; + if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM) + hmod += (c->land == laCocytus ? 1.5 : 10) * xrate; + if(c->monst == moGreaterShark) + hmod += 2 * xrate; + if(c->monst == moCultist) hmod += 3 * xrate; + if(c->monst == moCultistLeader) hmod += 4 * xrate; + if(c->monst == moPyroCultist) hmod += 6 * xrate; + if(c->monst == moFireFairy) hmod += 6 * xrate; + if(c->monst == moFireElemental) hmod += 8 * xrate; + if(isDragon(c->monst)) hmod += 2 * xrate; + if(c->monst == moGhost) hmod -= xrate; + if(c->monst == moFriendlyGhost) hmod -= xrate; + if(c->monst == moSkeleton) hmod -= .2 * xrate; + if(c->monst == moDraugr) hmod -= .75 * xrate; + if(c->monst == moWaterElemental) hmod -= xrate; + if(c->monst == moAirElemental) hmod -= .4 * xrate; + if(isFire(c)) hmod += 4 * xrate; + if(isPrincess(c->monst)) hmod += (markEmpathy(itOrbWinter) ? -1.2 : 1.2) * xrate; + + forCellEx(ct, c) if(!isIcyLand(ct) && isFire(ct)) + hmod += xrate*.1; + 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 || markOrb(itOrbWinter))) + if(isPlayerOn(c->mov[j]) && (c->land == laIce || markOrb(itOrbWinter))) hmod += (markOrb(itOrbWinter) ? -1.2 : 1.2) / 4; continue; } @@ -1641,34 +1696,40 @@ namespace heat { hdiff /= 10; if(shmup::on && (c->land == laCocytus || c->mov[j]->land == laCocytus)) hdiff /= 3; - if(c->mov[j]->cpdist <= 7) - HEAT(c->mov[j]) -= hdiff * rate; - else - hdiff = -HEAT(c) / 30; + // if(c->mov[j]->cpdist > 7 && !quotient) hdiff += -HEAT(c) / 30; hmod += hdiff; } - HEAT(c) += hmod * rate; - if(c->monst == moCrystalSage && HEAT(c) >= SAGEMELT) { - addMessage(XLAT("%The1 melts away!", c->monst)); - killWithMessage(c, false); - } + hmods[i] = hmod * rate; } - if(readd || HEAT(c)) + if((readd || HEAT(c)) && !quotient) offscreen.push_back(c); + } + #define MELTCOLOR 0xA04040 + for(int i=0; iwall == waIcewall && HEAT(c) > .4) c->wall = waNone, kills[0]++; - if(c->wall == waFrozenLake && HEAT(c) > (c->land == laCocytus ? .6 : .4)) c->wall = waLake, kills[0]++; + cell *c = allcells[i]; + HEAT(c) += hmods[i] * rate; + if(c->monst == moCrystalSage && HEAT(c) >= SAGEMELT) { + addMessage(XLAT("%The1 melts away!", c->monst)); + fallMonster(c); + } + if(c->wall == waIcewall && HEAT(c) > .4) + drawParticles(c, MELTCOLOR, 4, 60), + c->wall = waNone, kills[0]++; + if(c->wall == waFrozenLake && HEAT(c) > (c->land == laCocytus ? .6 : .4)) + drawParticles(c, MELTCOLOR, 4, 60), + playSound(c, "trapdoor", 50), + c->wall = waLake, kills[0]++; if(c->wall == waLake && HEAT(c) < (c->land == laCocytus ? -.4 : .4) && c->monst != moGreaterShark) { c->wall = waFrozenLake; if(c->monst == moShark || c->monst == moCShark) { addMessage(XLAT("%The1 is frozen!", c->monst)); - killWithMessage(c, false); + fallMonster(c); } } } @@ -1681,6 +1742,8 @@ namespace heat { makeflame(c, 6, false); else if(c->wall == waSolidBranch || c->wall == waTrunk || c->wall == waWeakBranch || c->wall == waCanopy) makeflame(c, 6, false); + else if(c->wall == waBigBush || c->wall == waSmallBush) + makeflame(c, 6, false); else if(cellHalfvine(c)) destroyHalfvine(c, waPartialFire, 6); } @@ -1697,10 +1760,11 @@ namespace heat { } void dryforest() { - int dcs = size(dcal); + vector& allcells = quotient ? quotientspace::allcells : dcal; + int dcs = size(allcells); for(int i=0; icpdist > 8) break; + cell *c = allcells[i]; + if(!quotient && c->cpdist > 8) break; if(c->land != laDryForest) continue; for(int j=0; jtype; j++) if(c->mov[j]) { @@ -1711,8 +1775,8 @@ namespace heat { } for(int i=0; icpdist > 8) break; + cell *c = allcells[i]; + if(!quotient && c->cpdist > 8) break; if(c->land != laDryForest) continue; if((c->wall == waBigTree || c->wall == waSmallTree || isFire(c)) && c->landparam >= 1) c->wall = waEternalFire; @@ -1725,15 +1789,16 @@ bool gardener = false; bool lifebrought = false; // was Life brought to the Dead Caves? void livecaves() { - int dcs = size(dcal); + vector& allcells = quotient ? quotientspace::allcells : dcal; + int dcs = size(allcells); vector bringlife; for(int i=0; icpdist > 8) break; + cell *c = allcells[i]; + if(!quotient && c->cpdist > 8) break; - if(c->wall == waCavefloor || c->wall == waCavewall) { + if(c->wall == waCavefloor || c->wall == waCavewall || c->wall == waDeadTroll) { c->aitmp = 0; if(c->monst == moDarkTroll) c->monst = moTroll; if(c->item || c->monst || c->cpdist == 0) continue; @@ -1749,9 +1814,13 @@ void livecaves() { else if(c->mov[j]->wall == waGargoyleBridge) c->aitmp--; else if(c->mov[j]->wall == waDeadTroll) c->aitmp -= 5; else if(c->mov[j]->wall == waDeadTroll2) c->aitmp -= 3; + else if(c->mov[j]->wall == waPetrified) c->aitmp -= 2; else if(c->mov[j]->wall == waVinePlant) c->aitmp--; else if(chaosmode && c->mov[j]->land != laCaves && c->mov[j]->land != laEmerald) ; + else if(c->mov[j]->land == laTrollheim) ; // trollheim floor does not count else if(c->mov[j]->wall != waBarrier) c->aitmp += 5; + + if(sword::at(c)) c->aitmp += 500; if(c->mov[j]->cpdist == 0 && markOrb(itOrbDigging)) c->aitmp+=100; if(items[itOrbEmpathy] && isFriendly(c->mov[j]) && markEmpathy(itOrbDigging)) @@ -1764,7 +1833,9 @@ void livecaves() { if(c->mov[j]->monst == moGhost) c->aitmp += 10; if(c->mov[j]->monst == moTentacleGhost) c->aitmp += 10; if(c->mov[j]->monst == moFriendlyGhost) c->aitmp += 10; + if(c->mov[j]->monst == moSkeleton) c->aitmp ++; if(c->mov[j]->monst == moGargoyle) c->aitmp--; + if(c->mov[j]->monst == moDraugr) c->aitmp--; if(isDragon(c->mov[j]->monst)) c->aitmp++; if(c->mov[j]->monst == moNecromancer) c->aitmp += 10; if(c->mov[j]->monst == moWormtail) c->aitmp++; @@ -1793,17 +1864,19 @@ void livecaves() { ; else if(c2->wall == waDeadTroll || c2->wall == waDeadTroll2 || c2->wall == waThumperOn || isFire(c2) || snakelevel(c2)) c->aitmp -= 10; + else if(c2->wall == waPetrified) + c->aitmp -= 10; if(c2->wall == waBigStatue) c->aitmp -= 10; if(c2->wall == waSea || c2->wall == waBoat) c->aitmp += (c2->land == laLivefjord ? 1 : 100); if(c2->monst == moWaterElemental) c->aitmp += 1000; - if(c2 == cwt.c && c2->wall == waBoat && markOrb(itOrbWater)) + if(isPlayerOn(c2) && c2->wall == waBoat && markOrb(itOrbWater)) c->aitmp += 1000; if(c2->monst == moEarthElemental) c->aitmp -= 1000; - if(c2 == cwt.c && markOrb(itOrbDigging)) + if(isPlayerOn(c2) && markOrb(itOrbDigging)) c->aitmp -= 1000; if(items[itOrbEmpathy] && isFriendly(c2) && markEmpathy(itOrbDigging)) c->aitmp -= 1000; @@ -1824,8 +1897,9 @@ void livecaves() { } for(int i=0; icpdist > 8) break; + cell *c = allcells[i]; + if(!quotient && c->cpdist > 8) break; + if(c->wall == waCavefloor || c->wall == waCavewall) { // if(c->land != laCaves) continue; // if(c->wall == waThumper || c->wall == waBonfire) continue; @@ -1866,7 +1940,7 @@ void livecaves() { if(c->item == itGreenStone) c->item = itOrbLife; if(c->monst == moEarthElemental) { addMessage(XLAT("%The1 is destroyed by the forces of Life!", c->monst)); - killWithMessage(c, false); + fallMonster(c); c->item = itOrbDigging; } } @@ -1965,15 +2039,18 @@ namespace dragon { int maxlen = 1000; while(maxlen-->0) { cell *c2 = c->mov[c->mondir]; - if(c2 == cwt.c) mountmove(c, c->mondir, true); + mountmove(c, c->mondir, true, c2); c->monst = c2->monst; c->hitpoints = c2->hitpoints; + animateMovement(c2, c, LAYER_BIG); c->stuntime = 2; if(c2->mondir == NODIR) { c->mondir = NODIR; c2->monst = moNone; return; } c = c2; } } + bool dragbugs = false; + cell *findhead(cell *c) { cell *cor = c; int maxlen=1000; @@ -1981,10 +2058,23 @@ namespace dragon { if(maxlen--<0) return c; if(c->monst == moDragonHead) return c; for(int i=0; itype; i++) - if(c->mov[i] && isDragon(c->mov[i]->monst) && c->mov[i]->mondir == c->spn[i]) { + if(c->mov[i] && isDragon(c->mov[i]->monst) && c->mov[i]->mondir == c->spn(i)) { c = c->mov[i]; goto findhead; } - printf("dragon bug #3 (%p -> %p)\n", cor, c); return c; + printf("dragon bug #3 (%p -> %p)\n", cor, c); + dragbugs = true; + c->monst = moDragonHead; return c; + } + + void validate(const char *where) { + dragbugs = false; + for(int i=0; imonst == moDragonTail) + findhead(dcal[i]); + if(dragbugs) { + printf("DRAGON BUG in %s\n", where); + exit(1); + } } int bodypart(cell *c, cell *head) { @@ -2004,15 +2094,21 @@ namespace dragon { return 0; } - void kill(cell *c) { + void kill(cell *c, eMonster who) { int delay = false; kills[moDragonHead]++; int penalty = 0; int maxlen = 1000; while(maxlen-->0) { makeflame(c, 5, false); + eMonster m = c->monst; + drawFireParticles(c, 16); c->monst = moNone; - if(c->wall == waFire) { + if(checkOrb(who, itOrbUndeath)) + c->monst = moFriendlyGhost; + if(checkOrb(who, itOrbStone)) + c->wparam = m, c->wall = waPetrified; + else if(c->wall == waFire) { if(delay) delay = false; else { if(c->land != laDragon) penalty += 3; @@ -2047,20 +2143,29 @@ namespace dragon { #define SWAPBITFIELD(x,y,t) { t bak=x; x=y; y=bak; } void pullfront(cell *c, cell *until) { - cell *buffer = c; int maxlen = 1000; + static vector allcells; + allcells.clear(); while(maxlen-->0) { - SWAPBITFIELD(c->monst, buffer->monst, eMonster); - SWAPBITFIELD(c->hitpoints, buffer->hitpoints, int); - if(c == cwt.c) cwt.c = buffer; - else if(buffer == cwt.c) mountmove(c, c->mondir, true); + allcells.push_back(c); + // SWAPBITFIELD(c->monst, buffer->monst, eMonster); + // SWAPBITFIELD(c->hitpoints, buffer->hitpoints, int); c->stuntime = 2; if(c == until) { - while(true) { - if(c->mondir == NODIR) return; + for(int i=size(allcells)-2; i>=0; i--) { + cell *cmt = allcells[i+1]; + cell *cft = allcells[i]; + cmt->hitpoints = cft->hitpoints; + cmt->monst = cft->monst; + cft->monst = moNone; + mountmove(cmt, cmt->mondir, true, cft); + animateMovement(cft, cmt, LAYER_BIG); + } + while(c->mondir != NODIR) { c = c->mov[c->mondir]; c->stuntime = 2; } + break; } if(c->mondir == NODIR) { printf("dragon bug\n"); break; } c = c->mov[c->mondir]; @@ -2100,3 +2205,525 @@ namespace dragon { } } + +namespace sword { + int angle[MAXPLAYER]; + + cell *pos(cell *c, int s) { + int t = c->type; + s *= 2; + s += S42/t; + s %= S84; + if(s<0) s += S84; + s /= (S84/t); + return c->mov[s]; + } + + eItem orbof(bool rev) { return rev ? itOrbSword2 : itOrbSword; } + int orbcount(bool rev) { return items[orbof(rev)]; } + + cell *pos(int id, bool rev) { + if(!orbcount(rev)) return NULL; + return pos(playerpos(id), angle[id] + (rev ? S21 : 0)); + } + + bool at(cell *where, bool noplayer) { + if(noplayer) return false; + if(!orbcount(0) && !orbcount(1)) return false; + for(int i=0; imirror(s1)) + return ((s2*S42/c2->type - angle + s1*S42/c1->type) + S21) % S42; + else + return ((s2*S42/c2->type - s1*S42/c1->type) + S21 + angle) % S42; + } + + void shuffle() { + for(int i=0; imonst == moKrakenH) return c; + if(c->monst == moKrakenT) return c->mov[c->mondir]; + return NULL; + } + + void kill(cell *c, eMonster who) { + drawParticles(c, minf[moKrakenH].color, 16); + c->monst = moNone; + if(checkOrb(who, itOrbUndeath)) c->monst = moFriendlyGhost; + if(c->land == laKraken && !c->item) c->item = itKraken; + kills[moKrakenH]++; + if(checkOrb(who, itOrbStone)) c->wall = waNone; + for(int i=0; itype; i++) + if(c->mov[i]->monst == moKrakenT) { + drawParticles(c, minf[moKrakenT].color, 16); + c->mov[i]->monst = moNone; + if(checkOrb(who, itOrbStone)) { + if(isWatery(c->mov[i])) + c->mov[i]->wall = waNone; + else + c->mov[i]->wall = waPetrified, c->mov[i]->wparam = moKrakenT; + } + } + } + + int totalhp(cell *c) { + int total = 0; + for(int i=0; itype; i++) + if(c->mov[i]->monst == moKrakenT) + total += c->mov[i]->hitpoints; + return total; + } + + void trymove(cell *c); + + void sleep(cell *c) { + if(c->monst == moKrakenT) c = c->mov[c->mondir]; + c->stuntime = 1; + forCellEx(c2, c) c2->stuntime = 1; + } + + void attacks() { + bool offboat[MAXPLAYER]; + for(int i=0; imonst == moKrakenT && !c->stuntime) forCellEx(c2, c) { + bool dboat = false; + if(c2->monst && canAttack(c, moKrakenT, c2, c2->monst, AF_ONLY_FBUG)) { + attackMonster(c2, AF_ORSTUN | AF_MSG, c->monst); + sleep(c); + } + else for(int i=0; imonst == moKrakenH && !c->stuntime && !isWateryOrBoat(c)) { + int qdir = 0; + cell *ctab[7]; + forCellEx(c2, c) if(isWatery(c2)) ctab[qdir++] = c2; + random_shuffle(ctab, ctab+qdir); + while(qdir--) trymove(ctab[qdir]); + } + } + } + + // c is the tentacle which will be the head after the move + void trymove(cell *c) { + if(pseudohept(c)) return; + cell *c2 = c->mov[c->mondir]; + if(!isWatery(c)) return; + if(againstCurrent(c, c2)) return; + forCellIdEx(c3, i, c) { + if(c3->monst && c3 != c2 && !(c3->mondir >= 0 && c3->mondir < c3->type && + c3->mov[c3->mondir] == c2)) + return; + if(isPlayerOn(c3)) return; + if(sword::at(c3)) return; + if(!passable(c3, c, P_FISH | P_MONSTER)) return; + } + if(c2->stuntime) return; + int hpcount[10]; + forCellIdEx(c3, i, c2) { + hpcount[i] = c3->hitpoints; + c3->monst = moNone; + } + c->monst = moKrakenH; + vector > acells; + acells.push_back(make_pair(c2, c)); + forCellIdEx(c3, i, c) { + c3->monst = moKrakenT, c3->mondir = c->spn(i), + c3->aitmp = sval; + int i0 = (i+c->spn(c->mondir)-c->mondir+99) % c2->type; + c3->hitpoints = hpcount[i0]; + acells.push_back(make_pair(c2->mov[i0], c3)); + if(c3->wall == waBoat) { + addMessage(XLAT("%The1 destroys %the2!", moKrakenH, waBoat)); + c3->wall = waSea; + } + if(c3->wall == waStrandedBoat) { + addMessage(XLAT("%The1 destroys %the2!", moKrakenH, waBoat)); + c3->wall = waNone; + } + } + + while(size(acells)) { + // bool found = false; + for(int i=0; iaitmp = sval; + return; + } + + } + +bool barrierhept(cell *c) { + return c->bardir != NOBARRIERS && c->bardir != NODIR; + } + +namespace prairie { + + using namespace fieldpattern; + + int getfval(cell *c3) { + if(barrierhept(c3)) return btspin(fieldval(c3).first, c3->bardir)+1; + return 0; + } + + void spread(cell *c, cell *from) { + int rd; + + c->LHU.fi.flowerdist = 8; + c->LHU.fi.walldist = 8; + c->LHU.fi.walldist2 = 8; + + if(euclid) { + eucoord x, y; + decodeMaster(c->master, x, y); + c->LHU.fi.rval = (y&15); + } + else if(sphere) { + c->LHU.fi.rval = celldistance(c, cwt.c) + 8 - (purehepta ? 2 : 3); + } + else { + if(!from) { + for(int i=0; ifval = fp43.inverses[i]+1; + } + else if(from && from->land == laPrairie && from->fval) + c->fval = from->fval; + else { + forCellEx(c2, c) if(c2->land == laPrairie && c2->fval) + c->fval = c2->fval; + if(!c->fval) forCellEx(c2, c) if(!c->fval) c->fval = getfval(c2); + if(!c->fval) forCellEx(c2, c) forCellEx(c3, c2) if(!c->fval) c->fval = getfval(c3); + if(!c->fval) { + + int barclose = 0; + + forCellEx(c2, c) if(barrierhept(c2)) barclose++; + + forCellEx(c2, c) forCellEx(c3, c2) + if(barrierhept(c3)) barclose++; + + printf("c = %p bc = %d\n", c, barclose); + + raiseBuggyGeneration(c, "could not set river fval"); + } + } + pair fv = fieldpattern::fieldval(c); + fv = fp43.gmul(fv, fp43.inverses[c->fval-1]); + + rd = fp43.getdist(fv, fp43.distriver); + int rl = fp43.getdist(fv, fp43.distriverleft); + int rr = fp43.getdist(fv, fp43.distriverright); + + c->LHU.fi.flowerdist = fp43.getdist(fv, fp43.distflower); + c->LHU.fi.walldist = fp43.getdist(fv, fp43.distwall); + c->LHU.fi.walldist2 = fp43.getdist(fv, fp43.distwall2); + + c->LHU.fi.rval = 0; + if(rd <= 7 && rl < rr) + c->LHU.fi.rval = 8 + rd; + if(rd <= 7 && rl > rr) + c->LHU.fi.rval = 7 - rd; + } + + if(c->LHU.fi.flowerdist == 0 && c->type != 6) c->item = itOrbSafety; + + if(c->LHU.fi.walldist == 0) c->wall = waBarrier; + + if(0) if(c->type == 7) for(int i=0; i<7; i++) { + eItem m = fp43.markers[fieldpattern::btspin(c->master->fieldval,i)]; + if(m) { + if(c->item) c->item = itBuggy2; + else c->item = m, c->mondir = i; + if(c->wall == waSea) c->wall = waBoat; + } + } + } + + #define RLOW (sphere?(purehepta?7:6):purehepta?4:2) + #define RHIGH (sphere?(purehepta?8:9):purehepta?11:13) + + bool isriver(cell *c) { + return c->land == laPrairie && c->LHU.fi.rval <= RHIGH && c->LHU.fi.rval >= RLOW; + } + + bool mainriver(cell *c) { + return c->LHU.fi.rval <= 8 && c->LHU.fi.rval >= 7; + } + + bool nearriver(cell *c) { + return c->LHU.fi.rval == RHIGH+1 || c->LHU.fi.rval == RLOW-1; + } + + cell *enter; + + bool opposite(cell *c) { + return (c->LHU.fi.rval ^ enter->LHU.fi.rval) & 8; + } + + bool isleft(cell *c) { + return c->LHU.fi.rval & 8; + } + + int towerleft(cell *c) { + return c->LHU.fi.rval; + } + + int towerright(cell *c) { + return 15^c->LHU.fi.rval; + } + + cell *next(cell *c, int pv=1) { + for(int i=0; itype; i++) { + cell *c1 = createMov(c, i); + cell *c2 = createMov(c, (i+pv+c->type)%c->type); + if(c1 && c1->LHU.fi.rval == c->LHU.fi.rval) + if(c2 && c2->LHU.fi.rval == c->LHU.fi.rval+1) + if(isNeighbor(c1,c2)) + return c1; + } + return NULL; + } + + cell *prev(cell *c) { return next(c, -1); } + + int beastdist(cell *c, int dir) { + cell *cx = c; int n=0; + while(true) { + if(cx->monst == moHerdBull) return n; + cx = next(cx, dir); + n++; + if(!cx || n >= 1000) return 1000; + } + } + + vector beaststogen; + + void generateBeast(cell *c) { + int beastdistance = min(beastdist(c, 1), beastdist(c, -1)); + if(hrand(1000) >= 15 * beastdistance + 2 * items[itGreenGrass]) return; + c->monst = moHerdBull; + cell *c2 = prev(c); + if(c2) c->mondir = neighborId(c, c2); + } + + void moveAt(cell *c) { + if(eq(c->aitmp, sval)) return; + vector whirlline; + whirlline.push_back(c); + c->aitmp = sval; + cell *c2 = prev(c); + while(c2 && !eq(c2->aitmp, sval)) { + whirlline.push_back(c2), c2->aitmp = sval; + c2 = prev(c2); + // in sphere/quotient geometries, never break before a bull + if((sphere || quotient) && !c2->monst) break; + } + reverse(whirlline.begin(), whirlline.end()); + c2 = next(c); + while(c2 && !eq(c2->aitmp, sval)) whirlline.push_back(c2), c2->aitmp = sval, c2 = next(c2); + int qty = size(whirlline); + // for(int i=0; iaitmp = sval; + if(shmup::on) { + for(int i=0; icpdist <= 7) { + generateBeast(whirlline[i]); + break; + } + } + else { + c2 = whirlline[qty-1]; + if(c2->monst == moHerdBull) c2->monst = moNone; + if(!shmup::on) for(int q=qty-2; q>=0; q--) { + cell *cp = whirlline[q]; + cell *cn = whirlline[q+1]; + if(cp->monst == moHerdBull && !cp->stuntime) { + forCellEx(c2, cp) { + int flags = AF_GETPLAYER | AF_BULL | AF_ORSTUN; + if(c2 != cn) flags |= AF_ONLY_FBUG; + if(canAttack(c, moHerdBull, c2, c2->monst, flags)) + attackMonster(c2, flags | AF_MSG, moHerdBull); + } + + if(!cn->monst && !isPlayerOn(cn) && passable_for(cp->monst, cn, cp, P_DEADLY)) + moveMonster(cn, cp); + else { + playSound(NULL, "hit-axe"+pick123()); + beastcrash(cn, cp); + cp->monst = moRagingBull; + cp->stuntime = 3; + cp->mondir = NODIR; + } + } + } + if(!sphere && !quotient) generateBeast(whirlline[0]); + } + } + + void move() { + sval++; + for(int i=0; i tchoices; + + cell *lasttreasure; + + // vector orbs; + + bool thisriver(cell *c) { + forCellEx(c2, c) if(c2->mpdist < c->mpdist && !isriver(c2)) return false; + forCellEx(c2, c) if(c2->mpdist < c->mpdist) return thisriver(c2); + return true; + } + + void generateTreasure(cell *c) { +// if(nearriver(c) && op + if(enter && nearriver(c) && opposite(c) && thisriver(c)) { + int hr = hrand(100); + if(hr == 0 && items[itGreenGrass] >= 10) { + c->item = itOrbBull; + // orbs.push_back(c); + } + else if(hr < 1+PRIZEMUL) { + placePrizeOrb(c); + // if(c->item) orbs.push_back(c); + } + else tchoices.push_back(c); + } + } + + void treasures() { + if(enter && !isriver(cwt.c)) enter = NULL; + else if(!enter && isriver(cwt.c)) enter = cwt.c; + if(size(tchoices)) { + if(lasttreasure && lasttreasure->item == itGreenGrass) { + if(celldistance(lasttreasure, cwt.c) >= (purehepta ? 7 : 10)) { + lasttreasure->item = itNone; + forCellEx(c2, lasttreasure) if(c2->item == itGreenGrass) c2->item = itNone; + } + else { tchoices.clear(); return; } + } + if(size(tchoices) < 3) { tchoices.clear(); return; } + lasttreasure = tchoices[hrand(size(tchoices))]; + lasttreasure->item = itGreenGrass; + for(int i=0; iitem = itGreenGrass; + tchoices.clear(); + } + } + } + +namespace ca { + ld prob = .2; + string carule[8][2]; + + void init() { + // hexagonal variant of Game of Life, as suggested by Wikipedia + for(int i=0; i<8; i++) + carule[i][0] = "00100000", + carule[i][1] = "00011000"; + } + +#ifdef USE_COMMANDLINE + bool readArg() { + using namespace arg; + if(argis("-caprob")) { + shift(); prob = argf(); + return true; + } + if(args()[0] != '-') return false; + if(args()[1] != 'c') return false; + int livedead = args()[2] - '0'; + if(livedead < 0 || livedead > 1) return false; + int nei = -1; + if(args()[3]) { + nei = args()[3] - '0'; + if(nei < 0 || nei > 7) return false; + if(args()[4]) return false; + } + shift(); + string s = args(); s += "00000000"; + if(nei == -1) for(int i=0; i<8; i++) + carule[i][livedead] = s; + else + carule[nei][livedead] = s; + return true; + } +#endif + + void simulate() { + if(cwt.c->land != laCA) return; + vector& allcells = quotient ? quotientspace::allcells : dcal; + int dcs = size(allcells); + bool willlive[dcs]; + for(int i=0; iland != laCA) return; + int nei = 0, live = 0; + forCellEx(c2, c) if(c2->land == laCA) { + nei++; if(c2->wall == waFloorA) live++; + } + int welive = 0; if(c->wall == waFloorA) welive++; + willlive[i] = carule[nei][welive][live] == '1'; + } + for(int i=0; iwall = willlive[i] ? waFloorA : waNone; + } + } + } diff --git a/conformal.cpp b/conformal.cpp index a4571f68..7ee04c8b 100644 --- a/conformal.cpp +++ b/conformal.cpp @@ -4,11 +4,10 @@ namespace polygonal { - typedef long double ld; - typedef complex cld; + typedef complex cld; int SI = 4; - double STAR = 0; + ld STAR = 0; int deg = 20; @@ -55,7 +54,7 @@ namespace polygonal { } pair compute(ld x, ld y, int prec) { - if(pmodel == 4) { + if(pmodel == mdPolynomial) { cld z(x,y); cld res (0,0); for(int i=maxcoef; i>=0; i--) { res += coef[i]; if(i) res *= z; } @@ -77,28 +76,21 @@ namespace polygonal { pair compute(ld x, ld y) { return compute(x,y,deg); } void drawBoundary(int color) { - #ifdef GL - if(vid.usingGL) { - qglcoords = 0; - glcolor(color); - int pts = 0; - for(int r=0; r<2000; r++) { - cld z = exp(cld(0, 2*M_PI * r / 2000.0)); - pair z2 = compute(real(z), imag(z), deg); - glcoords[pts][0] = vid.radius * z2.first; - glcoords[pts][1] = vid.radius * z2.second; - glcoords[pts][2] = vid.scrdist; - pts++; - } - - glVertexPointer(3, GL_FLOAT, 0, glcoords); - glEnableClientState(GL_VERTEX_ARRAY); - glDrawArrays(GL_LINE_LOOP, 0, pts); - return; + queuereset(mdDisk, PPR_CIRCLE); + + for(int r=0; r<=2000; r++) { + cld z = exp(cld(0, 2*M_PI * r / 2000.0)); + pair z2 = compute(real(z), imag(z), deg); + hyperpoint h; + h[0] = z2.first * vid.radius; + h[1] = z2.second * vid.radius; + h[2] = vid.scrdist; + curvepoint(h); } - #endif + + queuecurve(color, 0, PPR_CIRCLE); + queuereset(pmodel, PPR_CIRCLE); } - } @@ -247,7 +239,7 @@ namespace conformal { vector movehistory; bool includeHistory; - double lvspeed = 1; + ld lvspeed = 1; int bandhalf = 200; int bandsegment = 16000; int rotation = 0; @@ -368,10 +360,9 @@ namespace conformal { shmup::calc_relative_matrix(v[j+1]->base, v[j]->base->master) * v[j+1]->at * C0; - int x, y, shift; - getcoord(next, x, y, shift); - - tpixels += x-vid.xcenter; + hyperpoint nextscr; + applymodel(next, nextscr); + tpixels += nextscr[0] * vid.radius; } vid.radius = rad; @@ -447,7 +438,7 @@ namespace conformal { v[j+1]->at * C0; int x, y, shift; - getcoord(next, x, y, shift); + getcoord0(next, x, y, shift); int bwidth = x-bandhalf; @@ -489,62 +480,83 @@ namespace conformal { } #endif - const char* directions[5][4] = { + const char* directions[MODELCOUNT][4] = { { "right", "up", "left", "down" }, { "counterclockwise", "zoom out", "clockwise", "zoom in" }, { "left to right", "spin down", "right to left", "spin up" }, { "right", "up", "left", "down" }, + { "right", "up", "left", "down" }, + { "right", "up", "left", "down" }, + { "right", "up", "left", "down" }, + { "right", "up", "left", "down" }, { "right", "up", "left", "down" } }; - const char *modelnames[5] = { - "disk", "half-plane", "band", "polygonal", "polynomial" + const char *modelnames[MODELCOUNT] = { + "disk", "half-plane", "band", "polygonal", "polynomial", + "azimuthal equidistant", "azimuthal equi-area", + "ball model", "hyperboloid" }; void show() { - displayStat( 0, XLAT("conformal/history mode"), "", ' '); + dialog::init(XLAT("conformal/history mode")); - displayStat( 2, XLAT("include history"), ONOFF(includeHistory), 'i'); + dialog::addBoolItem(XLAT("include history"), (includeHistory), 'i'); + + bool notconformal = (pmodel >= 5 && pmodel <= 6) || abs(vid.alpha-1) > 1e-3; - displayStat( 4, XLAT("model used"), modelnames[pmodel], 'm'); - displayStat( 5, XLAT("rotation"), directions[pmodel][rotation&3], 'r'); + dialog::addSelItem(notconformal ? XLAT("model used (not conformal!)") : XLAT("model used"), XLAT(modelnames[pmodel]), 'm'); + dialog::addSelItem(XLAT("rotation"), directions[pmodel][rotation&3], 'r'); if(pmodel == 4) { - displayStat( 6, XLAT("coefficient"), - fts4(real(polygonal::coef[polygonal::coefid]))+"+"+ - fts4(imag(polygonal::coef[polygonal::coefid]))+"i", 'x'); - displayStat( 7, XLAT("which coefficient"), its(polygonal::coefid), 'n'); + dialog::addSelItem(XLAT("coefficient"), + fts4(real(polygonal::coef[polygonal::coefid])), 'x'); + dialog::addSelItem(XLAT("coefficient (imaginary)"), + fts4(imag(polygonal::coef[polygonal::coefid])), 'y'); + dialog::addSelItem(XLAT("which coefficient"), its(polygonal::coefid), 'n'); } if(pmodel == 3) { - displayStat( 6, XLAT("polygon sides"), its(polygonal::SI), 'x'); - displayStat( 7, XLAT("star factor"), fts(polygonal::STAR), 'y'); - displayStat( 8, XLAT("degree of the approximation"), its(polygonal::deg), 'n'); + dialog::addSelItem(XLAT("polygon sides"), its(polygonal::SI), 'x'); + dialog::addSelItem(XLAT("star factor"), fts(polygonal::STAR), 'y'); + dialog::addSelItem(XLAT("degree of the approximation"), its(polygonal::deg), 'n'); } - displayStat(10, XLAT("prepare the line animation"), ONOFF(on), 'e'); - if(on) displayStat(11, XLAT("animation speed"), fts(lvspeed), 'a'); + dialog::addBoolItem(XLAT("prepare the line animation"), (on), 'e'); + if(on) dialog::addSelItem(XLAT("animation speed"), fts(lvspeed), 'a'); #ifndef MOBILE - displayStat(13, XLAT("render bands automatically"), ONOFF(autoband), 'o'); + dialog::addBoolItem(XLAT("render bands automatically"), (autoband), 'o'); if(autoband) - displayStat(14, XLAT("include history when auto-rendering"), ONOFF(autobandhistory), 'j'); + dialog::addBoolItem(XLAT("include history when auto-rendering"), (autobandhistory), 'j'); bool renderable = on && pmodel == 2; if(renderable || autoband) { - displayStat(15, XLAT("band width"), its(bandhalf*2), 'd'); - displayStat(16, XLAT("length of a segment"), its(bandsegment), 's'); - displayStat(17, XLAT("spiral on rendering"), ONOFF(dospiral), 'g'); + dialog::addSelItem(XLAT("band width"), "2*"+its(bandhalf), 'd'); + dialog::addSelItem(XLAT("length of a segment"), its(bandsegment), 's'); + dialog::addBoolItem(XLAT("spiral on rendering"), (dospiral), 'g'); if(renderable) - displayStat(18, XLAT("render now (length: %1)", its(measureLength())), "", 'f'); + dialog::addItem(XLAT("render now (length: %1)", its(measureLength())), 'f'); } #endif - displayStat(20, XLAT("exit this menu"), "", 'q'); + dialog::addItem(XLAT("exit this menu"), 'q'); + dialog::display(); mouseovers = XLAT("see http://www.roguetemple.com/z/hyper/conformal.php"); } - void handleKey(int uni, int sym) { + int ib = 0; + ld compbuf; + void applyIB() { + using namespace polygonal; + cld& tgt = coef[coefid]; + if(ib == 1) tgt = cld(compbuf, imag(tgt)); + if(ib == 2) tgt = cld(real(tgt), compbuf); + } + + void handleKey(int sym, int uni) { + dialog::handleNavigation(sym, uni); + ib = 0; if(uni == 'e') { if(on) clear(); @@ -559,38 +571,53 @@ namespace conformal { } else if(uni == 'o') autoband = !autoband; - else if(uni == 'm') { - pmodel++; - pmodel %= 5; - if(pmodel == 3) polygonal::solve(); + else if(uni == 'm' || uni == 'M') { + + switchagain: { + pmodel = eModel((pmodel + (shiftmul > 0 ? 1 : -1) + MODELCOUNT) % MODELCOUNT); + if(sphere) + if(pmodel == mdHalfplane || pmodel == mdBand || pmodel == mdEquidistant || pmodel == mdEquiarea) + goto switchagain; + } + if(pmodel == mdPolygonal) polygonal::solve(); /* if(pmodel && vid.usingGL) { addMessage(XLAT("openGL mode disabled")); vid.usingGL = false; setvideomode(); } */ } - else if(sym == 'x' && pmodel == 3) { polygonal::SI += (shiftmul > 0 ? 1:-1); polygonal::solve(); } - else if(sym == 'y' && pmodel == 3) { polygonal::STAR += shiftmul/10; polygonal::solve(); } - else if(sym == 'n' && pmodel == 3) { polygonal::deg += (shiftmul>0?1:-1); - if(polygonal::deg < 2) polygonal::deg = 2; - if(polygonal::deg > MSI-1) polygonal::deg = MSI-1; + else if(sym == 'x' && pmodel == mdPolygonal) + dialog::editNumber(polygonal::SI, 3, 10, 1, 4, XLAT("polygon sides"), ""); + else if(sym == 'y' && pmodel == mdPolygonal) + dialog::editNumber(polygonal::STAR, -1, 1, .1, 0, XLAT("star factor"), ""); + else if(sym == 'n' && pmodel == mdPolygonal) + dialog::editNumber(polygonal::deg, 2, MSI-1, 1, 2, XLAT("degree of the approximation"), ""); + else if(sym == 'x' && pmodel == mdPolynomial) { + polygonal::maxcoef = max(polygonal::maxcoef, polygonal::coefid); + int ci = polygonal::coefid + 1; + compbuf = real(polygonal::coef[polygonal::coefid]); + dialog::editNumber(compbuf, -10, 10, .01/ci/ci, 0, XLAT("coefficient"), ""); + ib = 1; } - else if(sym == 'x' && pmodel == 4) { - int ci = polygonal::coefid; - polygonal::coef[polygonal::coefid] += polygonal::cld(shiftmul/100/ci/ci, 0); + else if(sym == 'y' && pmodel == mdPolynomial) { + polygonal::maxcoef = max(polygonal::maxcoef, polygonal::coefid); + int ci = polygonal::coefid + 1; + compbuf = imag(polygonal::coef[polygonal::coefid]); + dialog::editNumber(compbuf, -10, 10, .01/ci/ci, 0, XLAT("coefficient (imaginary)"), ""); + ib = 2; } - else if(sym == 'y' && pmodel == 4) { - int ci = polygonal::coefid; - polygonal::coef[polygonal::coefid] += polygonal::cld(0, shiftmul/100/ci/ci); - } - else if(sym == 'n' && pmodel == 4) { polygonal::coefid += (shiftmul>0?1:-1); polygonal::maxcoef = max(polygonal::maxcoef, polygonal::coefid); } + else if(sym == 'n' && pmodel == mdPolynomial) + dialog::editNumber(polygonal::coefid, 0, MSI-1, 1, 0, XLAT("which coefficient"), ""); else if(sym == 'r') rotation += (shiftmul > 0 ? 1:3); - else if(sym == 'a') { lvspeed += shiftmul/10; } - else if(sym == 'd') { bandhalf += int(5 * shiftmul); if(bandhalf < 5) bandhalf = 5; } - else if(sym == 's') { bandsegment += int(500 * shiftmul); if(bandsegment < 500) bandsegment = 500; } + else if(sym == 'a') + dialog::editNumber(lvspeed, -5, 5, .1, 1, XLAT("animation speed"), ""); + else if(sym == 'd') + dialog::editNumber(bandhalf, 5, 1000, 5, 200, XLAT("band width"), ""); + else if(sym == 's') + dialog::editNumber(bandsegment, 500, 32000, 500, 16000, XLAT("band segment"), ""); else if(sym == 'g') { dospiral = !dospiral; } #ifndef MOBILE - else if(uni == 'f' && pmodel == 2 && on) createImage(dospiral); + else if(uni == 'f' && pmodel == mdBand && on) createImage(dospiral); #endif else if(sym == 'q' || sym == SDLK_ESCAPE || sym == '0') { cmode = emNormal; } else if(sym == 'i') { @@ -646,10 +673,10 @@ namespace conformal { #ifndef MOBILE if(celldist(cwt.c) <= 7) return; if(!autoband) return; - int spm = pmodel; + eModel spm = pmodel; bool ih = includeHistory; includeHistory = autobandhistory; - pmodel = 2; + pmodel = mdBand; create(); createImage(dospiral); clear(); diff --git a/dialogs.cpp b/dialogs.cpp new file mode 100644 index 00000000..ed8a40f3 --- /dev/null +++ b/dialogs.cpp @@ -0,0 +1,727 @@ +/* Missing. + +#ifndef MOBILE + dialog::addItemHelp(16, XLAT("use Shift to decrease and Ctrl to fine tune ")); + dialog::addItemHelp(17, XLAT("(e.g. Shift+Ctrl+Z)")); +#endif + + if(xuni == 'i') { + } + + +*/ + +namespace dialog { + + namespace zoom { + int zoomf = 1, shiftx, shifty; + bool zoomoff = false; + }; + +#ifdef MENU_SCALING +#ifndef MOBILE + void handleZooming(SDL_Event &ev) { + using namespace zoom; + if(zoomoff || (cmode != emOverview && cmode != emTactic)) { + zoomf = 1; shiftx = shifty = 0; zoomoff = false; return; + } + if(ev.type == SDL_MOUSEBUTTONDOWN) { + zoomf = 3; + } + if(zoomf == 3) { + shiftx = -2*mousex; + shifty = -2*mousey; + } + if(ev.type == SDL_MOUSEBUTTONUP && zoomf > 1) { + zoomoff = true; + } + } +#endif +#else + inline void handleZooming(SDL_Event &ev) {} +#endif + + bool displayzoom(int x, int y, int b, int size, const string &s, int color, int align) { + using namespace zoom; + return displayfr(x * zoomf + shiftx, y * zoomf + shifty, b, size * zoomf, s, color, align); + } + + vector items; + + item& lastItem() { return items[items.size() - 1]; } + + void init() { items.clear(); } + + string keyname(int k) { + if(k == 0) return ""; + if(k == SDLK_ESCAPE) return "Esc"; + if(k == SDLK_F5) return "F5"; + if(k == SDLK_F10) return "F10"; + if(k == SDLK_HOME) return "Home"; + if(k == 32) return "space"; + if(k >= 1 && k <= 26) { string s = "Ctrl+"; s += (k+64); return s; } + if(k < 128) { string s; s += k; return s; } + return "?"; + } + + void addSlider(double d1, double d2, double d3, int key) { + item it; + it.type = diSlider; + it.color = 0xC0C0C0; + it.scale = 100; + it.key = key; + it.param = (d2-d1) / (d3-d1); + items.push_back(it); + } + + void addSelItem(string body, string value, int key) { + item it; + it.type = diItem; + it.body = body; + it.value = value; + it.keycaption = keyname(key); + it.key = key; + it.color = 0xC0C0C0; + it.colork = 0x808080; + it.colorv = 0x80A040; + it.colors = 0xFFD500; + it.colorc = 0xFFD500; + it.colors = 0xFF8000; + if(value == ONOFF(true)) it.colorv = 0x40FF40; + if(value == ONOFF(false)) it.colorv = 0xC04040; + it.scale = 100; + items.push_back(it); + } + + void addBoolItem(string body, bool value, int key) { + addSelItem(body, ONOFF(value), key); + } + + void addColorItem(string body, int value, int key) { + item it; + it.type = diItem; + it.body = body; + it.value = COLORBAR; + it.keycaption = keyname(key); + it.key = key; + it.color = it.colorv = value >> 8; + it.colors = it.color ^ 0x404040; + it.colorc = it.color ^ 0x808080; + it.scale = 100; + items.push_back(it); + } + + void addHelp(string body) { + item it; + it.type = diHelp; + it.body = body; + it.scale = 100; + + if(size(body) >= 500) it.scale = 70; + + items.push_back(it); + } + + void addInfo(string body, int color) { + item it; + it.type = diInfo; + it.body = body; + it.color = color; + it.scale = 100; + items.push_back(it); + } + + void addItem(string body, int key) { + item it; + it.type = diItem; + it.body = body; + it.keycaption = keyname(key); + it.key = key; + it.color = 0xC0C0C0; + it.colork = 0x808080; + it.colors = 0xFFD500; + it.colorc = 0xFF8000; + it.scale = 100; + items.push_back(it); + } + + void addBreak(int val) { + item it; + it.type = diBreak; + it.scale = val; + items.push_back(it); + } + + void addTitle(string body, int color, int scale) { + item it; + it.type = diTitle; + it.body = body; + it.color = color; + it.scale = scale; + items.push_back(it); + } + + void init(string title, int color, int scale, int brk) { + init(); + addTitle(title, color, scale); + addBreak(brk); + } + + int displayLong(string str, int siz, int y, bool measure) { + + int last = 0; + int lastspace = 0; + + int xs = vid.xres * 618/1000; + int xo = vid.xres * 186/1000; + + for(int i=0; i<=size(str); i++) { + int ls = 0; + int prev = last; + if(str[i] == ' ') lastspace = i; + if(textwidth(siz, str.substr(last, i-last)) > xs) { + if(lastspace == last) ls = i-1, last = i-1; + else ls = lastspace, last = ls+1; + } + if(str[i] == 10 || i == size(str)) ls = i, last = i+1; + if(ls) { + if(!measure) displayfr(xo, y, 2, siz, str.substr(prev, ls-prev), 0xC0C0C0, 0); + if(ls == prev) y += siz/2; + else y += siz; + lastspace = last; + } + + } + + y += siz/2; + return y; + } + + int tothei, dialogwidth, dfsize, dfspace, leftwidth, rightwidth, innerwidth, itemx, keyx, valuex; + + string highlight_text; + + void measure() { + tothei = 0; + dialogwidth = 0; + innerwidth = 0; + int N = items.size(); + for(int i=0; i vid.yres - 5 * vid.fsize) { + int adfsize = int(dfsize * sqrt((vid.yres - 5. * vid.fsize) / tothei)); + if(adfsize < dfsize-1) dfsize = adfsize + 1; + else dfsize--; + dfspace = dfsize * 5/4; + measure(); + } + while(dialogwidth > vid.xres) { + int adfsize = int(dfsize * sqrt(vid.xres * 1. / dialogwidth)); + if(adfsize < dfsize-1) dfsize = adfsize + 1; + else dfsize--; // keep dfspace + measure(); + } + + tothei = (vid.yres - tothei) / 2; + + for(int i=0; i= top && mousey < tothei); +#ifdef MOBILE + if(xthis && mousepressed) + I.color = I.colorc; +#else + if(xthis && mousemoved) { + highlight_text = I.body; + mousemoved = false; + } + if(highlight_text == I.body) { + I.color = (xthis&&mousepressed&&actonrelease) ? I.colorc : I.colors; + } +#endif + if(!mousing) + displayfr(keyx, mid, 2, dfsize * I.scale/100, I.keycaption, I.colork, 16); + displayfr(itemx, mid, 2, dfsize * I.scale/100, I.body, I.color, 0); + displayfr(valuex, mid, 2, dfsize * I.scale/100, I.value, I.colorv, 0); + if(xthis) getcstat = I.key; + } + else if(I.type == diSlider) { + bool xthis = (mousey >= top && mousey < tothei); + displayfr(vid.xres*1/4, mid, 2, dfsize * I.scale/100, "(", I.color, 16); + displayfr(vid.xres*1/4 + double(vid.xres/2 * I.param), mid, 2, dfsize * I.scale/100, "#", I.color, 8); + displayfr(vid.xres*3/4, mid, 2, dfsize * I.scale/100, ")", I.color, 0); + if(xthis) getcstat = I.key, inslider = true; + } + } + } + + void handleNavigation(int &sym, int &uni) { +#ifndef MOBILE + if(uni == '\n' || uni == '\r' || sym == SDLK_KP5) + for(int i=0; i> ash); + vid.fsize /= 2; + } + + if(palette) { + int q = palette[0]; + for(int i=0; i> ash); + vid.fsize /= 2; + } + } + + for(int i=0; i<4; i++) { + int y = vid.yres / 2 + (2-i) * vid.fsize * 2; + + displayColorButton(vid.xres / 4, y, "(", 0, 16, 0, 0xFFFFFF); + string rgt = ") "; rgt += "ABGR" [i]; + displayColorButton(vid.xres * 3/4, y, rgt, 0, 0, 0, 0xFFFFFF); + displayColorButton(vid.xres /4 + vid.xres * ((color >> (8*i)) & 0xFF) / 510, y, "#", 0, 8, 0, 0xFFFFFF); + + if(mousey >= y - vid.fsize && mousey < y + vid.fsize) + getcstat = 'A' + i, inslider = true; + } + + displayColorButton(vid.xres/2, vid.yres/2+vid.fsize * 6, XLAT("select this color") + " : " + itsh(color), ' ', 8, 0, color >> ash); + } + + // 0: nothing happened, 1: color accepted, 2: break + int handleKeyColor(int sym, int uni, int& color) { + + if(uni >= 'A' && uni <= 'D') { + int x = (mousex - vid.xres/4) * 510 / vid.xres; + if(x < 0) x = 0; + if(x > 255) x = 255; + unsigned char* pts = (unsigned char*) &color; + pts[uni - 'A'] = x; + } + else if(uni == ' ') { + bool inHistory = false; + for(int i=0; i<10; i++) if(colorhistory[i] == (unsigned) color) + inHistory = true; + if(!inHistory) { colorhistory[lch] = color; lch++; lch %= 10; } + return 1; + } + else if(uni >= '0' && uni <= '9') { + color = colorhistory[uni - '0']; + } + else if(palette && uni >= 'a' && uni < 'a'+(int) palette[0]) { + color = palette[1 + uni - 'a']; + } + else if(uni || sym == SDLK_F10) return 2; + return 0; + } + + int *colorPointer; + emtype lastmode; + + void openColorDialog(int& col, unsigned int *pal) { + colorPointer = &col; palette = pal; + lastmode = cmode; cmode = emColor; + } + + void handleColor(int sym, int uni) { + int ret = handleKeyColor(sym, uni, *colorPointer); + if(ret) cmode = lastmode; + } + + struct numberEditor { + ld *editwhat; + string s; + ld vmin, vmax, step, dft; + string title, help; + ld (*scale) (ld); + ld (*inverse_scale) (ld); + int *intval; ld intbuf; + bool positive; + } ne; + + bool editingDetail() { + return ne.editwhat == &geom3::highdetail || ne.editwhat == &geom3::middetail; + } + + ld identity(ld x) { return x; } + + void scaleSinh() { + ne.scale = ASINH; + ne.inverse_scale = sinh; + } + + void scaleLog() { + ne.scale = log; + ne.inverse_scale = exp; + ne.positive = true; + } + + void editNumber(ld& x, ld vmin, ld vmax, ld step, ld dft, string title, string help) { + ne.editwhat = &x; + ne.s = fts(x); + ne.vmin = vmin; + ne.vmax = vmax; + ne.step = step; + ne.dft = dft; + ne.title = title; + ne.help = help; + lastmode = cmode; cmode = emNumber; + ne.scale = ne.inverse_scale = identity; + ne.intval = NULL; + ne.positive = false; + } + + void editNumber(int& x, int vmin, int vmax, int step, int dft, string title, string help) { + editNumber(ne.intbuf, vmin, vmax, step, dft, title, help); + ne.intbuf = x; ne.intval = &x; ne.s = its(x); + } + + string disp(ld x) { if(ne.intval) return its((int) (x+.5)); else return fts(x); } + + void affect(char kind) { + + if(ne.intval) { + if(kind == 's') sscanf(ne.s.c_str(), "%d", ne.intval), *ne.editwhat = *ne.intval; + if(kind == 'v') *ne.intval = (int) (*ne.editwhat + .5), ne.s = its(*ne.intval); + } + else { + if(kind == 's') { + ld x; + sscanf(ne.s.c_str(), LDF, &x); + if(ne.positive && x <= 0) return; + *ne.editwhat = x; + } + if(kind == 'v') ne.s = fts(*ne.editwhat); + } + +#ifndef NOAUDIO + if(ne.intval == &musicvolume) { + if(musicvolume < 0) + *ne.editwhat = musicvolume = 0, affect('v'); + else if(musicvolume > MIX_MAX_VOLUME) + *ne.editwhat = musicvolume = MIX_MAX_VOLUME, affect('v'); +#ifdef SDLAUDIO + else Mix_VolumeMusic(musicvolume); +#endif +#ifdef ANDROID + settingsChanged = true; +#endif + } + + if(ne.intval == &effvolume) { + if(effvolume < 0) + *ne.editwhat = effvolume = 0, affect('v'); + else if(effvolume > MIX_MAX_VOLUME) + *ne.editwhat = effvolume = MIX_MAX_VOLUME, affect('v'); +#ifdef ANDROID + settingsChanged = true; +#endif + } +#endif + + if(ne.intval == &vid.framelimit && vid.framelimit < 5) + *ne.editwhat = vid.framelimit = 5, affect('v'); + +#ifdef MOBILE + if(ne.intval == &fontscale && fontscale < 50) + *ne.editwhat = fontscale = 50, affect('v'); +#endif + + if(ne.intval == &sightrange && sightrange < 4) + *ne.editwhat = sightrange = 4, affect('v'); + + int msr = cheater ? 15 : 7; + + if(ne.intval == &sightrange && sightrange > msr) + *ne.editwhat = sightrange = msr, affect('v'); + + if(ne.intval == &conformal::bandhalf && conformal::bandhalf < 5) + *ne.editwhat = *ne.intval = 5, affect('v'); + + if(ne.intval == &conformal::bandsegment && conformal::bandsegment < 500) + *ne.editwhat = *ne.intval = 500, affect('v'); + + if(ne.intval == &polygonal::coefid && polygonal::coefid < 0) + *ne.editwhat = *ne.intval = 0, affect('v'); + + if(ne.intval == &polygonal::coefid && polygonal::coefid >= MSI) + *ne.editwhat = *ne.intval = MSI-1, affect('v'); + + if(ne.intval == &polygonal::deg && polygonal::deg < 0) + *ne.editwhat = *ne.intval = MSI-1, affect('v'); + + if(ne.intval == &polygonal::deg && polygonal::deg >= MSI) + *ne.editwhat = *ne.intval = MSI-1, affect('v'); + + if(ne.intval == &polygonal::SI) polygonal::solve(); + if(ne.editwhat == &polygonal::STAR) polygonal::solve(); + + conformal::applyIB(); + + if(ne.editwhat == &geom3::highdetail && geom3::highdetail > geom3::middetail) + geom3::middetail = geom3::highdetail; + + if(ne.editwhat == &geom3::middetail && geom3::highdetail > geom3::middetail) + geom3::highdetail = geom3::middetail; + + if(lastmode == em3D) buildpolys(), resetGL(); + } + + void drawNumberDialog() { + init(ne.title); + addInfo(ne.s); + addSlider(ne.scale(ne.vmin), ne.scale(*ne.editwhat), ne.scale(ne.vmax), 500); + addBreak(100); +#ifndef MOBILE + addHelp("You can scroll with arrow keys -- Ctrl to fine-tune"); + addBreak(100); +#endif + + addItem("return", ' '); + addSelItem("default value", disp(ne.dft), SDLK_HOME); + + addBreak(100); + + if(lastmode == em3D) ne.help = explain3D(ne.editwhat); + + if(ne.help != "") { + addHelp(ne.help); + bool scal = size(ne.help) > 160; +#ifndef MOBILE +#ifndef PANDORA + scal = false; +#endif +#endif + if(scal) lastItem().scale = 30; + } + + if(ne.editwhat == &vid.alpha) { + addBreak(100); + addSelItem(sphere ? "stereographic" : "Poincaré model", "1", 'p'); + addSelItem(sphere ? "gnomonic" : "Klein model", "0", 'k'); + addItem(sphere ? "towards orthographic" : "towards Gans model", 'o'); + } + + if(ne.editwhat == &ne.intbuf && ne.intval == &sightrange && cheater) + addBoolItem("overgenerate", overgenerate, 'o'); + + display(); + } + + void handleNumber(int sym, int uni) { + handleNavigation(sym, uni); + if((uni >= '0' && uni <= '9') || (uni == '.' && !ne.intval) || (uni == '-' && !ne.positive)) { + ne.s += uni; + affect('s'); + } + else if(uni == '\b' || uni == '\t') { + ne.s = ne.s. substr(0, size(ne.s)-1); + sscanf(ne.s.c_str(), LDF, ne.editwhat); + affect('s'); + } +#ifndef MOBILE + else if(sym == SDLK_RIGHT || sym == SDLK_KP6) { + if(ne.intval && abs(shiftmul) < .6) + (*ne.editwhat)++; + else + *ne.editwhat = ne.inverse_scale(ne.scale(*ne.editwhat) + shiftmul * ne.step); + affect('v'); + } + else if(sym == SDLK_LEFT || sym == SDLK_KP4) { + if(ne.intval && abs(shiftmul) < .6) + (*ne.editwhat)--; + else + *ne.editwhat = ne.inverse_scale(ne.scale(*ne.editwhat) - shiftmul * ne.step); + affect('v'); + } +#endif + else if(sym == SDLK_HOME) { + *ne.editwhat = ne.dft; + affect('v'); + } + else if(uni == 500) { + ld d = (mousex - vid.xres/4 + .0) / (vid.xres/2); + *ne.editwhat = + ne.inverse_scale(d * (ne.scale(ne.vmax) - ne.scale(ne.vmin)) + ne.scale(ne.vmin)); + affect('v'); + } + else if(uni == 'o' && ne.editwhat == &ne.intbuf && ne.intval == &sightrange && cheater) + overgenerate = !overgenerate; + else if(uni == 'p' && ne.editwhat == &vid.alpha) { + *ne.editwhat = 1; vid.scale = 1; ne.s = "1"; + } + else if(uni == 'k' && ne.editwhat == &vid.alpha) { + *ne.editwhat = 0; vid.scale = 1; ne.s = "0"; + } + else if((uni == 'i' || uni == 'I' || uni == 'o' || uni == 'O') && ne.editwhat == &vid.alpha) { + double d = exp(shiftmul/10); + vid.alpha *= d; + vid.scale *= d; + ne.s = fts(vid.alpha); + } + else if(uni) { + cmode = lastmode; + } + } + + int nlpage = 1; + int wheelshift = 0; + + int handlePage(int& nl, int& nlm, int perpage) { + nlm = nl; + int onl = nl; + int ret = 0; + if(nlpage) { + nl = nlm = perpage; + if(nlpage == 2) ret = nlm; + int w = wheelshift; + int realw = 0; + while(w<0 && ret) { + ret--; w++; realw--; + } + while(w>0 && ret+perpage < onl) { + ret++; w--; realw++; + } + wheelshift = realw; + if(ret+nl > onl) nl = onl-ret; + } + return ret; + } + + void displayPageButtons(int i, bool pages) { + int i0 = vid.yres - vid.fsize; + int xr = vid.xres / 80; + if(pages) if(displayfrZ(xr*8, i0, 1, vid.fsize, IFM("1 - ") + XLAT("page") + " 1", nlpage == 1 ? 0xD8D8C0 : 0xC0C0C0, 8)) + getcstat = '1'; + if(pages) if(displayfrZ(xr*24, i0, 1, vid.fsize, IFM("2 - ") + XLAT("page") + " 2", nlpage == 1 ? 0xD8D8C0 : 0xC0C0C0, 8)) + getcstat = '2'; + if(pages) if(displayfrZ(xr*40, i0, 1, vid.fsize, IFM("3 - ") + XLAT("all"), nlpage == 1 ? 0xD8D8C0 : 0xC0C0C0, 8)) + getcstat = '3'; + if(i&1) if(displayfrZ(xr*56, i0, 1, vid.fsize, IFM("0 - ") + XLAT("return"), 0xC0C0C0, 8)) + getcstat = '0'; + if(i&2) if(displayfrZ(xr*72, i0, 1, vid.fsize, IFM("F1 - ") + XLAT("help"), 0xC0C0C0, 8)) + getcstat = SDLK_F1; + } + + bool handlePageButtons(int uni) { + if(uni == '1') nlpage = 1, wheelshift = 0; + else if(uni == '2') nlpage = 2, wheelshift = 0; + else if(uni == '3') nlpage = 0, wheelshift = 0; + else if(uni == PSEUDOKEY_WHEELUP) wheelshift--; + else if(uni == PSEUDOKEY_WHEELDOWN) wheelshift++; + else return false; + return true; + } + + }; diff --git a/fake-mobile.cpp b/fake-mobile.cpp new file mode 100644 index 00000000..ca0657b6 --- /dev/null +++ b/fake-mobile.cpp @@ -0,0 +1,234 @@ +#define ISANDROID 0 +#define ISMOBILE 1 +#define ISIOS 0 +#define MOBILE +#define MOBPAR_FORMAL int +#define MOBPAR_ACTUAL 0 +#define FAKEMOBILE +#define MIX_MAX_VOLUME 128 + +const char *scorefile = "fakemobile_score.txt"; +const char *conffile = "fakemobile_config.txt"; + +#include + +#include +#include "init.cpp" + +#include +#include + +#undef main + +void playSound(cell *, const string &s, int vol) { + printf("play sound: %s vol %d\n", s.c_str(), vol); + } + +SDL_Surface *s; + +int gdpos = 0; + +int gdpop() { return graphdata[gdpos++]; } + +TTF_Font *font[256]; + +bool rawdisplaystr(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) { + return false; + } + 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; + + if(!font[size]) + font[size] = TTF_OpenFont("VeraBd.ttf", size); + + SDL_Surface *txt = 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); + + SDL_BlitSurface(txt, NULL, s,&rect); + SDL_FreeSurface(txt); + + return clicked; + } + +int textwidth(int siz, const string &str) { + if(size(str) == 0) return 0; + + if(!font[siz]) font[siz] = TTF_OpenFont("VeraBd.ttf", siz); + + int w, h; + TTF_SizeUTF8(font[siz], str.c_str(), &w, &h); + // printf("width = %d [%d]\n", w, size(str)); + return w; + } + +char action; + +int getticks() { return SDL_GetTicks(); } + +bool currentlyConnecting() { return false; } +bool currentlyConnected() { return false; } +void viewAchievements() { printf("view Achievements\n"); } +void viewLeaderboard(string id) { printf("view Leaderboard :: %s\n", id.c_str()); } +void switchGoogleConnection() { printf("sgc\n"); } + +int main(int argc, char **argv) { + + initAll(); + + vid.xres = 800; vid.yres = 450; + vid.usingGL = false; + // 450; vid.yres = 600; + + s= SDL_SetVideoMode(vid.xres, vid.yres, 32, 0); + + if(TTF_Init() != 0) { + printf("Failed to initialize TTF.\n"); + exit(2); + } + + int mx = 0; int my = 0; bool _clicked = false; + int action = 0; + + firstland = laMinefield; + + items[itGreenStone] = 100; + items[itDiamond] = 50; + for(int i=1; i<10; i++) kills[i] = 5; + + while(true) { + + SDL_LockSurface(s); + memset(s->pixels, 0, vid.xres * vid.yres * 4); + SDL_UnlockSurface(s); + + mousex = mx; + mousey = my; + clicked = _clicked; + + mobile_draw(MOBPAR_ACTUAL); + action = 0; + + gdpos = 0; + while(gdpos < graphdata.size()) { + switch(gdpop()) { + case 2: { + int x = gdpop(), y = gdpop(), al = gdpop(); + int color = gdpop(); + int size = gdpop(); + int b = gdpop(); + int n = gdpop(); + string s; + for(int i=0; i \n"); + } diff --git a/fieldpattern.cpp b/fieldpattern.cpp new file mode 100644 index 00000000..59fe1579 --- /dev/null +++ b/fieldpattern.cpp @@ -0,0 +1,684 @@ + +namespace fieldpattern { + +extern int subpathid; +extern int subpathorder; + +bool isprime(int n) { + for(int k=2; k= Prime && tx % Prime) neasy++; + if(ty >= Prime && ty % Prime) neasy++; + int x[2], y[2], z[3]; + for(int i=0; i<3; i++) z[i] = 0; + for(int i=0; i<2; i++) + x[i] = tx%Prime, tx /= Prime; + for(int i=0; i<2; i++) + y[i] = ty%Prime, ty /= Prime; + for(int i=0; i<2; i++) + for(int j=0; j<2; j++) + z[i+j] = (z[i+j] + x[i] * y[j]) % Prime; + z[0] += z[2] * wsquare; + + return m(z[0]) + Prime * m(z[1]); + #endif + } + + int sqr(int x) { return mul(x,x); } + + matrix mmul(const matrix& A, const matrix& B) { + matrix res; + for(int i=0; i<3; i++) for(int k=0; k<3; k++) { + #ifdef EASY + res[i][k] = + (mul(A[i][0], B[0][k]) + mul(A[i][1], B[1][k]) + mul(A[i][2], B[2][k])) % Prime; + #else + int t=0; + for(int j=0; j<3; j++) t = add(t, mul(A[i][j], B[j][k])); + res[i][k] = t; + #endif + } + return res; + } + + map matcode; + vector matrices; + + vector qpaths; + + vector qcoords; + + matrix Id, R, P; + + matrix strtomatrix(string s) { + matrix res = Id; + matrix m = Id; + for(int i=size(s)-1; i>=0; i--) + if(s[i] == 'R') res = mmul(R, res); + else if (s[i] == 'P') res = mmul(P, res); + else if (s[i] == 'x') { m[0][0] = -1; res = mmul(m, res); m[0][0] = +1; } + else if (s[i] == 'y') { m[1][1] = -1; res = mmul(m, res); m[1][1] = +1; } + else if (s[i] == 'z') { m[2][2] = -1; res = mmul(m, res); m[2][2] = +1; } + return res; + } + + void addas(const matrix& M, int i) { + if(!matcode.count(M)) { + matcode[M] = i; + for(int j=0; j connections; + + vector inverses; + vector rrf; // rrf[i] equals gmul(i, 6) + vector rpf; // rpf[i] equals gmul(i, 7) + + matrix mpow(matrix M, int N) { + while((N&1) == 0) N >>= 1, M = mmul(M, M); + matrix res = M; + N >>= 1; + while(N) { + M = mmul(M,M); if(N&1) res = mmul(res, M); + N >>= 1; + } + return res; + } + + int gmul(int a, int b) { return matcode[mmul(matrices[a], matrices[b])]; } + int gpow(int a, int N) { return matcode[mpow(matrices[a], N)]; } + + pair gmul(pair a, int b) { + return make_pair(gmul(a.first,b), a.second); + } + + int order(const matrix& M) { + int cnt = 1; + matrix Po = M; + while(Po != Id) Po = mmul(Po, M), cnt++; + return cnt; + } + + string decodepath(int i) { + string s; + while(i) { + if(i % 7) i--, s += 'R'; + else i = connections[i], s += 'P'; + } + return s; + } + + int orderstats(); + + int cs, sn, ch, sh; + + int solve() { + + for(int a=0; a<3; a++) for(int b=0; b<3; b++) Id[a][b] = a==b?1:0; + + if(!isprime(Prime)) { + return 1; + } + + for(int pw=1; pw<3; pw++) { + if(pw>3) break; + Field = pw==1? Prime : Prime*Prime; + + if(pw == 2) { + for(wsquare=1; wsquare disthep; + vector disthex; + + vector distwall, distriver, distwall2, distriverleft, distriverright, distflower; + + vector markers; + + int getdist(pair a, vector& dists) { + if(!a.second) return dists[a.first]; + int m = 60; + int ma = dists[a.first]; + int mb = dists[connections[btspin(a.first, 3)]]; + int mc = dists[connections[btspin(a.first, 4)]]; + m = min(m, 1 + ma); + m = min(m, 1 + mb); + m = min(m, 1 + mc); + if(m <= 2 && ma+mb+mc <= m*3-2) return m-1; // special case + m = min(m, 2 + dists[connections[btspin(a.first, 2)]]); + m = min(m, 2 + dists[connections[btspin(a.first, 5)]]); + m = min(m, 2 + dists[connections[btspin(connections[btspin(a.first, 3)], 5)]]); + return m; + } + + int getdist(pair a, pair b) { + if(a.first == b.first) return a.second == b.second ? 0 : 1; + if(b.first) a.first = gmul(a.first, inverses[b.first]), b.first = 0; + return getdist(a, b.second ? disthex : disthep); + } + + int maxdist, otherpole, circrad, wallid, wallorder, riverid; + + int dijkstra(vector& dists, vector indist[64]) { + int N = connections.size(); + dists.resize(N); + for(int i=0; i indist[64]; + + indist[0].push_back(0); + int md0 = dijkstra(disthep, indist); + + indist[1].push_back(0); + indist[1].push_back(connections[3]); + indist[1].push_back(connections[4]); + indist[2].push_back(connections[btspin(connections[3], 5)]); + indist[2].push_back(connections[2]); + indist[2].push_back(connections[5]); + int md1 = dijkstra(disthex, indist); + + maxdist = max(md0, md1); + + otherpole = 0; + + for(int i=0; i disthep[otherpole]) otherpole = i; + // for(int r=0; r<7; r++) { + // printf("Matrix: "); for(int a=0; a<3; a++) for(int b=0; b<3; b++) + // printf("%4d", matrices[i+r][a][b]); printf("\n"); + // } + } + } + } + + circrad = 99; + + for(int i=0; iwall == waBoat || c->wall == waVineHalfA || c->wall == waVineHalfB || - c->wall == waStrandedBoat || c->wall == waTrunk; + c->wall == waStrandedBoat || c->wall == waTrunk || + c->wall == waBigBush || c->wall == waSmallBush; } bool boatStrandable(cell *c) { @@ -93,7 +96,7 @@ bool isNonliving(eMonster m) { m == moMirage || m == moMirror || m == moGolem || m == moGolemMoved || m == moZombie || m == moGhost || m == moShadow || m == moSkeleton || m == moEvilGolem || m == moIllusion || m == moEarthElemental || - m == moWaterElemental; + m == moWaterElemental || m == moDraugr; } bool isMetalBeast(eMonster m) { @@ -102,7 +105,8 @@ bool isMetalBeast(eMonster m) { bool isStunnable(eMonster m) { return m == moPalace || m == moFatGuard || m == moSkeleton || isPrincess(m) || - isMetalBeast(m) || m == moTortoise || isDragon(m); + isMetalBeast(m) || m == moTortoise || isDragon(m) || + m == moReptile; } bool hasHitpoints(eMonster m) { @@ -110,15 +114,23 @@ bool hasHitpoints(eMonster m) { } bool isMountable(eMonster m) { - return isWorm(m); + return isWorm(m) && m != moTentacleGhost; } bool isFriendly(eMonster m) { - return isMimic(m) || isGolemOrKnight(m) || m == moIllusion; + return isMimic(m) || isGolemOrKnight(m) || m == moIllusion || m == moFriendlyIvy; + } + +bool isFriendlyOrPlayer(eMonster m) { + return isFriendly(m) || m == moPlayer; } bool isFriendly(cell *c) { - if(items[itOrbDomination] && c->monst && sameMonster(c, cwt.c)) return true; + if(items[itOrbDomination] && c->monst && c->monst != moTentacleGhost) { + for(int i=0; imonst); } @@ -146,13 +158,18 @@ bool isIvy(cell *c) { } bool isMonsterPart(eMonster m) { - return m == moMutant || (isIvy(m) && m != moIvyRoot); + return m == moMutant || (isIvy(m) && m != moIvyRoot) || + m == moDragonTail || m == moKrakenT; } bool isMutantIvy(eMonster m) { return m == moMutant; } +bool isAnyIvy(eMonster m) { + return isIvy(m) || isMutantIvy(m) || m == moFriendlyIvy; + } + bool isBulletType(eMonster m) { return m == moBullet || m == moFlailBullet || @@ -213,7 +230,7 @@ bool cellHalfvine(cell *c) { } bool isWarped(eLand l) { - return l == laGridCoast || l == laGridSea; + return l == laWarpCoast || l == laWarpSea; } bool isElementalShard(eItem i) { @@ -229,19 +246,28 @@ eMonster elementalOf(eLand l) { return moNone; } +eItem localshardof(eLand l) { + return eItem(itFireShard + (l - laEFire)); + } + 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 || i == itPirate || i == itRedGem || i == itBombEgg || i == itCoast || i == itWhirlpool || i == itPalace || i == itFjord || - i == itElemental || i == itZebra || i == itEdge || + i == itElemental || i == itZebra || i == itIvory || i == itBounty || i == itFulgurite || i == itMutant || i == itLotus || i == itMutant2 || i == itWindstone || i == itCoral || i == itRose || - i == itBabyTortoise || i == itDragon || i == itApple) + i == itBabyTortoise || i == itDragon || i == itApple || + i == itKraken || i == itBarrow || i == itTrollEgg || i == itTreat || + i == itSlime || i == itAmethyst || i == itDodeca || + i == itGreenGrass || i == itBull) return IC_TREASURE; + if(i == itSavedPrincess || i == itStrongWind || i == itWarning) + return IC_NAI; if(i == itKey || i == itOrbYendor || i == itGreenStone || i == itHolyGrail || i == itCompass || - i == itSavedPrincess || isElementalShard(i) || i == itRevolver || i == itStrongWind) + isElementalShard(i) || i == itRevolver) return IC_OTHER; return IC_ORB; } @@ -264,10 +290,11 @@ bool realred(eWall w) { int snakelevel(eWall w) { if(w == waRed1 || w == waDeadfloor2 || w == waRubble || w == waGargoyleFloor || - w == waGargoyleBridge || w == waTempFloor || w == waTempBridge) + w == waGargoyleBridge || w == waTempFloor || w == waTempBridge || w == waRoundTable) return 1; if(w == waRed2) return 2; if(w == waRed3) return 3; + if(w == waTower) return 3; return 0; } @@ -285,7 +312,9 @@ bool isWall(cell *w) { w->wall == waStrandedBoat || w->wall == waOpenGate || w->wall == waClosePlate || w->wall == waOpenPlate || w->wall == waTrapdoor || w->wall == waGiantRug || w->wall == waLadder || w->wall == waTrunk || w->wall == waSolidBranch || - w->wall == waWeakBranch || w->wall == waCanopy) + w->wall == waWeakBranch || w->wall == waCanopy || w->wall == waTower || + w->wall == waSmallBush || w->wall == waBigBush || + w->wall == waReptile || w->wall == waReptileBridge) return false; if(isWatery(w) || isChasmy(w) || isFire(w)) return false; return true; @@ -293,18 +322,25 @@ bool isWall(cell *w) { bool isAngryBird(eMonster m) { return m == moEagle || m == moAlbatross || m == moBomberbird || m == moGargoyle || - m == moWindCrow || m == moKestrel || m == moNighthawk; + m == moWindCrow || m == moSparrowhawk || + m == moVampire || m == moBat || m == moButterfly || m == moGadfly; } bool isBird(eMonster m) { - return isAngryBird(m) || m == moTameBomberbird || m == moTameBomberbirdMoved; + return isAngryBird(m) || m == moTameBomberbird || m == moTameBomberbirdMoved; + } + +bool slowMover(eMonster m) { + return + m == moLesser || m == moGreater || isMetalBeast(m) || + m == moTortoise || m == moDraugr; } 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 == moRunDog || m == moPyroCultist || m == moFireFairy || m == moCrystalSage || m == moHedge || m == moVineBeast || m == moLancer || m == moFlailer || m == moMiner || m == moDarkTroll || @@ -314,12 +350,12 @@ bool normalMover(eMonster m) { m == moRedTroll || m == moPalace || m == moFatGuard || m == moSkeleton || m == moVizier || m == moFjordTroll || m == moStormTroll || m == moForestTroll || - m == moEdgeMonkey || - m == moFireElemental || m == moOrangeDog || - isMetalBeast(m) || + m == moFamiliar || + m == moFireElemental || m == moOrangeDog || m == moOutlaw || m == moRedFox || m == moFalsePrincess || m == moRoseLady || m == moRoseBeauty || m == moWolf || - m == moTortoise || m == moLemur; + m == moResearcher || m == moRagingBull || + slowMover(m); } // from-to @@ -339,20 +375,24 @@ bool isShark(eMonster m) { bool isSlimeMover(eMonster m) { return - m == moSlime || m == moSeep || m == moShark || - m == moVineSpirit || m == moCShark || m == moParrot; + m == moSlime || m == moSeep || m == moVineSpirit || m == moParrot; } +bool isDragon(eMonster m) { return m == moDragonHead || m == moDragonTail; } + +bool isKraken(eMonster m) { return m == moKrakenH || m == moKrakenT; } + bool isBlowableMonster(eMonster m) { return m && !( isWorm(m) || isIvy(m) || isMutantIvy(m) || isSlimeMover(m) || m == moGhost || m == moGreaterShark || - m == moWaterElemental || m == moWitchGhost || isMimic(m) + m == moWaterElemental || m == moWitchGhost || isMimic(m) || + isKraken(m) ); } bool isMultitile(eMonster m) { - return isWorm(m) || isIvy(m) || isMutantIvy(m); + return isWorm(m) || isIvy(m) || isMutantIvy(m) || isKraken(m); } bool isSlimeMover(cell *c) { @@ -380,8 +420,7 @@ bool isLeader(eMonster m) { } bool isFlying(eMonster m) { - return isBird(m) || isGhost(m) || m == moAirElemental || isDragon(m) || - (isFriendly(m) && checkOrb(m, itOrbGhost)); + return isBird(m) || isGhost(m) || m == moAirElemental || isDragon(m) || checkOrb(m, itOrbAether); } bool survivesChasm(eMonster m) { @@ -393,7 +432,7 @@ bool ignoresPlates(eMonster m) { } bool itemBurns(eItem it) { - return it && it != itOrbDragon && it != itOrbFire && it != itDragon; + return it && it != itOrbDragon && it != itOrbFire && it != itDragon && it != itTreat; } bool attackThruVine(eMonster m) { @@ -404,6 +443,11 @@ bool attackNonAdjacent(eMonster m) { return m == moGhost || m == moFriendlyGhost || m == moTentacleGhost; } +bool noHighlight(eMonster m) { + return + (m == moIvyWait || m == moIvyNext || m == moIvyDead); + } + bool isInactiveEnemy(cell *w, eMonster forwho) { if(w->monst == moWormtail || w->monst == moWormwait || w->monst == moTentacletail || w->monst == moTentaclewait || w->monst == moHexSnakeTail) return true; @@ -429,7 +473,7 @@ bool isActiveEnemy(cell *w, eMonster forwho) { bool isUnarmed(eMonster m) { return m == moMouse || m == moMouseMoved || m == moPrincess || m == moPrincessMoved || - m == moCrystalSage; + m == moCrystalSage || m == moVampire || m == moBat; } bool isArmedEnemy(cell *w, eMonster forwho) { @@ -451,16 +495,17 @@ bool eternalFire(cell *c) { bool isCyclic(eLand l) { return - l == laWhirlpool || l == laTemple || l == laCamelot || l == laClearing; + l == laWhirlpool || l == laTemple || l == laCamelot || l == laClearing || + l == laMountain; } bool haveRangedOrb() { return items[itOrbPsi] || items[itOrbDragon] || items[itOrbTeleport] || - items[itOrbIllusion] || items[itOrbTelekinesis] || items[itOrbAir] || + items[itOrbIllusion] || items[itOrbSpace] || items[itOrbAir] || items[itOrbFrog] || items[itOrbSummon] || items[itOrbMatter] || items[itRevolver] || items[itOrbStunning] || items[itStrongWind] || - items[itOrbDomination]; + items[itOrbDomination] || items[itOrbNature] || items[itOrbDash]; } bool isOffensiveOrb(eItem it) { @@ -471,9 +516,9 @@ bool isOffensiveOrb(eItem it) { bool isRangedOrb(eItem i) { return i == itOrbPsi || i == itOrbDragon || i == itOrbTeleport || i == itOrbIllusion || - i == itOrbTelekinesis || i == itOrbAir || i == itOrbFrog || + i == itOrbSpace || i == itOrbAir || i == itOrbFrog || i == itOrbSummon || i == itOrbMatter || i == itRevolver || i == itOrbStunning || - i == itOrbDomination; + i == itOrbDomination || i == itOrbNature || i == itOrbDash; } bool isProtectionOrb(eItem i) { @@ -484,59 +529,72 @@ bool isEmpathyOrb(eItem i) { return i == itOrbFire || i == itOrbDigging || i == itOrbWinter || i == itOrbUndeath || i == itOrbSpeed || i == itOrbShield || - i == itOrbGhost || i == itOrbInvis || i == itOrbThorns || - i == itOrbWater; + i == itOrbAether || i == itOrbInvis || i == itOrbThorns || + i == itOrbWater || i == itOrbStone; } bool isUtilityOrb(eItem i) { return i == itOrbSpeed || i == itOrbDigging || - i == itOrbSafety || i == itOrbTeleport || i == itOrbGhost || - i == itOrbPreserve || i == itOrbTelekinesis || + i == itOrbSafety || i == itOrbTeleport || i == itOrbAether || + i == itOrbTime || i == itOrbSpace || i == itOrbSummon || i == itOrbLuck || i == itOrbEnergy; } +bool isDirectionalOrb(eItem i) { + return i == itOrbHorns || i == itOrbBull; + } + +bool isRevivalOrb(eItem i) { + return i == itOrbLife || i == itOrbFriend || i == itOrbUndeath; + } + bool isFriendOrb(eItem i) { return i == itOrbLife || i == itOrbFriend || i == itOrbDiscord || i == itOrbLove || i == itOrbEmpathy || i == itOrbUndeath; } bool isFriendlyGhost(eMonster m) { - return m == moFriendlyGhost || (markEmpathy(itOrbGhost) && isFriendly(m)); + return m == moFriendlyGhost || (markEmpathy(itOrbAether) && isFriendly(m)); } -bool isDragon(eMonster m) { return m == moDragonHead || m == moDragonTail; } - bool survivesWater(eMonster m) { return m == moShark || m == moGreaterShark || m == moCShark || - isGhost(m) || m == moWitchGhost || + isGhost(m) || m == moWitchGhost || m == moShadow || isBird(m) || m == moWaterElemental || m == moAirElemental || - isWorm(m) || isIvy(m) || isDragon(m) || + isWorm(m) || isIvy(m) || isDragon(m) || isKraken(m) || + m == moMutant || m == moFriendlyIvy || m == moTortoise; // Tortoises and Ivies survive, but don't go through water } +// flying even if stunned +bool isPermanentFlying(eMonster m) { + return m == moAirElemental || isGhost(m); + } + bool survivesFire(eMonster m) { return isGhost(m) || m == moWitchWinter || m == moWitchGhost || m == moBomberbird || m == moTameBomberbird || m == moTameBomberbirdMoved || (isFriendly(m) && markOrb(itOrbWinter)) || isWorm(m) || m == moFireElemental || - isDragon(m); + isDragon(m) || m == moShadow; } -bool survivesMine(eMonster m) { - return isFlying(m); - } +/* bool survivesMine(eMonster m) { + return ignoresPlates(m) || isFlying(m); + } */ bool survivesWall(eMonster m) { return isGhost(m); } bool survivesThorns(eMonster m) { - return isGhost(m) || m == moSkeleton; + return isGhost(m) || m == moSkeleton || m == moDraugr; } bool survivesFall(eMonster m) { - return isBird(m) || m == moAirElemental || m == moSkeleton || isDragon(m); + return isBird(m) || m == moAirElemental || m == moSkeleton || isDragon(m) || m == moShadow || + isGhost(m); } bool isThorny(eWall w) { return w == waRose; } @@ -547,9 +605,75 @@ bool checkOrb(eMonster m1, eItem orb) { return false; } +bool checkOrb2(eMonster m1, eItem orb) { + if(m1 == moPlayer) return markOrb2(orb); + if(isFriendly(m1)) return markEmpathy2(orb); + return false; + } + bool ignoresSmell(eMonster m) { return m == moHexSnake || isIvy(m) || isMutantIvy(m) || isGhostMover(m) || isSlimeMover(m) || - m == moRoseLady || checkOrb(m, itOrbSkunk) || checkOrb(m, itOrbGhost) || checkOrb(m, itOrbShield); + m == moRoseLady || checkOrb(m, itOrbBeauty) || checkOrb(m, itOrbAether) || checkOrb(m, itOrbShield); + } + +bool isTroll(eMonster m) { + return + m == moTroll || m == moRedTroll || m == moDarkTroll || + m == moForestTroll || m == moStormTroll || m == moFjordTroll; + } + +bool isGrave(eWall w) { + return w == waFreshGrave || w == waAncientGrave; + } + +bool isTree(cell *c) { + return false; // c->wall == waBigTree || c->wall == waSmallTree; + } + +bool highwall(cell *c) { + if(c->wall == waGlass) return false; + if(wmescher && wmspatial && c->wall == waBarrier && c->land == laOceanWall) + return false; + // if(wmspatial && isTree(c)) return false; + if(isGrave(c->wall)) return true; + return winf[c->wall].glyph == '#' || c->wall == waClosedGate; + } + +int chasmgraph(cell *c) { + if(c->wall == waChasm) return 2; + if(isChasmy(c)) return 1; + if(isWateryOrBoat(c)) return 1; + if(wmescher && c->wall == waBarrier && c->land == laOceanWall) return 1; + if(c->wall == waReptileBridge) return 1; + return 0; + } + +bool conegraph(cell *c) { + return wmescher && wmspatial && (c->wall == waDune || c->wall == waBigTree || c->wall == waSmallTree || c->wall == waCTree || (c->wall == waBarrier && c->land == laOceanWall)); + } + +bool isReptile(eWall w) { + return w == waReptile || w == waReptileBridge; + } + +bool isBull(eMonster m) { + return m == moRagingBull || m == moHerdBull || m == moSleepBull; + } + +bool hornStuns(cell *c) { + eMonster m = c->monst; + return + m == moRagingBull || m == moSleepBull || m == moHerdBull || + m == moButterfly || m == moGreater || m == moGreaterM || m == moDraugr || + m == moHedge || m == moFlailer || m == moVizier || + attackJustStuns(c); + } + +// generate all the world first in the quotient geometry +bool generateAll(eLand l) { + return + l == laIce || l == laDryForest || l == laCocytus || l == laLivefjord || + l == laCaves || l == laCA; } diff --git a/game.cpp b/game.cpp index ed7322d4..adb1ea26 100644 --- a/game.cpp +++ b/game.cpp @@ -3,6 +3,7 @@ // Copyright (C) 2011-2016 Zeno Rogue, see 'hyper.cpp' for details +int lastsafety; int mutantphase; int turncount; int rosewave, rosephase; @@ -20,8 +21,31 @@ bool survivalist; bool hardcore = false; int hardcoreAt; -bool havebugs, haveearth, haveeagles, haveleader, havehex, havewhirlpool, havewater, - haveair, havemutant, haveoutlaw, havewhirlwind, haverose, hadrose, havedragon; +flagtype havewhat, hadwhat; + +#define HF_BUG (1<<0) +#define HF_EARTH (1<<1) +#define HF_BIRD (1<<2) +#define HF_LEADER (1<<3) +#define HF_HEX (1<<4) +#define HF_WHIRLPOOL (1<<5) +#define HF_WATER (1<<6) +#define HF_AIR (1<<7) +#define HF_MUTANT (1<<8) +#define HF_OUTLAW (1<<9) +#define HF_WHIRLWIND (1<<10) +#define HF_ROSE (1<<11) +#define HF_DRAGON (1<<12) +#define HF_KRAKEN (1<<13) +#define HF_SHARK (1<<14) +#define HF_BATS (1<<15) +#define HF_REPTILE (1<<16) +#define HF_EAGLES (1<<17) +#define HF_SLOW (1<<18) +#define HF_FAST (1<<19) +#define HF_WARP (1<<20) +#define HF_MOUSE (1<<21) +#define HF_RIVER (1<<22) bool seenSevenMines = false; @@ -63,7 +87,6 @@ bool eq(short a, short b) { return a==b; } // game state int items[ittypes], hiitems[MODECODES][ittypes], kills[motypes], explore[10], exploreland[10][landtypes], landcount[landtypes]; bool orbused[ittypes], lastorbused[ittypes]; -bool playerdead = false; // obsolete bool playermoved = true; // center on the PC? bool flipplayer = true; // flip the player image after move, do not flip after attack int cheater = 0; // did the player cheat? @@ -76,7 +99,7 @@ vector pathq; // queue for pathdist vector offscreen; // offscreen cells to take care off vector pathqm; // list of monsters to move (pathq restriced to monsters) - + vector targets; // list of monster targets // monsters of specific types to move @@ -96,7 +119,10 @@ vector movesofgood[8]; int first7; // the position of the first monster at distance 7 in dcal -cellwalker cwt; // player character position +cellwalker cwt; // single player character position + +inline cell*& singlepos() { return cwt.c; } +inline bool singleused() { return !(shmup::on || multi::players > 1); } #include "mtrand.cpp" @@ -115,7 +141,7 @@ int hrand(int i) { 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->pathdist = PINFD;// current distance from the player, along paths (used by yetis) c->landparam = 0; c->landflags = 0; c->wparam = 0; c->aitmp = 0; c->wall = waNone; @@ -129,12 +155,38 @@ void initcell(cell *c) { c->stuntime = 0; } +bool doesnotFall(cell *c) { + if(c->wall == waChasm) return false; + else if(cellUnstable(c)) { + fallingFloorAnimation(c); + c->wall = waChasm; + return false; + } + return true; + } + +bool doesFall(cell *c) { return !doesnotFall(c); } + +bool doesFallSound(cell *c) { + if(c->land != laMotion && c->land != laZebra) + playSound(c, "trapdoor"); + return !doesnotFall(c); + } + bool itemHidden(cell *c) { - return isWatery(c); + return isWatery(c) && !(shmup::on && shmup::boatAt(c)); + } + +bool playerInWater() { + for(int i=0; i 1) return multi::player[i].c; + return singlepos(); + } + +bool allPlayersInBoats() { + for(int i=0; iwall != waBoat) return true; + return false; + } + +int whichPlayerOn(cell *c) { + if(singleused()) return c == singlepos() ? 0 : -1; + for(int i=0; i= 0; + } + +bool isPlayerInBoatOn(cell *c, int i) { + return + (playerpos(i) == c && ( + c->wall == waBoat || c->wall == waStrandedBoat || (shmup::on && shmup::playerInBoat(i)) + )); + } + +bool playerInBoat(int i) { + return isPlayerInBoatOn(playerpos(i), i); } bool isPlayerInBoatOn(cell *c) { - if(!shmup::on) return c == cwt.c && (c->wall == waBoat || c->wall == waStrandedBoat); - for(int i=0; iwall == waBoat || c->wall == waStrandedBoat || shmup::playerInBoat(i) - )) - return true; + for(int i=0; iwall == waBoat) placeWater(c, c2); + if(strandedToo && c->wall == waStrandedBoat) c->wall = waNone; + shmup::destroyBoats(c); + } + bool playerInPower() { - if(!shmup::on) return cwt.c->land == laPower; - for(int i=0; iland == laPower) - return true; + if(singleused()) + return singlepos()->land == laPower || singlepos()->land == laHalloween; + for(int i=0; iland == laPower || playerpos(i)->land == laHalloween)) + return true; return false; } eItem localTreasureType() { - lastland = cwt.c->land; + lastland = singlepos()->land; return treasureType(lastland); } @@ -232,7 +309,7 @@ int* killtable[] = { kills[moViking], &kills[moFjordTroll], &kills[moWaterElemental], & kills[moAlbatross], &kills[moBomberbird], & kills[moAirElemental], &kills[moFireElemental], & - kills[moGargoyle], &kills[moEdgeMonkey], &kills[moOrangeDog], & + kills[moGargoyle], &kills[moFamiliar], &kills[moOrangeDog], & items[itMutant], &kills[moMetalBeast], &kills[moMetalBeast2], & kills[moOutlaw], &kills[moForestTroll], &kills[moStormTroll], & kills[moRedFox], &kills[moWindCrow], & @@ -240,11 +317,13 @@ int* killtable[] = { kills[moRoseBeauty], & kills[moRatling], &kills[moRatlingAvenger], & kills[moDragonHead], & - kills[moNighthawk], &kills[moKestrel], &kills[moLemur], + kills[moGadfly], &kills[moSparrowhawk], &kills[moResearcher], + &kills[moKrakenH], &kills[moDraugr], + &kills[moBat], &kills[moReptile], + &kills[moHerdBull], &kills[moSleepBull], &kills[moRagingBull], + &kills[moGadfly], &kills[moButterfly], NULL }; - - int tkills() { int res = 0; @@ -267,6 +346,11 @@ bool isWarped(cell *c) { // which roughly corresponds to the heptagons in the normal tiling bool pseudohept(cell *c) { if(purehepta) { + if(sphere) + return + c->master == &dodecahedron[3] || + c->master == &dodecahedron[5] || + c->master == &dodecahedron[6]; int z = zebra40(c); return z == 5 || z == 8 || z == 15; } @@ -284,6 +368,10 @@ bool nonAdjacent(cell *c, cell *c2) { return false; } +bool nonAdjacentPlayer(cell *c, cell *c2) { + return nonAdjacent(c, c2) && !markOrb(itOrb37); + } + bool thruVine(cell *c, cell *c2) { return (cellHalfvine(c) && c2->wall == c->wall && c2 != c); // ((c->wall == waFloorC || c->wall == waFloorD) && c2->wall == c->wall && !c2->item && !c->item); @@ -314,9 +402,9 @@ bool againstCurrent(cell *w, cell *from) { bool boatGoesThrough(cell *c) { if(isGravityLand(c->land)) return false; return - (c->wall == waNone && c->land != laMotion && c->land != laZebra) || + (c->wall == waNone && c->land != laMotion && c->land != laZebra && c->land != laReptile) || isAlchAny(c) || - c->wall == waCavefloor || c->wall == waFrozenLake || + c->wall == waCavefloor || c->wall == waFrozenLake || isReptile(c->wall) || c->wall == waDeadfloor || c->wall == waCIsland || c->wall == waCIsland2 || c->wall == waMineUnknown || c->wall == waMineMine || c->wall == waMineOpen || c->wall == waBonfireOff || c->wall == waFire || c->wall == waPartialFire; @@ -330,6 +418,8 @@ void placeWater(cell *c, cell *c2) { c->wall = waLake; else c->wall = waSea; + // destroy the ancient treasure! + if(c->item == itBarrow) c->item = itNone; } int incline(cell *cfrom, cell *cto) { @@ -338,14 +428,14 @@ int incline(cell *cfrom, cell *cto) { #define F(x) checkflags(flags,x) -bool checkflags(int flags, int x) { +bool checkflags(flagtype flags, int x) { if(flags & x) return true; if(flags & P_ISPLAYER) { if((x & P_WINTER) && markOrb(itOrbWinter)) return true; if((x & P_IGNORE37) && markOrb(itOrb37)) return true; if((x & P_FISH) && markOrb(itOrbFish)) return true; if((x & P_MARKWATER) && markOrb(itOrbWater)) return true; - if((x & P_AETHER) && markOrb2(itOrbGhost) && !(flags&P_NOAETHER)) return true; + if((x & P_AETHER) && markOrb2(itOrbAether) && !(flags&P_NOAETHER)) return true; } if(flags & P_ISFRIEND) if(items[itOrbEmpathy]) if(checkflags(flags ^ P_ISPLAYER ^ P_ISFRIEND, x) && markOrb(itOrbEmpathy)) @@ -353,21 +443,31 @@ bool checkflags(int flags, int x) { return false; } -bool passable(cell *w, cell *from, int flags) { +bool strictlyAgainstGravity(cell *w, cell *from, bool revdir, flagtype flags) { + return + cellEdgeUnstable(w, flags) && cellEdgeUnstable(from, flags) && + !(shmup::on && from == w) && gravityLevel(w) != gravityLevel(from) + (revdir?1:-1); + } + +bool passable(cell *w, cell *from, flagtype flags) { bool revdir = (flags&P_REVDIR); if(from && from != w && nonAdjacent(from, w) && !F(P_IGNORE37 | P_BULLET)) return false; - if(w == cwt.c && F(P_ONPLAYER)) return true; - if(from == cwt.c && F(P_ONPLAYER) && F(P_REVDIR)) return true; - - if(from && !(F(P_ISPLAYER) && getMount())) { - int i = revdir ? incline(w, from) : incline(from, w); - if(i < -1 && F(P_ROSE)) return false; - if((i > 1) && !F(P_JUMP1 | P_JUMP2 | P_BULLET | P_FLYING | P_BLOW | P_CLIMBUP | P_AETHER)) - return false; - if((i < -2) && !F(P_DEADLY | P_JUMP1 | P_JUMP2 | P_BULLET | P_FLYING | P_BLOW | P_CLIMBDOWN | P_AETHER)) - return false; + for(int i=0; i 1) && !F(P_JUMP1 | P_JUMP2 | P_BULLET | P_FLYING | P_BLOW | P_CLIMBUP | P_AETHER | P_REPTILE)) + return false; + if((i < -2) && !F(P_DEADLY | P_JUMP1 | P_JUMP2 | P_BULLET | P_FLYING | P_BLOW | P_CLIMBDOWN | P_AETHER | P_REPTILE)) + return false; + } } if(F(P_ROSE)) { @@ -375,22 +475,25 @@ bool passable(cell *w, cell *from, int flags) { if(againstWind(w,from)) return false; } - if(from && !F(P_GRAVITY | P_BLOW | P_JUMP1 | P_JUMP2 | P_FLYING | P_BULLET | P_AETHER) && cellEdgeUnstable(w) && cellEdgeUnstable(from) && - !(shmup::on && from == w) && coastvalEdge(w) != coastvalEdge(from) + (revdir?1:-1)) return false; + if(from && strictlyAgainstGravity(w, from, revdir, flags) + && !F(P_GRAVITY | P_BLOW | P_JUMP1 | P_JUMP2 | P_FLYING | P_BULLET | P_AETHER) + ) return false; - if(from && !F(P_WIND | P_BLOW | P_JUMP1 | P_JUMP2 | P_BULLET) && (revdir ? againstWind(from,w) : againstWind(w, from))) return false; + if(from && (revdir ? againstWind(from,w) : againstWind(w, from)) && !F(P_WIND | P_BLOW | P_JUMP1 | P_JUMP2 | P_BULLET | P_AETHER)) return false; if(revdir && from && w->monst && passable(from, w, flags &~ (P_REVDIR|P_MONSTER))) return true; - - bool alchok = F(P_JUMP1 | P_JUMP2 | P_FLYING | P_TELE | P_BLOW | P_AETHER | P_BULLET) - && !F(P_ROSE); - if(!alchok) { - 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(!shmup::on && sword::at(w, flags & P_ISPLAYER) && !F(P_DEADLY | P_BULLET | P_ROSE)) + return false; + + bool alch1 = w->wall == waFloorA && from && from->wall == waFloorB && !w->item && !from->item; + alch1 |= w->wall == waFloorB && from && from->wall == waFloorA && !w->item && !from->item; + + if(alch1) { + bool alchok = F(P_JUMP1 | P_JUMP2 | P_FLYING | P_TELE | P_BLOW | P_AETHER | P_BULLET) + && !F(P_ROSE); + if(!alchok) return false; } if(from && thruVine(from, w) && !F(P_AETHER)) return false; @@ -434,32 +537,45 @@ bool passable(cell *w, cell *from, int flags) { if(F(P_WATERELEM)) { if(isWatery(w) || boatGoesThrough(w) || + w->wall == waBoat || w->wall == waDeadTroll || w->wall == waDeadTroll2) return true; + return false; } if(isThorny(w->wall) && F(P_BLOW | P_DEADLY)) return true; - if(isFire(w) && !F(P_AETHER | P_WINTER | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY)) return false; + if(isFire(w)) { + if(!F(P_AETHER | P_WINTER | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY)) return false; + } if(isWatery(w)) { if(from && from->wall == waBoat && F(P_USEBOAT) && (!againstCurrent(w, from) || F(P_MARKWATER))) ; - else if(!F(P_AETHER | P_FISH | P_FLYING | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY)) return false; + else if(!F(P_AETHER | P_FISH | P_FLYING | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY | P_REPTILE)) return false; } - if(isChasmy(w) && !F(P_AETHER | P_FLYING | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY)) return false; - if(w->wall == waRoundTable && from && from->wall != waRoundTable && F(P_ISPLAYER)) return true; + if(isChasmy(w)) { + if(!F(P_AETHER | P_FLYING | P_BLOW | P_JUMP1 | P_BULLET | P_DEADLY | P_REPTILE)) return false; + } + + if(w->wall == waRoundTable && from && from->wall != waRoundTable && (flags & P_ISPLAYER)) return true; if(isNoFlight(w) && F(P_FLYING | P_BLOW | P_JUMP1)) return false; - - if(isWall(w) && !F(P_AETHER)) return false; + + if(isWall(w)) { + // a special case: empathic aethereal beings cannot go through Round Table + // (but truly aetheral beings can) + if(w->wall == waRoundTable) { + if(!(flags & P_AETHER)) return false; + } + else if(!F(P_AETHER)) return false; + } return true; } -extern bool haveair; int airdir; vector > airmap; int airdist(cell *c) { - if(!haveair) return 3; + if(!(havewhat & HF_AIR)) return 3; vector >::iterator it = lower_bound(airmap.begin(), airmap.end(), make_pair(c,0)); if(it != airmap.end() && it->first == c) return it->second; @@ -472,7 +588,7 @@ void calcAirdir(cell *c) { for(int i=0; itype; i++) { cell *c2 = c->mov[i]; if(c2 && c2->monst == moAirElemental) { - airdir = c->spn[i] * 42 / c2->type; + airdir = c->spn(i) * S42 / c2->type; return; } } @@ -484,7 +600,7 @@ void calcAirdir(cell *c) { for(int i=0; itype; i++) { cell *c3 = c2->mov[i]; if(c3 && c3->monst == moAirElemental) { - airdir = c2->spn[i] * 42 / c3->type; + airdir = c2->spn(i) * S42 / c3->type; return; } } @@ -503,6 +619,8 @@ bool againstWind(cell *cto, cell *cfrom) { bool ghostmove(eMonster m, cell* to, cell* from) { if(!isGhost(m) && nonAdjacent(to, from)) return false; + if(sword::at(to, 0)) return false; + if(!shmup::on && isPlayerOn(to)) return false; if(to->monst && !(to->monst == moTentacletail && isGhost(m) && m != moFriendlyGhost) && !(to->monst == moTortoise && isGhost(m) && m != moFriendlyGhost)) return false; @@ -517,7 +635,6 @@ bool ghostmove(eMonster m, cell* to, cell* from) { if(m == moGreaterShark) return isWatery(to); if(m == moWitchWinter) return passable(to, from, P_WINTER | P_ONPLAYER); - if(isPlayerOn(to)) return true; return false; } @@ -531,11 +648,9 @@ bool slimepassable(cell *w, cell *c) { int ogroup = slimegroup(w); if(!ogroup) return false; bool hv = (group == ogroup); - - // don't go against the current - if(isWateryOrBoat(w) && isWateryOrBoat(c)) - return !againstCurrent(w, c); + if(sword::at(w, 0)) return false; + if(w->item) return false; // only travel to halfvines correctly @@ -544,29 +659,44 @@ bool slimepassable(cell *w, cell *c) { 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 false; - hv=true; + hv=(group == ogroup); } // only travel from halfvines correctly if(cellHalfvine(w)) { int i=0; for(int t=0; ttype; t++) if(w->mov[t] && w->mov[t]->wall == w->wall) i=t; - int z = i-c->spn[u]; if(z<0) z=-z; z%=6; + int z = i-c->spn(u); if(z<0) z=-z; z%=6; if(z>1) return false; - hv=true; + hv=(group == ogroup); } if(!hv) return false; return true; } +bool sharkpassable(cell *w, cell *c) { + if(w == c || !c) return true; + if(nonAdjacent(w,c)) return false; + if(isPlayerOn(w)) return true; + if(!isWatery(w)) return false; + if(sword::at(w, 0)) return false; + + // don't go against the current + if(isWateryOrBoat(w) && isWateryOrBoat(c)) + return !againstCurrent(w, c); + + return true; + } + bool canPushStatueOn(cell *c) { return passable(c, NULL, P_MONSTER) && c->wall != waBoat && !snakelevel(c) && - !isWorm(c->monst); + !isWorm(c->monst) && !isReptile(c->wall); } void moveBoat(cell *to, cell *from) { eWall x = to->wall; to->wall = from->wall; from->wall = x; to->mondir = neighborId(to, from); moveItem(from, to, false); + animateMovement(from, to, LAYER_BOAT); } void moveBoatIfUsingOne(cell *to, cell *from) { @@ -577,19 +707,32 @@ void moveBoatIfUsingOne(cell *to, cell *from) { } } -bool passable_for(eMonster m, cell *w, cell *from, int extra) { - if(w->monst && !(extra & P_MONSTER) && w != cwt.c) +bool passable_for(eMonster m, cell *w, cell *from, flagtype extra) { + if(w->monst && !(extra & P_MONSTER) && !isPlayerOn(w)) return false; if(m == moWolf) { return isIcyLand(w) && (isPlayerOn(w) || passable(w, from, extra)); } - if(normalMover(m) || isBug(m) || isDemon(m)) { - if((isWitch(m) || m == moEvilGolem) && w->land != laPower) + if(normalMover(m) || isBug(m) || isDemon(m) || m == moHerdBull) { + if((isWitch(m) || m == moEvilGolem) && w->land != laPower && w->land != laHalloween) return false; return passable(w, from, extra); } + if(isShark(m)) + return sharkpassable(w, from); if(isSlimeMover(m)) return slimepassable(w, from); + if(m == moKrakenH) { + if(extra & P_ONPLAYER) { + if(isPlayerOn(w)) return true; + } + if((extra & P_ONPLAYER) && isPlayerOn(w)) + return true; + if(pseudohept(w) || pseudohept(from)) return false; + if(w->wall != waBoat && !slimepassable(w, from)) return false; + forCellEx(w2, w) if(w2->wall != waBoat && !passable(w2, w, P_FISH | P_MONSTER)) return false; + return true; + } if(m == moEarthElemental) return passable(w, from, extra | P_EARTHELEM); if(m == moWaterElemental) @@ -605,6 +748,8 @@ bool passable_for(eMonster m, cell *w, cell *from, int extra) { return !pseudohept(w) && passable(w, from, extra|P_WIND|P_FISH); if(isBird(m)) return passable(w, from, extra | P_FLYING); + if(m == moReptile) + return passable(w, from, extra | P_REPTILE); if(isDragon(m)) return passable(w, from, extra | P_FLYING | P_WINTER); if(m == moAirElemental) @@ -618,7 +763,7 @@ bool passable_for(eMonster m, cell *w, cell *from, int extra) { if(isGolemOrKnight(m)) return passable(w, from, extra | P_ISFRIEND); if(isWorm(m)) - return passable(w, from, extra) && !cellUnstable(w) && ((m != moWorm) || !cellEdgeUnstable(w)); + return passable(w, from, extra) && !cellUnstable(w) && ((m != moWorm && m != moTentacle) || !cellEdgeUnstable(w)); return false; } @@ -629,11 +774,14 @@ eMonster movegroup(eMonster m) { return moWitch; } if(normalMover(m)) return moYeti; + if(m == moShark || m == moCShark) return moShark; if(isSlimeMover(m)) return moSlime; if(m == moEarthElemental) return moEarthElemental; if(isLeader(m)) return moPirate; + if(m == moButterfly) return moButterfly; if(isAngryBird(m)) return moEagle; if(isBird(m)) return moTameBomberbird; + if(m == moReptile) return moReptile; if(m == moGhost) return moGhost; if(m == moFriendlyGhost) return moFriendlyGhost; if(m == moGreaterShark) return moGreaterShark; @@ -643,12 +791,14 @@ eMonster movegroup(eMonster m) { if(isBug(m)) return m; if(m == moWaterElemental) return moWaterElemental; if(m == moAirElemental) return moAirElemental; + if(isBull(m)) return moRagingBull; return moNone; } void useup(cell *c) { c->wparam--; if(c->wparam == 0) { + drawParticles(c, c->wall == waFire ? 0xC00000 : winf[c->wall].color, 10, 50); if(c->wall == waTempFloor) c->wall = waChasm; else if(c->wall == waTempBridge) @@ -667,7 +817,7 @@ bool childbug = false; // is w killed if killed is killed? bool isChild(cell *w, cell *killed) { - if(isIvy(w) || isMutantIvy(w)) { + if(isAnyIvy(w->monst)) { int lim = 0; // printf("w = %p mondir = %d **\n", w, w->mondir); while(w != killed && w->mondir != NODIR) { @@ -676,7 +826,7 @@ bool isChild(cell *w, cell *killed) { printf("childbug!\n"); w->item = itBuggy; break; } - if(!isIvy(w) && !isMutantIvy(w)) { + if(!isAnyIvy(w->monst)) { return false; } w = w->mov[w->mondir]; @@ -687,8 +837,7 @@ bool isChild(cell *w, cell *killed) { return w == killed; } - -bool canAttack(cell *c1, eMonster m1, cell *c2, eMonster m2, int flags) { +bool canAttack(cell *c1, eMonster m1, cell *c2, eMonster m2, flagtype flags) { // cannot eat worms if((flags & AF_EAT) && isWorm(m2)) return false; @@ -701,10 +850,16 @@ bool canAttack(cell *c1, eMonster m1, cell *c2, eMonster m2, int flags) { if((flags & AF_ONLY_FBUG) && m2 != moPlayer && !isFriendlyOrBug(c2)) return false; if((flags & AF_ONLY_ENEMY) && (m2 == moPlayer || isFriendlyOrBug(c2))) return false; - if(m2 == moHedge && !(flags & (AF_STAB | AF_TOUGH | AF_EAT | AF_MAGIC | AF_LANCE))) + if(m2 == moHedge && !(flags & (AF_STAB | AF_TOUGH | AF_EAT | AF_MAGIC | AF_LANCE | AF_SWORD_INTO | AF_HORNS))) if(!checkOrb(m1, itOrbThorns)) return false; - if(!(flags & AF_NOSHIELD) && checkOrb(m2, itOrbShield)) return false; + if(m2 == moDraugr && !(flags & (AF_SWORD | AF_MAGIC | AF_SWORD_INTO | AF_HORNS))) return false; + + // if(m2 == moHerdBull && !(flags & AF_MAGIC)) return false; + if(isBull(m2) && !(flags & (AF_MAGIC | AF_HORNS | AF_SWORD_INTO))) return false; + if(m2 == moButterfly && !(flags & (AF_MAGIC | AF_BULL | AF_HORNS | AF_SWORD_INTO))) return false; + + if(!(flags & AF_NOSHIELD) && ((flags & AF_NEXTTURN) ? checkOrb2 : checkOrb)(m2, itOrbShield)) return false; if((flags & AF_STAB) && m2 != moHedge) if(!checkOrb(m1, itOrbThorns)) return false; @@ -717,22 +872,23 @@ bool canAttack(cell *c1, eMonster m1, cell *c2, eMonster m2, int flags) { if(flags & AF_APPROACH) { if(m2 == moLancer) ; + else if((flags & AF_HORNS) && checkOrb(m1, itOrbHorns)) ; else return false; } if(!(flags & AF_IGNORE_UNARMED) && isUnarmed(m1)) return false; if(m2 == moGreater || m2 == moGreaterM) - if(!(flags & AF_MAGIC)) return false; + if(!(flags & (AF_MAGIC | AF_SWORD_INTO | AF_HORNS))) return false; if(!(flags & AF_GUN)) { if(c1 && c2 && nonAdjacent(c1,c2) && !attackNonAdjacent(m1) && !attackNonAdjacent(m2) && - !checkOrb(m1, itOrb37) && !checkOrb(m1, itOrbGhost) && !checkOrb(m2, itOrbGhost)) + !checkOrb(m1, itOrb37) && !checkOrb(m1, itOrbAether) && !checkOrb(m2, itOrbAether)) return false; if(c1 && c2 && thruVine(c1,c2) && !attackThruVine(m1) && !attackThruVine(m2) && - !checkOrb(m1, itOrbGhost) && !checkOrb(m2, itOrbGhost)) + !checkOrb(m1, itOrbAether) && !checkOrb(m2, itOrbAether)) return false; if(!(flags & (AF_LANCE | AF_STAB | AF_BACK | AF_APPROACH))) @@ -741,40 +897,50 @@ bool canAttack(cell *c1, eMonster m1, cell *c2, eMonster m2, int flags) { } - if(m2 == moShadow) return false; + if(m2 == moShadow && !(flags & AF_SWORD)) return false; if(isWorm(m2) && m2 != moTentacleGhost && !isDragon(m2)) return false; // dragon can't attack itself, or player who mounted it if(c1 && c2 && isWorm(c1->monst) && isWorm(c2->monst) && wormhead(c1) == wormhead(c2) - && m1 != moPlayer) + && m1 != moTentacleGhost && m2 != moTentacleGhost) return false; // if(m2 == moTortoise && !(flags & AF_MAGIC)) return false; - if(m2 == moGreater || m2 == moGreaterM) - if(!(flags & AF_MAGIC)) return false; - if(m2 == moRoseBeauty) - if(!(flags & (AF_MAGIC | AF_LANCE | AF_GUN))) - if(!checkOrb(m1, itOrbSkunk) && !checkOrb(m1, itOrbGhost) && !checkOrb(m1, itOrbShield)) + if(!(flags & (AF_MAGIC | AF_LANCE | AF_GUN | AF_SWORD_INTO))) + if(!isMimic(m1)) + if(!checkOrb(m1, itOrbBeauty) && !checkOrb(m1, itOrbAether) && !checkOrb(m1, itOrbShield)) if(!c1 || !c2 || !withRose(c1,c2)) return false; if(m2 == moFlailer && !c2->stuntime) - if(!(flags & (AF_MAGIC | AF_TOUGH | AF_EAT | AF_LANCE | AF_BACK))) return false; + if(!(flags & (AF_MAGIC | AF_TOUGH | AF_EAT | AF_HORNS | AF_LANCE | AF_BACK | AF_SWORD_INTO))) return false; if(m2 == moVizier && c2->hitpoints > 1) - if(!(flags & (AF_MAGIC | AF_TOUGH | AF_EAT | AF_LANCE | AF_BACK | AF_FAST))) return false; + if(!(flags & (AF_MAGIC | AF_TOUGH | AF_EAT | AF_HORNS | AF_LANCE | AF_BACK | AF_FAST))) return false; return true; } -bool stalemate::isKilled(cell *w) { +bool stalemate1::isKilled(cell *w) { if(w->monst == moNone || w == killed) return true; if(!moveto) return false; + + for(int b=0; b<2; b++) + if((w == swordnext[b] || w == swordtransit[b]) && canAttack(moveto, who, w, w->monst, AF_SWORD)) + return true; + + if(isNeighbor(w, moveto) && moveto != comefrom) { + int wid = neighborId(moveto, w); + int wfrom = neighborId(moveto, comefrom); + int flag = AF_APPROACH; + if(wid >= 0 && wfrom >= 0 && angledist(moveto, wfrom, wid) >= 3) flag |= AF_HORNS; + if(canAttack(moveto, who, w, w->monst, flag)) return true; + } - if(canAttack(moveto, who, w, w->monst, AF_STAB)) - if(isNeighbor(w, comefrom) && isNeighbor(w, moveto) && moveto != comefrom) + if(isNeighbor(w, comefrom) && isNeighbor(w, moveto) && moveto != comefrom) + if(canAttack(moveto, who, w, w->monst, AF_STAB)) return true; if(who == moPlayer && (killed || moveto != comefrom) && mirrorkill(w)) return true; @@ -787,14 +953,32 @@ bool stalemate::isKilled(cell *w) { if(head1 == head2 && dragon::totalhp(head1) ==1) return true; } + if(w->monst == moKrakenT && killed && killed->monst == moKrakenT && killed->hitpoints) { + cell *head1 = w->mov[w->mondir]; + cell *head2 = killed->mov[killed->mondir]; + if(head1 == head2 && kraken::totalhp(head1) == 1) return true; + } + return false; } +bool stalemate::isKilled(cell *w) { + for(int f=0; ftype; i++) if(c1->mov[i] == c2) return true; return false; } +bool isNeighborCM(cell *c1, cell *c2) { + for(int i=0; itype; i++) if(createMov(c1, i) == c2) return true; + return false; + } + int neighborId(cell *ofWhat, cell *whichOne) { for(int i=0; itype; i++) if(ofWhat->mov[i] == whichOne) return i; return -1; @@ -810,16 +994,14 @@ bool mirrorkill(cell *c) { 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, int extra) { +bool flashWouldKill(cell *c, flagtype extra) { 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) { + if(c3 != c && (c3->monst || isPlayerOn(c3)) && !stalemate::isKilled(c3)) { bool b = canAttack(NULL, moWitchFlash, c3, c3->monst, AF_MAGIC | extra); if(b) return true; } @@ -834,9 +1016,9 @@ bool outlawNearby(cell *c, int dist) { for(int i=0; itype; i++) { cell *c2 = c->mov[i]; if(!c2) continue; - if(c2->monst == moOutlaw && c2 != stalemate::killed) + if(c2->monst == moOutlaw && !stalemate::isKilled(c2)) return true; - if((passable(c2, NULL, 0) || c2 == stalemate::killed) && + if((passable(c2, NULL, 0) || (c2->monst && stalemate::isKilled(c2))) && outlawNearby(c2, dist-1)) return true; } @@ -845,23 +1027,57 @@ bool outlawNearby(cell *c, int dist) { // int monstersnear(cell *c, cell *nocount = NULL, eMonster who = moPlayer, cell *pushto = NULL) { -int monstersnear() { +namespace stalemate { + bool anyKilled() { + for(int i=0; iwall == waBoat) || (cf->wall == waBoat && c->wall == waSea); + } + +bool krakensafe(cell *c) { + return items[itOrbFish] || items[itOrbAether] || + (c->item == itOrbFish && c->wall == waBoat) || + (c->item == itOrbAether && c->wall == waBoat); + } + +int monstersnear(stalemate1& sm) { + + cell *c = sm.moveto; bool eaten = false; - if(hardcore && stalemate::who == moPlayer) return 0; + if(hardcore && sm.who == moPlayer) return 0; int res = 0; bool fast = false; elec::builder b; if(elec::affected(c)) { which = moLightningBolt; res++; } - if(stalemate::who == moPlayer || items[itOrbEmpathy]) { - fast = (items[itOrbSpeed] && !(items[itOrbSpeed] & 1)); + if(sm.who == moPlayer || items[itOrbEmpathy]) { + fast = (items[itOrbSpeed] && (items[itOrbSpeed] & 1)); } - if(haveoutlaw) + if(havewhat&HF_OUTLAW) if(outlawNearby(c, 3)) { res++; which = moOutlaw; } @@ -871,7 +1087,7 @@ int monstersnear() { // consider monsters who attack from distance 2 if(c2) for(int u=2; u<=c2->type-2; u++) { - cell *c3 = c2->mov[(c->spn[t]+u) % c2->type]; + cell *c3 = c2->mov[(c->spn(t)+u) % c2->type]; if(!c3) continue; if(c3->monst != moWitchFlash) if(nonAdjacent(c, c2) || nonAdjacent(c2,c3) || thruVine(c,c2) || thruVine(c2,c3)) @@ -891,71 +1107,170 @@ int monstersnear() { if(isPlayerOn(c2) && items[itOrbFire]) continue; } // flashwitches cannot attack if it would kill another enemy - if(c3->monst == moWitchFlash && flashWouldKill(c3, false)) continue; - if(stalemate::killed && mirrorkill(c3)) continue; + if(c3->monst == moWitchFlash && flashWouldKill(c3, 0)) continue; + if(stalemate::anyKilled() && mirrorkill(c3)) continue; res++, which = c3->monst; } // consider normal monsters if(c2 && - isArmedEnemy(c2, stalemate::who) && + isArmedEnemy(c2, sm.who) && !stalemate::isKilled(c2) && - (c2->monst != moLancer || isUnarmed(stalemate::who))) { + (c2->monst != moLancer || isUnarmed(sm.who))) { + eMonster m = c2->monst; if(elec::affected(c2)) continue; if(fast && c2->monst != moWitchSpeed) continue; + // Krakens just destroy boats + if(c2->monst == moKrakenT && onboat(sm)) { + if(krakensafe(c)) continue; + else if(warningprotection() && res == 0) m = moWarning; + else continue; + } // they cannot attack through vines - if(!canAttack(c2, c2->monst, c, stalemate::who, 0)) continue; + if(!canAttack(c2, c2->monst, c, sm.who, AF_NEXTTURN)) continue; if(c2->monst == moWorm || c2->monst == moTentacle || c2->monst == moHexSnake) { if(passable_for(c2->monst, c, c2, 0)) eaten = true; else if(c2->monst != moHexSnake) continue; } - res++, which = c2->monst; + res++, which = m; } } - if(stalemate::who == moPlayer && res && (markOrb2(itOrbShield) || markOrb2(itOrbShell)) && !eaten) + if(sm.who == moPlayer && res && (markOrb2(itOrbShield) || markOrb2(itOrbShell)) && !eaten) res = 0; - if(stalemate::who == moPlayer && res && markOrb2(itOrbDomination) && c->monst) + if(sm.who == moPlayer && res && markOrb2(itOrbDomination) && c->monst) res = 0; return res; } -bool monstersnear(cell *c, cell *nocount, eMonster who, cell *pushto, cell *comefrom) { - dynamicval - x1(stalemate::moveto, c), - x2(stalemate::killed, nocount), - x3(stalemate::pushto, pushto), - x4(stalemate::comefrom, comefrom), - x5(cwt.c, who == moPlayer ? c : cwt.c); +namespace multi { bool aftermove; } + +int monstersnear2(); + +int lastkills; + +bool multimove() { + if(multi::cpid == 0) lastkills = tkills(); + if(!multi::playerActive(multi::cpid)) return !monstersnear2(); + cellwalker bcwt = cwt; + cwt = multi::player[multi::cpid]; + bool b = movepcto(multi::whereto[multi::cpid]); + if(b) { + multi::aftermove = true; + multi::player[multi::cpid] = cwt; + multi::whereto[multi::cpid].d = MD_UNDECIDED; + int curkills = tkills(); + multi::kills[multi::cpid] += (curkills - lastkills); + lastkills = curkills; + } + cwt = bcwt; + return b; + } + +namespace multi { bool checkonly = false; } + +bool swordConflict(const stalemate1& sm1, const stalemate1& sm2) { + if(items[itOrbSword] || items[itOrbSword2]) + for(int b=0; b<2; b++) + if(sm1.comefrom == sm2.swordlast[b] || sm1.comefrom == sm2.swordtransit[b] || sm1.comefrom == sm2.swordnext[b]) + if(sm1.moveto == sm2.swordlast[b] || sm1.moveto == sm2.swordtransit[b] || sm1.moveto == sm2.swordnext[b]) + return true; + return false; + } + +int monstersnear2() { + multi::cpid++; + int b = 0; + bool recorduse[ittypes]; + for(int i=0; i 8) + { b = 1; which = moAirball; } + } + + for(int i=0; !b && i 1) wcw = &multi::player[multi::cpid].c; + + dynamicval x5(*wcw, c); dynamicval x6(stalemate::nextturn, true); - dynamicval x7(stalemate::who, who); + dynamicval x7(sword::angle[multi::cpid], + who == moPlayer ? sword::shift(comefrom, c, sword::angle[multi::cpid]) : + sword::angle[multi::cpid]); + + if(who == moPlayer) for(int b=0; b<2; b++) { + sm.swordnext[b] = sword::pos(multi::cpid, b); + sm.swordtransit[b] = NULL; + if(sm.swordnext[b] && sm.swordnext[b] != sm.swordlast[b] && !isNeighbor(sm.swordlast[b], sm.swordnext[b])) + forCellEx(c2, sm.swordnext[b]) + if(c2 != c && c2 != comefrom && isNeighbor(c2, sm.swordlast[b])) + sm.swordtransit[b] = c2; + } + + stalemate::moves.push_back(sm); + + // dynamicval x7(stalemate::who, who); + + int b; if(who == moPlayer && c->wall == waBigStatue) { eWall w = comefrom->wall; c->wall = waNone; - comefrom->wall = cellUnstable(comefrom) ? waChasm : waBigStatue; - bool b = monstersnear(); + if(doesnotFall(comefrom)) comefrom->wall = waBigStatue; + b = monstersnear2(); comefrom->wall = w; c->wall = waBigStatue; - return b; } if(who == moPlayer && c->wall == waThumperOn) { c->wall = waNone; - bool b = monstersnear(); + b = monstersnear2(); c->wall = waThumperOn; - return b; } - return monstersnear(); + b = monstersnear2(); + stalemate::moves.pop_back(); + return b; } -void killIvy(cell *c) { +void killIvy(cell *c, eMonster who) { if(c->monst == moIvyDead) return; + if(checkOrb(who, itOrbStone)) c->wall = waPetrified, c->wparam = c->monst; c->monst = moIvyDead; // NEWYEARFIX 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]); + if(isIvy(c->mov[i]) && c->mov[i]->mondir == c->spn(i)) + killIvy(c->mov[i], who); } void prespill(cell* c, eWall t, int rad, cell *from) { @@ -971,21 +1286,23 @@ void prespill(cell* c, eWall t, int rad, cell *from) { } // slimedeath spill if((c->monst == moSlime || c->monst == moSlimeNextTurn) && t == waNone) { - c->wall = waNone; killMonster(c); + c->wall = waNone; attackMonster(c, 0, moNone); } // these walls block spilling completely if(c->wall == waIcewall || c->wall == waBarrier || c->wall == waWarpGate || - c->wall == waDeadTroll || c->wall == waDeadTroll2 || + c->wall == waDeadTroll || c->wall == waDeadTroll2 || c->wall == waDune || c->wall == waAncientGrave || c->wall == waThumperOff || c->wall == waThumperOn || c->wall == waFreshGrave || c->wall == waColumn || c->wall == waPartialFire || c->wall == waDeadwall || c->wall == waWaxWall || c->wall == waCamelot || c->wall == waRoundTable || c->wall == waBigStatue || c->wall == waRed1 || c->wall == waRed2 || c->wall == waRed3 || + c->wall == waTower || c->wall == waPalace || c->wall == waOpenGate || c->wall == waClosedGate || c->wall == waPlatform || c->wall == waStone || c->wall == waTempWall || c->wall == waTempFloor || c->wall == waTempBridge || c->wall == waSandstone || c->wall == waCharged || c->wall == waGrounded || - c->wall == waMetal || c->wall == waSaloon || c->wall == waFan) + c->wall == waMetal || c->wall == waSaloon || c->wall == waFan || + c->wall == waBarrowDig || c->wall == waBarrowWall) return; // these walls block further spilling if(c->wall == waCavewall || cellUnstable(c) || c->wall == waSulphur || @@ -994,7 +1311,7 @@ void prespill(cell* c, eWall t, int rad, cell *from) { c->wall == waVinePlant || isFire(c) || c->wall == waBonfireOff || c->wall == waVineHalfA || c->wall == waVineHalfB || c->wall == waCamelotMoat || c->wall == waSea || c->wall == waCTree || c->wall == waRubble || c->wall == waGargoyleFloor || c->wall == waGargoyle || - c->wall == waRose) + c->wall == waRose || c->wall == waPetrified) t = waTemporary; if(c->wall == waSulphur) { @@ -1002,7 +1319,12 @@ void prespill(cell* c, eWall t, int rad, cell *from) { for(int i=0; itype; i++) if(c->mov[i] && c->mov[i]->wall == waSulphurC) c->mov[i]->wall = waSulphur; } - + + if(isReptile(c->wall)) { + if(c->monst || isPlayerOn(c)) kills[moReptile]++; + else c->monst = moReptile, c->stuntime = 3, c->hitpoints = 3; + } + destroyHalfvine(c); c->wall = t; // destroy items... @@ -1056,10 +1378,19 @@ bool earthFloor(cell *c) { c->wall = waCIsland; return true; } - if(c->wall == waSea && c->land == laGridSea) + if(c->wall == waSea && c->land == laWarpSea) c->wall = waNone; - if(c->wall == waBoat && c->land == laGridSea) + if(c->wall == waBoat && c->land == laWarpSea) c->wall = waStrandedBoat; + if((c->wall == waBarrowDig || c->wall == waBarrowWall) && c->land == laBurial) { + c->item = itNone; + c->wall = waNone; + return true; + } + if(c->wall == waPlatform && c->land == laMountain) { + c->wall = waNone; + return true; + } return false; } @@ -1069,6 +1400,10 @@ bool earthWall(cell *c) { c->wall = waDeadwall; return true; } + if(c->wall == waNone && c->land == laMountain) { + c->wall = waPlatform; + return true; + } if(c->wall == waNone && c->land == laDesert) { c->item = itNone; c->wall = waDune; @@ -1084,6 +1419,11 @@ bool earthWall(cell *c) { c->wall = waRed3; return true; } + if(c->wall == waNone && c->land == laBurial) { + c->item = itNone; + c->wall = waBarrowDig; + return true; + } if(c->wall == waCIsland || c->wall == waCIsland2 || (c->wall == waNone && c->land == laOcean)) { c->item = itNone; c->wall = waSea; @@ -1093,11 +1433,10 @@ bool earthWall(cell *c) { return false; } -void snakepile(cell *c, eMonster m) { +bool snakepile(cell *c, eMonster m) { if(c->wall == waRed1 || c->wall == waOpenGate) c->wall = waRed2; else if(c->wall == waRed2) c->wall = waRed3; - else if(cellUnstableOrChasm(c)) - c->wall = waChasm; + else if(doesFall(c)) return false; else if((c->wall == waSea && c->land == laLivefjord)) c->wall = waNone; else if((c->wall == waSea && isWarped(c->land))) @@ -1112,14 +1451,15 @@ void snakepile(cell *c, eMonster m) { c->wall == waCIsland || c->wall == waCIsland2 || (c->wall == waSea && c->land == laOcean) || c->wall == waOpenPlate || c->wall == waClosePlate || - c->wall == waMineUnknown || c->wall == waMineOpen) { + c->wall == waMineUnknown || c->wall == waMineOpen || isReptile(c->wall)) { + if(isReptile(c->wall)) kills[moReptile]++; c->wall = waRed1; if(m == moDarkTroll) c->wall = waDeadfloor2; } else if(c->wall == waDeadfloor) c->wall = waDeadfloor2; else if(c->wall == waDeadfloor2) { - if(m == moDarkTroll && c->land == laDeadCaves) ; + if(m == moDarkTroll && c->land == laDeadCaves) return false; else c->wall = waDeadwall; } @@ -1130,7 +1470,7 @@ void snakepile(cell *c, eMonster m) { } else if(c->wall == waCavefloor) c->wall = waCavewall; else if(c->wall == waSea && c->land == laCaribbean) c->wall = waCIsland; - else if(c->wall == waSea && c->land == laWhirlpool) ; + else if(c->wall == waSea && c->land == laWhirlpool) return false; else if(c->wall == waSea) c->wall = waNone; else if(isWateryOrBoat(c) || c->wall == waFrozenLake) c->wall = waNone; else if(isWateryOrBoat(c) || c->wall == waFrozenLake) c->wall = waNone; @@ -1138,10 +1478,10 @@ void snakepile(cell *c, eMonster m) { destroyHalfvine(c, waRed1); if(c->wall == waRed1 && m == moDarkTroll) c->wall = waDeadfloor2; } + else return false; + return true; } -void explodeMine(cell *c); - bool makeflame(cell *c, int timeout, bool checkonly) { if(itemBurns(c->item)) { if(checkonly) return true; @@ -1149,13 +1489,15 @@ bool makeflame(cell *c, int timeout, bool checkonly) { } if(cellUnstable(c)) { if(checkonly) return true; - c->wall = waChasm; + doesFall(c); } - else if(c->wall == waChasm || c->wall == waOpenGate || c->wall == waRed2 || c->wall == waRed3) + else if(c->wall == waChasm || c->wall == waOpenGate || c->wall == waRed2 || c->wall == waRed3 || + c->wall == waTower) return false; else if(c->wall == waBoat) { if(checkonly) return true; addMessage(XLAT("%The1 burns!", winf[c->wall].name)); + drawFireParticles(c, 24); placeWater(c, c); if(isIcyLand(c)) HEAT(c) += 1; } @@ -1165,10 +1507,12 @@ bool makeflame(cell *c, int timeout, bool checkonly) { } else if(c->wall == waFrozenLake) { if(checkonly) return true; + drawParticles(c, MELTCOLOR, 8, 8); c->wall = waLake, HEAT(c) += 1; } else if(c->wall == waIcewall) { if(checkonly) return true; + drawParticles(c, MELTCOLOR, 8, 8); c->wall = waNone; } else if(c->wall == waMineMine) { @@ -1178,10 +1522,14 @@ bool makeflame(cell *c, int timeout, bool checkonly) { else if(c->wall != waCTree && c->wall != waBigTree && c->wall != waSmallTree && c->wall != waVinePlant && !passable(c, NULL, P_MONSTER | P_MIRROR) && c->wall != waSaloon && c->wall != waRose) return false; + // reptiles are able to use the water to put the fire off + else if(c->wall == waReptileBridge) return false; else { eWall w = eternalFire(c) ? waEternalFire : waFire; + if(!checkonly) drawFireParticles(c, 10); if(w == c->wall) return false; if(checkonly) return true; + if(isReptile(c->wall)) kills[moReptile]++; destroyHalfvine(c); if(!isFire(c)) c->wparam = 0; c->wall = w; @@ -1194,6 +1542,9 @@ void explodeMine(cell *c) { if(c->wall != waMineMine) return; + playSound(c, "explosion"); + drawFireParticles(c, 30, 150); + c->wall = waMineOpen; makeflame(c, 20, false); @@ -1201,7 +1552,7 @@ void explodeMine(cell *c) { cell *c2 = c->mov[i]; if(c2->wall == waRed2 || c2->wall == waRed3) c2->wall = waRed1; - else if(c2->wall == waDeadTroll || c2->wall == waDeadTroll2 || c2->wall == waGargoyle) { + else if(c2->wall == waDeadTroll || c2->wall == waDeadTroll2 || c2->wall == waPetrified || c2->wall == waGargoyle) { c2->wall = waNone; makeflame(c2, 10, false); } @@ -1210,32 +1561,54 @@ void explodeMine(cell *c) { c2->wall = waNone; makeflame(c2, 10, false); } + else if(c2->wall == waTower) + c2->wall = waRubble; + else if(c2->wall == waBarrowWall) + c2->wall = waBarrowDig; + else if(c2->wall == waBarrowDig) + c2->wall = waNone; else makeflame(c2, 20, false); } } +bool mayExplodeMine(cell *c, eMonster who) { + if(c->wall != waMineMine) return false; + if(ignoresPlates(who)) return false; + explodeMine(c); + return true; + } + void stunMonster(cell *c2) { - if(c2->monst != moSkeleton && !isMetalBeast(c2->monst) && c2->monst != moTortoise) + if(c2->monst != moSkeleton && !isMetalBeast(c2->monst) && c2->monst != moTortoise && + c2->monst != moReptile) { c2->hitpoints--; + if(c2->monst == moPrincess) + playSound(c2, princessgender() ? "hit-princess" : "hit-prince"); + } c2->stuntime = ( c2->monst == moFatGuard ? 2 : - c2->monst == moSkeleton && c2->land != laPalace ? 7 : + c2->monst == moSkeleton && c2->land != laPalace && c2->land != laHalloween ? 7 : isMetalBeast(c2->monst) ? 7 : c2->monst == moTortoise ? 7 : + c2->monst == moReptile ? 7 : + isPrincess(c2->monst) ? 6 : + // spear stunning + isBull(c2->monst) ? 3 : + (c2->monst == moGreater || c2->monst == moGreaterM) ? 5 : + c2->monst == moButterfly ? 2 : + c2->monst == moDraugr ? 1 : + c2->monst == moVizier ? 1 : + c2->monst == moHedge ? 1 : + c2->monst == moFlailer ? 1 : 3); + if(isBull(c2->monst)) c2->mondir = NODIR; + checkStunKill(c2); } bool attackJustStuns(cell *c2) { return isStunnable(c2->monst) && c2->hitpoints > 1; } -void killOrStunMonster(cell *c2) { - if(attackJustStuns(c2)) - stunMonster(c2); - else - killMonster(c2); - } - void moveEffect(cell *ct, cell *cf, eMonster m); void flameHalfvine(cell *c, int val) { @@ -1248,6 +1621,7 @@ void flameHalfvine(cell *c, int val) { } void minerEffect(cell *c) { + eWall ow = c->wall; if(c->wall == waOpenGate || c->wall == waFrozenLake || c->wall == waBoat || c->wall == waStrandedBoat || c->wall == waCIsland || c->wall == waCIsland2 || c->wall == waTrapdoor || @@ -1256,35 +1630,59 @@ void minerEffect(cell *c) { c->wall = waMineOpen; else if(c->wall == waRed2) c->wall = waRed1; else if(c->wall == waRed3) c->wall = waRed2; + else if(isReptile(c->wall)) + c->wparam = 1; // wake up next turn else if(c->wall == waTempFloor) c->wall = waChasm; else if(c->wall == waTempBridge) placeWater(c, NULL); - else if(cellUnstableOrChasm(c)) - c->wall = waChasm; + else if(doesFall(c)) + ow = waNone; else c->wall = waNone; + if(c->wall != ow && ow) drawParticles(c, winf[ow].color, 16); } bool isRatling(eMonster m) { return m == moRatling || m == moRatlingAvenger; } -void killMonster(cell *c) { +void killMutantIvy(cell *c, eMonster who) { + if(checkOrb(who, itOrbStone)) c->wall = waPetrified, c->wparam = moMutant; + removeIvy(c); + for(int i=0; itype; i++) + if(c->mov[i]->mondir == c->spn(i) && (isMutantIvy(c->mov[i]) || c->mov[i]->monst == moFriendlyIvy)) + killMutantIvy(c->mov[i], who); + } + +void killMonster(cell *c, eMonster who, flagtype deathflags) { eMonster m = c->monst; DEBB(DF_TURN, (debugfile,"killmonster %s", dnameof(m))); if(!m) return; + + if(m == moKrakenH) return; + if(m == moKrakenT) { + if(c->hitpoints && m == moKrakenT) kills[moKrakenT]++; + c->hitpoints = 0; + c->stuntime = 1; + cell *head = kraken::head(c); + if(kraken::totalhp(head) == 0) kraken::kill(head, who); + return; + } + if(isDragon(m)) { + if(c->hitpoints && m != moDragonHead) kills[moDragonTail]++; c->hitpoints = 0; c->stuntime = 1; cell *head = dragon::findhead(c); - if(dragon::totalhp(head) == 0) dragon::kill(head); + if(dragon::totalhp(head) == 0) dragon::kill(head, who); } if(isWorm(c) && m != moTentacleGhost) return; + int pcount = (deathflags & AF_FALL) ? 0 : 16; if(m == moShadow) return; #ifdef HASLINEVIEW - if(!isBug(m) && !isIvy(m) && !isMutantIvy(m)) { + if(!isBug(m) && !isAnyIvy(m)) { conformal::killhistory.push_back(make_pair(c,m)); } #endif @@ -1298,26 +1696,33 @@ void killMonster(cell *c) { if(isPrincess(m)) m = moPrincess; if(m == moTentacleGhost) m = moGhost; if(!isBulletType(m)) kills[m]++; + + if(!c->item) if(m == moButterfly && (deathflags & AF_BULL)) + c->item = itBull; if(isRatling(m) && c->wall == waBoat) { bool avenge = false; for(int i=0; itype; i++) if(!isWarped(c->mov[i]->land)) avenge = true; - if(avenge) { if(cheater) printf("avengers called\n"); avengers += 2; } + if(avenge) { avengers += 2; } } - if(isMutantIvy(m)) { - removeIvy(c); - for(int i=0; itype; i++) - if(c->mov[i]->mondir == c->spn[i] && isMutantIvy(c->mov[i])) - killMonster(c->mov[i]); + if(isMutantIvy(m) || m == moFriendlyIvy) { + pcount = 0; + killMutantIvy(c, who); } if(m == moPrincess) { princess::info *i = princess::getPrincessInfo(c); if(i) { i->princess = NULL; - if(i->bestdist == OUT_OF_PALACE) items[itSavedPrincess]--; + if(i->bestdist == OUT_OF_PALACE) { + items[itSavedPrincess]--; + if(items[itSavedPrincess] == 0) { + items[itOrbLove] = 0; + princess::reviveAt = gold() + 20; + } + } if(princess::challenge) showMissionScreen(); } } @@ -1329,7 +1734,8 @@ void killMonster(cell *c) { for(int i=0; itype; i++) { cell *c2 = c->mov[i]; if(c2->wall == waPlatform || c2->wall == waGargoyle || c2->wall == waBarrier || - c2->wall == waDeadTroll || c2->wall == waDeadTroll2 || c2->wall == waTrunk) + c2->wall == waDeadTroll || c2->wall == waDeadTroll2 || c2->wall == waTrunk || + c2->wall == waPetrified || isAlchAny(c2->wall)) connected = true; } } @@ -1341,6 +1747,8 @@ void killMonster(cell *c) { } if(connected) { + pcount = 0; + playSound(c, "die-troll"); destroyHalfvine(c); if(cellUnstableOrChasm(c)) c->wall = waGargoyleFloor; else if(isWatery(c)) c->wall = waGargoyleBridge; @@ -1351,7 +1759,13 @@ void killMonster(cell *c) { if(m == moTroll) { destroyHalfvine(c); - c->wall = cellUnstableOrChasm(c) ? waChasm : waDeadTroll; + if(doesnotFall(c)) { + pcount = 0; + playSound(c, "die-troll"); + if(isReptile(c->wall)) kills[moReptile]++; + c->wall = waDeadTroll; + } + else fallingFloorAnimation(c, waDeadTroll, m), pcount = 0; c->item = itNone; for(int i=0; itype; i++) if(c->mov[i]) { c->mov[i]->item = itNone; @@ -1361,32 +1775,72 @@ void killMonster(cell *c) { } if(m == moFjordTroll || m == moForestTroll || m == moStormTroll) { destroyHalfvine(c); - c->wall = cellUnstableOrChasm(c) ? waChasm : waDeadTroll2; + if(doesnotFall(c)) { + if(isReptile(c->wall)) kills[moReptile]++; + c->wall = waDeadTroll2; + pcount = 0; + playSound(c, "die-troll"); + } + else fallingFloorAnimation(c, waDeadTroll2, m), pcount = 0; + c->wparam = m; c->item = itNone; } if(m == moMiner) { + pcount = 32; + playSound(c, "splash" + pick12()); destroyHalfvine(c); minerEffect(c); for(int i=0; itype; i++) if(passable(c->mov[i], c, P_MONSTER | P_MIRROR | P_CLIMBUP | P_CLIMBDOWN)) { destroyHalfvine(c->mov[i]); minerEffect(c->mov[i]); if(c->mov[i]->monst == moSlime || c->mov[i]->monst == moSlimeNextTurn) - killMonster(c->mov[i]); + killMonster(c->mov[i], who); } } + if(m == moOrangeDog) { + if(pcount) for(int i=0; i<8; i++) { + drawParticle(c, 0xFFFFFF); + drawParticle(c, 0x202020); + } + pcount = 0; + } + if(m == moDraugr) { + if(pcount) drawParticles(c, 0x804000, 8); + pcount = 0; + } + if(m == moPalace) { + if(pcount) { + pcount = 4; + for(int i=0; i<12; i++) drawParticle(c, 0x20C020); + } + } + if(m == moPrincess) { + playSound(c, princessgender() ? "die-princess" : "die-prince"); + } if(m == moVineBeast) { destroyHalfvine(c); - c->wall = cellUnstableOrChasm(c) ? waChasm : waVinePlant; + if(doesnotFall(c)) { + if(isReptile(c->wall)) kills[moReptile]++; + c->wall = waVinePlant; + pcount = 0; + playSound(c, "die-vinebeast"); + } + else fallingFloorAnimation(c, waVinePlant, m), pcount = 0; c->item = itNone; } if(isBird(m)) moveEffect(c, c, moDeadBird); if(m == moBomberbird || m == moTameBomberbird) { + pcount = 0; + playSound(c, "die-bomberbird"); if(c->wall == waNone || c->wall == waMineUnknown || c->wall == waMineOpen || c->wall == waCavefloor || c->wall == waDeadfloor || c->wall == waDeadfloor2 || c->wall == waRubble || c->wall == waGargoyleFloor || c->wall == waCIsland || c->wall == waCIsland2 || - c->wall == waStrandedBoat || c->wall == waRed1 || c->wall == waGiantRug) + c->wall == waStrandedBoat || c->wall == waRed1 || c->wall == waGiantRug) { c->wall = waMineMine; + if(c->item) explodeMine(c); + else if(c->land == laMinefield) c->landparam |= 1; + } else if(c->wall == waFrozenLake) c->wall = waLake; else if(c->wall == waGargoyleBridge) @@ -1397,35 +1851,64 @@ void killMonster(cell *c) { c->mov[i]->wall = waRed2; c->item = itNone; } - if(isFire(c)) { + eWall w = c->wall; + if(isFire(c) || c->wall == waRose || isReptile(c->wall)) { c->wall = waMineMine; explodeMine(c); - } + if(isReptile(w)) kills[moReptile]++; + if(w == waReptile) c->wall = waChasm; + if(w == waReptileBridge) placeWater(c, NULL); + } } if(m == moVineSpirit) { + pcount = 32; + playSound(c, "die-vinespirit"); destroyHalfvine(c); if(!isFire(c)) c->wall = waNone; } - if(m == moRedTroll) snakepile(c, m); + if(m == moRedTroll) { + playSound(c, "die-troll"); + if(doesFall(c)) fallingFloorAnimation(c, waRed1, m), pcount = 0; + else if(snakepile(c, m)) pcount = 0; + } if(m == moDarkTroll) { - if(c->wall == waRed1 || c->wall == waRed2 || c->wall == waRed3) - c->wall = waDeadwall; - else { - snakepile(c, m); - } + playSound(c, "die-troll"); + if(doesFall(c)) fallingFloorAnimation(c, waDeadwall, m), pcount = 0; + else if(c->wall == waRed1 || c->wall == waRed2 || c->wall == waRed3) + c->wall = waDeadwall, pcount = 0; + else if(snakepile(c, m)) + pcount = 0; } if(isWitch(m) && (isFire(c) || passable(c, NULL, P_MONSTER)) && !c->item) { if(m == moWitchFire) c->item = itOrbFire; if(m == moWitchFlash) c->item = itOrbFlash; - if(m == moWitchGhost) c->item = itOrbGhost; + if(m == moWitchGhost) c->item = itOrbAether; if(m == moWitchWinter) c->item = itOrbWinter; if(m == moWitchSpeed) c->item = itOrbSpeed; if(isFire(c) && itemBurns(c->item)) c->item = itNone; } - if(m == moFireFairy) + if(checkOrb(who, itOrbStone)) { + destroyHalfvine(c); + if(doesnotFall(c)) { + if(isReptile(c->wall)) kills[moReptile]++; + c->wall = waPetrified; + pcount = 0; + playSound(c, "die-troll"); + } + else fallingFloorAnimation(c, waPetrified, m), pcount = 0; + c->wparam = m; + c->item = itNone; + } + if(m == moFireFairy) { + drawFireParticles(c, 16); pcount = 0; + playSound(c, "die-fairy"); + playSound(c, "fire"); makeflame(c, 50, false); - if(m == moPyroCultist && c->item == itNone && c->wall != waChasm) { + } + if(c->monst == moMetalBeast2 && !c->item && who == moLightningBolt && c->wall != waPetrified && c->wall != waChasm) + c->item = itFulgurite; // this is actually redundant in many cases + if(m == moPyroCultist && c->item == itNone && c->wall != waChasm && c->wall != waPetrified) { // a reward for killing him before he shoots! c->item = itOrbDragon; } @@ -1449,7 +1932,13 @@ void killMonster(cell *c) { } if(!toomany) c->item = itCompass; } - if(m == moSlime) { c->monst = moNone; spill(c, c->wall, 2); } + if(m == moSlime) { + pcount = 0; + drawParticles(c, winf[c->wall].color, 80, 200); + playSound(c, "splash" + pick12()); + c->monst = moNone; + spill(c, c->wall, 2); + } // if(c->monst == moShark) c->heat += 1; // if(c->monst == moGreaterShark) c->heat += 10; if(isIcyLand(c)) { @@ -1460,15 +1949,16 @@ void killMonster(cell *c) { if(m == moLesser && !(kills[m] % 10)) degradeDemons(); if(isIvy(c)) { + pcount = 0; eMonster m = c->monst; /*if((m == moIvyBranch || m == moIvyHead) && c->mov[c->mondir]->monst == moIvyRoot) ivynext(c, moIvyNext); */ - killIvy(c); + killIvy(c, who); 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]) + 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; @@ -1497,41 +1987,181 @@ void killMonster(cell *c) { airmap.push_back(make_pair(dcal[i],0)); buildAirmap(); } + drawParticles(c, minf[m].color, pcount); + if(deathflags & AF_FALL) + fallingMonsterAnimation(c, m); } -void killWithMessage(cell *c, bool orStun, eMonster killer) { - if(killer) { - if((orStun && attackJustStuns(c)) || isDragon(c->monst)) - addMessage(XLAT("%The1 attacks %the2!", killer, c->monst)); - else messageKill(killer, c->monst); +void fightmessage(eMonster victim, eMonster attacker, bool stun, int flags) { + + if(isBird(attacker)) { + playSound(NULL, "hit-axe"+pick123()); + addMessage(XLAT("%The1 claws %the2!", attacker, victim)); } + + else if(isGhost(attacker)) + addMessage(XLAT("%The1 scares %the2!", attacker, victim)); + + else if(isSlimeMover(attacker) && !stun) { + playSound(NULL, "hit-crush"+pick123()); + addMessage(XLAT("%The1 eats %the2!", attacker, victim)); + } + + else if(flags & AF_EAT) { + playSound(NULL, "hit-crush"+pick123()); + addMessage(XLAT("%The1 eats %the2!", attacker, victim)); + } + + else if(attacker == moLancer) { + playSound(NULL, "hit-rose"); + addMessage(XLAT("%The1 pierces %the2!", attacker, victim)); + } + + else if(attacker == moEarthElemental) { + playSound(NULL, "hit-crush"+pick123()); + addMessage(XLAT("%The1 punches %the2!", attacker, victim)); + } + + else if(attacker == moPlayer) { + if(flags & (AF_SWORD | AF_SWORD_INTO)) { + playSound(NULL, "hit-axe"+pick123()); + addMessage(XLAT("You slash %the1.", victim)); + if(victim == moGoblin) + achievement_gain("GOBLINSWORD"); + } + else if(victim == moKrakenT || victim == moDragonTail || victim == moDragonHead) { + playSound(NULL, "hit-sword"+pick123()); + addMessage(XLAT("You hit %the1.", victim)); // normal + } + else if(stun && victim == moVizier) { + playSound(NULL, "hit-sword"+pick123()); + addMessage(XLAT("You hit %the1.", victim)); // normal + } + else if(stun) { + playSound(NULL, "hit-sword"+pick123()); + addMessage(XLAT("You stun %the1.", victim)); // normal + } + else if(isNonliving(victim)) { + playSound(NULL, "hit-sword"+pick123()); + addMessage(XLAT("You destroy %the1.", victim)); // normal + } + else if(flags & AF_STAB) { + playSound(NULL, "hit-axe"+pick123()); + addMessage(XLAT("You stab %the1.", victim)); // normal + } + else if(flags & AF_APPROACH) { + playSound(NULL, "hit-sword"+pick123()); + if(victim == moLancer) + addMessage(XLAT("You trick %the1.", victim)); // normal + else + addMessage(XLAT("You pierce %the1.", victim)); // normal + } + else { + playSound(NULL, "hit-sword"+pick123()); + addMessage(XLAT("You kill %the1.", victim)); // normal + } + } + + else { + if(victim == moKrakenT || victim == moDragonTail || victim == moDragonHead) { + playSound(NULL, "hit-crush"+pick123()); + addMessage(XLAT("%The1 hits %the2.", attacker, victim)); // normal + } + else if(stun && victim == moVizier) { + playSound(NULL, "hit-sword"+pick123()); + addMessage(XLAT("%The1 hits %the2.", attacker, victim)); // normal + } + else if(stun) { + playSound(NULL, "hit-sword"+pick123()); + addMessage(XLAT("%The1 attacks %the2!", attacker, victim)); // normal + } + else if(isNonliving(victim)) { + playSound(NULL, "hit-sword"+pick123()); + addMessage(XLAT("%The1 destroys %the2!", attacker, victim)); // normal + } + else if(flags & AF_STAB) { + playSound(NULL, "hit-axe"+pick123()); + addMessage(XLAT("%The1 stabs %the2.", attacker, victim)); + } + else if(flags & AF_APPROACH) { + playSound(NULL, "hit-sword"+pick123()); + addMessage(XLAT("%The1 tricks %the2.", attacker, victim)); + } + else { + playSound(NULL, "hit-sword"+pick123()); + addMessage(XLAT("%The1 kills %the2!", attacker, victim)); + } + } + } + +void fallMonster(cell *c, flagtype flags) { + attackMonster(c, flags, moNone); + } + +bool notthateasy(eMonster m) { + return + isMultitile(m) || isStunnable(m) || m == moDraugr; + } + +bool attackMonster(cell *c, flagtype flags, eMonster killer) { + + if((flags & AF_GETPLAYER) && isPlayerOn(c)) { + killThePlayerAt(killer, c, flags); + return true; + } + eMonster m = c->monst; int tk = tkills(); int tkt = killtypes(); - if(orStun) - killOrStunMonster(c); - else - killMonster(c); + bool dostun = (flags & AF_ORSTUN) && attackJustStuns(c); + + if((flags & AF_HORNS) && hornStuns(c)) dostun = true; + + if(c->monst == moSkeleton && (flags & AF_SWORD)) dostun = false; + + bool eu = elementalUnlocked(); + bool tu = trollUnlocked(); + + if(flags & AF_MSG) fightmessage(m, killer, dostun, flags); + if(dostun) + stunMonster(c); + else + killMonster(c, killer, flags); + int ntk = tkills(); int ntkt = killtypes(); - - if(tkt < 20 && ntkt >= 20) - addMessage(XLAT("You hear a distant roar!")); - - if(tk == 0 && ntk > 0 && !tactic::on) - addMessage(XLAT("That was easy, but groups could be dangerous.")); - if(tk < 10 && ntk >= 10 && !tactic::on) + if(tkt < 20 && ntkt >= 20) { + addMessage(XLAT("You hear a distant roar!")); + playSound(NULL, "message-roar"); + } + + if(tk == 0 && ntk > 0 && !tactic::on && !euclid && !sphere) { + if(notthateasy(m)) + addMessage(XLAT("Quite tough, for your first fight.")); + else + addMessage(XLAT("That was easy, but groups could be dangerous.")); + } + + if(tk < 10 && ntk >= 10 && !tactic::on && !euclid && !sphere) addMessage(XLAT("Good to know that your fighting skills serve you well in this strange world.")); - if(tk < 50 && ntk >= 50) + if(tk < 50 && ntk >= 50 && !euclid && !sphere) addMessage(XLAT("You wonder where all these monsters go, after their death...")); - if(tk < 100 && ntk >= 100) + if(tk < 100 && ntk >= 100 && !euclid && !sphere) addMessage(XLAT("You feel that the souls of slain enemies pull you to the Graveyard...")); + if(!tu && trollUnlocked()) { + playSound(c, "message-troll"); + addMessage(XLAT("%The1 says, \"I die, but my clan in Trollheim will avenge me!\"", m)); + } + + if(!eu && elementalUnlocked()) + addMessage(XLAT("After killing %the1, you feel able to reach the Elemental Planes!", m)); + if(m == moVizier && c->monst != moVizier && kills[moVizier] == 1) { addMessage(XLAT("Hmm, he has been training in the Emerald Mine. Interesting...")); princess::forceMouse = true; @@ -1539,6 +2169,8 @@ void killWithMessage(cell *c, bool orStun, eMonster killer) { if(m == moIvyRoot && ntk>tk) achievement_gain("IVYSLAYER"); + + return ntk > tk; } void pushMonster(cell *ct, cell *cf) { @@ -1561,23 +2193,39 @@ bool destroyHalfvine(cell *c, eWall newwall, int tval) { int coastvalEdge(cell *c) { return coastval(c, laIvoryTower); } -bool cellEdgeUnstable(cell *c) { - if(!isGravityLand(c->land) || (c->wall != waNone && c->wall != waCanopy)) return false; - int d = coastvalEdge(c); - for(int i=0; itype; i++) - if(coastvalEdge(c->mov[i]) == d-1) { +int gravityLevel(cell *c) { + if(c->land == laIvoryTower || c->land == laEndorian) + return coastval(c, laIvoryTower); + if(c->land == laDungeon) + return -coastval(c, laIvoryTower); + if(c->land == laMountain) + return 1-celldistAlt(c); + return 0; + } + +bool canUnstable(eWall w, flagtype flags) { + return w == waNone || w == waCanopy || w == waOpenPlate || w == waClosePlate || + w == waOpenGate || ((flags & MF_STUNNED) && (w == waLadder || w == waTrunk || w == waSolidBranch || w == waWeakBranch + || w == waBigBush || w == waSmallBush)); + } + +bool cellEdgeUnstable(cell *c, flagtype flags) { + if(!isGravityLand(c->land) || !canUnstable(c->wall, flags)) return false; + int d = gravityLevel(c); + for(int i=0; itype; i++) if(c->mov[i]) { + if(isAnyIvy(c->mov[i]->monst) && + c->land == laMountain && !(flags & MF_IVY)) return false; + if(gravityLevel(c->mov[i]) == d-1) { if(againstWind(c->mov[i], c)) return false; - if(!passable(c->mov[i], NULL, P_MONSTER) && - !isFire(c->mov[i])) + if(!passable(c->mov[i], NULL, P_MONSTER | P_DEADLY)) return false; if(isWorm(c->mov[i])) return false; } + } return true; } -bool havemouse, havewarp; - // find worms and ivies void settemp(cell *c) { temps.push_back(c); tempval.push_back(c->monst); c->monst = moNone; @@ -1736,7 +2384,7 @@ map rosemap; // 3 - wave phase 2 int rosedist(cell *c) { - if(!haverose) return 0; + if(!(havewhat&HF_ROSE)) return 0; int&r (rosemap[c]); if((r&7) == 7) return 0; if(r&3) return (r&3)-1; @@ -1759,7 +2407,7 @@ void buildRosemap() { rosephase++; rosephase &= 7; - if(haverose && !rosephase) { + if((havewhat&HF_ROSE) && !rosephase) { rosewave++; for(int k=0; k= gravityLevel(from)) return true; + if(cellUnstable(to)) return true; + return false; + } + +// pathdist begin +void computePathdist(eMonster param) { + int pqs = size(pathq); + for(int i=0; ipathdist = PINFD; + pathq.clear(); + pathqm.clear(); + reachedfrom.clear(); + + for(int i=0; ipathdist = isPlayerOn(targets[i]) ? 0 : 1; + reachedfrom.push_back(hrand(targets[i]->type)); + } + + int qtarg = size(targets); + + for(int qb=0; qb < size(pathq); qb++) { + int fd = reachedfrom[qb] + 3; + cell *c = pathq[qb]; + if(c->monst && !isBug(c) && !(isFriendly(c) && !c->stuntime)) { + pathqm.push_back(c); + continue; // no paths going through monsters + } + for(int i=0; imonst && c != pc && getMount(i) && !isDragon(c->monst) && sameMonster(c, pc)) { + // don't treat the Worm you are riding as passable + pathqm.push_back(c); + continue; + } + } + if(c->cpdist > 7 && !(c->land == laTrollheim && turncount < c->landparam)) continue; + int d = c->pathdist; + if(d == PINFD - 1) continue; + for(int j=0; jtype; j++) { + int i = (fd+j) % c->type; + // printf("i=%d cd=%d\n", i, c->mov[i]->cpdist); + + if(c->mov[i] && c->mov[i]->pathdist == PINFD && + passable(c->mov[i], (qbmov[i]) && !thruVine(c,c->mov[i]) ?NULL:c, P_MONSTER | P_REVDIR)) { + + if(qb >= qtarg) { + if(param == moTortoise && nogoSlow(c, c->mov[i])) continue; + if(param == moIvyRoot && strictlyAgainstGravity(c, c->mov[i], false, MF_IVY)) continue; + if(param == moWorm && (cellUnstable(c) || cellEdgeUnstable(c))) continue; + } + + c->mov[i]->pathdist = d+1; + pathq.push_back(c->mov[i]); reachedfrom.push_back(c->spn(i)); + } + } + } + } +// pathdist end + +vector > butterflies; + +void addButterfly(cell *c) { + for(int i=0; ipathdist = INFD; - pathq.clear(); - recalcTide = false; for(int i=0; icpdist == 0) continue; c->cpdist = 0; checkTide(c); @@ -1842,20 +2554,27 @@ void bfs() { int distlimit = getDistLimit(); - if(getMount() && (cwt.c->monst == moTentacle || cwt.c->monst == moTentaclewait || cwt.c->monst == moTentacleEscaping)) - worms.push_back(cwt.c); + for(int i=0; imonst == moTentacle || c->monst == moTentaclewait || c->monst == moTentacleEscaping)) + worms.push_back(c); + } int qb = 0; while(true) { + if(qb == size(dcal)) break; int i, fd = reachedfrom[qb] + 3; cell *c = dcal[qb++]; + int d = c->cpdist; if(d == distlimit) { 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) continue; - if(isWarped(c2->land)) havewarp = true; + if(isWarped(c2->land)) havewhat |= HF_WARP; if((c->wall == waBoat || c->wall == waSea) && (c2->wall == waSulphur || c2->wall == waSulphurC)) @@ -1867,19 +2586,27 @@ void bfs() { // remove treasures if(c2->item && c2->cpdist == distlimit && itemclass(c2->item) == IC_TREASURE && c2->item != itBabyTortoise && - (items[c2->item] >= (chaosmode?10:20) + currentLocalTreasure || getGhostcount() >= 2)) + (items[c2->item] >= (chaosmode?10:20) + currentLocalTreasure || getGhostcount() >= 2)) { c2->item = itNone; + if(c2->land == laMinefield) { c2->landparam &= ~3; } + } if(c2->item == itBombEgg && c2->cpdist == distlimit && items[itBombEgg] >= c2->landparam) { c2->item = itNone; + c2->landparam |= 2; + c2->landparam &= ~1; if(!c2->monst) c2->monst = moBomberbird; } + if(c2->item == itBarrow && c2->cpdist == distlimit && c2->wall != waBarrowDig) { + c2->item = itNone; + } + if(c2->item == itLotus && c2->cpdist == distlimit && items[itLotus] >= getHauntedDepth(c2)) { c2->item = itNone; } - if(c2->item == itMutant2) { + if(c2->item == itMutant2 && timerghost) { bool rotten = true; for(int i=0; itype; i++) if(c2->mov[i] && c2->mov[i]->monst == moMutant) @@ -1891,62 +2618,82 @@ void bfs() { turncount-c2->landparam > 500)) c2->item = itNone; + if(c2->item == itTrollEgg && c2->cpdist == distlimit && !shmup::on && c2->landparam && turncount-c2->landparam > 650) + c2->item = itNone; + if(c2->item == itMutant && c2->cpdist == distlimit && items[itMutant] >= c2->landparam) { c2->item = itNone; } - if(c2->item == itEdge && c2->cpdist == distlimit && items[itEdge] >= c2->landparam) { + if(c2->item == itIvory && c2->cpdist == distlimit && items[itIvory] >= c2->landparam) { + c2->item = itNone; + } + + if(c2->item == itAmethyst && c2->cpdist == distlimit && items[itAmethyst] >= -celldistAlt(c2)/5) { c2->item = itNone; } if(!keepLightning) c2->ligon = 0; dcal.push_back(c2); - reachedfrom.push_back(c->spn[i]); + reachedfrom.push_back(c->spn(i)); checkTide(c2); if(c2->wall == waBigStatue && c2->land != laTemple) statuecount++; + + if(cellHalfvine(c2) && isWarped(c2)) { + addMessage(XLAT("%The1 is destroyed!", c2->wall)); + destroyHalfvine(c2); + } if(c2->wall == waCharged) elec::havecharge = true; + if(c2->land == laStorms) elec::haveelec = true; - if(c2->land == laWhirlpool) havewhirlpool = true; - if(c2->land == laWhirlwind) havewhirlwind = true; + if(c2->land == laWhirlpool) havewhat |= HF_WHIRLPOOL; + if(c2->land == laWhirlwind) havewhat |= HF_WHIRLWIND; + if(c2->land == laPrairie) havewhat |= HF_RIVER; - if(c2->wall == waRose) haverose = true; + if(c2->wall == waRose) havewhat |= HF_ROSE; - if(hadrose && (rosemap[c2] & 3)) haverose = true; + if((hadwhat & HF_ROSE) && (rosemap[c2] & 3)) havewhat |= HF_ROSE; if(c2->monst) { if(isHaunted(c2->land) && c2->monst != moGhost && c2->monst != moZombie && c2->monst != moNecromancer) survivalist = false; if(c2->monst == moHexSnake || c2->monst == moHexSnakeTail) { - havehex = true; + havewhat |= HF_HEX; if(c2->monst == moHexSnake) hexsnakes.push_back(c2); else findWormIvy(c2); } - else if(c2->monst == moDragonHead || c2->monst == moDragonTail) { - havedragon = true; + else if(c2->monst == moKrakenT || c2->monst == moKrakenH) { + havewhat |= HF_KRAKEN; } + else if(c2->monst == moDragonHead || c2->monst == moDragonTail) { + havewhat |= HF_DRAGON; + } + else if(c2->monst == moWitchSpeed) + havewhat |= HF_FAST; else if(c2->monst == moMutant) - havemutant = true; + havewhat |= HF_MUTANT; else if(c2->monst == moOutlaw) - haveoutlaw = true; + havewhat |= HF_OUTLAW; else if(isGhostMover(c2->monst)) ghosts.push_back(c2); else if(isWorm(c2) || isIvy(c2)) findWormIvy(c2); else if(isBug(c2)) { - havebugs = true; + havewhat |= HF_BUG; targets.push_back(c2); } else if(isFriendly(c2)) { - if(c2->monst != moMouse && !markEmpathy(itOrbInvis)) targets.push_back(c2); + if(c2->monst != moMouse && !markEmpathy(itOrbInvis) && + !c2->stuntime) targets.push_back(c2); if(c2->monst == moGolem) golems.push_back(c2); if(c2->monst == moFriendlyGhost) golems.push_back(c2); if(c2->monst == moKnight) golems.push_back(c2); if(c2->monst == moTameBomberbird) golems.push_back(c2); - if(c2->monst == moMouse) { golems.push_back(c2); havemouse = true; } + if(c2->monst == moMouse) { golems.push_back(c2); havewhat |= HF_MOUSE; } if(c2->monst == moPrincess || c2->monst == moPrincessArmed) golems.push_back(c2); if(c2->monst == moIllusion) { if(items[itOrbIllusion]) items[itOrbIllusion]--; @@ -1954,18 +2701,28 @@ void bfs() { } if(isMimic(c2)) mirrors.push_back(c2); } - else if(isAngryBird(c2->monst)) haveeagles = true; - else if(isLeader(c2->monst)) haveleader = true; - else if(c2->monst == moEarthElemental) haveearth = true; - else if(c2->monst == moWaterElemental) havewater = true; + else if(c2->monst == moButterfly) { + addButterfly(c2); + } + else if(isAngryBird(c2->monst)) { + havewhat |= HF_BIRD; + if(c2->monst == moBat) havewhat |= HF_BATS | HF_EAGLES; + if(c2->monst == moEagle) havewhat |= HF_EAGLES; + } + else if(c2->monst == moReptile) havewhat |= HF_REPTILE; + else if(isLeader(c2->monst)) havewhat |= HF_LEADER; + else if(c2->monst == moEarthElemental) havewhat |= HF_EARTH; + else if(c2->monst == moWaterElemental) havewhat |= HF_WATER; + else if(c2->monst == moShark || c2->monst == moCShark) havewhat |= HF_SHARK; else if(c2->monst == moAirElemental) - haveair = true, airmap.push_back(make_pair(c2,0)); + havewhat |= HF_AIR, airmap.push_back(make_pair(c2,0)); } // pheromones! if(c2->land == laHive && c2->landparam >= 50 && c2->wall != waWaxWall) - havebugs = true; + havewhat |= HF_BUG; if(c2->wall == waThumperOn) targets.push_back(c2); + } } } @@ -1975,48 +2732,23 @@ void bfs() { for(int i=0; ipathdist = isPlayerOn(targets[i]) ? 0 : 1; - pathq.push_back(targets[i]); - reachedfrom.push_back(hrand(targets[i]->type)); - } - int qtemp = size(temps); for(int i=0; imonst = tempval[i]; - pathqm.clear(); - - qb = 0; - for(qb=0; qb < size(pathq); qb++) { - int fd = reachedfrom[qb] + 3; - cell *c = pathq[qb]; - if(c->monst && !isBug(c) && !isFriendly(c)) { - pathqm.push_back(c); - continue; // no paths going through monsters - } - if(c->monst && c != cwt.c && getMount() && !isDragon(c->monst) && sameMonster(c, cwt.c)) { - // don't treat the Worm you are riding as passable - pathqm.push_back(c); - continue; - } - 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 && - passable(c->mov[i], d==0 && !nonAdjacent(c,c->mov[i]) && !thruVine(c,c->mov[i]) ?NULL:c, P_MONSTER | P_REVDIR)) { - c->mov[i]->pathdist = d+1; - pathq.push_back(c->mov[i]); reachedfrom.push_back(c->spn[i]); - } - } - } - buildAirmap(); + + if(overgenerate) doOvergenerate(); } -void makeEmpty(cell *c) { +bool makeEmpty(cell *c) { + if(c->monst != moPrincess) { + if(isAnyIvy(c->monst)) killMonster(c, moPlayer, 0); + else if(isWorm(c->monst)) { + if(!items[itOrbDomination]) return false; + } + else c->monst = moNone; + } + if(c->land == laCocytus) c->wall = waFrozenLake; else if(c->land == laAlchemist || c->land == laCanvas) @@ -2025,14 +2757,25 @@ void makeEmpty(cell *c) { c->wall = waCavefloor; else if(c->land == laDeadCaves) c->wall = waDeadfloor2; - else if(c->land == laCaribbean || c->land == laOcean || c->land == laWhirlpool || c->land == laLivefjord || c->land == laGridSea) + else if(c->land == laCaribbean || c->land == laOcean || c->land == laWhirlpool || c->land == laLivefjord || c->land == laWarpSea || c->land == laKraken) c->wall = waBoat; // , c->item = itOrbYendor; else if(c->land == laMinefield) c->wall = waMineOpen; + else if(c->wall == waFan && sphere) + ; + else if(c->wall == waOpenPlate && sphere) + ; + else if(c->wall == waGiantRug) + ; + else if(c->wall == waFreshGrave && (sphere || quotient)) + ; + else if(isReptile(c->wall)) + c->wparam = reptilemax(); + else if(c->wall == waAncientGrave && sphere) + ; else c->wall = waNone; c->item = itNone; - c->monst = moNone; if(c->land == laWildWest) { for(int i=0; itype; i++) { @@ -2043,8 +2786,12 @@ void makeEmpty(cell *c) { } } } + + return true; } +int numgates = 0; + void toggleGates(cell *ct, eWall type, int rad) { if(!ct) return; if(ct->wall == waOpenGate && type == waClosePlate) { @@ -2053,30 +2800,49 @@ void toggleGates(cell *ct, eWall type, int rad) { for(int i=0; itype; i++) if(ct->mov[i] && ct->mov[i]->wall == waOpenGate && isWorm(ct->mov[i])) onWorm = true; if(!onWorm) { - ct->wall = waClosedGate, rad = 1; + ct->wall = waClosedGate, numgates++; + if(rad<1) rad=1; if(ct->item) { + playSound(ct, "hit-crush"+pick123()); addMessage(XLAT("%The1 is crushed!", ct->item)); ct->item = itNone; } } } if(ct->wall == waClosedGate && type == waOpenPlate) - ct->wall = waOpenGate, rad = 1; + ct->wall = waOpenGate, rad = 1, numgates++; if(rad) for(int i=0; itype; i++) toggleGates(ct->mov[i], type, rad-1); } void toggleGates(cell *ct, eWall type) { + playSound(ct, "click"); + numgates = 0; if(type == waClosePlate && purehepta) toggleGates(ct, type, 2); else toggleGates(ct, type, 3); + if(numgates && type == waClosePlate) + playSound(ct, "closegate"); + if(numgates && type == waOpenPlate) + playSound(ct, "opengate"); } -void destroyWeakBranch(cell *cf, cell *ct) { +void destroyWeakBranch(cell *cf, cell *ct, eMonster who) { if(cf && ct && cf->wall == waWeakBranch && cellEdgeUnstable(ct) && - coastvalEdge(ct) >= coastvalEdge(cf)) + gravityLevel(ct) >= gravityLevel(cf) && !ignoresPlates(who)) { cf->wall = waCanopy; + if(!cellEdgeUnstable(cf)) { cf->wall = waWeakBranch; return; } + playSound(cf, "trapdoor"); + drawParticles(cf, winf[waWeakBranch].color, 4); + } + if(cf && ct && cf->wall == waSmallBush && cellEdgeUnstable(ct) && + gravityLevel(ct) >= gravityLevel(cf) && !ignoresPlates(who)) { + cf->wall = waNone; + if(!cellEdgeUnstable(cf)) { cf->wall = waSmallBush; return; } + playSound(cf, "trapdoor"); + drawParticles(cf, winf[waWeakBranch].color, 4); + } } // effect of moving monster m from cf to ct @@ -2085,12 +2851,14 @@ void destroyWeakBranch(cell *cf, cell *ct) { void moveEffect(cell *ct, cell *cf, eMonster m) { - if(cf && !ignoresPlates(m)) destroyWeakBranch(cf, ct); + if(cf) destroyWeakBranch(cf, ct, m); - if(!survivesMine(m)) - explodeMine(ct); + mayExplodeMine(ct, m); + + if(ct->wall == waMineUnknown && !ct->item && !ignoresPlates(m)) + ct->landparam |= 2; // mark as safe - if(!ignoresPlates(m) && (ct->wall == waClosePlate || ct->wall == waOpenPlate)) + if((ct->wall == waClosePlate || ct->wall == waOpenPlate) && !ignoresPlates(m)) toggleGates(ct, ct->wall); if(cf && isPrincess(m)) princess::move(ct, cf); @@ -2117,51 +2885,72 @@ void gainItem(eItem it) { int g = gold(); items[it]++; if(it != itLotus) updateHi(it, items[it]); achievement_collection(it, gold(), g); + multi::treasures[multi::cpid]++; + } + +string itemcounter(int qty) { + string s = ""; s += " ("; s += its(qty); s += ")"; return s; + } + +void gainShard(cell *c2, const char *msg) { + invismove = false; + string s = XLAT(msg); + if(c2->land == laMirror) { + gainItem(itShard); + s += itemcounter(items[itShard]); + collectMessage(c2, itShard); + } + addMessage(s); + c2->wall = waNone; + invismove = false; } void playerMoveEffects(cell *c1, cell *c2) { - bool nomine = (c2->wall == waMineMine || c2->wall == waMineUnknown) && markOrb(itOrbGhost); - - if(!markOrb(itOrbGhost)) destroyWeakBranch(c1, c2); + sword::angle[multi::cpid] = sword::shift(c1, c2, sword::angle[multi::cpid]); + + destroyWeakBranch(c1, c2, moPlayer); + + bool nomine = (c2->wall == waMineMine || c2->wall == waMineUnknown) && markOrb(itOrbAether); + if(!nomine) { uncoverMines(c2, items[itBombEgg] < 10 && hiitemsMax(itBombEgg) < 25 && !shmup::on ? 0 : items[itBombEgg] < 20 ? 1 : items[itBombEgg] < 30 ? 2 : - 3 + 3, 0 ); - explodeMine(c2); + mayExplodeMine(c2, moPlayer); } - if((c2->wall == waClosePlate || c2->wall == waOpenPlate) && !markOrb(itOrbGhost)) + if((c2->wall == waClosePlate || c2->wall == waOpenPlate) && !markOrb(itOrbAether)) toggleGates(c2, c2->wall); princess::playernear(c2); - if(c2->wall == waGlass && items[itOrbGhost] > 2) { + if(c2->wall == waGlass && items[itOrbAether] > ORBBASE+1) { addMessage(XLAT("Your Aether powers are drained by %the1!", c2->wall)); - drainOrb(itOrbGhost, 2); + drainOrb(itOrbAether, 2); } - if(c2->wall == waMirror && !markOrb(itOrbGhost)) { - invismove = false; - addMessage(XLAT("The mirror shatters!")); - if(c2->land == laMirror) gainItem(itShard); - c2->wall = waNone; + if(c2->wall == waMirror && !markOrb(itOrbAether)) { + drawParticles(c2, winf[c2->wall].color, 16); + gainShard(c2, "The mirror shatters!"); + playSound(c2, "pickup-mirror"); + cell *pc = multi::player[multi::cpid].c; + multi::player[multi::cpid].c = c2; mirror::createMirrors(cwt.c, cwt.spin, moMirage); + multi::player[multi::cpid].c = pc; } - if(c2->wall == waCloud && !markOrb(itOrbGhost)) { - invismove = false; - addMessage(XLAT("The cloud turns into a bunch of images!")); - if(c2->land == laMirror) gainItem(itShard); - c2->wall = waNone; + if(c2->wall == waCloud && !markOrb(itOrbAether)) { + drawParticles(c2, winf[c2->wall].color, 16); + gainShard(c2, "The cloud turns into a bunch of images!"); + playSound(c2, "pickup-mirror"); mirror::createMirages(cwt.c, cwt.spin, moMirage); } - if(cellUnstable(c2) && !markOrb(itOrbGhost)) - c2->wall = waChasm; + if(cellUnstable(c2) && !markOrb(itOrbAether)) doesFallSound(c2); if(c2->wall == waStrandedBoat && markOrb(itOrbWater)) c2->wall = waBoat; @@ -2173,15 +2962,67 @@ void playerMoveEffects(cell *c1, cell *c2) { hauntedWarning = true; addMessage(XLAT("You become a bit nervous...")); addMessage(XLAT("Better not to let your greed make you stray from your path.")); + playSound(c2, "nervous"); } } +void beastcrash(cell *c, cell *beast) { + if(c->wall == waPetrified || c->wall == waDeadTroll || c->wall == waDeadTroll2 || + c->wall == waGargoyle) { + addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->wall)); + c->wall = waNone; + } + else if(c->wall == waDeadwall || c->wall == waCavewall || c->wall == waSandstone || + c->wall == waVinePlant || c->wall == waIcewall || + c->wall == waMirror || c->wall == waCloud || c->wall == waBigTree || c->wall == + waSmallTree || c->wall == waGlass || c->wall == waClosedGate || c->wall == waStone) { + addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->wall)); + c->wall = waNone; + } + else if(cellHalfvine(c)) { + addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->wall)); + destroyHalfvine(c); + } + else if(c->wall == waThumperOff) { + addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->wall)); + c->wall = waThumperOn; + c->wparam = 100; + } + else if(isBull(c->monst)) { + addMessage(XLAT("%The1 crashes into %the2!", beast->monst, c->monst)); + if(c->monst == moSleepBull) c->monst = moRagingBull, c->stuntime = 3; + } + } + +void stayEffect(cell *c) { + eMonster m = c->monst; + if(m == moAirElemental) airmap.push_back(make_pair(c, 0)); + if(m == moRagingBull && c->mondir != NODIR) { + playSound(NULL, "hit-axe"+pick123()); + forCellIdEx(c2, d, c) { + bool opposite = angledist(c, d, c->mondir) >= 3; + if(opposite) beastcrash(c2, c); + } + c->mondir = NODIR; c->stuntime = 3; + } + } + +void makeTrollFootprints(cell *c) { + if(c->land != laTrollheim) return; + if(c->item == itTrollEgg && c->landparam) return; + c->landparam = turncount + 100; + } + void moveMonster(cell *ct, cell *cf) { - eMonster m = cf->monst; + eMonster m = cf->monst; + bool fri = isFriendly(cf); if(isDragon(m)) { printf("called for Dragon\n"); return; } + animateMovement(cf, ct, LAYER_SMALL); + // the following line is necessary because otherwise plates disappear only inside the sight range + if(cellUnstable(cf) && !ignoresPlates(m)) cf->wall = waChasm; moveEffect(ct, cf, m); if(ct->wall == waCamelotMoat && (m == moShark || m == moCShark || m == moGreaterShark)) @@ -2202,18 +3043,21 @@ void moveMonster(cell *ct, cell *cf) { } ct->hitpoints = cf->hitpoints; ct->stuntime = cf->stuntime; - - if(isFriendly(cf) || isBug(m) || items[itOrbDiscord]) stabbingAttack(cf, ct, m); + + if(fri || isBug(m) || items[itOrbDiscord]) stabbingAttack(cf, ct, m); if(isLeader(m)) { if(ct->wall == waBigStatue) { ct->wall = cf->wall; cf->wall = waBigStatue; + animateMovement(ct, cf, LAYER_BOAT); } moveBoatIfUsingOne(ct, cf); } + if(isTroll(m)) { makeTrollFootprints(ct); makeTrollFootprints(cf); } + if(m == moEarthElemental) { if(!passable(ct, cf, 0)) earthFloor(ct); earthMove(cf, neighborId(cf, ct)); @@ -2224,7 +3068,7 @@ void moveMonster(cell *ct, cell *cf) { for(int i=0; itype; i++) { cell *c2 = ct->mov[i]; if(!c2) continue; - if(c2->wall == waBoat && !(c2 == cwt.c && markOrb(itOrbWater))) { + if(c2->wall == waBoat && !(isPlayerOn(c2) && markOrb(itOrbWater))) { addMessage(XLAT("%The1 is washed away!", c2->wall, moWaterElemental)); placeWater(c2, ct); } @@ -2244,6 +3088,7 @@ void moveMonster(cell *ct, cell *cf) { addMessage(XLAT("%The1 is extinguished!", c2->wall, moWaterElemental)); c2->wall = waNone; } + if(shmup::on && isWatery(c2)) shmup::destroyBoats(c2); } } @@ -2259,12 +3104,7 @@ void moveMonster(cell *ct, cell *cf) { for(int u=2; u<=ct->type-2; u++) { cell *c3 = ct->mov[(ct->mondir+u)%ct->type]; if(canAttack(ct, moLancer, c3, c3->monst, AF_LANCE | AF_GETPLAYER)) { - if(isPlayerOn(c3)) - killThePlayerAt(m, c3, 0); - else { - addMessage(XLAT("%The1 pierces %the2!", m, c3->monst)); - killWithMessage(c3, true); - } + attackMonster(c3, AF_LANCE | AF_ORSTUN | AF_MSG | AF_GETPLAYER, m); } } } @@ -2273,18 +3113,24 @@ void moveMonster(cell *ct, cell *cf) { if(m == moFireElemental) { makeflame(cf, 20, false); if(cf->wparam < 20) cf->wparam = 20; } bool adj = false; - if(ct->cpdist == 1 && (items[itOrb37] || !nonAdjacent(cf,ct)) && markOrb(itOrbSkunk)) + if(ct->cpdist == 1 && (items[itOrb37] || !nonAdjacent(cf,ct)) && markOrb(itOrbBeauty)) adj = true; - if(!adj && items[itOrbEmpathy] && items[itOrbSkunk]) { + if(!adj && items[itOrbEmpathy] && items[itOrbBeauty]) { for(int i=0; itype; i++) if(ct->mov[i] && isFriendly(ct->mov[i])) - adj = true, markOrb(itOrbEmpathy), markOrb(itOrbSkunk); + adj = true, markOrb(itOrbEmpathy), markOrb(itOrbBeauty); } - if(adj && ct->stuntime == 0) ct->stuntime = 2; + if(adj && ct->stuntime == 0 && !isMimic(m)) { + ct->stuntime = 2; + checkStunKill(ct); + } - if(isMetalBeast(m)) ct->stuntime += 2; - if(m == moTortoise) ct->stuntime += 3; + if(!cellEdgeUnstable(ct)) { + if(isMetalBeast(m)) ct->stuntime += 2; + if(m == moTortoise) ct->stuntime += 3; + if(m == moDraugr && ct->land != laBurial && ct->land != laHalloween) ct->stuntime += 2; + } if(isWitch(m) && ct->item == itOrbLife && passable(cf, NULL, P_MIRROR)) { // note that Fire Witches don't pick up Orbs of Life, @@ -2297,40 +3143,106 @@ void moveMonster(cell *ct, cell *cf) { pickup = true, m = moWitchFlash; if(ct->item == itOrbWinter) pickup = true, m = moWitchWinter; - if(ct->item == itOrbGhost) + if(ct->item == itOrbAether) pickup = true, m = moWitchGhost; if(ct->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(ct->item == itOrbSpeed) + pickup = true, m = moWitchSpeed; if(ct->item == itOrbLife) pickup = true, cf->monst = moEvilGolem; if(pickup) { addMessage(XLAT("%The1 picks up %the2!", moWitch, ct->item)); ct->monst = m; ct->item = itNone; + // speedwitches are stunned to prevent them from making a move + // immediately + if(m == moWitchSpeed) ct->stuntime = 1; } } if(m == moAirElemental) airmap.push_back(make_pair(ct, 0)); - if(incline(cf, ct) == -3 && !survivesFall(ct->monst)) { + int inc = incline(cf, ct); + if(inc == -3 && ct->monst == moReptile) + ct->stuntime =3; + else if(inc == 2 && ct->monst == moReptile) + ct->stuntime = 2; + else if(inc == 3 && ct->monst == moReptile) + ct->stuntime = 3; + else if(inc == -3 && !survivesFall(ct->monst)) { addMessage(XLAT("%The1 falls!", ct->monst)); - killMonster(ct); + fallMonster(ct); } if(isThorny(ct->wall) && !survivesThorns(ct->monst)) { addMessage(XLAT("%The1 is killed by thorns!", ct->monst)); - killMonster(ct); + playSound(ct, "hit-rose"); + if(isBull(ct->monst)) ct->wall = waNone; + fallMonster(ct); + } + if(sword::at(ct) && canAttack(NULL, moPlayer, ct, m, AF_SWORD_INTO)) { + attackMonster(ct, AF_SWORD_INTO | AF_ORSTUN | AF_MSG, moPlayer); + achievement_gain("GOSWORD"); } } bool cannotGo(eMonster m, cell *c) { - if(m == moCrystalSage && (c->land != laCocytus || HEAT(c) > SAGEMELT || cwt.c->wall == waBoat)) + if(m == moCrystalSage && (c->land != laCocytus || HEAT(c) > SAGEMELT || allPlayersInBoats())) return true; return false; } bool wantsToStay(eMonster m) { - return m == moCrystalSage && cwt.c->wall == waBoat; + return m == moCrystalSage && allPlayersInBoats(); + } + +bool batsAfraid(cell *c) { + // bats + for(int i=-1; imonst && invismove) continue; + bool enear = false; + forCellEx(c, targets[i]) + forCellEx(c2, c) + forCellEx(c3, c2) + if(isActiveEnemy(c3, targets[i]->monst) && c3->monst != moBat && + passable_for(c3->monst, c2, c3, 0) && + passable_for(c3->monst, c, c2, 0) + ) + enear = true; + if(!enear) return true; + } + return false; + } + +int angledist(int t, int d1, int d2) { + int dd = d1 - d2; + while(dd<0) dd += t; + while(dd>t/2) dd -= t; + if(dd<0) dd = -dd; + return dd; + } + +int angledistButterfly(int t, int d1, int d2) { + int dd = d1 - d2; + while(dd<0) dd += t; + return dd; + } + +int angledist(cell *c, int d1, int d2) { + return angledist(c->type, d1, d2); + } + +int bulldist(cell *c) { + int low = 0; + forCellEx(c2, c) if(c2->cpdist < c->cpdist) low++; + return 8 * c->cpdist - low; + } + +int bulldistance(cell *c, cell *d) { + int low = 0; + int cd = celldistance(c, d); + forCellEx(c2, c) if(celldistance(c2, d) < cd) low++; + return 8 * cd - low; } // move value @@ -2339,6 +3251,9 @@ int moveval(cell *c1, cell *c2, int d, int mf) { eMonster m = c1->monst; + // Angry Beasts can only go forward + if(m == moRagingBull && c1->mondir != NODIR && angledist(c1, c1->mondir, d) < 3) return -1700; + // never move against a rose if(againstRose(c1, c2) && !ignoresSmell(m)) return -1600; @@ -2361,6 +3276,8 @@ int moveval(cell *c1, cell *c2, int d, int mf) { // never move into a wall (passable_for(c1->monst, c2, c1, P_DEADLY)) ? -1300 : -1700; // move impossible + + if(slowMover(m) && nogoSlow(c2, c1)) return -1300; if(isPlayerOn(c2)) return -1700; // probably shielded @@ -2379,8 +3296,8 @@ int moveval(cell *c1, cell *c2, int d, int mf) { if(m == moLancer) { bool lancerok = true; for(int u=2; u<=c2->type-2; u++) { - cell *c3 = c2->mov[(c1->spn[d]+u)%c2->type]; - if(canAttack(c2, moLancer, c3, c3->monst, AF_LANCE | AF_ONLY_ENEMY)) + cell *c3 = c2->mov[(c1->spn(d)+u)%c2->type]; + if(c3 && canAttack(c2, moLancer, c3, c3->monst, AF_LANCE | AF_ONLY_ENEMY)) lancerok = false; } if(!lancerok) return 750; @@ -2400,30 +3317,55 @@ int moveval(cell *c1, cell *c2, int d, int mf) { if((mf & MF_MOUNT) && dragon::target) return 1500 + celldistance(c1, dragon::target) - celldistance(c2, dragon::target); + + // Goblins avoid getting near the Sword + if(m == moGoblin && sword::near(c2)) return 790; + if(m == moBat && batsAfraid(c2)) return 790; + + if(m == moButterfly) + return 1500 + angledistButterfly(c1->type, c1->mondir, d); + + if(m == moRagingBull && c1->mondir != NODIR) + return 1500 - bulldist(c2); if((mf & MF_PATHDIST) && c2->pathdist < c1->pathdist) return 1500; // good move // prefer straight direction when wandering - int dd = c1->mondir - d; - while(dd<0) dd += c1->type; - while(dd>c1->type/2) dd -= c1->type; + int dd = angledist(c1, c1->mondir, d); + + // goblins blocked by anglophobia prefer to move around than to stay + if(m == moGoblin) { + bool swn = false; + forCellEx(c3, c1) if(sword::near(c3)) swn = true; + if(swn) dd += 210; + } + return 800 + dd; } // stay value -int stayval(cell *c, int mf) { +int stayval(cell *c, flagtype mf) { + if(isShark(c->monst) && !isWatery(c)) + return 525; + if(againstRose(c, NULL) && !ignoresSmell(c->monst)) return -1500; if(!passable_for(c->monst, c, NULL, P_MONSTER | P_MIRROR)) return 525; if(cellEdgeUnstable(c)) - return 525; - if(againstRose(c, NULL) && !ignoresSmell(c->monst)) return -1500; + return -1500; if(isRatling(c->monst) && lastmovetype != lmSkip) return 700; + // Goblins avoid staying near the Sword (if there is no choice, movement is preferred) + if(c->monst == moGoblin && sword::near(c)) return 780; + // Vikings move in a roughly straight line even if they cannot detect you + if(c->monst == moViking && c->wall == waBoat) + return 750; if(isWorm(c->monst)) return 550; + if(c->monst == moRagingBull) return -1690; // worse than to stay in place + if(c->monst == moBat && batsAfraid(c)) return 575; return 1000; } -int pickMoveDirection(cell *c, int mf) { +int pickMoveDirection(cell *c, flagtype mf) { int posdir[10], nc = 0, bestval = stayval(c, mf); // printf("stayval [%p, %s]: %d\n", c, dnameof(c->monst), bestval); @@ -2434,18 +3376,85 @@ int pickMoveDirection(cell *c, int mf) { if(val > bestval) nc = 0, bestval = val; if(val == bestval) posdir[nc++] = d; } + + if(c->monst == moRagingBull) { + // determinize the Angry Beast movement: + // use the previous PC's positions as the tiebreaker + for(int k=0; k1; k++) { + int pts[10]; + for(int d=0; dmov[posdir[d]], c2); + } + int bestpts = 1000; + for(int d=0; dmonst, c2, c, P_MIRROR) && + !isPlayerOn(c2)) { + int cdif = i-c->mondir; + if(cdif < 0) cdif += c->type; + if(cdif > c->type/2) cdif = cdif - c->type; + if(cdif < 0) cdif = -2*cdif+1; else cdif = 2*cdif; + // printf("i=%d md=%d dif=%d\n", i, c->mondir, cdif); + if(c2->wall == waClosePlate || c->wall == waClosePlate) + cdif += 20; + if(cdif > bestdif) bestdif = cdif, qdowns = 0; + if(cdif == bestdif) downs[qdowns++] = i; + } + } + if(!qdowns) return -1; + return downs[hrand(qdowns)]; + } + +// Angry Beast attack +// note: this is done both before and after movement +void beastAttack(cell *c, bool player) { + if(c->mondir == NODIR) return; + forCellIdEx(c2, d, c) { + bool opposite = angledist(c, d, c->mondir) >= 3; + int flags = AF_BULL | AF_ORSTUN; + if(player) flags |= AF_GETPLAYER; + if(!opposite) flags |= AF_ONLY_FBUG; + if(canAttack(c, moRagingBull, c2, c2->monst, flags)) + attackMonster(c2, flags | AF_MSG, moRagingBull); + } + } + +cell *moveNormal(cell *c, flagtype mf) { eMonster m = c->monst; - if(c->stuntime) return NULL; - int d = pickMoveDirection(c, mf); - if(d == -1) return c; + int d; + + if(c->stuntime) { + if(cellEdgeUnstable(c, MF_STUNNED)) d = pickDownDirection(c, mf); + else return NULL; + } + else { + // Angry Beasts attack all neighbors first + if(m == moRagingBull) beastAttack(c, true); + d = pickMoveDirection(c, mf); + } + if(d == -1) { + stayEffect(c); + return c; + } cell *c2 = c->mov[d]; if(isPlayerOn(c2)) { @@ -2455,13 +3464,14 @@ cell *moveNormal(cell *c, int mf) { eMonster m2 = c2->monst; if(m2) { - killWithMessage(c2, true, m); + attackMonster(c2, AF_ORSTUN | AF_MSG, m); if(m == moFlailer && m2 == moIllusion) - killWithMessage(c, true, m2); + attackMonster(c, 0, m2); return c2; } moveMonster(c2, c); + if(m == moRagingBull) beastAttack(c2, false); return c2; } @@ -2470,6 +3480,7 @@ void explodeAround(cell *c) { cell* c2 = c->mov[j]; if(c2) { if(isIcyLand(c2)) HEAT(c2) += 0.5; + eWall ow = c2->wall; if((c2->wall == waDune || c2->wall == waIcewall || c2->wall == waAncientGrave || c2->wall == waFreshGrave || c2->wall == waColumn || c2->wall == waThumperOff || c2->wall == waThumperOn || @@ -2481,6 +3492,7 @@ void explodeAround(cell *c) { } if(c2->wall == waCavewall || c2->wall == waDeadTroll) c2->wall = waCavefloor; if(c2->wall == waDeadTroll2) c2->wall = waNone; + if(c2->wall == waPetrified) c2->wall = waNone; if(c2->wall == waDeadfloor2) c2->wall = waDeadfloor; if(c2->wall == waGargoyleFloor) c2->wall = waChasm; if(c2->wall == waGargoyleBridge) placeWater(c2, c2); @@ -2498,86 +3510,126 @@ void explodeAround(cell *c) { c2->wall = waNone; if(isAlch(c2) && isAlch(c)) c2->wall = c->wall; + if(c2->wall != ow && ow) drawParticles(c2, winf[ow].color, 16); } } } -void killThePlayer(eMonster m, int id, int flags) { +void killThePlayer(eMonster m, int id, flagtype flags) { if(markOrb(itOrbShield)) return; if(shmup::on) { - shmup::cpid = id; + multi::cpid = id; shmup::killThePlayer(m); } - else if(items[itOrbDomination] && cwt.c->monst) { + else if(items[itOrbDomination] && playerpos(id)->monst) { addMessage(XLAT("%The1 tries to dismount you!", m)); - killOrStunMonster(cwt.c); + attackMonster(playerpos(id), AF_ORSTUN, m); useupOrb(itOrbDomination, items[itOrbDomination]/2); } else if(items[itOrbShell] && !(flags & AF_EAT)) { addMessage(XLAT("%The1 attacks your shell!", m)); useupOrb(itOrbShell, 10); - if(items[itOrbShell] < 1) items[itOrbShell] = 1; + if(items[itOrbShell] < 1) items[itOrbShell] = 1, orbused[itOrbShell] = true; } else if(hardcore) { addMessage(XLAT("You are killed by %the1!", m)); - canmove = false; - achievement_final(true); - msgscroll = 0; + if(multi::players > 1 && multi::activePlayers() > 1) + multi::leaveGame(id); + else { + canmove = false; + achievement_final(true); + msgscroll = 0; + } } - else if(m == moLightningBolt && lastmovetype == lmAttack && isAlchAny(cwt.c)) { + else if(m == moLightningBolt && lastmovetype == lmAttack && isAlchAny(playerpos(id))) { addMessage(XLAT("You are killed by %the1!", m)); addMessage(XLAT("Don't play with slime and electricity next time, okay?")); kills[moPlayer]++; items[itOrbSafety] = 0; } - else + else { + printf("confused!\n"); addMessage(XLAT("%The1 is confused!", m)); + } } -void killThePlayerAt(eMonster m, cell *c, int flags) { +void killThePlayerAt(eMonster m, cell *c, flagtype flags) { for(int i=0; i 1) { + multi::player[id].c = c; + multi::player[id].spin = spin; + multi::flipped[id] = fp; + } + else { + cwt.c = c; + cwt.spin = spin; + flipplayer = fp; + } + afterplayermoved(); + } + +void mountmove(cell *c, int spin, bool fp, cell *ppos) { + for(int i=0; imonst == moWormwait) { c->monst = moWorm; return; } else if(c->monst == moTentaclewait) { c->monst = moTentacle; return; } else if(c->monst == moTentacleEscaping) { // explodeAround(c); forCellEx(c2, c) - if(canAttack(c, c->monst, c2, c2->monst, AF_GETPLAYER | AF_ONLY_FBUG)) { - if(isPlayerOn(c2)) - killThePlayerAt(c->monst, c2, 0); - else { - addMessage(XLAT("%The1 attacks %the2!", c->monst, c2->monst)); - killWithMessage(c2, true); - } + if(canAttack(c, c->monst, c2, c2->monst, mounted ? AF_ONLY_ENEMY : (AF_GETPLAYER | AF_ONLY_FBUG))) { + attackMonster(c2, AF_ORSTUN | AF_MSG | AF_GETPLAYER, c->monst); } cell *c2 = c; + vector allcells; while(c2->mondir != NODIR) { - if(c2 == cwt.c && c2->mov[c2->mondir]->monst != moTentacleGhost) - mountmove(c2->mov[c2->mondir], c2->spn[c2->mondir], false); + allcells.push_back(c2); c2 = c2->mov[c2->mondir]; } + allcells.push_back(c2); + for(int i=size(allcells)-2; i>=0; i--) { + cell *cmt = allcells[i+1]; + cell *cft = allcells[i]; + if(cft->monst != moTentacleGhost && cmt->monst != moTentacleGhost) + mountmove(cmt, cft->spn(cft->mondir), false, cft); + animateMovement(cft, cmt, LAYER_BIG); + } c->monst = moNone; if(c->mondir != NODIR) c->mov[c->mondir]->monst = moTentacleEscaping; return; } else if(c->monst != moWorm && c->monst != moTentacle) return; - int id = c->monst - moWorm; + eMonster m = c->monst; + int id = m - moWorm; int mf = MF_PATHDIST | AF_EAT; - if(getMount() && sameMonster(c, cwt.c)) mf ^= (MF_MOUNT | MF_PATHDIST); + + if(mounted) mf ^= (MF_MOUNT | MF_PATHDIST); int dir = pickMoveDirection(c, mf); @@ -2604,6 +3656,7 @@ void moveWorm(cell *c) { while(c->monst == moWorm || c->monst == moWormtail || c->monst == moTentacle || c->monst == moTentacletail) { // if(!id) explodeAround(c); + drawParticles(c, minf[c->monst].color, 16); if(spices > 0 && c->land == laDesert) { if(notDippingForExtra(itSpice, loc)) { c->item = itSpice; @@ -2619,43 +3672,53 @@ void moveWorm(cell *c) { addMessage(XLAT("The sandworm explodes in a cloud of Spice!")); else addMessage(XLAT("The sandworm explodes!")); + playSound(NULL, "explosion"); + achievement_gain("ZEBRAWORM", 'q'); } return; } cell* goal = c->mov[dir]; - if(isPlayerOn(goal)) killThePlayerAt(c->monst, goal, AF_EAT); - if(goal->monst) { - addMessage(XLAT("%The1 eats %the2!", c->monst, goal->monst)); - killWithMessage(goal, false); - } + if(isPlayerOn(goal) || goal->monst) + attackMonster(goal, AF_EAT | AF_MSG | AF_GETPLAYER, c->monst); for(int j=0; jtype; j++) if(c->mov[j] == goal) { goal->monst = eMonster(moWormwait + id); moveEffect(goal, NULL, eMonster(moWormwait + id)); + animateMovement(c, goal, LAYER_BIG); c->monst = eMonster(moWormtail + id); - goal->mondir = c->spn[j]; + goal->mondir = c->spn(j); - if(c == cwt.c) mountmove(goal, goal->mondir, true); + mountmove(goal, goal->mondir, true, c); if(id) { cell *c2 = c, *c3 = c2; while(c2->monst == moTentacletail || c2->monst == moTentacleGhost) { - if(c2->mondir == NODIR) return; + if(c2->mondir == NODIR) { + drawParticles(c2, (linf[c2->land].color & 0xF0F0F0), 16, 50); + return; + } c3 = c2, c2 = c3->mov[c2->mondir]; - if(c2 == cwt.c && c3->monst != moTentacleGhost) mountmove(c3, c3->mondir, true); + if(c3->monst != moTentacleGhost && c2->monst != moTentacleGhost) + mountmove(c3, c3->mondir, true, c2); + animateMovement(c2, c3, LAYER_BIG); } } cell *c2 = c, *c3 = c2; - for(int a=0; a<15; a++) + for(int a=0; amonst == moWormtail) { - if(c2->mondir == NODIR) return; + if(c2->mondir == NODIR) { + drawParticles(c2, (linf[c2->land].color & 0xF0F0F0), 16); + return; + } c3 = c2, c2 = c3->mov[c2->mondir]; - if(c2 == cwt.c) mountmove(c3, c3->mondir, true); + mountmove(c3, c3->mondir, true, c2); + animateMovement(c2, c3, LAYER_BIG); } + } if(c2->monst == moWormtail) c2->monst = moNone, c3->mondir = NODIR; } @@ -2664,6 +3727,16 @@ void moveWorm(cell *c) { void ivynext(cell *c) { cellwalker cw(c, c->mondir); + + // check the mirroring status + cell *c2 = c; + while(true) { + if(c2->monst == moIvyRoot) break; + if(!isIvy(c2->monst)) break; + if(c2->mirror(c2->mondir)) cw.mirrored = !cw.mirrored; + c2 = c2->mov[c2->mondir]; + } + cw.c->monst = moIvyWait; bool findleaf = false; while(true) { @@ -2687,6 +3760,7 @@ void ivynext(cell *c) { // this removes Ivy, but also potentially causes Vines to grow void removeIvy(cell *c) { + eMonster m = c->monst; c->monst = moNone; // NEWYEARFIX for(int i=0; itype; i++) // note that semi-vines don't count @@ -2694,9 +3768,16 @@ void removeIvy(cell *c) { destroyHalfvine(c); c->wall = waVinePlant; } + if(c->wall != waVinePlant) { + if(m == moIvyDead) + m = moIvyWait; + drawParticles(c, minf[m].color, 2); + } } void moveivy() { + if(size(ivies) == 0) return; + computePathdist(moIvyRoot); for(int i=0; imov[j]->monst)); else addMessage(XLAT("The ivy kills %the1!", c->mov[j]->monst)); - killWithMessage(c->mov[j], true); + attackMonster(c->mov[j], AF_ORSTUN, c->monst); } continue; } - if(c->mov[j] && signed(c->mov[j]->pathdist) < pd && passable(c->mov[j], c, 0)) - mto = c->mov[j], pd = mto->pathdist, sp = c->spn[j]; + if(c->mov[j] && signed(c->mov[j]->pathdist) < pd && passable(c->mov[j], c, 0) + && !strictlyAgainstGravity(c->mov[j], c, false, MF_IVY)) + mto = c->mov[j], pd = mto->pathdist, sp = c->spn(j); } c = c->mov[c->mondir]; } - if(mto && mto->cpdist) { + if(mto && mto->cpdist) { + animateMovement(mto->mov[sp], mto, LAYER_BIG); mto->monst = moIvyWait, mto->mondir = sp; moveEffect(mto, NULL, moIvyWait); // if this is the only branch, we want to move the head immediately to mto instead @@ -2753,7 +3836,7 @@ void moveivy() { bool earthMove(cell *from, int dir) { bool b = false; cell *c2 = from->mov[dir]; - int d = from->spn[dir]; + int d = from->spn(dir); b |= earthWall(from); if(c2) for(int u=2; u<=c2->type-2; u++) { cell *c3 = c2->mov[(d + u)% c2->type]; @@ -2764,28 +3847,33 @@ bool earthMove(cell *from, int dir) { vector gendfs; -#define ONLY_ONE_PLAYER_POSSIBLE 0 - -void monsterfight(cell *attacker, cell *victim) { - if(isBird(attacker->monst)) - addMessage(XLAT("%The1 claws %the2!", attacker->monst, victim->monst)); - else if(isSlimeMover(attacker)) { - if(attackJustStuns(victim)) - addMessage(XLAT("%The1 attacks %the2!", attacker->monst, victim->monst)); - else - addMessage(XLAT("%The1 eats %the2!", attacker->monst, victim->monst)); - } - else if(isDragon(attacker->monst)) - addMessage(XLAT("%The1 attacks %the2!", attacker->monst, victim->monst)); - else - addMessage(XLAT("%The1 punches %the2!", attacker->monst, victim->monst)); - killWithMessage(victim, true); +bool isMounted(cell *c) { + for(int i=0; iaitmp, sval)) return; - if(!passable_for(movtype, from, c, P_ONPLAYER | P_CHAIN | P_MONSTER)) return; + + if(eq(c->aitmp, sval)) return; + + if(movtype == moKrakenH && isTargetOrAdjacent(from)) ; +/* else if(passable_for(movtype, from, c, P_ONPLAYER | P_CHAIN | P_MONSTER)) ; + else if(canAttack(c, movtype, from, from->monst, AF_GETPLAYER)) ; */ + else if(passable_for(movtype, from, c, P_CHAIN | P_MONSTER)) ; + else if(canAttack(c, movtype, from, from->monst, AF_GETPLAYER)) ; + else return; if(from->monst) { if(mf & MF_MOUNT) { @@ -2794,7 +3882,7 @@ void groupmove2(cell *c, cell *from, int d, eMonster movtype, int mf) { } else { // go through the player (even mounted) - if(from == cwt.c) ; + if(isPlayerOn(from)) ; // go through the mounted dragon else if(isDragon(from->monst) && isFriendlyOrBug(from)) ; // but not through other worms @@ -2805,26 +3893,33 @@ void groupmove2(cell *c, cell *from, int d, eMonster movtype, int mf) { } } - if(movegroup(c->monst) == movtype) { + // Kraken movement + if(movtype == moKrakenH && c->monst == moKrakenT && c->stuntime == 0) + kraken::trymove(c); + if(movegroup(c->monst) == movtype) { + int af = AF_ONLY_FBUG | AF_GETPLAYER; if(mf & MF_MOUNT) af = 0; - + if(!passable_for(movtype, from, c, P_ONPLAYER | P_MONSTER)) return; if(!ignoresSmell(c->monst) && againstRose(c, from)) return; - if((mf & MF_ONLYEAGLE) && c->monst != moEagle) return; + if((mf & MF_ONLYEAGLE) && c->monst != moEagle && c->monst != moBat) return; + // in the gravity lands, eagles cannot ascend in their second move + if((mf & MF_ONLYEAGLE) && gravityLevel(c) < gravityLevel(from)) { + c->aitmp = sval; return; + } if((mf & MF_NOFRIEND) && isFriendly(c)) return; - if((mf & MF_MOUNT) && !sameMonster(c, cwt.c)) return; + if((mf & MF_MOUNT) && !isMounted(c)) return; if(isRatling(c->monst) && lastmovetype == lmSkip) return; if(c->stuntime) return; + if(c->monst == moBat && batsAfraid(from)) return; // note: move from 'c' to 'from'! if(!(mf & MF_NOATTACKS)) for(int j=0; jtype; j++) if(c->mov[j] && canAttack(c, c->monst, c->mov[j], c->mov[j]->monst, af)) { - if(isPlayerOn(c->mov[j])) - killThePlayer(c->monst, ONLY_ONE_PLAYER_POSSIBLE, 0); - else monsterfight(c, c->mov[j]); + attackMonster(c->mov[j], AF_ORSTUN | AF_GETPLAYER | AF_MSG, c->monst); c->aitmp = sval; // XLATC eagle return; @@ -2836,18 +3931,19 @@ void groupmove2(cell *c, cell *from, int d, eMonster movtype, int mf) { dragon::move(from, c); return; } - + moveMonster(from, c); + from->aitmp = sval; } c->aitmp = sval; // MAXGCELL if(size(gendfs) < 1000 || c->cpdist <= 6) gendfs.push_back(c); } -void groupmove(eMonster movtype, int mf) { +void groupmove(eMonster movtype, flagtype mf) { sval++; gendfs.clear(); - + if(mf & MF_MOUNT) { if(dragon::target) gendfs.push_back(dragon::target); if(movtype == moDragonHead) { @@ -2855,7 +3951,7 @@ void groupmove(eMonster movtype, int mf) { cell *c = (i == 0 && dragon::target) ? dragon::target : dcal[i]; if(!c->monst) continue; if(isFriendlyOrBug(c)) continue; - forCellIdEx(c2, d, c) if(c2->monst && sameMonster(cwt.c, c2)) { + forCellIdEx(c2, d, c) if(c2->monst && isMounted(c2)) { groupmove2(c2,c,d,movtype,mf); } } @@ -2864,17 +3960,27 @@ void groupmove(eMonster movtype, int mf) { else { for(int i=0; imov[t],c,t,movtype,mf); + } - if(movtype == moEagle && c->monst == moNone && c != cwt.c) { + if(movtype == moEagle && c->monst == moNone && !isPlayerOn(c)) { cell *c2 = whirlwind::jumpFromWhereTo(c, false); groupmove2(c2, c, NODIR, movtype, mf); } @@ -2882,7 +3988,7 @@ void groupmove(eMonster movtype, int mf) { if(movtype != moDragonHead) for(int i=0; imonst != moEagle) return; + if((mf & MF_ONLYEAGLE) && c->monst != moEagle && c->monst != moBat) return; if(movegroup(c->monst) == movtype && !eq(c->aitmp, sval)) { cell *c2 = moveNormal(c, mf); if(c2) c2->aitmp = sval; @@ -2900,13 +4006,17 @@ void moveHexSnake(cell *from, cell *c, int d, bool mounted) { moveEffect(from, c, c->monst); from->monst = c->monst; from->mondir = d; c->monst = moHexSnakeTail; - if(c == cwt.c) mountmove(from, from->mondir, true); + preventbarriers(from); + + animateMovement(c, from, LAYER_BIG); + mountmove(from, from->mondir, true, c); cell *c2 = c, *c3=c2; for(int a=0;; a++) if(c2->monst == moHexSnakeTail) { if(a == ROCKSNAKELENGTH) { c2->monst = moNone, c3->mondir = NODIR; break; } if(c2->mondir == NODIR) break; - if(c2->mov[c2->mondir] == cwt.c) mountmove(c2, c2->mondir, true); + mountmove(c2, c2->mondir, true, c2->mov[c2->mondir]); + animateMovement(c2->mov[c2->mondir], c2, LAYER_BIG); c3 = c2, c2 = c3->mov[c2->mondir]; } else break; @@ -2917,21 +4027,13 @@ void snakeAttack(cell *c, bool mounted) { if(c->mov[j] && canAttack(c, moHexSnake, c->mov[j], c->mov[j]->monst, mounted ? AF_ONLY_ENEMY : (AF_ONLY_FBUG | AF_GETPLAYER))) { - if(isPlayerOn(c->mov[j])) - killThePlayerAt(moHexSnake, c->mov[j], 0); - else { - if(attackJustStuns(c->mov[j])) - addMessage(XLAT("%The1 attacks %the2!", c->monst, c->mov[j]->monst)); - else - addMessage(XLAT("%The1 kills %the2!", c->monst, c->mov[j]->monst)); - killWithMessage(c->mov[j], true); - } + attackMonster(c->mov[j], AF_ORSTUN | AF_GETPLAYER | AF_MSG, moHexSnake); } } bool goodmount(cell *c, bool mounted) { - if(mounted) return sameMonster(c, cwt.c); - else return !sameMonster(c, cwt.c); + if(mounted) return isMounted(c); + else return !isMounted(c); } // note: move from 'c' to 'from'! @@ -2952,26 +4054,18 @@ void hexvisit(cell *c, cell *from, int d, bool mounted) { if(pseudohept(from)) return; if(!goodmount(c, mounted)) return; - + if(canAttack(c, moHexSnake, from, from->monst, AF_EAT | (mounted ? AF_ONLY_ENEMY : AF_ONLY_FBUG | AF_GETPLAYER))) { - if(isPlayerOn(from)) - killThePlayerAt(moHexSnake, from, AF_EAT); - else { - addMessage(XLAT("%The1 eats %the2!", c->monst, from->monst)); - killWithMessage(from, false); - } + attackMonster(from, AF_MSG | AF_EAT | AF_GETPLAYER, c->monst); } if(from->cpdist == 0 || from->monst) return; snakeAttack(c, mounted); - if(c->monst == moRedTroll) - moveMonster(from, c); - else moveHexSnake(from, c, d, mounted); + moveHexSnake(from, c, d, mounted); } c->aitmp = sval; - if(c->bardir == 0) c->bardir = NOBARRIERS; // MAXGCELL if(size(hexdfs) < 2000 || c->cpdist <= 6) @@ -2996,8 +4090,15 @@ void movehex(bool mounted) { for(int i=0; itype; t++) if(c->mov[t] && !pseudohept(c->mov[t])) + dirtable[qdirtable++] = t; + + random_shuffle(dirtable, dirtable+qdirtable); + while(qdirtable--) { + int t = dirtable[qdirtable]; hexvisit(c->mov[t], c, t, mounted); + } } for(int i=0; itype; u++) { createMov(c, t[u]); if(!pseudohept(c->mov[t[u]])) - hexvisit(c, c->mov[t[u]], c->spn[t[u]], mounted); + hexvisit(c, c->mov[t[u]], c->spn(t[u]), mounted); } } if(c->monst == moHexSnake) { snakeAttack(c, mounted); kills[moHexSnake]++; + playSound(c, "die-troll"); cell *c2 = c; while(c2->monst == moHexSnakeTail || c2->monst == moHexSnake) { if(c2->monst != moHexSnake && c2->mondir != NODIR) @@ -3048,10 +4150,7 @@ void movemutant() { if(!c2) continue; if(c2->monst != moMutant && canAttack(c, moMutant, c2, c2->monst, AF_ONLY_FBUG | AF_GETPLAYER)) { - if(isPlayerOn(c2)) - killThePlayerAt(moMutant, c2, 0); - else - killWithMessage(c2, true, moMutant); + attackMonster(c2, AF_ORSTUN | AF_MSG | AF_GETPLAYER, moMutant); continue; } @@ -3060,30 +4159,49 @@ void movemutant() { if((c2->land == laOvergrown || !pseudohept(c2)) && passable(c2, c, 0)) { if(c2->land == laClearing && c2->mpdist > 7) continue; c2->monst = moMutant; - c2->mondir = c->spn[j]; + c2->mondir = c->spn(j); c2->stuntime = mutantphase; + animateMovement(c, c2, LAYER_BIG); } } } } -#define SHSIZE 16 - -cell *shpos[SHSIZE]; +cell *shpos[MAXPLAYER][SHSIZE]; int cshpos = 0; void clearshadow() { - for(int i=0; imonst == moShadow) - shpos[cshpos]->monst = moNone; - shpos[cshpos] = cwt.c; + + cell *shfrom = NULL; + + for(int p=0; pmonst == moShadow) { + for(int j=0; jtype; j++) + if(c->mov[j] && canAttack(c, moShadow, c->mov[j], c->mov[j]->monst, AF_ONLY_FBUG | AF_GETPLAYER)) + attackMonster(c->mov[j], AF_ORSTUN | AF_MSG | AF_GETPLAYER, c->monst); + c->monst = moNone; + shfrom = c; + } + shpos[p][cshpos] = playerpos(p); + } cshpos = (cshpos+1) % SHSIZE; - if(shpos[cshpos] && shpos[cshpos]->monst == moNone && shpos[cshpos]->cpdist && shpos[cshpos]->land == laGraveyard) { - shpos[cshpos]->monst = moShadow; - shpos[cshpos]->stuntime = 0; + for(int p=0; pmonst == moNone && where->cpdist && where->land == laGraveyard && + !sword::at(where)) { + if(shfrom) animateMovement(shfrom, where, LAYER_SMALL); + where->monst = moShadow; + where->hitpoints = p; + where->stuntime = 0; + // the Shadow sets off the mines + mayExplodeMine(where, moShadow); + } } } @@ -3101,7 +4219,7 @@ void moveghosts() { int goodmoves = 0; for(int k=0; ktype; k++) if(c->mov[k] && c->mov[k]->cpdist < c->cpdist) - if(ghostmove(c->monst, c->mov[k], c)) + if(ghostmove(c->monst, c->mov[k], c) && !isPlayerOn(c->mov[k])) goodmoves++; movesofgood[goodmoves].push_back(c); @@ -3112,7 +4230,7 @@ void moveghosts() { cell *c = movesofgood[d][i]; if(c->stuntime) return; - if(isGhostMover(c->monst) && c->cpdist > 1) { + if(isGhostMover(c->monst) && c->cpdist >= 1) { int mdir[7]; @@ -3120,14 +4238,8 @@ void moveghosts() { if(c->mov[j] && canAttack(c, c->monst, c->mov[j], c->mov[j]->monst, AF_GETPLAYER | AF_ONLY_FBUG)) { // XLATC ghost/greater shark - if(isPlayerOn(c->mov[j])) { - killThePlayerAt(c->monst, c->mov[j], 0); - } - else { - addMessage(XLAT("%The1 scares %the2!", c->monst, c->mov[j]->monst)); - killWithMessage(c->mov[j], true); - } - return; + attackMonster(c->mov[j], AF_ORSTUN | AF_MSG | AF_GETPLAYER, c->monst); + goto nextghost; } int qmpos = 0; @@ -3142,27 +4254,14 @@ void moveghosts() { c2->stuntime = 1; } else moveMonster(c2, c); + } + nextghost: ; } } int lastdouble = -3; -void messageKill(eMonster killer, eMonster victim) { - if(isNonliving(victim)) { - if(killer && killer != moPlayer) - addMessage(XLAT("%The1 destroys %the2!", killer, victim)); - else - addMessage(XLAT("You destroy %the1.", victim)); - } - else { - if(killer && killer != moPlayer) - addMessage(XLAT("%The1 kills %the2!", killer, victim)); - else - addMessage(XLAT("You kill %the1.", victim)); - } - } - void produceGhost(cell *c, eMonster victim, eMonster who) { if(who != moPlayer && !items[itOrbEmpathy]) return; if(markOrb(itOrbUndeath) && !c->monst && isGhostable(victim)) { @@ -3171,75 +4270,121 @@ void produceGhost(cell *c, eMonster victim, eMonster who) { } } -void stabbingAttack(cell *mf, cell *mt, eMonster who) { - int numsh = 0, numflail = 0, numlance = 0; +bool swordAttack(cell *mt, eMonster who, cell *c, int bb) { + eMonster m = c->monst; + if(c->wall == waCavewall) markOrb(bb ? itOrbSword2: itOrbSword); + if(c->wall == waSmallTree || c->wall == waBigTree || c->wall == waRose || c->wall == waCTree || c->wall == waVinePlant || + thruVine(mt, c)) { + playSound(NULL, "hit-axe"+pick123()); + markOrb(bb ? itOrbSword2: itOrbSword); + drawParticles(c, winf[c->wall].color, 16); + addMessage(XLAT("You chop down %the1.", c->wall)); + destroyHalfvine(c); + c->wall = waNone; + } + if(c->wall == waBarrowDig) { + playSound(NULL, "hit-axe"+pick123()); + markOrb(bb ? itOrbSword2: itOrbSword); + drawParticles(c, winf[c->wall].color, 16); + c->wall = waNone; + } + if(c->wall == waBarrowWall && items[itBarrow] >= 25) { + playSound(NULL, "hit-axe"+pick123()); + markOrb(bb ? itOrbSword2: itOrbSword); + drawParticles(c, winf[c->wall].color, 16); + c->wall = waNone; + } + if(canAttack(mt, who, c, m, AF_SWORD)) { + markOrb(bb ? itOrbSword2: itOrbSword); + int k = tkills(); + attackMonster(c, AF_ORSTUN | AF_MSG | AF_SWORD, who); + if(c->monst == moShadow) c->monst = moNone; + produceGhost(c, m, who); + if(tkills() > k) return true; + } + return false; + } + +void swordAttackStatic() { + for(int bb = 0; bb < 2; bb++) + if(sword::orbcount(bb)) + swordAttack(cwt.c, moPlayer, sword::pos(multi::cpid, bb), bb); + } + +void stabbingAttack(cell *mf, cell *mt, eMonster who, int bonuskill) { + int numsh = 0, numflail = 0, numlance = 0, numslash = 0; + + int backdir = neighborId(mt, mf); + + for(int bb=0; bb<2; bb++) if(who == moPlayer && sword::orbcount(bb)) { + int numbb = 0; + cell *sf = sword::pos(mf, sword::angle[multi::cpid] + (bb?S21:0)); + cell *st = sword::pos(mt, sword::shift(mf, mt, sword::angle[multi::cpid]) + (bb?S21:0)); + if(swordAttack(mt, who, st, bb)) numbb++; + if(sf != st && !isNeighbor(sf,st)) { + // also attack the in-transit cell + forCellEx(sb, sf) if(isNeighbor(sb, st) && sb != mf && sb != mt) + if(swordAttack(mt, who, sb, bb)) numbb++; + } + achievement_count("SLASH", numbb, 0); + numslash += numbb; + } + for(int t=0; ttype; t++) { cell *c = mf->mov[t]; if(!c) continue; - if(canAttack(mt,who,c,c->monst,AF_STAB)) { - for(int u=0; utype; u++) { - if(c->mov[u] == mt) { - if(c->monst != moHedge) { - markOrb(itOrbThorns); if(who != moPlayer) markOrb(itOrbEmpathy); - } - if(who != moPlayer) - addMessage(XLAT("%The1 stabs %the2.", who, c->monst)); - else - addMessage(XLAT("You stab %the1.", c->monst)); - int k = tkills(); - eMonster m = c->monst; - killWithMessage(c, true); - produceGhost(c, m, who); - if(tkills() > k) numsh++; - } + + bool stabthere = false, away = true; + for(int u=0; utype; u++) if(c->mov[u] == mt) stabthere = true, away = false; + + if(stabthere && canAttack(mt,who,c,c->monst,AF_STAB)) { + if(c->monst != moHedge) { + markOrb(itOrbThorns); if(who != moPlayer) markOrb(itOrbEmpathy); } + eMonster m = c->monst; + int k = tkills(); + if(attackMonster(c, AF_ORSTUN | AF_STAB | AF_MSG, who)) + produceGhost(c, m, who); + if(tkills() > k) numsh++; } - if(canAttack(mt,who,c,c->monst,AF_BACK)) { - bool away = true; - if(c == mt) away = false; - for(int u=0; utype; u++) if(c->mov[u] == mt) away = false; - if(away) { - if(c->monst == moVizier && c->hitpoints > 1) { - if(who != moPlayer) - addMessage(XLAT("%The1 hits %the2.", who, c->monst)); - else - addMessage(XLAT("You hit %the1.", c->monst)); - c->hitpoints--; - // c->stuntime = 1; + + if(away && c != mt && canAttack(mt,who,c,c->monst,AF_BACK)) { + if(c->monst == moVizier && c->hitpoints > 1) { + fightmessage(c->monst, who, true, AF_BACK); + c->hitpoints--; + // c->stuntime = 1; + } + else { + eMonster m = c->monst; + if(c->monst != moFlailer) { + playSound(NULL, "hit-sword"+pick123()); + fightmessage(c->monst, who, false, AF_BACK); } else { - eMonster m = c->monst; - if(c->monst != moFlailer) { - messageKill(who, c->monst); - } - else { - if(who != moPlayer) - addMessage(XLAT("%The1 tricks %the2.", who, c->monst)); - else - addMessage(XLAT("You trick %the1.", c->monst)); - } - if(c->monst == moFlailer && isPrincess(who) && isUnarmed(who)) - achievement_gain("PRINCESS_PACIFIST"); - - int k = tkills(); - killWithMessage(c); - if(tkills() > k) numflail++; - if(m == moVizier) produceGhost(c, m, who); + playSound(NULL, "hit-sword"+pick123()); + if(who != moPlayer) + addMessage(XLAT("%The1 tricks %the2.", who, c->monst)); + else + addMessage(XLAT("You trick %the1.", c->monst)); } + if(c->monst == moFlailer && isPrincess(who) && isUnarmed(who)) + achievement_gain("PRINCESS_PACIFIST"); + + if(attackMonster(c, 0, who)) numflail++; + if(m == moVizier) produceGhost(c, m, who); } } } if(!isUnarmed(who)) for(int t=0; ttype; t++) { cell *c = mt->mov[t]; - if(c && canAttack(mt,who,c,c->monst,AF_APPROACH)) { - if(who != moPlayer) - addMessage(XLAT("%The1 tricks %the2.", who, c->monst)); - else - addMessage(XLAT("You trick %the1.", c->monst)); - int k = tkills(); - killWithMessage(c); - if(tkills() > k) numlance++; + if(!c) continue; + eMonster mm = c->monst; + int flag = AF_APPROACH; + if(angledist(mt, backdir, t) >= 3) flag |= AF_HORNS; + if(canAttack(mt,who,c,c->monst, flag)) { + if(attackMonster(c, flag | AF_MSG, who)) numlance++; + produceGhost(c, mm, who); } } @@ -3247,7 +4392,7 @@ void stabbingAttack(cell *mf, cell *mt, eMonster who) { if(numlance && numflail && numsh) achievement_gain("MELEE3"); - if(numlance + numflail + numsh >= 5) achievement_gain("MELEE5"); + if(numlance + numflail + numsh + numslash + bonuskill >= 5) achievement_gain("MELEE5"); if(numsh == 2) { if(lastdouble == turncount-1) achievement_count("STAB", 4, 0); @@ -3265,27 +4410,45 @@ bool hasPrincessWeapon(eMonster m) { return m == moPalace || m == moFatGuard; } -int movevalue(eMonster m, cell *c, cell *c2, int flags) { +int stayvalue(eMonster m, cell *c) { + if(!passable_for(c->monst, c, NULL, P_MONSTER | P_MIRROR)) + return -1501; + if(cellEdgeUnstable(c)) + return -1501; + if(againstRose(c, NULL) && !ignoresSmell(c->monst)) return -1500; + return 100; + } + +// friendly version of moveval +int movevalue(eMonster m, cell *c, cell *c2, flagtype flags) { int val = 0; - if(c2 == cwt.c) val = -3000; + if(isPlayerOn(c2)) val = -3000; else if(againstRose(c, c2) && !ignoresSmell(m)) return -1200; else if(m == moPrincess && c2->stuntime && hasPrincessWeapon(c2->monst) && !cellDangerous(c) && !cellDangerous(c2) && canAttack(c, m, c2, c2->monst, AF_IGNORE_UNARMED | flags)) { val = 15000; } else if(canAttack(c,m,c2,c2->monst,flags)) + val = (m == moPrincessArmed && isPrincess(c2->monst)) ? 14000 : // jealousy! - val = isActiveEnemy(c2,m) ? 12000 : - (c2->monst == moSkeleton || c2->monst == moMetalBeast || c2->monst == moTortoise) ? -400 : + isActiveEnemy(c2,m) ? 12000 : + (c2->monst == moSkeleton || c2->monst == moMetalBeast || + c2->monst == moReptile || c2->monst == moTortoise) ? -400 : isIvy(c2) ? 8000 : isInactiveEnemy(c2,m) ? 1000 : -500; else if(monstersnear(c2, NULL, m, NULL, c)) val = 50; // linked with mouse suicide! - else if(passable_for(m, c2, c, 0)) val = 4000; + else if(passable_for(m, c2, c, 0)) { + if(mineMarked(c2) && !ignoresPlates(m)) + val = 50; + else + val = 4000; + } else if(passable_for(m, c2, c, P_DEADLY)) val = -1100; - else val = 0; + else val = -1750; + if(c->monst == moGolem ) val -= c2->cpdist; else if(c->monst == moFriendlyGhost ) @@ -3341,14 +4504,19 @@ int movevalue(eMonster m, cell *c, cell *c2, int flags) { if(d == 2 && c->cpdist > 2) d = 4; val -= d; } - if(!euclid && c->monst == moKnight && c2->master->alt) + if(!euclid && c->monst == moKnight && c2->master->alt) { val -= celldistAlt(c2); + // don't go to external towers + if(c2->wall == waTower && c2->wparam == 1 && !c2->monst) + return 60; + } return val; } #define STRONGWIND 99 -void movegolems(int flags) { +void movegolems(flagtype flags) { + computePathdist(moMouse); int qg = 0; for(int i=0; itype; k++) if(c->mov[k]) { cell *c2 = c->mov[k]; int val = movevalue(m, c, c2, flags); + if(val > bestv) bestv = val, bq = 0; if(val == bestv) bdirs[bq++] = k; } @@ -3378,44 +4551,50 @@ void movegolems(int flags) { if(val == bestv) bdirs[bq++] = STRONGWIND; } } + + for(int i=0; imov[dir] : whirlwind::jumpDestination(c); if(c2->monst) { - if(m == moPrincess) { + bool revenge = (m == moPrincess); + bool jealous = (isPrincess(c->monst) && isPrincess(c2->monst)); + eMonster m2 = c2->monst; + if(revenge) { + playSound(c2, princessgender() ? "dzia-princess" : "dzia-prince"); addMessage(XLAT("%The1 takes %his1 revenge on %the2!", m, c2->monst)); - killWithMessage(c2, false); - c->monst = m = moPrincessArmed; } - else { - if(attackJustStuns(c2) || isDragon(c2->monst)) - addMessage(XLAT("%The1 attacks %the2!", m, c2->monst)); - else { - messageKill(c->monst, c2->monst); - if(isPrincess(c->monst) && isPrincess(c2->monst)) - addMessage("\"That should teach you to take me seriously!\""); - } - eMonster m2 = c2->monst; - killWithMessage(c2, true); - produceGhost(c2, m2, m); + attackMonster(c2, ((revenge||jealous)?0:AF_ORSTUN) | AF_MSG, m); + produceGhost(c2, m2, m); + if(revenge) c->monst = m = moPrincessArmed; + if(jealous) { + playSound(c2, princessgender() ? "dzia-princess" : "dzia-prince"); + addMessage("\"That should teach you to take me seriously!\""); } } else { + passable_for(m, c2, c, P_DEADLY); + DEBT("move"); moveMonster(c2, c); - if(m != moTameBomberbird) + if(m != moTameBomberbird && m != moFriendlyGhost) moveBoatIfUsingOne(c2, c); - if(m == moGolem) c2->monst = moGolemMoved; - if(m == moMouse) c2->monst = moMouseMoved; - if(m == moPrincess) c2->monst = moPrincessMoved; - if(m == moPrincessArmed) c2->monst = moPrincessArmedMoved; - if(m == moTameBomberbird) c2->monst = moTameBomberbirdMoved; - if(m == moKnight) c2->monst = moKnightMoved; - if(m == moFriendlyGhost) c2->stuntime = 1; + if(c2->monst == m) { + if(m == moGolem) c2->monst = moGolemMoved; + if(m == moMouse) c2->monst = moMouseMoved; + if(m == moPrincess) c2->monst = moPrincessMoved; + if(m == moPrincessArmed) c2->monst = moPrincessArmedMoved; + if(m == moTameBomberbird) c2->monst = moTameBomberbirdMoved; + if(m == moKnight) c2->monst = moKnightMoved; + if(m == moFriendlyGhost) c2->stuntime = 1; + } empathyMove(c, c2, dir); } + DEBT("other"); } } achievement_count("GOLEM", qg, 0); @@ -3426,47 +4605,72 @@ void sageheat(cell *c, double v) { if(c->wall == waFrozenLake && HEAT(c) > .6) c->wall = waLake; } -void moveFastMonsters() { - for(int d=0; d<8; d++) movesofgood[d].clear(); - - for(int i=0; imonst == moWitchSpeed) { - 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, MF_PATHDIST); - } - } - -void activateFlashFrom(cell *cf); +void activateFlashFrom(cell *cf, eMonster who, flagtype flags); vector nonmovers; bool sagefresh = true; -void considerEnemyMove(cell *c) { - eMonster m = c->monst; - - if(isActiveEnemy(c, moPlayer)) { - - if(c->monst == moWitch && c->item == itOrbSpeed) { - addMessage(XLAT("%The1 picks up %the2!", moWitch, c->item)); - c->monst = moWitchSpeed; c->item = itNone; - } +int nearestPathPlayer(cell *c) { + for(int i=0; ipathdist < c->pathdist) return nearestPathPlayer(c2); + for(int i=0; imonst == moNecromancer) { +// note: butterflies don't use moveNormal for two reasons: +// 1) to make sure that they move AFTER bulls +// 2) to make sure that they move offscreen +void moveButterflies() { + int j = 0; + for(int i=0; imonst == moButterfly) { + /* // don't move if under attack of a bull + bool underattack = false; + forCellEx(c3, c) + if(c3->monst == moRagingBull && c3->mondir != NODIR && + angledist(c3->type, c3->mondir, neighborId(c3, c)) == 3 && + canAttack(c3, moRagingBull, c, c->monst, AF_BULL) + ) + underattack = true; + if(underattack) continue; */ + cell *c2 = moveNormal(c, 0); + if(butterflies[i].second < 50 && c2) + butterflies[j++] = make_pair(c2, butterflies[i].second+1); + } + } + butterflies.resize(j); + } + +// assume pathdist +void specialMoves() { + for(int i=0; istuntime) continue; + + eMonster m = c->monst; + + if(m == moSleepBull) { + bool wakeup = false; + forCellEx(c2, c) if(c2->monst == moGadfly) { + addMessage(XLAT("%The1 wakes up %the2.", c2->monst, m)); + wakeup = true; + } + for(int i=0; imonst = m = moRagingBull; + c->mondir = NODIR; + } + } + + if(m == moNecromancer) { + computePathdist(moNecromancer); int gravenum = 0, zombienum = 0; cell *gtab[8], *ztab[8]; for(int j=0; jtype; j++) if(c->mov[j]) { @@ -3479,66 +4683,94 @@ void considerEnemyMove(cell *c) { gr->wall = waAncientGrave; gr->monst = moGhost; ztab[hrand(zombienum)]->monst = moZombie; - addMessage(XLAT("%The1 raises some undead!", c->monst)); - return; + addMessage(XLAT("%The1 raises some undead!", m)); + playSound(c, "necromancy"); } } - if(c->monst == moPyroCultist && c->cpdist <= 4 && makeflame(cwt.c, 20, true)) { - addMessage(XLAT("%The1 throws fire at you!", c->monst)); - makeflame(cwt.c, 20, false); - c->monst = moCultist; + else if(m == moOutlaw && c->pathdist <= GUNRANGE) { + killThePlayer(m, nearestPathPlayer(c), 0); + c->stuntime = 1; } - else if(c->monst == moOutlaw && c->pathdist <= GUNRANGE) - killThePlayer(c->monst, ONLY_ONE_PLAYER_POSSIBLE, 0); - - else if(c->monst == moPyroCultist && c->cpdist <= 3) { - // just wait until the player moves + else if(m == moWitchFlash && flashWouldKill(c, AF_GETPLAYER | AF_ONLY_FBUG) && !flashWouldKill(c, false)) { + addMessage(XLAT("%The1 activates her Flash spell!", m)); + m = moWitch; + activateFlashFrom(c, moWitchFlash, AF_MAGIC | AF_GETPLAYER | AF_MSG); + c->stuntime = 1; } - else if(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) && cwt.c->wall != waBoat) { + else if(m == moCrystalSage && c->cpdist <= 4 && isIcyLand(cwt.c) && cwt.c->wall != waBoat) { // only one sage attacks if(sagefresh) { sagefresh = false; if(sagephase == 0) { - addMessage(XLAT("%The1 shows you two fingers.", c->monst)); + addMessage(XLAT("%The1 shows you two fingers.", m)); addMessage(XLAT("You wonder what does it mean?")); } else if(sagephase == 1) { - addMessage(XLAT("%The1 shows you a finger.", c->monst)); + addMessage(XLAT("%The1 shows you a finger.", m)); addMessage(XLAT("You think about possible meanings.")); } else { - addMessage(XLAT("%The1 moves his finger downwards.", c->monst)); + addMessage(XLAT("%The1 moves his finger downwards.", m)); addMessage(XLAT("Your brain is steaming.")); } sagephase++; - sageheat(cwt.c, .0); - for(int i=0; itype; i++) - sageheat(cwt.c->mov[i], .3); + for(int i=0; i 4) continue; + sageheat(t, .0); + for(int i=0; itype; i++) + sageheat(t->mov[i], .3); + } } + c->stuntime = 1; } - 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++; + else if(m == moPyroCultist) { + bool shot = false; + bool dont_approach = false; + // smaller range on the sphere + int firerange = sphere ? 2 : 4; + for(int i=0; imonst)); + makeflame(t, 20, false); + playSound(t, "fire"); + c->monst = moCultist; + shot = true; + } + if(celldistance(c,t) <= 3 && !sphere) dont_approach = true; + } + if(shot || dont_approach) c->stuntime = 1; + } + + else if(m == moVampire) { + for(int i=0; istuntime = 1; + } } - movesofgood[goodmoves].push_back(c); } } } void moveworms() { + if(!size(worms)) return; + computePathdist(moWorm); int wrm = size(worms); for(int i=0; imonst == moTameBomberbirdMoved) c->monst = moTameBomberbird; } -void moverefresh() { +void moverefresh(bool turn = true) { int dcs = size(dcal); for(int i=0; imonst == moSlimeNextTurn) c->monst = moSlime; - if(c->monst == moLesser) c->monst = moLesserM; + if(c->monst == moLesser && !cellEdgeUnstable(c)) c->monst = moLesserM; else if(c->monst == moLesserM) c->monst = moLesser; - if(c->monst == moGreater) c->monst = moGreaterM; + if(c->monst == moGreater && !cellEdgeUnstable(c)) c->monst = moGreaterM; else if(c->monst == moGreaterM) c->monst = moGreater; if(c->stuntime && !isMutantIvy(c)) { c->stuntime--; + int breathrange = sphere ? 2 : 3; if(c->stuntime == 0 && c->monst == moDragonHead) { if(!c->hitpoints) c->hitpoints = 1; else if(shmup::on && dragon::totalhp(c) > 2 && shmup::dragonbreath(c)) { c->hitpoints = 0; } else if(dragon::totalhp(c) <= 2) ; - else if(getMount() && sameMonster(c, cwt.c)) { - if(dragon::target && celldistance(c, dragon::target) <= 3 && makeflame(dragon::target, 5, true)) { + else if(items[itOrbDomination] && isMounted(c)) { + if(dragon::target && celldistance(c, dragon::target) <= breathrange && makeflame(dragon::target, 5, true)) { addMessage(XLAT("%The1 breathes fire!", c->monst)); makeflame(dragon::target, 5, false); + playSound(dragon::target, "fire"); c->hitpoints = 0; } } else { - if(c->cpdist <= 3 && makeflame(cwt.c, 5, true)) { - addMessage(XLAT("%The1 breathes fire at you!", c->monst)); - makeflame(cwt.c, 5, false); - c->hitpoints = 0; + for(int i=0; imonst)); + else if(t->monst) + addMessage(XLAT("%The1 breathes fire at %the2!", c->monst, t->monst)); + else + addMessage(XLAT("%The1 breathes fire!", c->monst)); + makeflame(t, 5, false); + playSound(t, "fire"); + c->hitpoints = 0; + } } } } @@ -3603,20 +4845,56 @@ void moverefresh() { !((tortoise::getb(c) ^ tortoise::babymap[c]) & tortoise::mask)) c->stuntime = 2; + if(c->monst == moReptile) { + if(c->wall == waChasm || cellUnstable(c)) { + c->monst = moNone; + c->wall = waReptile; + c->wparam = reptilemax(); + playSound(c, "click"); + } + else if(isChasmy(c) || isWatery(c)) { + c->wall = waReptileBridge; + c->item = itNone; + c->wparam = reptilemax(); + c->monst = moNone; + playSound(c, "click"); + } + } + if(c->wall == waChasm) { if(c->land != laWhirlwind) c->item = itNone; - if(c->monst && !survivesChasm(c->monst)) { + + if(c->monst && !survivesChasm(c->monst) && c->monst != moReptile) { if(c->monst != moRunDog && c->land == laMotion) achievement_gain("FALLDEATH1"); addMessage(XLAT("%The1 falls!", c->monst)); - killWithMessage(c, false); + fallMonster(c, AF_FALL); + } + } + + else if(isReptile(c->wall) && turn) { + if(c->monst || isPlayerOn(c)) c->wparam = -1; + else if(c->cpdist <= 7) { + c->wparam--; + if(c->wparam == 0) { + if(c->wall == waReptile) c->wall = waChasm; + else placeWater(c, NULL); + c->monst = moReptile; + c->hitpoints = 3; + c->stuntime = 0; + playSound(c, "click"); + } } } - if(isFire(c)) { + else if(isFire(c)) { if(c->monst && !survivesFire(c->monst)) { addMessage(XLAT("%The1 burns!", c->monst)); - killWithMessage(c, false); + if(isBull(c->monst)) { + addMessage(XLAT("Fire is extinguished!")); + c->wall = waNone; + } + fallMonster(c); } if(c->item && itemBurns(c->item)) { addMessage(XLAT("%The1 burns!", c->item)); @@ -3624,28 +4902,97 @@ void moverefresh() { } } - if(c->wall == waMineMine && c->monst && !survivesMine(c->monst)) - explodeMine(c); - - if(isWatery(c)) { + else if(isWatery(c)) { if(c->monst == moLesser || c->monst == moLesserM || c->monst == moGreater || c->monst == moGreaterM) c->monst = moGreaterShark; if(c->monst && !survivesWater(c->monst)) { + playSound(c, "splash"+pick12()); if(isNonliving(c->monst)) addMessage(XLAT("%The1 sinks!", c->monst)); else addMessage(XLAT("%The1 drowns!", c->monst)); - killWithMessage(c, false); + if(isBull(c->monst)) { + addMessage(XLAT("%The1 is filled!", c->wall)); + c->wall = waNone; + } + fallMonster(c, AF_FALL); + } + } + else if(!isWateryOrBoat(c)) { + if(c->monst == moGreaterShark) + c->monst = moGreaterM; + else if(c->monst == moShark || c->monst == moCShark) { + addMessage(XLAT("%The1 suffocates!", c->monst)); + fallMonster(c); + } + else if(c->monst == moKrakenH) { + addMessage(XLAT("%The1 suffocates!", c->monst)); + kraken::kill(c, moNone); } } + if(c->monst == moVineSpirit && !cellHalfvine(c) && c->wall != waVinePlant) { + addMessage(XLAT("%The1 is destroyed!", c->monst)); + fallMonster(c); + } + + if(c->monst) mayExplodeMine(c, c->monst); + if(c->monst && c->wall == waClosedGate && !survivesWall(c->monst)) { + playSound(c, "hit-crush"+pick123()); addMessage(XLAT("%The1 is crushed!", c->monst)); - killWithMessage(c, false); + fallMonster(c); } - if(c->monst && cellUnstable(c) && !ignoresPlates(c->monst) && !shmup::on) { - c->wall = waChasm; + if(c->monst && cellUnstable(c) && !ignoresPlates(c->monst) && !shmup::on) + doesFallSound(c); + } + } + +void consMove(cell *c, eMonster param) { + eMonster m = c->monst; + + if(!normalMover(m)) return; + + if(m == moWitchSpeed) havewhat |= HF_FAST; + bool slow = slowMover(m); + if(slow) havewhat |= HF_SLOW; + + if(param == moYeti && slow) return; + if(param == moTortoise && !slow) return; + if(param == moWitchSpeed && m != moWitchSpeed) return; + + if(isActiveEnemy(c, moPlayer)) { + int goodmoves = 0; + for(int t=0; ttype; t++) { + cell *c2 = c->mov[t]; + if(c2 && c2->pathdist < c->pathdist) + goodmoves++; + } + movesofgood[goodmoves].push_back(c); + } + else + movesofgood[0].push_back(c); + } + +void moveNormals(eMonster param) { + computePathdist(param); + + for(int d=0; d<8; d++) movesofgood[d].clear(); + + for(int i=0; ipathdist == PINFD) consMove(c, param); + } + + for(int d=0; d<8; d++) for(int i=0; imonst)) { + moveNormal(c, MF_PATHDIST); } } } @@ -3655,67 +5002,71 @@ void movemonsters() { sagefresh = true; turncount++; + specialMoves(); + DEBT("ghosts"); moveghosts(); - + + DEBT("butterflies"); + moveButterflies(); + DEBT("normal"); + moveNormals(moYeti); + + DEBT("slow"); + if(havewhat & HF_SLOW) moveNormals(moTortoise); - 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, MF_PATHDIST); - } - if(sagefresh) sagephase = 0; DEBT("ivy"); moveivy(); DEBT("slimes"); groupmove(moSlime, 0); + DEBT("sharks"); + if(havewhat & HF_SHARK) groupmove(moShark, 0); DEBT("eagles"); - if(haveeagles) groupmove(moEagle, 0), groupmove(moEagle, MF_NOATTACKS | MF_ONLYEAGLE); + if(havewhat & HF_BIRD) groupmove(moEagle, 0); + if(havewhat & HF_EAGLES) groupmove(moEagle, MF_NOATTACKS | MF_ONLYEAGLE); + DEBT("eagles"); + if(havewhat & HF_REPTILE) groupmove(moReptile, 0); DEBT("air"); - if(haveair) { + if(havewhat & HF_AIR) { airmap.clear(); groupmove(moAirElemental, 0); buildAirmap(); } DEBT("earth"); - if(haveearth) groupmove(moEarthElemental, 0); + if(havewhat & HF_EARTH) groupmove(moEarthElemental, 0); DEBT("water"); - if(havewater) groupmove(moWaterElemental, 0); + if(havewhat & HF_WATER) groupmove(moWaterElemental, 0); DEBT("leader"); - if(haveleader) groupmove(moPirate, 0); + if(havewhat & HF_LEADER) groupmove(moPirate, 0); DEBT("mutant"); - if(havemutant) movemutant(); + if(havewhat & HF_MUTANT) movemutant(); DEBT("bugs"); - if(havebugs) hive::movebugs(); + if(havewhat & HF_BUG) hive::movebugs(); DEBT("whirlpool"); - if(havewhirlpool) whirlpool::move(); - DEBT("whirlpool"); - if(havewhirlwind) whirlwind::move(); + if(havewhat & HF_WHIRLPOOL) whirlpool::move(); + DEBT("whirlwind"); + if(havewhat & HF_WHIRLWIND) whirlwind::move(); + DEBT("river"); + if(havewhat & HF_RIVER) prairie::move(); DEBT("worm"); - cell *savepos = cwt.c; + cell *savepos[MAXPLAYER]; + + for(int i=0; i 1 && cwt.c->monst) + if(items[itOrbDomination] > ORBBASE && cwt.c->monst) return false; if(cwt.c->monst) { if(checkonly) return true; @@ -3750,50 +5104,52 @@ bool checkNeedMove(bool checkonly, bool attacking) { addMessage(XLAT("You need to move to give space to %the1!", cwt.c->monst)); } else if(cwt.c->wall == waRoundTable) { - if(markOrb2(itOrbGhost)) return false; + if(markOrb2(itOrbAether)) return false; if(checkonly) return true; addMessage(XLAT("It would be impolite to land on the table!")); } else if(cwt.c->wall == waLake) { - if(markOrb2(itOrbGhost)) return false; + if(markOrb2(itOrbAether)) return false; + if(markOrb2(itOrbFish)) return false; if(checkonly) return true; addMessage(XLAT("Ice below you is melting! RUN!")); } else if(!attacking && cellEdgeUnstable(cwt.c)) { - if(markOrb2(itOrbGhost)) return false; + if(markOrb2(itOrbAether)) return false; if(checkonly) return true; addMessage(XLAT("Nothing to stand on here!")); } else if(cwt.c->wall == waSea || cwt.c->wall == waCamelotMoat) { if(markOrb(itOrbFish)) return false; - if(markOrb2(itOrbGhost)) return false; + if(markOrb2(itOrbAether)) return false; if(checkonly) return true; addMessage(XLAT("You have to run away from the water!")); } else if(cwt.c->wall == waClosedGate) { - if(markOrb2(itOrbGhost)) return false; + if(markOrb2(itOrbAether)) return false; if(checkonly) return true; addMessage(XLAT("The gate is closing right on you! RUN!")); } else if(isFire(cwt.c) && !markOrb(itOrbWinter) && !markOrb2(itOrbShield)) { - if(markOrb2(itOrbGhost)) return false; + if(markOrb2(itOrbAether)) return false; if(checkonly) return true; addMessage(XLAT("This spot will be burning soon! RUN!")); } - else if(items[itOrbGhost] == 1 && !passable(cwt.c, NULL, P_ISPLAYER)) { - if(markOrb2(itOrbGhost)) return false; - if(checkonly) return true; - addMessage(XLAT("Your Aether power has expired! RUN!")); - } else if(cwt.c->wall == waChasm) { - if(markOrb2(itOrbGhost)) return false; + if(markOrb2(itOrbAether)) return false; if(checkonly) return true; addMessage(XLAT("The floor has collapsed! RUN!")); } - else if(items[itOrbGhost] > 1 && !passable(cwt.c, NULL, P_ISPLAYER | P_NOAETHER)) { - if(markOrb2(itOrbGhost)) return false; + else if(items[itOrbAether] > ORBBASE && !passable(cwt.c, NULL, P_ISPLAYER | P_NOAETHER)) { + if(markOrb2(itOrbAether)) return false; return true; } + else if(!passable(cwt.c, NULL, P_ISPLAYER)) { + if(isFire(cwt.c)) return false; // already checked: have Shield + if(markOrb2(itOrbAether)) return false; + if(checkonly) return true; + addMessage(XLAT("Your Aether power has expired! RUN!")); + } else return false; if(hardcore) { canmove = false; @@ -3846,7 +5202,51 @@ void restoreGolems(int qty, eMonster m, int hp = 0) { } } +cell *recallCell; + +bool activateRecall() { + if(!recallCell) { + addMessage("Error: no recall"); + return false; + } + items[itOrbRecall] = 0; items[itOrbSafety] = 0; + if(!makeEmpty(recallCell)) { + addMessage(XLAT("Your Orb of Recall is blocked by something big!")); + recallCell = NULL; + return false; + } + + killFriendlyIvy(); + movecost(cwt.c, recallCell); + playerMoveEffects(cwt.c, recallCell); + mirror::destroy(); + + items[itOrbSword] = 0; + items[itOrbSword2] = 0; + + cwt.c = recallCell; recallCell = NULL; + cwt.spin = hrand(cwt.c->type); flipplayer = !!(hrand(2)); + fullcenter(); + makeEmpty(cwt.c); + forCellEx(c2, cwt.c) + if(c2->monst != moMutant) + c2->stuntime = 4; + if(shmup::on) shmup::recall(); + if(multi::players > 1) multi::recall(); + bfs(); + checkmove(); + drawSafety(); + addMessage(XLAT("You are recalled!")); + return true; + } + +void saveRecall(cell *c2) { + if(!recallCell) recallCell = c2; + } + void activateSafety(eLand l) { + if(recallCell && activateRecall()) + return; savePrincesses(); int gg = countMyGolems(moGolem); int gb = countMyGolems(moTameBomberbird); @@ -3861,6 +5261,7 @@ void activateSafety(eLand l) { if(l == laTemple) l = laRlyeh; if(l == laClearing) l = laOvergrown; if(l == laWhirlpool) l = laOcean; + if(l == laCrossroads5) l = laCrossroads2; // could not fit! if(l == laCamelot && !(tactic::on && tactic::lasttactic == laCamelot)) l = laCrossroads; firstland = l; @@ -3885,10 +5286,12 @@ bool hasSafeOrb(cell *c) { return c->item == itOrbSafety || c->item == itOrbShield || + c->item == itOrbShell || c->item == itOrbYendor; } void checkmove() { + if(multi::players > 1 && !multi::checkonly) return; if(hardcore) return; msgscroll = 0; bool orbusedbak[ittypes]; @@ -3897,6 +5300,7 @@ void checkmove() { for(int i=0; itype; i++) @@ -3911,18 +5315,25 @@ void checkmove() { timerstart = time(NULL); timerstopped = false; } + items[itWarning]-=2; for(int i=0; imonst == moFriendlyIvy) + killMonster(on, moPlayer); + if(on->monst) { + addMessage(XLAT("There is no room for %the1!", m)); + return; + } if(passable(on, moveto, m == moTameBomberbird ? P_FLYING : 0)) - cwt.c->monst = m; + on->monst = m; else { - cwt.c->monst = m; - killWithMessage(cwt.c); + on->monst = m; if(isFire(on)) addMessage(XLAT("%The1 burns!", m)); else if(on->wall == waChasm) @@ -3933,9 +5344,37 @@ void placeGolem(cell *on, cell *moveto, eMonster m) { addMessage(XLAT("%The1 drowns!", m)); else if(isWall(on)) addMessage(XLAT("%The1 is crushed!", m)); + else if(m == moTameBomberbird && cwt.c->wall == waBoat) + return; + else + addMessage(XLAT("%The1 is destroyed!", m)); + + printf("mondir = %d\n", on->mondir); + fallMonster(cwt.c); } } +bool multiRevival(cell *on, cell *moveto) { + int fl = 0; + if(items[itOrbAether]) fl |= P_AETHER; + if(items[itOrbFish]) fl |= P_FISH; + if(items[itOrbWinter]) fl |= P_WINTER; + if(passable(on, moveto, fl)) { + int id = multi::revive_queue[0]; + for(int i=1; iland == laPower && to->land != laPower) { int n=0; @@ -3965,7 +5404,8 @@ void movecost(cell* from, cell *to) { bool tortoiseOK = to->land == from->land || to->land == laTortoise || - (to->land == laDragon && from->land != laTortoise); + (to->land == laDragon && from->land != laTortoise) || + chaosmode; if(tortoise::seek() && !from->item && !tortoiseOK && passable(from, NULL, 0)) { from->item = itBabyTortoise; @@ -3991,6 +5431,100 @@ void gainLife() { if(items[itOrbLife] > 5) items[itOrbLife] = 5; } +void collectMessage(cell *c2, eItem which) { + bool specialmode = + yendor::on || tactic::on || princess::challenge || euclid || sphere; + + if(which == itTreat) ; + else if(isElementalShard(which)) { + int tsh = + items[itFireShard] + items[itAirShard] + items[itWaterShard] + items[itEarthShard] + + items[itElemental]; + if(tsh == 0) { + addMessage(XLAT("Collect four different Elemental Shards!")); + addMessage(XLAT("Unbalanced shards in your inventory are dangerous.")); + } + else { + string t = XLAT("You collect %the1. (%2)", which, its(items[which]+1)); + addMessage(t); + } + } + else if(which == itKey) + addMessage(XLAT("You have found the Key! Now unlock this Orb of Yendor!")); + else if(which == itGreenStone && !items[itGreenStone]) + addMessage(XLAT("This orb is dead...")); + else if(which == itGreenStone) + addMessage(XLAT("Another Dead Orb.")); + else if(itemclass(which) != IC_TREASURE) + addMessage(XLAT("You have found %the1!", which)); + else if(which == itBabyTortoise) { + playSound(c2, playergender() ? "speak-princess" : "speak-prince"); + addMessage(XLAT("Aww, poor %1... where is your family?", which)); + } + else if(gold() == 0 && !specialmode) + addMessage(XLAT("Wow! %1! This trip should be worth it!", which)); + else if(gold() == 1 && !specialmode) + addMessage(XLAT("For now, collect as much treasure as possible...")); + else if(gold() == 2 && !specialmode) + addMessage(XLAT("Prove yourself here, then find new lands, with new quests...")); + else if(!items[which] && itemclass(which) == IC_TREASURE) + addMessage(XLAT("You collect your first %1!", which)); + else if(items[which] == 4 && maxgold() == 4 && !specialmode) { + addMessage(XLAT("You feel that %the2 become%s2 more dangerous.", which, c2->land)); + addMessage(XLAT("With each %1 you collect...", which, c2->land)); + } + else if(items[which] == 9 && maxgold() == 9 && !specialmode) + addMessage(XLAT("Are there any magical orbs in %the1?...", c2->land)); + else if(items[which] == 10 && maxgold() == 10 && !specialmode) { + addMessage(XLAT("You feel that %the1 slowly become%s1 dangerous...", c2->land)); + addMessage(XLAT("Better find some other place.")); + } + else if(which == itSpice && items[itSpice] == 7 && !specialmode) + addMessage(XLAT("You have a vision of the future, fighting demons in Hell...")); + else if(which == itSpice && items[itSpice] == 9 && !specialmode) + addMessage(XLAT("You will be fighting red rock snakes, too...")); + else if(which == itKraken && items[itKraken] == 9 && !specialmode) + addMessage(XLAT("You feel that a magical weapon is waiting for you...")); +// else if(which == itFeather && items[itFeather] == 10) +// addMessage(XLAT("There should be a Palace somewhere nearby...")); + else if(which == itElixir && items[itElixir] == 4 && !specialmode) + addMessage(XLAT("With this Elixir, your life should be long and prosperous...")); + else if(which == itRuby && items[itRuby] == 4 && !specialmode) { + addMessage(XLAT("You feel something strange about gravity here...")); + } + else if(which == itPalace && items[itPalace] == 4 && !specialmode) { + addMessage(XLAT("The rug depicts a man in a deep dungeon, unable to leave.")); + } + else if(which == itIvory && items[itIvory] == 4 && !specialmode) { + addMessage(XLAT("You feel attuned to gravity, ready to face mountains and dungeons.")); + } + else if(which == itBone && items[itBone] == 6 && !specialmode) + addMessage(XLAT("The Necromancer's Totem contains hellish incantations...")); + else if(which == itStatue && items[itStatue] == 6 && !specialmode) + addMessage(XLAT("The inscriptions on the Statue of Cthulhu point you toward your destiny...")); + else if(which == itStatue && items[itStatue] == 4 && !specialmode) + addMessage(XLAT("There must be some temples of Cthulhu in R'Lyeh...")); + else if(which == itDiamond && items[itDiamond] == 8 && !specialmode) + addMessage(XLAT("Still, even greater treasures lie ahead...")); + else if(which == itFernFlower && items[itFernFlower] == 4 && !specialmode) + addMessage(XLAT("You overheard Hedgehog Warriors talking about emeralds...")); + else if(which == itEmerald && items[itEmerald] == 4 && !specialmode && !chaosmode) + addMessage(XLAT("You overhear miners talking about a castle...")); + else if(which == itEmerald && items[itEmerald] == 5 && !specialmode && !chaosmode) + addMessage(XLAT("A castle in the Crossroads...")); + else if(which == itShard) ; + else { + int qty = (which == itBarrow) ? c2->landparam : 1; + string t; + if(which == itBarrow && items[which] < 25 && items[which] + qty >= 25) + t = XLAT("Your energy swords get stronger!"); + else if(maxgold() < 25 && items[which] + qty >= 25) + t = XLAT("You feel even more attuned to the magic of this land!"); + else t = XLAT("You collect %the1. (%2)", which, its(items[which]+qty)); + addMessage(t); + } + } + bool collectItem(cell *c2, bool telekinesis) { int pg = gold(); @@ -3999,9 +5533,6 @@ bool collectItem(cell *c2, bool telekinesis) { if(itemHidden(c2) && !telekinesis && !(isWatery(c2) && markOrb(itOrbFish))) return false; - if(c2->item == itOrbYendor && telekinesis && yendor::state(c2) != yendor::ysUnlocked) - return false; - /* if(c2->item == itHolyGrail && telekinesis) return false; */ @@ -4013,90 +5544,28 @@ bool collectItem(cell *c2, bool telekinesis) { if(c2->item == itPalace && items[c2->item] == 12) princess::forceVizier = true; - bool specialmode = - yendor::on || tactic::on || princess::challenge || euclid; - - if(isElementalShard(c2->item)) { - int tsh = - items[itFireShard] + items[itAirShard] + items[itWaterShard] + items[itEarthShard] + - items[itElemental]; - if(tsh == 0) { - addMessage(XLAT("Collect four different Elemental Shards!")); - addMessage(XLAT("Unbalanced shards in your inventory are dangerous.")); - } - else { - string t = XLAT("You collect %the1.", c2->item); - addMessage(t); - } - } - 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(c2->item == itBabyTortoise) - addMessage(XLAT("Aww, poor %1... where is your family?", c2->item)); - else if(gold() == 0 && !specialmode) - addMessage(XLAT("Wow! %1! This trip should be worth it!", c2->item)); - else if(gold() == 1 && !specialmode) - addMessage(XLAT("For now, collect as much treasure as possible...")); - else if(gold() == 2 && !specialmode) - addMessage(XLAT("Prove yourself here, then find new lands, with new quests...")); - else if(!items[c2->item] && itemclass(c2->item) == IC_TREASURE) - addMessage(XLAT("You collect your first %1!", c2->item)); - else if(items[c2->item] == 4 && maxgold() == 4 && !specialmode) { - 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 && !specialmode) - addMessage(XLAT("Are there any magical orbs in %the1?...", c2->land)); - else if(items[c2->item] == 10 && maxgold() == 10 && !specialmode) { - 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 && !specialmode) - addMessage(XLAT("You have a vision of the future, fighting demons in Hell...")); - else if(c2->item == itSpice && items[itSpice] == 10 && !specialmode) - addMessage(XLAT("You will be fighting red rock snakes, too...")); -// else if(c2->item == itFeather && items[itFeather] == 10) -// addMessage(XLAT("There should be a Palace somewhere nearby...")); - else if(c2->item == itElixir && items[itElixir] == 4 && !specialmode) - addMessage(XLAT("With this Elixir, your life should be long and prosperous...")); - else if(c2->item == itBone && items[itBone] == 6 && !specialmode) - addMessage(XLAT("The Necromancer's Totem contains hellish incantations...")); - else if(c2->item == itStatue && items[itStatue] == 6 && !specialmode) - addMessage(XLAT("The inscriptions on the Statue of Cthulhu point you toward your destiny...")); - else if(c2->item == itStatue && items[itStatue] == 4 && !specialmode) - addMessage(XLAT("There must be some temples of Cthulhu in R'Lyeh...")); - else if(c2->item == itDiamond && items[itDiamond] == 8 && !specialmode) - addMessage(XLAT("Still, even greater treasures lie ahead...")); - else if(c2->item == itFernFlower && items[itFernFlower] == 4 && !specialmode) - addMessage(XLAT("You overheard Hedgehog Warriors talking about emeralds...")); - else if(c2->item == itEmerald && items[itEmerald] == 4 && !specialmode && !chaosmode) - addMessage(XLAT("You overhear miners talking about a castle...")); - else if(c2->item == itEmerald && items[itEmerald] == 5 && !specialmode && !chaosmode) - addMessage(XLAT("A castle in the Crossroads...")); - else { - string t = XLAT("You collect %the1.", c2->item); - addMessage(t); - } + if(!cantGetGrimoire(c2, false)) collectMessage(c2, c2->item); } - if(c2->item == itOrbSpeed) { + if(isRevivalOrb(c2->item) && multi::revive_queue.size()) { + multiRevival(cwt.c, c2); + } + else if(c2->item == itOrbSpeed) { items[c2->item] += 31; + playSound(c2, "pickup-speed"); } else if(c2->item == itOrbLife) { + playSound(c2, "pickup-orb"); // TODO summon if(shmup::on) gainLife(); else placeGolem(cwt.c, c2, moGolem); } else if(c2->item == itOrbFriend) { + playSound(c2, "pickup-orb"); // TODO summon if(shmup::on) gainLife(); else placeGolem(cwt.c, c2, moTameBomberbird); } else if(c2->item == itOrbSafety) { + playSound(c2, "pickup-orb"); // TODO safety items[c2->item] += 7; if(shmup::on) shmup::safety = true; @@ -4105,80 +5574,142 @@ bool collectItem(cell *c2, bool telekinesis) { return true; } else if(c2->item == itOrbLightning) { + playSound(c2, "pickup-orb"); items[c2->item] += 78; } - else if(c2->item == itOrbPreserve) { + else if(c2->item == itOrbStone) { + playSound(c2, "pickup-orb"); + items[c2->item] += 61; + } + else if(c2->item == itOrbNature) { + playSound(c2, "pickup-orb"); + if(shmup::on) gainLife(); + else items[c2->item] += 61; + } + else if(c2->item == itOrbRecall) { + saveRecall(c2); + playSound(c2, "pickup-orb"); + items[c2->item] += 61; + } + else if(c2->item == itOrbTime) { + playSound(c2, "pickup-orb"); items[c2->item] += 78; } else if(c2->item == itOrbLove) { + playSound(c2, "pickup-orb"); items[c2->item] += 31; } - else if(c2->item == itOrbTelekinesis) { + else if(c2->item == itOrbSpace) { + playSound(c2, "pickup-orb"); items[c2->item] += 78; } else if(c2->item == itOrbThorns) { + playSound(c2, "pickup-orb"); items[c2->item] += 78; } + else if(c2->item == itOrbSword) { + playSound(c2, "pickup-orb"); + if(!items[itOrbSword] && !items[itOrbSword2]) + sword::shuffle(); + items[c2->item] += 61 + 30 * multi::activePlayers(); + } + else if(c2->item == itOrbSword2) { + playSound(c2, "pickup-orb"); + if(!items[itOrbSword] && !items[itOrbSword2]) + sword::shuffle(); + items[c2->item] += 41 + 20 * multi::activePlayers(); + } else if(c2->item == itOrbFlash) { + playSound(c2, "pickup-orb"); items[c2->item] += 78; } else if(c2->item == itOrbShield) { + playSound(c2, "pickup-orb"); // TODO Shield items[c2->item] += 16; orbused[itOrbShield] = false; } else if(c2->item == itOrbWater) { + playSound(c2, "pickup-orb"); items[c2->item] += 31; } else if(c2->item == itOrbAir) { + playSound(c2, "pickup-orb"); items[c2->item] += 67; } else if(c2->item == itOrbFrog) { + playSound(c2, "pickup-orb"); + items[c2->item] += 45; + } + else if(c2->item == itOrbDash) { + playSound(c2, "pickup-orb"); items[c2->item] += 45; } else if(c2->item == itOrbDiscord) { + playSound(c2, "pickup-orb"); // make it seem to be 23 if(!items[c2->item]) items[c2->item]++; items[c2->item] += 23; } else if(c2->item == itOrbSummon) { + playSound(c2, "pickup-orb"); items[c2->item] += 121; } else if(c2->item == itOrbMatter) { + playSound(c2, "pickup-orb"); + items[c2->item] += 67; + } + else if(c2->item == itOrbHorns) { + playSound(c2, "pickup-orb"); + items[c2->item] += 67; + } + else if(c2->item == itOrbBull) { + playSound(c2, "pickup-orb"); items[c2->item] += 67; } else if(c2->item == itOrbFish) { - items[c2->item] += 31; + playSound(c2, "pickup-orb"); + items[c2->item] += 21 + 10 * multi::activePlayers(); } else if(c2->item == itOrbWinter) { + playSound(c2, "pickup-winter"); items[c2->item] += 31; } else if(c2->item == itRevolver) { + playSound(c2, "pickup-key"); items[c2->item] = 6; } else if(c2->item == itOrbStunning) { + playSound(c2, "pickup-orb"); items[c2->item] += 61; } else if(c2->item == itOrbLuck) { + playSound(c2, "pickup-orb"); items[c2->item] += 61; } else if(c2->item == itOrbUndeath) { + playSound(c2, "pickup-orb"); if(shmup::on) gainLife(); else items[c2->item] += 31; } else if(c2->item == itOrbFreedom) { + playSound(c2, "pickup-orb"); items[c2->item] += 31; } else if(c2->item == itOrb37) { + playSound(c2, "pickup-orb"); items[c2->item] += 51; } else if(c2->item == itOrbEnergy) { + playSound(c2, "pickup-orb"); items[c2->item] += 51; } else if(c2->item == itOrbDomination) { + playSound(c2, "pickup-orb"); if(shmup::on) gainLife(); else items[c2->item] += 91; } else if(c2->item == itOrbShell) { + playSound(c2, "pickup-orb"); // TOOD shield items[c2->item] += 67; } else if(c2->item == itBabyTortoise) { @@ -4190,42 +5721,57 @@ bool collectItem(cell *c2, bool telekinesis) { if(seek()) { cell *c = passable(cwt.c, NULL, 0) ? cwt.c : c2; c->item = itBabyTortoise; + if(c == c2) dopickup = false; babymap[c] = bold; } else items[itBabyTortoise]++; } - else if(c2->item == itOrbSkunk) { + else if(c2->item == itOrbBeauty) { + playSound(c2, "pickup-orb"); items[c2->item] += 41; } else if(c2->item == itOrbEmpathy) { + playSound(c2, "pickup-orb"); if(shmup::on) gainLife(); else items[c2->item] += 41; } else if(c2->item == itOrbFire) { - items[c2->item] += 31; + playSound(c2, "fire"); + items[c2->item] += (sphere ? 3 : 31); } else if(c2->item == itOrbDragon) { - items[c2->item] += 78; + playSound(c2, "pickup-orb"); + items[c2->item] += sphere ? 10 : 78; } else if(c2->item == itOrbIllusion) { + playSound(c2, "pickup-orb"); items[c2->item] += 78; } else if(c2->item == itOrbPsi) { + playSound(c2, "pickup-orb"); items[c2->item] += 78; } else if(c2->item == itOrbInvis) { + playSound(c2, "pickup-orb"); items[c2->item] += 31; } - else if(c2->item == itOrbGhost) { + else if(c2->item == itOrbAether) { + playSound(c2, "pickup-orb"); items[c2->item] += 31; } else if(c2->item == itOrbDigging) { + playSound(c2, "pickup-orb"); items[c2->item] += 78; } else if(c2->item == itOrbTeleport) { + playSound(c2, "pickup-orb"); items[c2->item] += 78; } + else if(c2->item == itOrbYendor && yendor::state(c2) != yendor::ysUnlocked) { + dopickup = false; + } else if(c2->item == itOrbYendor) { + playSound(c2, "tada"); items[itOrbShield] += 31; for(int i=0; i c(chaosmode, false); yendor::bestscore[modecode()][yendor::challenge] = max(yendor::bestscore[modecode()][yendor::challenge], items[itOrbYendor]); - yendor::uploadScore(); + yendor::uploadScore(); } } addMessage(XLAT("CONGRATULATIONS!")); @@ -4262,6 +5809,7 @@ bool collectItem(cell *c2, bool telekinesis) { achievement_victory(false); } else if(c2->item == itHolyGrail) { + playSound(c2, "tada"); int v = newRoundTableRadius() + 12; items[itOrbTeleport] += v; items[itOrbSpeed] += v; @@ -4271,6 +5819,7 @@ bool collectItem(cell *c2, bool telekinesis) { achievement_collection(c2->item, pg, gold()); } else if(c2->item == itKey) { + playSound(c2, "pickup-key"); for(int i=0; iitem == itTreat) { + playSound(c2, "pickup-scroll"); + halloween::getTreat(c2); + } else { bool lhu = hellUnlocked(); - if(c2->item) gainItem(c2->item); + if(c2->item == itBarrow) + for(int i=0; ilandparam; i++) gainItem(c2->item); + else if(c2->item) gainItem(c2->item); int g2 = gold(); + + if(c2->item) { + char ch = iinf[c2->item].glyph; + if(ch == '*') playSound(c2, "pickup-gem"); + else if(ch == '$' || ch == 'x') playSound(c2, "pickup-gold"); + else if(ch == '%' || ch == ';') playSound(c2, "pickup-potion"); + else playSound(c2, "pickup-scroll"); + } if(items[itFireShard] && items[itAirShard] && items[itWaterShard] && items[itEarthShard]) { items[itFireShard]--; @@ -4306,7 +5869,7 @@ bool collectItem(cell *c2, bool telekinesis) { gainItem(itElemental); gainItem(itElemental); gainItem(itElemental); - addMessage(XLAT("You construct some Elemental Gems!", c2->item)); + addMessage(XLAT("You construct some Elemental Gems!", c2->item) + itemcounter(items[itElemental])); } if(c2->item == itBounty) @@ -4319,6 +5882,11 @@ bool collectItem(cell *c2, bool telekinesis) { achievement_gain("CHAOS", 'C'); chaosAchieved = true; } + +#ifdef MOBILE + if(pg < lastsafety + 45 && g2 >= lastsafety + 45) + addMessage(XLAT("The Orb of Safety from the Land of Eternal Motion might save you.")); +#endif if(pg < 15 && g2 >= 15) addMessage(XLAT("Collect treasure to access more different lands...")); @@ -4354,6 +5922,12 @@ bool collectItem(cell *c2, bool telekinesis) { numOrb++; if(numOrb) achievement_count("ORB", numOrb, 0); + if(princess::reviveAt && gold() >= princess::reviveAt) { + princess::reviveAt = 0, + items[itSavedPrincess] = 1; + addMessage("You have enough treasure now to revive the Princess!"); + } + return false; } @@ -4510,6 +6084,21 @@ void knightFlavorMessage(cell *c2) { else if(msgid == 13) { addMessage(XLAT("\"Be careful in the Rose Garden! It is beautiful, but very dangerous!\"")); } + else if(msgid == 14) { + addMessage(XLAT("\"There is no royal road to geometry.\"")); + } + else if(msgid == 15) { + addMessage(XLAT("\"There is no branch of mathematics, however abstract, ")); + addMessage(XLAT("which may not some day be applied to phenomena of the real world.\"")); + } + else if(msgid == 16) { + addMessage(XLAT("\"It is not possession but the act of getting there, ")); + addMessage(XLAT("which grants the greatest enjoyment.\"")); + } + else if(msgid == 17) { + addMessage(XLAT("\"We live in a beautiful and orderly world, ")); + addMessage(XLAT("and not in a chaos without norms.\"")); + } else if(msgid == 25) { addMessage(XLAT("\"Thank you very much for talking, and have a great rest of your day!\"")); } @@ -4520,19 +6109,78 @@ void knightFlavorMessage(cell *c2) { msgid++; } -void uncoverMines(cell *c, int lev) { +void uncoverMines(cell *c, int lev, int dist) { if(c->wall == waMineUnknown) c->wall = waMineOpen; - if(!lev) return; - if(c->wall == waMineOpen) { - bool minesNearby = false; - for(int i=0; itype; i++) - if(c->mov[i] && c->mov[i]->wall == waMineMine) - minesNearby = true; - if(!minesNearby) for(int i=0; itype; i++) - if(c->mov[i] && (c->mov[i]->wall == waMineUnknown || c->mov[i]->wall == waMineOpen)) - uncoverMines(c->mov[i], lev-1); + bool minesNearby = false; + bool nominesNearby = false; + bool mineopens = false; + + forCellEx(c2, c) { + if(c2->wall == waMineMine) minesNearby = true; + if(c2->wall == waMineOpen) mineopens = true; + if(c2->wall == waMineUnknown && !c2->item) nominesNearby = true; + } + + if(lev && (nominesNearby || mineopens) && !minesNearby) for(int i=0; itype; i++) + if(c->mov[i] && (c->mov[i]->wall == waMineUnknown || c->mov[i]->wall == waMineOpen)) + uncoverMines(c->mov[i], lev-1, dist+1); + + if(minesNearby && !nominesNearby && dist == 0) { + forCellEx(c2, c) + if(c2->wall == waMineMine && c2->land == laMinefield) + c2->landparam |= 1; + } + } + +namespace orbbull { + cell *prev[MAXPLAYER]; + eLastmovetype prevtype[MAXPLAYER]; + int count; + + bool is(cell *c1, cell *c2, cell *c3) { + int lp = neighborId(c2, c1); + int ln = neighborId(c2, c3); + return lp >= 0 && ln >= 0 && angledist(c2, lp, ln) == 3; + } + + void gainBullPowers() { + items[itOrbShield]++; orbused[itOrbShield] = true; + items[itOrbThorns]++; orbused[itOrbThorns] = true; + items[itOrbHorns]++; orbused[itOrbHorns] = true; + } + + void check() { + int cp = multi::cpid; + if(cp < 0 || cp >= MAXPLAYER) cp = 0; + + if(!items[itOrbBull]) { + prev[cp] = NULL; + return; + } + + bool seq = false; + + if(prev[cp] && prevtype[cp] == lmMove && lastmovetype == lmMove) + seq = is(prev[cp], lastmove, cwt.c); + + if(prev[cp] && prevtype[cp] == lmMove && lastmovetype == lmAttack) + seq = is(prev[cp], cwt.c, lastmove); + + if(prev[cp] && prevtype[cp] == lmAttack && lastmovetype == lmAttack && count) + seq = lastmove == prev[cp]; + + if(prev[cp] && prevtype[cp] == lmAttack && lastmovetype == lmMove && count) + seq = cwt.c == prev[cp]; + + prev[cp] = lastmove; prevtype[cp] = lastmovetype; + + if(seq) { + if(lastmovetype == lmMove) count++; + gainBullPowers(); + } + else count = 0; } } @@ -4542,15 +6190,13 @@ void monstersTurn() { DEBT("charge"); if(elec::havecharge) elec::act(); mirror::destroyStray(); - DEBT("heat"); - heat::processheat(); - 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(); } + + if(playerInPower() && (phase2 || !items[itOrbSpeed]) && (havewhat & HF_FAST)) + moveNormals(moWitchSpeed); + if(phase2 && markOrb(itOrbEmpathy)) { bfs(); movegolems(AF_FAST); @@ -4560,20 +6206,34 @@ void monstersTurn() { refreshFriend(dcal[i]); } } + DEBT("rop"); + reduceOrbPowers(); + int phase1 = (1 & items[itOrbSpeed]); DEBT("lc"); if(!phase1) livecaves(); + if(!phase1) ca::simulate(); if(!phase1) heat::dryforest(); + DEBT("heat"); + heat::processheat(); // if(elec::havecharge) elec::drawcharges(); if(items[itOrbFreedom]) - checkFreedom(cwt.c); + for(int i=0; iland == laMotion) { addMessage(XLAT("%The1 falls!", waThumperOn)); + doesFallSound(cto); } else if(cellUnstable(cto)) { addMessage(XLAT("%The1 fills the hole!", waThumperOn)); @@ -4604,6 +6265,7 @@ void pushThumper(cell *th, cell *cto) { bool canPushThumperOn(cell *tgt, cell *thumper, cell *player) { if(tgt->wall == waBoat) return false; + if(isReptile(tgt->wall)) return false; if(isWatery(tgt) && !tgt->monst) return true; if(tgt->wall == waChasm && !tgt->monst) @@ -4616,24 +6278,78 @@ bool canPushThumperOn(cell *tgt, cell *thumper, cell *player) { void activateActiv(cell *c, bool msg) { if(msg) addMessage(XLAT("You activate %the1.", c->wall)); - if(c->wall == waThumperOff) c->wall = waThumperOn; - if(c->wall == waBonfireOff) c->wall = waFire; + if(c->wall == waThumperOff) { + playSound(c, "click"); + c->wall = waThumperOn; + } + if(c->wall == waBonfireOff) { + playSound(c, "fire"); + c->wall = waFire; + } c->wparam = 100; } bool scentResistant() { - return markOrb(itOrbSkunk) || markOrb(itOrbGhost) || markOrb(itOrbShield); + return markOrb(itOrbBeauty) || markOrb(itOrbAether) || markOrb(itOrbShield); + } + +void wouldkill(const char *msg) { + if(which == moWarning) + addMessage(XLAT("This move appears dangerous -- are you sure?")); + else if(which == moFireball) + addMessage(XLAT("Cannot move into the current location of another player!")); + else if(which == moAirball) + addMessage(XLAT("Players cannot get that far away!")); + else + addMessage(XLAT(msg, which)); + } + +bool havePushConflict(cell *pushto, bool checkonly) { + if(pushto && multi::activePlayers() > 1) { + for(int i=0; imonst == moFriendlyIvy) + killMonster(c2, moPlayer, 0); } bool movepcto(int d, int subdir, bool checkonly) { + global_pushto = NULL; + bool switchplaces = false; + + if(d == MD_USE_ORB) + return targetRangedOrb(multi::whereto[multi::cpid].tgt, roMultiGo); + + bool errormsgs = multi::players == 1 || multi::cpid == multi::players-1; if(hardcore && !canmove) return false; - if(hardcore && checkonly) { printf("hardcore check\n"); return false; } + if(hardcore && checkonly) { return false; } if(checkonly && haveRangedTarget()) return true; - if(!checkonly && d >= 0) flipplayer = false; + if(!checkonly && d >= 0) { + flipplayer = false; + if(multi::players > 1) multi::flipped[multi::cpid] = false; + } if(!checkonly) { DEBB(DF_TURN, (debugfile,"movepc\n")); } + int origd = d; if(d >= 0) { cwspin(cwt, d); - mirror::spin(d); + if(multi::players == 1) mirror::spin(d); d = cwt.spin; } if(d != -1 && !checkonly) playermoved = true; @@ -4655,11 +6371,14 @@ bool movepcto(int d, int subdir, bool checkonly) { return false; } - if(items[itOrbDomination] > 1 && isMountable(c2->monst)) { + if(items[itOrbDomination] > ORBBASE && isMountable(c2->monst) && !monstersnear2()) { if(checkonly) return true; if(!isMountable(cwt.c->monst)) dragon::target = NULL; movecost(cwt.c, c2); + flipplayer = true; if(multi::players > 1) multi::flipped[multi::cpid] = true; + invismove = (turncount >= noiseuntil) && items[itOrbInvis] > 0; + killFriendlyIvy(); goto mountjump; } @@ -4667,6 +6386,7 @@ bool movepcto(int d, int subdir, bool checkonly) { if(checkonly) return true; activateFlash(); bfs(); + if(multi::players > 1) { multi::whereto[multi::cpid].d = MD_UNDECIDED; return false; } checkmove(); return true; } @@ -4677,6 +6397,7 @@ bool movepcto(int d, int subdir, bool checkonly) { keepLightning = true; bfs(); keepLightning = false; + if(multi::players > 1) { multi::whereto[multi::cpid].d = MD_UNDECIDED; return false; } checkmove(); return true; } @@ -4685,11 +6406,12 @@ bool movepcto(int d, int subdir, bool checkonly) { if(checkonly) return true; activateActiv(c2, true); bfs(); + if(multi::players > 1) { multi::whereto[multi::cpid].d = MD_UNDECIDED; return false; } checkmove(); return true; } - if(c2->wall == waThumperOn && !monstersnear(c2, NULL, moPlayer, NULL, cwt.c) && !c2->monst && !nonAdjacent(c2, cwt.c)) { + if(c2->wall == waThumperOn && !c2->monst && !nonAdjacentPlayer(c2, cwt.c)) { cellwalker push = cwt; cwstep(push); cwspin(push, 3 * -subdir); @@ -4709,6 +6431,11 @@ bool movepcto(int d, int subdir, bool checkonly) { addMessage(XLAT("No room to push %the1.", c2->wall)); return false; } + if(monstersnear(c2, NULL, moPlayer, NULL, cwt.c)) { + if(!checkonly && errormsgs) wouldkill("%The1 would kill you there!"); + return false; + } + global_pushto = push.c; if(checkonly) return true; addMessage(XLAT("You push %the1.", c2->wall)); lastmovetype = lmPush; lastmove = cwt.c; @@ -4742,7 +6469,15 @@ bool movepcto(int d, int subdir, bool checkonly) { } } */ - if(isWatery(c2) && !monstersnear(c2,NULL,moPlayer,NULL,cwt.c) && (markOrb(itOrb37) || !nonAdjacent(cwt.c,c2)) && !c2->monst && cwt.c->wall == waBoat) { + if(c2->item == itHolyGrail && roundTableRadius(c2) < newRoundTableRadius()) { + if(checkonly) return false; + addMessage(XLAT("That was not a challenge. Find a larger castle!")); + return false; + } + + if(isWatery(c2) && !nonAdjacentPlayer(cwt.c,c2) && !c2->monst && cwt.c->wall == waBoat) { + + if(havePushConflict(cwt.c, checkonly)) return false; if(againstWind(c2, cwt.c)) { if(!checkonly) @@ -4756,21 +6491,36 @@ bool movepcto(int d, int subdir, bool checkonly) { addMessage(XLAT("You cannot go against the current!")); return false; } + + if(monstersnear(c2, NULL, moPlayer, NULL, cwt.c)) { + if(!checkonly && errormsgs) wouldkill("%The1 would kill you there!"); + return false; + } + if(checkonly) return true; moveBoat(c2, cwt.c); boatmove = true; + goto boatjump; } - if(!monstersnear(c2,NULL,moPlayer,NULL,cwt.c) && !c2->monst && cwt.c->wall == waBoat && boatGoesThrough(c2) && markOrb(itOrbWater)) { + if(!c2->monst && cwt.c->wall == waBoat && boatGoesThrough(c2) && markOrb(itOrbWater)) { + + if(havePushConflict(cwt.c, checkonly)) return false; + if(monstersnear(c2,NULL,moPlayer,NULL,cwt.c)) { + if(!checkonly && errormsgs) wouldkill("%The1 would kill you there!"); + return false; + } + if(checkonly) return true; - placeWater(cwt.c, c2); - c2->wall = waBoat; + placeWater(c2, cwt.c); + moveBoat(c2, cwt.c); c2->mondir = neighborId(c2, cwt.c); if(cwt.c->item) moveItem(cwt.c, c2, false), boatmove = true; + goto boatjump; } escape: - if(c2->wall == waBigStatue && !monstersnear(c2,NULL,moPlayer,NULL,cwt.c) && !c2->monst && !nonAdjacent(c2, cwt.c)) { + if(c2->wall == waBigStatue && !c2->monst && !nonAdjacentPlayer(c2, cwt.c)) { if(!canPushStatueOn(cwt.c)) { if(checkonly) return false; if(isFire(cwt.c)) @@ -4779,35 +6529,60 @@ bool movepcto(int d, int subdir, bool checkonly) { addMessage(XLAT("There is not enough space!")); return false; } - - if(checkonly) return true; - addMessage(XLAT("You push %the1 behind you!", c2->wall)); + + if(havePushConflict(cwt.c, checkonly)) return false; + + eWall save_c2 = c2->wall; + eWall save_cw = cwt.c->wall; c2->wall = cwt.c->wall; - if(cellUnstable(cwt.c)) - cwt.c->wall = waChasm; - else + if(doesnotFall(cwt.c)) cwt.c->wall = waBigStatue; + + if(monstersnear(c2,NULL,moPlayer,NULL,cwt.c)) { + if(!checkonly && errormsgs) wouldkill("%The1 would kill you there!"); + c2->wall = save_c2; cwt.c->wall = save_cw; + return false; + } + + if(checkonly) { c2->wall = save_c2; cwt.c->wall = save_cw; return true; } + addMessage(XLAT("You push %the1 behind you!", waBigStatue)); + animateMovement(c2, cwt.c, LAYER_BOAT); + goto statuejump; } - if(c2->wall == waBigTree && !markOrb(itOrbGhost) && !monstersnear(cwt.c,NULL,moPlayer,NULL,cwt.c) && !nonAdjacent(cwt.c,c2)) { + if(c2->wall == waBigTree && !c2->monst && !markOrb(itOrbAether) && !nonAdjacentPlayer(cwt.c,c2)) { if(checkNeedMove(checkonly, true)) return false; + if(monstersnear(cwt.c,NULL,moPlayer,NULL,cwt.c)) { + if(!checkonly && errormsgs) wouldkill("%The1 would get you!"); + return false; + } if(checkonly) return true; - addMessage(XLAT("You start cutting down the tree.")); + drawParticles(c2, winf[c2->wall].color, 4); + addMessage(XLAT("You start chopping down the tree.")); + playSound(c2, "hit-axe" + pick123()); if(survivalist && isHaunted(c2->land)) survivalist = false; - mirror::go(0); + mirror::spingo(origd, 0); c2->wall = waSmallTree; lastmovetype = lmTree; lastmove = c2; + swordAttackStatic(); } - else if(c2->wall == waSmallTree && !markOrb(itOrbGhost) && !monstersnear(cwt.c,NULL,moPlayer,NULL,cwt.c) && !nonAdjacent(cwt.c,c2)) { + else if(c2->wall == waSmallTree && !c2->monst && !markOrb(itOrbAether) && !nonAdjacentPlayer(cwt.c,c2)) { if(checkNeedMove(checkonly, true)) return false; + if(monstersnear(cwt.c,NULL,moPlayer,NULL,cwt.c)) { + if(!checkonly && errormsgs) wouldkill("%The1 would get you!"); + return false; + } if(checkonly) return true; - addMessage(XLAT("You cut down the tree.")); + drawParticles(c2, winf[c2->wall].color, 8); + addMessage(XLAT("You chop down the tree.")); + playSound(c2, "hit-axe" + pick123()); if(survivalist && isHaunted(c2->land)) survivalist = false; - mirror::go(0); + mirror::spingo(origd, 0); lastmovetype = lmTree; lastmove = c2; c2->wall = waNone; + swordAttackStatic(); } else if(c2->monst == moKnight) { if(checkonly) return false; @@ -4834,7 +6609,7 @@ bool movepcto(int d, int subdir, bool checkonly) { addMessage(XLAT("You cannot attack %the1 directly!", c2->monst)); addMessage(XLAT("Stab them by walking around them.")); } - else if(c2->monst == moRoseBeauty) + else if(c2->monst == moRoseBeauty || isBull(c2->monst) || c2->monst == moButterfly) addMessage(XLAT("You cannot attack %the1!", c2->monst)); else if(c2->monst == moFlailer && !c2->stuntime) { addMessage(XLAT("You cannot attack %the1 directly!", c2->monst)); @@ -4848,6 +6623,8 @@ bool movepcto(int d, int subdir, bool checkonly) { addMessage(XLAT("You cannot defeat the Shadow!")); else if(c2->monst == moGreater || c2->monst == moGreaterM) addMessage(XLAT("You cannot defeat the Greater Demon yet!")); + else if(c2->monst == moDraugr) + addMessage(XLAT("Your mundane weapon cannot hurt %the1!", c2->monst)); else addMessage(XLAT("For some reason... cannot attack!")); return false; @@ -4868,6 +6645,15 @@ bool movepcto(int d, int subdir, bool checkonly) { cwspin(push, 1 * -subdir); cwstep(push); } + if(!passable(push.c, c2, P_BLOW) && gravityLevel(push.c) < gravityLevel(c2)) { + cwstep(push); cwspin(push, 1); cwstep(push); + if(gravityLevel(push.c) < gravityLevel(c2)) { + cwstep(push); cwspin(push, -2); cwstep(push); + } + if(gravityLevel(push.c) < gravityLevel(c2)) { + cwstep(push); cwspin(push, 1); cwstep(push); + } + } if(passable(push.c, c2, P_BLOW)) pushto = push.c; } @@ -4876,15 +6662,19 @@ bool movepcto(int d, int subdir, bool checkonly) { c2->monst == moForestTroll || c2->monst == moStormTroll || c2->monst == moVineSpirit) pushto = c2; - if(monstersnear(cwt.c, c2, moPlayer, pushto, cwt.c)) { - if(checkonly) return false; - addMessage(XLAT("You would be killed by %the1!", which)); - return false; - } - + global_pushto = pushto; + + if(havePushConflict(pushto, checkonly)) return false; + if(!(isWatery(cwt.c) && c2->monst == moWaterElemental) && checkNeedMove(checkonly, true)) return false; + if(monstersnear(cwt.c, c2, moPlayer, pushto, cwt.c)) { + if(errormsgs && !checkonly) + wouldkill("You would be killed by %the1!"); + return false; + } + if(checkonly) return true; @@ -4902,38 +6692,31 @@ bool movepcto(int d, int subdir, bool checkonly) { updateHi(itBabyTortoise, items[itBabyTortoise]); c2->item = itBabyTortoise; tortoise::babymap[c2] = tortoise::seekbits; + playSound(c2, playergender() ? "heal-princess" : "heal-prince"); addMessage(XLAT(playergender() == GEN_F ? "You are now a tortoise heroine!" : "You are now a tortoise hero!")); c2->stuntime = 2; achievement_collection(itBabyTortoise, 0, 0); } else if(isStunnable(c2->monst) && c2->hitpoints > 1) { - addMessage(XLAT("You stun %the1.", c2->monst)); - stunMonster(c2); + attackMonster(c2, AF_ORSTUN | AF_MSG, moPlayer); if(pushto && pushto != c2) pushMonster(pushto, c2); } else if(c2->monst == moVizier && c2->hitpoints > 1) { - addMessage(XLAT("You hit %the1.", c2->monst)); + fightmessage(c2->monst, moPlayer, true, 0); c2->hitpoints--; } - else if(isDragon(c2->monst)) { - addMessage(XLAT("You hit %the1.", c2->monst)); - if(c2->hitpoints) c2->hitpoints--; - c2->stuntime = 1; - cell *head = dragon::findhead(c2); - int hp = dragon::totalhp(head); - if(!hp) dragon::kill(head); + else if(isDragon(c2->monst) || isKraken(c2->monst)) { + attackMonster(c2, AF_ORSTUN | AF_MSG, moPlayer); } else { - messageKill(moNone, c2->monst); - eMonster m = c2->monst; - - killWithMessage(c2); + attackMonster(c2, AF_MSG, moPlayer); produceGhost(c2, m, moPlayer); } - mirror::go(0); + mirror::spingo(origd, 0); lastmovetype = lmAttack; lastmove = c2; + swordAttackStatic(); } else if(!passable(c2, cwt.c, P_USEBOAT | P_ISPLAYER | P_MIRROR | P_MONSTER)) { if(checkonly) return false; @@ -4949,6 +6732,8 @@ bool movepcto(int d, int subdir, bool checkonly) { addMessage(XLAT("It would be impolite to land on the table!")); else if(cwt.c->wall == waRed3 && snakelevel(c2) == 0) addMessage(XLAT("You would get hurt!", c2->wall)); + else if(cwt.c->wall == waTower && snakelevel(c2) == 0) + addMessage(XLAT("You would get hurt!", c2->wall)); else if(cellEdgeUnstable(cwt.c) && cellEdgeUnstable(c2)) addMessage(XLAT("Gravity does not allow this!")); else if(againstWind(c2, cwt.c)) @@ -4959,17 +6744,14 @@ bool movepcto(int d, int subdir, bool checkonly) { return false; } else { - if(c2->item == itOrbYendor && !boatmove && !yendor::check(c2, checkonly)) { + if(mineMarked(c2) && !minesafe() && !checkonly && warningprotection()) { + addMessage("Are you sure you want to step there?"); 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(c2->item == itOrbYendor && !boatmove && !checkonly && yendor::check(c2)) { + return false; } - if(!hasSafeOrb(c2) && monstersnear(c2, NULL, moPlayer, NULL, cwt.c)) { + if(monstersnear(c2, NULL, moPlayer, NULL, cwt.c)) { if(checkonly) return false; if(items[itOrbFlash]) { @@ -5002,11 +6784,14 @@ bool movepcto(int d, int subdir, bool checkonly) { } } - addMessage(XLAT("%The1 would kill you there!", which)); + if(!checkonly && errormsgs) + wouldkill("%The1 would kill you there!"); return false; } if(checkonly) return true; - flipplayer = true; + boatjump: + statuejump: + flipplayer = true; if(multi::players > 1) multi::flipped[multi::cpid] = true; if(c2->item && c2->land == laAlchemist) c2->wall = cwt.c->wall; if(c2->wall == waRoundTable) { addMessage(XLAT("You jump over the table!")); @@ -5022,6 +6807,29 @@ bool movepcto(int d, int subdir, bool checkonly) { if(makeflame(cwt.c, 10, false)) markOrb(itOrbFire); } + { + bool haveIvy = false; + forCellEx(c3, cwt.c) if(c3->monst == moFriendlyIvy) haveIvy = true; + + bool killIvy = haveIvy; + + if(items[itOrbNature]) { + if(c2->monst != moFriendlyIvy && strictlyAgainstGravity(c2, cwt.c, false, MF_IVY)) { + invismove = false; + } + else if(cwt.c->monst) invismove = false; + else if(haveIvy || !cellEdgeUnstable(cwt.c, MF_IVY)) { + cwt.c->monst = moFriendlyIvy; + cwt.c->mondir = neighborId(cwt.c, c2); + invismove = false; + markOrb(itOrbNature); + killIvy = false; + } + } + + if(killIvy) killFriendlyIvy(); + } + if(items[itOrbDigging]) { invismove = false; if(earthMove(cwt.c, d)) markOrb(itOrbDigging); @@ -5038,6 +6846,7 @@ bool movepcto(int d, int subdir, bool checkonly) { if(c2->monst == moGolem || c2->monst == moIllusion || isPrincess(c2->monst) || c2->monst == moMouse || c2->monst == moFriendlyGhost) { + bool pswitch = false; if(c2->monst == moMouse) princess::mouseSqueak(c2); else if(isPrincess(c2->monst)) { @@ -5045,15 +6854,19 @@ bool movepcto(int d, int subdir, bool checkonly) { princess::move(cwt.c, c2); } else - addMessage(XLAT("You switch places with %the1.", c2->monst)); + pswitch = true; cwt.c->hitpoints = c2->hitpoints; cwt.c->stuntime = c2->stuntime; placeGolem(cwt.c, c2, c2->monst); + if(cwt.c->monst != moNone && pswitch) + addMessage(XLAT("You switch places with %the1.", c2->monst)); c2->monst = moNone; + switchplaces = true; } else if(isMimic(c2->monst)) { addMessage(XLAT("You rejoin %the1.", c2->monst)); - killMonster(c2); + playSound(c2, "click"); + killMonster(c2, moNone); } mountjump: @@ -5062,14 +6875,20 @@ bool movepcto(int d, int subdir, bool checkonly) { stabbingAttack(cwt.c, c2, moPlayer); cell *c1 = cwt.c; cwstep(cwt); + if(switchplaces) + animateReplacement(c1, cwt.c, LAYER_SMALL); + else + animateMovement(c1, cwt.c, LAYER_SMALL); - mirror::go(1); + mirror::spingo(origd, 1); playerMoveEffects(c1, c2); + if(c2->monst == moFriendlyIvy) c2->monst = moNone; + countLocalTreasure(); landvisited[cwt.c->land] = true; - setdist(cwt.c, 0, NULL); + afterplayermoved(); } } else { @@ -5077,26 +6896,29 @@ bool movepcto(int d, int subdir, bool checkonly) { if(checkNeedMove(checkonly, false)) return false; if(monstersnear(cwt.c, NULL, moPlayer, NULL, cwt.c)) { - if(checkonly) return false; - addMessage(XLAT("%The1 would get you!", which)); + if(errormsgs && !checkonly) + wouldkill("%The1 would get you!"); return false; } if(checkonly) return true; + swordAttackStatic(); if(d == -2) dropGreenStone(cwt.c); - if(cellUnstable(cwt.c) && !markOrb(itOrbGhost)) - cwt.c->wall = waChasm; + if(cellUnstable(cwt.c) && !markOrb(itOrbAether)) + doesFallSound(cwt.c); } invisfish = false; if(items[itOrbFish]) { invisfish = true; for(int i=0; iland != laWhirlpool && !whirlpool::escaped) { whirlpool::escaped = true; @@ -5122,14 +6944,14 @@ bool movepcto(int d, int subdir, bool checkonly) { dst->cpdist > 1 && dst->monst && !(isWorm(dst) || dst->monst == moShadow); - } */ + } */ void moveItem1(cell *from, cell *to, bool activateYendor) { if(from->item == itOrbYendor) { bool xnew = true; for(int i=0; iitem == itKey) { for(int i=0; iitem == itBabyTortoise) { + tortoise::babymap[to] = tortoise::babymap[from]; + tortoise::babymap.erase(from); + } -void moveItem (cell *from, cell *to, bool activateYendor) { - moveItem1(from, to, activateYendor); - moveItem1(to, from, activateYendor); eItem i = to->item; to->item = from->item; from->item = i; } +void moveItem (cell *from, cell *to, bool activateYendor) { + static cell dummy; + dummy.item = itNone; + moveItem1(from, &dummy, activateYendor); + moveItem1(to, from, activateYendor); + moveItem1(&dummy, to, activateYendor); + } + +void fixWormBug(cell *c) { + printf("worm bug!\n"); + if(c->monst == moWormtail) c->monst = moWormwait; + if(c->monst == moTentacletail || c->monst == moTentacleGhost) c->monst = moTentacle; + if(c->monst == moHexSnakeTail) c->monst = moHexSnake; + } + cell *wormhead(cell *c) { // cell *cor = c; findhead: @@ -5156,23 +6994,91 @@ cell *wormhead(cell *c) { c->monst == moWormwait || c->monst == moTentacleEscaping || c->monst == moTentaclewait || c->monst == moDragonHead) return c; for(int i=0; itype; i++) - if(c->mov[i] && isWorm(c->mov[i]->monst) && c->mov[i]->mondir == c->spn[i]) { + if(c->mov[i] && isWorm(c->mov[i]->monst) && c->mov[i]->mondir == c->spn(i)) { c = c->mov[i]; goto findhead; } - printf("worm bug!\n"); + fixWormBug(c); return c; } +int wormpos(cell *c) { + // cell *cor = c; + int cnt = 0; + findhead: + if(c->monst == moTentacle || c->monst == moWorm || c->monst == moHexSnake || + c->monst == moWormwait || c->monst == moTentacleEscaping || c->monst == moTentaclewait || + c->monst == moDragonHead) return cnt; + for(int i=0; itype; i++) + if(c->mov[i] && isWorm(c->mov[i]->monst) && c->mov[i]->mondir == c->spn(i)) { + c = c->mov[i]; cnt++; goto findhead; + } + fixWormBug(c); + return cnt; + } + // currently works for worms only bool sameMonster(cell *c1, cell *c2) { if(c1 == c2) return true; if(isWorm(c1->monst) && isWorm(c2->monst)) return wormhead(c1) == wormhead(c2); + if(isKraken(c1->monst) && isKraken(c2->monst)) + return kraken::head(c1) == kraken::head(c2); return false; } -eMonster getMount() { +eMonster getMount(int i) { if(!items[itOrbDomination]) return moNone; - return cwt.c->monst; + return playerpos(i)->monst; + } + +eMonster haveMount() { + for(int i=0; iwall == waMineUnknown || c->wall == waMineMine; + } + +void performMarkCommand(cell *c) { + if(c->land == laCA && c->wall == waNone) + c->wall = waFloorA; + else if(c->land == laCA && c->wall == waFloorA) + c->wall = waNone; + if(c->land != laMinefield) return; + if(c->item) return; + if(!mightBeMine(c)) return; + bool adj = false; + forCellEx(c2, c) if(c2->wall == waMineOpen) adj = true; + if(adj) c->landparam ^= 1; + } + +bool mineMarked(cell *c) { + if(!mightBeMine(c)) return false; + if(c->item) return false; + if(c->land != laMinefield) return true; + return c->landparam & 1; + } + +bool mineMarkedSafe(cell *c) { + if(!mightBeMine(c)) return false; + if(c->item) return true; + if(c->land != laMinefield) return false; + return c->landparam & 2; + } + +bool minesafe() { + return items[itOrbAether]; + } + +bool warningprotection() { + if(hardcore) return false; + if(multi::activePlayers() > 1) return false; + if(items[itWarning]) return false; + items[itWarning] = 1; + return true; } diff --git a/geometry.cpp b/geometry.cpp index bb8d37e5..fd6c9c81 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -4,21 +4,33 @@ // Copyright (C) 2011-2012 Zeno Rogue, see 'hyper.cpp' for details -ld tessf, crossf, hexf, hcrossf; +ld tessf, crossf, hexf, hcrossf, hexhexdist; // tessf: distance from heptagon center to another heptagon center // hexf: distance from heptagon center to heptagon vertex // crossf: distance from heptagon center to adjacent hexagon center +// hexhexdist: distance between adjacent hexagon vertices -#define ALPHA (M_PI*2/7) +#define ALPHA (M_PI*2/S7) hyperpoint Crad[42]; transmatrix heptmove[7], hexmove[7]; transmatrix invheptmove[7], invhexmove[7]; +transmatrix spinmatrix[84]; + +const transmatrix& getspinmatrix(int id) { + while(id>=S84) id -= S84; + while(id<0) id += S84; + return spinmatrix[id]; + } + // the results are: // hexf = 0.378077 hcrossf = 0.620672 tessf = 1.090550 +// hexhexdist = 0.566256 + +// the distance between two hexagon centers void precalc() { @@ -31,15 +43,15 @@ void precalc() { 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; + ld v1 = intval(H, C0), v2 = intval(H, spin(2*M_PI/S7)*H); + if(sphere ? v1 < v2 : v1 > v2) fmin = f; else fmax = f; } tessf = fmin; - fmin = 0, fmax = 2; + fmin = 0, fmax = sphere ? M_PI / 2 : 2; for(int p=0; p<100; p++) { ld f = (fmin+fmax) / 2; - hyperpoint H = spin(M_PI/7) * xpush(f) * C0; + hyperpoint H = spin(M_PI/S7) * xpush(f) * C0; ld v1 = intval(H, C0), v2 = intval(H, xpush(tessf) * C0); if(v1 < v2) fmin = f; else fmax = f; } @@ -50,7 +62,7 @@ void precalc() { 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 H1 = spin(2*M_PI/S7) * H; hyperpoint H2 = xpush(tessf-f) * C0; ld v1 = intval(H, H1), v2 = intval(H, H2); if(v1 < v2) fmin = f; else fmax = f; @@ -59,84 +71,180 @@ void precalc() { // printf("hexf = %.6Lf cross = %.6Lf tessf = %.6Lf\n", hexf, crossf, tessf); - 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++) + for(int i=0; i %d\n", i,t, N); - tess[N] = T; N++; - if(N == NUMFACE) return; - nextt: ; + int tc_alpha=3, tc_depth=1, tc_camera=2; + + ld depth = 1; // world below the plane + ld camera = 1; // camera above the plane + ld wall_height = .3; + ld slev = .08; + ld lake_top = .25, lake_bottom = .9; + ld rock_wall_ratio = .9; + ld human_wall_ratio = .7; + ld human_height; + + ld highdetail = 8, middetail = 8; + + // Here we convert between the following parameters: + + // abslev: level below the plane + // lev: level above the world (abslev = depth-lev) + // projection: projection parameter + // factor: zoom factor + + ld abslev_to_projection(ld abslev) { + if(sphere || euclid) return camera+abslev; + return tanh(abslev) / tanh(camera); + } + + ld projection_to_abslev(ld proj) { + if(sphere || euclid) return proj-camera; + // tanh(abslev) / tanh(camera) = proj + return atanh(proj * tanh(camera)); + } + + ld lev_to_projection(ld lev) { + return abslev_to_projection(depth - lev); + } + + ld projection_to_factor(ld proj) { + return lev_to_projection(0) / proj; + } + + ld factor_to_projection(ld fac) { + return lev_to_projection(0) / fac; + } + + ld lev_to_factor(ld lev) { + return projection_to_factor(lev_to_projection(lev)); + } + ld factor_to_lev(ld fac) { + return depth - projection_to_abslev(factor_to_projection(fac)); + } + + // how should we scale at level lev + ld scale_at_lev(ld lev) { + if(sphere || euclid) return 1; + return cosh(depth - lev); + } + + ld INFDEEP, BOTTOM, HELLSPIKE, LAKE, WALL, + SLEV[4], FLATEYE, + LEG1, LEG, LEG3, GROIN, GROIN1, GHOST, + BODY, NECK1, NECK, NECK3, HEAD, + ABODY, AHEAD, BIRD; + + string invalid; + + void compute() { + // tanh(depth) / tanh(camera) == vid.alpha + invalid = ""; + + if(tc_alpha < tc_depth && tc_alpha < tc_camera) + vid.alpha = tanh(depth) / tanh(camera); + else if(tc_depth < tc_alpha && tc_depth < tc_camera) { + ld v = vid.alpha * tanh(camera); + if(v<-1 || v>1) invalid = "cannot adjust depth", depth = camera; + else depth = atanh(v); } - } - } - -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 + else { + ld v = tanh(depth) / vid.alpha; + if(v<-1 || v>1) invalid = "cannot adjust camera", camera = depth; + else camera = atanh(v); + } + + if(fabs(vid.alpha) < 1e-6) invalid = "does not work with perfect Klein"; - if(0) for(int t =0; ttype==6?0:1) +#ifdef ROGUEVIZ +#define DOSHMUP (shmup::on || rogueviz::on) +#else +#define DOSHMUP shmup::on +#endif + +// #define PANDORA #ifndef MOBILE #include -#ifdef AUDIO +#ifdef SDLAUDIO #include #endif -bool audio; -int audiovolume = 60; #ifndef MAC #undef main @@ -27,17 +44,14 @@ int audiovolume = 60; #include #endif +int colorbar; +#define COLORBAR "###" + #ifndef MOBILE - -// x resolutions - -#define NUMMODES 7 - SDL_Surface *s; TTF_Font *font[256]; SDL_Joystick* sticks[8]; int numsticks; - #endif ld shiftmul = 1; @@ -59,6 +73,8 @@ bool mousing = true; // is the mouse button pressed? bool mousepressed = false; +bool mousemoved = false; +bool actonrelease = false; emtype cmode = emNormal, lastmode = emNormal; // last mode in Help @@ -66,13 +82,14 @@ emtype cmode = emNormal, lastmode = emNormal; // last mode in Help int axestate; int ticks; +int frameid; videopar vid; int default_language; charstyle& getcs() { - if(shmup::on && shmup::players>1 && shmup::cpid >= 0 && shmup::cpid < shmup::players) - return shmup::scs[shmup::cpid]; + if(multi::players>1 && multi::cpid >= 0 && multi::cpid < multi::players) + return multi::scs[multi::cpid]; else return vid.cs; } @@ -92,9 +109,13 @@ int lang() { return default_language; } +eItem orbToTarget; +eMonster monsterToSummon; + int sightrange = 7; - -cell *mouseover, *mouseover2, *lmouseover, *centerover, *lcenterover; +bool overgenerate = false; // generate a bigger area with high sightrange + +cell *mouseover, *mouseover2, *lmouseover, *centerover; ld modist, modist2, centdist; string mouseovers; @@ -104,11 +125,12 @@ int mousex, mousey, joyx, joyy, panjoyx, panjoyy; bool autojoy = true; hyperpoint mouseh; -bool leftclick, rightclick, targetclick, hiliteclick, anyshiftclick; +bool leftclick, rightclick, targetclick, hiliteclick, anyshiftclick, wheelclick, + forcetarget, lshiftclick, lctrlclick; bool gtouched; bool revcontrol; -int getcstat; ld getcshift; +int getcstat; ld getcshift; bool inslider; int ZZ; @@ -118,8 +140,17 @@ int andmode = 0; int darken = 0; +struct fallanim { + int t_mon, t_floor; + eWall walltype; + eMonster m; + fallanim() { t_floor = 0; t_mon = 0; walltype = waNone; } + }; + +map fallanims; + bool doHighlight() { - return (hiliteclick && darken < 2) ? vid.monmode == 2 : vid.monmode == 3; + return (hiliteclick && darken < 2) ? !mmhigh : mmhigh; } #ifndef MOBILE @@ -142,7 +173,11 @@ int qpixel3(SDL_Surface *surf, int x, int y) { void loadfont(int siz) { if(!font[siz]) { +#ifdef WEB + font[siz] = TTF_OpenFont("sans-serif", siz); +#else font[siz] = TTF_OpenFont("DejaVuSans-Bold.ttf", siz); +#endif // Destination set by ./configure (in the GitHub repository) #ifdef FONTDESTDIR if (font[siz] == NULL) { @@ -156,18 +191,28 @@ void loadfont(int siz) { } } +int gl_width(int size, const char *s); + int textwidth(int siz, const string &str) { if(size(str) == 0) return 0; - + +#ifdef WEB + return gl_width(siz, str.c_str()); + +#else + 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 } #endif +int textwidth(int siz, const string &str); + #ifdef IOS int textwidth(int siz, const string &str) { @@ -204,48 +249,82 @@ int darkena(int c, int lev, int a) { #ifdef GL -int lalpha = 0xFF; +bool cameraangle_on; -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 setcameraangle(bool b) { + if(cameraangle_on != b) { + glMatrixMode(GL_PROJECTION); + cameraangle_on = b; + ld cam = vid.camera_angle * M_PI / 180; + + GLfloat cc = cos(cam); + GLfloat ss = sin(cam * (b?1:-1)); + + GLfloat yzspin[16] = { + 1, 0, 0, 0, + 0, cc, ss, 0, + 0, -ss, cc, 0, + 0, 0, 0, 1 + }; + + glMultMatrixf(yzspin); + } } void selectEyeGL(int ed) { DEBB(DF_GRAPH, (debugfile,"selectEyeGL\n")); - float ve = ed*vid.eye; - ve *= 2; // vid.xres; ve /= vid.radius; glMatrixMode(GL_PROJECTION); glLoadIdentity(); - - float lowdepth = 0.1; - float hidepth = 1e9; - // simulate glFrustum - GLfloat frustum[16] = { - GLfloat(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); + if(pmodel) { + vid.scrdist = 4 * vid.radius; - glMatrixMode(GL_MODELVIEW); - glLoadIdentity(); - - GLfloat sc = vid.radius / (vid.yres/2.); - GLfloat mat[16] = {sc,0,0,0, 0,-sc,0,0, 0,0,-1,0, 0,0, GLfloat(-vid.alpha),1}; - glMultMatrixf(mat); + // simulate glOrtho + GLfloat ortho[16] = { + GLfloat(2. / vid.xres), 0, 0, 0, + 0, GLfloat(-2. / vid.yres), 0, 0, + 0, 0, GLfloat(.4 / vid.scrdist), 0, + 0, 0, 0, 1}; - if(ve) glTranslatef(ve, 0, vid.eye); + vid.scrdist = -vid.scrdist; + glMultMatrixf(ortho); - vid.scrdist = -vid.alpha + vid.yres * sc / 2; + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + } + else { + float ve = ed*vid.eye; + ve *= 2; // vid.xres; ve /= vid.radius; + if(ve) + glTranslatef(-(ve * vid.radius) * (vid.alpha - (vid.radius*1./vid.xres) * vid.eye) / vid.xres, 0, 0); + + float lowdepth = .1; + float hidepth = 1e9; + + // simulate glFrustum + GLfloat frustum[16] = { + GLfloat(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}; + + glMultMatrixf(frustum); + + glMatrixMode(GL_MODELVIEW); + glLoadIdentity(); + + GLfloat sc = vid.radius / (vid.yres/2.); + GLfloat mat[16] = {sc,0,0,0, 0,-sc,0,0, 0,0,-1,0, 0,0, 0,1}; + glMultMatrixf(mat); + + if(ve) glTranslatef(ve, 0, vid.eye); + vid.scrdist = vid.yres * sc / 2; + } + + cameraangle_on = false; } void selectEyeMask(int ed) { @@ -266,16 +345,28 @@ void setGLProjection() { glMatrixMode(GL_PROJECTION); glLoadIdentity(); - glClearColor(0,0,0,1); + unsigned char *c = (unsigned char*) (&backcolor); + glClearColor(c[2] / 255.0, c[1] / 255.0, c[0]/255.0, 1); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glEnable(GL_BLEND); - glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - + glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + + if(pmodel == mdBall || pmodel == mdHyperboloid) { +#ifdef GL_ES + glClearDepthf(1.0f); +#else + glClearDepth(1.0f); +#endif + glEnable(GL_DEPTH_TEST); + glDepthFunc(GL_LEQUAL); + } + else + glDisable(GL_DEPTH_TEST); + selectEyeGL(0); } -bool GL_initialized = false; void buildpolys(); #ifndef MOBILE @@ -291,8 +382,6 @@ struct glfont_t { glfont_t *glfont[256]; -void init(const char * fname, unsigned int h); - inline int next_p2 (int a ) { int rval=1; @@ -304,7 +393,7 @@ inline int next_p2 (int a ) void glError(const char* GLcall, const char* file, const int line) { GLenum errCode = glGetError(); if(errCode!=GL_NO_ERROR) { - fprintf(stderr, "OPENGL ERROR #%i: in file %s on line %i :: %s\n",errCode,file, line, GLcall); + // fprintf(stderr, "OPENGL ERROR #%i: in file %s on line %i :: %s\n",errCode,file, line, GLcall); } } #define GLERR(call) glError(call, __FILE__, __LINE__) @@ -321,6 +410,7 @@ void init_glfont(int size) { //f.list_base = glGenLists(128); glGenTextures( 128+NUMEXTRA, f.textures ); +#ifndef WEB loadfont(size); if(!font[size]) return; @@ -328,14 +418,20 @@ void init_glfont(int size) { SDL_Color white; white.r = white.g = white.b = 255; +#endif - glListBase(0); +// glListBase(0); - for(unsigned char ch=1;ch<128+NUMEXTRA;ch++) { - // printf("init size=%d ch=%d\n", size, ch); + for(int ch=1;ch<128+NUMEXTRA;ch++) { +#ifdef WEB + if(ch<32) continue; + int otwidth, otheight, tpix[3000], tpixindex = 0; + loadCompressedChar(otwidth, otheight, tpix); + +#else + SDL_Surface *txt; - if(ch < 128) { str[0] = ch; txt = TTF_RenderText_Blended(font[size], str, white); @@ -344,33 +440,56 @@ void init_glfont(int size) { txt = TTF_RenderUTF8_Blended(font[size], natchars[ch-128], white); } if(txt == NULL) continue; -// printf("a\n"); - - int twidth = next_p2( txt->w ); - int theight = next_p2( txt->h ); - + + int otwidth = txt->w; + int otheight = txt->h; +#endif + + int twidth = next_p2( otwidth ); + int theight = next_p2( otheight ); + +#ifdef WEB + int expanded_data[twidth * theight]; +#else Uint16 expanded_data[twidth * theight]; +#endif for(int j=0; j =otwidth || j>=otheight) ? 0 : tpix[tpixindex++]; +#else expanded_data[(i+j*twidth)] = (i>=txt->w || j>=txt->h) ? 0 : ((qpixel(txt, i, j)>>24)&0xFF) * 0x101; +#endif } + +/* if(ch == '@') { + for(int j=0; j w; - f.heights[ch] = txt->h; + f.widths[ch] = otwidth; + f.heights[ch] = otheight; 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 ); +#ifdef WEB + GL_RGBA, GL_UNSIGNED_BYTE, +#else + GL_LUMINANCE_ALPHA, GL_UNSIGNED_BYTE, +#endif + expanded_data ); // printf("c\n"); - float x=(float)txt->w / (float)twidth; - float y=(float)txt->h / (float)theight; + float x=(float)otwidth / (float)twidth; + float y=(float)otheight / (float)theight; f.tx[ch] = x; f.ty[ch] = y; @@ -394,7 +513,9 @@ void init_glfont(int size) { glEndList(); */ //glPopMatrix(); +#ifndef WEB SDL_FreeSurface(txt); +#endif } //printf("init size=%d ok\n", size); @@ -413,6 +534,33 @@ int getnext(const char* s, int& i) { return s[i++]; } +GLfloat tver[24]; + +int gl_width(int size, const char *s) { + int gsiz = size; + if(size > vid.fsize || size > 72) gsiz = 72; + +#ifdef WEB + gsiz = 36; +#endif + + init_glfont(gsiz); + +#ifndef WEB + if(!font[gsiz]) return 0; +#endif + + glfont_t& f(*glfont[gsiz]); + + int x = 0; + for(int i=0; s[i];) { + int tabid = getnext(s,i); + x += f.widths[tabid] * size/gsiz; + } + + return x; + } + bool gl_print(int x, int y, int shift, int size, const char *s, int color, int align) { // printf("gl_print: %s\n", s.c_str()); @@ -425,10 +573,21 @@ bool gl_print(int x, int y, int shift, int size, const char *s, int color, int a glLoadIdentity(); gluOrtho2D(0, 0, vid.xscr, vid.yscr); */ - init_glfont(size); - if(!font[size]) return false; + int gsiz = size; + if(size > vid.fsize || size > 72) gsiz = 72; + // if(size >= 36) gsiz = 72; - glfont_t& f(*glfont[size]); +#ifdef WEB + gsiz = 36; +#endif + + init_glfont(gsiz); + +#ifndef WEB + if(!font[gsiz]) return false; +#endif + + glfont_t& f(*glfont[gsiz]); glDisable(GL_LIGHTING); @@ -436,15 +595,28 @@ bool gl_print(int x, int y, int shift, int size, const char *s, int color, int a glMatrixMode(GL_MODELVIEW); - glcolor(color); + glcolor2((color << 8) | 0xFF); int tsize = 0; - for(int i=0; s[i];) tsize += f.widths[getnext(s,i)]; + for(int i=0; s[i];) tsize += f.widths[getnext(s,i)] * size/gsiz; x -= tsize * align / 16; - y += f.heights[32]/2; - - bool clicked = (mousex >= x && mousey <= y && mousex <= x+tsize && mousey >= y-f.heights[32]); + y += f.heights[32] * size / (gsiz*2); + int ysiz = f.heights[32] * size / gsiz; + + bool clicked = (mousex >= x && mousey <= y && mousex <= x+tsize && mousey >= y-ysiz); + + /* extern bool markcorner; + if(clicked && markcorner) { + markcorner = false; + int w = tsize, h = -ysiz; + displaystr(x, y, 1, 10, "X", 0xFFFFFF, 8); + displaystr(x+w, y, 1, 10, "X", 0xFFFFFF, 8); + displaystr(x, y+h, 1, 10, "X", 0xFFFFFF, 8); + displaystr(x+w, y+h, 1, 10, "X", 0xFFFFFF, 8); + markcorner = true; + } */ + for(int i=0; s[i];) { // glListBase(f.list_base); @@ -453,8 +625,8 @@ bool gl_print(int x, int y, int shift, int size, const char *s, int color, int a 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]; + int wi = f.widths[tabid] * size/gsiz; + int hi = f.heights[tabid] * size/gsiz; GLERR("pre-print"); @@ -463,12 +635,25 @@ bool gl_print(int x, int y, int shift, int size, const char *s, int color, int a glTranslatef(x-ed*shift-vid.xcenter,y-vid.ycenter, vid.scrdist); selectEyeMask(ed); glBindTexture(GL_TEXTURE_2D, f.textures[tabid]); + +#if 1 + tver[1] = tver[10] = -hi; + tver[6] = tver[9] = wi; + tver[12+4] = tver[12+7] = fy; + tver[12+6] = tver[12+9] = fx; + activateVertexArray(tver, 8); + glEnableClientState(GL_TEXTURE_COORD_ARRAY); + glTexCoordPointer(3, GL_FLOAT, 0, &tver[12]); + glDrawArrays(GL_TRIANGLE_FAN, 0, 4); + glDisableClientState(GL_TEXTURE_COORD_ARRAY); +#else 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(); +#endif glPopMatrix(); } @@ -477,7 +662,7 @@ bool gl_print(int x, int y, int shift, int size, const char *s, int color, int a GLERR("print"); // int tabid = s[i]; - x += f.widths[tabid]; + x += wi; /* printf("point %d,%d\n", x, y); @@ -496,7 +681,6 @@ bool gl_print(int x, int y, int shift, int size, const char *s, int color, int a void resetGL() { DEBB(DF_INIT, (debugfile,"reset GL\n")); - GL_initialized = false; #ifndef MOBILE for(int i=0; i<128; i++) if(glfont[i]) { delete glfont[i]; @@ -510,14 +694,17 @@ void resetGL() { #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) { + if(size < 4 || size > 255) { // printf("size = %d\n", size); return false; } +#ifdef WEB + return gl_print(x, y, shift, size, str, color, align); +#endif + #ifdef GL if(vid.usingGL) return gl_print(x, y, shift, size, str, color, align); #endif @@ -626,9 +813,9 @@ bool displayfr(int x, int y, int b, int size, const string &s, int color, int al gdpush_utf8(s); int mx = mousex - x; int my = mousey - y; - int len = (int) s.size(); + int len = textwidth(size, s); return - mx >= -len*size*align/32 && mx <= +len*size*(16-align)/32 && + mx >= -len*align/32 && mx <= +len*(16-align)/32 && my >= -size*3/4 && my <= +size*3/4; } @@ -659,12 +846,13 @@ struct msginfo { int stamp; char flashout; char spamtype; + int quantity; string msg; }; vector msgs; -vector gamelog; +vector gamelog; void flashMessages() { for(int i=0; i& log) { + + if(size(log) != 0) { + msginfo& last = log[size(log)-1]; + if(last.msg == m.msg) { + int q = m.quantity + last.quantity; + last = m; last.quantity = q; + return; + } + } + if(size(log) < 200) + log.push_back(m); + else { + for(int i=0; i 1) s += " (x" + its(msgs[j].quantity) + ")"; + displayfr(x, y, 1, vid.fsize, s, 0x10101 * (255 - age/vid.flashtime), 8); msgs[i++] = msgs[j]; } } msgs.resize(i); } -int pmodel = 0; +eModel pmodel = mdDisk; ld ghx, ghy, ghgx, ghgy; hyperpoint ghpm = C0; -void xybound(int& x, int &y) { - if(x<-vid.xres) x=-vid.xres; if(x>2*vid.xres) x=2*vid.xres; - if(y<-vid.yres) y=-vid.yres; if(y>2*vid.yres) y=2*vid.yres; - } - -void ghcheck(int& x, int &y, const hyperpoint &H) { - xybound(x,y); - if(hypot(x-ghx, y-ghy) < hypot(ghgx-ghx, ghgy-ghy)) { - ghpm = H; ghgx = x; ghgy = y; +void ghcheck(hyperpoint &ret, const hyperpoint &H) { + if(hypot(ret[0]-ghx, ret[1]-ghy) < hypot(ghgx-ghx, ghgy-ghy)) { + ghpm = H; ghgx = ret[0]; ghgy = ret[1]; } } @@ -736,19 +938,24 @@ hyperpoint gethyper(ld x, ld y) { if(euclid) return hpxy(hx * (EUCSCALE + vid.alphax), hy * (EUCSCALE + vid.alphax)); - + ld hr = hx*hx+hy*hy; - if(hr > .9999) return Hypc; + if(hr > .9999 && !sphere) 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; + + ld A, B, C; + + ld curv = sphere ? 1 : -1; + + A = 1+curv*hr; + B = 2*hr*vid.alphax*-curv; + C = 1 - curv*hr*vid.alphax*vid.alphax; // Az^2 - Bz = C B /= A; C /= A; @@ -757,7 +964,11 @@ hyperpoint gethyper(ld x, ld y) { // 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); + ld rootsign = 1; + if(sphere && vid.alphax > 1) rootsign = -1; + + ld hz = B / 2 + rootsign * sqrt(C + B*B/4); + hyperpoint H; H[0] = hx * (hz+vid.alphax); H[1] = hy * (hz+vid.alphax); @@ -766,35 +977,99 @@ hyperpoint gethyper(ld x, ld y) { return H; } -void getcoord(const hyperpoint& H, int& x, int& y, int &shift) { +void ballmodel(hyperpoint& ret, double alpha, double d, double zl) { + hyperpoint H = ypush(geom3::camera) * xpush(d) * ypush(zl) * C0; + ld tzh = vid.ballproj + H[2]; + ld ax = H[0] / tzh; + ld ay = H[1] / tzh; + ld ball = vid.ballangle * M_PI / 180; + + ld ca = cos(alpha), sa = sin(alpha); + ld cb = cos(ball), sb = sin(ball); + + ret[0] = ax * ca; + ret[1] = ay * cb + ax * sa * sb; + ret[2] = - ax * sa * cb - ay * sb; + } + +void applymodel(hyperpoint H, hyperpoint& ret) { - 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; - if(pmodel == 0) { - 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 - xybound(x,y); + if(pmodel == mdUnchanged) { + for(int i=0; i<3; i++) ret[i] = H[i] / vid.radius; + return; + } + + if(pmodel == mdBall) { + ld zlev = zlevel(H); + using namespace hyperpoint_vec; + H = H / zlev; + + ld zl = geom3::depth-geom3::factor_to_lev(zlev); + double alpha = atan2(H[1], H[0]); + double d = hdist0(H); + + ballmodel(ret, alpha, d, zl); + + return; + } + + if(pmodel == mdHyperboloid) { + + ld ball = vid.ballangle * M_PI / 180; + ld cb = cos(ball), sb = sin(ball); + + ret[0] = H[0] / 3; + ret[1] = (1 - H[2]) / 3 * cb + H[1] / 3 * sb; + ret[2] = H[1] / 3 * cb - (1 - H[2]) / 3 * sb; + return; + } + + if(pmodel == mdDisk) { + ret[0] = H[0] / tz; + ret[1] = H[1] / tz; + ret[2] = (1 - vid.beta / tz); return; } - if(pmodel == 3 || pmodel == 4) { + ld zlev = 1; + + if(wmspatial || mmspatial) { + zlev = zlevel(H); + using namespace hyperpoint_vec; + H = H / zlev; + } + + if(pmodel == mdEquidistant || pmodel == mdEquiarea) { + ld rad = sqrt(H[0] * H[0] + H[1] * H[1]); + ld d = hdist0(H); + if(pmodel == 6 && sphere) + d = sqrt(2*(1 - cos(d))) * 1.25; // /1.5 to make it fit on the screen better + else if(pmodel == 6) + d = sqrt(2*(cosh(d) - 1)) / 1.5; + ret[0] = d * H[0] / rad / 4; + ret[1] = d * H[1] / rad / 4; + ret[2] = 0; + if(zlev != 1 && vid.goteyes) + ret[2] = geom3::factor_to_lev(zlev); + ghcheck(ret,H); + return; + } + + tz = H[2]+vid.alphax; + + if(pmodel == mdPolygonal || pmodel == mdPolynomial) { pair p = polygonal::compute(H[0]/tz, H[1]/tz); - x = vid.xcenter + int(vid.radius * p.first); - y = vid.ycenter + int(vid.radius * p.second); - shift = 0; ghcheck(x,y,H); + ret[0] = p.first; + ret[1] = p.second; + ret[2] = 0; + ghcheck(ret,H); return; } // Poincare to half-plane - tz = H[2]+vid.alphax; ld x0, y0; x0 = H[0] / tz; @@ -805,10 +1080,14 @@ void getcoord(const hyperpoint& H, int& x, int& y, int &shift) { x0 /= rad; y0 -= .5; - if(pmodel == 1) { - x = vid.xcenter + int(x0 * vid.radius); - y = vid.ycenter*2 - int(y0 * vid.radius); - shift = 0; ghcheck(x,y,H); + if(pmodel == mdHalfplane) { + ret[0] = x0; + if(wmspatial || mmspatial) y0 *= zlev; + ret[1] = 1 - y0; + ret[2] = 0; + if(zlev != 1 && vid.goteyes) + ret[2] = y0 * geom3::factor_to_lev(zlev); + ghcheck(ret,H); return; } @@ -819,160 +1098,37 @@ void getcoord(const hyperpoint& H, int& x, int& y, int &shift) { double tau = (log((x0+1)*(x0+1) + y0*y0) - log((x0-1)*(x0-1) + y0*y0)) / 2; double u=(1-x0*x0-y0*y0); u = (1 - x0*x0 - y0*y0 + sqrt(u*u+4*y0*y0)); - double sigma = 2 * atan(2*y0 / u) - M_PI/2; + double yv = 2*y0 / u; + double sigma = 2 * atan(yv * zlev) - M_PI/2; x0 = tau; y0 = sigma; - x = vid.xcenter + int(x0 * vid.radius/M_PI*2); - y = vid.ycenter - int(y0 * vid.radius/M_PI*2); - shift = 0; ghcheck(x,y,H); + /* if(zlev != 1) { + double alp = (y0 * y0) / (1-y0*y0); + double gx = alp + sqrt(alp*alp-1); + double gy = y0 * (gx+1); + double yr = zlev * gy / (zlev * gx + 1); + printf("zlev = %10.5lf y0 = %20.10lf yr = %20.10lf\n", double(zlev), (double)y0, yr); + y0 = yr; + } */ + + ret[0] = x0/M_PI*2; + ret[1] = y0/M_PI*2; + ret[2] = 0; + + if(zlev != 1 && vid.goteyes) + ret[2] = geom3::factor_to_lev(zlev) / (1 + yv * yv); + + ghcheck(ret,H); } int dlit; -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(pmodel) { - glcoords[qglcoords][0] = x1 - vid.xcenter; - glcoords[qglcoords][1] = y1 - vid.ycenter; - glcoords[qglcoords][2] = vid.scrdist; - } - else 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 && col == -1) { - if(polyi >= POLYMAX) return; - polyx[polyi] = x1-s1; - polyxr[polyi] = x1+s1; - polyy[polyi] = y1; - polyi++; - return; - } - if(dst <= 20 && !vid.usingGL) { - (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 fixcolor(int& col) { - 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 - } - } - -void drawline(const hyperpoint& H1, const hyperpoint& H2, int col) { - if(vid.usingGL && !pmodel) { - qglcoords = 0; - } - - 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(pmodel) { - glcoords[qglcoords][0] = x2 - vid.xcenter; - glcoords[qglcoords][1] = y2 - vid.ycenter; - glcoords[qglcoords][2] = vid.scrdist; - } - else 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++; - - if(pmodel) return; - - 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; i 15 * eurad; + else if(sphere) + return h[2] < .1 && h[2] > -.1 && h[1] > -.1 && h[1] < .1 && h[0] > -.1 && h[0] < .1; else return h[2] < .5; } void drawShield(const transmatrix& V, eItem it) { float ds = ticks / 300.; - int col = darkened(iinf[it].color); - if(it == itOrbShield && items[itOrbPreserve] && !orbused[it]) + int col = iinf[it].color; + if(it == itOrbShield && items[itOrbTime] && !orbused[it]) col = (col & 0xFEFEFE) / 2; + if(sphere && cwt.c->land == laHalloween && !wmblack && !wmascii) + col = 0; double d = it == itOrbShield ? hexf : hexf - .1; - for(int a=0; a<84*5; a++) - queueline(V*ddi(a, d + sin(ds + M_PI*2*a/20)*.1)*C0, V*ddi((a+1), d + sin(ds + M_PI*2*(a+1)/20)*.1)*C0, col); - } - -void drawShell(const transmatrix& V) { - float ds = ticks / 300.; - int col = darkened(iinf[itOrbShell].color); - for(int a=0; a<84*5; a++) - queueline(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); + int mt = sphere ? 7 : 5; + for(int a=0; a<=S84*mt; a++) + curvepoint(V*ddi0(a, d + sin(ds + M_PI*2*a/4/mt)*.1)); + queuecurve(darkena(col, 0, 0xFF), 0x8080808, PPR_LINE); } 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++) - queueline(V*ddi(ds+b+a, hexf*a/84)*C0, V*ddi(ds+b+(a+1), hexf*(a+1)/84)*C0, col); + int col = darkena(iinf[itOrbSpeed].color, 0, 0xFF); + for(int b=0; b42) z = 84-z; + for(int a=0; a<=S84; a++) { + double d = (1 + cos(a * M_PI/S42)) / 2; + int z = a; if(z>S42) z = S84-z; if(z <= 10) d += (10-z) * (10-z) * (10-z) / 3000.; ld rad = hexf * (2.5 + .5 * sin(ds+u*.3)) * d; - queueline(V*ddi(hdir+a-1, rad)*C0, V*ddi(hdir+a+1, rad)*C0, col); + curvepoint(V*ddi0(S42+hdir+a-1, rad)); } + queuecurve(col, 0x8080808, PPR_LINE); } } void drawWinter(const transmatrix& V, int hdir) { float ds = ticks / 300.; - int col = darkened(iinf[itOrbWinter].color); + int col = darkena(iinf[itOrbWinter].color, 0, 0xFF); for(int u=0; u<20; u++) { ld rad = 6 * sin(ds+u * 2 * M_PI / 20); - queueline(V*ddi(hdir+rad, hexf*.5)*C0, V*ddi(hdir+rad, hexf*3)*C0, col); + queueline(V*ddi0(S42+hdir+rad, hexf*.5), V*ddi0(S42+hdir+rad, hexf*3), col, 2); } } void drawLightning(const transmatrix& V) { - int col = iinf[itOrbLightning].color; + int col = darkena(iinf[itOrbLightning].color, 0, 0xFF); for(int u=0; u<20; u++) { ld leng = 0.5 / (0.1 + (rand() % 100) / 100.0); - ld rad = rand() % 84; - queueline(V*ddi(rad, hexf*0.3)*C0, V*ddi(rad, hexf*leng)*C0, col); + ld rad = rand() % S84; + queueline(V*ddi0(rad, hexf*0.3), V*ddi0(rad, hexf*leng), col, 2); } } int displaydir(cell *c, int d) { if(euclid) - return - d * 84 / c->type; + return - d * S84 / c->type; else - return 42 - d * 84 / c->type; + return S42 - d * S84 / c->type; } +transmatrix ddspin(cell *c, int d, int bonus = 0) { + int hdir = displaydir(c, d) + bonus; + return getspinmatrix(hdir); + } + +#ifdef WEB +Uint8 *SDL_GetKeyState(void *v) { static Uint8 tab[1024]; return tab; } +#endif + #include "shmup.cpp" -#include "rug.cpp" #include "conformal.cpp" +#include "rug.cpp" void drawPlayerEffects(const transmatrix& V, cell *c, bool onplayer) { if(!onplayer && !items[itOrbEmpathy]) return; - if(items[itOrbShield] > (shmup::on ? 0 : 1)) drawShield(V, itOrbShield); - if(items[itOrbShell] > (shmup::on ? 0 : 1)) drawShield(V, itOrbShell); + if(items[itOrbShield] > (shmup::on ? 0 : ORBBASE)) drawShield(V, itOrbShield); + if(items[itOrbShell] > (shmup::on ? 0 : ORBBASE)) drawShield(V, itOrbShell); if(items[itOrbSpeed]) drawSpeed(V); int ct = c->type; + if(onplayer && (items[itOrbSword] || items[itOrbSword2])) { + using namespace sword; + + double esh = euclid ? M_PI - M_PI*3/S84 + 2.5 * M_PI/S42: 0; + + if(shmup::on) { + if(items[itOrbSword]) + queuepoly(V*spin(esh+shmup::pc[multi::cpid]->swordangle), shMagicSword, darkena(iinf[itOrbSword].color, 0, 0xC0 + 0x30 * sin(ticks / 200.0))); + + if(items[itOrbSword2]) + queuepoly(V*spin(esh+shmup::pc[multi::cpid]->swordangle+M_PI), shMagicSword, darkena(iinf[itOrbSword2].color, 0, 0xC0 + 0x30 * sin(ticks / 200.0))); + } + + else { + int& ang = angle[multi::cpid]; + ang %= S42; + + transmatrix Vnow = gmatrix[c] * rgpushxto0(inverse(gmatrix[c]) * tC0(V)); + + if(!euclid) for(int a=0; a 1) drawLightning(V); + if(onplayer && items[itOrbLightning]) drawLightning(V); if(safetyat > 0) { int tim = ticks - safetyat; @@ -1164,9 +1381,9 @@ void drawPlayerEffects(const transmatrix& V, cell *c, bool onplayer) { 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++) - queueline(V*ddi(a, rad)*C0, V*ddi(a+1, rad)*C0, col); + int col = darkena(iinf[itOrbSafety].color, 0, 0xFF); + for(int a=0; ad[i]); hpcshape& sh(ds.sh); + if(sh.s != sh.e) - queuepoly(V, sh, ds.color ? darkena(ds.color, 0, 0xFF) : color); + queuepoly(V, sh, ds.color ? ds.color : color); } - + +#ifndef NOEDIT if(cmode == emDraw && mapeditor::editingShape(group, id)) { usershapelayer &ds(usershapes[group][id]->d[mapeditor::dslayer]); @@ -1216,20 +1435,11 @@ bool drawUserShape(transmatrix V, int group, int id, int color) { hyperpoint P2 = V * spin(2*M_PI*a/ds.rots) * (b?Mirror*mh:mh); - int xc, yc, sc; - getcoord(P2, xc, yc, sc); - queuechr(xc, yc, sc, 10, 'x', 0xFF00FF); - - /* if(crad > 0 && c->cpdist <= 3) { - lalpha = 0x80; - transmatrix movtocc = V2 * inverse(cwtV) * rgpushxto0(ccenter); - for(int d=0; d<84; d++) - queueline(movtocc * ddi(d+1, crad) * C0, movtocc * ddi(d, crad) * C0, 0xC00000); - lalpha = 0xFF; - } */ + queuechr(P2, 10, 'x', 0xFF00FF); } } - +#endif + return true; #endif } @@ -1241,6 +1451,7 @@ string csnameid(int id) { if(id == 3) return XLAT("Princess"); if(id == 4 || id == 5) return XLAT("cat"); if(id == 6 || id == 7) return XLAT("dog"); + if(id == 8 || id == 9) return XLATN("Familiar"); return XLAT("none"); } @@ -1297,31 +1508,176 @@ namespace tortoise { else if(d < 0) mcol = 0; int dd = 0xFF * (atan(fabs(d)/2) / (M_PI/2)); - /* poly_outline = 0; + /* poly_outline = OUTLINE_TRANS; queuepoly(V, shHeptaMarker, darkena(mcol, 0, dd)); poly_outline = OUTLINE_NONE; */ return gradient(0x487830, mcol, 0, dd, 0xFF); } }; -bool drawMonsterType(eMonster m, cell *where, const transmatrix& V, int col) { +double footfun(double d) { + d -= floor(d); + return + d < .25 ? d : + d < .75 ? .5-d : + d-1; + } - char xch = minf[m].glyph; +bool ivoryz; -#ifndef MOBILE +void animallegs(const transmatrix& V, eMonster mo, int col, double footphase) { + footphase /= SCALE; + + bool dog = mo == moRunDog; + bool bug = mo == moBug0 || mo == moMetalBeast; + + if(bug) footphase *= 2.5; + + double rightfoot = footfun(footphase / .4 / 2) / 4 * 2; + double leftfoot = footfun(footphase / .4 / 2 - (bug ? .5 : dog ? .1 : .25)) / 4 * 2; + + if(bug) rightfoot /= 2.5, leftfoot /= 2.5; + + if(!footphase) rightfoot = leftfoot = 0; + + transmatrix VAML = mmscale(V, 1.04); + + hpcshape* sh[6][4] = { + {&shDogFrontPaw, &shDogRearPaw, &shDogFrontLeg, &shDogRearLeg}, + {&shWolfFrontPaw, &shWolfRearPaw, &shWolfFrontLeg, &shWolfRearLeg}, + {&shReptileFrontFoot, &shReptileRearFoot, &shReptileFrontLeg, &shReptileRearLeg}, + {&shBugLeg, NULL, NULL, NULL}, + {&shTrylobiteFrontClaw, &shTrylobiteRearClaw, &shTrylobiteFrontLeg, &shTrylobiteRearLeg}, + {&shBullFrontHoof, &shBullRearHoof, &shBullFrontHoof, &shBullRearHoof}, + }; + + hpcshape **x = sh[mo == moRagingBull ? 5 : mo == moBug0 ? 3 : mo == moMetalBeast ? 4 : mo == moRunDog ? 0 : mo == moReptile ? 2 : 1]; + + if(x[0]) queuepolyat(V * xpush(rightfoot), *x[0], col, PPR_MONSTER_FOOT); + if(x[0]) queuepolyat(V * Mirror * xpush(leftfoot), *x[0], col, PPR_MONSTER_FOOT); + if(x[1]) queuepolyat(V * xpush(-rightfoot), *x[1], col, PPR_MONSTER_FOOT); + if(x[1]) queuepolyat(V * Mirror * xpush(-leftfoot), *x[1], col, PPR_MONSTER_FOOT); + + + if(x[2]) queuepolyat(VAML * xpush(rightfoot/2), *x[2], col, PPR_MONSTER_FOOT); + if(x[2]) queuepolyat(VAML * Mirror * xpush(leftfoot/2), *x[2], col, PPR_MONSTER_FOOT); + if(x[3]) queuepolyat(VAML * xpush(-rightfoot/2), *x[3], col, PPR_MONSTER_FOOT); + if(x[3]) queuepolyat(VAML * Mirror * xpush(-leftfoot/2), *x[3], col, PPR_MONSTER_FOOT); + } + +void ShadowV(const transmatrix& V, const hpcshape& bp, int prio) { + if(mmspatial) { + if(pmodel == mdHyperboloid || pmodel == mdBall) + return; // shadows break the depth testing + int p = poly_outline; poly_outline = OUTLINE_TRANS; + queuepolyat(V, bp, SHADOW_MON, prio); + poly_outline = p; + } + } + + +void otherbodyparts(const transmatrix& V, int col, eMonster who, double footphase) { + +#define VFOOT V +#define VLEG mmscale(V, geom3::LEG) +#define VGROIN mmscale(V, geom3::GROIN) +#define VBODY mmscale(V, geom3::BODY) +#define VNECK mmscale(V, geom3::NECK) +#define VHEAD mmscale(V, geom3::HEAD) + +#define VALEGS V +#define VABODY mmscale(V, geom3::ABODY) +#define VAHEAD mmscale(V, geom3::AHEAD) + +#define VFISH V +#define VBIRD mmscale(V, geom3::BIRD + .05 * sin((int) (size_t(where)) + ticks / 1000.)) +#define VGHOST mmscale(V, geom3::GHOST) + +#define VSLIMEEYE mscale(V, geom3::FLATEYE) + + // if(!mmspatial && !footphase && who != moSkeleton) return; + + footphase /= SCALE; + double rightfoot = footfun(footphase / .4 / 2.5) / 4 * 2.5; + + // todo + + if(detaillevel >= 2) { + transmatrix VL = mmscale(V, geom3::LEG1); + queuepoly(VL * xpush(rightfoot*3/4), shHumanLeg, col); + queuepoly(VL * Mirror * xpush(-rightfoot*3/4), shHumanLeg, col); + } + + if(true) { + transmatrix VL = mmscale(V, geom3::LEG); + queuepoly(VL * xpush(rightfoot/2), shHumanLeg, col); + queuepoly(VL * Mirror * xpush(-rightfoot/2), shHumanLeg, col); + } + + if(detaillevel >= 2) { + transmatrix VL = mmscale(V, geom3::LEG3); + queuepoly(VL * xpush(rightfoot/4), shHumanLeg, col); + queuepoly(VL * Mirror * xpush(-rightfoot/4), shHumanLeg, col); + } + + if(who == moWaterElemental) { + double fishtail = footfun(footphase / .4) / 4 * 1.5; + queuepoly(VFOOT * xpush(fishtail), shFishTail, watercolor(100)); + } + else if(who == moSkeleton) { + queuepoly(VFOOT * xpush(rightfoot), shSkeletalFoot, col); + queuepoly(VFOOT * Mirror * xpush(-rightfoot), shSkeletalFoot, col); + return; + } + else if(isTroll(who) || who == moMonkey || who == moYeti || who == moRatling || who == moRatlingAvenger || who == moGoblin) { + queuepoly(VFOOT * xpush(rightfoot), shYetiFoot, col); + queuepoly(VFOOT * Mirror * xpush(-rightfoot), shYetiFoot, col); + } + else { + queuepoly(VFOOT * xpush(rightfoot), shHumanFoot, col); + queuepoly(VFOOT * Mirror * xpush(-rightfoot), shHumanFoot, col); + } + + if(!mmspatial) return; + + if(detaillevel >= 2 && who != moZombie) + queuepoly(mmscale(V, geom3::NECK1), shHumanNeck, col); + if(detaillevel >= 1) { + queuepoly(VGROIN, shHumanGroin, col); + if(who != moZombie) queuepoly(VNECK, shHumanNeck, col); + } + if(detaillevel >= 2) { + queuepoly(mmscale(V, geom3::GROIN1), shHumanGroin, col); + if(who != moZombie) queuepoly(mmscale(V, geom3::NECK3), shHumanNeck, col); + } + } + +bool drawstar(cell *c) { + for(int t=0; ttype; t++) + if(c->mov[t] && c->mov[t]->wall != waSulphur && c->mov[t]->wall != waSulphurC && + c->mov[t]->wall != waBarrier) + return false; + return true; + } + +bool drawMonsterType(eMonster m, cell *where, const transmatrix& V, int col, double footphase) { + + char xch = minf[m].glyph; + +#ifndef NOEDIT if(where == mapeditor::drawcell) mapeditor::drawtrans = V; #endif - if(m == moTortoise && where->stuntime >= 3) + if(m == moTortoise && where && where->stuntime >= 3) drawStunStars(V, where->stuntime-2); - else if (m == moTortoise || m == moPlayer || !where->stuntime) ; - else if(!(isMetalBeast(m) && where->stuntime == 1)) + else if (m == moTortoise || m == moPlayer || (where && !where->stuntime)) ; + else if(where && !(isMetalBeast(m) && where->stuntime == 1)) drawStunStars(V, where->stuntime); if(m == moTortoise) { - int bits = tortoise::getb(where); - tortoise::draw(V, bits, 0, where->stuntime); + int bits = where ? tortoise::getb(where) : 0; + tortoise::draw(V, bits, 0, where ? where->stuntime : 0); if(tortoise::seek() && !tortoise::diff(bits)) queuepoly(V, shRing, darkena(0xFFFFFF, 0, 0x80 + 0x70 * sin(ticks / 200.0))); } @@ -1333,70 +1689,113 @@ bool drawMonsterType(eMonster m, cell *where, const transmatrix& V, int col) { bool havus = drawUserShape(V, 0, cs.charid, cs.skincolor); if(mapeditor::drawplayer && !havus) { - - if(cs.charid >= 6) { - queuepoly(V, shWolf, fc(0, cs.skincolor, 0)); + + if(cs.charid >= 8) { + queuepoly(VABODY, shWolfBody, fc(0, cs.skincolor, 0)); + if(!mmspatial && !footphase) + queuepoly(VALEGS, shWolfLegs, fc(150, cs.dresscolor, 4)); + else { + ShadowV(V, shWolfBody); + animallegs(VALEGS, moWolf, fc(500, cs.dresscolor, 4), footphase); + } + queuepoly(VAHEAD, shFamiliarHead, fc(500, cs.haircolor, 2)); if(!shmup::on || shmup::curtime >= shmup::getPlayer()->nextshot) { - queuepoly(V, shWolf1, fc(314, cs.swordcolor, 3)); - queuepoly(V, shWolf2, fc(314, cs.swordcolor, 3)); - queuepoly(V, shWolf3, fc(314, cs.swordcolor, 3)); + int col = items[itOrbDiscord] ? watercolor(0) : fc(314, cs.swordcolor, 3); + queuepoly(VAHEAD, shFamiliarEye, col); + queuepoly(VAHEAD * Mirror, shFamiliarEye, col); + } + } + else if(cs.charid >= 6) { + if(!mmspatial && !footphase) + queuepoly(VABODY, shDogBody, fc(0, cs.skincolor, 0)); + else { + ShadowV(V, shDogTorso); + queuepoly(VABODY, shDogTorso, fc(0, cs.skincolor, 0)); + animallegs(VALEGS, moRunDog, fc(500, cs.dresscolor, 4), footphase); + } + queuepoly(VAHEAD, shDogHead, fc(150, cs.haircolor, 2)); + + if(!shmup::on || shmup::curtime >= shmup::getPlayer()->nextshot) { + int col = items[itOrbDiscord] ? watercolor(0) : fc(314, cs.swordcolor, 3); + queuepoly(VAHEAD, shWolf1, col); + queuepoly(VAHEAD, shWolf2, col); + queuepoly(VAHEAD, shWolf3, col); } } else if(cs.charid >= 4) { - queuepoly(V, shCatBody, fc(0, cs.skincolor, 0)); - queuepoly(V, shCatHead, fc(150, cs.haircolor, 2)); - queuepoly(V, shCatLegs, fc(500, cs.dresscolor, 4)); + queuepoly(VABODY, shCatBody, fc(0, cs.skincolor, 0)); + queuepoly(VAHEAD, shCatHead, fc(150, cs.haircolor, 2)); + if(!mmspatial && !footphase) + queuepoly(VALEGS, shCatLegs, fc(500, cs.dresscolor, 4)); + else { + ShadowV(V, shCatBody); + animallegs(VALEGS, moRunDog, fc(500, cs.dresscolor, 4), footphase); + } if(!shmup::on || shmup::curtime >= shmup::getPlayer()->nextshot) { - queuepoly(V * xpush(.04), shWolf1, fc(314, cs.swordcolor, 3)); - queuepoly(V * xpush(.04), shWolf2, fc(314, cs.swordcolor, 3)); + int col = items[itOrbDiscord] ? watercolor(0) : fc(314, cs.swordcolor, 3); + queuepoly(VAHEAD * xpush(.04), shWolf1, col); + queuepoly(VAHEAD * xpush(.04), shWolf2, col); } } else { - queuepoly(V, (cs.charid&1) ? shFemaleBody : shPBody, fc(0, cs.skincolor, 0)); + otherbodyparts(V, fc(0, cs.skincolor, 0), items[itOrbFish] ? moWaterElemental : moPlayer, footphase); + queuepoly(VBODY, (cs.charid&1) ? shFemaleBody : shPBody, fc(0, cs.skincolor, 0)); + + ShadowV(V, (cs.charid&1) ? shFemaleBody : shPBody); + + if(items[itOrbHorns]) { + queuepoly(VBODY, shBullHead, items[itOrbDiscord] ? watercolor(0) : 0xFF000030); + queuepoly(VBODY, shBullHorn, items[itOrbDiscord] ? watercolor(0) : 0xFF000040); + queuepoly(VBODY * Mirror, shBullHorn, items[itOrbDiscord] ? watercolor(0) : 0xFF000040); + } if(items[itOrbThorns]) - queuepoly(V, shHedgehogBladePlayer, items[itOrbDiscord] ? watercolor(0) : 0x00FF00FF); + queuepoly(VBODY, shHedgehogBladePlayer, items[itOrbDiscord] ? watercolor(0) : 0x00FF00FF); else if(!shmup::on && items[itOrbDiscord]) - queuepoly(V, shPSword, watercolor(0)); + queuepoly(VBODY, cs.charid >= 2 ? shSabre : shPSword, watercolor(0)); else if(items[itRevolver]) - queuepoly(V, shGunInHand, fc(314, cs.swordcolor, 3)); // 3 not colored + queuepoly(VBODY, shGunInHand, fc(314, cs.swordcolor, 3)); // 3 not colored else if(!shmup::on) - queuepoly(V, cs.charid >= 2 ? shSabre : shPSword, fc(314, cs.swordcolor, 3)); // 3 not colored + queuepoly(VBODY, cs.charid >= 2 ? shSabre : shPSword, fc(314, cs.swordcolor, 3)); // 3 not colored else if(shmup::curtime >= shmup::getPlayer()->nextshot) - queuepoly(V, shPKnife, fc(314, cs.swordcolor, 3)); // 3 not colored - - if(where->land == laWildWest) { - queuepoly(V, shWestHat1, darkena(cs.swordcolor, 1, 0XFF)); - queuepoly(V, shWestHat2, darkena(cs.swordcolor, 0, 0XFF)); + queuepoly(VBODY, shPKnife, fc(314, cs.swordcolor, 3)); // 3 not colored + + if(items[itOrbBeauty]) { + if(cs.charid&1) + queuepoly(VHEAD, shFlowerHair, darkena(iinf[itOrbBeauty].color, 0, 0xFF)); + else + queuepoly(VBODY, shFlowerHand, darkena(iinf[itOrbBeauty].color, 0, 0xFF)); + } + + if(where && where->land == laWildWest) { + queuepoly(VHEAD, shWestHat1, darkena(cs.swordcolor, 1, 0XFF)); + queuepoly(VHEAD, shWestHat2, darkena(cs.swordcolor, 0, 0XFF)); } - if(cheater) { - queuepoly(V, (cs.charid&1) ? shGoatHead : shDemon, darkena(0xFFFF00, 0, 0xFF)); + if(cheater && !autocheat) { + queuepoly(VHEAD, (cs.charid&1) ? shGoatHead : shDemon, darkena(0xFFFF00, 0, 0xFF)); // queuepoly(V, shHood, darkena(0xFF00, 1, 0xFF)); } else { - queuepoly(V, shPFace, fc(500, cs.skincolor, 1)); - queuepoly(V, (cs.charid&1) ? shFemaleHair : shPHead, fc(150, cs.haircolor, 2)); + queuepoly(VHEAD, shPFace, fc(500, cs.skincolor, 1)); + queuepoly(VHEAD, (cs.charid&1) ? shFemaleHair : shPHead, fc(150, cs.haircolor, 2)); } if(cs.charid&1) - queuepoly(V, shFemaleDress, fc(500, cs.dresscolor, 4)); + queuepoly(VBODY, shFemaleDress, fc(500, cs.dresscolor, 4)); if(cs.charid == 2) - queuepoly(V, shPrinceDress, fc(400, cs.dresscolor, 5)); + queuepoly(VBODY, shPrinceDress, fc(400, cs.dresscolor, 5)); if(cs.charid == 3) - queuepoly(V, shPrincessDress, fc(400, cs.dresscolor2, 5)); + queuepoly(VBODY, shPrincessDress, fc(400, cs.dresscolor2, 5)); } if(knighted) - queuepoly(V, shKnightCloak, darkena(cloakcolor(knighted), 1, 0xFF)); - - if(items[itOrbFish]) - queuepoly(V, shFishTail, watercolor(100)); + queuepoly(VBODY, shKnightCloak, darkena(cloakcolor(knighted), 1, 0xFF)); if(tortoise::seek()) - tortoise::draw(V * ypush(-crossf*.25), tortoise::seekbits, 4, 0); + tortoise::draw(VBODY * ypush(-crossf*.25), tortoise::seekbits, 4, 0); } } @@ -1405,92 +1804,93 @@ bool drawMonsterType(eMonster m, cell *where, const transmatrix& V, int col) { else if(isMimic(m) || m == moShadow || m == moIllusion) { charstyle& cs = getcs(); + if(drawUserShape(V, 0, (cs.charid&1)?1:0, darkena(col, 0, 0x80))) return false; - if(cs.charid >= 6) { - queuepoly(V, shWolf, darkena(col, 0, 0xC0)); - queuepoly(V, shWolf1, darkena(col, 1, 0xC0)); - queuepoly(V, shWolf2, darkena(col, 1, 0xC0)); - queuepoly(V, shWolf3, darkena(col, 1, 0xC0)); + if(cs.charid >= 8) { + queuepoly(VABODY, shWolfBody, darkena(col, 0, 0xC0)); + ShadowV(V, shWolfBody); + + if(mmspatial || footphase) + animallegs(VALEGS, moWolf, darkena(col, 0, 0xC0), footphase); + else + queuepoly(VABODY, shWolfLegs, darkena(col, 0, 0xC0)); + + queuepoly(VABODY, shFamiliarHead, darkena(col, 0, 0xC0)); + queuepoly(VAHEAD, shFamiliarEye, darkena(col, 0, 0xC0)); + queuepoly(VAHEAD * Mirror, shFamiliarEye, darkena(col, 0, 0xC0)); + } + else if(cs.charid >= 6) { + ShadowV(V, shDogBody); + queuepoly(VAHEAD, shDogHead, darkena(col, 0, 0xC0)); + if(mmspatial || footphase) { + animallegs(VALEGS, moRunDog, darkena(col, 0, 0xC0), footphase); + queuepoly(VABODY, shDogTorso, darkena(col, 0, 0xC0)); + } + else + queuepoly(VABODY, shDogBody, darkena(col, 0, 0xC0)); + queuepoly(VABODY, shWolf1, darkena(col, 1, 0xC0)); + queuepoly(VABODY, shWolf2, darkena(col, 1, 0xC0)); + queuepoly(VABODY, shWolf3, darkena(col, 1, 0xC0)); } else if(cs.charid >= 4) { - queuepoly(V, shCatBody, darkena(col, 0, 0xC0)); - queuepoly(V, shCatHead, darkena(col, 0, 0xC0)); - queuepoly(V, shCatLegs, darkena(col, 0, 0xC0)); - queuepoly(V * xpush(.04), shWolf1, darkena(col, 1, 0xC0)); - queuepoly(V * xpush(.04), shWolf2, darkena(col, 1, 0xC0)); + ShadowV(V, shCatBody); + queuepoly(VABODY, shCatBody, darkena(col, 0, 0xC0)); + queuepoly(VAHEAD, shCatHead, darkena(col, 0, 0xC0)); + if(mmspatial || footphase) + animallegs(VALEGS, moRunDog, darkena(col, 0, 0xC0), footphase); + else + queuepoly(VALEGS, shCatLegs, darkena(col, 0, 0xC0)); + queuepoly(VAHEAD * xpush(.04), shWolf1, darkena(col, 1, 0xC0)); + queuepoly(VAHEAD * xpush(.04), shWolf2, darkena(col, 1, 0xC0)); } else { - queuepoly(V, (cs.charid&1) ? shFemaleBody : shPBody, darkena(col, 0, 0X80)); + otherbodyparts(V, darkena(col, 0, 0x40), m, footphase); + queuepoly(VBODY, (cs.charid&1) ? shFemaleBody : shPBody, darkena(col, 0, 0X80)); if(!shmup::on) - queuepoly(V, (cs.charid >= 2 ? shSabre : shPSword), darkena(col, 0, 0XC0)); + queuepoly(VBODY, (cs.charid >= 2 ? shSabre : shPSword), darkena(col, 0, 0XC0)); else if(shmup::curtime >= shmup::getPlayer()->nextshot) - queuepoly(V, shPKnife, darkena(col, 0, 0XC0)); + queuepoly(VBODY, shPKnife, darkena(col, 0, 0XC0)); - queuepoly(V, (cs.charid&1) ? shFemaleHair : shPHead, darkena(col, 1, 0XC0)); - queuepoly(V, shPFace, darkena(col, 0, 0XC0)); + queuepoly(VHEAD, (cs.charid&1) ? shFemaleHair : shPHead, darkena(col, 1, 0XC0)); + queuepoly(VHEAD, shPFace, darkena(col, 0, 0XC0)); if(cs.charid&1) - queuepoly(V, shFemaleDress, darkena(col, 1, 0XC0)); + queuepoly(VBODY, shFemaleDress, darkena(col, 1, 0XC0)); if(cs.charid == 2) - queuepoly(V, shPrinceDress, darkena(col, 1, 0XC0)); + queuepoly(VBODY, shPrinceDress, darkena(col, 1, 0XC0)); if(cs.charid == 3) - queuepoly(V, shPrincessDress, darkena(col, 1, 0XC0)); + queuepoly(VBODY, shPrincessDress, darkena(col, 1, 0XC0)); } } -/* else if(m == moShadow) { - charstyle& cs = getcs(); - queuepoly(V, (cs.charid&1) ? shFemaleBody : shPBody, darkena(col, 0, 0X80)); - queuepoly(V, (cscharid >= 2 ? shSabre : shPSword), darkena(col, 0, 0XC0)); - queuepoly(V, (cs.charid&1) ? shFemaleHair : shPHead, darkena(col, 1, 0XC0)); - queuepoly(V, shPFace, darkena(col, 0, 0XC0)); - - if(cs.charid&1) - queuepoly(V, shFemaleDress, darkena(col, 1, 0xC0)); - if(cs.charid == 2) - queuepoly(V, shPrinceDress, darkena(col, 1, 0XC0)); - if(cs.charid == 3) - queuepoly(V, shPrincessDress, darkena(col, 1, 0XC0)); - } - - else if(m == moIllusion) { - charstyle& cs = getcs(); - if(drawUserShape(V, 0, (cs.charid&1)?1:0, darkena(col, 0, 0x80))) return false; - queuepoly(V, (cs.charid&1) ? shFemaleBody : shPBody, darkena(col, 0, 0X80)); - queuepoly(V, (cscharid >= 2 ? shSabre : shPSword), darkena(col, 0, 0XC0)); - queuepoly(V, (cs.charid&1) ? shFemaleHair : shPHead, darkena(col, 1, 0XC0)); - queuepoly(V, shPFace, darkena(col, 0, 0XC0)); - if(cs.charid&1) - queuepoly(V, shFemaleDress, darkena(col, 1, 0XC0)); - if(cs.charid == 2) - queuepoly(V, shPrinceDress, darkena(col, 1, 0XC0)); - if(cs.charid == 3) - queuepoly(V, shPrincessDress, darkena(col, 1, 0XC0)); - } */ - else if(m == moBullet) { - queuepoly(V * spin(-M_PI/4), shKnife, getcs().swordcolor); + ShadowV(V, shKnife); + queuepoly(VBODY * spin(-M_PI/4), shKnife, getcs().swordcolor); } else if(m == moKnight || m == moKnightMoved) { - queuepoly(V, shPBody, darkena(0xC0C0A0, 0, 0xC0)); - queuepoly(V, shPSword, darkena(0xFFFF00, 0, 0xFF)); - queuepoly(V, shKnightArmor, darkena(0xD0D0D0, 1, 0xFF)); + ShadowV(V, shPBody); + otherbodyparts(V, darkena(0xC0C0A0, 0, 0xC0), m, footphase); + queuepoly(VBODY, shPBody, darkena(0xC0C0A0, 0, 0xC0)); + queuepoly(VBODY, shPSword, darkena(0xFFFF00, 0, 0xFF)); + queuepoly(VBODY, shKnightArmor, darkena(0xD0D0D0, 1, 0xFF)); int col; - if(!euclid && where->master->alt) + if(!euclid && where && where->master->alt) col = cloakcolor(roundTableRadius(where)); else col = cloakcolor(newRoundTableRadius()); - queuepoly(V, shKnightCloak, darkena(col, 1, 0xFF)); - queuepoly(V, shPHead, darkena(0x703800, 1, 0XFF)); - queuepoly(V, shPFace, darkena(0xC0C0A0, 0, 0XFF)); + queuepoly(VBODY, shKnightCloak, darkena(col, 1, 0xFF)); + queuepoly(VHEAD, shPHead, darkena(0x703800, 1, 0XFF)); + queuepoly(VHEAD, shPFace, darkena(0xC0C0A0, 0, 0XFF)); return false; } else if(m == moGolem) { - queuepoly(V, shPBody, darkena(col, 0, 0XC0)); - queuepoly(V, shGolemhead, darkena(col, 1, 0XFF)); + ShadowV(V, shPBody); + otherbodyparts(V, darkena(col, 1, 0XC0), m, footphase); + queuepoly(VBODY, shPBody, darkena(col, 0, 0XC0)); + queuepoly(VHEAD, shGolemhead, darkena(col, 1, 0XFF)); } else if(isPrincess(m) || m == moFalsePrincess || m == moRoseLady || m == moRoseBeauty) { @@ -1500,318 +1900,514 @@ bool drawMonsterType(eMonster m, cell *where, const transmatrix& V, int col) { int facecolor = evil ? 0xC0B090FF : 0xD0C080FF; - queuepoly(V, girl ? shFemaleBody : shPBody, facecolor); + ShadowV(V, girl ? shFemaleBody : shPBody); + otherbodyparts(V, facecolor, m, footphase); + queuepoly(VBODY, girl ? shFemaleBody : shPBody, facecolor); if(m == moPrincessArmed) - queuepoly(V, vid.cs.charid < 2 ? shSabre : shPSword, 0xFFFFFFFF); + queuepoly(VBODY, vid.cs.charid < 2 ? shSabre : shPSword, 0xFFFFFFFF); - if((m == moFalsePrincess || m == moRoseBeauty) && where->cpdist == 1) - queuepoly(V, shPKnife, 0xFFFFFFFF); + if((m == moFalsePrincess || m == moRoseBeauty) && where && where->cpdist == 1) + queuepoly(VBODY, shPKnife, 0xFFFFFFFF); if(m == moRoseLady) { - queuepoly(V, shPKnife, 0xFFFFFFFF); - queuepoly(V * Mirror, shPKnife, 0xFFFFFFFF); + queuepoly(VBODY, shPKnife, 0xFFFFFFFF); + queuepoly(VBODY * Mirror, shPKnife, 0xFFFFFFFF); } if(m == moRoseLady) { // queuepoly(V, girl ? shGoatHead : shDemon, 0x800000FF); - queuepoly(V, girl ? shFemaleHair : shPHead, evil ? 0x500050FF : 0x332A22FF); + queuepoly(VHEAD, girl ? shFemaleHair : shPHead, evil ? 0x500050FF : 0x332A22FF); } else if(m == moRoseBeauty) { if(girl) { - queuepoly(V, shBeautyHair, 0xF0A0D0FF); - queuepoly(V, shFlowerHair, 0xC00000FF); + queuepoly(VHEAD, shBeautyHair, 0xF0A0D0FF); + queuepoly(VHEAD, shFlowerHair, 0xC00000FF); } else { - queuepoly(V, shPHead, 0xF0A0D0FF); - queuepoly(V, shFlowerHand, 0xC00000FF); - queuepoly(V, shSuspenders, 0xC00000FF); + queuepoly(VHEAD, shPHead, 0xF0A0D0FF); + queuepoly(VHEAD, shFlowerHand, 0xC00000FF); + queuepoly(VBODY, shSuspenders, 0xC00000FF); } } else { - queuepoly(V, girl ? shFemaleHair : shPHead, + queuepoly(VHEAD, girl ? shFemaleHair : shPHead, evil ? 0xC00000FF : 0x332A22FF); } - queuepoly(V, shPFace, facecolor); + queuepoly(VHEAD, shPFace, facecolor); if(girl) { - queuepoly(V, shFemaleDress, evil ? 0xC000C0FF : 0x00C000FF); + queuepoly(VBODY, shFemaleDress, evil ? 0xC000C0FF : 0x00C000FF); if(vid.cs.charid < 2) - queuepoly(V, shPrincessDress, evil ? 0xC040C0C0 : 0x8080FFC0); + queuepoly(VBODY, shPrincessDress, evil ? 0xC040C0C0 : 0x8080FFC0); } else { if(vid.cs.charid < 2) - queuepoly(V, shPrinceDress, evil ? 0x802080FF : 0x404040FF); + queuepoly(VBODY, shPrinceDress, evil ? 0x802080FF : 0x404040FF); } } else if(m == moWolf || m == moRedFox) { - queuepoly(V, shWolfLegs, darkena(col, 0, 0xFF)); - queuepoly(V, shWolfBody, darkena(col, 0, 0xFF)); - queuepoly(V, shWolfHead, darkena(col, 0, 0xFF)); - queuepoly(V, shWolfEyes, darkena(col, 3, 0xFF)); + ShadowV(V, shWolfBody); + if(mmspatial || footphase) + animallegs(VALEGS, moWolf, darkena(col, 0, 0xFF), footphase); + else + queuepoly(VALEGS, shWolfLegs, darkena(col, 0, 0xFF)); + queuepoly(VABODY, shWolfBody, darkena(col, 0, 0xFF)); + queuepoly(VAHEAD, shWolfHead, darkena(col, 0, 0xFF)); + queuepoly(VAHEAD, shWolfEyes, darkena(col, 3, 0xFF)); + } + + else if(isBull(m)) { + ShadowV(V, shBullBody); + int hoofcol = darkena(gradient(0, col, 0, .65, 1), 0, 0xFF); + if(mmspatial || footphase) + animallegs(VALEGS, moRagingBull, hoofcol, footphase); + queuepoly(VABODY, shBullBody, darkena(gradient(0, col, 0, .80, 1), 0, 0xFF)); + queuepoly(VAHEAD, shBullHead, darkena(col, 0, 0xFF)); + queuepoly(VAHEAD, shBullHorn, darkena(0xFFFFFF, 0, 0xFF)); + queuepoly(VAHEAD * Mirror, shBullHorn, darkena(0xFFFFFF, 0, 0xFF)); + } + + else if(m == moReptile) { + ShadowV(V, shReptileBody); + animallegs(VALEGS, moReptile, darkena(col, 0, 0xFF), footphase); + queuepoly(VABODY, shReptileBody, darkena(col, 0, 0xFF)); + queuepoly(VAHEAD, shReptileHead, darkena(col, 0, 0xFF)); + queuepoly(VAHEAD, shReptileEye, darkena(col, 3, 0xFF)); + queuepoly(VAHEAD * Mirror, shReptileEye, darkena(col, 3, 0xFF)); + queuepoly(VABODY, shReptileTail, darkena(col, 2, 0xFF)); } else if(m == moVineBeast) { - queuepoly(V, shWolfLegs, 0x00FF00FF); - queuepoly(V, shWolfBody, darkena(col, 1, 0xFF)); - queuepoly(V, shWolfHead, darkena(col, 0, 0xFF)); - queuepoly(V, shWolfEyes, 0xFF0000FF); + ShadowV(V, shWolfBody); + if(mmspatial || footphase) + animallegs(VALEGS, moWolf, 0x00FF00FF, footphase); + else + queuepoly(VALEGS, shWolfLegs, 0x00FF00FF); + queuepoly(VABODY, shWolfBody, darkena(col, 1, 0xFF)); + queuepoly(VAHEAD, shWolfHead, darkena(col, 0, 0xFF)); + queuepoly(VAHEAD, shWolfEyes, 0xFF0000FF); } else if(m == moMouse || m == moMouseMoved) { - queuepoly(V, shMouse, darkena(col, 0, 0xFF)); - queuepoly(V, shMouseLegs, darkena(col, 1, 0xFF)); - queuepoly(V, shMouseEyes, 0xFF); + queuepoly(VALEGS, shMouse, darkena(col, 0, 0xFF)); + queuepoly(VALEGS, shMouseLegs, darkena(col, 1, 0xFF)); + queuepoly(VALEGS, shMouseEyes, 0xFF); } else if(isBug(m)) { - queuepoly(V, shBugBody, darkena(col, 0, 0xFF)); - queuepoly(V, shBugArmor, darkena(col, 1, 0xFF)); + ShadowV(V, shBugBody); + if(!mmspatial && !footphase) + queuepoly(VABODY, shBugBody, darkena(col, 0, 0xFF)); + else { + animallegs(VALEGS, moBug0, darkena(col, 0, 0xFF), footphase); + queuepoly(VABODY, shBugAntenna, darkena(col, 1, 0xFF)); + } + queuepoly(VABODY, shBugArmor, darkena(col, 1, 0xFF)); } else if(m == moRunDog) { - queuepoly(V, shWolf, darkena(col, 0, 0xFF)); - queuepoly(V, shWolf1, darkena(0x202020, 0, 0xFF)); - queuepoly(V, shWolf2, darkena(0x202020, 0, 0xFF)); - queuepoly(V, shWolf3, darkena(0x202020, 0, 0xFF)); + if(!mmspatial && !footphase) + queuepoly(VABODY, shDogBody, darkena(col, 0, 0xFF)); + else { + ShadowV(V, shDogTorso); + queuepoly(VABODY, shDogTorso, darkena(col, 0, 0xFF)); + animallegs(VALEGS, moRunDog, darkena(col, 0, 0xFF), footphase); + } + queuepoly(VAHEAD, shDogHead, darkena(col, 0, 0xFF)); + queuepoly(VAHEAD, shWolf1, darkena(0x202020, 0, 0xFF)); + queuepoly(VAHEAD, shWolf2, darkena(0x202020, 0, 0xFF)); + queuepoly(VAHEAD, shWolf3, darkena(0x202020, 0, 0xFF)); } else if(m == moOrangeDog) { - queuepoly(V, shWolf, darkena(0xFFFFFF, 0, 0xFF)); - queuepoly(V, shWolf1, darkena(0x202020, 0, 0xFF)); - queuepoly(V, shWolf2, darkena(0x202020, 0, 0xFF)); - queuepoly(V, shWolf3, darkena(0x202020, 0, 0xFF)); - queuepoly(V, shDogStripes, darkena(0x303030, 0, 0xFF)); + if(!mmspatial && !footphase) + queuepoly(VABODY, shDogBody, darkena(0xFFFFFF, 0, 0xFF)); + else { + ShadowV(V, shDogTorso); + queuepoly(VABODY, shDogTorso, darkena(0xFFFFFF, 0, 0xFF)); + animallegs(VALEGS, moRunDog, darkena(0xFFFFFF, 0, 0xFF), footphase); + } + queuepoly(VAHEAD, shDogHead, darkena(0xFFFFFF, 0, 0xFF)); + queuepoly(VABODY, shDogStripes, darkena(0x303030, 0, 0xFF)); + queuepoly(VAHEAD, shWolf1, darkena(0x202020, 0, 0xFF)); + queuepoly(VAHEAD, shWolf2, darkena(0x202020, 0, 0xFF)); + queuepoly(VAHEAD, shWolf3, darkena(0x202020, 0, 0xFF)); } else if(m == moShark || m == moGreaterShark || m == moCShark) - queuepoly(V, shShark, darkena(col, 0, 0xFF)); + queuepoly(VFISH, shShark, darkena(col, 0, 0xFF)); else if(m == moEagle || m == moParrot || m == moBomberbird || m == moAlbatross || - m == moTameBomberbird || m == moWindCrow) - queuepoly(V, shEagle, darkena(col, 0, 0xFF)); - else if(m == moNighthawk || m == moKestrel) - queuepoly(V, shHawk, darkena(col, 0, 0xFF)); - else if(m == moGargoyle) { - queuepoly(V, shGargoyleWings, darkena(col, 0, 0xD0)); - queuepoly(V, shGargoyleBody, darkena(col, 0, 0xFF)); + m == moTameBomberbird || m == moWindCrow) { + ShadowV(V, shEagle); + queuepoly(VBIRD, shEagle, darkena(col, 0, 0xFF)); + } + else if(m == moSparrowhawk) { + ShadowV(V, shHawk); + queuepoly(VBIRD, shHawk, darkena(col, 0, 0xFF)); + } + else if(m == moButterfly) { + transmatrix Vwing = Id; + Vwing[1][1] = .85 + .15 * sin(ticks / 100.0); + ShadowV(V * Vwing, shButterflyWing); + queuepoly(VBIRD * Vwing, shButterflyWing, darkena(col, 0, 0xFF)); + queuepoly(VBIRD, shButterflyBody, darkena(col, 2, 0xFF)); + } + else if(m == moGadfly) { + transmatrix Vwing = Id; + Vwing[1][1] = .85 + .15 * sin(ticks / 100.0); + ShadowV(V * Vwing, shGadflyWing); + queuepoly(VBIRD * Vwing, shGadflyWing, darkena(col, 0, 0xFF)); + queuepoly(VBIRD, shGadflyBody, darkena(col, 1, 0xFF)); + queuepoly(VBIRD, shGadflyEye, darkena(col, 2, 0xFF)); + queuepoly(VBIRD * Mirror, shGadflyEye, darkena(col, 2, 0xFF)); + } + else if(m == moVampire || m == moBat) { + if(m == moBat) ShadowV(V, shBatWings); // but vampires have no shadow + queuepoly(VBIRD, shBatWings, darkena(0x303030, 0, 0xFF)); + queuepoly(VBIRD, shBatBody, darkena(0x606060, 0, 0xFF)); + /* queuepoly(V, shBatMouth, darkena(0xC00000, 0, 0xFF)); + queuepoly(V, shBatFang, darkena(0xFFC0C0, 0, 0xFF)); + queuepoly(V*Mirror, shBatFang, darkena(0xFFC0C0, 0, 0xFF)); + queuepoly(V, shBatEye, darkena(00000000, 0, 0xFF)); + queuepoly(V*Mirror, shBatEye, darkena(00000000, 0, 0xFF)); */ + } + else if(m == moGargoyle) { + ShadowV(V, shGargoyleWings); + queuepoly(VBIRD, shGargoyleWings, darkena(col, 0, 0xD0)); + queuepoly(VBIRD, shGargoyleBody, darkena(col, 0, 0xFF)); + } + else if(m == moZombie) { + int c = darkena(col, where->land == laHalloween ? 1 : 0, 0xFF); + otherbodyparts(V, c, m, footphase); + ShadowV(V, shPBody); + queuepoly(VBODY, shPBody, c); } - else if(m == moZombie) - queuepoly(V, shPBody, darkena(col, 0, 0xFF)); else if(m == moDesertman) { - queuepoly(V, shPBody, darkena(col, 0, 0xC0)); - queuepoly(V, shPSword, 0xFFFF00FF); - queuepoly(V, shHood, 0xD0D000C0); + otherbodyparts(V, darkena(col, 0, 0xC0), m, footphase); + ShadowV(V, shPBody); + queuepoly(VBODY, shPBody, darkena(col, 0, 0xC0)); + queuepoly(VBODY, shPSword, 0xFFFF00FF); + queuepoly(VHEAD, shHood, 0xD0D000C0); } else if(m == moPalace || m == moFatGuard || m == moVizier || m == moSkeleton) { - queuepoly(V, shSabre, 0xFFFFFFFF); + queuepoly(VBODY, shSabre, 0xFFFFFFFF); if(m == moSkeleton) { - queuepoly(V, shSkeletonBody, darkena(0xFFFFFF, 0, 0xFF)); - queuepoly(V, shSkull, darkena(0xFFFFFF, 0, 0xFF)); - queuepoly(V, shSkullEyes, darkena(0, 0, 0xFF)); + otherbodyparts(V, darkena(0xFFFFFF, 0, 0xFF), moSkeleton, footphase); + queuepoly(VBODY, shSkeletonBody, darkena(0xFFFFFF, 0, 0xFF)); + queuepoly(VHEAD, shSkull, darkena(0xFFFFFF, 0, 0xFF)); + queuepoly(VHEAD, shSkullEyes, darkena(0, 0, 0xFF)); + ShadowV(V, shSkeletonBody); } else { + ShadowV(V, shPBody); + otherbodyparts(V, darkena(0xFFD500, 0, 0xFF), m, footphase); if(m == moFatGuard) { - queuepoly(V, shFatBody, darkena(0xC06000, 0, 0xFF)); + queuepoly(VBODY, shFatBody, darkena(0xC06000, 0, 0xFF)); col = 0xFFFFFF; - if(where->hitpoints >= 3) - queuepoly(V, shKnightCloak, darkena(0xFFC0C0, 1, 0xFF)); + if(where && where->hitpoints >= 3) + queuepoly(VBODY, shKnightCloak, darkena(0xFFC0C0, 1, 0xFF)); } else { - queuepoly(V, shPBody, darkena(0xFFD500, 0, 0xFF)); - queuepoly(V, shKnightArmor, m == moVizier ? 0xC000C0FF : + queuepoly(VBODY, shPBody, darkena(0xFFD500, 0, 0xFF)); + queuepoly(VBODY, shKnightArmor, m == moVizier ? 0xC000C0FF : darkena(0x00C000, 1, 0xFF)); - if(where->hitpoints >= 3) - queuepoly(V, shKnightCloak, m == moVizier ? 0x800080Ff : + if(where && where->hitpoints >= 3) + queuepoly(VBODY, shKnightCloak, m == moVizier ? 0x800080Ff : darkena(0x00FF00, 1, 0xFF)); } - queuepoly(V, shTurban1, darkena(col, 1, 0xFF)); - if(where->hitpoints >= 2) - queuepoly(V, shTurban2, darkena(col, 0, 0xFF)); + queuepoly(VHEAD, shTurban1, darkena(col, 1, 0xFF)); + if(where && where->hitpoints >= 2) + queuepoly(VHEAD, shTurban2, darkena(col, 0, 0xFF)); } - drawStunStars(V, where->stuntime); + drawStunStars(V, where ? where->stuntime : 0); } else if(m == moCrystalSage) { - queuepoly(V, shPBody, 0xFFFFFFFF); - queuepoly(V, shPHead, 0xFFFFFFFF); - queuepoly(V, shPFace, 0xFFFFFFFF); + otherbodyparts(V, 0xFFFFFFFF, m, footphase); + ShadowV(V, shPBody); + queuepoly(VBODY, shPBody, 0xFFFFFFFF); + queuepoly(VHEAD, shPHead, 0xFFFFFFFF); + queuepoly(VHEAD, shPFace, 0xFFFFFFFF); } else if(m == moHedge) { - queuepoly(V, shPBody, darkena(col, 0, 0xFF)); - queuepoly(V, shHedgehogBlade, 0xC0C0C0FF); - queuepoly(V, shPHead, 0x804000FF); - queuepoly(V, shPFace, 0xF09000FF); + ShadowV(V, shPBody); + otherbodyparts(V, darkena(col, 1, 0xFF), m, footphase); + queuepoly(VBODY, shPBody, darkena(col, 0, 0xFF)); + queuepoly(VBODY, shHedgehogBlade, 0xC0C0C0FF); + queuepoly(VHEAD, shPHead, 0x804000FF); + queuepoly(VHEAD, shPFace, 0xF09000FF); } else if(m == moYeti || m == moMonkey) { - queuepoly(V, shYeti, darkena(col, 0, 0xC0)); - queuepoly(V, shPHead, darkena(col, 0, 0xFF)); + otherbodyparts(V, darkena(col, 0, 0xC0), m, footphase); + ShadowV(V, shPBody); + queuepoly(VBODY, shYeti, darkena(col, 0, 0xC0)); + queuepoly(VHEAD, shPHead, darkena(col, 0, 0xFF)); } - else if(m == moLemur) { - queuepoly(V, shPBody, darkena(0xFFFF00, 0, 0xC0)); - queuepoly(V, shAztecHead, darkena(col, 0, 0xFF)); - queuepoly(V, shAztecCap, darkena(0xC000C0, 0, 0xFF)); + else if(m == moResearcher) { + otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase); + ShadowV(V, shPBody); + queuepoly(VBODY, shPBody, darkena(0xFFFF00, 0, 0xC0)); + queuepoly(VHEAD, shAztecHead, darkena(col, 0, 0xFF)); + queuepoly(VHEAD, shAztecCap, darkena(0xC000C0, 0, 0xFF)); } - else if(m == moEdgeMonkey) { - queuepoly(V, shYeti, darkena(0xC04040, 0, 0xC0)); - queuepoly(V, shPHead, darkena(col, 0, 0xFF)); + else if(m == moFamiliar) { + /* queuepoly(V, shFemaleBody, darkena(0xC0B070, 0, 0xFF)); + queuepoly(V, shPFace, darkena(0xC0B070, 0, 0XFF)); + queuepoly(V, shWizardCape1, 0x601000FF); + queuepoly(V, shWizardCape2, 0x781800FF); + queuepoly(V, shWizardHat1, 0x902000FF); + queuepoly(V, shWizardHat2, 0xA82800FF); */ + + // queuepoly(V, shCatBody, darkena(0x601000, 0, 0xFF)); + // queuepoly(V, shGargoyleBody, darkena(0x903000, 0, 0xFF)); + + ShadowV(V, shWolfBody); + queuepoly(VABODY, shWolfBody, darkena(0xA03000, 0, 0xFF)); + if(mmspatial || footphase) + animallegs(VALEGS, moWolf, darkena(0xC04000, 0, 0xFF), footphase); + else + queuepoly(VALEGS, shWolfLegs, darkena(0xC04000, 0, 0xFF)); + + queuepoly(VAHEAD, shFamiliarHead, darkena(0xC04000, 0, 0xFF)); + // queuepoly(V, shCatLegs, darkena(0x902000, 0, 0xFF)); + if(true) { + queuepoly(VAHEAD, shFamiliarEye, darkena(0xFFFF000, 0, 0xFF)); + queuepoly(VAHEAD * Mirror, shFamiliarEye, darkena(0xFFFF000, 0, 0xFF)); + } } else if(m == moRanger) { - queuepoly(V, shPBody, darkena(col, 0, 0xC0)); - queuepoly(V, shPSword, darkena(col, 0, 0xFF)); - queuepoly(V, shArmor, darkena(col, 1, 0xFF)); + ShadowV(V, shPBody); + otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase); + queuepoly(VBODY, shPBody, darkena(col, 0, 0xC0)); + queuepoly(VBODY, shPSword, darkena(col, 0, 0xFF)); + queuepoly(VHEAD, shArmor, darkena(col, 1, 0xFF)); } else if(m == moGhost || m == moSeep || m == moFriendlyGhost) { - queuepoly(V, shGhost, darkena(col, 0, 0x80)); - queuepoly(V, shEyes, 0xFF); + if(m == moFriendlyGhost) col = fghostcolor(ticks, where); + queuepoly(VGHOST, shGhost, darkena(col, 0, m == moFriendlyGhost ? 0xC0 : 0x80)); + queuepoly(VGHOST, shEyes, 0xFF); } else if(m == moVineSpirit) { - queuepoly(V, shGhost, 0xD0D0D0C0); - queuepoly(V, shEyes, 0xFF0000FF); + queuepoly(VGHOST, shGhost, 0xD0D0D0C0); + queuepoly(VGHOST, shEyes, 0xFF0000FF); } else if(m == moFireFairy) { col = firecolor(0); - queuepoly(V, shFemaleBody, darkena(col, 0, 0XC0)); - queuepoly(V, shWitchHair, darkena(col, 1, 0xFF)); - queuepoly(V, shPFace, darkena(col, 0, 0XFF)); + otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase); + ShadowV(V, shFemaleBody); + queuepoly(VBODY, shFemaleBody, darkena(col, 0, 0XC0)); + queuepoly(VHEAD, shWitchHair, darkena(col, 1, 0xFF)); + queuepoly(VHEAD, shPFace, darkena(col, 0, 0XFF)); } else if(m == moSlime) { - queuepoly(V, shSlime, darkena(col, 0, 0x80)); - queuepoly(V, shEyes, 0xFF); + queuepoly(VFISH, shSlime, darkena(col, 0, 0x80)); + queuepoly(VSLIMEEYE, shEyes, 0xFF); + } + else if(m == moKrakenH) { + queuepoly(VFISH, shKrakenHead, darkena(col, 0, 0xD0)); + queuepoly(VFISH, shKrakenEye, 0xFFFFFFC0); + queuepoly(VFISH, shKrakenEye2, 0xC0); + queuepoly(VFISH * Mirror, shKrakenEye, 0xFFFFFFC0); + queuepoly(VFISH * Mirror, shKrakenEye2, 0xC0); + } + else if(m == moKrakenT) { + queuepoly(VFISH, shSeaTentacle, darkena(col, 0, 0xD0)); } else if(m == moCultist || m == moPyroCultist || m == moCultistLeader) { - queuepoly(V, shPBody, darkena(col, 0, 0xC0)); - queuepoly(V, shPSword, darkena(col, 2, 0xFF)); - queuepoly(V, shHood, darkena(col, 1, 0xFF)); + otherbodyparts(V, darkena(col, 1, 0xFF), m, footphase); + ShadowV(V, shPBody); + queuepoly(VBODY, shPBody, darkena(col, 0, 0xC0)); + queuepoly(VBODY, shPSword, darkena(col, 2, 0xFF)); + queuepoly(VHEAD, shHood, darkena(col, 1, 0xFF)); } else if(m == moPirate) { - queuepoly(V, shPBody, darkena(0x404040, 0, 0xFF)); - queuepoly(V, shPirateHook, darkena(0xD0D0D0, 0, 0xFF)); - queuepoly(V, shPFace, darkena(0xFFFF80, 0, 0xFF)); - queuepoly(V, shEyepatch, darkena(0, 0, 0xC0)); - queuepoly(V, shPirateHood, darkena(col, 0, 0xFF)); + otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase); + ShadowV(V, shPBody); + queuepoly(VBODY, shPBody, darkena(0x404040, 0, 0xFF)); + queuepoly(VBODY, shPirateHook, darkena(0xD0D0D0, 0, 0xFF)); + queuepoly(VHEAD, shPFace, darkena(0xFFFF80, 0, 0xFF)); + queuepoly(VHEAD, shEyepatch, darkena(0, 0, 0xC0)); + queuepoly(VHEAD, shPirateHood, darkena(col, 0, 0xFF)); } else if(m == moRatling || m == moRatlingAvenger) { - queuepoly(V, shYeti, darkena(col, 1, 0xFF)); - queuepoly(V, shRatHead, darkena(col, 0, 0xFF)); - queuepoly(V, shRatTail, darkena(col, 0, 0xFF)); + otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase); + ShadowV(V, shYeti); + queuepoly(VBODY, shYeti, darkena(col, 1, 0xFF)); + queuepoly(VHEAD, shRatHead, darkena(col, 0, 0xFF)); + queuepoly(VLEG, shRatTail, darkena(col, 0, 0xFF)); - float t = sin(ticks / 1000.0 + where->cpdist); + float t = sin(ticks / 1000.0 + (where ? where->cpdist : 0)); int eyecol = t > 0.92 ? 0xFF0000 : 0; - queuepoly(V, shWolf1, darkena(eyecol, 0, 0xFF)); - queuepoly(V, shWolf2, darkena(eyecol, 0, 0xFF)); - queuepoly(V, shWolf3, darkena(0x202020, 0, 0xFF)); + queuepoly(VHEAD, shWolf1, darkena(eyecol, 0, 0xFF)); + queuepoly(VHEAD, shWolf2, darkena(eyecol, 0, 0xFF)); + queuepoly(VHEAD, shWolf3, darkena(0x202020, 0, 0xFF)); if(m == moRatlingAvenger) { - queuepoly(V, shRatCape1, 0x303030FF); - queuepoly(V, shRatCape2, 0x484848FF); + queuepoly(VBODY, shRatCape1, 0x303030FF); + queuepoly(VHEAD, shRatCape2, 0x484848FF); } } else if(m == moViking) { - queuepoly(V, shPBody, darkena(0xE00000, 0, 0xFF)); - queuepoly(V, shPSword, darkena(0xD0D0D0, 0, 0xFF)); - queuepoly(V, shKnightCloak, darkena(0x404040, 0, 0xFF)); - queuepoly(V, shVikingHelmet, darkena(0xC0C0C0, 0, 0XFF)); - queuepoly(V, shPFace, darkena(0xFFFF80, 0, 0xFF)); + otherbodyparts(V, darkena(col, 1, 0xFF), m, footphase); + ShadowV(V, shPBody); + queuepoly(VBODY, shPBody, darkena(0xE00000, 0, 0xFF)); + queuepoly(VBODY, shPSword, darkena(0xD0D0D0, 0, 0xFF)); + queuepoly(VBODY, shKnightCloak, darkena(0x404040, 0, 0xFF)); + queuepoly(VHEAD, shVikingHelmet, darkena(0xC0C0C0, 0, 0XFF)); + queuepoly(VHEAD, shPFace, darkena(0xFFFF80, 0, 0xFF)); } else if(m == moOutlaw) { - queuepoly(V, shPBody, darkena(col, 0, 0xFF)); - queuepoly(V, shKnightCloak, darkena(col, 1, 0xFF)); - queuepoly(V, shWestHat1, darkena(col, 2, 0XFF)); - queuepoly(V, shWestHat2, darkena(col, 1, 0XFF)); - queuepoly(V, shPFace, darkena(0xFFFF80, 0, 0xFF)); - queuepoly(V, shGunInHand, darkena(col, 1, 0XFF)); + otherbodyparts(V, darkena(col, 1, 0xFF), m, footphase); + ShadowV(V, shPBody); + queuepoly(VBODY, shPBody, darkena(col, 0, 0xFF)); + queuepoly(VBODY, shKnightCloak, darkena(col, 1, 0xFF)); + queuepoly(VHEAD, shWestHat1, darkena(col, 2, 0XFF)); + queuepoly(VHEAD, shWestHat2, darkena(col, 1, 0XFF)); + queuepoly(VHEAD, shPFace, darkena(0xFFFF80, 0, 0xFF)); + queuepoly(VBODY, shGunInHand, darkena(col, 1, 0XFF)); } else if(m == moNecromancer) { - queuepoly(V, shPBody, 0xC00000C0); - queuepoly(V, shHood, darkena(col, 1, 0xFF)); + otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase); + ShadowV(V, shPBody); + queuepoly(VBODY, shPBody, 0xC00000C0); + queuepoly(VHEAD, shHood, darkena(col, 1, 0xFF)); + } + else if(m == moDraugr) { + otherbodyparts(V, 0x483828D0, m, footphase); + queuepoly(VBODY, shPBody, 0x483828D0); + queuepoly(VBODY, shPSword, 0xFFFFD0A0); + queuepoly(VHEAD, shPHead, 0x483828D0); + // queuepoly(V, shSkull, 0xC06020D0); + //queuepoly(V, shSkullEyes, 0x000000D0); +// queuepoly(V, shWightCloak, 0xC0A080A0); + int b = where->cpdist; + b--; + if(b < 0) b = 0; + if(b > 6) b = 6; + queuepoly(VHEAD, shWightCloak, 0x605040A0 + 0x10101000 * b); } else if(m == moGoblin) { - queuepoly(V, shYeti, darkena(col, 0, 0xC0)); - queuepoly(V, shArmor, darkena(col, 1, 0XFF)); + otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase); + ShadowV(V, shYeti); + queuepoly(VBODY, shYeti, darkena(col, 0, 0xC0)); + queuepoly(VHEAD, shArmor, darkena(col, 1, 0XFF)); } else if(m == moLancer || m == moFlailer || m == moMiner) { transmatrix V2 = V; if(m == moLancer) - V2 = V * spin(where->type == 6 ? -M_PI/3 : -M_PI/2 ); - queuepoly(V2, shPBody, darkena(col, 0, 0xC0)); - queuepoly(V2, m == moFlailer ? shArmor : shHood, darkena(col, 1, 0XFF)); + V2 = V * spin((where && where->type == 6) ? -M_PI/3 : -M_PI/2 ); + transmatrix Vh = mmscale(V2, geom3::HEAD); + transmatrix Vb = mmscale(V2, geom3::BODY); + otherbodyparts(V2, darkena(col, 1, 0xFF), m, footphase); + ShadowV(V2, shPBody); + queuepoly(Vb, shPBody, darkena(col, 0, 0xC0)); + queuepoly(Vh, m == moFlailer ? shArmor : shHood, darkena(col, 1, 0XFF)); if(m == moMiner) - queuepoly(V2, shPickAxe, darkena(0xC0C0C0, 0, 0XFF)); + queuepoly(Vb, shPickAxe, darkena(0xC0C0C0, 0, 0XFF)); if(m == moLancer) - queuepoly(V2, shPike, darkena(col, 0, 0XFF)); + queuepoly(Vb, shPike, darkena(col, 0, 0XFF)); if(m == moFlailer) { - queuepoly(V2, shFlailBall, darkena(col, 0, 0XFF)); - queuepoly(V2, shFlailChain, darkena(col, 1, 0XFF)); - queuepoly(V2, shFlailTrunk, darkena(col, 0, 0XFF)); + queuepoly(Vb, shFlailBall, darkena(col, 0, 0XFF)); + queuepoly(Vb, shFlailChain, darkena(col, 1, 0XFF)); + queuepoly(Vb, shFlailTrunk, darkena(col, 0, 0XFF)); } } else if(m == moTroll) { - queuepoly(V, shYeti, darkena(col, 0, 0xC0)); - queuepoly(V, shPHead, darkena(col, 1, 0XFF)); - queuepoly(V, shPFace, darkena(col, 2, 0XFF)); + otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase); + ShadowV(V, shYeti); + queuepoly(VBODY, shYeti, darkena(col, 0, 0xC0)); + queuepoly(VHEAD, shPHead, darkena(col, 1, 0XFF)); + queuepoly(VHEAD, shPFace, darkena(col, 2, 0XFF)); } else if(m == moFjordTroll || m == moForestTroll || m == moStormTroll) { - queuepoly(V, shYeti, darkena(col, 0, 0xC0)); - queuepoly(V, shPHead, darkena(col, 1, 0XFF)); - queuepoly(V, shPFace, darkena(col, 2, 0XFF)); + otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase); + ShadowV(V, shYeti); + queuepoly(VBODY, shYeti, darkena(col, 0, 0xC0)); + queuepoly(VHEAD, shPHead, darkena(col, 1, 0XFF)); + queuepoly(VHEAD, shPFace, darkena(col, 2, 0XFF)); } else if(m == moDarkTroll) { - queuepoly(V, shYeti, darkena(col, 0, 0xC0)); - queuepoly(V, shPHead, darkena(col, 1, 0XFF)); - queuepoly(V, shPFace, 0xFFFFFF80); + otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase); + ShadowV(V, shYeti); + queuepoly(VBODY, shYeti, darkena(col, 0, 0xC0)); + queuepoly(VHEAD, shPHead, darkena(col, 1, 0XFF)); + queuepoly(VHEAD, shPFace, 0xFFFFFF80); } else if(m == moRedTroll) { - queuepoly(V, shYeti, darkena(col, 0, 0xC0)); - queuepoly(V, shPHead, darkena(0xFF8000, 0, 0XFF)); - queuepoly(V, shPFace, 0xFFFFFF80); + otherbodyparts(V, darkena(col, 0, 0xFF), m, footphase); + ShadowV(V, shYeti); + queuepoly(VBODY, shYeti, darkena(col, 0, 0xC0)); + queuepoly(VHEAD, shPHead, darkena(0xFF8000, 0, 0XFF)); + queuepoly(VHEAD, shPFace, 0xFFFFFF80); } else if(m == moEarthElemental) { - queuepoly(V, shYeti, darkena(col, 0, 0xC0)); - queuepoly(V, shPHead, darkena(col, 0, 0XFF)); - queuepoly(V, shPFace, 0xF0000080); + otherbodyparts(V, darkena(col, 1, 0xFF), m, footphase); + ShadowV(V, shYeti); + queuepoly(VBODY, shYeti, darkena(col, 0, 0xC0)); + queuepoly(VHEAD, shPHead, darkena(col, 0, 0XFF)); + queuepoly(VHEAD, shPFace, 0xF0000080); } else if(m == moWaterElemental) { - queuepoly(V, shWaterElemental, watercolor(0)); - queuepoly(V, shFemaleHair, watercolor(100)); - queuepoly(V, shPFace, watercolor(200)); + otherbodyparts(V, watercolor(50), m, footphase); + ShadowV(V, shWaterElemental); + queuepoly(VBODY, shWaterElemental, watercolor(0)); + queuepoly(VHEAD, shFemaleHair, watercolor(100)); + queuepoly(VHEAD, shPFace, watercolor(200)); } else if(m == moFireElemental) { - queuepoly(V, shWaterElemental, darkena(firecolor(0), 0, 0xFF)); - queuepoly(V, shFemaleHair, darkena(firecolor(100), 0, 0xFF)); - queuepoly(V, shPFace, darkena(firecolor(200), 0, 0xFF)); + otherbodyparts(V, darkena(firecolor(50), 0, 0xFF), m, footphase); + ShadowV(V, shWaterElemental); + queuepoly(VBODY, shWaterElemental, darkena(firecolor(0), 0, 0xFF)); + queuepoly(VHEAD, shFemaleHair, darkena(firecolor(100), 0, 0xFF)); + queuepoly(VHEAD, shPFace, darkena(firecolor(200), 0, 0xFF)); } else if(m == moAirElemental) { - queuepoly(V, shWaterElemental, darkena(col, 0, 0x80)); - queuepoly(V, shFemaleHair, darkena(col, 0, 0x80)); - queuepoly(V, shPFace, darkena(col, 0, 0x80)); + otherbodyparts(V, darkena(col, 0, 0x40), m, footphase); + ShadowV(V, shWaterElemental); + queuepoly(VBODY, shWaterElemental, darkena(col, 0, 0x80)); + queuepoly(VHEAD, shFemaleHair, darkena(col, 0, 0x80)); + queuepoly(VHEAD, shPFace, darkena(col, 0, 0x80)); } else if(xch == 'd' || xch == 'D') { - queuepoly(V, shPBody, darkena(col, 1, 0xC0)); + otherbodyparts(V, darkena(col, 0, 0xC0), m, footphase); + queuepoly(VBODY, shPBody, darkena(col, 1, 0xC0)); + ShadowV(V, shPBody); int acol = col; if(xch == 'D') acol = 0xD0D0D0; - queuepoly(V, shDemon, darkena(acol, 0, 0xFF)); + queuepoly(VHEAD, shDemon, darkena(acol, 0, 0xFF)); } else if(isMetalBeast(m)) { - queuepoly(V, shTrylobite, darkena(col, 1, 0xC0)); + ShadowV(V, shTrylobite); + if(!mmspatial) + queuepoly(VABODY, shTrylobite, darkena(col, 1, 0xC0)); + else { + queuepoly(VABODY, shTrylobiteBody, darkena(col, 1, 0xFF)); + animallegs(VALEGS, moMetalBeast, darkena(col, 1, 0xFF), footphase); + } int acol = col; - queuepoly(V, shTrylobiteHead, darkena(acol, 0, 0xFF)); + queuepoly(VAHEAD, shTrylobiteHead, darkena(acol, 0, 0xFF)); } else if(m == moEvilGolem) { - queuepoly(V, shPBody, darkena(col, 0, 0XC0)); - queuepoly(V, shGolemhead, darkena(col, 1, 0XFF)); + otherbodyparts(V, darkena(col, 2, 0xC0), m, footphase); + ShadowV(V, shPBody); + queuepoly(VBODY, shPBody, darkena(col, 0, 0XC0)); + queuepoly(VHEAD, shGolemhead, darkena(col, 1, 0XFF)); } else if(isWitch(m)) { + otherbodyparts(V, darkena(col, 1, 0xFF), m, footphase); int c = 0xFF; if(m == moWitchGhost) c = 0x85 + 120 * sin(ticks / 160.0); - if(m == moWitchWinter) drawWinter(V, 42); + if(m == moWitchWinter) drawWinter(V, 0); if(m == moWitchFlash) drawFlash(V); if(m == moWitchSpeed) drawSpeed(V); if(m == moWitchFire) col = firecolor(0); - queuepoly(V, shFemaleBody, darkena(col, 0, c)); + ShadowV(V, shFemaleBody); + queuepoly(VBODY, shFemaleBody, darkena(col, 0, c)); // queuepoly(cV2, ct, shPSword, darkena(col, 0, 0XFF)); // queuepoly(V, shHood, darkena(col, 0, 0XC0)); if(m == moWitchFire) col = firecolor(100); - queuepoly(V, shWitchHair, darkena(col, 1, c)); + queuepoly(VHEAD, shWitchHair, darkena(col, 1, c)); if(m == moWitchFire) col = firecolor(200); - queuepoly(V, shPFace, darkena(col, 0, c)); + queuepoly(VHEAD, shPFace, darkena(col, 0, c)); if(m == moWitchFire) col = firecolor(300); - queuepoly(V, shWitchDress, darkena(col, 1, 0XC0)); + queuepoly(VBODY, shWitchDress, darkena(col, 1, 0XC0)); } else return true; @@ -1819,20 +2415,12 @@ bool drawMonsterType(eMonster m, cell *where, const transmatrix& V, int col) { return false; } -#define OUTLINE_NONE 0x000000FF -#define OUTLINE_FRIEND 0x00FF00FF -#define OUTLINE_ENEMY 0xFF0000FF -#define OUTLINE_TREASURE 0xFFFF00FF -#define OUTLINE_ORB 0xFF8000FF -#define OUTLINE_OTHER 0xFFFFFFFF -#define OUTLINE_DEAD 0x800000FF - -bool drawMonsterTypeDH(eMonster m, cell *where, const transmatrix& V, int col, bool dh) { +bool drawMonsterTypeDH(eMonster m, cell *where, const transmatrix& V, int col, bool dh, ld footphase) { if(dh) { poly_outline = OUTLINE_DEAD; darken++; } - bool b = drawMonsterType(m,where,V,col); + bool b = drawMonsterType(m,where,V,col, footphase); if(dh) { poly_outline = OUTLINE_NONE; darken--; @@ -1840,207 +2428,344 @@ bool drawMonsterTypeDH(eMonster m, cell *where, const transmatrix& V, int col, b return b; } -bool drawMonster(const transmatrix& V, int ct, cell *c, int col) { +transmatrix playerV; - if(shmup::on) shmup::drawMonster(V, c); +bool applyAnimation(cell *c, transmatrix& V, double& footphase, int layer) { + if(!animations[layer].count(c)) return false; + animation& a = animations[layer][c]; + + int td = ticks - a.ltick; + ld aspd = td / 1000.0 * exp(vid.mspeed); + ld R = hdist0(tC0(a.wherenow)); + aspd *= (1+R+(shmup::on?1:0)); + if(R < aspd || isnan(R) || isnan(aspd) || R > 10) { + animations[layer].erase(c); + return false; + } + else { + a.wherenow = a.wherenow * rspintox(tC0(inverse(a.wherenow))); + a.wherenow = a.wherenow * xpush(aspd); + fixmatrix(a.wherenow); + a.footphase += aspd; + footphase = a.footphase; + V = V * a.wherenow; + a.ltick = ticks; + return true; + } + } + +double chainAngle(cell *c, transmatrix& V, cell *c2, double dft) { + if(!gmatrix0.count(c2)) return dft; + hyperpoint h = C0; + if(animations[LAYER_BIG].count(c2)) h = animations[LAYER_BIG][c2].wherenow * h; + h = inverse(V) * gmatrix0[c2] * h; + return atan2(h[1], h[0]); + } + +// equivalent to V = V * spin(-chainAngle(c,V,c2,dft)); +bool chainAnimation(cell *c, transmatrix& V, cell *c2, int i, int b) { + if(!gmatrix0.count(c2)) { + V = V * ddspin(c,i,b); + return false; + } + hyperpoint h = C0; + if(animations[LAYER_BIG].count(c2)) h = animations[LAYER_BIG][c2].wherenow * h; + h = inverse(V) * gmatrix0[c2] * h; + V = V * rspintox(h); + return true; + } + +// push down the queue after q-th element, `down` absolute units down, +// based on cell c and transmatrix V +// do change the zoom factor? do change the priorities? + +int cellcolor(cell *c) { + if(isPlayerOn(c) || isFriendly(c)) return OUTLINE_FRIEND; + if(noHighlight(c->monst)) return OUTLINE_NONE; + if(c->monst) return OUTLINE_ENEMY; + + if(c->wall == waMirror) return c->land == laMirror ? OUTLINE_TREASURE : OUTLINE_ORB; + + if(c->item) { + int k = itemclass(c->item); + if(k == IC_TREASURE) + return OUTLINE_TREASURE; + else if(k == IC_ORB) + return OUTLINE_ORB; + else + return OUTLINE_OTHER; + } + + return OUTLINE_NONE; + } + +bool drawMonster(const transmatrix& Vparam, int ct, cell *c, int col) { + bool darkhistory = conformal::includeHistory && eq(c->aitmp, sval); if(doHighlight()) poly_outline = - (c == cwt.c || isFriendly(c)) ? OUTLINE_FRIEND : OUTLINE_ENEMY; + (isPlayerOn(c) || isFriendly(c)) ? OUTLINE_FRIEND : + noHighlight(c->monst) ? OUTLINE_NONE : + OUTLINE_ENEMY; + + bool nospins = false, nospinb = false; + double footphaseb = 0, footphase = 0; + + transmatrix Vs = Vparam; nospins = applyAnimation(c, Vs, footphase, LAYER_SMALL); + transmatrix Vb = Vparam; nospinb = applyAnimation(c, Vb, footphaseb, LAYER_BIG); +// nospin = true; eMonster m = c->monst; - if(c == cwt.c && !shmup::on && mapeditor::drawplayer) { - transmatrix cV2 = cwtV; - // if(flipplayer) cV2 = cV2 * spin(M_PI); - if(flipplayer) cV2 = cV2 * spin(M_PI); + if(isIvy(c) || isWorm(c) || isMutantIvy(c) || c->monst == moFriendlyIvy) { - drawPlayerEffects(V, c, true); - if(vid.monmode > 1) { - drawMonsterType(moPlayer, c, cV2, col); - } - else return true; - } - - if(isIvy(c) || isWorm(c) || isMutantIvy(c)) { - - transmatrix V2 = V; if(isDragon(c->monst) && c->stuntime == 0) col = 0xFF6000; + transmatrix Vb0 = Vb; if(c->mondir != NODIR) { - int hdir = displaydir(c, c->mondir); - if(vid.monmode > 1) { - V2 = V2 * spin(hdir * M_PI / 42); + if(mmmon) { + if(nospinb) + chainAnimation(c, Vb, c->mov[c->mondir], c->mondir, 0); + else + Vb = Vb * ddspin(c, c->mondir); -#ifndef MOBILE - if(c == mapeditor::drawcell) mapeditor::drawtrans = V2; +#ifndef NOEDIT + if(c == mapeditor::drawcell) mapeditor::drawtrans = Vb; #endif - if(drawUserShape(V2, 1, c->monst, (col << 8) + 0xFF)) return false; + if(drawUserShape(Vb, 1, c->monst, (col << 8) + 0xFF)) return false; - if(isIvy(c) || isMutantIvy(c)) - queuepoly(V2, shIBranch, (col << 8) + 0xFF); + if(isIvy(c) || isMutantIvy(c) || c->monst == moFriendlyIvy) + queuepoly(Vb, shIBranch, (col << 8) + 0xFF); else if(c->monst < moTentacle) { - queuepoly(V2, shTentacleX, 0xFF); - queuepoly(V2, shTentacle, (col << 8) + 0xFF); + ShadowV(Vb, shTentacleX, PPR_GIANTSHADOW); + queuepoly(mmscale(Vb, geom3::ABODY), shTentacleX, 0xFF); + queuepoly(mmscale(Vb, geom3::ABODY), shTentacle, (col << 8) + 0xFF); } else if(c->monst == moDragonHead || c->monst == moDragonTail) { char part = dragon::bodypart(c, dragon::findhead(c)); - if(part != '2') queuepoly(V2, shDragonSegment, darkena(col, 0, 0xFF)); + if(part != '2') { + queuepoly(mmscale(Vb, geom3::ABODY), shDragonSegment, darkena(col, 0, 0xFF)); + ShadowV(Vb, shDragonSegment, PPR_GIANTSHADOW); + } } else { if(c->monst == moTentacleGhost) { - hyperpoint V0 = conformal::on ? V*C0 : inverse(cwtV) * V * C0; + hyperpoint V0 = conformal::on ? tC0(Vs) : inverse(cwtV) * tC0(Vs); hyperpoint V1 = spintox(V0) * V0; - transmatrix VL = cwtV * rspintox(V0) * rpushxto0(V1) * spin(M_PI); - drawMonsterType(moGhost, c, VL, darkena(col, 0, 0xFF)); + Vs = cwtV * rspintox(V0) * rpushxto0(V1) * pispin; + drawMonsterType(moGhost, c, Vs, darkena(col, 0, 0xFF), footphase); col = minf[moTentacletail].color; } - queuepoly(V2, shTentacleX, 0xFFFFFFFF); - queuepoly(V2, shTentacle, (col << 8) + 0xFF); + queuepoly(mmscale(Vb, geom3::ABODY), shTentacleX, 0xFFFFFFFF); + queuepoly(mmscale(Vb, geom3::ABODY), shTentacle, (col << 8) + 0xFF); + ShadowV(Vb, shTentacleX, PPR_GIANTSHADOW); } } - else for(int u=-1; u<=1; u++) - queueline(V*ddi(hdir+21, u*crossf/5)*C0, V*ddi(hdir, crossf)*ddi(hdir+21, u*crossf/5)*C0, 0x606020 >> darken); + else { + int hdir = displaydir(c, c->mondir); + int col = darkena(0x606020, 0, 0xFF); + for(int u=-1; u<=1; u++) + queueline(Vparam*ddi0(hdir+S21, u*crossf/5), Vparam*ddi(hdir, crossf)*ddi0(hdir+S21, u*crossf/5), col, 2); + } } - if(vid.monmode > 1) { - if(isIvy(c) || isMutantIvy(c)) - queuepoly(V, shILeaf[ct-6], darkena(col, 0, 0xFF)); + if(mmmon) { + if(isIvy(c) || isMutantIvy(c) || c->monst == moFriendlyIvy) { + queuepoly(mmscale(Vb, geom3::ABODY), shILeaf[K(c)], darkena(col, 0, 0xFF)); + ShadowV(Vb, shILeaf[K(c)], PPR_GIANTSHADOW); + } else if(m == moWorm || m == moWormwait || m == moHexSnake) { - queuepoly(V2 * spin(M_PI), shWormHead, darkena(col, 0, 0xFF)); - queuepoly(V2 * spin(M_PI), shDragonEyes, 0xFF); + Vb = Vb * pispin; + transmatrix Vbh = mmscale(Vb, geom3::AHEAD); + queuepoly(Vbh, shWormHead, darkena(col, 0, 0xFF)); + queuepolyat(Vbh, shEyes, 0xFF, PPR_ONTENTACLE_EYES); + ShadowV(Vb, shWormHead, PPR_GIANTSHADOW); } else if(m == moDragonHead) { - queuepoly(V2, shDragonHead, darkena(col, c->hitpoints?0:1, 0xFF)); - queuepoly(V2/* * spin(M_PI) */, shDragonEyes, 0xFF); + transmatrix Vbh = mmscale(Vb, geom3::AHEAD); + ShadowV(Vb, shDragonHead, PPR_GIANTSHADOW); + queuepoly(Vbh, shDragonHead, darkena(col, c->hitpoints?0:1, 0xFF)); + queuepolyat(Vbh/* * pispin */, shEyes, 0xFF, PPR_ONTENTACLE_EYES); int noscolor = (c->hitpoints == 1 && c->stuntime ==1) ? 0xFF0000FF : 0xFF; - queuepoly(V2/* * spin(M_PI) */, shDragonNostril, noscolor); - queuepoly(V2 * Mirror, shDragonNostril, noscolor); + queuepoly(Vbh, shDragonNostril, noscolor); + queuepoly(Vbh * Mirror, shDragonNostril, noscolor); + } + else if(m == moTentacle || m == moTentaclewait || m == moTentacleEscaping) { + Vb = Vb * pispin; + transmatrix Vbh = mmscale(Vb, geom3::AHEAD); + queuepoly(Vbh, shTentHead, darkena(col, 0, 0xFF)); + ShadowV(Vb, shTentHead, PPR_GIANTSHADOW); } - else if(m == moTentacle || m == moTentaclewait || m == moTentacleEscaping) - queuepoly(V2 * spin(M_PI), shTentHead, darkena(col, 0, 0xFF)); else if(m == moDragonTail) { cell *c2 = NULL; for(int i=0; itype; i++) - if(c->mov[i] && isDragon(c->mov[i]->monst) && c->mov[i]->mondir == c->spn[i]) + if(c->mov[i] && isDragon(c->mov[i]->monst) && c->mov[i]->mondir == c->spn(i)) c2 = c->mov[i]; int nd = neighborId(c, c2); char part = dragon::bodypart(c, dragon::findhead(c)); if(part == 't') { - int hdir = displaydir(c, nd); - V2 = V * spin(hdir * M_PI / 42 + M_PI); - queuepoly(V2, shDragonTail, darkena(col, c->hitpoints?0:1, 0xFF)); + if(nospinb) { + chainAnimation(c, Vb, c2, nd, 0); + Vb = Vb * pispin; + } + else { + Vb = Vb0 * ddspin(c, nd, S42); + } + transmatrix Vbb = mmscale(Vb, geom3::ABODY); + queuepoly(Vbb, shDragonTail, darkena(col, c->hitpoints?0:1, 0xFF)); + ShadowV(Vb, shDragonTail, PPR_GIANTSHADOW); } else if(true) { - int hdir0 = displaydir(c, nd) + 42; - int hdir1 = displaydir(c, c->mondir); - while(hdir1 > hdir0 + 42) hdir1 -= 84; - while(hdir1 < hdir0 - 42) hdir1 += 84; - V2 = V * spin((hdir0 + hdir1)/2 * M_PI / 42 + M_PI); - if(part == 'l' || part == '2') - queuepoly(V2, shDragonLegs, darkena(col, c->hitpoints?0:1, 0xFF)); - queuepoly(V2, shDragonWings, darkena(col, c->hitpoints?0:1, 0xFF)); + if(nospinb) { + chainAnimation(c, Vb, c2, nd, 0); + Vb = Vb * pispin; + double ang = chainAngle(c, Vb, c->mov[c->mondir], (displaydir(c, c->mondir) - displaydir(c, nd)) * M_PI / S42); + ang /= 2; + Vb = Vb * spin(M_PI-ang); + } + else { + int hdir0 = displaydir(c, nd) + S42; + int hdir1 = displaydir(c, c->mondir); + while(hdir1 > hdir0 + S42) hdir1 -= S84; + while(hdir1 < hdir0 - S42) hdir1 += S84; + Vb = Vb0 * spin((hdir0 + hdir1)/2 * M_PI / S42 + M_PI); + } + transmatrix Vbb = mmscale(Vb, geom3::ABODY); + if(part == 'l' || part == '2') { + queuepoly(Vbb, shDragonLegs, darkena(col, c->hitpoints?0:1, 0xFF)); + } + queuepoly(Vbb, shDragonWings, darkena(col, c->hitpoints?0:1, 0xFF)); } } - else - queuepoly(V2, shJoint, darkena(col, 0, 0xFF)); + else if(!(c->mondir == NODIR && (c->monst == moTentacletail || (c->monst == moWormtail && wormpos(c) < WORMLENGTH)))) + queuepoly(Vb, shJoint, darkena(col, 0, 0xFF)); } - return vid.monmode < 2; + if(!mmmon) return true; } else if(isMimic(c)) { - int hdir = displaydir(c, c->mondir); + if(!nospins) + Vs = Vs * ddspin(c, c->mondir, flipplayer ? S42 : 0); - transmatrix V2 = V * spin(M_PI*hdir/42); - if(c->monst == moMirror) V2 = V2 * Mirror; - - if(flipplayer) V2 = V2 * spin(M_PI); + if(c->monst == moMirror) Vs = Vs * Mirror; + + multi::cpid = c->hitpoints; - if(vid.monmode > 1) { - drawMonsterType(c->monst, c, V2, col); - drawPlayerEffects(V2, c, false); + if(mmmon) { + drawMonsterType(c->monst, c, Vs, col, footphase); + drawPlayerEffects(Vs, c, false); } - if(flipplayer) V2 = V2 * spin(M_PI); + if(flipplayer) Vs = Vs * pispin; - if(!outofmap(mouseh)) { + if(!outofmap(mouseh) && !nospins) { // transmatrix invxy = Id; invxy[0][0] = invxy[1][1] = -1; - hyperpoint P2 = V2 * inverse(cwtV) * mouseh; - int xc, yc, sc; - getcoord(P2, xc, yc, sc); - queuechr(xc, yc, sc, 10, 'x', 0xFF00); + hyperpoint P2 = Vs * inverse(cwtV) * mouseh; + queuechr(P2, 10, 'x', 0xFF00); } - return vid.monmode < 2; + return !mmmon; } - else if(c->monst && vid.monmode < 2) return true; + else if(c->monst && !mmmon) return true; // illusions face randomly else if(c->monst == moIllusion) { - drawMonsterType(c->monst, c, V, col); - drawPlayerEffects(V, c, false); + multi::cpid = 0; + drawMonsterType(c->monst, c, Vs, col, footphase); + drawPlayerEffects(Vs, c, false); } // wolves face the heat else if(c->monst == moWolf && c->cpdist > 1) { - int d = 0; - double bheat = -999; - for(int i=0; itype; i++) if(c->mov[i] && HEAT(c->mov[i]) > bheat) { - bheat = HEAT(c->mov[i]); - d = i; + if(!nospins) { + int d = 0; + double bheat = -999; + for(int i=0; itype; i++) if(c->mov[i] && HEAT(c->mov[i]) > bheat) { + bheat = HEAT(c->mov[i]); + d = i; + } + Vs = Vs * ddspin(c, d); } - int hdir = displaydir(c, d); - transmatrix V2 = V * spin(hdir * M_PI / 42); - return drawMonsterTypeDH(m, c, V2, col, darkhistory); + return drawMonsterTypeDH(m, c, Vs, col, darkhistory, footphase); } // golems, knights, and hyperbugs don't face the player (mondir-controlled) // also whatever in the lineview mode - else if(isFriendly(c) || isBug(c) || (c->monst && conformal::on)) { - int hdir = displaydir(c, c->mondir) + 42; - transmatrix V2 = V * spin(hdir * M_PI / 42); - if(!isBug(c)) drawPlayerEffects(V2, c, false); - return drawMonsterTypeDH(m, c, V2, col, darkhistory); + else if(isFriendly(c) || isBug(c) || (c->monst && conformal::on) || c->monst == moKrakenH || (isBull(c->monst) && c->mondir != NODIR) || c->monst == moButterfly) { + if(c->monst == moKrakenH) Vs = Vb, nospins = nospinb; + if(!nospins) Vs = Vs * ddspin(c, c->mondir, S42); + if(isFriendly(c)) drawPlayerEffects(Vs, c, false); + return drawMonsterTypeDH(m, c, Vs, col, darkhistory, footphase); + } + + else if(c->monst == moKrakenT) { + if(c->hitpoints == 0) col = 0x404040; + if(nospinb) { + chainAnimation(c, Vb, c->mov[c->mondir], c->mondir, 0); + Vb = Vb * pispin; + } + else Vb = Vb * ddspin(c, c->mondir, S42); + if(c->type != 6) Vb = Vb * xpush(hexhexdist - hcrossf); + return drawMonsterTypeDH(m, c, Vb, col, darkhistory, footphase); } else if(c->monst) { // other monsters 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; + if(!nospins) { + hyperpoint V0 = inverse(cwtV) * tC0(Vs); hyperpoint V1 = spintox(V0) * V0; - VL = cwtV * rspintox(V0) * rpushxto0(V1) * spin(M_PI); + Vs = cwtV * rspintox(V0) * rpushxto0(V1) * pispin; } - return drawMonsterTypeDH(m, c, VL, col, darkhistory); + if(c->monst == moShadow) + multi::cpid = c->hitpoints; + + return drawMonsterTypeDH(m, c, Vs, col, darkhistory, footphase); } + for(int i=0; i 1 ? multi::flipped[i] : flipplayer) Vs = Vs * pispin; + } + shmup::cpid = i; + + drawPlayerEffects(Vs, c, true); + if(!mmmon) return true; + + if(isWorm(m)) { + ld depth = geom3::factor_to_lev(wormhead(c) == c ? geom3::AHEAD : geom3::ABODY); + footphase = 0; + int q = ptds.size(); + drawMonsterType(moPlayer, c, Vs, col, footphase); + pushdown(c, q, Vs, -depth, true, false); + } + + else if(mmmon) + drawMonsterType(moPlayer, c, Vs, col, footphase); + } + return false; } bool showPirateX; cell *keycell, *pirateTreasureSeek, *pirateTreasureFound; -transmatrix pirateCoords; +hyperpoint pirateCoords; double downspin; cell *straightDownSeek; @@ -2048,10 +2773,11 @@ cell *straightDownSeek; int keycelldist; void drawCircle(int x, int y, int size, int color) { + if(size < 0) size = -size; #ifdef GL if(vid.usingGL) { qglcoords = 0; - glcolor(color); + glcolor2(color); x -= vid.xcenter; y -= vid.ycenter; int pts = size * 4; if(pts > 1500) pts = 1500; @@ -2062,9 +2788,9 @@ void drawCircle(int x, int y, int size, int color) { glcoords[r][1] = y + size * cos(rr); glcoords[r][2] = vid.scrdist; } - - glVertexPointer(3, GL_FLOAT, 0, glcoords); - glEnableClientState(GL_VERTEX_ARRAY); + + qglcoords = pts; + activateGlcoords(); glDrawArrays(GL_LINE_LOOP, 0, pts); return; } @@ -2074,7 +2800,7 @@ void drawCircle(int x, int y, int size, int color) { gdpush(4); gdpush(color); gdpush(x); gdpush(y); gdpush(size); #else #ifdef GFX - (vid.usingAA?aacircleColor:circleColor) (s, x, y, size, (color << 8) | 0x80); + (vid.usingAA?aacircleColor:circleColor) (s, x, y, size, color); #else int pts = size * 4; if(pts > 1500) pts = 1500; @@ -2087,7 +2813,7 @@ void drawCircle(int x, int y, int size, int color) { int fnt[100][7]; bool bugsNearby(cell *c, int dist = 2) { - if(!havebugs) return false; + if(!(havewhat&HF_BUG)) return false; if(isBug(c)) return true; if(dist) for(int t=0; ttype; t++) if(c->mov[t] && bugsNearby(c->mov[t], dist-1)) return true; return false; @@ -2117,11 +2843,8 @@ int countMinesAround(cell *c) { return mines; } -transmatrix movecell[7], curcell; - transmatrix applyPatterndir(cell *c, char patt = mapeditor::whichPattern) { - int hdir = displaydir(c, mapeditor::patterndir(c, patt)); - transmatrix V = spin((42+hdir) * M_PI / 42); + transmatrix V = ddspin(c, mapeditor::patterndir(c, patt), S42); if(mapeditor::reflectPatternAt(c, patt)) return V * Mirror; @@ -2130,8 +2853,7 @@ transmatrix applyPatterndir(cell *c, char patt = mapeditor::whichPattern) { } transmatrix applyDowndir(cell *c, cellfunction *cf) { - int hdir = displaydir(c, mapeditor::downdir(c, cf)); - return spin((42+hdir) * M_PI / 42); + return ddspin(c, mapeditor::downdir(c, cf), S42); } void drawTowerFloor(const transmatrix& V, cell *c, int col, cellfunction *cf = coastvalEdge) { @@ -2155,15 +2877,15 @@ void drawTowerFloor(const transmatrix& V, cell *c, int col, cellfunction *cf = c } if(j >= 0) - queuepoly(V * applyDowndir(c, cf), shTower[j], col); + qfloor(c, V, applyDowndir(c, cf), shTower[j], col); else if(c->wall != waLadder) - queuepoly(V, shMFloor[c->type-6], col); + qfloor(c, V, shMFloor[K(c)], col); } void drawZebraFloor(const transmatrix& V, cell *c, int col) { - if(euclid) { queuepoly(V, shTower[10], col); return; } - + if(euclid) { qfloor(c, V, shTower[10], col); return; } + int i = zebra40(c); i &= ~3; @@ -2175,11 +2897,75 @@ void drawZebraFloor(const transmatrix& V, cell *c, int col) { else if(i >= 28 && i < 40) j = 3; else j = 0; - queuepoly(V * applyPatterndir(c, 'z'), shZebra[j], col); + qfloor(c, V, applyPatterndir(c, 'z'), shZebra[j], col); } -#define ECT (euclid?2:ct-6) - +void qplainfloor(cell *c, bool warp, const transmatrix &V, int col); + +void drawReptileFloor(const transmatrix& V, cell *c, int col, bool usefloor) { + + int i = zebra40(c); + i &= ~3; + + int j; + + if(!wmescher) j = 4; + else if(purehepta) j = 0; + else if(i < 4) j = 0; + else if(i >=4 && i < 16) j = 1; + else if(i >= 16 && i < 28) j = 2; + else if(i >= 28 && i < 40) j = 3; + else j = 4; + + transmatrix V2 = V * applyPatterndir(c, 'z'); + + if(wmescher) { + if(usefloor) + qfloor(c, V, applyPatterndir(c, 'z'), shReptile[j][0], darkena(col, 0, 0xFF)); + else + queuepoly(V2, shReptile[j][0], darkena(col, 0, 0xFF)); + } + else + qplainfloor(c, isWarped(c), V, darkena(col, 0, 0xFF)); + + if(usefloor && chasmg == 2) return; + + int dcol = 0; + + int ecol = -1; + + if(isReptile(c->wall)) { + unsigned char wp = c->wparam; + if(wp == 1) + ecol = 0xFFFF00; + else if(wp <= 5) + ecol = 0xFF0000; + else + ecol = 0; + if(ecol) ecol = gradient(0, ecol, -1, sin(M_PI / 100 * ticks), 1); + } + + if(ecol == -1 || ecol == 0) dcol = darkena(col, 1, 0xFF); + else dcol = darkena(ecol, 0, 0x80); + + dynamicval p(poly_outline, + doHighlight() && ecol != -1 && ecol != 0 ? OUTLINE_ENEMY : OUTLINE_NONE); + + if(!chasmg) { + if(wmescher) + queuepoly(V2, shReptile[j][1], dcol); + else + queuepoly(V2, shMFloor[c->type!=6], dcol); + } + + if(ecol != -1) { + queuepoly(V2, shReptile[j][2], (ecol << 8) + 0xFF); + queuepoly(V2, shReptile[j][3], (ecol << 8) + 0xFF); + } + } + +#define ECT (euclid?2:ct6) + void drawEmeraldFloor(const transmatrix& V, cell *c, int col) { int j = -1; @@ -2194,9 +2980,9 @@ void drawEmeraldFloor(const transmatrix& V, cell *c, int col) { } if(j >= 0) - queuepoly(V * applyPatterndir(c, 'f'), shEmeraldFloor[j], col); + qfloor(c, V, applyPatterndir(c, 'f'), shEmeraldFloor[j], col); else - queuepoly(V, shCaveFloor[euclid?2:c->type-6], col); + qfloor(c, V, shCaveFloor[euclid?2:K(c)], col); } double fanframe; @@ -2222,7 +3008,735 @@ void viewBuggyCells(cell *c, transmatrix V) { } } -void drawcell(cell *c, const transmatrix& V, int spinv) { +transmatrix pushone() { return euclid ? eupush(1, 0) : xpush(sphere?.5 : 1); } + +void drawMovementArrows(cell *c, transmatrix V) { + + for(int d=0; d<8; d++) { + + movedir md = vectodir(spin(-d * M_PI/4) * tC0(pushone())); + int u = md.d; + cellwalker xc = cwt; cwspin(xc, u); cwstep(xc); + if(xc.c == c) { + transmatrix fixrot = rgpushxto0(tC0(V)); + // make it more transparent + int col = getcs().uicolor; + col -= (col & 0xFF) >> 1; + poly_outline = OUTLINE_NONE; + queuepoly(fixrot * spin(-d * M_PI/4 + (sphere && vid.alpha>1?M_PI:0))/* * eupush(1,0)*/, shArrow, col); + + if(c->type != 6 && (isStunnable(c->monst) || c->wall == waThumperOn)) { + transmatrix Centered = rgpushxto0(tC0(cwtV)); + int sd = md.subdir; + if(sphere) sd = -sd; + queuepoly(inverse(Centered) * rgpushxto0(Centered * tC0(V)) * rspintox(Centered*tC0(V)) * spin(-sd * M_PI/S7) * xpush(0.2), shArrow, col); + } + else break; + } + } + } + +transmatrix screenpos(ld x, ld y) { + transmatrix V = Id; + V[0][2] += (x - vid.xcenter) / vid.radius * (1+vid.alphax); + V[1][2] += (y - vid.ycenter) / vid.radius * (1+vid.alphax); + // V[2][0] -= (x - vid.xcenter) / vid.radius * (1+vid.alphax); + // V[2][1] -= (y - vid.ycenter) / vid.radius * (1+vid.alphax); + return V; + } + +#define SKIPFAC .4 + +void drawMobileArrow(cell *c, transmatrix V) { + + // int col = getcs().uicolor; + // col -= (col & 0xFF) >> 1; + + int col = cellcolor(c); + if(col == OUTLINE_NONE) col = 0xC0C0C0FF; + col -= (col & 0xFF) >> 1; + + poly_outline = OUTLINE_NONE; + transmatrix m2 = Id; + ld scale = vid.mobilecompasssize / 15.; + m2[0][0] = scale; m2[1][1] = scale; m2[2][2] = 1; + + transmatrix Centered = rgpushxto0(tC0(cwtV)); + transmatrix t = inverse(Centered) * V; + double alpha = atan2(tC0(t)[1], tC0(t)[0]); + + using namespace shmupballs; + + double dx = xmove + rad*(1+SKIPFAC-.2)/2 * cos(alpha); + double dy = yb + rad*(1+SKIPFAC-.2)/2 * sin(alpha); + + queuepoly( + screenpos(dx, dy) * spin(-alpha) * m2, shArrow, col); + /* + if(c->type != 6 && (isStunnable(c->monst) || c->wall == waThumperOn)) { + transmatrix Centered = rgpushxto0(tC0(cwtV)); + int sd = md.subdir; + if(sphere) sd = -sd; + queuepoly(inverse(Centered) * rgpushxto0(Centered * tC0(V)) * rspintox(Centered*tC0(V)) * spin(-sd * M_PI/S7) * xpush(0.2), shArrow, col); + } + else break; + } */ + } + +int celldistAltPlus(cell *c) { return 1000000 + celldistAlt(c); } + +bool drawstaratvec(double dx, double dy) { + return dx*dx+dy*dy > .05; + } + +int reptilecolor(cell *c) { + int i = zebra40(c); + + if(!euclid) { + if(i >= 4 && i < 16) i = 0; + else if(i >= 16 && i < 28) i = 1; + else if(i >= 28 && i < 40) i = 2; + else i = 3; + } + + int fcoltab[4] = {0xe3bb97, 0xc2d1b0, 0xebe5cb, 0xA0A0A0}; + return fcoltab[i]; + } + +ld wavefun(ld x) { + return sin(x); + /* x /= (2*M_PI); + x -= (int) x; + if(x > .5) return (x-.5) * 2; + else return 0; */ + } + +void setcolors(cell *c, int& wcol, int &fcol) { + + wcol = fcol = winf[c->wall].color; + + // floor colors for all the lands + if(c->land == laKraken) fcol = 0x20A020; + if(c->land == laBurial) fcol = linf[laBurial].color; + if(c->land == laTrollheim) fcol = linf[c->land].color; + + if(c->land == laBarrier) fcol = linf[c->land].color; + if(c->land == laOceanWall) fcol = linf[c->land].color; + + if(c->land == laAlchemist) { + fcol = 0x202020; + if(c->item && !(conformal::includeHistory && eq(c->aitmp, sval))) + fcol = wcol = iinf[c->item].color; + } + + if(c->land == laBull) + fcol = 0x800080; + + if(c->land == laCA) + fcol = 0x404040; + + if(c->land == laReptile) { + fcol = reptilecolor(c); + } + + if(c->land == laCrossroads) fcol = (vid.goteyes2 ? 0xFF3030 : 0xFF0000); + if(c->land == laCrossroads2) fcol = linf[laCrossroads2].color; + if(c->land == laCrossroads3) fcol = linf[laCrossroads3].color; + if(c->land == laCrossroads4) fcol = linf[laCrossroads4].color; + if(c->land == laCrossroads5) fcol = linf[laCrossroads5].color; + if(isElemental(c->land)) fcol = linf[c->land].color; + if(c->land == laDesert) fcol = 0xEDC9AF; + if(c->land == laCaves) fcol = 0x202020; + if(c->land == laEmerald) fcol = 0x202020; + if(c->land == laDeadCaves) fcol = 0x202020; + if(c->land == laJungle) fcol = (vid.goteyes2 ? 0x408040 : 0x008000); + if(c->land == laMountain) { + if(euclid || c->master->alt) + fcol = celldistAlt(c) & 1 ? 0x604020 : 0x302010; + else fcol = 0; + if(c->wall == waPlatform) wcol = 0xF0F0A0; + } + if(c->land == laWineyard) fcol = 0x006000; + if(c->land == laMirror) fcol = 0x808080; + if(c->land == laMotion) fcol = 0xF0F000; + if(c->land == laGraveyard) fcol = 0x107010; + if(c->land == laDryForest) fcol = gradient(0x008000, 0x800000, 0, c->landparam, 10); + if(c->land == laRlyeh) fcol = (vid.goteyes2 ? 0x4080C0 : 0x004080); + if(c->land == laPower) fcol = linf[c->land].color; + if(c->land == laHell) fcol = (vid.goteyes2 ? 0xC03030 : 0xC00000); + if(c->land == laLivefjord) fcol = 0x306030; + if(c->land == laWildWest) fcol = linf[c->land].color; + if(c->land == laHalloween) fcol = linf[c->land].color; + if(c->land == laMinefield) fcol = 0x80A080; + if(c->land == laCaribbean) fcol = 0x006000; + if(c->land == laRose) fcol = linf[c->land].color; + if(c->land == laCanvas) fcol = c->landparam; + if(c->land == laRedRock) fcol = linf[c->land].color; + if(c->land == laDragon) fcol = linf[c->land].color; + if(c->land == laStorms) fcol = linf[c->land].color; + + if(c->land == laPalace) { + fcol = 0x806020; + if(c->wall == waClosedGate || c->wall == waOpenGate) + fcol = wcol; + } + + if(c->land == laElementalWall) + fcol = (linf[c->barleft].color>>1) + (linf[c->barright].color>>1); + + if(c->land == laZebra) { + fcol = 0xE0E0E0; + if(c->wall == waTrapdoor) fcol = 0x808080; + } + + if(c->land == laCaribbean && (c->wall == waCIsland || c->wall == waCIsland2)) + fcol = wcol = winf[c->wall].color; + + if(isHive(c->land)) { + fcol = linf[c->land].color; + if(c->wall == waWaxWall) wcol = c->landparam; + if(items[itOrbInvis] && c->wall == waNone && c->landparam) + fcol = gradient(fcol, 0xFF0000, 0, c->landparam, 100); + if(c->bardir == NOBARRIERS && c->barleft) + fcol = minf[moBug0+c->barright].color; + } + + if(isWarped(c->land)) { + fcol = pseudohept(c) ? 0x80C080 : 0xA06020; + if(c->wall == waSmallTree) wcol = 0x608000; + } + + if(c->land == laTortoise) { + fcol = tortoise::getMatchColor(getBits(c)); + if(c->wall == waBigTree) wcol = 0x709000; + else if(c->wall == waSmallTree) wcol = 0x905000; + } + + if(c->land == laOvergrown || c->land == laClearing) { + fcol = (c->land == laOvergrown/* || (celldistAlt(c)&1)*/) ? 0x00C020 : 0x60E080; + if(c->wall == waSmallTree) wcol = 0x008060; + else if(c->wall == waBigTree) wcol = 0x0080C0; + } + + if(c->land == laTemple) { + int d = showoff ? 0 : (euclid||c->master->alt) ? celldistAlt(c) : 99; + if(chaosmode) + fcol = 0x405090; + else if(d % TEMPLE_EACH == 0) + fcol = gradient(0x304080, winf[waColumn].color, 0, 0.5, 1); +// else if(c->type == 7) +// wcol = 0x707070; + else if(d% 2 == -1) + fcol = 0x304080; + else + fcol = 0x405090; + } + + if(isHaunted(c->land)) { + int itcolor = 0; + for(int i=0; itype; i++) if(c->mov[i] && c->mov[i]->item) + itcolor = 1; + if(c->item) itcolor |= 2; + fcol = 0x609F60 + 0x202020 * itcolor; + + forCellEx(c2, c) if(c2->monst == moFriendlyGhost) + fcol = gradient(fcol, fghostcolor(ticks, c2), 0, .25, 1); + + if(c->monst == moFriendlyGhost) + fcol = gradient(fcol, fghostcolor(ticks, c), 0, .5, 1); + + if(c->wall == waSmallTree) wcol = 0x004000; + else if(c->wall == waBigTree) wcol = 0x008000; + } + + if(c->land == laCamelot) { + int d = showoff ? 0 : ((euclid||c->master->alt) ? celldistAltRelative(c) : 0); + if(d < 0) + fcol = 0xA0A0A0; + else { + // a nice floor pattern + int v = emeraldval(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) + fcol = 0xC0C0C0; + else + fcol = 0xA0A0A0; + } + } + + if(c->land == laPrairie) { + /* if(isWateryOrBoat(c)) { + if(prairie::isriver(c)) + fcol = ((c->LHU.fi.rval & 1) ? 0x000090 : 0x0000E0) + + int(16 * wavefun(ticks / 200. + (c->wparam)*1.5)) + + ((prairie::next(c) ? 0 : 0xC00000)); + else + fcol = 0x000080; + } */ + + if(prairie::isriver(c)) { + fcol = ((c->LHU.fi.rval & 1) ? 0x402000: 0x503000); + } + else { + fcol = 0x004000 + 0x001000 * c->LHU.fi.walldist; + fcol += 0x10000 * (255 - 511 / (1 + max((int) c->LHU.fi.flowerdist, 1))); + // fcol += 0x1 * (511 / (1 + max((int) c->LHU.fi.walldist2, 1))); + } + } + + else if(isIcyLand(c) && isIcyWall(c)) { + float h = HEAT(c); + bool showcoc = c->land == laCocytus && chaosmode && !wmescher; + if(h < -0.4) + wcol = gradient(showcoc ? 0x4080FF : 0x4040FF, 0x0000FF, -0.4, h, -1); + else if(h < 0) + wcol = gradient(showcoc ? 0x80C0FF : 0x8080FF, showcoc ? 0x4080FF : 0x4040FF, 0, h, -0.4); + else if(h < 0.2) + wcol = gradient(showcoc ? 0x80C0FF : 0x8080FF, 0xFFFFFF, 0, h, 0.2); + // else if(h < 0.4) + // wcol = gradient(0xFFFFFF, 0xFFFF00, 0.2, h, 0.4); + else if(h < 0.6) + wcol = gradient(0xFFFFFF, 0xFF0000, 0.2, h, 0.6); + else if(h < 0.8) + wcol = gradient(0xFF0000, 0xFFFF00, 0.6, h, 0.8); + else + wcol = 0xFFFF00; + if(c->wall == waFrozenLake) + fcol = wcol; + else + fcol = (wcol & 0xFEFEFE) >> 1; + if(c->wall == waLake) + fcol = wcol = (wcol & 0xFCFCFC) >> 2; + } + + else if(isWateryOrBoat(c) || c->wall == waReptileBridge) { + if(c->land == laOcean) + fcol = (c->landparam > 25 && !chaosmode) ? 0x000090 : + 0x1010C0 + int(32 * sin(ticks / 500. + (chaosmode ? c->CHAOSPARAM : c->landparam)*1.5)); + else if(c->land == laOceanWall) + fcol = 0x2020FF; + else if(c->land == laKraken) { + fcol = 0x0000A0; + int mafcol = (pseudohept(c) ? 64 : 8); + /* bool nearshore = false; + for(int i=0; itype; i++) + if(c->mov[i]->wall != waSea && c->mov[i]->wall != waBoat) + nearshore = true; + if(nearshore) mafcol += 30; */ + fcol = fcol + mafcol * (4+sin(ticks / 500. + ((euclid||c->master->alt) ? celldistAlt(c) : 0)*1.5))/5; + } + else if(c->land == laAlchemist) + fcol = 0x900090; + else if(c->land == laWhirlpool) + fcol = 0x0000C0 + int(32 * sin(ticks / 200. + ((euclid||c->master->alt) ? celldistAlt(c) : 0)*1.5)); + else if(c->land == laLivefjord) + fcol = 0x000080; + else if(isWarped(c->land)) + fcol = 0x0000C0 + int((pseudohept(c)?30:-30) * sin(ticks / 600.)); + else + fcol = wcol; + } + + else if(c->land == laOcean) { + if(chaosmode) + fcol = gradient(0xD0A090, 0xD0D020, 0, c->CHAOSPARAM, 30); + else + fcol = gradient(0xD0D090, 0xD0D020, -1, sin((double) c->landparam), 1); + } + + if(c->land == laEmerald) { + if(c->wall == waCavefloor || c->wall == waCavewall) { + fcol = wcol = gradient(winf[waCavefloor].color, 0xFF00, 0, 0.5, 1); + if(c->wall == waCavewall) wcol = 0xC0FFC0; + } + } + + if(c->land == laWhirlwind) { + int wcol[4] = {0x404040, 0x404080, 0x2050A0, 0x5050C0}; + fcol = wcol[whirlwind::fzebra3(c)]; + } + + if(c->land == laIvoryTower) + fcol = 0x10101 * (32 + (c->landparam&1) * 32) - 0x000010; + + if(c->land == laDungeon) { + int lp = c->landparam % 5; + // xcol = (c->landparam&1) ? 0xD00000 : 0x00D000; + int lps[5] = { 0x402000, 0x302000, 0x202000, 0x282000, 0x382000 }; + fcol = lps[lp]; + if(c->wall == waClosedGate) + fcol = wcol = 0xC0C0C0; + if(c->wall == waOpenGate) + fcol = wcol = 0x404040; + if(c->wall == waPlatform) + fcol = wcol = 0xDFB520; + } + + if(c->land == laEndorian) { + int clev = cwt.c->land == laEndorian ? edgeDepth(cwt.c) : 0; + // xcol = (c->landparam&1) ? 0xD00000 : 0x00D000; + fcol = 0x10101 * (32 + (c->landparam&1) * 32) - 0x000010; + fcol = gradient(fcol, 0x0000D0, clev-10, edgeDepth(c), clev+10); + if(c->wall == waTrunk) fcol = winf[waTrunk].color; + + if(c->wall == waCanopy || c->wall == waSolidBranch || c->wall == waWeakBranch) { + fcol = winf[waCanopy].color; + if(c->landparam & 1) fcol = gradient(0, fcol, 0, .75, 1); + } + + } + + // floors become fcol + if(c->wall == waSulphur || c->wall == waSulphurC || isAlch(c) || c->wall == waPlatform) + fcol = wcol; + + if(c->wall == waDeadTroll2 || c->wall == waPetrified) { + eMonster m = eMonster(c->wparam); + if(c->wall == waPetrified) + wcol = gradient(wcol, minf[m].color, 0, .2, 1); + if(c->wall == waPetrified || isTroll(m)) if(!(m == moForestTroll && c->land == laOvergrown)) + wcol = gradient(wcol, minf[m].color, 0, .4, 1); + } + + if(c->land == laNone && c->wall == waNone) + wcol = fcol = 0x101010; + + if(isFire(c)) + fcol = wcol = c->wall == waEternalFire ? weakfirecolor(1500) : firecolor(100); + + if(c->wall == waBoat && wmascii) { + wcol = 0xC06000; + } + + if(mightBeMine(c) || c->wall == waMineOpen) { + fcol = wcol; + if(wmblack || wmascii) fcol >>= 1, wcol >>= 1; + } + + if(c->wall == waAncientGrave || c->wall == waFreshGrave || c->wall == waThumperOn || c->wall == waThumperOff || c->wall == waBonfireOff) + fcol = wcol; + + if(c->land == laMinefield && c->wall == waMineMine && (cmode == emMapEditor || !canmove)) + fcol = wcol = 0xFF4040; + + if(mightBeMine(c) && mineMarkedSafe(c)) + fcol = wcol = gradient(wcol, 0x40FF40, 0, 0.2, 1); + + if(mightBeMine(c) && mineMarked(c)) + fcol = wcol = gradient(wcol, 0xFF4040, -1, sin(ticks/100.0), 1); + + int rd = rosedist(c); + if(rd == 1) + wcol = gradient(0x804060, wcol, 0,1,3), + fcol = gradient(0x804060, fcol, 0,1,3); + if(rd == 2) + wcol = gradient(0x804060, wcol, 0,2,3), + fcol = gradient(0x804060, fcol, 0,2,3); + + if(items[itRevolver] && c->pathdist > GUNRANGE && !shmup::on) + fcol = gradient(fcol, 0, 0, 25, 100), + wcol = gradient(wcol, 0, 0, 25, 100); + + if(c->wall == waDeadfloor || c->wall == waCavefloor) fcol = wcol; + if(c->wall == waDeadwall) fcol = winf[waDeadfloor].color; + if(c->wall == waCavewall && c->land != laEmerald) fcol = winf[waCavefloor].color; + + if(highwall(c) && !wmspatial) + fcol = wcol; + + if(wmascii && (c->wall == waNone || isWatery(c))) wcol = fcol; + + if(c->wall == waNone && c->land == laHive) wcol = fcol; + + if(!wmspatial && snakelevel(c) && !realred(c->wall)) fcol = wcol; + + if(c->wall == waGlass && !wmspatial) fcol = wcol; + + if(c->wall == waRoundTable) fcol = wcol; + } + +bool noAdjacentChasms(cell *c) { + forCellEx(c2, c) if(c2->wall == waChasm) return false; + return true; + } + +// -1 if away, 0 if not away +int away(const transmatrix& V2) { + return intval(C0, V2 * xpush0(1)) > intval(C0, tC0(V2)); + } + +void floorShadow(cell *c, const transmatrix& V, int col, bool warp) { + if(pmodel == mdHyperboloid || pmodel == mdBall) + return; // shadows break the depth testing + if(shmup::on || purehepta) warp = false; + dynamicval p(poly_outline, OUTLINE_TRANS); + if(wmescher && qfi.special) { + queuepolyat(V * qfi.spin * shadowmulmatrix, *qfi.shape, col, PPR_WALLSHADOW); + } + else if(warp) { + if(euclid) { + if(ishex1(c)) + queuepolyat(V * pispin * applyPatterndir(c), shTriheptaEucShadow[0], col, PPR_WALLSHADOW); + else + queuepolyat(V * applyPatterndir(c), shTriheptaEucShadow[ishept(c)?1:0], col, PPR_WALLSHADOW); + } + else + queuepolyat(V * applyPatterndir(c), shTriheptaFloorShadow[ishept(c)?1:0], col, PPR_WALLSHADOW); + } + else { + queuepolyat(V, shFloorShadow[c->type==6?0:1], col, PPR_WALLSHADOW); + } + } + +void plainfloor(cell *c, bool warp, const transmatrix &V, int col, int prio) { + if(warp) { + if(euclid) { + if(ishex1(c)) + queuepolyat(V * pispin * applyPatterndir(c), shTriheptaEuc[0], col, prio); + else + queuepolyat(V * applyPatterndir(c), shTriheptaEuc[ishept(c)?1:0], col, prio); + } + else + queuepolyat(V * applyPatterndir(c), shTriheptaFloor[sphere ? 6-c->type : mapeditor::nopattern(c)], col, prio); + } + else { + queuepolyat(V, shFloor[c->type==6?0:1], col, prio); + } + } + +void qplainfloor(cell *c, bool warp, const transmatrix &V, int col) { + if(warp) { + if(euclid) { + if(ishex1(c)) + qfloor(c, V, pispin * applyPatterndir(c), shTriheptaEuc[0], col); + else + qfloor(c, V, applyPatterndir(c), shTriheptaEuc[ishept(c)?1:0], col); + } + else + qfloor(c, V, applyPatterndir(c), shTriheptaFloor[sphere ? 6-c->type : mapeditor::nopattern(c)], col); + } + else { + qfloor(c, V, shFloor[c->type==6?0:1], col); + } + } + +void warpfloor(cell *c, const transmatrix& V, int col, int prio, bool warp) { + if(shmup::on || purehepta) warp = false; + if(wmescher && qfi.special) + queuepolyat(V*qfi.spin, *qfi.shape, col, prio); + else plainfloor(c, warp, V, col, prio); + } + +#define placeSidewallX(a,b,c,d,e,f,g) \ + { if((wmescher && qfi.special) || !validsidepar[c]) { \ + escherSidewall(a,c,d,g); break; } \ + else placeSidewall(a,b,c,d,e,f,g); } +#define placeSidewallXB(a,b,c,d,e,f,g, Break) \ + { if((wmescher && qfi.shape) || !validsidepar[c]) { \ + escherSidewall(a,c,d,g); Break; break; } \ + else placeSidewall(a,b,c,d,e,f,g); } + +/* double zgrad(double f1, double f2, int nom, int den) { + using namespace geom3; + ld fo1 = factor_to_lev(f1); + ld fo2 = factor_to_lev(f2); + return lev_to_factor(fo1 + (fo2-fo1) * nom / den); + } */ + +double zgrad0(double l1, double l2, int nom, int den) { + using namespace geom3; + return lev_to_factor(l1 + (l2-l1) * nom / den); + } + +void escherSidewall(cell *c, int sidepar, const transmatrix& V, int col) { + if(sidepar >= SIDE_SLEV && sidepar <= SIDE_SLEV+2) { + int sl = sidepar - SIDE_SLEV; + for(int z=1; z<=4; z++) if(z == 1 || (z == 4 && detaillevel == 2)) + warpfloor(c, mscale(V, zgrad0(geom3::slev * sl, geom3::slev * (sl+1), z, 4)), col, PPR_REDWALL-4+z+4*sl, false); + } + else if(sidepar == SIDE_WALL) { + const int layers = 2 << detaillevel; + for(int z=1; zmov[i] || !ishept(c->mov[i]))) return; + int prio; + if(mirr) prio = PPR_GLASS - 2; + else if(sidepar == SIDE_WALL) prio = PPR_WALL3 - 2; + else if(sidepar == SIDE_WTS3) prio = PPR_WALL3 - 2; + else if(sidepar == SIDE_LAKE) prio = PPR_LAKEWALL; + else if(sidepar == SIDE_LTOB) prio = PPR_INLAKEWALL; + else if(sidepar == SIDE_BTOI) prio = PPR_BELOWBOTTOM; + else prio = PPR_REDWALL-2+4*(sidepar-SIDE_SLEV); + + transmatrix V2 = V * ddspin(c, i); + + int aw = away(V2); prio += aw; + if(!detaillevel && aw < 0) return; + + // prio += c->cpdist - c->mov[i]->cpdist; + queuepolyat(V2, + (mirr?shMFloorSide:warp?shTriheptaSide:shFloorSide)[sidepar][c->type==6?0:1], col, prio); + } + +bool openorsafe(cell *c) { + return c->wall == waMineOpen || mineMarkedSafe(c); + } + +#define Dark(x) darkena(x,0,0xFF) + +int gridcolor(cell *c1, cell *c2) { + if(cmode == emDraw) return Dark(0xFFFFFF); + if(!c2) + return 0x202020 >> darken; + int rd1 = rosedist(c1), rd2 = rosedist(c2); + if(rd1 != rd2) { + int r = rd1+rd2; + if(r == 1) return Dark(0x802020); + if(r == 3) return Dark(0xC02020); + if(r == 2) return Dark(0xF02020); + } + if(chasmgraph(c1) != chasmgraph(c2)) + return Dark(0x808080); + if(c1->land == laAlchemist && c2->land == laAlchemist && c1->wall != c2->wall) + return Dark(0xC020C0); + if((c1->land == laWhirlpool || c2->land == laWhirlpool) && (celldistAlt(c1) != celldistAlt(c2))) + return Dark(0x2020A0); + if(c1->land == laMinefield && c2->land == laMinefield && (openorsafe(c1) != openorsafe(c2))) + return Dark(0xA0A0A0); + return Dark(0x202020); + } + +void pushdown(cell *c, int& q, const transmatrix &V, double down, bool rezoom, bool repriority) { + + // since we might be changing priorities, we have to make sure that we are sorting correctly + if(down > 0 && repriority) { + int qq = q+1; + while(qq < size(ptds)) + if(qq > q && ptds[qq].prio < ptds[qq-1].prio) { + swap(ptds[qq], ptds[qq-1]); + qq--; + } + else qq++; + } + + while(q < size(ptds)) { + polytodraw& ptd = ptds[q++]; + if(ptd.kind == pkPoly) { + + double z2; + + double z = zlevel(tC0(ptd.u.poly.V)); + double lev = geom3::factor_to_lev(z); + double nlev = lev - down; + + double xyscale = rezoom ? geom3::scale_at_lev(lev) / geom3::scale_at_lev(nlev) : 1; + z2 = geom3::lev_to_factor(nlev); + double zscale = z2 / z; + + // xyscale = xyscale + (zscale-xyscale) * (1+sin(ticks / 1000.0)) / 2; + + ptd.u.poly.V = xyzscale( V, xyscale*zscale, zscale) + * inverse(V) * ptd.u.poly.V; + + if(!repriority) ; + else if(nlev < -geom3::lake_bottom-1e-3) { + ptd.prio = PPR_BELOWBOTTOM; + if(c->wall != waChasm) + ptd.col = 0; // disappear! + } + else if(nlev < -geom3::lake_top-1e-3) + ptd.prio = PPR_INLAKEWALL; + else if(nlev < 0) + ptd.prio = PPR_LAKEWALL; + } + } + } + +bool dodrawcell(cell *c) { + // todo: fix when scrolling + if(!buggyGeneration && c->land != laCanvas && sightrange < 10) { + // not yet created + if(c->mpdist > 7 && !cheater) return false; + // in the Yendor Challenge, scrolling back is forbidden + if(c->cpdist > 7 && (yendor::on && !cheater)) return false; + // (incorrect comment) too far, no bugs nearby + if(playermoved && sightrange <= 7 && c->cpdist > sightrange) return false; + } + + return true; + } + +// 1 : (floor, water); 2 : (water, bottom); 4 : (bottom, inf) + +int shallow(cell *c) { + if(cellUnstable(c)) return 0; + else if( + c->wall == waReptile) return 1; + else if(c->wall == waReptileBridge || + c->wall == waGargoyleFloor || + c->wall == waGargoyleBridge || + c->wall == waTempFloor || + c->wall == waTempBridge || + c->wall == waFrozenLake) + return 5; + return 7; + } + +bool viewdists = false; + +bool allemptynear(cell *c) { + if(c->wall) return false; + forCellEx(c2, c) if(c2->wall) return false; + return true; + } + +void drawcell(cell *c, transmatrix V, int spinv, bool mirrored) { + + qfi.shape = NULL; qfi.special = false; + ivoryz = isGravityLand(c->land); + + transmatrix& gm = gmatrix[c]; + bool orig = (gm[2][2] == 0 || fabs(gm[2][2]-1) >= fabs(V[2][2]-1)) - 1e-8; + + if(sphere && vid.alpha > 1) { + long double d = V[2][2]; + if(d > -1/vid.alpha) return; + } + + if(sphere && vid.alpha <= 1) { + if(V[2][2] < -.8) return; + } + + if(orig) gm = V; + + ld dist0 = hdist0(tC0(V)) - 1e-6; + if(dist0 < geom3::highdetail) detaillevel = 2; + else if(dist0 < geom3::middetail) detaillevel = 1; + else detaillevel = 0; #ifdef BUILDZEBRA if(c->type == 6 && c->tmp > 0) { @@ -2236,16 +3750,6 @@ void drawcell(cell *c, const transmatrix& V, int spinv) { viewBuggyCells(c,V); - // todo: fix when scrolling - if(!buggyGeneration && c->land != laCanvas && sightrange < 10) { - // not yet created - if(c->mpdist > 7 && !cheater) return; - // in the Yendor Challenge, scrolling back is forbidden - if(c->cpdist > 7 && (yendor::on && !cheater)) return; - // (incorrect comment) too far, no bugs nearby - if(playermoved && c->cpdist > sightrange) return; - } - if(conformal::on || inHighQual) checkTide(c); if(!euclid) { @@ -2253,36 +3757,50 @@ void drawcell(cell *c, const transmatrix& V, int spinv) { if(webdisplay & 1) { if(c->type == 6) { for(int a=0; a<3; a++) - queueline(V*Crad[a*7], V*Crad[a*7+21], 0xd0d0 >> darken); + queueline(V*Crad[a*S7], V*Crad[a*S7+S42/2], darkena(0xd0d0, 0, 0xFF), 2); } else { - for(int a=0; a<7; a++) - queueline(V*C0, V*Crad[(21+a*6)%42], 0xd0d0 >> darken); + for(int a=0; atype == 7) { - queueline(V*C0, V*xpush(tessf)*C0, 0xd0d0 >> darken); + if(webdisplay & 2) if(c->type != 6) { + queueline(tC0(V), V*ddi0(purehepta?S42:0, tessf), darkena(0xd0d0, 0, 0xFF), 2); } - if(webdisplay & 4) if(c->type == 7 && !euclid && c->master->alt) { - for(int i=0; i<7; i++) + if(webdisplay & 4) if(c->type != 6 && !euclid && c->master->alt) { + for(int i=0; imaster->move[i] && c->master->move[i]->alt == c->master->alt->move[0]) - queueline(V*C0, V*spin(-2*M_PI*i/7)*xpush(tessf)*C0, 0xd000d0 >> darken); + queueline(tC0(V), V*xspinpush0((purehepta?M_PI:0) -2*M_PI*i/S7, tessf), darkena(0xd000d0, 0, 0xFF), 2); } } // save the player's view center - if(c == cwt.c && !shmup::on) { + if(isPlayerOn(c) && !shmup::on) { playerfound = true; /* if(euclid) - return d * 84 / c->type; + return d * S84 / c->type; else - return 42 - d * 84 / c->type; - cwtV = V * spin(-cwt.spin * 2*M_PI/c->type) * spin(M_PI); */ + return S42 - d * S84 / c->type; + cwtV = V * spin(-cwt.spin * 2*M_PI/c->type) * pispin; */ - cwtV = V * spin(displaydir(c, cwt.spin) * M_PI/42); + if(multi::players > 1) { + for(int i=0; iland == laEdge) { @@ -2294,7 +3812,7 @@ void drawcell(cell *c, const transmatrix& V, int spinv) { if(1) { - hyperpoint VC0 = V*C0; + hyperpoint VC0 = tC0(V); if(intval(mouseh, VC0) < modist) { modist2 = modist; mouseover2 = mouseover; @@ -2312,10 +3830,18 @@ void drawcell(cell *c, const transmatrix& V, int spinv) { centdist = dfc; centerover = c; } + + int orbrange = (items[itRevolver] ? 3 : 2); + + if(c->cpdist <= orbrange) if(multi::players > 1 || multi::alwaysuse) + for(int i=0; imaxdist - 0x2000 * c->cpdist; if(!buggyGeneration && c->mpdist > 8 && !cheater) return; // not yet generated @@ -2325,279 +3851,30 @@ void drawcell(cell *c, const transmatrix& V, int spinv) { } 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.goteyes2 ? 0xFF3030 : 0xFF0000); - if(c->land == laCrossroads2 && c->wall == waNone) - col = linf[laCrossroads2].color; - - if(c->land == laCrossroads3 && c->wall == waNone) - col = linf[laCrossroads3].color; - - if(c->land == laCrossroads4 && c->wall == waNone) - col = linf[laCrossroads4].color; - - if(isElemental(c->land) && c->wall == waNone) - col = linf[c->land].color; - - if(c->land == laElementalWall && c->wall == waNone) - col = (linf[c->barleft].color>>1) + (linf[c->barright].color>>1); - - if(c->land == laZebra && c->wall == waNone) { - col = 0xE0E0E0; - } - - if(c->land == laZebra && c->wall == waTrapdoor) - col = 0x808080; - - if(c->land == laDesert && c->wall == waNone) col = 0xEDC9AF; - if(c->land == laCaves && c->wall == waNone) col = 0x202020; - if(c->land == laEmerald && c->wall == waNone) col = 0x202020; - if(c->land == laDeadCaves && c->wall == waNone) col = 0x202020; - - if(c->land == laNone && c->wall == waNone) { - col = 0x101010; + int wcol, fcol, asciicol; + + setcolors(c, wcol, fcol); + asciicol = wcol; + + if(c->land == laNone && c->wall == waNone) queuepoly(V, shTriangle, 0xFFFF0000); - } - if(isHive(c->land) && !isWateryOrBoat(c) && c->wall != waCloud && c->wall != waMirror && c->wall != waMineMine) { - col = linf[c->land].color; - if(c->wall == waWaxWall) col = c->landparam; - } - if(c->land == laJungle && c->wall == waNone) col = (vid.goteyes2 ? 0x408040 : 0x008000); - if(c->land == laPower && c->wall == waNone) - col = linf[c->land].color; -/* if(c->land == laEmerald && c->wall == waNone) { - col = 0x50A020; - } - if(c->land == laEmerald && 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 == laTortoise && (c->wall == waNone || c->wall == waBigTree || c->wall == waSmallTree)) { - - if(c->wall == waBigTree) col = 0x709000; - else if(c->wall == waSmallTree) col = 0x905000; - else col = tortoise::getMatchColor(getBits(c)); - } - - if(c->land == laDryForest && c->wall == waNone) { - /*if(c->wall == waBigTree) - col = (vid.goteyes ? 0xC0C060 : 0xC0C000); - else if(c->wall == waSmallTree) - col = (vid.goteyes ? 0x60C060 : 0x00C000); - else*/ if(c->wall == waNone) { - col = gradient(0x008000, 0x800000, 0, c->landparam, 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 : ((euclid||c->master->alt) ? celldistAltRelative(c) : 0); - if(d < 0) - col = 0xA0A0A0; - else { - // a nice floor pattern - int v = emeraldval(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.goteyes2 ? 0x4080C0 : 0x004080); - if(c->land == laTemple) { - int d = showoff ? 0 : (euclid||c->master->alt) ? celldistAlt(c) : 99; - if(chaosmode) - col = c->wall == waColumn ? winf[waColumn].color : 0x405090; - else 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.goteyes2 ? 0xC03030 : 0xC00000); - - if(c->land == laPalace && (c->wall == waNone || c->wall == waClosePlate || c->wall == waOpenPlate || - c->wall == waTrapdoor) && (c->wall == waNone || vid.wallmode != 0)) - col = 0x806020; - - if(c->land == laPalace && c->wall == waCamelot) - col = 0xFFD500; - - if(isIcyLand(c) && isIcyWall(c)) { - float h = HEAT(c); - bool showcoc = c->land == laCocytus && chaosmode && vid.wallmode < 3; - if(h < -0.4) - col = gradient(showcoc ? 0x4080FF : 0x4040FF, 0x0000FF, -0.4, h, -1); - else if(h < 0) - col = gradient(showcoc ? 0x80C0FF : 0x8080FF, showcoc ? 0x4080FF : 0x4040FF, 0, h, -0.4); - else if(h < 0.2) - col = gradient(showcoc ? 0x80C0FF : 0x8080FF, 0xFFFFFF, 0, h, 0.2); - // else if(h < 0.4) - // col = gradient(0xFFFFFF, 0xFFFF00, 0.2, h, 0.4); - else if(h < 0.6) - col = gradient(0xFFFFFF, 0xFF0000, 0.2, h, 0.6); - else if(h < 0.8) - col = gradient(0xFF0000, 0xFFFF00, 0.6, h, 0.8); - else - col = 0xFFFF00; - if(c->wall == waNone) - col = (col & 0xFEFEFE) >> 1; - if(c->wall == waLake) - col = (col & 0xFCFCFC) >> 2; - } - - /* if(c->wall == waBonfireOff) - col = 0x404040; */ - - if(isFire(c)) - col = c->wall == waEternalFire ? weakfirecolor(1500) : firecolor(100); - - /* if(c->wall == waThumperOff) - col = 0xEDC9AF; */ - if(c->wall == waThumperOn) { 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++) - queueline(V*ddi(a, rad)*C0, V*ddi(a+1, rad)*C0, col); + int tcol = darkena(gradient(0xFFFFFF, 0, 0, rad, 1.5 * hexf), 0, 0xFF); + for(int a=0; aland == laEmerald && c->wall == waCavefloor) { - col = gradient(col, 0xFF00, 0, 0.5, 1); - // col |= 0xFF00; // col += 0x300060; // col += 0x2F18; col -= 0x100000; - } + + // bool dothept = false; - if(c->land == laOcean && (c->wall == waNone || c->wall == waStrandedBoat)) { - if(chaosmode) - col = gradient(0xD0A090, 0xD0D020, 0, c->CHAOSPARAM, 30); - else - col = gradient(0xD0D090, 0xD0D020, -1, sin((double) c->landparam), 1); - } - - if(c->land == laLivefjord && (c->wall == waNone || c->wall == waStrandedBoat)) - col = 0x306030; + /* if(pseudohept(c) && vid.darkhepta) { + col = gradient(0, col, 0, 0.75, 1); + } */ - if(c->land == laEmerald && c->wall == waCavewall) { - col = 0xC0FFC0; - // col |= 0xFF00; // col += 0x300060; // col += 0x2F18; col -= 0x100000; - } - - if(c->land == laHive && items[itOrbInvis] && c->wall == waNone && c->landparam) - col = gradient(col, 0xFF0000, 0, c->landparam, 100); - - if(c->land == laStorms && (c->wall == waNone)) - col = linf[c->land].color; - - if(c->land == laWhirlwind && (c->wall == waNone)) { - int wcol[4] = {0x404040, 0x404080, 0x2050A0, 0x5050C0}; - col = wcol[whirlwind::fzebra3(c)]; - } - - if(c->land == laOvergrown || c->land == laClearing) { - if(c->wall == waSmallTree) col = 0x008060; - else if(c->wall == waBigTree) col = 0x0080C0; - else if(c->wall == waNone) - col = (c->land == laOvergrown/* || (celldistAlt(c)&1)*/) ? 0x00C020 : 0x60E080; - } - - if(c->land == laGridCoast && c->wall == waSmallTree) col = 0x608000; - - if(isHaunted(c->land)) { - if(c->wall == waSmallTree) col = 0x004000; - else if(c->wall == waBigTree) col = 0x008000; - else if(c->wall == waNone) { - int itcolor = 0; - for(int i=0; itype; i++) if(c->mov[i] && c->mov[i]->item) - itcolor = 1; - if(c->item) itcolor |= 2; - col = 0x609F60 + 0x202020 * itcolor; - } - } - - if(c->land == laWildWest && (c->wall == waNone)) - col = linf[c->land].color; - - if(c->land == laCaribbean && (c->wall == waCIsland || c->wall == waCIsland2)) - col = winf[c->wall].color; - - if(c->wall == waBoat && !vid.wallmode) { - col = 0xC06000; - } - - if(c->land == laMinefield && c->wall == waMineMine && (cmode == emMapEditor || !canmove)) - col = 0xFF4040; - - if(c->wall == waMineMine && c->land != laMinefield) - col = gradient(col, 0xFF4040, -1, sin(ticks/100.0), 1); - - if(c->land == laMinefield && c->wall == waNone) - col = 0x80A080; - - if(c->land == laCaribbean && c->wall == waNone) - col = 0x006000; - - if(c->land == laRose && c->wall == waNone) { - col = linf[c->land].color; - } - - if(isWarped(c->land) && c->wall == waNone) - col = pseudohept(c) ? 0x80C080 : 0xA06020; - - if(c->land == laRedRock && (c->wall == waNone || snakelevel(c)) && c->wall != waDeadfloor2) { - col = linf[c->land].color; - } - - if(c->land == laDragon && (c->wall == waNone || snakelevel(c)) && c->wall != waDeadfloor2) { - col = linf[c->land].color; - } - - if(c->land == laCanvas && c->wall == waNone) { - col = c->landparam; - } - - if(pseudohept(c)) { - if(vid.darkhepta) - col = gradient(0, col, 0, 0.75, 1); - } - - int rd = rosedist(c); - if(rd == 1) col = gradient(0x804060, col, 0,1,3); - if(rd == 2) col = gradient(0x804060, col, 0,2,3); - - int ycol = col; - - if(c->land == laHive && c->bardir == NOBARRIERS && c->barleft) { - col = minf[moBug0+c->barright].color; - } - - if(items[itRevolver] && c->pathdist > GUNRANGE && !shmup::on) - col = gradient(col, 0, 0, 25, 100); - - int xcol = col; - eItem it = c->item; bool hidden = itemHidden(c); @@ -2611,501 +3888,757 @@ void drawcell(cell *c, const transmatrix& V, int spinv) { if(hiddens && cmode != emMapEditor) it = itNone; + int icol = 0, moncol = 0xFF00FF; + if(it) - ch = iinf[it].glyph, col = iinf[it].color; + ch = iinf[it].glyph, asciicol = icol = iinf[it].color; - int icol = col; - - if(it && c->land == laAlchemist) - if(!(conformal::includeHistory && eq(c->aitmp, sval))) - xcol = col; - if(c->monst) { - ch = minf[c->monst].glyph, col = minf[c->monst].color; + ch = minf[c->monst].glyph, moncol = minf[c->monst].color; if(c->monst == moMutant) { // root coloring if(c->stuntime != mutantphase) - col = gradient(0xC00030, 0x008000, 0, (c->stuntime-mutantphase) & 15, 15); + moncol = + gradient(0xC00030, 0x008000, 0, (c->stuntime-mutantphase) & 15, 15); } if(isMetalBeast(c->monst) && c->stuntime) - col >>= 1; + moncol >>= 1; + + if(c->monst == moSlime) { + moncol = winf[c->wall].color; + moncol |= (moncol>>1); + } + + asciicol = moncol; + + if(isDragon(c->monst) || isKraken(c->monst)) if(!c->hitpoints) + asciicol = 0x505050; + + if(c->monst == moTortoise) + asciicol = tortoise::getMatchColor(tortoise::getb(c)); + + if(c->monst != moMutant) for(int k=0; kstuntime; k++) + asciicol = ((asciicol & 0xFEFEFE) >> 1) + 0x101010; } if(c->cpdist == 0 && mapeditor::drawplayer) { ch = '@'; - if(vid.monmode == 0) col = cheater ? 0xFF3030 : 0xD0D0D0; - } - - if(c->monst == moSlime) { - col = winf[c->wall].color; - col |= (col>>1); + if(!mmitem) asciicol = moncol = cheater ? 0xFF3030 : 0xD0D0D0; } if(c->ligon) { int tim = ticks - lightat; if(tim > 1000) tim = 800; if(elec::havecharge && tim > 400) tim = 400; - for(int t=0; t<7; t++) if(c->mov[t] && c->mov[t]->ligon) { + for(int t=0; ttype; t++) if(c->mov[t] && c->mov[t]->ligon) { int hdir = displaydir(c, t); - int col = gradient(iinf[itOrbLightning].color, 0, 0, tim, 1100); - queueline(V*ddi(ticks, hexf/2)*C0, V*ddi(hdir, crossf)*C0, col); + int lcol = darkena(gradient(iinf[itOrbLightning].color, 0, 0, tim, 1100), 0, 0xFF); + queueline(V*ddi0(ticks, hexf/2), V*ddi0(hdir, crossf), lcol, 2); } } int ct = c->type; + int ct6 = K(c); bool error = false; - if(c->land == laIvoryTower && (c->wall == waNone || c->wall == waLadder)) - // xcol = (c->landparam&1) ? 0xD00000 : 0x00D000; - xcol = 0x10101 * (32 + (c->landparam&1) * 32) - 0x000010; + chasmg = chasmgraph(c); - if(c->land == laEndorian && c->wall == waNone) { - int clev = cwt.c->land == laEndorian ? edgeDepth(cwt.c) : 0; - // xcol = (c->landparam&1) ? 0xD00000 : 0x00D000; - xcol = 0x10101 * (32 + (c->landparam&1) * 32) - 0x000010; - xcol = gradient(xcol, 0x0000D0, clev-10, edgeDepth(c), clev+10); + int fd = + c->land == laRedRock ? 0 : + (c->land == laOcean || c->land == laLivefjord || c->land == laWhirlpool) ? 1 : + c->land == laAlchemist || c->land == laIce || c->land == laGraveyard || + c->land == laRlyeh || c->land == laTemple || c->land == laWineyard || + c->land == laDeadCaves || c->land == laPalace || c->land == laCA ? 1 : + c->land == laCanvas ? 0 : + c->land == laKraken ? 1 : + c->land == laBurial ? 1 : + c->land == laIvoryTower ? 1 : + c->land == laDungeon ? 1 : + c->land == laMountain ? 1 : + c->land == laEndorian ? 1 : + c->land == laCaribbean ? 1 : + c->land == laWhirlwind ? 1 : + c->land == laRose ? 1 : + c->land == laWarpSea ? 1 : + c->land == laTortoise ? 1 : + c->land == laDragon ? 1 : + c->land == laHalloween ? 1 : + c->land == laTrollheim ? 2 : + c->land == laReptile ? 0 : + 2; + + poly_outline = OUTLINE_NONE; + + int sl = snakelevel(c); + + transmatrix Vd0, Vf0, Vboat0; + const transmatrix *Vdp = + (!wmspatial) ? &V : + sl ? &(Vd0= mscale(V, geom3::SLEV[sl])) : + highwall(c) ? &(Vd0= mscale(V, (1+geom3::WALL)/2)) : + (chasmg==1) ? &(Vd0 = mscale(V, geom3::LAKE)) : + &V; + + const transmatrix& Vf = (chasmg && wmspatial) ? (Vf0=mscale(V, geom3::BOTTOM)) : V; + + const transmatrix *Vboat = &(*Vdp); + + if(DOSHMUP) { + ld zlev = -geom3::factor_to_lev(zlevel(tC0((*Vdp)))); + shmup::drawMonster(V, c, Vboat, Vboat0, zlev); } - - if(c->wall == waTrunk) xcol = winf[waTrunk].color; - - if(c->wall == waCanopy || c->wall == waSolidBranch || c->wall == waWeakBranch) { - xcol = winf[waCanopy].color; - if(c->landparam & 1) xcol = gradient(0, xcol, 0, .75, 1); - } - - if(c->wall == waSea || c->wall == waBoat) { - if(c->land == laOcean) - xcol = (c->landparam > 25 && !chaosmode) ? 0x000090 : - 0x1010C0 + int(32 * sin(ticks / 500. + (chaosmode ? c->CHAOSPARAM : c->landparam)*1.5)); - else if(c->land == laOceanWall) - xcol = 0x2020FF; - else if(c->land == laAlchemist) - xcol = 0x900090; - else if(c->land == laWhirlpool) - xcol = 0x0000C0 + int(32 * sin(ticks / 200. + ((euclid||c->master->alt) ? celldistAlt(c) : 0)*1.5)); - else if(c->land == laLivefjord) - xcol = 0x000080; - else if(isWarped(c->land)) - xcol = 0x0000C0 + int((pseudohept(c)?30:-30) * sin(ticks / 600.)); - } - - if(vid.wallmode) { - - poly_outline = OUTLINE_NONE; + + poly_outline = (backcolor << 8) + 0xFF; + + if(!wmascii) { // floor - int fd = - c->land == laRedRock ? 0 : - (c->land == laOcean || c->land == laLivefjord || c->land == laWhirlpool) ? 1 : - c->land == laAlchemist || c->land == laIce || c->land == laGraveyard || - c->land == laRlyeh || c->land == laTemple || c->land == laWineyard || - c->land == laDeadCaves || c->land == laPalace ? 1 : - c->land == laCanvas ? 0 : - c->land == laIvoryTower ? 1 : - c->land == laEndorian ? 1 : - c->land == laCaribbean ? 1 : - c->land == laWhirlwind ? 1 : - c->land == laRose ? 1 : - c->land == laGridSea ? 1 : - c->land == laTortoise ? 1 : - c->land == laDragon ? 1 : - 2; - -#ifndef MOBILE +#ifndef NOEDIT transmatrix Vpdir = V * applyPatterndir(c); #endif - // printf("ql\n"); - // queueline(Vpdir * C0, Vpdir * xpush(crossf/3) * C0, 0xFFFFFF); - bool eoh = euclid || purehepta; -#ifndef MOBILE +#ifndef NOEDIT if(c == mapeditor::drawcell && c != cwt.c && !c->monst && !c->item) { mapeditor::drawtrans = Vpdir; } #endif - if(c->wall == waChasm) ; - - /* else if(purehepta) - queuepoly(V * spin(M_PI), shBigHepta, darkena(xcol, fd, 0xFF)); */ - - -#ifndef MOBILE - else if(drawUserShape(Vpdir, mapeditor::cellShapeGroup(), mapeditor::realpattern(c), - darkena(xcol, fd, cmode == emDraw ? 0xC0 : 0xFF))); + if(c->wall == waChasm) { + if(c->land == laZebra) fd++; + if(c->land == laHalloween && !wmblack) { + transmatrix Vdepth = mscale(V, geom3::BOTTOM); + queuepolyat(Vdepth, shFloor[ct6], darkena(firecolor(ticks / 10), 0, 0xDF), + PPR_LAKEBOTTOM); + } + } + +#ifndef NOEDIT + if(drawUserShape(Vpdir, mapeditor::cellShapeGroup(), mapeditor::realpatternsh(c), + darkena(fcol, fd, cmode == emDraw ? 0xC0 : 0xFF))); else if(mapeditor::whichShape == '7') { if(ishept(c)) - queuepoly(V, vid.wallmode == 1 ? shBFloor[ct-6] : + qfloor(c, Vf, wmblack ? shBFloor[ct6] : euclid ? shBigHex : - shBigHepta, darkena(xcol, fd, 0xFF)); + shBigHepta, darkena(fcol, fd, 0xFF)); } else if(mapeditor::whichShape == '8') { if(euclid) - queuepoly(V, shTriheptaEuc[ishept(c) ? 1 : ishex1(c) ? 0 : 2], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shTriheptaEuc[ishept(c) ? 1 : ishex1(c) ? 0 : 2], darkena(fcol, fd, 0xFF)); else - queuepoly(V, shTriheptaFloor[ishept(c) ? 1 : 0], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shTriheptaFloor[ishept(c) ? 1 : 0], darkena(fcol, fd, 0xFF)); } else if(mapeditor::whichShape == '6') { if(!ishept(c)) - queuepoly(V, - vid.wallmode == 1 ? shBFloor[ct-6] : + qfloor(c, Vf, + wmblack ? shBFloor[ct6] : euclid ? (ishex1(c) ? shBigHexTriangle : shBigHexTriangleRev) : - shBigTriangle, darkena(xcol, fd, 0xFF)); + shBigTriangle, darkena(fcol, fd, 0xFF)); } #endif - else if(c->land == laWineyard && (c->wall == waVineHalfA || c-> wall == waVineHalfB)) { + else if(c->land == laWineyard && cellHalfvine(c)) { 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); + qfi.spin = ddspin(c, i, S14); + transmatrix V2 = V * qfi.spin; + + if(wmspatial && wmescher) { + qfi.shape = &shSemiFeatherFloor[0]; qfi.special = true; + int dk = 1; + int vcol = winf[waVinePlant].color; + warpfloor(c, mscale(V, geom3::WALL), darkena(vcol, dk, 0xFF), PPR_WALL3A, false); + escherSidewall(c, SIDE_WALL, V, darkena(gradient(0, vcol, 0, .8, 1), dk, 0xFF)); + qfloor(c, V2, shSemiFeatherFloor[1], darkena(fcol, dk, 0xFF)); + qfi.shape = &shFeatherFloor[0]; qfi.special = true; + } + + else if(wmspatial) { + hpcshape *shar = wmplain ? shFloor : shFeatherFloor; + + int dk = 1; + qfloor(c, V, shar[0], darkena(fcol, dk, 0xFF)); + + int vcol = winf[waVinePlant].color; + int vcol2 = gradient(0, vcol, 0, .8, 1); + + transmatrix Vdepth = mscale(V2, geom3::WALL); - transmatrix V2 = V * spin(M_PI*hdir/42); + queuepolyat(Vdepth, shSemiFloor[0], darkena(vcol, dk, 0xFF), PPR_WALL3A); + {dynamicval p(poly_outline, OUTLINE_TRANS); queuepolyat(V2 * spin(M_PI*2/3), shSemiFloorShadow, SHADOW_WALL, PPR_WALLSHADOW); } + queuepolyat(V2, shSemiFloorSide[SIDE_WALL], darkena(vcol, dk, 0xFF), PPR_WALL3A-2+away(V2)); + + if(validsidepar[SIDE_WALL]) forCellIdEx(c2, j, c) { + int dis = i-j; + dis %= 6; + if(dis<0) dis += 6; + if(dis != 1 && dis != 5) continue; + placeSidewall(c, j, SIDE_WALL, V, false, false, darkena(vcol2, fd, 0xFF)); + } + } - hpcshape *shar = shSemiFeatherFloor; - - if(vid.wallmode == 1) shar = shSemiBFloor; - if(vid.wallmode == 2) shar = shSemiFloor; - - int dk = vid.wallmode == 1 ? 0 : vid.wallmode == 2 ? 1 : 1; - - queuepoly(V2, shar[0], darkena(winf[waVinePlant].color, dk, 0xFF)); - queuepoly(V2, shar[1], darkena(xcol, dk, 0xFF)); + else { + hpcshape *shar = shSemiFeatherFloor; + + if(wmblack) shar = shSemiBFloor; + if(wmplain) shar = shSemiFloor; + + int dk = wmblack ? 0 : wmplain ? 1 : 1; + + qfloor(c, V2, shar[0], darkena(winf[waVinePlant].color, dk, 0xFF)); + qfloor(c, V2, shar[1], darkena(fcol, dk, 0xFF)); + } } - else if(vid.wallmode == 1 && c->land == laAlchemist) - queuepoly(V, shFloor[ct-6], darkena(xcol, 1, 0xFF)); + else if(c->land == laReptile || c->wall == waReptile) + drawReptileFloor(Vf, c, fcol, true); - else if(vid.wallmode == 1 && c->wall == waMineOpen) + else if(wmblack == 1 && c->wall == waMineOpen && vid.grid) ; - else if(vid.wallmode == 1) - queuepoly(V, shBFloor[ct-6], darkena(xcol, 0, 0xFF)); + else if(wmblack) { + qfloor(c, Vf, shBFloor[ct6], darkena(fcol, 0, 0xFF)); + int rd = rosedist(c); + if(rd == 1) + qfloor(c, Vf, shHeptaMarker, darkena(fcol, 0, 0x80)); + else if(rd == 2) + qfloor(c, Vf, shHeptaMarker, darkena(fcol, 0, 0x40)); + } else if(isWarped(c) && euclid) - queuepoly(V, shTriheptaEuc[ishept(c)?1:ishex1(c)?0:2], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shTriheptaEuc[ishept(c)?1:ishex1(c)?0:2], darkena(fcol, fd, 0xFF)); else if(isWarped(c) && !purehepta && !shmup::on) { - transmatrix V2 = V * applyPatterndir(c); int np = mapeditor::nopattern(c); if(c->landparam == 1337) np = 0; // for the achievement screenshot if(np < 11) - queuepoly(V2, shTriheptaFloor[np], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, applyPatterndir(c), shTriheptaFloor[np], darkena(fcol, fd, 0xFF)); } - else if(vid.wallmode == 2) { - queuepoly(V, shFloor[ct-6], darkena(xcol, fd, 0xFF)); + else if(wmplain) { + if(wmspatial && highwall(c)) ; + else qfloor(c, Vf, shFloor[ct6], darkena(fcol, fd, 0xFF)); } else if(randomPatternsMode && c->land != laBarrier && !isWarped(c->land)) { int j = (randompattern[c->land]/5) % 15; - int col = darkena(xcol, fd, 0xFF); + int dfcol = darkena(fcol, fd, 0xFF); int k = randompattern[c->land] % RPV_MODULO; int k7 = randompattern[c->land] % 7; - if(k == RPV_ZEBRA && k7 < 2) drawZebraFloor(V, c, col); - else if(k == RPV_EMERALD && k7 == 0) drawEmeraldFloor(V, c, col); - else if(k == RPV_CYCLE && k7 < 4) drawTowerFloor(V, c, col, celldist); - + if(k == RPV_ZEBRA && k7 < 2) drawZebraFloor(Vf, c, dfcol); + else if(k == RPV_EMERALD && k7 == 0) drawEmeraldFloor(Vf, c, dfcol); + else if(k == RPV_CYCLE && k7 < 4) drawTowerFloor(Vf, c, dfcol, celldist); + else switch(j) { - case 0: queuepoly(V, shCloudFloor[ct-6], col); break; - case 1: queuepoly(V, shFeatherFloor[ECT], col); break; - case 2: queuepoly(V, shStarFloor[ct-6], col); break; - case 3: queuepoly(V, shTriFloor[ct-6], col); break; - case 4: queuepoly(V, shSStarFloor[ct-6], col); break; - case 5: queuepoly(V, shOverFloor[ECT], col); break; - case 6: queuepoly(V, shFeatherFloor[ECT], col); break; - case 7: queuepoly(V, shDemonFloor[ct-6], col); break; - case 8: queuepoly(V, shCrossFloor[ct-6], col); break; - case 9: queuepoly(V, shMFloor[ct-6], col); break; - case 10: queuepoly(V, shCaveFloor[ECT], col); break; - case 11: queuepoly(V, shPowerFloor[ct-6], col); break; - case 12: queuepoly(V, shDesertFloor[ct-6], col); break; - case 13: queuepoly(V, purehepta ? shChargedFloor[3] : shChargedFloor[ct-6], col); break; - case 14: queuepoly(V, ct==6?shChargedFloor[2]:shFloor[1], col); break; + case 0: qfloor(c, Vf, shCloudFloor[ct6], dfcol); break; + case 1: qfloor(c, Vf, shFeatherFloor[ECT], dfcol); break; + case 2: qfloor(c, Vf, shStarFloor[ct6], dfcol); break; + case 3: qfloor(c, Vf, shTriFloor[ct6], dfcol); break; + case 4: qfloor(c, Vf, shSStarFloor[ct6], dfcol); break; + case 5: qfloor(c, Vf, shOverFloor[ECT], dfcol); break; + case 6: qfloor(c, Vf, shFeatherFloor[ECT], dfcol); break; + case 7: qfloor(c, Vf, shDemonFloor[ct6], dfcol); break; + case 8: qfloor(c, Vf, shCrossFloor[ct6], dfcol); break; + case 9: qfloor(c, Vf, shMFloor[ct6], dfcol); break; + case 10: qfloor(c, Vf, shCaveFloor[ECT], dfcol); break; + case 11: qfloor(c, Vf, shPowerFloor[ct6], dfcol); break; + case 12: qfloor(c, Vf, shDesertFloor[ct6], dfcol); break; + case 13: qfloor(c, Vf, purehepta ? shChargedFloor[3] : shChargedFloor[ct6], dfcol); break; + case 14: qfloor(c, Vf, ct==6?shChargedFloor[2]:shFloor[1], dfcol); break; } } + + // else if(c->land == laPrairie && !eoh && allemptynear(c) && fieldpattern::getflowerdist(c) <= 1) + // queuepoly(Vf, shLeafFloor[ct6], darkena(fcol, fd, 0xFF)); + + /* else if(c->land == laPrairie && prairie::isriver(c)) + drawTowerFloor(Vf, c, darkena(fcol, fd, 0xFF), + prairie::isleft(c) ? river::towerleft : river::towerright); */ + + else if(c->land == laPrairie) + qfloor(c, Vf, shCloudFloor[ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laWineyard) { - queuepoly(V, shFeatherFloor[euclid?2:ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shFeatherFloor[euclid?2:ct6], darkena(fcol, fd, 0xFF)); } else if(c->land == laZebra) - drawZebraFloor(V, c, darkena(xcol, fd, 0xFF)); + drawZebraFloor(Vf, c, darkena(fcol, fd, 0xFF)); else if(c->wall == waTrunk) - queuepoly(V, shFloor[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shFloor[ct6], darkena(fcol, fd, 0xFF)); else if(c->wall == waCanopy || c->wall == waSolidBranch || c->wall == waWeakBranch) - queuepoly(V, shFeatherFloor[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shFeatherFloor[ct6], darkena(fcol, fd, 0xFF)); + + else if(c->land == laMountain) + drawTowerFloor(Vf, c, darkena(fcol, fd, 0xFF), + euclid ? celldist : c->master->alt ? celldistAltPlus : celldist); else if(isGravityLand(c->land)) - drawTowerFloor(V, c, darkena(xcol, fd, 0xFF)); + drawTowerFloor(Vf, c, darkena(fcol, fd, 0xFF)); else if(c->land == laEmerald) - drawEmeraldFloor(V, c, darkena(xcol, fd, 0xFF)); + drawEmeraldFloor(Vf, c, darkena(fcol, fd, 0xFF)); else if(c->land == laRlyeh) - queuepoly(V, (eoh ? shFloor: shTriFloor)[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, (eoh ? shFloor: shTriFloor)[ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laTemple) - queuepoly(V, (eoh ? shFloor: shTriFloor)[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, (eoh ? shFloor: shTriFloor)[ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laAlchemist) - queuepoly(V, shCloudFloor[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shCloudFloor[ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laRose) - queuepoly(V, shRoseFloor[purehepta ? 2 : ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shRoseFloor[purehepta ? 2 : ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laTortoise) - queuepoly(V, shTurtleFloor[purehepta ? 2 : ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shTurtleFloor[purehepta ? 2 : ct6], darkena(fcol, fd, 0xFF)); - else if(c->land == laDragon && !purehepta) - queuepoly(V, shDragonFloor[euclid?2:ct-6], darkena(xcol, fd, 0xFF)); + else if(c->land == laDragon && !purehepta) { + /* if(!wmspatial || noAdjacentChasms(c)) */ + qfloor(c, Vf, shDragonFloor[euclid?2:ct6], darkena(fcol, fd, 0xFF)); + /* if(wmspatial) + qfloor(c, Vf, shFloor[euclid?2:ct6], darkena(fcol, fd, 0xFF)); */ + } else if((isElemental(c->land) || c->land == laElementalWall) && !eoh) - queuepoly(V, shNewFloor[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shNewFloor[ct6], darkena(fcol, fd, 0xFF)); + + else if(c->land == laBurial) + qfloor(c, Vf, shBarrowFloor[euclid?0:purehepta?2:ct6], darkena(fcol, fd, 0xFF)); + + else if(c->land == laTrollheim && !eoh) + qfloor(c, Vf, shTrollFloor[ct6], darkena(fcol, fd, 0xFF)); + + else if(c->land == laTrollheim) + qfloor(c, Vf, shCaveFloor[euclid?2:1], darkena(fcol, fd, 0xFF)); else if(c->land == laJungle) - queuepoly(V, shFeatherFloor[euclid?2:ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shFeatherFloor[euclid?2:ct6], darkena(fcol, fd, 0xFF)); + + else if(c->land == laMountain) + qfloor(c, Vf, shFeatherFloor[euclid?2:ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laGraveyard) - queuepoly(V, (eoh ? shFloor : shCrossFloor)[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, (eoh ? shFloor : shCrossFloor)[ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laDeadCaves) { - queuepoly(V, shCaveFloor[euclid?2:ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shCaveFloor[euclid?2:ct6], darkena(fcol, fd, 0xFF)); } else if(c->land == laMotion) - queuepoly(V, shMFloor[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shMFloor[ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laWhirlwind) -// drawZebraFloor(V, c, darkena(xcol, fd, 0xFF)); - queuepoly(V, (eoh ? shCloudFloor : shNewFloor)[ct-6], darkena(xcol, fd, 0xFF)); +// drawZebraFloor(V, c, darkena(fcol, fd, 0xFF)); + qfloor(c, Vf, (eoh ? shCloudFloor : shNewFloor)[ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laHell) - queuepoly(V, (euclid ? shStarFloor : shDemonFloor)[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, (euclid ? shStarFloor : shDemonFloor)[ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laIce) -// queuepoly(V, shFloor[ct-6], darkena(xcol, 2, 0xFF)); - queuepoly(V, shStarFloor[ct-6], darkena(xcol, fd, 0xFF)); +// qfloor(c, V, shFloor[ct6], darkena(fcol, 2, 0xFF)); + qfloor(c, Vf, shStarFloor[ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laCocytus) - queuepoly(V, (eoh ? shCloudFloor : shDesertFloor)[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, (eoh ? shCloudFloor : shDesertFloor)[ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laStorms) { if(euclid) - queuepoly(ishex1(c) ? V*spin(M_PI) : V, - ishept(c) ? shFloor[0] : shChargedFloor[2], darkena(xcol, fd, 0xFF)); + qfloor(c, ishex1(c) ? V*pispin : Vf, + ishept(c) ? shFloor[0] : shChargedFloor[2], darkena(fcol, fd, 0xFF)); else - queuepoly(V, (purehepta ? shChargedFloor[3] : ct==6 ? shChargedFloor[2] : shFloor[1]), darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, (purehepta ? shChargedFloor[3] : ct==6 ? shChargedFloor[2] : shFloor[1]), darkena(fcol, fd, 0xFF)); } else if(c->land == laWildWest) - queuepoly(V, (eoh ? shCloudFloor : shSStarFloor)[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, (eoh ? shCloudFloor : shSStarFloor)[ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laPower) - queuepoly(V, (eoh ? shStarFloor : shPowerFloor)[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, (eoh ? shStarFloor : shPowerFloor)[ct6], darkena(fcol, fd, 0xFF)); - else if(c->land == laHive && !isWateryOrBoat(c) && c->wall != waFloorB && c->wall != waFloorA && - c->wall != waMirror && c->wall != waCloud) { - queuepoly(V, shFloor[ct-6], darkena(xcol, 1, 0xFF)); - if(!snakelevel(c) && c->wall != waMirror && c->wall != waCloud) - queuepoly(V, shMFloor[ct-6], darkena(xcol, 2, 0xFF)); - if(c->wall != waWaxWall && c->wall != waDeadTroll && c->wall != waDeadTroll2 && c->wall != waVinePlant && - !snakelevel(c) && c->wall != waMirror && c->wall != waCloud && - c->wall != waStrandedBoat) - queuepoly(V, shMFloor2[ct-6], darkena(xcol, xcol==ycol ? 1 : 2, 0xFF)); + else if(c->land == laHive && c->wall != waFloorB && c->wall != waFloorA && c->wall != waMirror && c->wall != waCloud) { + qfloor(c, Vf, shFloor[ct6], darkena(fcol, 1, 0xFF)); + if(c->wall != waMirror && c->wall != waCloud) + qfloor(c, Vf, shMFloor[ct6], darkena(fcol, 2, 0xFF)); + if(c->wall != waMirror && c->wall != waCloud) + qfloor(c, Vf, shMFloor2[ct6], darkena(fcol, fcol==wcol ? 1 : 2, 0xFF)); } else if(c->land == laCaves) - queuepoly(V, shCaveFloor[ECT], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shCaveFloor[ECT], darkena(fcol, fd, 0xFF)); else if(c->land == laDesert) - queuepoly(V, (eoh ? shCloudFloor : shDesertFloor)[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, (eoh ? shCloudFloor : shDesertFloor)[ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laOvergrown || c->land == laClearing || isHaunted(c->land)) - queuepoly(V, shOverFloor[ECT], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shOverFloor[ECT], darkena(fcol, fd, 0xFF)); else if(c->land == laRose) - queuepoly(V, shOverFloor[ECT], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shOverFloor[ECT], darkena(fcol, fd, 0xFF)); else if(c->land == laDryForest) - queuepoly(V, (eoh ? shStarFloor : shDesertFloor)[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, (eoh ? shStarFloor : shDesertFloor)[ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laCaribbean || c->land == laOcean || c->land == laOceanWall || c->land == laWhirlpool) - queuepoly(V, shCloudFloor[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shCloudFloor[ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laLivefjord) - queuepoly(V, shCaveFloor[ECT], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shCaveFloor[ECT], darkena(fcol, fd, 0xFF)); else if(c->land == laRedRock) - queuepoly(V, eoh ? shFloor[ct-6] : shDesertFloor[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, eoh ? shFloor[ct6] : shDesertFloor[ct6], darkena(fcol, fd, 0xFF)); else if(c->land == laPalace) - queuepoly(V, (eoh?shFloor:shPalaceFloor)[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, (eoh?shFloor:shPalaceFloor)[ct6], darkena(fcol, fd, 0xFF)); else { - queuepoly(V, shFloor[ct-6], darkena(xcol, fd, 0xFF)); + qfloor(c, Vf, shFloor[ct6], darkena(fcol, fd, 0xFF)); } // walls -#ifndef MOBILE +#ifndef NOEDIT + + if(viewdists) { + string label = its(celldistance(c, cwt.c)); + // string label = its(fieldpattern::getriverdistleft(c)) + its(fieldpattern::getriverdistright(c)); + queuestr(V, .5, label, 0xFFFFFFFF); + } + if(cmode == emMapEditor && mapeditor::displaycodes) { int labeli = mapeditor::displaycodes == 1 ? mapeditor::realpattern(c) : mapeditor::subpattern(c); string label = its(labeli); - int siz = int(sqrt(squar(xc-xs)+squar(yc-ys))) / 5; - queuestr(xc, yc, sc, siz, label, 0xFFFFFFFF); + if(svg::in) + queuestr(V, .5, label, 0xFF000000); + else + queuestr(V, .2, label, 0xFFFFFFFF); /* transmatrix V2 = V * applyPatterndir(c); - queuepoly(V2, shNecro, 0x80808080); - queuepoly(V2, shStatue, 0x80808080); */ + qfloor(c, V2, shNecro, 0x80808080); + qfloor(c, V2, shStatue, 0x80808080); */ } #endif - if(realred(c->wall)) { - int s = snakelevel(c); - if(s >= 1) - queuepoly(V, shRedRockFloor[0][ct-6], darkena(winf[waRed1].color, 0, 0xFF)); - if(s >= 2) - queuepoly(V, shRedRockFloor[1][ct-6], darkena(winf[waRed2].color, 0, 0xFF)); - if(s >= 3) - queuepoly(V, shRedRockFloor[2][ct-6], darkena(winf[waRed3].color, 0, 0xFF)); + if(cmode == emNumber && (dialog::editingDetail())) { + int col = + dist0 < geom3::highdetail ? 0xFF80FF80 : + dist0 >= geom3::middetail ? 0xFFFF8080 : + 0XFFFFFF80; +#if 1 + queuepoly(V, shHeptaMarker, darkena(col & 0xFFFFFF, 0, 0xFF)); +#else + char buf[64]; + sprintf(buf, "%3.1f", float(dist0)); + + + queuestr(V, .6, buf, col); +#endif } - if(pseudohept(c) && (c->land == laRedRock || (purehepta && (c->land == laClearing || isWarped(c))))) { - queuepoly(V, shHeptaMarker, 0x00000080); + if(realred(c->wall) && !wmspatial) { + int s = snakelevel(c); + if(s >= 1) + qfloor(c, V, shRedRockFloor[0][ct6], darkena(winf[waRed1].color, 0, 0xFF)); + if(s >= 2) + queuepoly(V, shRedRockFloor[1][ct6], darkena(winf[waRed2].color, 0, 0xFF)); + if(s >= 3) + queuepoly(V, shRedRockFloor[2][ct6], darkena(winf[waRed3].color, 0, 0xFF)); + } + + if(c->wall == waTower && !wmspatial) { + qfloor(c, V, shMFloor[ct6], darkena(0xE8E8E8, fd, 0xFF)); + } + + if(pseudohept(c) && ( + c->land == laRedRock || + vid.darkhepta || + (purehepta && (c->land == laClearing || isWarped(c))))) { + queuepoly((*Vdp), shHeptaMarker, wmblack ? 0x80808080 : 0x00000080); } if(conformal::includeHistory && eq(c->aitmp, sval-1)) - queuepoly(V, shHeptaMarker, 0x000000C0); + queuepoly((*Vdp), shHeptaMarker, 0x000000C0); - /* if(c->land == laBarrier || c->land == laCrossroads2) { - int siz = int(sqrt(squar(xc-xs)+squar(yc-ys))) / 5; - displaystr(xc, yc, sc, siz, its(int(c->heat + .5)), 0xFFFFFFFF, 8); - } */ - char xch = winf[c->wall].glyph; + + if(c->wall == waBigBush) { + if(detaillevel >= 2) + queuepolyat(mmscale(V, zgrad0(0, geom3::slev, 1, 2)), shHeptaMarker, darkena(wcol, 0, 0xFF), PPR_REDWALL); + if(detaillevel >= 1) + queuepolyat(mmscale(V, geom3::SLEV[1]) * pispin, shWeakBranch, darkena(wcol, 0, 0xFF), PPR_REDWALL+1); + if(detaillevel >= 2) + queuepolyat(mmscale(V, zgrad0(0, geom3::slev, 3, 2)), shHeptaMarker, darkena(wcol, 0, 0xFF), PPR_REDWALL+2); + queuepolyat(mmscale(V, geom3::SLEV[2]), shSolidBranch, darkena(wcol, 0, 0xFF), PPR_REDWALL+3); + } - if(c->wall == waSolidBranch) { - queuepoly(V, shSolidBranch, 0x804000FF); + else if(c->wall == waSmallBush) { + if(detaillevel >= 2) + queuepolyat(mmscale(V, zgrad0(0, geom3::slev, 1, 2)), shHeptaMarker, darkena(wcol, 0, 0xFF), PPR_REDWALL); + if(detaillevel >= 1) + queuepolyat(mmscale(V, geom3::SLEV[1]) * pispin, shWeakBranch, darkena(wcol, 0, 0xFF), PPR_REDWALL+1); + if(detaillevel >= 2) + queuepolyat(mmscale(V, zgrad0(0, geom3::slev, 3, 2)), shHeptaMarker, darkena(wcol, 0, 0xFF), PPR_REDWALL+2); + queuepolyat(mmscale(V, geom3::SLEV[2]), shWeakBranch, darkena(wcol, 0, 0xFF), PPR_REDWALL+3); + } + + else if(c->wall == waSolidBranch) { + queuepoly(V, shSolidBranch, darkena(wcol, 0, 0xFF)); } else if(c->wall == waWeakBranch) { - queuepoly(V, shWeakBranch, 0x804000FF); + queuepoly(V, shWeakBranch, darkena(wcol, 0, 0xFF)); } else if(c->wall == waLadder) { if(euclid) { - queuepoly(V, shMFloor[ct-6], 0x804000FF); - queuepoly(V, shMFloor2[ct-6], 0x000000FF); + queuepoly(V, shMFloor[ct6], 0x804000FF); + queuepoly(V, shMFloor2[ct6], 0x000000FF); } else { - queuepoly(V, shFloor[ct-6], 0x804000FF); - queuepoly(V, shMFloor[ct-6], 0x000000FF); + queuepolyat(V, shFloor[ct6], 0x804000FF, PPR_FLOOR+1); + queuepolyat(V, shMFloor[ct6], 0x000000FF, PPR_FLOOR+2); } } - if(c->wall == waBoat || c->wall == waStrandedBoat) { - int hdir = displaydir(c, c->mondir); - transmatrix V2 = V * spin((42+hdir) * M_PI / 42); - if(items[itOrbWater] && (c == cwt.c || (isFriendly(c) && items[itOrbEmpathy]))) { - queuepoly(V2, shBoatOuter, watercolor(0)); - queuepoly(V2, shBoatInner, 0x0060C0FF); - } - else { - queuepoly(V2, shBoatOuter, 0xC06000FF); - queuepoly(V2, shBoatInner, 0x804000FF); - } + if(c->wall == waReptileBridge) { + Vboat = &(Vboat0 = V); + dynamicval qfi2(qfi, qfi); + int col = reptilecolor(c); + chasmg = 0; + drawReptileFloor(V, c, col, true); + forCellIdEx(c2, i, c) if(chasmgraph(c2)) + placeSidewallX(c, i, SIDE_LAKE, V, isWarped(c), false, darkena(gradient(0, col, 0, .8, 1), fd, 0xFF)); + chasmg = 1; } - else if(c->wall == waBigStatue) - queuepoly(V, shStatue, - darkena(winf[c->wall].color, 0, 0xFF) + if(c->wall == waBoat || c->wall == waStrandedBoat) { + double footphase; + bool magical = items[itOrbWater] && (isPlayerOn(c) || (isFriendly(c) && items[itOrbEmpathy])); + int outcol = magical ? watercolor(0) : 0xC06000FF; + int incol = magical ? 0x0060C0FF : 0x804000FF; + bool nospin = false; + + Vboat = &(Vboat0 = *Vboat); + if(wmspatial && c->wall == waBoat) { + nospin = c->wall == waBoat && applyAnimation(c, Vboat0, footphase, LAYER_BOAT); + if(!nospin) Vboat0 = Vboat0 * ddspin(c, c->mondir, S42); + queuepolyat(Vboat0, shBoatOuter, outcol, PPR_BOATLEV); + Vboat = &(Vboat0 = V); + } + if(c->wall == waBoat) { + nospin = applyAnimation(c, Vboat0, footphase, LAYER_BOAT); + } + if(!nospin) + Vboat0 = Vboat0 * ddspin(c, c->mondir, S42); + else { + transmatrix Vx; + if(applyAnimation(c, Vx, footphase, LAYER_SMALL)) + animations[LAYER_SMALL][c].footphase = 0; + } + if(wmspatial) + queuepolyat(mscale(Vboat0, (geom3::LAKE+1)/2), shBoatOuter, outcol, PPR_BOATLEV2); + queuepoly(Vboat0, shBoatOuter, outcol); + queuepoly(Vboat0, shBoatInner, incol); + } + + else if(c->wall == waBigStatue) { + transmatrix V2 = V; + double footphase; + applyAnimation(c, V2, footphase, LAYER_BOAT); + + queuepolyat(V2, shStatue, + darkena(winf[c->wall].color, 0, 0xFF), + PPR_BIGSTATUE ); + } else if(c->wall == waSulphurC) { - bool drawStar = true; - for(int t=0; ttype; t++) - if(c->mov[t] && c->mov[t]->wall != waSulphur && c->mov[t]->wall != waSulphurC && - c->mov[t]->wall != waBarrier) - drawStar = false; - if(drawStar) queuepoly(V, shGiantStar[ct-6], darkena(xcol, 0, 0xFF)); + if(drawstar(c)) { + if(wmspatial) + queuepolyat(mscale(V, geom3::HELLSPIKE), shGiantStar[ct6], darkena(wcol, 0, 0x40), PPR_HELLSPIKE); + else + queuepoly(V, shGiantStar[ct6], darkena(wcol, 0, 0xFF)); + } } else if(c->wall == waClosePlate || c->wall == waOpenPlate || (c->wall == waTrapdoor && c->land != laZebra)) { transmatrix V2 = V; - if(ct == 7 && vid.wallmode == 3) V2 = V * spin(M_PI); - queuepoly(V2, shMFloor[ct-6], darkena(winf[c->wall].color, 0, 0xFF)); - queuepoly(V2, shMFloor2[ct-6], vid.wallmode >= 2 ? darkena(xcol, 1, 0xFF) : darkena(0,1,0xFF)); + if(ct != 6 && wmescher) V2 = V * pispin; + queuepoly(V2, shMFloor[ct6], darkena(winf[c->wall].color, 0, 0xFF)); + queuepoly(V2, shMFloor2[ct6], (!wmblack) ? darkena(fcol, 1, 0xFF) : darkena(0,1,0xFF)); } else if(c->wall == waFrozenLake || c->wall == waLake || c->wall == waCamelotMoat || - c->wall == waRoundTable || c->wall == waSea || c->wall == waClosePlate || c->wall == waOpenPlate || + c->wall == waSea || c->wall == waClosePlate || c->wall == waOpenPlate || c->wall == waOpenGate || c->wall == waTrapdoor) ; else if(c->wall == waRose) { - xcol <<= 1; + wcol <<= 1; if(c->cpdist > 5) - xcol = 0xC0C0C0; + wcol = 0xC0C0C0; else if(rosephase == 7) - xcol = 0xFF0000; + wcol = 0xFF0000; else - xcol = gradient(xcol, 0xC00000, 0, rosephase, 6); + wcol = gradient(wcol, 0xC00000, 0, rosephase, 6); queuepoly(V, shThorns, 0xC080C0FF); for(int u=0; u<4; u+=2) - queuepoly(V * spin(2*M_PI / 3 / 4 * u), shRose, darkena(xcol, 0, 0xC0)); + queuepoly(V * spin(2*M_PI / 3 / 4 * u), shRose, darkena(wcol, 0, 0xC0)); + } + + else if(sl && wmspatial) { + + bool w = isWarped(c); + warpfloor(c, (*Vdp), darkena(wcol, fd, 0xFF), PPR_REDWALL-4+4*sl, w); + floorShadow(c, V, SHADOW_SL * sl, w); + bool tower = c->wall == waTower; + for(int s=0; s= sl2) + placeSidewallX(c, i, SIDE_SLEV+s, V, w, false, + darkena(tower?0xD0D0D0-i*0x101010 : s==sl-1?wcol:winf[waRed1+s].color, fd, 0xFF)); + } } - else if(xch == '#') { - if(c->wall == waVinePlant) - xcol = 0x60C000; - if(c->wall != waPlatform && c->wall != waWarpGate) - queuepoly(V, shWall[ct-6], darkena(xcol, 0, 0xFF)); + else if(c->wall == waRoundTable) ; + + else if(c->wall == waGlass && wmspatial) { + int col = winf[waGlass].color; + int dcol = darkena(col, 0, 0x80); + transmatrix Vdepth = mscale((*Vdp), geom3::WALL); + queuepolyat(Vdepth, shMFloor[ct6], dcol, PPR_GLASS); + if(validsidepar[SIDE_WALL]) forCellIdEx(c2, i, c) + placeSidewall(c, i, SIDE_WALL, (*Vdp), false, true, dcol); + } + + else if(c->wall == waGlass && !wmspatial) ; + + else if(wmescher && wmspatial && c->wall == waBarrier && c->land == laOceanWall) { + const int layers = 2 << detaillevel; + dynamicval ds(qfi.shape, &shCircleFloor); + dynamicval db(qfi.special, true); + for(int z=1; zwall == waWarpGate) starcol = 0; + if(c->wall == waVinePlant) starcol = 0x60C000; + + int wcol2 = gradient(0, wcol0, 0, .8, 1); + + if(c->wall == waClosedGate) { + int hdir = 0; + for(int i=0; itype; i++) if(c->mov[i]->wall == waClosedGate) + hdir = i; + transmatrix V2 = mscale(V, wmspatial?geom3::WALL:1) * ddspin(c, hdir, S42); + queuepolyat(V2, shPalaceGate, darkena(wcol, 0, 0xFF), wmspatial?PPR_WALL3A:PPR_WALL); + starcol = 0; + } + + hpcshape& shThisWall = isGrave(c->wall) ? shCross : shWall[ct6]; + + if(conegraph(c)) { + + const int layers = 2 << detaillevel; + for(int z=1; zwall == waPlatform)) + queuepolyat(Vdepth, shThisWall, darkena(starcol, 0, 0xFF), PPR_WALL3A); + + warpfloor(c, Vdepth, darkena(wcol0, fd, 0xFF), PPR_WALL3, warp); + floorShadow(c, V, SHADOW_WALL, warp); + + if(c->wall == waCamelot) { + forCellIdEx(c2, i, c) { + placeSidewallX(c, i, SIDE_SLEV, V, warp, false, darkena(wcol2, fd, 0xFF)); + } + forCellIdEx(c2, i, c) { + placeSidewallX(c, i, SIDE_SLEV+1, V, warp, false, darkena(wcol2, fd, 0xFF)); + } + forCellIdEx(c2, i, c) { + placeSidewallX(c, i, SIDE_SLEV+2, V, warp, false, darkena(wcol2, fd, 0xFF)); + } + forCellIdEx(c2, i, c) { + placeSidewallX(c, i, SIDE_WTS3, V, warp, false, darkena(wcol2, fd, 0xFF)); + } + } + else forCellIdEx(c2, i, c) { + if(!highwall(c2) || conegraph(c2)) + { placeSidewallX(c, i, SIDE_WALL, V, warp, false, darkena(wcol2, fd, 0xFF)); } + } + } + } } else if(c->wall == waFan) { - queuepoly(V * spin(M_PI/6 - fanframe * M_PI / 3), shFan, darkena(xcol, 0, 0xFF)); + queuepoly(V * spin(M_PI/6 - fanframe * M_PI / 3), shFan, darkena(wcol, 0, 0xFF)); } else if(xch == '%') { if(doHighlight()) poly_outline = (c->land == laMirror) ? OUTLINE_TREASURE : OUTLINE_ORB; - queuepoly(V, shMirror, darkena(xcol, 0, 0xC0)); + + if(wmspatial) { + int col = winf[c->wall].color; + int dcol = darkena(col, 0, 0xC0); + transmatrix Vdepth = mscale((*Vdp), geom3::WALL); + queuepolyat(Vdepth, shMFloor[ct6], dcol, PPR_GLASS); + if(validsidepar[SIDE_WALL]) forCellIdEx(c2, i, c) + placeSidewall(c, i, SIDE_WALL, (*Vdp), false, true, dcol); + } + else { + queuepoly(V, shMirror, darkena(wcol, 0, 0xC0)); + } poly_outline = OUTLINE_NONE; } else if(isFire(c) || isThumper(c) || c->wall == waBonfireOff) { ld sp = 0; if(hasTimeout(c)) sp = ticks / (c->land == laPower ? 5000. : 500.); - queuepoly(V * spin(sp), shStar, darkena(col, 0, 0xF0)); + queuepoly(V * spin(sp), shStar, darkena(wcol, 0, 0xF0)); + if(isFire(c) && rand() % 300 < ticks - lastt) + drawParticle(c, wcol, 75); } - else if(xch == '+' && (c->land == laGraveyard || isHaunted(c->land)) && c->wall != waFloorB && c->wall != waFloorA && - c->wall != waFloorC && c->wall != waFloorD) - queuepoly(V, shCross, darkena(xcol, 0, 0xFF)); - - else if(xch == '+' && c->wall == waClosedGate) { - int hdir = 0; - for(int i=0; itype; i++) if(c->mov[i]->wall == waClosedGate) - hdir = i; - hdir = displaydir(c, hdir); - transmatrix V2 = V * spin((42+hdir) * M_PI / 42); - queuepoly(V2, shPalaceGate, darkena(xcol, 0, 0xFF)); - } + else if(c->wall == waFreshGrave || c->wall == waAncientGrave) + queuepoly(V, shCross, darkena(wcol, 0, 0xFF)); else if(xch == '+' && c->wall == waGiantRug) { queuepoly(V, shBigCarpet1, darkena(0xC09F00, 0, 0xFF)); @@ -3113,47 +4646,138 @@ void drawcell(cell *c, const transmatrix& V, int spinv) { queuepoly(V, shBigCarpet3, darkena(0xC09F00, 0, 0xFF)); } - else if(xch != '.' && xch != '+' && xch != '>' && xch != ':' && xch != ';' && c->wall != waSulphur && xch != ',') + else if(xch != '.' && xch != '+' && xch != '>' && xch != ':'&& xch != '-' && xch != ';' && c->wall != waSulphur && xch != ',') 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(!(it || c->monst || c->cpdist == 0)) error = true; + + int sha = shallow(c); + + if(wmspatial && sha) { + bool w = isWarped(c); + int col = (highwall(c) || c->wall == waTower) ? wcol : fcol; + if(!chasmg) { + if(sha & 1) { + forCellIdEx(c2, i, c) if(chasmgraph(c2)) + placeSidewallX(c, i, SIDE_LAKE, V, w, false, darkena(gradient(0, col, 0, .8, 1), fd, 0xFF)); + } + if(sha & 2) { + forCellIdEx(c2, i, c) if(chasmgraph(c2)) + placeSidewallX(c, i, SIDE_LTOB, V, w, false, darkena(gradient(0, col, 0, .7, 1), fd, 0xFF)); + } + if(sha & 4) { + bool dbot = true; + forCellIdEx(c2, i, c) if(chasmgraph(c2) == 2) { + if(dbot) dbot = false, + warpfloor(c, mscale(V, geom3::BOTTOM), 0x080808FF, PPR_LAKEBOTTOM, isWarped(c)); + placeSidewallX(c, i, SIDE_BTOI, V, w, false, darkena(gradient(0, col, 0, .6, 1), fd, 0xFF)); + } + } + } + // wall between lake and chasm -- no Escher here + if(chasmg == 1) forCellIdEx(c2, i, c) if(chasmgraph(c2) == 2) { + placeSidewall(c, i, SIDE_LAKE, V, w, false, 0x202030FF); + placeSidewall(c, i, SIDE_LTOB, V, w, false, 0x181820FF); + placeSidewall(c, i, SIDE_BTOI, V, w, false, 0x101010FF); + } + } + + if(chasmg == 1 && wmspatial) { + int fd0 = fd ? fd-1 : 0; + warpfloor(c, (*Vdp), darkena(fcol, fd0, 0x80), PPR_LAKELEV, isWarped(c)); + } + + if(chasmg) { + int q = size(ptds); + if(fallanims.count(c)) { + fallanim& fa = fallanims[c]; + bool erase = true; + if(fa.t_floor) { + int t = (ticks - fa.t_floor); + if(t <= 1500) { + erase = false; + if(fa.walltype == waNone) + warpfloor(c, V, darkena(fcol, fd, 0xFF), PPR_FLOOR, isWarped(c)); + else { + int wcol2, fcol2; + eWall w = c->wall; int p = c->wparam; + c->wall = fa.walltype; c->wparam = fa.m; + setcolors(c, wcol2, fcol2); + int starcol = c->wall == waVinePlant ? 0x60C000 : wcol2; + c->wall = w; c->wparam = p; + bool warp = isWarped(c); + warpfloor(c, mscale(V, geom3::WALL), darkena(starcol, fd, 0xFF), PPR_WALL3, warp); + queuepolyat(mscale(V, geom3::WALL), shWall[ct6], darkena(wcol2, 0, 0xFF), PPR_WALL3A); + forCellIdEx(c2, i, c) + placeSidewallX(c, i, SIDE_WALL, V, warp, false, darkena(wcol2, 1, 0xFF)); + } + pushdown(c, q, V, t*t / 1000000. + t / 1000., true, true); + } + } + if(fa.t_mon) { + int t = (ticks - fa.t_mon); + if(t <= 1500) { + erase = false; + c->stuntime = 0; + transmatrix V2 = V; + double footphase = t / 200.0; + applyAnimation(c, V2, footphase, LAYER_SMALL); + drawMonsterType(fa.m, c, V2, minf[fa.m].color, footphase); + pushdown(c, q, V2, t*t / 1000000. + t / 1000., true, true); + } + } + if(erase) fallanims.erase(c); + } + } if(c->wall == waMineOpen) { int mines = countMinesAround(c); - if(vid.wallmode == 0) { + if(wmascii) { if(ch == '.') { if(mines == 0) ch = ' '; - else ch = '0' + mines, col = minecolors[mines]; + else ch = '0' + mines, asciicol = minecolors[mines]; } - else if(ch == '@') col = minecolors[mines]; + else if(ch == '@') asciicol = minecolors[mines]; } else if(mines > 0) - queuepoly(V, shMineMark[c->type-6], (minecolors[mines] << 8) | 0xFF); + queuepoly(V, shMineMark[ct6], (minecolors[mines] << 8) | 0xFF); } // treasure char xch = iinf[it].glyph; hpcshape *xsh = - it == itPirate ? &shPirateX : + (it == itPirate || it == itKraken) ? &shPirateX : (it == itBuggy || it == itBuggy2) ? &shPirateX : it == itHolyGrail ? &shGrail : isElementalShard(it) ? &shElementalShard : - xch == '*' ? &shGem[ct-6] : xch == '%' ? &shDaisy : xch == '$' ? &shStar : xch == ';' ? &shTriangle : + (it == itBombEgg || it == itTrollEgg) ? &shEgg : + it == itDodeca ? &shDodeca : + xch == '*' ? &shGem[ct6] : + it == itTreat ? &shTreat : + it == itSlime ? &shEgg : + xch == '%' ? &shDaisy : xch == '$' ? &shStar : xch == ';' ? &shTriangle : xch == '!' ? &shTriangle : it == itBone ? &shNecro : it == itStatue ? &shStatue : - it == itEdge ? &shFigurine : + it == itIvory ? &shFigurine : xch == '?' ? &shBookCover : it == itKey ? &shKey : it == itRevolver ? &shGun : NULL; + + if(c->land == laWhirlwind && c->wall != waBoat) { + double footphase = 0; + Vboat = &(Vboat0 = *Vboat); + applyAnimation(c, Vboat0, footphase, LAYER_BOAT); + } + + if(it && cellHalfvine(c)) { + int i =-1; + for(int t=0;t<6; t++) if(c->mov[t] && c->mov[t]->wall == c->wall) + i = t; + + Vboat = &(Vboat0 = *Vboat * ddspin(c, i) * xpush(-.13)); + } if(doHighlight()) { int k = itemclass(it); @@ -3167,120 +4791,106 @@ void drawcell(cell *c, const transmatrix& V, int spinv) { if(conformal::includeHistory && eq(c->aitmp, sval)) poly_outline = OUTLINE_DEAD; -#ifndef MOBILE +#ifndef NOEDIT if(c == mapeditor::drawcell && mapeditor::drawcellShapeGroup() == 2) mapeditor::drawtrans = V; #endif - if(vid.monmode == 0 && it) + if(!mmitem && it) error = true; else if(it == itBabyTortoise) { int bits = tortoise::babymap[c]; - tortoise::draw(V * spin(ticks / 5000.) * ypush(crossf*.15), bits, 2, 0); + int over = c->monst == moTortoise; + tortoise::draw(*Vboat * spin(ticks / 5000.) * ypush(crossf*.15), bits, over ? 4 : 2, 0); // queuepoly(V, shHeptaMarker, darkena(tortoise::getMatchColor(bits), 0, 0xC0)); } else if(it == itCompass) { - transmatrix V2 = V; - if(euclid) V2 = V2 * spin(M_PI/2); - else V2 = V2 * rspintox(inverse(V) * pirateCoords * C0); - V2 = V2 * spin(M_PI * sin(ticks/100.) / 30); - queuepoly(V2, shCompass1, 0xFF8080FF); - queuepoly(V2, shCompass2, 0xFFFFFFFF); - queuepoly(V2, shCompass3, 0xFF0000FF); - queuepoly(V2 * spin(M_PI), shCompass3, 0x000000FF); + if(euclid) Vboat0 = (*Vdp) * spin(M_PI/2); // todo incorrect + else Vboat0 = *Vboat * rspintox(inverse(*Vboat) * pirateCoords); + Vboat0 = Vboat0 * spin(M_PI * sin(ticks/100.) / 30); + queuepoly(Vboat0, shCompass1, 0xFF8080FF); + queuepoly(Vboat0, shCompass2, 0xFFFFFFFF); + queuepoly(Vboat0, shCompass3, 0xFF0000FF); + queuepoly(Vboat0 * pispin, shCompass3, 0x000000FF); xsh = NULL; } else if(it == itPalace) { - transmatrix V2 = V * spin(ticks / 1500.); - queuepoly(V2, shMFloor3[ct-6], 0xFFD500FF); - queuepoly(V2, shMFloor4[ct-6], darkena(icol, 0, 0xFF)); - queuepoly(V2, shGem[ct-6], 0xFFD500FF); + Vboat0 = *Vboat * spin(ticks / 1500.); + queuepoly(Vboat0, shMFloor3[ct6], 0xFFD500FF); + queuepoly(Vboat0, shMFloor4[ct6], darkena(icol, 0, 0xFF)); + queuepoly(Vboat0, shGem[ct6], 0xFFD500FF); xsh = NULL; } - else if(drawUserShape(V, 2, it, darkena(icol, 0, 0xFF))) ; + else if(drawUserShape(*Vboat, 2, it, darkena(icol, 0, 0xFF))) ; else if(it == itRose) { for(int u=0; u<4; u++) - queuepoly(V * spin(ticks / 1500.) * spin(2*M_PI / 3 / 4 * u), shRose, darkena(icol, 0, hidden ? 0x30 : 0xA0)); + queuepoly(*Vboat * spin(ticks / 1500.) * spin(2*M_PI / 3 / 4 * u), shRose, darkena(icol, 0, hidden ? 0x30 : 0xA0)); } + else if(it == itBarrow) { + for(int i = 0; ilandparam; i++) + queuepolyat(*Vboat * spin(2 * M_PI * i / c->landparam) * xpush(.15) * spin(ticks / 1500.), *xsh, darkena(icol, 0, hidden ? 0x40 : + (highwall(c) && wmspatial) ? 0x60 : 0xFF), + PPR_HIDDEN); + +// queuepoly(V*spin(M_PI+(1-2*ang)*2*M_PI/S84), shMagicSword, darkena(0xC00000, 0, 0x80 + 0x70 * sin(ticks / 200.0))); + } + else if(xsh) { if(it == itFireShard) icol = firecolor(100); if(it == itWaterShard) icol = watercolor(100) >> 8; - if(it == itZebra) icol = 0x202020; + if(it == itZebra) icol = 0xFFFFFF; if(it == itLotus) icol = 0x101010; - queuepoly(V * spin(ticks / 1500.), *xsh, darkena(icol, 0, hidden ? 0x40 : 0xF0)); + queuepoly(*Vboat * spin(ticks / 1500.), *xsh, darkena(icol, 0, hidden ? (it == itKraken ? 0xC0 : 0x40) : 0xF0)); - if(xsh == &shBookCover && vid.monmode) - queuepoly(V * spin(ticks / 1500.), shBook, 0x805020FF); + if(xsh == &shBookCover && mmitem) + queuepoly(*Vboat * spin(ticks / 1500.), shBook, 0x805020FF); if(it == itZebra) - queuepoly(V * spin(ticks / 1500. + M_PI/c->type), *xsh, darkena(0xFFFFFF, 0, hidden ? 0x40 : 0xF0)); + queuepolyat(*Vboat * spin(ticks / 1500. + M_PI/c->type), *xsh, darkena(0x202020, 0, hidden ? 0x40 : 0xF0), PPR_ITEMb); } else if(xch == 'o') { if(it == itOrbFire) icol = firecolor(100); - queuepoly(V, shDisk, darkena(icol, 0, hidden ? 0x20 : 0xC0)); + queuepoly(*Vboat, shDisk, darkena(icol, 0, hidden ? 0x20 : 0xC0)); if(it == itOrbFire) icol = firecolor(200); if(it == itOrbFriend || it == itOrbDiscord) icol = 0xC0C0C0; if(it == itOrbFrog) icol = 0xFF0000; + if(it == itOrbDash) icol = 0xFF0000; if(it == itOrbFreedom) icol = 0xC0FF00; if(it == itOrbAir) icol = 0xFFFFFF; if(it == itOrbUndeath) icol = minf[moFriendlyGhost].color; + if(it == itOrbRecall) icol = 0x101010; hpcshape& sh = isRangedOrb(it) ? shTargetRing : isOffensiveOrb(it) ? shSawRing : isFriendOrb(it) ? shPeaceRing : isUtilityOrb(it) ? shGearRing : + isDirectionalOrb(it) ? shSpearRing : it == itOrb37 ? shHeptaRing : shRing; - queuepoly(V * spin(ticks / 1500.), sh, darkena(icol, 0, int(0x80 + 0x70 * sin(ticks / 300.)))); + queuepoly(*Vboat * spin(ticks / 1500.), sh, darkena(icol, 0, int(0x80 + 0x70 * sin(ticks / 300.)))); } else if(it) error = true; - - - - // 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++) - queueline(V*ddi(a, rad)*C0, V*ddi(a+1, rad)*C0, col); - } - } - if(bigflashat > 0 && c == flashcell) { - int tim = ticks - bigflashat; - if(tim > 2000) bigflashat = 0; - for(int u=0; u<=tim; u++) { - if((u-tim)%50) continue; - if(u < tim-250) continue; - ld rad = u * 3 / 2000.; - rad = rad * (5-rad) * 1.25; - rad *= hexf; - int col = 0xC0FF00; - if(u > 1000) col = gradient(col, 0, 1000, u, 2200); - for(int a=0; a<84; a++) - queueline(V*ddi(a, rad)*C0, V*ddi(a+1, rad)*C0, col); - } + if(true) { + int q = ptds.size(); + error |= drawMonster(V, ct, c, moncol); + if(Vboat != &V && Vboat != &Vboat0 && q != size(ptds)) + pushdown(c, q, V, -geom3::factor_to_lev(zlevel(tC0((*Vboat)))), + !isMultitile(c->monst), false); + } + + if(!shmup::on && sword::at(c)) { + queuepolyat(V, shDisk, 0xC0404040, PPR_SWORDMARK); } - - error |= drawMonster(V, ct, c, col); int ad = airdist(c); if(ad == 1 || ad == 2) { @@ -3289,23 +4899,22 @@ void drawcell(cell *c, const transmatrix& V, int spinv) { cell *c2 = c->mov[i]; if(airdist(c2) < airdist(c)) { calcAirdir(c2); // printf("airdir = %d\n", airdir); - int hdir = displaydir(c, i); - transmatrix V0 = spin((42+hdir) * M_PI / 42); + transmatrix V0 = ddspin(c, i, S42); - double ph = ticks / 75.0 + airdir * M_PI / 21.; + double ph = ticks / (purehepta?150:75.0) + airdir * M_PI / (S21+.0); int aircol = 0x8080FF00 | int(32 + 32 * -cos(ph)); double ph0 = ph/2; ph0 -= floor(ph0/M_PI)*M_PI; - poly_outline = 0; - queuepoly(V*V0*ddi(0, hexf*-cos(ph0)), shDisk, aircol); + poly_outline = OUTLINE_TRANS; + queuepoly((*Vdp)*V0*xpush(hexf*-cos(ph0)), shDisk, aircol); poly_outline = OUTLINE_NONE; } } -// queuepoly(V*ddi(rand() % 84, hexf*(rand()%100)/100), shDisk, aircolor(airdir)); +// queuepoly(V*ddi(rand() % S84, hexf*(rand()%100)/100), shDisk, aircolor(airdir)); } /* int rd = rosedist(c); @@ -3315,18 +4924,18 @@ void drawcell(cell *c, const transmatrix& V, int spinv) { cell *c2 = c->mov[i]; if(rosedist(c2) == rosedist(c)-1) { int hdir = displaydir(c, i); - transmatrix V0 = spin((42+hdir) * M_PI / 42); + transmatrix V0 = spin((S42+hdir) * M_PI / S42); - double ph = ticks / 75.0; // + airdir * M_PI / 21.; + double ph = ticks / 75.0; // + airdir * M_PI / (S21+.0); int rosecol = 0x764e7c00 | int(32 + 32 * -cos(ph)); double ph0 = ph/2; ph0 -= floor(ph0/M_PI)*M_PI; - poly_outline = 0; + poly_outline = OUTLINE_TRANS; queuepoly(V*V0*ddi(0, hexf*-cos(ph0)), shDisk, rosecol); - poly_outline = 0xFF; + poly_outline = OUTLINE_NONE; } } } */ @@ -3335,165 +4944,144 @@ void drawcell(cell *c, const transmatrix& V, int spinv) { whirlwind::calcdirs(c); for(int i=0; i= hdir0+S42) hdir1 -= S84; + + int hdir = (hdir1*ph1+hdir0*(1-ph1)); + + transmatrix V0 = spin((hdir) * M_PI / S42); + + double ldist = purehepta ? crossf : c->type == 6 ? .2840 : 0.3399; + + poly_outline = OUTLINE_TRANS; + queuepoly((*Vdp)*V0*xpush(ldist*(2*ph1-1)), shDisk, aircol); + poly_outline = OUTLINE_NONE; + } - double ph1 = fanframe; - - int aircol = 0xC0C0FF40; - - ph1 -= floor(ph1); - - if(hdir1 < hdir0-42) hdir1 += 84; - if(hdir1 >= hdir0+42) hdir1 -= 84; - - int hdir = (hdir1*ph1+hdir0*(1-ph1)); - - transmatrix V0 = spin((hdir) * M_PI / 42); - - double ldist = purehepta ? crossf : c->type == 6 ? .2840 : 0.3399; - - poly_outline = 0; - queuepoly(V*V0*ddi(0, ldist*(2*ph1-1)), shDisk, aircol); - poly_outline = OUTLINE_NONE; - } - -// queuepoly(V*ddi(rand() % 84, hexf*(rand()%100)/100), shDisk, aircolor(airdir)); } -/* if(ch == '.') { - col = darkened(col); - for(int t=0; twall == waSea) col = xcol; - - queuechr(xc, yc, sc, siz, ch, col, 2); - } - - if(c == dragon::target && getMount()) { - queuechr(xc, yc, sc, 2*vid.fsize, 'X', - gradient(0, iinf[itOrbDomination].color, -1, sin(ticks/(dragon::whichturn == turncount ? 75. : 150.)), 1)); - } - - if(c == keycell) { - queuechr(xc, yc, sc, 2*vid.fsize, 'X', 0x10101 * int(128 + 100 * sin(ticks / 150.))); - queuestr(xc, yc, sc, vid.fsize, its(keycelldist), 0x10101 * int(128 - 100 * sin(ticks / 150.))); - } - - if(c == pirateTreasureFound) { - pirateCoords = V; - if(showPirateX) { - queuechr(xc, yc, sc, 2*vid.fsize, 'X', 0x10100 * int(128 + 100 * sin(ticks / 150.))); - if(cwt.c->master->alt) - queuestr(xc, yc, sc, vid.fsize, its(-celldistAlt(cwt.c)), 0x10101 * int(128 - 100 * sin(ticks / 150.))); + // hyper trihepta: 0.2849 + // hyper heptagonal: 0.6150 + // hyper: 0.3798 + + if(purehepta) { + double x = sphere?.645:.6150; + for(int t=0; tmov[t] && c->mov[t] < c) + queueline(V * ddspin(c,t,-6) * xpush0(x), + V * ddspin(c,t,6) * xpush0(x), + gridcolor(c, c->mov[t]), 1); + } + else if(isWarped(c)) { + double x = sphere?.3651:euclid?.2611:.2849; + if(!ishept(c)) for(int t=0; t<6; t++) if(c->mov[t] && ishept(c->mov[t])) + queueline(V * ddspin(c,t,-S14) * xpush0(x), + V * ddspin(c,t,+S14) * xpush0(x), + gridcolor(c, c->mov[t]), 1); + } + else if(ishept(c) && !euclid) ; + else { + double x = sphere?.401:euclid?.3 : .328; + for(int t=0; t<6; t++) + if(euclid ? c->mov[t]mov[t] < c)) + queueline(V * ddspin(c,t,-S7) * xpush0(x), + V * ddspin(c,t,+S7) * xpush0(x), + gridcolor(c, c->mov[t]), 1); } } - + if(!euclid && (!pirateTreasureSeek || compassDist(c) < compassDist(pirateTreasureSeek))) pirateTreasureSeek = c; if(!euclid) { bool usethis = false; double spd = 1; + bool rev = false; if(isGravityLand(cwt.c->land)) { + if(cwt.c->land == laDungeon) rev = true; if(!straightDownSeek || edgeDepth(c) < edgeDepth(straightDownSeek)) { usethis = true; spd = cwt.c->landparam / 10.; } } - if(pmodel) { - if(c->master->alt && cwt.c->master->alt && + if(c->master->alt && cwt.c->master->alt && + (cwt.c->land == laMountain || + (pmodel && (cwt.c->land == laTemple || cwt.c->land == laWhirlpool || (cheater && (cwt.c->land == laClearing || cwt.c->land == laCaribbean || - cwt.c->land == laCamelot || cwt.c->land == laPalace))) - && c->land == cwt.c->land && c->master->alt->alt == cwt.c->master->alt->alt) { - if(!straightDownSeek || celldistAlt(c) < celldistAlt(straightDownSeek)) { - usethis = true; - spd = .5; - } + cwt.c->land == laCamelot || cwt.c->land == laPalace))) + )) + && c->land == cwt.c->land && c->master->alt->alt == cwt.c->master->alt->alt) { + if(!straightDownSeek || !straightDownSeek->master->alt || celldistAlt(c) < celldistAlt(straightDownSeek)) { + usethis = true; + spd = .5; + if(cwt.c->land == laMountain) rev = true; } + } - if(cwt.c->land == laOcean && cwt.c->landparam < 25) { - if(!straightDownSeek || coastval(c, laOcean) < coastval(straightDownSeek, laOcean)) { - usethis = true; - spd = cwt.c->landparam / 10; - } - + if(pmodel && cwt.c->land == laOcean && cwt.c->landparam < 25) { + if(!straightDownSeek || coastval(c, laOcean) < coastval(straightDownSeek, laOcean)) { + usethis = true; + spd = cwt.c->landparam / 10; } + } if(usethis) { straightDownSeek = c; - downspin = atan2(VC0[1], VC0[0]) - M_PI/2; + downspin = atan2(VC0[1], VC0[0]); + downspin -= M_PI/2; + if(rev) downspin += M_PI; downspin += M_PI/2 * (conformal::rotation%4); while(downspin < -M_PI) downspin += 2*M_PI; while(downspin > +M_PI) downspin -= 2*M_PI; downspin = downspin * min(spd, (double)1); - // queuechr(xc, yc, sc, 2*vid.fsize, 'X', 0x10100 * int(128 + 100 * sin(ticks / 150.))); } } + if(!inHighQual) { - if(!inHighQual) { - -#if defined(ANDROID) || defined(PANDORA) || defined(IOS) - if(c == lmouseover && (mousepressed || ISANDROID || ISMOBILE)) { - queuecircle(xc, yc, int(sqrt(squar(xc-xs)+squar(yc-ys)) * .8), c->cpdist > 1 ? 0x00FFFF : 0xFF0000); - } -#endif - -#ifndef MOBILE - if(cmode == emMapEditor && !mapeditor::subscreen && lmouseover && +#ifndef NOEDIT + if(cmode == emMapEditor && !mapeditor::subscreen && lmouseover && darken == 0 && (mapeditor::whichPattern ? mapeditor::subpattern(c) == mapeditor::subpattern(lmouseover) : c == lmouseover)) { - queuecircle(xc, yc, int(sqrt(squar(xc-xs)+squar(yc-ys)) * .8), 0x00FFFF); + queuecircle(V, .78, 0x00FFFFFF); } #endif - if(joydir.d >= 0 && c == cwt.c->mov[(joydir.d+cwt.spin) % cwt.c->type]) - queuecircle(xc, yc, int(sqrt(squar(xc-xs)+squar(yc-ys)) * (.78 - .02 * sin(ticks/199.0))), 0x00FF00); - -#ifndef MOBILE - if(c == lcenterover && !playermoved && netgen::mode == 0 && !conformal::on) - queuecircle(xc, yc, int(sqrt(squar(xc-xs)+squar(yc-ys)) * (.70 - .06 * sin(ticks/200.0))), int(175 + 25 * sin(ticks / 200.0))); -#endif - -#ifndef MOBILE +#ifndef NOEDIT mapeditor::drawGhosts(c, V, ct); #endif } - // process mouse - - if(c == cwt.c) curcell = V; - for(int i=0; itype; i++) - if(c == cwt.c->mov[i]) movecell[i] = V; - - // drawline(V*C0, V*Crad[0], 0xC00000); if(c->bardir != NODIR && c->bardir != NOBARRIERS && c->land != laHauntedWall && c->barleft != NOWALLSEP_USED) { - queueline(V*C0, V*heptmove[c->bardir]*C0, 0x505050 >> darken); - queueline(V*C0, V*hexmove[c->bardir]*C0, 0x505050 >> darken); + int col = darkena(0x505050, 0, 0xFF); + queueline(tC0(V), V*tC0(heptmove[c->bardir]), col, 2); + queueline(tC0(V), V*tC0(hexmove[c->bardir]), col, 2); } -#ifndef MOBILE +#ifndef NOMODEL netgen::buildVertexInfo(c, V); - rug::buildVertexInfo(c, V); #endif #ifdef LOCAL @@ -3503,6 +5091,253 @@ void drawcell(cell *c, const transmatrix& V, int spinv) { } } +bool confusingGeometry() { + return elliptic || quotient == 1; + } + +struct flashdata { + int t; + int size; + cell *where; + double angle; + int spd; // 0 for flashes, >0 for particles + int color; + flashdata(int _t, int _s, cell *_w, int col, int sped) { + t=_t; size=_s; where=_w; color = col; + angle = rand() % 1000; spd = sped; + } + }; + +vector flashes; + +void drawFlash(cell *c) { + flashes.push_back(flashdata(ticks, 1000, c, iinf[itOrbFlash].color, 0)); + } +void drawBigFlash(cell *c) { + flashes.push_back(flashdata(ticks, 2000, c, 0xC0FF00, 0)); + } +void drawParticle(cell *c, int col, int maxspeed) { + if(vid.particles && !confusingGeometry()) + flashes.push_back(flashdata(ticks, rand() % 16, c, col, 1+rand() % maxspeed)); + } +void drawParticles(cell *c, int col, int qty, int maxspeed) { + if(vid.particles) + while(qty--) drawParticle(c,col, maxspeed); + } +void drawFireParticles(cell *c, int qty, int maxspeed) { + if(vid.particles) + for(int i=0; iland].color, 1), 4, 50); + } +void fallingMonsterAnimation(cell *c, eMonster m) { + if(!mmspatial) return; + fallanim& fa = fallanims[c]; + fa.t_mon = ticks; + fa.m = m; + // drawParticles(c, darkenedby(linf[c->land].color, 1), 4, 50); + } + +void queuecircleat(cell *c, double rad, int col) { + if(!c) return; + if(!gmatrix.count(c)) return; + queuecircle(gmatrix[c], rad, col); + if(!wmspatial) return; + if(highwall(c)) + queuecircle(mscale(gmatrix[c], geom3::WALL), rad, col); + int sl; + if((sl = snakelevel(c))) { + queuecircle(mscale(gmatrix[c], geom3::SLEV[sl]), rad, col); + } + if(chasmgraph(c)) + queuecircle(mscale(gmatrix[c], geom3::LAKE), rad, col); + } + +#define G(x) x && gmatrix.count(x) +#define IG(x) if(G(x)) +#define Gm(x) gmatrix[x] +#define Gm0(x) tC0(gmatrix[x]) + +#ifdef MOBILE +#define MOBON (clicked) +#else +#define MOBON true +#endif + +void drawMarkers() { + + if(darken || cmode == emNumber) return; + + if(!inHighQual) { + +#ifdef PANDORA + bool ok = mousepressed; +#else + bool ok = true; +#endif + + if(G(dragon::target) && haveMount()) { + queuechr(Gm0(dragon::target), 2*vid.fsize, 'X', + gradient(0, iinf[itOrbDomination].color, -1, sin(ticks/(dragon::whichturn == turncount ? 75. : 150.)), 1)); + } + + /* for(int i=0; i<12; i++) if(c->type == 5 && c->master == &dodecahedron[i]) + queuechr(xc, yc, sc, 4*vid.fsize, 'A'+i, iinf[itOrbDomination].color); */ + + IG(keycell) { + queuechr(Gm0(keycell), 2*vid.fsize, 'X', 0x10101 * int(128 + 100 * sin(ticks / 150.))); + queuestr(Gm0(keycell), vid.fsize, its(keycelldist), 0x10101 * int(128 - 100 * sin(ticks / 150.))); + } + + IG(pirateTreasureFound) { + pirateCoords = Gm0(pirateTreasureFound); + if(showPirateX) { + queuechr(pirateCoords, 2*vid.fsize, 'X', 0x10100 * int(128 + 100 * sin(ticks / 150.))); + if(numplayers() == 1 && cwt.c->master->alt) + queuestr(pirateCoords, vid.fsize, its(-celldistAlt(cwt.c)), 0x10101 * int(128 - 100 * sin(ticks / 150.))); + } + } + + if(lmouseover && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON) { + queuecircleat(lmouseover, .8, darkena(lmouseover->cpdist > 1 ? 0x00FFFF : 0xFF0000, 0, 0xFF)); + } + + if(global_pushto && vid.drawmousecircle && ok && DEFAULTCONTROL && MOBON) { + queuecircleat(global_pushto, .6, darkena(0xFFD500, 0, 0xFF)); + } + + if(joydir.d >= 0) + queuecircleat(cwt.c->mov[(joydir.d+cwt.spin) % cwt.c->type], .78 - .02 * sin(ticks/199.0), + darkena(0x00FF00, 0, 0xFF)); + +#ifndef NOMODEL + if(centerover && !playermoved && netgen::mode == 0 && !conformal::on) + queuecircleat(centerover, .70 - .06 * sin(ticks/200.0), + darkena(int(175 + 25 * sin(ticks / 200.0)), 0, 0xFF)); +#endif + + if(multi::players > 1 || multi::alwaysuse) for(int i=0; i 0) { + using namespace shmupballs; + calc(); + queuecircle(xmove, yb, rad, 0xFF0000FF); + queuecircle(xmove, yb, rad*SKIPFAC, 0xFF0000FF); + forCellAll(c2, cwt.c) IG(c2) drawMobileArrow(c2, Gm(c2)); + } +#endif + + if((vid.axes == 4 || (vid.axes == 1 && !mousing)) && !shmup::on) { + if(multi::players == 1) { + forCellAll(c2, cwt.c) IG(c2) drawMovementArrows(c2, Gm(c2)); + } + else if(multi::players > 1) for(int p=0; p ttm(cwtV, multi::whereis[p]); + dynamicval tcw(cwt, multi::player[p]); + drawMovementArrows(c2, Gm(c2)); + } + } + } + } + + monsterToSummon = moNone; + orbToTarget = itNone; + + if(mouseover && targetclick && cmode == emNormal) { + shmup::cpid = 0; + orbToTarget = targetRangedOrb(mouseover, roCheck); + if(orbToTarget == itOrbSummon) { + monsterToSummon = summonedAt(mouseover); + queuechr(mousex, mousey, 0, vid.fsize, minf[monsterToSummon].glyph, minf[monsterToSummon].color); + queuecircleat(mouseover, 0.6, darkena(minf[monsterToSummon].color, 0, 0xFF)); + } + else if(orbToTarget) { + queuechr(mousex, mousey, 0, vid.fsize, '@', iinf[orbToTarget].color); + queuecircleat(mouseover, 0.6, darkena(iinf[orbToTarget].color, 0, 0xFF)); + } + if(orbToTarget && rand() % 200 < ticks - lastt) { + if(orbToTarget == itOrbDragon) + drawFireParticles(mouseover, 2); + else if(orbToTarget == itOrbSummon) { + drawParticles(mouseover, iinf[orbToTarget].color, 1); + drawParticles(mouseover, minf[monsterToSummon].color, 1); + } + else { + drawParticles(mouseover, iinf[orbToTarget].color, 2); + } + } + } + } + +void drawFlashes() { + for(int k=0; k f.size; + + if(f.spd) { + kill = tim > 300; + int partcol = darkena(f.color, 0, max(255 - kill/2, 0)); + poly_outline = OUTLINE_NONE; + queuepoly(V * spin(f.angle) * xpush(f.spd * tim / 50000.), shParticle[f.size], partcol); + } + + else if(f.size == 1000) { + 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 flashcol = f.color; + if(u > 500) flashcol = gradient(flashcol, 0, 500, u, 1100); + flashcol = darkena(flashcol, 0, 0xFF); + for(int a=0; a<=S84; a++) curvepoint(V*ddi0(a, rad)); + queuecurve(flashcol, 0x8080808, PPR_LINE); + } + } + else if(f.size == 2000) { + for(int u=0; u<=tim; u++) { + if((u-tim)%50) continue; + if(u < tim-250) continue; + ld rad = u * 3 / 2000.; + rad = rad * (5-rad) * 1.25; + rad *= hexf; + int flashcol = f.color; + if(u > 1000) flashcol = gradient(flashcol, 0, 1000, u, 2200); + flashcol = darkena(flashcol, 0, 0xFF); + for(int a=0; a<=S84; a++) curvepoint(V*ddi0(a, rad)); + queuecurve(flashcol, 0x8080808, PPR_LINE); + } + } + + if(kill) { + f = flashes[size(flashes)-1]; + flashes.pop_back(); k--; + } + } + } + string buildCredits(); string buildHelpText() { @@ -3531,6 +5366,16 @@ string buildHelpText() { "The monster could also kill you by moving into your location, but the game " "automatically cancels all moves which result in that.\n\n" ); + + h += XLAT( + "There are many lands in HyperRogue. Collect 10 treasure " + "in the given land type to complete it; this enables you to " + "find the magical Orbs of this land, and in some cases " + "get access to new lands. At 25 treasures " + "this type of Orbs starts appearing in other lands as well. Press 'o' to " + "get the details of all the Lands.\n\n"); + h += "\n\n"; + #ifdef MOBILE h += XLAT( "Usually, you move by touching somewhere on the map; you can also touch one " @@ -3541,11 +5386,12 @@ string buildHelpText() { #else h += XLAT( "Move with mouse, num pad, qweadzxc, or hjklyubn. Wait by pressing 's' or '.'. Spin the world with arrows, PageUp/Down, and Home/Space. " - "To save the game you need an Orb of Safety. Press 'v' for config, ESC for the quest status and menu.\n\n" + "To save the game you need an Orb of Safety. Press 'v' for the main menu (configuration, special modes, etc.), ESC for the quest status.\n\n" ); h += XLAT( "You can right click any element to get more information about it.\n\n" ); + h += XLAT("(You can also use right Shift)\n\n"); #endif h += XLAT("See more on the website: ") + "http//roguetemple.com/z/hyper.php\n\n"; @@ -3553,16 +5399,9 @@ string buildHelpText() { h += XLAT("Still confused? Read the FAQ on the HyperRogue website!\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"); @@ -3577,11 +5416,16 @@ string buildCredits() { h += XLAT( "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, " - "wonderfullizardofoz, Piotr Migdał, tehora, Michael Heerdegen, Sprite Guard, zelda0x181e, Vipul, snowyowl0, Patashu" + "wonderfullizardofoz, Piotr Migdał, tehora, Michael Heerdegen, Sprite Guard, zelda0x181e, Vipul, snowyowl0, Patashu, phenomist, Alan Malloy, Tom Fryers, Sinquetica, _monad, CtrlAltDestroy, jruderman" ); #ifdef EXTRALICENSE h += EXTRALICENSE; #endif +#ifndef MOBILE + h += XLAT( + "\n\nSee sounds/credits.txt for credits for sound effects" + ); + #endif if(musiclicense != "") h += musiclicense; return h; } @@ -3603,19 +5447,63 @@ string princedesc() { return XLAT("Apparently a princess is kept locked somewhere, but you won't ever find her in this hyperbolic palace. "); } +string helptitle(string s, int col) { + return "@" + its(col) + "\t" + s + "\n"; + } + +string princessReviveHelp() { + string h = "\n\n" + + XLAT("Killed %1 can be revived with Orb of the Love, after you collect 20 more $$$.", moPrincess); + if(princess::reviveAt) + h += "\n\n" + + XLAT("%The1 will be revivable at %2 $$$", moPrincess, its(princess::reviveAt)); + return h; + } + string generateHelpForItem(eItem it) { - string help = XLAT(iinf[it].help); -#ifdef ANDROID + + string help = helptitle(XLATN(iinf[it].name), iinf[it].color); + + help += XLAT(iinf[it].help); + + if(it == itSavedPrincess || it == itOrbLove) + help += princessReviveHelp(); + + if(it == itTrollEgg) + help += XLAT("\n\nAfter the Trolls leave, you have 750 turns to collect %the1, or it gets stolen.", it); + + if(it == itIvory || it == itAmethyst || it == itLotus || it == itMutant) { + help += XLAT( + "\n\nEasy %1 might disappear when you collect more of its kind.", it); + if(it != itMutant) help += XLAT( + " You need to go deep to collect lots of them."); + } + +#ifdef MOBILE if(it == itOrbSafety) - help += XLAT("This might be useful for Android devices with limited memory."); + help += XLAT("This might be very useful for devices with limited memory."); #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"); #endif -#ifndef MOBILE - if(isRangedOrb(it)) - help += XLAT("You can also scroll to the desired location and then press 't'."); + + if(isRangedOrb(it)) { + help += XLAT("\nThis is a ranged Orb. "); +#ifdef ISMOBILE + if(vid.shifttarget&2) + help += XLAT("\nRanged Orbs can be targeted by long touching the desired location."); + else + help += XLAT("\nRanged Orbs can be targeted by touching the desired location."); +#else + if(vid.shifttarget&1) + help += XLAT("\nRanged Orbs can be targeted by shift-clicking the desired location. " + else + help += XLAT("\nRanged Orbs can be targeted by clicking the desired location. "); + help += "You can also scroll to the desired location and then press 't'."); #endif + help += XLAT("\nYou can never target cells which are adjacent to the player character, or ones out of the sight range."); + } + #ifdef MOBILE if(it == itGreenStone) help += XLAT("You can touch the Dead Orb in your inventory to drop it."); @@ -3623,6 +5511,12 @@ string generateHelpForItem(eItem it) { if(it == itGreenStone) help += XLAT("You can press 'g' or click them in the list to drop a Dead Orb."); #endif + if(it == itOrbLightning || it == itOrbFlash) + help += XLAT("\n\nThis Orb is triggered on your first attack or illegal move."); + if(it == itOrbShield) + help += XLAT("\n\nThis Orb protects you from attacks, scents, and insulates you " + "from electricity. It does not let you go through deadly terrain, but " + "if you are attacked with fire, it lets you stay in place in it."); if(it == itOrbEmpathy) { int cnt = 0; for(int i=0; iland); + + for(int i=0; iland, tr, treasureType(cwt.c->land)); + int t = items[tr] * landMultiplier(oi.l); + if(t >= 25) + if(olr == olrPrize25 || olr == olrPrize3 || olr == olrGuest || olr == olrMonster || olr == olrAlways) { + help += XLAT("\nSpawn rate (as prize Orb): %1%/%2\n", + its(int(.5 + 100 * orbprizefun(t))), + its(oi.gchance)); + } + if(t >= 10) + if(olr == olrHub) { + help += XLAT("\nSpawn rate (in Hubs): %1%/%2\n", + its(int(.5 + 100 * orbcrossfun(t))), + its(oi.gchance)); + } + } + } + } + return help; } +void addMinefieldExplanation(string& s) { + + s += XLAT( + "\n\nOnce you collect 10 Bomberbird Eggs, " + "stepping on a cell with no adjacent mines also reveals the adjacent cells. " + "Collecting even more Eggs will increase the radius. Additionally, collecting " + "25 Bomberbird Eggs will reveal adjacent cells even in your future games." + ); + + s += "\n\n"; +#ifdef MOBILE + s += XLAT("Known mines may be marked by pressing 'm'. Your allies won't step on marked mines."); +#else + s += XLAT("Known mines may be marked by touching while in drag mode. Your allies won't step on marked mines."); +#endif + } + string generateHelpForWall(eWall w) { - string s = XLAT(winf[w].help); + + string s = helptitle(XLATN(winf[w].name), winf[w].color); + + s += XLAT(winf[w].help); + if(w == waMineMine || w == waMineUnknown || w == waMineOpen) + addMinefieldExplanation(s); if(isThumper(w)) s += pushtext(w); if((w == waClosePlate || w == waOpenPlate) && purehepta) s += "\n\n(For the heptagonal mode, the radius has been reduced to 2 for closing plates.)"; return s; } +void buteol(string& s, int current, int req) { + int siz = size(s); + if(s[siz-1] == '\n') s.resize(siz-1); + char buf[100]; sprintf(buf, " (%d/%d)", current, req); + s += buf; s += "\n"; + } + string generateHelpForMonster(eMonster m) { - string s = XLAT(minf[m].help); + string s = helptitle(XLATN(minf[m].name), minf[m].color); + + s += XLAT(minf[m].help); if(m == moPalace || m == moSkeleton) s += pushtext(m); if(m == moTroll) s += XLAT(trollhelp2); @@ -3662,13 +5613,20 @@ string generateHelpForMonster(eMonster m) { if(isGhost(m)) s += XLAT("\n\nA Ghost never moves to a cell which is adjacent to another Ghost of the same kind.", m); + if(m == moBat || m == moEagle) + s += XLAT("\n\nFast flying creatures may attack or go against gravity only in their first move.", m); + return s; } string generateHelpForLand(eLand l) { - string s = XLAT(linf[l].help); + string s = helptitle(XLATN(linf[l].name), linf[l].color); + + if(l == laPalace) s += princedesc(); - if(l == laPalace) s = princedesc() + s; + s += XLAT(linf[l].help); + + if(l == laMinefield) addMinefieldExplanation(s); s += "\n\n"; if(l == laIce || l == laCaves || l == laDesert || l == laMotion || l == laJungle || @@ -3678,42 +5636,67 @@ string generateHelpForLand(eLand l) { #define ACCONLY(z) s += XLAT("Accessible only from %the1.\n", z); #define ACCONLY2(z,x) s += XLAT("Accessible only from %the1 or %the2.\n", z, x); #define ACCONLYF(z) s += XLAT("Accessible only from %the1 (until finished).\n", z); - #define TREQ(z) s += XLAT("Treasure required: %1 $$$.\n", #z); - #define TREQ2(z,x) s += XLAT("Treasure required: %1 x %2.\n", #z, x); + #define TREQ(z) { s += XLAT("Treasure required: %1 $$$.\n", #z); buteol(s, gold(), z); } + #define TREQ2(z,x) { s += XLAT("Treasure required: %1 x %2.\n", #z, x); buteol(s, items[x], z); } if(l == laMirror || l == laMinefield || l == laPalace || - l == laOcean || l == laLivefjord || l == laZebra || l == laGridCoast || l == laGridSea) + l == laOcean || l == laLivefjord || l == laZebra || l == laWarpCoast || l == laWarpSea || + l == laReptile || l == laIvoryTower) TREQ(30) - if(l == laCaribbean || l == laWhirlpool) ACCONLY(laOcean) + + if(isCoastal(l)) + s += XLAT("Coastal region -- connects inland and aquatic regions.\n"); + + if(isPureSealand(l)) + s += XLAT("Aquatic region -- accessible only from coastal regions and other aquatic regions.\n"); + + if(l == laWhirlpool) ACCONLY(laOcean) if(l == laRlyeh) ACCONLYF(laOcean) if(l == laTemple) ACCONLY(laRlyeh) if(l == laClearing) ACCONLY(laOvergrown) if(l == laHaunted) ACCONLY(laGraveyard) if(l == laPrincessQuest) ACCONLY(laPalace) + if(l == laMountain) ACCONLY(laJungle) if(l == laCamelot) ACCONLY2(laCrossroads, laCrossroads3) if(l == laDryForest || l == laWineyard || l == laDeadCaves || l == laHive || l == laRedRock || - l == laOvergrown || l == laStorms || l == laWhirlwind || l == laRose) + l == laOvergrown || l == laStorms || l == laWhirlwind || l == laRose || + l == laCrossroads2 || l == laRlyeh) TREQ(60) - if(l == laIvoryTower) TREQ(30) - if(l == laIvoryTower) TREQ2(10, itElixir) - if(l == laEndorian) TREQ2(10, itEdge) + if(l == laReptile) TREQ2(10, itElixir) + if(l == laEndorian) TREQ2(10, itIvory) + if(l == laKraken) TREQ2(10, itFjord) + if(l == laBurial) TREQ2(10, itKraken) + + if(l == laDungeon) TREQ2(5, itIvory) + if(l == laDungeon) TREQ2(5, itPalace) + if(l == laMountain) TREQ2(5, itIvory) + if(l == laMountain) TREQ2(5, itRuby) + if(l == laPrairie) TREQ(90) + if(l == laBull) TREQ(90) if(l == laCrossroads4) TREQ(200) + if(l == laCrossroads5) TREQ(300) - if(l == laGraveyard || l == laHive) + if(l == laGraveyard || l == laHive) { s += XLAT("Kills required: %1.\n", "100"); + buteol(s, tkills(), 100); + } - if(l == laDragon) + if(l == laDragon) { s += XLAT("Different kills required: %1.\n", "20"); + buteol(s, killtypes(), 20); + } if(l == laTortoise) ACCONLY(laDragon) if(l == laTortoise) s += XLAT("Find a %1 in %the2.", itBabyTortoise, laDragon); - if(l == laHell || l == laCrossroads3) + if(l == laHell || l == laCrossroads3) { s += XLAT("Finished lands required: %1 (collect 10 treasure)\n", "9"); + buteol(s, orbsUnlocked(), 9); + } if(l == laCocytus || l == laPower) TREQ2(10, itHell) if(l == laRedRock) TREQ2(10, itSpice) @@ -3727,19 +5710,34 @@ string generateHelpForLand(eLand l) { if(l == laEmerald) { TREQ2(5, itFernFlower) TREQ2(5, itGold) s += XLAT("Alternatively: kill a %1 in %the2.\n", moVizier, laPalace); + buteol(s, kills[moVizier], 1); } - if(l == laPrincessQuest) - s += XLAT("Kills required: %1.\n", moVizier); +#define KILLREQ(who, where) { s += XLAT("Kills required: %1 (%2).\n", who, where); buteol(s, kills[who], 1); } + if(l == laPrincessQuest) + KILLREQ(moVizier, laPalace); + if(l == laElementalWall) { - s += XLAT("Kills required: %1 (%2).\n", moFireElemental, laDragon); - s += XLAT("Kills required: %1 (%2).\n", moEarthElemental, laDeadCaves); - s += XLAT("Kills required: %1 (%2).\n", moWaterElemental, laLivefjord); - s += XLAT("Kills required: %1 (%2).\n", moAirElemental, laWhirlwind); + KILLREQ(moFireElemental, laDragon); + KILLREQ(moEarthElemental, laDeadCaves); + KILLREQ(moWaterElemental, laLivefjord); + KILLREQ(moAirElemental, laWhirlwind); + } + + if(l == laTrollheim) { + KILLREQ(moTroll, laCaves); + KILLREQ(moFjordTroll, laLivefjord); + KILLREQ(moDarkTroll, laDeadCaves); + KILLREQ(moStormTroll, laStorms); + KILLREQ(moForestTroll, laOvergrown); + KILLREQ(moRedTroll, laRedRock); } if(l == laZebra) TREQ2(10, itFeather) + + if(l == laCamelot || l == laPrincessQuest) + s += XLAT("Completing the quest in this land is not necessary for the Hyperstone Quest."); int rl = isRandland(l); if(rl == 2) @@ -3749,19 +5747,56 @@ string generateHelpForLand(eLand l) { "Variants of %the1 are available in the Random Pattern Mode after " "getting a highscore of at least 10 %2.", l, treasureType(l)); + if(l == laPrincessQuest) { + s += XLAT("Unavailable in the shmup mode.\n"); + s += XLAT("Unavailable in the multiplayer mode.\n"); + } + + if(noChaos(l)) + s += XLAT("Unavailable in the Chaos mode.\n"); + + if(l == laWildWest) + s += XLAT("Bonus land, available only in some special modes.\n"); + + if(l == laWhirlpool) + s += XLAT("Orbs of Safety always appear here, and may be used to escape.\n"); + + /* if(isHaunted(l) || l == laDungeon) + s += XLAT("You may be unable to leave %the1 if you are not careful!\n", l); */ + + if(l == laStorms) { + if(elec::lightningfast == 0) s += XLAT("\nSpecial conduct (still valid)\n"); + else s += XLAT("\nSpecial conduct failed:\n"); + + s += XLAT( + "Avoid escaping from a discharge (\"That was close\")."); + } + + if(isHaunted(l)) { + if(survivalist) s += XLAT("\nSpecial conduct (still valid)\n"); + else s += XLAT("\nSpecial conduct failed:\n"); + + s += XLAT( + "Avoid chopping trees, using Orbs, and non-graveyard monsters in the Haunted Woods." + ); + } + +#ifndef ISMOBILE + if(l == laCA) + s += XLAT("\n\nHint: use 'm' to toggle cells quickly"); +#endif + return s; } +bool instat; + void describeMouseover() { DEBB(DF_GRAPH, (debugfile,"describeMouseover\n")); -#ifdef LOCAL - if(localDescribe()) return; -#endif - cell *c = mousing ? mouseover : playermoved ? NULL : centerover; string out = mouseovers; - if(!c) { } + if(!c || instat || getcstat) { } else if(cmode == emNormal || cmode == emQuit || cmode == emMapEditor) { out = XLAT1(linf[c->land].name); help = generateHelpForLand(c->land); @@ -3769,14 +5804,26 @@ void describeMouseover() { // Celsius // if(c->land == laIce) out = "Icy Lands (" + fts(60 * (c->heat - .4)) + " C)"; + if(c->land == laIce || c->land == laCocytus) out += " (" + fts(heat::celsius(c)) + " °C)"; if(c->land == laDryForest && c->landparam) out += " (" + its(c->landparam)+"/10)"; if(c->land == laOcean && chaosmode) out += " (" + its(c->CHAOSPARAM)+"S"+its(c->SEADIST)+"L"+its(c->LANDDIST)+")"; - else if(c->land == laOcean && c->landparam <= 25) - out += " (" + its(c->landparam)+")"; + else if(c->land == laOcean && c->landparam <= 25) { + if(shmup::on) + out += " (" + its(c->landparam)+")"; + else { + bool b = c->landparam >= tide[(turncount-1) % tidalsize]; + int t = 1; + for(; t < 1000 && b == (c->landparam >= tide[(turncount+t-1) % tidalsize]); t++) ; + if(b) + out += " (" + its(t) + " turns to surface)"; + else + out += " (" + its(t) + " turns to submerge)"; + } + } if(c->land == laTortoise && tortoise::seek()) out += " " + tortoise::measure(getBits(c)); @@ -3804,7 +5851,9 @@ void describeMouseover() { out += " PD=" + its(c->pathdist); */ if(webdisplay & 8) { - out += " LP:" + its(c->landparam)+"/"+its(turncount); + out += " LP:" + itsh(c->landparam)+"/"+its(turncount); + + out += " D:" + its(c->mpdist); char zz[64]; sprintf(zz, " P%p", c); out += zz; // out += " rv" + its(rosedist(c)); @@ -3846,7 +5895,7 @@ void describeMouseover() { // char zz[64]; sprintf(zz, " P%d", princess::dist(c)); out += zz; // out += " MD"+its(c->mpdist); // out += " H "+its(c->heat); - // if(c->type == 7) out += " Z"+its(c->master->zebraval); + // if(c->type != 6) out += " Z"+its(c->master->zebraval); // out += " H"+its(c->heat); /* // Hive debug @@ -3871,7 +5920,7 @@ void describeMouseover() { if((c->wall == waBigTree || c->wall == waSmallTree) && c->land != laDryForest) help = - "Trees in this forest can be cut down. Big trees take two turns to cut down."; + "Trees in this forest can be chopped down. Big trees take two turns to chop down."; else if(c->wall != waSea && c->wall != waPalace) if(!((c->wall == waCavefloor || c->wall == waCavewall) && c->land == laEmerald)) help = generateHelpForWall(c->wall); @@ -3880,6 +5929,9 @@ void describeMouseover() { if(isActivable(c)) out += XLAT(" (touch to activate)"); if(hasTimeout(c)) out += XLAT(" [%1 turns]", its(c->wparam)); + + if(isReptile(c->wall)) + out += XLAT(" [%1 turns]", its((unsigned char) c->wparam)); if(c->monst) { out += ", "; out += XLAT1(minf[c->monst].name); @@ -3899,16 +5951,27 @@ void describeMouseover() { if(c->item && !itemHiddenFromSight(c)) { out += ", "; out += XLAT1(iinf[c->item].name); + if(c->item == itBarrow) out += " (x" + its(c->landparam) + ")"; if(c->item == itBabyTortoise && tortoise::seek()) out += " " + tortoise::measure(tortoise::babymap[c]); if(!c->monst) help = generateHelpForItem(c->item); } - if(c == cwt.c && !shmup::on) out += XLAT(", you"); + if(isPlayerOn(c) && !shmup::on) out += XLAT(", you"); - if(shmup::mousetarget && intval(mouseh, shmup::mousetarget->pat*C0) < .1) { - out += ", "; out += XLAT1(minf[shmup::mousetarget->type].name); - help = XLAT(minf[shmup::mousetarget->type].help); + if(shmup::mousetarget && intval(mouseh, tC0(shmup::mousetarget->pat)) < .1) { + out += ", "; +#ifdef ROGUEVIZ + if(shmup::mousetarget->type == moRogueviz) { + help = XLAT(minf[shmup::mousetarget->type].help); + out += rogueviz::describe(shmup::mousetarget); + } + else +#endif + { + out += XLAT1(minf[shmup::mousetarget->type].name); + help = XLAT(minf[shmup::mousetarget->type].help); + } /* char buf[64]; sprintf(buf, "%Lf", intval(mouseh, shmup::mousetarget->pat*C0)); mouseovers = mouseovers + " D: " + buf; @@ -3921,6 +5984,8 @@ void describeMouseover() { if(rosedist(c) == 2) out += ", wave of scent (back)"; + if(sword::at(c)) out += ", Energy Sword"; + if(rosedist(c) || c->land == laRose || c->wall == waRose) help += s0 + "\n\n" + rosedesc; @@ -3942,10 +6007,10 @@ void describeMouseover() { else if(getcstat == 'r') { out = XLAT("simply resize the window to change resolution"); } - else if(getcstat == 'f') { + /* else if(getcstat == 'f') { out = XLAT("[+] keep the window size, [-] use the screen resolution"); - } - else if(getcstat == 'a' && vid.aspeed > -4.99) + } */ + else if(getcstat == 'a' && vid.sspeed > -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"); @@ -3984,41 +6049,50 @@ void describeMouseover() { int col = linf[cwt.c->land].color; if(cwt.c->land == laRedRock) col = 0xC00000; - if(cmode != emPickScores) -#ifdef MOBILE - if(cmode != emNormal && cmode != emQuit) + +#ifndef MOBILE + displayfr(vid.xres/2, vid.fsize, 2, vid.fsize, out, col, 8); #endif - displayfr(vid.xres/2, vid.fsize, 2, vid.fsize, out, col, 8); + if(mousey < vid.fsize * 3/2) getcstat = SDLK_F1; if(false && shmup::mousetarget) { char buf[64]; - sprintf(buf, "%Lf", (long double) intval(mouseh, shmup::mousetarget->pat*C0)); + sprintf(buf, "%Lf", (long double) intval(mouseh, tC0(shmup::mousetarget->pat))); mouseovers = mouseovers + " D: " + buf; return; } } -void drawrec(const heptspin& hs, int lev, hstate s, transmatrix V) { +void drawrec(const heptspin& hs, int lev, hstate s, const transmatrix& V) { - shmup::calc_relative_matrix(cwt.c, hs.h); + // shmup::calc_relative_matrix(cwt.c, hs.h); cell *c = hs.h->c7; - drawcell(c, V * spin(hs.spin*2*M_PI/7 + (purehepta ? M_PI:0)), hs.spin); + transmatrix V10; + const transmatrix& V1 = hs.mirrored ? (V10 = V * Mirror) : V; + + if(dodrawcell(c)) { + reclevel = maxreclevel - lev; + drawcell(c, (hs.spin || purehepta) ? V1 * spin(hs.spin*2*M_PI/S7 + (purehepta ? M_PI:0)) : V1, hs.spin, + hs.mirrored); + } if(lev <= 0) return; - if(!purehepta) for(int d=0; d<7; d++) { + if(!purehepta) for(int d=0; dmov[ds] && c->spn[ds] == 0) - drawcell(c->mov[ds], V * hexmove[d], 0); + if(c->mov[ds] && c->spn(ds) == 0 && dodrawcell(c->mov[ds])) { + drawcell(c->mov[ds], V1 * hexmove[d], 0, hs.mirrored ^ c->mirror(ds)); + } } if(lev <= 1) return; - for(int d=0; d<7; d++) { + for(int d=0; d= 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; + return Mat; + } void drawEuclidean() { DEBB(DF_GRAPH, (debugfile,"drawEuclidean\n")); eucoord px, py; - if(!lcenterover) lcenterover = cwt.c; + if(!centerover) centerover = cwt.c; // printf("centerover = %p player = %p [%d,%d]-[%d,%d]\n", lcenterover, cwt.c, // mindx, mindy, maxdx, maxdy); - decodeMaster(lcenterover->master, px, py); + decodeMaster(centerover->master, px, py); int minsx = mindx-1, maxsx=maxdx+1, minsy=mindy-1, maxsy=maxdy+1; mindx=maxdx=mindy=maxdy=0; @@ -4045,17 +6132,10 @@ void drawEuclidean() { for(int dy=minsy; dy<=maxsy; dy++) { eucoord x = dx+px; eucoord y = dy+py; + reclevel = eudist(dx, dy); 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; + transmatrix Mat = eumove(x, y); Mat = View * Mat; // Mat[0][0] = -1; @@ -4066,7 +6146,7 @@ void drawEuclidean() { // Mat = Mat * xpush(x-30) * ypush(y-30); int cx, cy, shift; - getcoord(Mat * C0, cx, cy, shift); + getcoord0(tC0(Mat), cx, cy, shift); if(cx >= 0 && cy >= 0 && cx < vid.xres && cy < vid.yres) { if(dx < mindx) mindx = dx; if(dy < mindy) mindy = dy; @@ -4074,14 +6154,39 @@ void drawEuclidean() { if(dy > maxdy) maxdy = dy; } - drawcell(c, Mat, 0); + if(dodrawcell(c)) { + drawcell(c, Mat, 0, false); + } } } void drawthemap() { + frameid++; + + if(!cheater && !svg::in && !inHighQual) { + if(sightrange > 7) sightrange = 7; + overgenerate = false; + } + + profile_frame(); + profile_start(0); + swap(gmatrix0, gmatrix); + gmatrix.clear(); + + wmspatial = vid.wallmode == 4 || vid.wallmode == 5; + wmescher = vid.wallmode == 3 || vid.wallmode == 5; + wmplain = vid.wallmode == 2 || vid.wallmode == 4; + wmascii = vid.wallmode == 0; + wmblack = vid.wallmode == 1; + + mmitem = vid.monmode >= 1; + mmmon = vid.monmode >= 2; + mmhigh = vid.monmode == 3 || vid.monmode == 5; + mmspatial = vid.monmode == 4 || vid.monmode == 5; + DEBB(DF_GRAPH, (debugfile,"draw the map\n")); - fanframe = ticks / 150.0 / M_PI; + fanframe = ticks / (purehepta ? 300 : 150.0) / M_PI; for(int m=0; mitem == itCompass; + showPirateX = false; + for(int i=0; iitem == itCompass) showPirateX = true; using namespace yendor; @@ -4111,16 +6218,18 @@ void drawthemap() { if(yi[yii].path[i]->cpdist <= sightrange) { keycell = yi[yii].path[i]; keycelldist = YDIST - i; - } + } } - #ifndef MOBILE - lmouseover = mouseover; - #endif modist = 1e20; mouseover = NULL; modist2 = 1e20; mouseover2 = NULL; mouseovers = XLAT("Press F1 or right click for help"); - centdist = 1e20; lcenterover = centerover; centerover = NULL; + centdist = 1e20; centerover = NULL; + + for(int i=0; i 1) sphereflip[2][2] = -1; + maxreclevel = conformal::on ? sightrange + 2: - (!playermoved) ? sightrange+1 : sightrange + 4, - hsOrigin, View); - + (!playermoved) ? sightrange+1 : sightrange + 4; + + drawrec(viewctr, + maxreclevel, + hsOrigin, ypush(vid.yshift) * sphereflip * View); + } + + #ifdef ROGUEVIZ + rogueviz::drawExtra(); + #endif + + profile_stop(1); + profile_start(4); + drawMarkers(); + profile_stop(4); + drawFlashes(); + + if(multi::players > 1 && !shmup::on) { + if(shmup::centerplayer != -1) + cwtV = multi::whereis[shmup::centerplayer]; + else { + hyperpoint h; + for(int i=0; i<3; i++) h[i] = 0; + for(int p=0; ppat; else if(shmup::centerplayer != -1) cwtV = shmup::pc[shmup::centerplayer]->pat; - else if(shmup::players == 2) { - hyperpoint h0 = shmup::pc[0]->pat * C0; - hyperpoint h1 = shmup::pc[1]->pat * C0; - hyperpoint h2 = mid(h0, h1); - cwtV = rgpushxto0(h2); - } - else if(shmup::players == 3) { - hyperpoint h0 = shmup::pc[0]->pat * C0; - hyperpoint h1 = shmup::pc[1]->pat * C0; - hyperpoint h2 = shmup::pc[2]->pat * C0; - hyperpoint h3 = mid3(h0, h1, h2); - cwtV = rgpushxto0(h3); - } - else if(shmup::players == 4) { - hyperpoint h0 = shmup::pc[0]->pat * C0; - hyperpoint h1 = shmup::pc[1]->pat * C0; - hyperpoint h2 = shmup::pc[2]->pat * C0; - hyperpoint h3 = shmup::pc[3]->pat * C0; - hyperpoint h4 = mid4(h0, h1, h2, h3); - cwtV = rgpushxto0(h4); + else { + hyperpoint h; + for(int i=0; i<3; i++) h[i] = 0; + for(int p=0; ppat); + for(int i=0; i<3; i++) h[i] += h1[i]; + } + h = mid(h, h); + cwtV = rgpushxto0(h); } } - #ifdef LOCAL - localDrawMap(); + #ifndef MOBILE + Uint8 *keystate = SDL_GetKeyState(NULL); + lmouseover = mouseover; + bool useRangedOrb = (!(vid.shifttarget & 1) && haveRangedOrb() && lmouseover && lmouseover->cpdist > 1) || (keystate[SDLK_RSHIFT] | keystate[SDLK_LSHIFT]); + if(!useRangedOrb && cmode != emMapEditor && DEFAULTCONTROL && !outofmap(mouseh)) { + void calcMousedest(); + calcMousedest(); + cellwalker cw = cwt; bool f = flipplayer; + items[itWarning]+=2; + + bool recorduse[ittypes]; + for(int i=0; i= 0 ? cwt.c->mov[(cwt.spin + mousedest.d) % cwt.c->type] : cwt.c; + } #endif + profile_stop(0); } void spinEdge(ld aspd) { @@ -4178,14 +6326,17 @@ void spinEdge(ld aspd) { } void centerpc(ld aspd) { + if(vid.sspeed >= 4.99) aspd = 1000; DEBB(DF_GRAPH, (debugfile,"center pc\n")); - hyperpoint H = cwtV * C0; - ld R = sqrt(H[0] * H[0] + H[1] * H[1]); + hyperpoint H = ypush(-vid.yshift) * sphereflip * tC0(cwtV); + if(H[0] == 0 && H[1] == 0) return; // either already centered or direction unknown + ld R = hdist0(H); // = sqrt(H[0] * H[0] + H[1] * H[1]); if(R < 1e-9) { /* if(playerfoundL && playerfoundR) { } */ spinEdge(aspd); + fixmatrix(View); return; } @@ -4212,17 +6363,17 @@ void centerpc(ld aspd) { } } -void drawmovestar() { +void drawmovestar(double dx, double dy) { DEBB(DF_GRAPH, (debugfile,"draw movestar\n")); if(!playerfound) return; if(shmup::on) return; - if(rug::rugged) return; - - if(vid.axes == 0 || (vid.axes == 1 && mousing)) return; +#ifndef NORUG + if(rug::rugged && multi::players == 1 && !multi::alwaysuse) return; +#endif - hyperpoint H = cwtV * C0; + hyperpoint H = tC0(cwtV); ld R = sqrt(H[0] * H[0] + H[1] * H[1]); transmatrix Centered = Id; @@ -4230,23 +6381,32 @@ void drawmovestar() { Centered = eupush(H[0], H[1]); else if(R > 1e-9) Centered = rgpushxto0(H); - int starcol = (vid.goteyes2 ? 0xE08060 : 0xC00000); + Centered = Centered * rgpushxto0(hpxy(dx*5, dy*5)); + if(multi::cpid >= 0) multi::crosscenter[multi::cpid] = Centered; - if(vid.axes == 3 || (vid.wallmode == 2 && vid.axes == 1)) - queuepoly(Centered, shMovestar, darkena(starcol, 0, 0xFF)); + int rax = vid.axes; + if(rax == 1) rax = drawstaratvec(dx, dy) ? 2 : 0; + + if(rax == 0 || vid.axes == 4) return; + + int starcol = getcs().uicolor; + + if(vid.axes == 3) + queuepoly(Centered, shMovestar, starcol); 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; + if(leftclick && (d == 2 || d == 6 || d == 1 || d == 7)) col &= 0xFFFFFF3F; + if(rightclick && (d == 2 || d == 6 || d == 3 || d == 5)) col &= 0xFFFFFF3F; + if(!leftclick && !rightclick && (d&1)) col &= 0xFFFFFF3F; #endif // EUCLIDEAN if(euclid) - queueline(Centered * C0, Centered * ddi(d * 10.5, 0.5) * C0, col >> darken); + queueline(tC0(Centered), Centered * ddi0(d * 10.5, 0.5) , col, 0); else - queueline(Centered * C0, Centered * spin(M_PI*d/4)* xpush(.5) * C0, col >> darken); +// queueline(tC0(Centered), Centered * spin(M_PI*d/4)* xpush(d==0?.7:d==2?.6:.5) * C0, col >> darken); + queueline(tC0(Centered), Centered * xspinpush0(M_PI*d/4, d==0?.7:d==2?.5:.2), col, 3); } } @@ -4258,11 +6418,11 @@ void optimizeview() { transmatrix TB = Id; - for(int i=-1; i<7; i++) { + for(int i=-1; i 1e-9) Centered = eupush(-H[0], -H[1]) * Centered; ld binv = 99; ld dirdist[7]; - for(int i=0; itype; i++) - dirdist[i] = intval(Centered * spin(-i * 2 * M_PI /cwt.c->type) * xpush(1) * C0, P); - + for(int i=0; itype; i++) { + dirdist[i] = intval(Centered * xspinpush0(-i * 2 * M_PI /cwt.c->type, .5), P); + } + movedir res; res.d = -1; @@ -4296,8 +6458,10 @@ movedir vectodir(const hyperpoint& P) { binv = dirdist[i]; res.d = i; res.subdir = dirdist[(i+1)%cwt.c->type] < dirdist[(i+cwt.c->type-1)%cwt.c->type] ? 1 : -1; + if(sphere) res.subdir = -res.subdir; } } + // if(euclid) bdir = (bdir + 3) % 6; return res; } @@ -4305,22 +6469,25 @@ movedir vectodir(const hyperpoint& P) { void movepckeydir(int d) { DEBB(DF_GRAPH, (debugfile,"movepckeydir\n")); // 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)); + + movedir md = + vectodir(spin(-d * M_PI/4) * tC0(pushone())); + + movepcto(md); } void calcMousedest() { if(outofmap(mouseh)) return; if(revcontrol == true) { mouseh[0] = -mouseh[0]; mouseh[1] = -mouseh[1]; } - ld mousedist = intval(mouseh, curcell * C0); + ld mousedist = intval(mouseh, tC0(shmup::ggmatrix(cwt.c))); mousedest.d = -1; + cellwalker bcwt = cwt; + ld dists[7]; - for(int i=0; itype; i++) - dists[i] = intval(mouseh, movecell[i] * C0); + for(int i=0; itype; i++) + dists[i] = intval(mouseh, tC0(shmup::ggmatrix(cwt.c->mov[i]))); /* printf("curcell = %Lf\n", mousedist); for(int i=0; itype; i++) @@ -4332,26 +6499,41 @@ void calcMousedest() { mousedest.subdir = dists[(i+1)%cwt.c->type] < dists[(i+cwt.c->type-1)%cwt.c->type] ? 1 : -1; + + if(cwt.mirrored) + mousedest.d = fixdir(-mousedest.d, cwt.c), + mousedest.subdir = -mousedest.subdir; + + if(sphere) mousedest.subdir = -mousedest.subdir; } + if(revcontrol == true) { mouseh[0] = -mouseh[0]; mouseh[1] = -mouseh[1]; } + cwt = bcwt; } void mousemovement() { calcMousedest(); movepcto(mousedest); + lmouseover = NULL; } long double sqr(long double x) { return x*x; } +// old style joystick control + void checkjoy() { DEBB(DF_GRAPH, (debugfile,"check joy\n")); - if(shmup::on) return; + if(!DEFAULTCONTROL) return; ld joyvalue1 = sqr(vid.joyvalue); ld joyvalue2 = sqr(vid.joyvalue2); ld jx = joyx; ld jy = joyy; ld sq = jx*jx+jy*jy; + + static int laststate = 0; + int curstate = sq < joyvalue1 ? 0 : sq < joyvalue2 ? 1 : 2; + if(curstate != laststate) flashMessages(), laststate = curstate; if(autojoy) { if(sq < joyvalue1) { if(joydir.d >= 0) movepcto(joydir); joydir.d = -1; return; } @@ -4380,73 +6562,31 @@ void checkpanjoy(double t) { View = gpushxto0(hpxy(jx, jy)) * View; } +int realradius; + void calcparam() { DEBB(DF_GRAPH, (debugfile,"calc param\n")); vid.xcenter = vid.xres / 2; vid.ycenter = vid.yres / 2; + + realradius = min(vid.xcenter, vid.ycenter); + vid.radius = int(vid.scale * vid.ycenter) - (ISANDROID ? 2 : ISIOS ? 40 : 40); + realradius = min(realradius, vid.radius); + 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.ycenter = vid.yres - realradius - vid.fsize - (ISIOS ? 10 : 0); } - vid.beta = 1 + vid.alpha + vid.eye; - vid.alphax = vid.alpha + vid.eye; + ld eye = vid.eye; if(pmodel || rug::rugged) eye = 0; + vid.beta = 1 + vid.alpha + eye; + vid.alphax = vid.alpha + eye; vid.goteyes = vid.eye > 0.001 || vid.eye < -0.001; vid.goteyes2 = vid.goteyes; } -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 != "") { - if(val[0] != ' ') { - 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); - } - else - displaystr(dx+75, dy, 0, vid.fsize, val, xthis ? 0xFFFF00 : 0x808080, 16); - } - -#ifndef MOBILE - string mk = s0 + mkey; - int hkx = dx + 100; - if(mkey >= 1 && mkey <= 26) mk = s0 + "^", mk += (mkey+64), hkx -= vid.fsize; - // if(mkey >= 64+1 && mkey <= 64+26) mk = s0 + "Shift+", mk += mkey; - displaystr(hkx, dy, 0, vid.fsize, mk, xthis ? 0xFFFF00 : 0xC0F0C0, 0); -#endif - - 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) { if(displayfr(x, y, rad, vid.fsize, name, 0x808080, align)) { displayfr(x, y, rad, vid.fsize, name, 0xFFFF00, align); @@ -4454,6 +6594,14 @@ void displayButton(int x, int y, const string& name, int key, int align, int rad } } +bool displayButtonS(int x, int y, const string& name, int col, int align, int size) { + if(displaystr(x, y, 0, size, name, col, align)) { + displaystr(x, y, 0, size, name, 0xFFFF00, align); + return true; + } + else return false; + } + void displayColorButton(int x, int y, const string& name, int key, int align, int rad, int color, int color2) { if(displayfr(x, y, rad, vid.fsize, name, color, align)) { if(color2) displayfr(x, y, rad, vid.fsize, name, color2, align); @@ -4473,8 +6621,6 @@ void quitOrAgain() { displayButton(vid.xres/2, y + vid.fsize*7/2, XLAT("or 't' to see the top scores"), 't', 8, 2); displayButton(vid.xres/2, y + vid.fsize*10/2, XLAT("or 'v' to see the main menu"), 'v', 8, 2); displayButton(vid.xres/2, y + vid.fsize*13/2, XLAT("or 'o' to see the world overview"), 'o', 8, 2); - if(canmove) displayButton(vid.xres/2, y + vid.fsize*16/2, XLAT("or another key to continue"), ' ', 8, 2); - else displayButton(vid.xres/2, y + vid.fsize*16/2, XLAT("or ESC to see how it ended"), SDLK_ESCAPE, 8, 2); } #endif @@ -4503,16 +6649,38 @@ string timeline() { } void showGameover() { - int y = vid.yres * (1000-618) / 1000 - vid.fsize * 7/2; - displayfr(vid.xres/2, y, 4, vid.fsize*2, + + dialog::init( cheater ? XLAT("It is a shame to cheat!") : showoff ? XLAT("Showoff mode") : canmove && princess::challenge ? XLAT("%1 Challenge", moPrincess) : canmove ? XLAT("Quest status") : - XLAT("GAME OVER"), 0xC00000, 8 + XLAT("GAME OVER"), + 0xC00000, 200, 100 ); - 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); + dialog::addInfo(XLAT("Your score: %1", its(gold()))); + dialog::addInfo(XLAT("Enemies killed: %1", its(tkills()))); + + if(items[itOrbYendor]) { + dialog::addInfo(XLAT("Orbs of Yendor found: %1", its(items[itOrbYendor])), iinf[itOrbYendor].color); + dialog::addInfo(XLAT("CONGRATULATIONS!"), iinf[itOrbYendor].color); + } + else { + if(princess::challenge) + dialog::addInfo(XLAT("Follow the Mouse and escape with %the1!", moPrincess)); + else if(gold() < 30) + dialog::addInfo(XLAT("Collect 30 $$$ to access more worlds")); + else if(gold() < 60) + dialog::addInfo(XLAT("Collect 60 $$$ to access even more lands")); + else if(!hellUnlocked()) + dialog::addInfo(XLAT("Collect at least 10 treasures in each of 9 types to access Hell")); + else if(items[itHell] < 10) + dialog::addInfo(XLAT("Collect at least 10 Demon Daisies to find the Orbs of Yendor")); + else if(size(yendor::yi) == 0) + dialog::addInfo(XLAT("Look for the Orbs of Yendor in Hell or in the Crossroads!")); + else + dialog::addInfo(XLAT("Unlock the Orb of Yendor!")); + } if(!timerstopped && !canmove) { savetime += time(NULL) - timerstart; @@ -4521,58 +6689,33 @@ void showGameover() { 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(princess::challenge) ; - 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(yendor::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(princess::challenge) - displayfr(vid.xres/2, y+vid.fsize*6, 2, vid.fsize, XLAT("Follow the Mouse and escape with %the1!", moPrincess), 0xC0C0C0, 8); + if(princess::challenge) ; else 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(kills[moVizier] == 0 && (items[itFernFlower] < 5 || items[itGold] < 5)) { - displayfr(vid.xres/2, y+vid.fsize*6, 2, vid.fsize, XLAT("Kill a Vizier in the Palace to access Emerald Mine"), 0xC0C0C0, 8); - } + dialog::addInfo(XLAT("Defeat 100 enemies to access the Graveyard")); + else if(kills[moVizier] == 0 && (items[itFernFlower] < 5 || items[itGold] < 5)) + dialog::addInfo(XLAT("Kill a Vizier in the Palace to access Emerald Mine")); else if(items[itEmerald] < 5) - displayfr(vid.xres/2, y+vid.fsize*6, 2, vid.fsize, XLAT("Collect 5 Emeralds to access Camelot"), 0xC0C0C0, 8); - else if(hellUnlocked()) { + dialog::addInfo(XLAT("Collect 5 Emeralds to access Camelot")); + else if(hellUnlocked() && !chaosmode) { bool b = true; for(int i=0; i 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); + int vrx = min(vid.radius, vid.xres/2 - 40); + int vry = min(vid.radius, min(vid.ycenter, vid.yres - vid.ycenter) - 20); + int x = vid.xcenter + px * vrx; + int y = vid.ycenter + py * (vry - siz/2); + int vrr = int(hypot(vrx, vry) * sqrt(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 + && abs(mousex - vid.xcenter) < vrr + && abs(mousey - vid.ycenter) < vrr + && hypot(mousex-vid.xcenter, mousey-vid.ycenter) > vrr && px == (mousex > vid.xcenter ? 1 : -1) && py == (mousey > vid.ycenter ? 1 : -1) ) col = 0xFF0000; @@ -4621,6 +6772,7 @@ void displayabutton(int px, int py, string s, int col) { } #endif +#ifndef NOSAVE vector scores; int scoresort = 2; @@ -4655,7 +6807,6 @@ string displayfor(score* S) { return its(S->box[scoredisplay]); } -#ifndef ANDROID void loadScores() { scores.clear(); FILE *f = fopen(scorefile, "rt"); @@ -4669,7 +6820,7 @@ void loadScores() { 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; + {if(fscanf(f, "%s", buf) <= 0) break;} sc.ver = buf; for(int i=0; i > pickscore_options; + bool notgl = false; - -void showPickScores() { - - int d = scoredisplay; - - vector > v; - for(int i=0; i= size(scores)) break; - - score& S(scores[id]); - - bool wrongtype = false; - - wrongtype |= (euclid && (!S.box[116] || S.box[120] != euclidland)); - wrongtype |= (!euclid && S.box[116]); - - wrongtype |= (scoremode == 1 && !S.box[119]); - wrongtype |= (scoremode != 1 && S.box[119]); - wrongtype |= (scoremode == 2 && (!S.box[117] || S.box[118] >= PUREHARDCORE_LEVEL)); - - if(wrongtype) { id++; continue; } - - 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 - buttonclicked = false; - 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 == 'm') { scoremode++; scoremode %= 3; } - 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 void setAppropriateOverview() { + clearMessages(); if(tactic::on) cmode = emTactic; else if(yendor::on) cmode = emYendor; - else if(euclid) + else if(geometry != gNormal) cmode = emPickEuclidean; else cmode = emOverview; } -void drawStats() { - - DEBB(DF_GRAPH, (debugfile,"drawStats\n")); - -#ifdef IOS - if(cmode != emNormal && cmode != emQuit) - return; -#endif +ld textscale() { + return vid.fsize / (vid.radius * crossf) * (1+vid.alphax) * 2; + } - int vx, vy; +transmatrix xymatrix(int x, int y, ld scale) { + transmatrix V; + {for(int i=0; i<3; i++) for(int j=0; j<3; j++) V[i][j] = i==j ? 1 : 0; } + V[0][2] = (x - vid.xcenter + .0) / vid.radius * (1+vid.alphax); + V[1][2] = (y - vid.ycenter + .0) / vid.radius * (1+vid.alphax); + V[0][0] = scale; + V[1][1] = scale; + V[2][2] = 0; + return V; + } - int s = vid.fsize; - - bool portrait = vid.xres < vid.yres; - - if(portrait) { - vx = vid.fsize * 3; - vy = vid.fsize * 2; - if(havebugs) vy += vid.fsize * 3/2; +int monsterclass(eMonster m) { + if(isFriendly(m) || m == moTortoise) return 1; + else if(isMonsterPart(m)) return 2; + else return 0; + } + +int glyphclass(int i) { + if(i < ittypes) { + eItem it = eItem(i); + return itemclass(it) == IC_TREASURE ? 0 : 1; } else { - vx = vid.xres - vid.fsize * 3; - vy = vid.fsize; + eMonster m = eMonster(i-ittypes); + return monsterclass(m) == 0 ? 2 : 3; } - - #define ADV(z) \ - {if(portrait) { \ - 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 > vid.xres/2 && vy > vid.yres - s * 3)) vx += (vx > vid.xres/2 ? -5:5) * vid.fsize, vy = vid.fsize * 5/2; \ - }} + } - if(!portrait) vid.portreduction = 0; - if(vid.xres >= vid.yres * 5/3 || portrait) - vid.killreduction = vid.itemreduction = 0; - - if(portrait) vid.fsize = s - vid.portreduction; - else vid.fsize = s - vid.itemreduction; +int subclass(int i) { + if(i < ittypes) + return itemclass(eItem(i)); + else + return monsterclass(eMonster(i-ittypes)); + } + +#define GLYPH_MARKTODO 1 +#define GLYPH_MARKOVER 2 +#define GLYPH_LOCAL 4 +#define GLYPH_IMPORTANT 8 +#define GLYPH_NONUMBER 16 +#define GLYPH_DEMON 32 +#define GLYPH_RUNOUT 64 +#define GLYPH_INPORTRAIT 128 +#define GLYPH_LOCAL2 256 +#define GLYPH_TARGET 512 + +eGlyphsortorder glyphsortorder; - if(displaynum(vx, vy, 0, vid.fsize, 0xFFFFFF, gold(), "$$$")) { +int zero = 0; + +int& ikmerge(int i) { + if(i < ittypes) return items[i]; + else if(i == ittypes) return zero; + else return kills[i-ittypes]; + } + +const int glyphs = ittypes + motypes; + +int gfirsttime[glyphs], glasttime[glyphs], gcopy[glyphs], ikland[glyphs]; +int glyphorder[glyphs]; + +void updatesort() { + for(int i=0; i gfirsttime[j]; + if(glyphsortorder == gsoLastTop) + return glasttime[i] > glasttime[j]; + if(glyphsortorder == gsoLastBottom) + return glasttime[i] < glasttime[j]; + if(glyphsortorder == gsoValue) + return ikmerge(i) > ikmerge(j); + if(glyphsortorder == gsoLand) + return ikland[i] < ikland[j]; + return 0; + } + +int glyphflags(int gid) { + int f = 0; + if(gid < ittypes) { + eItem i = eItem(gid); + if(itemclass(i) == IC_NAI) f |= GLYPH_NONUMBER; + if(isElementalShard(i)) { + f |= GLYPH_LOCAL; + if(i == localshardof(cwt.c->land)) f |= GLYPH_LOCAL2; + } + if(i == treasureType(cwt.c->land)) f |= GLYPH_LOCAL | GLYPH_LOCAL2 | GLYPH_IMPORTANT; + if(i == itHolyGrail) { + if(items[i] >= 3) f |= GLYPH_MARKOVER; + } + else if(itemclass(i) == IC_TREASURE) { + if(items[i] >= 25 && items[i] < 100) f |= GLYPH_MARKOVER; + else if(items[i] < 10) f |= GLYPH_MARKTODO; + } + else { + f |= GLYPH_IMPORTANT; + if(itemclass(i) == IC_ORB && items[i] < 10) f |= GLYPH_RUNOUT; + } + if(i == orbToTarget) f |= GLYPH_TARGET; + f |= GLYPH_INPORTRAIT; + } + else { + eMonster m = eMonster(gid-ittypes); + if(m == moLesser) f |= GLYPH_IMPORTANT | GLYPH_DEMON | GLYPH_INPORTRAIT; + int isnat = isNative(cwt.c->land, m); + if(isnat) f |= GLYPH_LOCAL | GLYPH_IMPORTANT | GLYPH_INPORTRAIT; + if(isnat == 2) f |= GLYPH_LOCAL2; + if(m == monsterToSummon) f |= GLYPH_TARGET; + } + return f; + } + +bool displayglyph(int cx, int cy, int buttonsize, char glyph, int color, int qty, int flags) { + + bool b = + mousex >= cx && mousex < cx+buttonsize && mousey >= cy-buttonsize/2 && mousey <= cy-buttonsize/2+buttonsize; + + int glsize = buttonsize; + if(glyph == '%' || glyph == 'M' || glyph == 'W') glsize = glsize*4/5; + + if(glyph == '*') + displaychr(cx + buttonsize/2, cy+buttonsize/4, 0, glsize*3/2, glyph, darkenedby(color, b?0:1)); + else + displaychr(cx + buttonsize/2, cy, 0, glsize, glyph, darkenedby(color, b?0:1)); + + string fl = ""; + string str = its(qty); + + if(flags & GLYPH_TARGET) fl += "!"; + if(flags & GLYPH_LOCAL2) fl += "+"; + else if(flags & GLYPH_LOCAL) fl += "-"; + if(flags & GLYPH_DEMON) fl += "X"; + if(flags & GLYPH_MARKOVER) str += "!"; + + if(fl != "") + displaystr(cx + buttonsize, cy-buttonsize/2 + buttonsize/4, 0, buttonsize/2, fl, darkenedby(color, 0), 16); + + if(flags & GLYPH_NONUMBER) str = ""; + + int bsize = + (qty < 10 && (flags & (GLYPH_MARKTODO | GLYPH_RUNOUT))) ? buttonsize*3/4 : + qty < 100 ? buttonsize / 2 : + buttonsize / 3; + if(str != "") + displayfr(cx + buttonsize, cy + buttonsize/2 - bsize/2, 1, bsize, str, color, 16); + + return b; + } + +void drawStats() { +#ifdef ROGUEVIZ + if(rogueviz::on) return; +#endif + instat = false; + bool portrait = vid.xres < vid.yres; + int colspace = portrait ? (vid.yres - vid.xres - vid.fsize*3) : (vid.xres - vid.yres - 16) / 2; + int rowspace = portrait ? vid.xres - 16 : vid.yres - vid.fsize * 4; + int colid[4], rowid[4]; + int maxbyclass[4]; + for(int z=0; z<4; z++) maxbyclass[z] = 0; + for(int i=0; i columns) { vid.killreduction++; continue; } + coltaken = 0; + } + colid[z] = coltaken, rowid[z] = 0, + coltaken += (maxbyclass[z] + rows-1) / rows; + } + if(coltaken > columns) { vid.killreduction++; continue; } + break; + } + + if(buttonsize <= vid.fsize*3/4) { + imponly = true; buttonsize = minsize; + rows = rowspace / buttonsize; if(!rows) return; + colid[0] = 0; colid[2] = portrait ? 1 : 0; + } + + updatesort(); + stable_sort(glyphorder, glyphorder+glyphs, glyphsort); + + for(int i0=0; i0= rows) rowid[z] = 0, colid[z]++; + + char glyph = i < ittypes ? iinf[i].glyph : minf[i-ittypes].glyph; + int color = i < ittypes ? iinf[i].color : minf[i-ittypes].color; + + if(displayglyph(cx, cy, buttonsize, glyph, color, ikmerge(i), imp)) { + instat = true; + getcstat = SDLK_F1; + if(i < ittypes) { + eItem it = eItem(i); + int t = itemclass(it); + if(t == IC_TREASURE) + mouseovers = XLAT("treasure collected: %1", it); + if(t == IC_OTHER) + mouseovers = XLAT("objects found: %1", it); + if(t == IC_NAI) + mouseovers = XLAT("%1", it); + if(t == IC_ORB) + mouseovers = XLAT("orb power: %1", eItem(i)); + if(it == itGreenStone) { + mouseovers += XLAT(" (click to drop)"); + getcstat = 'g'; + } + if(imp & GLYPH_LOCAL) mouseovers += XLAT(" (local treasure)"); + help = generateHelpForItem(it); + } + else { + eMonster m = eMonster(i-ittypes); + if(isMonsterPart(m)) + mouseovers = s0 + XLAT("parts destroyed: %1", m); + else if(isFriendly(m) && isNonliving(m)) + mouseovers = s0 + XLAT("friends destroyed: %1", m); + else if(isFriendly(m)) + mouseovers = s0 + XLAT("friends killed: %1", m); + else if(isNonliving(m)) + mouseovers = s0 + XLAT("monsters destroyed: %1", m); + else if(m == moTortoise) + mouseovers = s0 + XLAT("animals killed: %1", m); + else + mouseovers = s0 + XLAT("monsters killed: %1", m); + if(imp & GLYPH_LOCAL2) mouseovers += XLAT(" (killing increases treasure spawn)"); + else if(imp & GLYPH_LOCAL) mouseovers += XLAT(" (appears here)"); + help = generateHelpForMonster(m); + } + } + } + + string s0; + if(displayButtonS(vid.xres - 8, vid.fsize, "score: " + its(gold()), 0xFFFFFFF, 16, vid.fsize)) { mouseovers = XLAT("Your total wealth"), - help = XLAT( + instat = true, + getcstat = SDLK_F1, + help = helptitle(XLAT("Your total wealth"), 0xFFD500) + + XLAT( "The total value of the treasure you have collected.\n\n" "Every world type contains a specific type of treasure, worth 1 $$$; " "your goal is to collect as much treasure as possible, but every treasure you find " @@ -4906,65 +7175,28 @@ void drawStats() { "Orbs of Yendor are worth 50 $$$ each.\n\n" ); } - - ADV(3); if(portrait) {ADV(3);} - -#ifndef MOBILE - int oldz = 0; -#endif - - for(int z=0; z<3; z++) for(int i=0; i= 3) { - int kvx, kvy; - if(!portrait) - kvx = vid.fsize * 8, kvy = vid.fsize; - else { ADV(3); 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."); - if(portrait) ADV(6); - } - - if(!portrait) vid.fsize = s - vid.killreduction; - - for(int i=1; i s * 3) vid.killreduction++; - if(portrait && vy > vid.ycenter - vid.radius && vid.fsize > 1) vid.portreduction ++; - vid.fsize = s; - achievement_display(); - #ifdef LOCAL process_local_stats(); #endif @@ -5040,90 +7224,156 @@ void IMAGESAVE(SDL_Surface *s, const char *fname) { } #endif -void saveHighQualityShot() { +int pngres = 2000; + +void saveHighQualityShot(const char *fname) { #ifndef GFX addMessage(XLAT("High quality shots not available on this platform")); return; #endif - int dcs = size(dcal); - for(int i=0; icpdist <= 4) setdist(c, 1, NULL); - } + dynamicval v3(sightrange, (cheater && sightrange < 10) ? 10 : sightrange); + + if(cheater) doOvergenerate(); time_t timer; timer = time(NULL); - SDL_Surface *sav = s; - s = SDL_CreateRGBSurface(SDL_SWSURFACE,2000,2000,32,0,0,0,0); - - int ssr = sightrange; sightrange = 10; int sch = cheater; cheater = 0; - - bool b = vid.usingGL; + dynamicval v(vid, vid); + dynamicval v2(inHighQual, true); + dynamicval v4(cheater, 0); + vid.xres = vid.yres = pngres; vid.usingGL = false; - videopar vid2 = vid; - vid.xres = vid.yres = 2000; // if(vid.pmodel == 0) vid.scale = 0.99; calcparam(); - inHighQual = true; + #ifdef ROGUEVIZ + rogueviz::fixparam(); + #endif + + dynamicval v5(s, SDL_CreateRGBSurface(SDL_SWSURFACE,vid.xres,vid.yres,32,0,0,0,0)); darken = 0; - for(int i=0; i<2; i++) { - SDL_FillRect(s, NULL, i ? 0xFFFFFF : 0); + for(int i=0; i<(fname?1:2); i++) { + SDL_FillRect(s, NULL, fname ? backcolor : i ? 0xFFFFFF : 0); drawfullmap(); char buf[128]; strftime(buf, 128, "bigshota-%y%m%d-%H%M%S" IMAGEEXT, localtime(&timer)); buf[7] += i; - IMAGESAVE(s, buf); + if(!fname) fname = buf; + IMAGESAVE(s, fname); - if(i == 0) addMessage(XLAT("Saved the high quality shot to %1", buf)); + if(i == 0) addMessage(XLAT("Saved the high quality shot to %1", fname)); } - inHighQual = false; - SDL_FreeSurface(s); s = sav; vid = vid2; sightrange = ssr; cheater = sch; - vid.usingGL = b; + SDL_FreeSurface(s); } #endif +void addball(ld a, ld b, ld c) { + hyperpoint h; + ballmodel(h, a, b, c); + for(int i=0; i<3; i++) h[i] *= vid.radius; + curvepoint(h); + } + +void ballgeometry() { + queuereset(vid.usingGL ? mdDisk : mdUnchanged, PPR_CIRCLE); + for(int i=0; i<60; i++) + addball(i * M_PI/30, 10, 0); + for(double d=10; d>=-10; d-=.2) + addball(0, d, 0); + for(double d=-10; d<=10; d+=.2) + addball(0, d, geom3::depth); + addball(0, 0, -geom3::camera); + addball(0, 0, geom3::depth); + addball(0, 0, -geom3::camera); + addball(0, -10, 0); + addball(0, 0, -geom3::camera); + queuecurve(darkena(0xFF, 0, 0x80), 0, PPR_CIRCLE); + queuereset(pmodel, PPR_CIRCLE); + } + void drawfullmap() { DEBB(DF_GRAPH, (debugfile,"draw full map\n")); ptds.clear(); - if(!vid.goteyes && !euclid && pmodel == 0) { - queuecircle(vid.xcenter, vid.ycenter, vid.radius, 0xFF >> darken); + if(!vid.goteyes && !euclid && (pmodel == mdDisk || pmodel == mdBall)) { + double rad = vid.radius; + if(sphere) { + if(!vid.grid && !elliptic) + rad = 0; + else if(vid.alphax <= 0) + ; + else if(vid.alphax <= 1 && (vid.grid || elliptic)) // mark the equator + rad = rad * 1 / vid.alphax; + else if(vid.grid) // mark the edge + rad /= sqrt(vid.alphax*vid.alphax - 1); + } + queuecircle(vid.xcenter, vid.ycenter, rad, + svg::in ? 0x808080FF : darkena(0xFF, 0, 0xFF), + vid.usingGL ? PPR_CIRCLE : PPR_OUTCIRCLE); + if(pmodel == mdBall) ballgeometry(); } - - if(pmodel == 3 || pmodel == 4) polygonal::drawBoundary(0xFF >> darken); - if(vid.wallmode < 2 && !euclid && !mapeditor::whichShape) { + if(pmodel == mdHyperboloid) { + int col = darkena(0x80, 0, 0x80); + queueline(hpxyz(0,0,1), hpxyz(0,0,-vid.alpha), col, 0, PPR_CIRCLE); + queueline(xpush(+4)*C0, hpxyz(0,0,0), col, 0, PPR_CIRCLE); + queueline(xpush(+4)*C0, hpxyz(0,0,-vid.alpha), col, 0, PPR_CIRCLE); + queueline(xpush(-4)*C0, hpxyz(0,0,0), col, 0, PPR_CIRCLE); + queueline(xpush(-4)*C0, hpxyz(0,0,-vid.alpha), col, 0, PPR_CIRCLE); + queueline(hpxyz(-1,0,0), hpxyz(1,0,0), col, 0, PPR_CIRCLE); + } + + if(pmodel == mdPolygonal || pmodel == mdPolynomial) + polygonal::drawBoundary(darkena(0xFF, 0, 0xFF)); + + /* if(vid.wallmode < 2 && !euclid && !mapeditor::whichShape) { int ls = size(lines); if(ISMOBILE) ls /= 10; - for(int t=0; t> darken); - } + for(int t=0; t> (darken+1)); + } */ drawthemap(); - #ifndef MOBILE + #ifndef NORUG if(!inHighQual) { - if(cmode == emNormal && !rug::rugged) drawmovestar(); - if(rug::rugged && !rug::renderonce) queueline(C0, mouseh, 0xFF00FF); + if(cmode == emNormal && !rug::rugged) { + if(multi::players > 1) { + transmatrix bcwtV = cwtV; + for(int i=0; i>= 1, b++, n--; // memset(s->pixels, 0, vid.xres * vid.yres * 4); - if(!vid.usingGL) SDL_FillRect(s, NULL, 0); + if(!vid.usingGL) SDL_FillRect(s, NULL, backcolor); #endif if(!canmove) darken = 1; if(cmode != emNormal && cmode != emDraw && cmode != emCustomizeChar) darken = 2; if(cmode == emQuit && !canmove) darken = 0; - if(cmode == emOverview) darken = 4; + if(cmode == emOverview) darken = 16; + if(cmode == emNumber && dialog::lastmode == em3D) darken = 0; -#ifdef MOBILE - if(cmode == emQuit) darken = 1; -#endif - -#ifndef MOBILE +#ifndef NOEDIT if(cmode == emMapEditor && !mapeditor::subscreen && !mapeditor::choosefile) darken = 0; if(cmode == emDraw && mapeditor::choosefile) darken = 2; #endif - if(hiliteclick && darken == 0 && vid.monmode == 2) darken = 1; + if(hiliteclick && darken == 0 && mmmon) darken = 1; if(cmode == emProgress) darken = 0; if(conformal::includeHistory && cmode != emProgress) conformal::restore(); - if(rug::rugged) { -#ifndef MOBILE + if(darken >= 8) ; +#ifndef NORUG + else if(rug::rugged) { rug::actDraw(); -#endif } +#endif else drawfullmap(); if(conformal::includeHistory && cmode != emProgress) conformal::restoreBack(); - getcstat = 0; + getcstat = 0; inslider = false; if(cmode == emNormal || cmode == emQuit) drawStats(); @@ -5173,17 +7421,21 @@ void drawscreen() { buttonclicked = false; - 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("MENU"), andmode == 3 ? BTON : BTOFF); - } - - if(cmode == emQuit) { - displayabutton(-1, +1, XLAT("NEW"), BTON); - displayabutton(+1, -1, XLAT(canmove ? "PLAY" : ISIOS ? " " : "SHARE"), BTON); - displayabutton(+1, +1, XLAT("MENU"), BTON); + if(cmode == (canmove ? emNormal : emQuit)) { + if(andmode == 0 && shmup::on) { + using namespace shmupballs; + calc(); + drawCircle(xmove, yb, rad, 0xFFFFFFFF); + drawCircle(xmove, yb, rad/2, 0xFFFFFFFF); + drawCircle(xfire, yb, rad, 0xFF0000FF); + drawCircle(xfire, yb, rad/2, 0xFF0000FF); + } + else { + if(andmode != 0) 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 == 12 ? BTON : BTOFF); + displayabutton(+1, -1, XLAT("MENU"), andmode == 3 ? BTON : BTOFF); } #endif @@ -5194,25 +7446,16 @@ void drawscreen() { drawmessages(); 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 == emProgress) mouseovers = ""; - #endif displayMenus(); describeMouseover(); - if(havebugs && darken == 0) for(int k=0; k<3; k++) + if((havewhat&HF_BUG) && darken == 0 && (cmode == emNormal || cmode == emQuit)) for(int k=0; k<3; k++) displayfr(vid.xres/2 + vid.fsize * 5 * (k-1), vid.fsize*2, 2, vid.fsize, its(hive::bugcount[k]), minf[moBug0+k].color, 8); @@ -5221,6 +7464,7 @@ void drawscreen() { for(int p=0; ptype; i++) if(c->mov[i]) { if(c->mov[i]->land == laMinefield) minefieldNearby = true; @@ -5231,14 +7475,14 @@ void drawscreen() { } } - if((minefieldNearby || tmines) && canmove && !items[itOrbGhost] && darken == 0) { + if((minefieldNearby || tmines) && canmove && !items[itOrbAether] && darken == 0 && cmode == emNormal) { string s; if(tmines > 7) tmines = 7; int col = minecolors[tmines]; if(tmines == 7) seenSevenMines = true; - for(int p=0; p= 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 - // DEB - if(mouseover && targetclick) { - shmup::cpid = 0; - eItem i = targetRangedOrb(mouseover, roCheck); - if(i == itOrbSummon) { - eMonster m = summonedAt(mouseover); - displaychr(mousex, mousey, 0, vid.fsize, minf[m].glyph, minf[m].color); - } - else if(i) - displaychr(mousex, mousey, 0, vid.fsize, '@', iinf[i].color); - } -#endif - #ifndef MOBILE // SDL_UnlockSurface(s); -//profile("swapbuffers"); + DEBT("swapbuffers"); #ifdef GL if(vid.usingGL) SDL_GL_SwapBuffers(); else #endif @@ -5323,18 +7517,10 @@ void drawscreen() { //printf("\ec"); #endif - -//profile("centerpc"); - if(playermoved && vid.aspeed > 4.99 && !shmup::on) { - centerpc(1000); - playermoved = false; - return; - } - } #ifndef MOBILE -bool setfsize = false; +bool setfsize = true; void setvideomode() { @@ -5391,11 +7577,26 @@ void restartGraph() { else { viewctr.h = &origin; viewctr.spin = 0; + viewctr.mirrored = false; } View = Id; webdisplay = 0; + if(sphere) View = spin(-M_PI/2); } +void resetview() { + DEBB(DF_GRAPH, (debugfile,"reset view\n")); + View = Id; + // EUCLIDEAN + if(!euclid) + viewctr.h = cwt.c->master, + viewctr.spin = cwt.spin; + else centerover = cwt.c; + // SDL_LockSurface(s); + // SDL_UnlockSurface(s); + } + + void initcs(charstyle &cs) { cs.charid = 0; cs.skincolor = 0xD0D0D0FF; @@ -5403,21 +7604,22 @@ void initcs(charstyle &cs) { cs.dresscolor = 0xC00000FF; cs.swordcolor = 0xD0D0D0FF; cs.dresscolor2= 0x8080FFC0; + cs.uicolor = 0xFF0000FF; } -#ifndef ANDROID - -void savecs(FILE *f, charstyle& cs) { +#ifndef NOCONFIG +void savecs(FILE *f, charstyle& cs, int xvernum) { int gflags = cs.charid; if(vid.samegender) gflags |= 16; fprintf(f, "%d %d %08x %08x %08x %08x", gflags, vid.language, cs.skincolor, cs.haircolor, cs.swordcolor, cs.dresscolor); if(cs.charid == 3) fprintf(f, " %08x", cs.dresscolor2); + if(xvernum >= 8990) fprintf(f, " %x", cs.uicolor); fprintf(f, "\n"); } -void loadcs(FILE *f, charstyle& cs) { +void loadcs(FILE *f, charstyle& cs, int xvernum) { int gflags, err = fscanf(f, "%d%d%x%x%x%x", &gflags, &vid.language, &cs.skincolor, &cs.haircolor, &cs.swordcolor, &cs.dresscolor); @@ -5425,6 +7627,8 @@ void loadcs(FILE *f, charstyle& cs) { if(err) vid.samegender = (gflags & 16) ? true : false; if(cs.charid == 3) if(fscanf(f, "%x", &cs.dresscolor2)) ; + if(xvernum >= 8990) if(fscanf(f, "%x", &cs.uicolor)) + ; } void saveConfig() { @@ -5435,11 +7639,11 @@ void saveConfig() { 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 %lf %d %d\n", vid.joyvalue, vid.joyvalue2, vid.joypanthreshold, vid.joypanspeed, autojoy, vid.flashtime); + fprintf(f, "%f %f %f %f\n", float(vid.scale), float(vid.eye), float(vid.alpha), float(vid.sspeed)); + fprintf(f, "%d %d %d %d %d %d %d\n", vid.wallmode, vid.monmode, vid.axes, musicvolume, vid.framelimit, vid.usingGL, vid.usingAA); + fprintf(f, "%d %d %d %f %d %d\n", vid.joyvalue, vid.joyvalue2, vid.joypanthreshold, float(vid.joypanspeed), autojoy, vid.flashtime); - savecs(f, vid.cs); + savecs(f, vid.cs, 0); fprintf(f, "%d %d\n", vid.darkhepta, vid.shifttarget); @@ -5449,9 +7653,9 @@ void saveConfig() { fprintf(f, "%d %d %d %d %f %d %d\n", rug::renderonce, rug::rendernogl, rug::texturesize, purehepta, rug::scale, vid.steamscore, chaosmode); - fprintf(f, "%d %d %lf %d %d %lf\n", - pmodel, polygonal::SI, polygonal::STAR, polygonal::deg, - conformal::includeHistory, conformal::lvspeed); + fprintf(f, "%d %d %f %d %d %f\n", + pmodel, polygonal::SI, float(polygonal::STAR), polygonal::deg, + conformal::includeHistory, float(conformal::lvspeed)); fprintf(f, "%d %d %d %d %d %d\n", conformal::bandhalf, conformal::bandsegment, @@ -5461,21 +7665,57 @@ void saveConfig() { fprintf(f, "%d", polygonal::maxcoef); for(int i=0; i<=polygonal::maxcoef; i++) fprintf(f, " %lf %lf", (double) real(polygonal::coef[i]), (double) imag(polygonal::coef[i])); + + fprintf(f, "\n%d %d %d %f %d %d\n", + revcontrol, vid.drawmousecircle, sightrange, float(vid.mspeed), effvolume, vid.particles); + + { + int pt_depth = 0, pt_camera = 0, pt_alpha = 0; + using namespace geom3; + if(tc_depth > tc_camera) pt_depth++; + if(tc_depth < tc_camera) pt_camera++; + if(tc_depth > tc_alpha ) pt_depth++; + if(tc_depth < tc_alpha ) pt_alpha ++; + if(tc_alpha > tc_camera) pt_alpha++; + if(tc_alpha < tc_camera) pt_camera++; + + fprintf(f, "%f %f %f %f %f %f %f %d %d %d %f %f %d\n", + float(geom3::depth), float(geom3::camera), float(geom3::wall_height), + float(geom3::rock_wall_ratio), + float(geom3::human_wall_ratio), + float(geom3::lake_top), + float(geom3::lake_bottom), + pt_depth, pt_camera, pt_alpha, + float(geom3::highdetail), float(geom3::middetail), + glyphsortorder); + + fprintf(f, "%f %f %f %f\n", + float(vid.yshift), float(vid.camera_angle), + float(vid.ballangle), float(vid.ballproj) + ); + + fprintf(f, "%d\n", vid.mobilecompasssize); + + } 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, "scale, eye distance, parameter, scrolling speed\n"); + fprintf(f, "wallmode, monster mode, cross mode, music volume, 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, 16=same gender prince), language, skin color, hair color, sword color, dress color\n"); fprintf(f, "darken hepta, shift target\n"); fprintf(f, "euclid, euclid land, shmup, hardcore\n"); - fprintf(f, "version number, shmup players, shmup keyboard/joystick config\n"); + fprintf(f, "version number, shmup players, alwaysuse, shmup keyboard/joystick config\n"); fprintf(f, "hypersian rug config: renderonce, rendernogl, texturesize; purehepta; rug scale; share score; chaosmode\n"); fprintf(f, "conformal: model, sides, star, degree, includeHistory, speed\n"); fprintf(f, "conformal: bandwidth, segment, rotation, autoband, autohistory, dospiral\n"); fprintf(f, "conformal: degree, (degree+1) times {real, imag}\n"); + fprintf(f, "revcontrol, drawmousecircle, sight range, movement animation speed, sound effect volume, particle effects\n"); + fprintf(f, "3D parameters, sort order\n"); + fprintf(f, "yhsift, camera angle, ball angle, ball projection\n"); + fprintf(f, "compass size\n"); fclose(f); #ifndef MOBILE @@ -5485,6 +7725,13 @@ void saveConfig() { #endif } +void readf(FILE *f, ld& x) { + double fl = x; + if(fscanf(f, "%lf", &fl)) + ; + x = fl; + } + void loadConfig() { DEBB(DF_INIT, (debugfile,"load config\n")); @@ -5498,41 +7745,48 @@ void loadConfig() { 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; + vid.scale = a; vid.eye = b; vid.alpha = c; vid.sspeed = d; } - err=fscanf(f, "%d%d%d%d%d%d%d", &vid.wallmode, &vid.monmode, &vid.axes, &audiovolume, &vid.framelimit, &gl, &aa); + err=fscanf(f, "%d%d%d%d%d%d%d", &vid.wallmode, &vid.monmode, &vid.axes, &musicvolume, &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); + double jps = vid.joypanspeed; + err=fscanf(f, "%d%d%d%lf%d%d", &vid.joyvalue, &vid.joyvalue2, &vid.joypanthreshold, &jps, &aa, &vid.flashtime); + vid.joypanspeed = jps; autojoy = aa; aa = 0; - loadcs(f, vid.cs); + loadcs(f, vid.cs, 0); aa=0; bb=0; err=fscanf(f, "%d%d", &aa, &bb); vid.darkhepta = aa; vid.shifttarget = bb; - aa = euclid; bb = euclidland; cc = shmup::on; dd = hardcore; + aa = geometry; bb = euclidland; cc = shmup::on; dd = hardcore; err=fscanf(f, "%d%d%d%d", &aa, &bb, &cc, &dd); - euclid = aa; euclidland = eLand(bb); shmup::on = cc; hardcore = dd; + geometry = eGeometry(aa); euclidland = eLand(bb); shmup::on = cc; hardcore = dd; shmup::loadConfig(f); aa = rug::renderonce; bb = rug::rendernogl; cc = purehepta; dd = chaosmode; ee = vid.steamscore; - err=fscanf(f, "%d%d%d%d%lf%d%d", &aa, &bb, &rug::texturesize, &cc, &rug::scale, &ee, &dd); + double rs = rug::scale; + err=fscanf(f, "%d%d%d%d%lf%d%d", &aa, &bb, &rug::texturesize, &cc, &rs, &ee, &dd); rug::renderonce = aa; rug::rendernogl = bb; purehepta = cc; chaosmode = dd; vid.steamscore = ee; + rug::scale = rs; aa=conformal::includeHistory; + double ps = polygonal::STAR, lv = conformal::lvspeed; + int pmb = pmodel; err=fscanf(f, "%d%d%lf%d%d%lf", - &pmodel, &polygonal::SI, &polygonal::STAR, &polygonal::deg, - &aa, &conformal::lvspeed); - conformal::includeHistory = aa; + &pmb, &polygonal::SI, &ps, &polygonal::deg, + &aa, &lv); + pmodel = eModel(pmb); + conformal::includeHistory = aa; polygonal::STAR = ps; conformal::lvspeed = lv; aa=conformal::autoband; bb=conformal::autobandhistory; cc=conformal::dospiral; err=fscanf(f, "%d%d%d%d%d%d", &conformal::bandhalf, &conformal::bandsegment, &conformal::rotation, &aa, &bb, &cc); conformal::autoband = aa; conformal::autobandhistory = bb; conformal::dospiral = cc; - + err=fscanf(f, "%d", &polygonal::maxcoef); if(polygonal::maxcoef < 0) polygonal::maxcoef = 0; if(polygonal::maxcoef > MSI) polygonal::maxcoef = MSI; @@ -5541,90 +7795,47 @@ void loadConfig() { err=fscanf(f, "%lf%lf", &re, &im); polygonal::coef[i] = polygonal::cld(re, im); } + + aa=revcontrol; bb=vid.drawmousecircle; + d = vid.mspeed; + err=fscanf(f, "%d%d%d%f%d%d", &aa, &bb, &sightrange, &d, &effvolume, &vid.particles); + vid.mspeed = d; + if(sightrange < 4) sightrange = 4; + if(sightrange > 7) sightrange = 7; + revcontrol = aa; vid.drawmousecircle = bb; + + readf(f, geom3::depth); readf(f, geom3::camera); readf(f, geom3::wall_height); + readf(f, geom3::rock_wall_ratio); readf(f, geom3::human_wall_ratio); + readf(f, geom3::lake_top); readf(f, geom3::lake_bottom); + + err=fscanf(f, "%d %d %d", &geom3::tc_depth, &geom3::tc_camera, &geom3::tc_alpha); + + readf(f, geom3::highdetail); + geom3::middetail = 200; readf(f, geom3::middetail); + if(geom3::middetail == 200) { + if(ISMOBILE) + geom3::highdetail = 0, geom3::middetail = 3; + else + geom3::highdetail = geom3::middetail = 5; + } + + int gso = glyphsortorder; err=fscanf(f, "%d", &gso); glyphsortorder = eGlyphsortorder(gso); + + readf(f, vid.yshift); readf(f, vid.camera_angle); readf(f, vid.ballangle); readf(f, vid.ballproj); + + err=fscanf(f, "%d\n", &vid.mobilecompasssize); fclose(f); DEBB(DF_INIT, (debugfile,"Loaded configuration: %s\n", conffile)); + + if(err) + ; } -#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 - #ifndef MOBILE void initJoysticks() { DEBB(DF_INIT, (debugfile,"init joysticks\n")); @@ -5659,11 +7870,25 @@ void initgraph() { vid.flashtime = 8; vid.scale = 1; vid.alpha = 1; - vid.aspeed = 0; + vid.sspeed = 0; + vid.mspeed = 1; vid.eye = 0; vid.full = false; - vid.quick = true; + vid.ballangle = 20; + vid.yshift = 0; + vid.camera_angle = 0; + vid.ballproj = 1; + +#ifdef ANDROID + vid.monmode = 2; vid.wallmode = 3; +#else + vid.monmode = 4; + vid.wallmode = 5; +#endif + + vid.particles = 1; + vid.mobilecompasssize = 30; vid.joyvalue = 4800; vid.joyvalue2 = 5600; @@ -5675,9 +7900,9 @@ void initgraph() { #endif vid.framelimit = 75; - vid.monmode = 2; vid.axes = 1; - vid.steamscore = true; + vid.shifttarget = 2; + vid.steamscore = 1; initcs(vid.cs); @@ -5688,6 +7913,15 @@ void initgraph() { joyx = joyy = 0; joydir.d = -1; + vid.drawmousecircle = false; + revcontrol = false; +#ifdef MOBILE + vid.drawmousecircle = true; +#endif +#ifdef PANDORA + vid.drawmousecircle = true; +#endif + shmup::initConfig(); restartGraph(); @@ -5702,14 +7936,29 @@ void initgraph() { printf("Failed to initialize video.\n"); exit(2); } + +#ifdef WEB + vid.xscr = vid.xres = 1200; + vid.yscr = vid.yres = 900; +#else const SDL_VideoInfo *inf = SDL_GetVideoInfo(); vid.xscr = vid.xres = inf->current_w; vid.yscr = vid.yres = inf->current_h; +#endif SDL_WM_SetCaption("HyperRogue " VER, "HyperRogue " VER); + #endif + preparesort(); +#ifndef NOCONFIG loadConfig(); +#endif +#ifdef USE_COMMANDLINE + arg::read(2); +#endif + + #ifndef MOBILE setvideomode(); if(!s) { printf("Failed to initialize graphics.\n"); @@ -5726,32 +7975,8 @@ void initgraph() { initJoysticks(); - #ifdef AUDIO - - audio = - loadMusicInfo(musicfile) - || loadMusicInfo("./hyperrogue-music.txt") - || loadMusicInfo("music/hyperrogue-music.txt") -// Destination set by ./configure (in the GitHub repository) -#ifdef MUSICDESTDIR - || loadMusicInfo(MUSICDESTDIR) -#endif -#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); - } - } + #ifdef SDLAUDIO + initAudio(); #endif #endif @@ -5760,70 +7985,6 @@ void initgraph() { 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() { - DEBB(DF_GRAPH, (debugfile,"handle music\n")); - if(audio && audiovolume) { - eLand id = cwt.c->land; - if(isHaunted(id)) id = laHaunted; - if(id == laGridSea) id = laGridCoast; -#ifdef LOCAL - extern bool local_changemusic(eLand& id); - if(local_changemusic(id)) return; -#endif - 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; imaster; - else centerover = cwt.c; - // SDL_LockSurface(s); - // SDL_UnlockSurface(s); +bool needConfirmation() { + return canmove && (gold() >= 30 || tkills() >= 50) && !cheater && !quitsaves(); } void fullcenter() { if(playerfound && false) centerpc(INF); else { + bfs(); resetview(); - drawthemap(); + drawthemap(); centerpc(INF); } playermoved = true; } +bool didsomething; + +bool quitmainloop = false; + +void handleKeyQuit(int sym, int uni) { + dialog::handleNavigation(sym, uni); + // ignore the camera movement keys + +#ifndef NORUG + if(rug::rugged && (sym == SDLK_UP || sym == SDLK_DOWN || sym == SDLK_PAGEUP || sym == SDLK_PAGEDOWN || + sym == SDLK_RIGHT || sym == SDLK_LEFT)) + sym = 0; +#endif + + if(sym == SDLK_RETURN || sym == SDLK_F10) quitmainloop = true; + 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 == 'v') cmode = emMenu; + else if(sym == SDLK_HOME || sym == SDLK_F3 || (sym == ' ' && DEFAULTCONTROL)) + fullcenter(); + else if(uni == 'o') setAppropriateOverview(); +#ifndef NOSAVE + else if(uni == 't') { + if(!canmove) restartGame(); + loadScores(); + msgs.clear(); + } + #endif + + else if((sym != 0 && sym != SDLK_F12) && !didsomething) { + cmode = emNormal; + msgscroll = 0; + msgs.clear(); + } + } + +#ifdef MOBILE +#define extra int +#else +#define extra SDL_Event +#endif + +void handleKeyNormal(int sym, int uni, extra& ev) { + + if(cheater) { + if(applyCheat(uni, mouseover)) + sym = 0; + } + + if(!(uni >= 'A' && uni <= 'Z') && DEFAULTCONTROL) { + 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(DEFAULTCONTROL) { + 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(DEFAULTCONTROL) { + if(sym == '.' || sym == 's') movepcto(-1, 1); + if(uni == '%' && sym == '5') { + if(vid.wallmode == 0) vid.wallmode = 6; + vid.wallmode--; + } + if(uni == sym) { + if(uni == '1') { + vid.alpha = 999; vid.scale = 998; + } + if(uni == '2') { + vid.alpha = 1; vid.scale = 0.4; + } + if(uni == '3') { + vid.alpha = 1; vid.scale = 1; + } + if(uni == '4') { + vid.alpha = 0; vid.scale = 1; + } + if(uni == '5') { + vid.wallmode++; + if(vid.wallmode == 6) vid.wallmode = 0; + } + if(uni == '6') { + vid.grid = !vid.grid; + } + if(uni == '7') { + vid.darkhepta = !vid.darkhepta; + } + if(uni == '8') { + backcolor = backcolor ^ 0xFFFFFF; + printf("back = %x\n", backcolor); + } + if(uni == '9') { + pmodel = eModel(8 - pmodel); + // vid.yshift = 1 - vid.yshift; + // vid.drawmousecircle = true; + } + } + if((sym == SDLK_DELETE || sym == SDLK_KP_PERIOD || sym == 'g') && uni != 'G' && uni != 'G'-64) + movepcto(MD_DROP, 1); + if(sym == 'm' && canmove && cmode == emNormal && (centerover == cwt.c ? mouseover : centerover)) + performMarkCommand(mouseover); + if(sym == 't' && uni != 'T' && uni != 'T'-64 && canmove && cmode == emNormal) { + if(playermoved && items[itStrongWind]) { + cell *c = whirlwind::jumpDestination(cwt.c); + if(c) centerover = c; + } + targetRangedOrb(centerover, roKeyboard); + sym = 0; uni = 0; + } + } + + if(sym == SDLK_KP5 && DEFAULTCONTROL) movepcto(-1, 1); + + // if(sym == SDLK_F4) restartGameSwitchEuclid(); + + if(sym == SDLK_F5) { + if(needConfirmation()) cmode = emQuit; + else restartGame(); + } + + if(sym == SDLK_ESCAPE) { + cmode = emQuit; + achievement_final(false); + if(!canmove) { + addMessage(XLAT("GAME OVER")); + addMessage(timeline()); + } + msgscroll = 0; + } + if(sym == SDLK_F10) { + if(needConfirmation()) cmode = emQuit; + else quitmainloop = true; + } + + if(!canmove) { + if(sym == SDLK_RETURN) quitmainloop = true; + else if(uni == 'r') restartGame(); +#ifndef NOSAVE + else if(uni == 't') { + restartGame(); + loadScores(); + } +#endif +#ifndef NORUG + else if(rug::rugged) ; +#endif + 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; + } + + if(uni == 'o') setAppropriateOverview(); + + if(sym == SDLK_HOME || sym == SDLK_F3 || (sym == ' ' && DEFAULTCONTROL)) + fullcenter(); + +/* if(sym == SDLK_F6) { + View = spin(M_PI/2) * inverse(cwtV) * View; + if(flipplayer) View = pispin * View; + cmode = emDraw; + } */ + + if(sym == 'v') { + cmode = emMenu; + } + + if(sym == SDLK_F2) { + cmode = emVisual1; + } + +#ifndef MOBILE +#ifdef PANDORA + if(ev.type == SDL_MOUSEBUTTONUP && sym == 0 && !rightclick) +#else + if(ev.type == SDL_MOUSEBUTTONDOWN && sym == 0 && !rightclick) +#endif + if(canmove && getcstat != 'v' && getcstat != 'g' && getcstat != SDLK_F1) + { + actonrelease = false; + + shmup::cpid = 0; + if(mouseover && + targetclick && (!shmup::on || numplayers() == 1) && targetRangedOrb(mouseover, forcetarget ? roMouseForce : roMouse)) { + } + else if(forcetarget) + ; + else if(!DEFAULTCONTROL) { + if(!shmup::on) + multi::mousemovement(mouseover); + } + else mousemovement(); + } +#endif + + if(sym == SDLK_F1) { + lastmode = cmode; + cmode = emHelp; + } + +#ifdef ROGUEVIZ + rogueviz::processKey(sym, uni); +#endif + +#ifdef LOCAL + process_local0(sym); +#endif + } + +void handlekey(int sym, int uni, extra& ev) { + + if(((cmode == emNormal && canmove) || (cmode == emQuit && !canmove) || cmode == emDraw || cmode == emMapEditor) && DEFAULTCONTROL && !rug::rugged) { +#ifndef PANDORA + if(sym == SDLK_RIGHT) + View = xpush(-0.2*shiftmul) * View, playermoved = false, didsomething = true; + if(sym == SDLK_LEFT) + View = xpush(+0.2*shiftmul) * View, playermoved = false, didsomething = true; + if(sym == SDLK_UP) + View = ypush(+0.2*shiftmul) * View, playermoved = false, didsomething = true; + if(sym == SDLK_DOWN) + View = ypush(-0.2*shiftmul) * View, playermoved = false, didsomething = true; +#endif + if(sym == SDLK_PAGEUP) { + View = spin(M_PI/S21*shiftmul) * View, didsomething = true; + } + if(sym == SDLK_PAGEDOWN) + View = spin(-M_PI/S21*shiftmul) * View, didsomething = true; + + if(sym == SDLK_PAGEUP || sym == SDLK_PAGEDOWN) + if(isGravityLand(cwt.c->land)) playermoved = false; + } + +#ifndef MOBILE + if(sym == SDLK_F7 && !vid.usingGL) { + + time_t timer; + timer = time(NULL); + char buf[128]; strftime(buf, 128, "shot-%y%m%d-%H%M%S" IMAGEEXT, localtime(&timer)); + + IMAGESAVE(s, buf); + addMessage(XLAT("Screenshot saved to %1", buf)); + } +#endif + + #ifdef DEMO + if(cmode == emOverview || cmode == emMenu) handleDemoKey(sym, uni); else + #endif + + if(cmode == emNormal) handleKeyNormal(sym, uni, ev); + else if(cmode == emMenu) handleMenuKey(sym, uni); + else if(cmode == emCheatMenu) handleCheatMenu(sym, uni); + else if(cmode == emVisual1) handleVisual1(sym, uni); + else if(cmode == emJoyConfig) handleJoystickConfig(sym, uni); + else if(cmode == emCustomizeChar) handleCustomizeChar(sym, uni); + else if(cmode == emVisual2) handleVisual2(sym, uni); + else if(cmode == emChangeMode) handleChangeMode(sym, uni); + else if(cmode == emShmupConfig) shmup::handleConfig(sym, uni); +#ifndef NOMODEL + else if(cmode == emNetgen) netgen::handleKey(sym, uni); +#endif +#ifndef NORUG + else if(cmode == emRugConfig) rug::handleKey(sym, uni); +#endif +#ifndef NOEDIT + else if(cmode == emMapEditor) mapeditor::handleKey(sym, uni); + else if(cmode == emDraw) mapeditor::drawHandleKey(sym, uni); +#endif +#ifndef NOSAVE +#ifndef MOBILE + else if(cmode == emScores) handleScoreKeys(sym); +#endif + else if(cmode == emPickScores) handlePickScoreKeys(uni); +#endif + else if(cmode == emConformal) conformal::handleKey(sym, uni); + else if(cmode == emYendor) yendor::handleKey(sym, uni); + else if(cmode == emTactic) tactic::handleKey(sym, uni); + else if(cmode == emOverview) handleOverview(sym, uni); + else if(cmode == emPickEuclidean) handleEuclidean(sym, uni); +#ifdef MOBILE + else if(cmode == emLeader) leader::handleKey(sym, uni); +#endif + else if(cmode == emColor) dialog::handleColor(sym, uni); + else if(cmode == emNumber) dialog::handleNumber(sym, uni); + else if(cmode == emHelp) handleHelp(sym, uni); + else if(cmode == em3D) handle3D(sym, uni); + else if(cmode == emQuit) handleKeyQuit(sym, uni); +#ifdef ROGUEVIZ + else if(cmode == emRogueviz) rogueviz::handleMenu(sym, uni); +#endif + } + #ifndef MOBILE // Warning: a very long function! todo: refactor -void mainloop() { - int lastt = 0; - cmode = emNormal; - while(true) { - DEBB(DF_GRAPH, (debugfile,"main loop\n")); +void mainloopiter() { - #ifndef GFX - #ifndef GL - vid.wallmode = 0; - vid.monmode = 0; - #endif - #endif + DEBB(DF_GRAPH, (debugfile,"main loop\n")); + + #ifndef GFX + #ifndef GL + vid.wallmode = 0; + vid.monmode = 0; + #endif + #endif #ifdef LOCAL - process_local_extra(); + process_local_extra(); #endif - - optimizeview(); + + optimizeview(); - if(conformal::on) conformal::apply(); + if(conformal::on) conformal::apply(); + + ticks = SDL_GetTicks(); - ticks = SDL_GetTicks(); - - int cframelimit = vid.framelimit; - if((cmode == emVisual1 || cmode == emVisual2 || cmode == emHelp || cmode == emQuit || - cmode == emCustomizeChar || cmode == emMenu || cmode == emPickEuclidean || - cmode == emScores || cmode == emPickScores) && cframelimit > 15) - cframelimit = 15; - if(outoffocus && cframelimit > 10) cframelimit = 10; + int cframelimit = vid.framelimit; + if((cmode == emVisual1 || cmode == emVisual2 || cmode == emHelp || cmode == emQuit || + cmode == emCustomizeChar || cmode == emMenu || cmode == emPickEuclidean || + cmode == emScores || cmode == emPickScores) && cframelimit > 15) + cframelimit = 15; + if(outoffocus && cframelimit > 10) cframelimit = 10; + + int timetowait = lastt + 1000 / cframelimit - ticks; + + if(DOSHMUP && cmode == emNormal) + timetowait = 0, shmup::turn(ticks - lastt); - int timetowait = lastt + 1000 / cframelimit - ticks; + if(!DOSHMUP && (multi::alwaysuse || multi::players > 1) && cmode == emNormal) + timetowait = 0, multi::handleMulti(ticks - lastt); - if(shmup::on && cmode == emNormal) - timetowait = 0, shmup::turn(ticks - lastt); + if(vid.sspeed >= 5 && gmatrix.count(cwt.c) && !elliptic) { + cwtV = gmatrix[cwt.c] * ddspin(cwt.c, cwt.spin); + if(cwt.mirrored) playerV = playerV * Mirror; + } - if(timetowait > 0) - SDL_Delay(timetowait); - else { - if(playermoved && vid.aspeed > -4.99 && !outoffocus) - centerpc((ticks - lastt) / 1000.0 * exp(vid.aspeed)); +#ifdef WEB + if(playermoved && vid.sspeed > -4.99 && !outoffocus) { + centerpc((ticks - lastt) / 1000.0 * exp(vid.sspeed)); + } + if(!outoffocus) drawscreen(); +#else + if(timetowait > 0) + SDL_Delay(timetowait); + else { + if(cmode != emOverview) { + if(playermoved && vid.sspeed > -4.99 && !outoffocus) + centerpc((ticks - lastt) / 1000.0 * exp(vid.sspeed)); if(panjoyx || panjoyy) checkpanjoy((ticks - lastt) / 1000.0); - tortoise::updateVals(ticks - lastt); - lastt = ticks; - frames++; - if(!outoffocus) { - drawscreen(); - } - } + } + tortoise::updateVals(ticks - lastt); + frames++; + if(!outoffocus) { + drawscreen(); + } + lastt = ticks; + } +#endif - Uint8 *keystate = SDL_GetKeyState(NULL); - rightclick = keystate[SDLK_RCTRL]; + Uint8 *keystate = SDL_GetKeyState(NULL); + rightclick = keystate[SDLK_RCTRL]; + leftclick = keystate[SDLK_RSHIFT]; + lctrlclick = keystate[SDLK_LCTRL]; + lshiftclick = keystate[SDLK_LSHIFT]; + forcetarget = (keystate[SDLK_RSHIFT] | keystate[SDLK_LSHIFT]); + hiliteclick = keystate[SDLK_LALT] | keystate[SDLK_RALT]; + anyshiftclick = keystate[SDLK_LSHIFT] | keystate[SDLK_RSHIFT]; + wheelclick = false; + + getcshift = 1; + if(keystate[SDLK_LSHIFT] || keystate[SDLK_RSHIFT]) getcshift = -1; + if(keystate[SDLK_LCTRL] || keystate[SDLK_RCTRL]) getcshift /= 10; + if(keystate[SDLK_LALT] || keystate[SDLK_RALT]) getcshift *= 10; + + didsomething = false; + + if(vid.shifttarget&1) { + leftclick = false; + targetclick = keystate[SDLK_RSHIFT] | keystate[SDLK_LSHIFT]; + } + else { leftclick = keystate[SDLK_RSHIFT]; - hiliteclick = keystate[SDLK_LALT] | keystate[SDLK_RALT]; - anyshiftclick = keystate[SDLK_LSHIFT] | keystate[SDLK_RSHIFT]; - - bool didsomething = false; - - if(vid.shifttarget) { - leftclick = false; - targetclick = keystate[SDLK_RSHIFT] | keystate[SDLK_LSHIFT]; - } - else { - leftclick = keystate[SDLK_RSHIFT]; - targetclick = true; - } - -#ifdef AUDIO - if(audio) handlemusic(); + targetclick = true; + } + +#ifdef SDLAUDIO + if(audio) handlemusic(); #endif - SDL_Event ev; - DEBB(DF_GRAPH, (debugfile,"polling for events\n")); + SDL_Event ev; + DEBB(DF_GRAPH, (debugfile,"polling for events\n")); + + achievement_pump(); + while(SDL_PollEvent(&ev)) { + DEBB(DF_GRAPH, (debugfile,"got event type #%d\n", ev.type)); + int sym = 0; + int uni = 0; + shiftmul = 1; - achievement_pump(); - while(SDL_PollEvent(&ev)) { - DEBB(DF_GRAPH, (debugfile,"got event type #%d\n", ev.type)); - int sym = 0; - int uni = 0; - shiftmul = 1; - /* if(ev.type == SDL_JOYDEVICEADDED || ev.type == SDL_JOYDEVICEREMOVED) { - joyx = joyy = 0; - panjoyx = panjoyy = 0; - closeJoysticks(); - initJoysticks(); - }*/ + joyx = joyy = 0; + panjoyx = panjoyy = 0; + closeJoysticks(); + initJoysticks(); + }*/ - 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; - extern bool setfsize; - setfsize = true; - setvideomode(); -#ifdef GL - if(vid.usingGL) glViewport(0, 0, vid.xres, vid.yres); -#endif - } - - if(ev.type == SDL_VIDEOEXPOSE) { - drawscreen(); - } - - if(ev.type == SDL_JOYAXISMOTION) { - flashMessages(); - if(ev.jaxis.value != 0) - 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); + if(ev.type == SDL_ACTIVEEVENT) { + if(ev.active.state & SDL_APPINPUTFOCUS) { + if(ev.active.gain) { + outoffocus = false; } else { - if(ev.jaxis.axis == 0) - panjoyx = ev.jaxis.value; - else - panjoyy = ev.jaxis.value; + outoffocus = true; } } + } + + if(ev.type == SDL_VIDEORESIZE) { + vid.xres = ev.resize.w; + vid.yres = ev.resize.h; + vid.killreduction = 0; + extern bool setfsize; + setfsize = true; + setvideomode(); +#ifdef GL + if(vid.usingGL) glViewport(0, 0, vid.xres, vid.yres); +#endif + } + + if(ev.type == SDL_VIDEOEXPOSE) { + drawscreen(); + } + + if(ev.type == SDL_JOYAXISMOTION) { + 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 && cmode == emShmupConfig && vid.scfg.setwhat) { - int joyid = ev.jbutton.which; - int button = ev.jbutton.button; - if(joyid < 8 && button < 32) - vid.scfg.joyaction[joyid][button] = vid.scfg.setwhat; + if(ev.type == SDL_JOYBUTTONDOWN && cmode == emShmupConfig && vid.scfg.setwhat) { + int joyid = ev.jbutton.which; + int button = ev.jbutton.button; + if(joyid < 8 && button < 32) + vid.scfg.joyaction[joyid][button] = vid.scfg.setwhat; + vid.scfg.setwhat = 0; + } + + else if(ev.type == SDL_JOYHATMOTION && cmode == emShmupConfig && vid.scfg.setwhat) { + int joyid = ev.jhat.which; + int hat = ev.jhat.hat; + int dir = 4; + if(ev.jhat.value == SDL_HAT_UP) dir = 0; + if(ev.jhat.value == SDL_HAT_RIGHT) dir = 1; + if(ev.jhat.value == SDL_HAT_DOWN) dir = 2; + if(ev.jhat.value == SDL_HAT_LEFT) dir = 3; + if(joyid < 8 && hat < 4 && dir < 4) { + vid.scfg.hataction[joyid][hat][dir] = vid.scfg.setwhat; vid.scfg.setwhat = 0; } + } - else if(ev.type == SDL_JOYHATMOTION && cmode == emShmupConfig && vid.scfg.setwhat) { - int joyid = ev.jhat.which; - int hat = ev.jhat.hat; - int dir = 4; - if(ev.jhat.value == SDL_HAT_UP) dir = 0; - if(ev.jhat.value == SDL_HAT_RIGHT) dir = 1; - if(ev.jhat.value == SDL_HAT_DOWN) dir = 2; - if(ev.jhat.value == SDL_HAT_LEFT) dir = 3; - if(joyid < 8 && hat < 4 && dir < 4) { - vid.scfg.hataction[joyid][hat][dir] = vid.scfg.setwhat; - vid.scfg.setwhat = 0; - } + else if(ev.type == SDL_JOYBUTTONDOWN && DEFAULTCONTROL) { + flashMessages(); + 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)) shiftmul = -1; + if(ev.key.keysym.mod & (KMOD_LCTRL | KMOD_RCTRL)) shiftmul /= 10; + } + + dialog::handleZooming(ev); + + if(sym == SDLK_F1 && cmode == emNormal && playermoved) + help = "@"; + + bool rollchange = + cmode == emOverview && getcstat >= 2000 && cheater; + + if(ev.type == SDL_MOUSEBUTTONDOWN) { + flashMessages(); + mousepressed = true; + mousing = true; + actonrelease = true; + if(ev.button.button==SDL_BUTTON_WHEELUP && ((cmode == emQuit) ^ !canmove)) { } - - else if(ev.type == SDL_JOYBUTTONDOWN && !shmup::on) { - flashMessages(); - 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)) shiftmul = -1; - if(ev.key.keysym.mod & (KMOD_LCTRL | KMOD_RCTRL)) shiftmul /= 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, didsomething = true; - else if(ev.button.button==SDL_BUTTON_WHEELUP && ((cmode == emQuit) ^ !canmove)) { - sym = 1; msgscroll++; didsomething = true; - } - else if(ev.button.button==SDL_BUTTON_WHEELDOWN && ((cmode == emQuit) ^ !canmove)) { + else if(ev.button.button==SDL_BUTTON_WHEELDOWN) { + if(cmode == (canmove ? emQuit : emNormal)) { sym = 1; msgscroll--; didsomething = true; } - else if(ev.button.button==SDL_BUTTON_WHEELUP) { + else + if(cmode == emTactic || cmode == emYendor || cmode == emPickEuclidean || + cmode == emLeader || cmode == emScores || cmode == emOverview) + if(!rollchange) + sym = uni = PSEUDOKEY_WHEELDOWN; + } + if(ev.button.button==SDL_BUTTON_WHEELUP) { + if(cmode == (canmove ? emQuit : emNormal)) { + sym = 1; msgscroll++; didsomething = true; + } + else if(cmode == (canmove ? emNormal : emQuit) || cmode == emMapEditor || cmode == emDraw) { 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, shiftmul = getcshift; - } + else + if(cmode == emTactic || cmode == emYendor || cmode == emPickEuclidean || + cmode == emLeader || cmode == emScores || cmode == emOverview) + if(getcstat < 2000 || !cheater) + sym = uni = PSEUDOKEY_WHEELUP; } - - if(ev.type == SDL_MOUSEBUTTONUP) - mousepressed = false; - - if(((cmode == emNormal && canmove) || (cmode == emQuit && !canmove) || cmode == emDraw || cmode == emMapEditor) && !shmup::on && !rug::rugged) { -#ifndef PANDORA - if(sym == SDLK_RIGHT) - View = xpush(-0.2*shiftmul) * View, playermoved = false, didsomething = true; - if(sym == SDLK_LEFT) - View = xpush(+0.2*shiftmul) * View, playermoved = false, didsomething = true; - if(sym == SDLK_UP) - View = ypush(+0.2*shiftmul) * View, playermoved = false, didsomething = true; - if(sym == SDLK_DOWN) - View = ypush(-0.2*shiftmul) * View, playermoved = false, didsomething = true; -#endif - if(sym == SDLK_PAGEUP) { - View = spin(M_PI/21*shiftmul) * View, didsomething = true; - } - if(sym == SDLK_PAGEDOWN) - View = spin(-M_PI/21*shiftmul) * View, didsomething = true; - - if(sym == SDLK_PAGEUP || sym == SDLK_PAGEDOWN) - if(isGravityLand(cwt.c->land)) playermoved = false; + else if(ev.button.button == SDL_BUTTON_RIGHT) { + sym = 1; didsomething = true; } + else if(ev.button.button == SDL_BUTTON_MIDDLE) { + sym = 2; didsomething = true; + } + } + + if(ev.type == SDL_MOUSEBUTTONUP) { + mousepressed = false; + mousing = true; + if(ev.button.button==SDL_BUTTON_RIGHT || leftclick) + sym = SDLK_F1; + else if(ev.button.button==SDL_BUTTON_MIDDLE || rightclick) + sym = 1, didsomething = true; + else if(ev.button.button == SDL_BUTTON_LEFT && actonrelease) { + sym = getcstat, uni = getcstat, shiftmul = getcshift; + } + else if(ev.button.button == SDL_BUTTON_WHEELUP && rollchange) { + sym = getcstat, uni = getcstat, shiftmul = getcshift, wheelclick = true; + } + else if(ev.button.button == SDL_BUTTON_WHEELDOWN && rollchange) { + sym = getcstat, uni = getcstat, shiftmul = -getcshift, wheelclick = true; + } + } + + if(ev.type == SDL_MOUSEMOTION) { + hyperpoint mouseoh = mouseh; - if(ev.type == SDL_MOUSEMOTION) { - hyperpoint mouseoh = mouseh; - - mousing = true; - mousex = ev.motion.x; - mousey = ev.motion.y; - if(rug::rugged) - mouseh = rug::gethyper(mousex, mousey); - else - 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); - } + mousing = true; + mousemoved = true; + mousex = ev.motion.x; + mousey = ev.motion.y; + +#ifndef NORUG + if(rug::rugged) + mouseh = rug::gethyper(mousex, mousey); + else +#endif + 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 - } - - if(sym == SDLK_F7 && !vid.usingGL) { - - time_t timer; - timer = time(NULL); - char buf[128]; strftime(buf, 128, "shot-%y%m%d-%H%M%S" IMAGEEXT, localtime(&timer)); - - IMAGESAVE(s, buf); - addMessage(XLAT("Screenshot saved to %1", buf)); - } - - if(cmode == emNormal) { - - if(cheater) { - if(applyCheat(uni, mouseover)) - sym = 0; - } - - if(!(uni >= 'A' && uni <= 'Z') && !shmup::on) { - 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(!shmup::on) { - 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)); - } + // pretend that both joysticks are present + stick = panstick = (SDL_Joystick*) (&vid); + panjoyx = 20 * (mousex - vid.xcenter); + panjoyy = 20 * (mousey - vid.ycenter); + checkjoy(); #endif - if(!shmup::on) { - if(sym == '.' || sym == 's') movepcto(-1, 1); - if((sym == SDLK_DELETE || sym == SDLK_KP_PERIOD || sym == 'g') && uni != 'G' && uni != 'G'-64) - movepcto(-2, 1); - if(sym == 't' && uni != 'T' && uni != 'T'-64 && canmove && cmode == emNormal) { - if(playermoved && items[itStrongWind]) { - cell *c = whirlwind::jumpDestination(cwt.c); - if(c) centerover = c; - } - targetRangedOrb(centerover, roKeyboard); - sym = 0; uni = 0; - } - } - - if(sym == SDLK_KP5 && !shmup::on) movepcto(-1, 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(); - } - else if(rug::rugged) ; - 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; - } - - if(uni == 'o') setAppropriateOverview(); - - if(sym == SDLK_HOME || sym == SDLK_F3 || (sym == ' ' && !shmup::on)) - fullcenter(); - -/* if(sym == SDLK_F6) { - View = spin(M_PI/2) * inverse(cwtV) * View; - if(flipplayer) View = spin(M_PI) * View; - cmode = emDraw; - } */ - - if(sym == 'v') { - cmode = emMenu; - } - - if(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]); - - shmup::cpid = 0; - if(mouseover && targetclick && (numplayers() == 1) && targetRangedOrb(mouseover, forcetarget ? roMouseForce : roMouse)) - ; - else if(forcetarget) - ; - else if(shmup::on) - ; - else mousemovement(); - } - - if(sym == SDLK_F1) { - lastmode = cmode; - cmode = emHelp; - } - -#ifdef LOCAL - process_local0(sym); -#endif + if(mousepressed && inslider) { + sym = getcstat, uni = getcstat, shiftmul = getcshift; } - - else if(cmode == emMenu) { if(handleMenuKey(sym, ev.type == SDL_MOUSEBUTTONDOWN)) return; } - else if(cmode == emCheatMenu) handleCheatMenu(uni); - else if(cmode == emVisual1) handleVisual1(sym, uni); - else if(cmode == emCustomizeChar) handleCustomizeChar(sym, uni, ev.key.keysym.mod); - else if(cmode == emVisual2) handleVisual2(sym, uni); - else if(cmode == emChangeMode) handleChangeMode(sym, uni); - else if(cmode == emShmupConfig) shmup::handleConfig(uni, sym); - else if(cmode == emNetgen) netgen::handleKey(uni, sym); - else if(cmode == emRugConfig) rug::handleKey(uni, sym); - else if(cmode == emConformal) conformal::handleKey(uni, sym); - else if(cmode == emYendor) yendor::handleKey(uni, sym); - else if(cmode == emTactic) tactic::handleKey(uni, sym); - else if(cmode == emMapEditor) mapeditor::handleKey(uni, sym); - else if(cmode == emOverview) handleOverview(uni); - else if(cmode == emDraw) mapeditor::drawHandleKey(uni, sym, shiftmul); - else if(cmode == emPickEuclidean) handleEuclidean(sym, uni); - else if(cmode == emScores) handleScoreKeys(sym, ev); - else if(cmode == emPickScores) handlePickScoreKeys(uni, ev); - - 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 = lastmode; - } - - else if(cmode == emQuit) { - // ignore the camera movement keys - if(rug::rugged && (sym == SDLK_UP || sym == SDLK_DOWN || sym == SDLK_PAGEUP || sym == SDLK_PAGEDOWN || - sym == SDLK_RIGHT || sym == SDLK_LEFT)) - sym = 0; - - 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 == 'v') cmode = emMenu; - else if(sym == SDLK_HOME || sym == SDLK_F3 || (sym == ' ' && !shmup::on)) - fullcenter(); - else if(uni == 'o') setAppropriateOverview(); - else if(uni == 't') { - if(!canmove) restartGame(); - loadScores(); - msgs.clear(); - } - - else if(((sym != 0 && sym != SDLK_F12) || ev.type == SDL_MOUSEBUTTONDOWN) && !didsomething) { - cmode = emNormal; - msgscroll = 0; - msgs.clear(); - } - } - - if(ev.type == SDL_QUIT) - return; - } + + handlekey(sym, uni, ev); - if(playerdead) break; + if(ev.type == SDL_QUIT) { + if(needConfirmation() && cmode !=emQuit) cmode = emQuit; + else quitmainloop = true; + } + } - + } + +void mainloop() { + lastt = 0; + cmode = emNormal; +#ifdef WEB + initweb(); + emscripten_set_main_loop(mainloopiter, 0, true); +#else + while(!quitmainloop) mainloopiter(); +#endif } #endif @@ -6327,7 +8658,7 @@ void cleargraph() { void cleargraphmemory() { DEBB(DF_INIT, (debugfile,"clear graph memory\n")); mouseover = centerover = lmouseover = NULL; -#ifndef MOBILE +#ifndef NOEDIT if(mapeditor::painttype == 4) mapeditor::painttype = 0, mapeditor::paintwhat = 0, mapeditor::paintwhat_str = "clear monster"; @@ -6336,9 +8667,78 @@ void cleargraphmemory() { if(!cheater) mapeditor::displaycodes = 0; if(!cheater) mapeditor::whichShape = 0; #endif + for(int i=0; i animations[ANIMLAYERS]; +unordered_map gmatrix, gmatrix0; + +void animateMovement(cell *src, cell *tgt, int layer) { + if(vid.mspeed >= 5) return; // no animations! + if(confusingGeometry()) return; + if(gmatrix.count(src) && gmatrix.count(tgt)) { + animation& a = animations[layer][tgt]; + if(animations[layer].count(src)) { + a = animations[layer][src]; + a.wherenow = inverse(gmatrix[tgt]) * gmatrix[src] * a.wherenow; + animations[layer].erase(src); + } + else { + a.ltick = ticks; + a.wherenow = inverse(gmatrix[tgt]) * gmatrix[src]; + a.footphase = 0; + } + } + } + +vector > animstack; + +void indAnimateMovement(cell *src, cell *tgt, int layer) { + if(vid.mspeed >= 5) return; // no animations! + if(confusingGeometry()) return; + if(animations[layer].count(tgt)) { + animation res = animations[layer][tgt]; + animations[layer].erase(tgt); + animateMovement(src, tgt, layer); + if(animations[layer].count(tgt)) + animstack.push_back(make_pair(tgt, animations[layer][tgt])); + animations[layer][tgt] = res; + } + else { + animateMovement(src, tgt, layer); + if(animations[layer].count(tgt)) { + animstack.push_back(make_pair(tgt, animations[layer][tgt])); + animations[layer].erase(tgt); + } + } + } + +void commitAnimations(int layer) { + for(int i=0; i= 5) return; // no animations! + static cell c1; + gmatrix[&c1] = gmatrix[b]; + if(animations[layer].count(b)) animations[layer][&c1] = animations[layer][b]; + animateMovement(a, b, layer); + animateMovement(&c1, a, layer); + } diff --git a/heptagon.cpp b/heptagon.cpp index 9424491c..b6b53fb5 100644 --- a/heptagon.cpp +++ b/heptagon.cpp @@ -4,24 +4,42 @@ // 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 }; +#define MIRR(x) x.mirrored -int fixrot(int a) { return (a+98)% 7; } -int fix42(int a) { return (a+420)% 42; } +// automaton state +enum hstate { hsOrigin, hsA, hsB, hsError, hsA0, hsA1, hsB0, hsB1, hsC }; + +int fixrot(int a) { return (a+490)% S7; } +int fix42(int a) { return (a+420)% S42; } struct heptagon; struct cell; cell *newCell(int type, heptagon *master); -#define CDATA +// spintable functions + +int tspin(uint32_t& t, int d) { + return (t >> (d<<2)) & 7; + } + +int tmirror(uint32_t& t, int d) { + return (t >> ((d<<2)+3)) & 1; + } + +void tsetspin(uint32_t& t, int d, int spin) { + t &= ~(15 << (d<<2)); + t |= spin << (d<<2); + } struct heptagon { // automaton state hstate s : 8; // we are spin[i]-th neighbor of move[i] - unsigned char spin[7]; + uint32_t spintable; + int spin(int d) { return tspin(spintable, d); } + int mirror(int d) { return tmirror(spintable, d); } + void setspin(int d, int sp) { tsetspin(spintable, d, sp); } // neighbors; move[0] always goes towards origin, // and then we go clockwise heptagon* move[7]; @@ -33,18 +51,18 @@ struct heptagon { short fiftyval; // zebra generator (1B actually) short zebraval; -#ifdef CDATA + // field id + int fieldval; // evolution data short rval0, rval1; struct cdata *cdata; -#endif // central cell cell *c7; // associated generator of alternate structure, for Camelot and horocycles heptagon *alt; // functions heptagon*& modmove(int i) { return move[fixrot(i)]; } - unsigned char& gspin(int i) { return spin[fixrot(i)]; } + unsigned char gspin(int i) { return spin(fixrot(i)); } }; // the automaton is used to generate each heptagon in an unique way @@ -54,15 +72,32 @@ struct heptagon { // 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; + if(sphere) { + if(S7 == 4) { + if(s == hsOrigin) return dir == 0 ? hsB0 : hsB1; + } + if(S7 == 3) { + if(s == hsOrigin) return hsB1; + } + if(s == hsOrigin) return dir == 0 ? hsA0 : hsA1; + if(s == hsA0 && dir == 2) return hsB0; + if(s == hsA1 && dir == 2) return hsB1; + if(s == hsB0 && dir == S7-2) return hsC; + return hsError; + } + else { + 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; +heptagon dodecahedron[12]; +#define origin (dodecahedron[0]) + vector allAlts; // create h->move[d] if not created yet @@ -74,16 +109,16 @@ heptagon *buildHeptagon(heptagon *parent, int d, hstate s, int pard = 0) { 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; + h->spintable = 0; + h->move[pard] = parent; tsetspin(h->spintable, pard, d); + parent->move[d] = h; tsetspin(parent->spintable, d, pard); if(parent->c7) { h->c7 = newCell(7, h); h->emeraldval = emerald_heptagon(parent->emeraldval, d); h->zebraval = zebra_heptagon(parent->zebraval, d); -#ifdef CDATA + h->fieldval = fp43.connections[fieldpattern::btspin(parent->fieldval, d)]; h->rval0 = h->rval1 = 0; h->cdata = NULL; -#endif - if(parent == &origin) + if(parent == &origin || parent == origin.alt) h->fiftyval = fiftytable[0][d]; else h->fiftyval = nextfiftyval(parent->fiftyval, parent->move[0]->fiftyval, d); @@ -98,10 +133,10 @@ heptagon *buildHeptagon(heptagon *parent, int d, hstate s, int pard = 0) { if(pard == 0) { if(purehepta) h->distance = parent->distance + 1; else if(parent->s == hsOrigin) h->distance = 2; - else if(h->spin[0] == 5) + 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 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 - (purehepta?1:2); @@ -112,9 +147,9 @@ 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; + h->setspin(d, fixrot(from->spin(rot) + spin)); + h->move[d]->move[fixrot(from->spin(rot) + spin)] = h; + h->move[d]->setspin(fixrot(from->spin(rot) + spin), d); //generateEmeraldval(h->move[d]); generateEmeraldval(h); } @@ -122,7 +157,7 @@ extern int hrand(int); heptagon *createStep(heptagon *h, int d) { d = fixrot(d); - if(h->s != hsOrigin && !h->move[0]) { + if(!h->move[0] && h->s != hsOrigin) { buildHeptagon(h, 0, hsA, 3 + hrand(2)); } if(h->move[d]) return h->move[d]; @@ -130,18 +165,18 @@ heptagon *createStep(heptagon *h, int d) { buildHeptagon(h, d, hsA); } else if(d == 1) { - addSpin(h, d, h->move[0], h->spin[0]-1, -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); + 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); + 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); + 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); @@ -153,20 +188,24 @@ heptagon *createStep(heptagon *h, int d) { struct heptspin { heptagon *h; int spin; + bool mirrored; + heptspin() { mirrored = false; } }; 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); + res.mirrored = hs.mirrored ^ hs.h->mirror(hs.spin); + res.spin = fixrot(hs.h->spin(hs.spin) + (MIRR(res)?-spin:spin)); return res; } heptspin hsspin(const heptspin &hs, int val) { heptspin res; res.h = hs.h; - res.spin = fixrot(hs.spin + val); + res.spin = fixrot(hs.spin + (MIRR(hs)?-val:val)); + res.mirrored = hs.mirrored; return res; } @@ -174,7 +213,7 @@ heptspin hsspin(const heptspin &hs, int val) { void backtrace(heptagon *pos) { if(pos == &origin) return; backtrace(pos->move[0]); - printf(" %d", pos->spin[0]); + printf(" %d", pos->spin(0)); } void hsshow(const heptspin& t) { diff --git a/hyper.cpp b/hyper.cpp index fffe12dc..0a39cd36 100644 --- a/hyper.cpp +++ b/hyper.cpp @@ -15,46 +15,17 @@ // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. -#ifdef LOCAL -#define CDATA -#endif - -#define VER "8.3j" -#define VERNUM 8310 -#define VERNUM_HEX 0x8310 - #define ISANDROID 0 #define ISMOBILE 0 #define ISIOS 0 +#define USE_SDL +#define USE_COMMANDLINE -#include - -#include - -#ifndef MAC -#undef main +#ifdef STEAM +#define NOLICENSE #endif -#include -#include -#include -#include -#include -#include - -using namespace std; - -FILE *debugfile; -int debugflags; - -const char *scorefile = "hyperrogue.log"; -const char *conffile = "hyperrogue.ini"; - -string levelfile = "hyperrogue.lev"; -string picfile = "hyperrogue.pic"; - -const char *loadlevel = NULL; -const char *musicfile = ""; +#include "init.cpp" #ifdef LINUX #include @@ -77,58 +48,29 @@ void moreStack() { } #endif -string s0; -void addMessage(string s, char spamtype = 0); +eLand readland(const char *s) { + string ss = s; + if(ss == "II") return laCrossroads2; + if(ss == "III") return laCrossroads3; + if(ss == "IV") return laCrossroads4; + if(ss == "V") return laCrossroads5; + for(int l=0; l - -int main(int argc, char **argv) { - -#ifdef LINUX - moreStack(); -#endif +eItem readItem(const char *s) { + string ss = s; + for(int i=0; i, version " VER "\n"); #ifndef NOLICENSE @@ -136,208 +78,290 @@ int main(int argc, char **argv) { printf("comes with absolutely no warranty; see COPYING for details\n"); #endif - // printf("cell size = %d\n", int(sizeof(cell))); - srand(time(NULL)); - shrand(time(NULL)); - #ifdef FHS - char sbuf[640], cbuf[640]; + static string sbuf, cbuf; if(getenv("HOME")) { - snprintf(sbuf, 640, "%s/.%s", getenv("HOME"), scorefile); scorefile = sbuf; - snprintf(cbuf, 640, "%s/.%s", getenv("HOME"), conffile); conffile = cbuf; + sbuf = getenv("HOME"); sbuf += "/."; sbuf += scorefile; + cbuf = getenv("HOME"); cbuf += "/."; cbuf += conffile; + scorefile = sbuf.c_str(); + conffile = cbuf.c_str(); } #endif + } - for(int i=1; i - fieldpattern: quotient by the given (must be followed by qpar)\n"); + printf(" -qpar - fieldpattern: use the given prime instead of 43\n"); + printf(" -cs - fieldpattern: set subpath to the given (cannot be followed by qpar)\n"); + printf(" -csp - fieldpattern: find the subpath of order (cannot be followed by qpar)\n"); + printf(" -S* - toggle Shmup\n"); + printf(" -P n - switch Shmup number of players (n=1..7)\n"); + printf(" -PM - switch the model index\n"); + printf(" -H* - toggle Hardcore\n"); + printf(" -T* - toggle Tactical\n"); + printf(" -7* - toggle heptagonal mode\n"); + printf(" -C* - toggle Chaos mode\n"); + printf(" -R* - toggle Random Pattern\n"); + printf(" -Y id - enable Yendor, level id\n"); + printf(" -D - disable all the special game modes\n"); + printf(" -L - list of features\n"); + printf(" -debugf 7 - output debugging information to hyperrogue-debug.txt\n"); + printf(" -debuge 7 - output debugging information to stderr\n"); + printf(" -offline - don't connect to Steam (for Steam versions)\n"); + printf(" -I ITEM n - start with n of ITEM (activates cheat and disables ghosts)\n"); + printf(" -fix - fix the seed\n"); + printf("Toggles: -o0 disables, -o1 enables, -o switches"); + printf("Not all options are documented, see hyper.cpp"); + exit(0); + } + else if(ca::readArg()) ; + else return 1; + return 0; + } + +int main(int argc, char **argv) { +#ifndef WEB + #ifdef LINUX + moreStack(); + #endif + arg::init(argc, argv); + initializeCLI(); +#endif + initAll(); + arg::read(3); + mainloop(); + finishAll(); + profile_info(); + return 0; + } + +#ifdef USE_COMMANDLINE +namespace arg { + int argc; char **argv; + + void read(int phase) { + curphase = phase; + while(argc) { + int r; + r = readCommon(); if(r == 2) return; if(r == 0) { lshift(); continue; } +#ifdef LOCAL + r = readLocal(); if(r == 2) return; if(r == 0) { lshift(); continue; } +#endif +#ifdef ROGUEVIZ + r = rogueviz::readArgs(); if(r == 2) return; if(r == 0) { lshift(); continue; } +#endif + printf("Unknown option: %s\n", args()); exit(3); } } - - achievement_init(); - - eLand f = firstland; - - // initlanguage(); - initgraph(); - loadsave(); - precalc(); - resetGL(); - initcells(); - - #ifdef BUILDZEBRA - firstland = laCanvas; - shmup::on = false; - #endif - shmup::safety = safety; - initgame(); - #ifdef BUILDZEBRA - zebraPattern(); - #endif - - if(!shmup::on) { - restoreGolems(items[itOrbLife], moGolem); items[itOrbLife] = 0; - restoreGolems(items[itOrbFriend], moTameBomberbird); items[itOrbFriend] = 0; - restoreGolems(kills[moPrincessMoved], moPrincess, princess::saveHP); kills[moPrincessMoved] = 0; - restoreGolems(kills[moPrincessArmedMoved], moPrincessArmed, princess::saveArmedHP); kills[moPrincessArmedMoved] = 0; - } - - firstland = f; - - // verifyHell(); - // exit(1); - - int t1 = SDL_GetTicks(); - - // if(switchEuclid) restartGame('e'); - - if(loadlevel) mapstream::loadMap(loadlevel); - -#ifdef LOCAL - // river(); - autoplay(); -#endif - mainloop(); - - achievement_final(!items[itOrbSafety]); - - saveStats(); - int msec = SDL_GetTicks() - t1; - DEBB(DF_INIT, (debugfile, "frame : %f ms (%f fps)\n", 1.*msec/frames, 1000.*frames/msec)); - offscreen.clear(); - clearMemory(); - cleargraph(); - - achievement_close(); - - return 0; } +#endif diff --git a/hyper.h b/hyper.h index 685f704a..b31ea4b8 100644 --- a/hyper.h +++ b/hyper.h @@ -7,6 +7,9 @@ #define GL +#define PSEUDOKEY_WHEELDOWN 2501 +#define PSEUDOKEY_WHEELUP 2502 + #ifdef NOGFX #undef GFX #endif @@ -18,9 +21,10 @@ // scale the Euclidean #define EUCSCALE 2.3 -// disable this if you have no access to SDL_mixer #ifndef MOBILE -#define AUDIO +#ifndef NOAUDIO +#define SDLAUDIO +#endif #endif #define NUMWITCH 7 @@ -29,7 +33,7 @@ #define LB_YENDOR_CHALLENGE 40 #define LB_PURE_TACTICS 41 -#define NUMLEADER 57 +#define NUMLEADER 69 #define LB_PURE_TACTICS_SHMUP 49 #define LB_PURE_TACTICS_COOP 50 @@ -75,6 +79,7 @@ void achievement_pump(); vector achievementsReceived; // game forward declarations +typedef int flagtype; bool mirrorkill(cell *c); bool isNeighbor(cell *c1, cell *c2); @@ -82,7 +87,7 @@ void checkTide(cell *c); namespace anticheat { extern bool tampered; } int numplayers(); void removeIvy(cell *c); -bool cellEdgeUnstable(cell *c); +bool cellEdgeUnstable(cell *c, flagtype flags = 0); int coastvalEdge(cell *c); typedef int cellfunction(cell*); int towerval(cell *c, cellfunction* cf = &coastvalEdge); @@ -94,13 +99,13 @@ eItem treasureType(eLand l); void buildBarrier(cell *c, int d, eLand l = laNone); void extendBarrier(cell *c); bool buildBarrier4(cell *c, int d, int mode, eLand ll, eLand lr); -void makeEmpty(cell *c); +bool makeEmpty(cell *c); bool isCrossroads(eLand l); -enum orbAction { roMouse, roKeyboard, roCheck, roMouseForce }; +enum orbAction { roMouse, roKeyboard, roCheck, roMouseForce, roMultiCheck, roMultiGo }; void moveItem (cell *from, cell *to, bool activateYendor); -void uncoverMines(cell *c, int lev); +void uncoverMines(cell *c, int lev, int dist); bool survivesMine(eMonster m); -void killMonster(cell *c); +void killMonster(cell *c, eMonster who_killed, flagtype flags = 0); void toggleGates(cell *ct, eWall type, int rad); bool destroyHalfvine(cell *c, eWall newwall = waNone, int tval = 6); void buildCrossroads2(cell *c); @@ -109,12 +114,12 @@ heptagon *createAlternateMap(cell *c, int rad, hstate firststate, int special=0) void generateAlts(heptagon *h); void setdist(cell *c, int d, cell *from); void checkOnYendorPath(); -void killThePlayerAt(eMonster m, cell *c, int flags); +void killThePlayerAt(eMonster m, cell *c, flagtype flags); bool notDippingFor(eItem i); bool collectItem(cell *c2, bool telekinesis = false); void castLightningBolt(struct cellwalker lig); bool movepcto(int d, int subdir = 1, bool checkonly = false); -void stabbingAttack(cell *mf, cell *mt, eMonster who); +void stabbingAttack(cell *mf, cell *mt, eMonster who, int bonuskill = 0); bool earthMove(cell *from, int dir); void messageKill(eMonster killer, eMonster victim); void moveMonster(cell *ct, cell *cf); @@ -122,6 +127,7 @@ int palaceHP(); void placeLocalOrbs(cell *c); int elementalKills(); bool elementalUnlocked(); +bool trollUnlocked(); bool isMultitile(eMonster m); void checkFreedom(cell *cf); int rosedist(cell *c); @@ -137,7 +143,16 @@ namespace mirror { int neighborId(cell *c1, cell *c2); -struct movedir { int d; int subdir; }; +struct movedir { + int d; // 0 to 6, or one of the following -- warning: not used consistently + #define MD_WAIT (-1) + #define MD_DROP (-2) + #define MD_UNDECIDED (-3) + #define MD_USE_ORB (-4) + int subdir; // for normal movement (0-6): turn left or right + cell *tgt; // for MD_USE_ORB: target cell + }; + inline bool movepcto(const movedir& md) { return movepcto(md.d, md.subdir); } void activateActiv(cell *c, bool msg); @@ -145,19 +160,65 @@ void activateActiv(cell *c, bool msg); // shmup struct charstyle { - int charid, skincolor, haircolor, dresscolor, swordcolor, dresscolor2; + int charid, skincolor, haircolor, dresscolor, swordcolor, dresscolor2, uicolor; }; string csname(charstyle& cs); void initcs(charstyle& cs); -void savecs(FILE *f, charstyle& cs); -void loadcs(FILE *f, charstyle& cs); +void savecs(FILE *f, charstyle& cs, int vernum); +void loadcs(FILE *f, charstyle& cs, int vernum); + +#define MAXPLAYER 7 +#define MAXJOY 8 + +#define MAXBUTTON 64 +#define MAXAXE 16 +#define MAXHAT 4 + +namespace multi { + + void recall(); + extern cell *origpos[MAXPLAYER], *origtarget[MAXPLAYER]; + extern int players; + extern cellwalker player[MAXPLAYER]; + extern bool flipped[MAXPLAYER]; + cell *mplayerpos(int i); + + extern vector revive_queue; // queue for revival + + extern movedir whereto[MAXPLAYER]; // player's target cell + + extern int cpid; // player id -- an extra parameter for player-related functions + extern int cpid_edit; // cpid currently being edited + + // treasure collection, kill, and death statistics + extern int treasures[MAXPLAYER], kills[MAXPLAYER], deaths[MAXPLAYER]; + + struct config { + int players; + int subconfig; + int setwhat; + char keyaction[512]; + char joyaction[MAXJOY][MAXBUTTON]; + char axeaction[MAXJOY][MAXAXE]; + char hataction[MAXJOY][MAXHAT][4]; + int deadzoneval[MAXJOY][MAXAXE]; + }; + + charstyle scs[MAXPLAYER]; + + bool playerActive(int p); + int activePlayers(); + cell *multiPlayerTarget(int i); + void checklastmove(); + void leaveGame(int i); + } namespace shmup { + void recall(); extern bool on; extern bool safety; extern int curtime; - extern int players, cpid; void clearMemory(); void init(); void teleported(); @@ -175,22 +236,8 @@ namespace shmup { cell *playerpos(int i); bool playerInBoat(int i); - -#define MAXBUTTON 64 -#define MAXAXE 16 -#define MAXHAT 4 - - struct config { - int players; - int subconfig; - int setwhat; - char keyaction[512]; - char joyaction[8][MAXBUTTON]; - char axeaction[8][MAXAXE]; - char hataction[8][MAXHAT][4]; - }; - - charstyle scs[4]; + void destroyBoats(cell *c); + bool boatAt(cell *c); } // graph @@ -206,6 +253,10 @@ void cleargraphmemory(); void drawFlash(cell* c); void drawBigFlash(cell* c); +void drawParticle(cell *c, int col, int maxspeed = 100); +void drawParticles(cell *c, int col, int qty, int maxspeed = 100); +void drawFireParticles(cell *c, int qty, int maxspeed = 100); +int firecolor(int phase); void drawLightning(); void drawSafety(); @@ -214,8 +265,6 @@ void movepckeydir(int); void centerpc(ld aspd); -void displayStatHelp(int y, string name); -void displayStat(int y, const string& name, const string& val, char mkey); void displayButton(int x, int y, const string& name, int key, int align, int rad = 0); void displayColorButton(int x, int y, const string& name, int key, int align, int rad, int color, int color2 = 0); inline string ONOFF(bool b) { return XLAT(b ? "ON" : "OFF"); } @@ -223,17 +272,15 @@ int darkened(int c); extern int getcstat; bool displaychr(int x, int y, int shift, int size, char chr, int col); bool displayfr(int x, int y, int b, int size, const string &s, int color, int align); -void saveHighQualityShot(); +void saveHighQualityShot(const char *fname = NULL); bool outofmap(hyperpoint h); -void getcoord(const hyperpoint& H, int& x, int& y, int &shift); -void drawline(const hyperpoint& H1, int x1, int y1, int s1, const hyperpoint& H2, int x2, int y2, int col); -void drawline(const hyperpoint& H1, const hyperpoint& H2, int col); +void applymodel(hyperpoint H, hyperpoint& Hscr); void drawCircle(int x, int y, int size, int color); void fixcolor(int& col); int displaydir(cell *c, int d); hyperpoint gethyper(ld x, ld y); -void resetview(); extern cell *lcenterover; extern heptspin viewctr; +void resetview(); extern heptspin viewctr; extern cell *centerover; #ifndef MOBILE int& qpixel(SDL_Surface *surf, int x, int y); #endif @@ -245,31 +292,30 @@ extern int darken; void setvideomode(); void calcparam(); -string ifMousing(string key, string s); - +#ifndef NOCONFIG void saveConfig(); +#endif extern hyperpoint mouseh; extern int webdisplay; -extern bool GL_initialized; extern hyperpoint ccenter; extern ld crad; extern bool mousepressed, anyshiftclick; extern string help; -extern int lalpha; - struct videopar { - ld scale, eye, alpha, aspeed; + ld scale, eye, alpha, sspeed, mspeed, yshift, camera_angle; + ld ballangle, ballproj; + int mobilecompasssize; + bool full; bool goteyes; // for rendering bool goteyes2; // for choosing colors - bool quick; bool darkhepta; - bool shifttarget; + int shifttarget; int xres, yres, framelimit; @@ -280,6 +326,9 @@ struct videopar { int radius; ld alphax, beta; + bool grid; + int particles; + int fsize; int flashtime; @@ -292,7 +341,7 @@ struct videopar { bool usingAA; int joyvalue, joyvalue2, joypanthreshold; - float joypanspeed; + ld joypanspeed; charstyle cs; @@ -301,9 +350,10 @@ struct videopar { int killreduction, itemreduction, portreduction; - shmup::config scfg; + multi::config scfg; - bool steamscore; + int steamscore; + bool drawmousecircle; // draw the circle around the mouse }; extern videopar vid; @@ -322,7 +372,10 @@ enum emtype {emNormal, emHelp, emYendor, emTactic, emRugConfig, emConformal, emProgress, - emCheatMenu + emCheatMenu, emLeader, + emJoyConfig, + emColor, emNumber, + em3D, emRogueviz }; extern emtype cmode, lastmode; @@ -338,14 +391,18 @@ extern struct SDL_Surface *s; namespace mapeditor { extern bool drawplayer; extern char whichPattern, whichShape; + extern char whichCanvas; + extern int displaycodes; int generateCanvas(cell *c); void clearModelCells(); void applyModelcell(cell *c); int realpattern(cell *c); int patterndir(cell *c, char w = whichPattern); + int subpattern(cell *c); extern cell *drawcell; } +#ifndef NORUG namespace rug { extern bool rugged; void init(); @@ -353,6 +410,7 @@ namespace rug { void actDraw(); void buildVertexInfo(cell *c, transmatrix V); } +#endif #define HASLINEVIEW namespace conformal { @@ -372,9 +430,8 @@ namespace conformal { namespace polygonal { extern int SI; - extern double STAR; + extern ld STAR; void solve(); - typedef long double ld; pair compute(ld x, ld y); } @@ -422,8 +479,9 @@ extern bool localKill(shmup::monster *m); #define P_ROSE (1<<28) // rose smell #define P_CLIMBUP (1<<29) // allow climbing up #define P_CLIMBDOWN (1<<30) // allow climbing down +#define P_REPTILE (1<<31) // is reptile -bool passable(cell *w, cell *from, int flags); +bool passable(cell *w, cell *from, flagtype flags); bool isElemental(eLand l); int coastval(cell *c, eLand base); @@ -450,13 +508,15 @@ extern bool safety; #define SAGEMELT .1 #define TEMPLE_EACH 6 -#define PT(x, y) (tactic::on ? (y) : (x)) +#define PT(x, y) ((tactic::on || quotient == 2) ? (y) : (x)) #define ROCKSNAKELENGTH 50 +#define WORMLENGTH 15 #define PUREHARDCORE_LEVEL 10 #define PRIZEMUL 7 #define INF 9999 #define INFD 20 +#define PINFD 125 #define BARLEV ((ISANDROID||ISIOS||purehepta)?9:10) #define BUGLEV 15 // #define BARLEV 9 @@ -469,7 +529,7 @@ bool isAlchAny(eWall w); bool isAlchAny(cell *c); #define YDIST 101 -#define MODECODES 38 +#define MODECODES 254 extern cellwalker cwt; // player character position extern int sval; @@ -494,10 +554,14 @@ bool hellUnlocked(); bool markOrb(eItem it); // mark the orb as 'used', return true if exists bool markEmpathy(eItem it); // mark both the given orb and Empathy as 'used', return true if exists +bool markEmpathy2(eItem it); // as above, but next turn bool isMimic(eMonster m); bool isMimic(cell *c); -void killWithMessage(cell *c, bool orStun = false, eMonster killer = moNone); + +void fallMonster(cell *c, flagtype flags = 0); // kill monster due to terrain + +bool attackMonster(cell *c, flagtype flags, eMonster killer); bool isWorm(eMonster m); bool isWorm(cell *c); @@ -507,10 +571,11 @@ bool isIvy(cell *c); #define GUNRANGE 3 -// 0 = basic treasure, 1 = something else, 2 = power orb +// 0 = basic treasure, 1 = other item, 2 = power orb, 3 = not an item #define IC_TREASURE 0 #define IC_OTHER 1 #define IC_ORB 2 +#define IC_NAI 3 bool playerInPower(); void activateFlash(); @@ -528,7 +593,7 @@ int realstuntime(cell *c); extern bool invismove, invisfish; bool attackingForbidden(cell *c, cell *c2); -void killOrStunMonster(cell *c2); +void killOrStunMonster(cell *c2, eMonster who_killed); extern vector offscreen; // offscreen cells to take care off @@ -538,6 +603,8 @@ cell *playerpos(int i); bool makeflame(cell *c, int timeout, bool checkonly); void bfs(); bool isPlayerInBoatOn(cell *c); +bool isPlayerInBoatOn(cell *c, int i); +void destroyBoats(cell *c, cell *cf, bool strandedToo); extern bool showoff; extern int lastexplore; extern int truelotus; @@ -583,7 +650,18 @@ bool withRose(cell *cfrom, cell *cto); #define MF_MOUNT (1<<18) // don't do #define MF_NOFRIEND (1<<19) // don't do it for friends -bool canAttack(cell *c1, eMonster m1, cell *c2, eMonster m2, int flags); +#define AF_SWORD (1<<20) // big sword +#define AF_SWORD_INTO (1<<21) // moving into big sword +#define AF_MSG (1<<22) // produce a message +#define AF_ORSTUN (1<<23) // attackMonster: allow stunning +#define AF_NEXTTURN (1<<24) // next turn -- don't count shield at power 1 +#define AF_FALL (1<<25) // death by falling +#define MF_STUNNED (1<<26) // edgeunstable: ignore ladders (as stunned monsters do) +#define MF_IVY (1<<27) // edgeunstable: ignore ivy (ivy cannot climb ivy) +#define AF_HORNS (1<<28) // spear attack (always has APPROACH too) +#define AF_BULL (1<<29) // bull attack + +bool canAttack(cell *c1, eMonster m1, cell *c2, eMonster m2, flagtype flags); extern bool chaosmode; extern bool chaosUnlocked; @@ -594,22 +672,6 @@ int getGhostcount(); void raiseBuggyGeneration(cell *c, const char *s); void verifyMutantAround(cell *c); -extern FILE *debugfile; -extern int debugflags; -extern bool offline; - -#ifdef ANDROID -#define DEBB(r,x) -#else -#define DEBB(r,x) { if(debugfile && (!(r) || (debugflags & (r)))) { fprintf x; fflush(debugfile); } } -#endif - -#define DF_INIT 0 // always display these -#define DF_MSG 0 // always display these -#define DF_STEAM 1 -#define DF_GRAPH 2 -#define DF_TURN 4 - #ifdef MOBILE #define NOPNG #endif @@ -633,7 +695,8 @@ void buildAirmap(); // currently works for worms only bool sameMonster(cell *c1, cell *c2); cell *wormhead(cell *c); -eMonster getMount(); +eMonster getMount(int player_id); +eMonster haveMount(); bool isDragon(eMonster m); @@ -647,6 +710,8 @@ extern bool autocheat; extern bool inHighQual; void mountmove(cell *c, int spin, bool fp); +void mountmove(cell *c, int spin, bool fp, cell *ppos); +void mountswap(cell *c1, int spin1, bool fp1, cell *c2, int spin2, bool fp2); template struct dynamicval { T& where; @@ -655,18 +720,403 @@ template struct dynamicval { ~dynamicval() { where = backup; } }; -namespace stalemate { +struct stalemate1 { eMonster who; cell *moveto; cell *killed; cell *pushto; cell *comefrom; + cell *swordlast[2], *swordtransit[2], *swordnext[2]; + bool isKilled(cell *c); + stalemate1(eMonster w, cell *mt, cell *ki, cell *pt, cell *cf) : who(w), moveto(mt), killed(ki), pushto(pt), comefrom(cf) {} + }; + +namespace stalemate { + vector moves; bool nextturn; bool isKilled(cell *c); + + bool anyKilled(); + bool isMoveto(cell *c); + bool isKilledDirectlyAt(cell *c); + bool isPushto(cell *c); }; extern int turncount; bool reduceOrbPower(eItem it, int cap); bool checkOrb(eMonster m1, eItem orb); + +movedir vectodir(const hyperpoint& P); + +namespace sword { + + extern int angle[MAXPLAYER]; + + cell *pos(cell *c, int s); + cell *pos(int id); + bool at(cell *where, bool noplayer = false); + int shift(cell *c1, cell *c2); + } + +void killThePlayer(eMonster m, int id, flagtype flags); +bool attackJustStuns(cell *c2); + +bool isTargetOrAdjacent(cell *c); +bool warningprotection(); +bool mineMarked(cell *c); +bool minesafe(); +bool hasSafeOrb(cell *c); +void placeWater(cell *c, cell *c2); +bool againstCurrent(cell *w, cell *from); + +#define DEFAULTCONTROL (multi::players == 1 && !shmup::on && !multi::alwaysuse) + +extern bool timerghost; + +#ifdef PANDORA +#define MENU_SCALING +#endif + +#ifdef MOBILE +#define MENU_SCALING +#endif + +#ifdef MENU_SCALING +#define displayfrZ dialog::displayzoom +#else +#define displayfrZ displayfr +#endif + +namespace dialog { + + enum tDialogItem {diTitle, diItem, diBreak, diHelp, diInfo, diSlider}; + + struct item { + tDialogItem type; + string body; + string value; + string keycaption; + int key; + int color, colorv, colork, colors, colorc; + int scale; + double param; + }; + + item& lastItem(); + + void addSelItem(string body, string value, int key); + void addBoolItem(string body, bool value, int key); + void addColorItem(string body, int value, int key); + void addHelp(string body); + void addInfo(string body, int color = 0xC0C0C0); + void addItem(string body, int key); + void addBreak(int val); + void addTitle(string body, int color, int scale); + + void init(); + void init(string title, int color = 0xE8E8E8, int scale = 150, int brk = 60); + void display(); + + void drawColorDialog(int color); + int handleKeyColor(int sym, int uni, int& color); + + void editNumber(ld& x, ld vmin, ld vmax, ld step, ld dft, string title, string help); + void editNumber(int& x, int vmin, int vmax, int step, int dft, string title, string help); + void scaleLog(); + void scaleSinh(); + void handleNavigation(int &sym, int &uni); + bool displayzoom(int x, int y, int b, int size, const string &s, int color, int align); + bool editingDetail(); + + int handlePage(int& nl, int& nlm, int perpage); + void displayPageButtons(int i, bool pages); + bool handlePageButtons(int uni); + } + +void checkStunKill(cell *dest); + +void clearMessages(); + +void resetGeometry(); + +namespace svg { + void circle(int x, int y, int size, int col); + void polygon(int *polyx, int *polyy, int polyi, int col, int outline); + void text(int x, int y, int size, const string& str, bool frame, int col, int align); + extern bool in; + extern string *info; + void render(const char *fname = NULL); + } + +extern int sightrange; + +namespace halloween { + void getTreat(cell *where); + } + +// just in case if I change my mind about when Orbs lose their power +#define ORBBASE 0 + +transmatrix mscale(const transmatrix& t, double fac); +transmatrix mzscale(const transmatrix& t, double fac); +extern bool ivoryz; +#define mmscale(V, x) (mmspatial ? (ivoryz ? mzscale(V,x) : mscale(V, x)) : (V)) + +#define SHADOW_WALL 0x60 +#define SHADOW_SL 0x18 +#define SHADOW_MON 0x30 + +bool drawMonsterType(eMonster m, cell *where, const transmatrix& V, int col, double footphase); +void drawPlayerEffects(const transmatrix& V, cell *c, bool onPlayer); + +// monster movement animations + +struct animation { + int ltick; + double footphase; + transmatrix wherenow; + }; + +// we need separate animation layers for Orb of Domination and Tentacle+Ghost, +// and also to mark Boats +#define ANIMLAYERS 3 +#define LAYER_BIG 0 // for worms and krakens +#define LAYER_SMALL 1 // for others +#define LAYER_BOAT 2 // mark that a boat has moved + +extern map animations[ANIMLAYERS]; +extern unordered_map gmatrix, gmatrix0; + +void animateMovement(cell *src, cell *tgt, int layer); + +// for animations which might use the same locations, +// such as replacements or multi-tile monsters +void indAnimateMovement(cell *src, cell *tgt, int layer); +void commitAnimations(int layer); + +void animateReplacement(cell *a, cell *b, int layer); +void fallingFloorAnimation(cell *c, eWall w = waNone, eMonster m = moNone); +void fallingMonsterAnimation(cell *c, eMonster m); + +// ranks: +enum PPR { + PPR_ZERO, PPR_OUTCIRCLE, PPR_MOVESTAR, + PPR_MINUSINF, + PPR_BELOWBOTTOMm, + PPR_BELOWBOTTOM, + PPR_BELOWBOTTOMp, + PPR_LAKEBOTTOM, PPR_HELLSPIKE, + PPR_INLAKEWALLm, PPR_INLAKEWALL, PPR_INLAKEWALLp, + PPR_SUBLAKELEV, PPR_LAKELEV, PPR_BOATLEV, PPR_BOATLEV2, PPR_BOATLEV3, + PPR_LAKEWALLm, PPR_LAKEWALL, PPR_LAKEWALLp, + PPR_FLOOR_TOWER, + PPR_FLOOR, + PPR_FLOOR_DRAGON, + PPR_FLOORa, PPR_FLOORb, PPR_FLOORc, PPR_FLOORd, + PPR_LIZEYE, + PPR_BFLOOR, + PPR_GFLOORa, PPR_GFLOORb, PPR_GFLOORc, + PPR_WALLSHADOW, + PPR_STRUCT0, PPR_STRUCT1, PPR_STRUCT2, PPR_STRUCT3, + PPR_THORNS, PPR_WALL, + PPR_REDWALLm, PPR_REDWALLs, PPR_REDWALLp, PPR_REDWALL, + PPR_REDWALLm2, PPR_REDWALLs2, PPR_REDWALLp2, PPR_REDWALLt2, + PPR_REDWALLm3, PPR_REDWALLs3, PPR_REDWALLp3, PPR_REDWALLt3, + PPR_HEPTAMARK, + PPR_ITEM, PPR_ITEMa, PPR_ITEMb, + PPR_BIGSTATUE, + PPR_GLASSm, PPR_GLASSs, PPR_GLASSp, PPR_GLASS, + PPR_WALL3m, PPR_WALL3s, PPR_WALL3p, PPR_WALL3, PPR_WALL3A, + PPR_HIDDEN, PPR_GIANTSHADOW, + PPR_TENTACLE0, PPR_TENTACLE1, + PPR_ONTENTACLE, PPR_ONTENTACLE_EYES, PPR_ONTENTACLE_EYES2, + PPR_MONSTER_SHADOW, + PPR_MONSTER_FOOT, PPR_MONSTER_LEG, PPR_MONSTER_GROIN, + PPR_MONSTER_BODY, PPR_MONSTER_SUBWPN, PPR_MONSTER_WPN, PPR_MONSTER_ARMOR0, PPR_MONSTER_ARMOR1, + PPR_MONSTER_CLOAK, PPR_MONSTER_NECK, + PPR_MONSTER_HEAD, PPR_MONSTER_FACE, PPR_MONSTER_EYE0, PPR_MONSTER_EYE1, + PPR_MONSTER_HAIR, PPR_MONSTER_HAT0, PPR_MONSTER_HAT1, + PPR_MONSTER_HOODCLOAK1, PPR_MONSTER_HOODCLOAK2, + PPR_STUNSTARS, + PPR_CARRIED, PPR_CARRIEDa, PPR_CARRIEDb, + PPR_PARTICLE, PPR_SWORDMARK, PPR_MAGICSWORD, PPR_MISSILE, + PPR_MINEMARK, PPR_ARROW, + PPR_LINE, PPR_TEXT, PPR_CIRCLE, + PPR_MAX + }; + +void ShadowV(const transmatrix& V, const struct hpcshape& bp, int prio = PPR_MONSTER_SHADOW); + +#define OUTLINE_NONE 0x000000FF +#define OUTLINE_FRIEND 0x00FF00FF +#define OUTLINE_ENEMY 0xFF0000FF +#define OUTLINE_TREASURE 0xFFFF00FF +#define OUTLINE_ORB 0xFF8000FF +#define OUTLINE_OTHER 0xFFFFFFFF +#define OUTLINE_DEAD 0x800000FF +#define OUTLINE_TRANS 0 + +extern bool audio; +extern string musiclicense; +extern string musfname[landtypes]; +extern int musicvolume, effvolume; +void initAudio(); +bool loadMusicInfo(); +void handlemusic(); +void playSeenSound(cell *c); +void playSound(cell *c, const string& fname, int vol = 100); + +inline string pick123() { return cts('1' + rand() % 3); } +inline string pick12() { return cts('1' + rand() % 2); } + +bool playerInBoat(int i); + +extern int lowfar; +extern bool wmspatial, wmescher, wmplain, wmblack, wmascii; +extern bool mmspatial, mmhigh, mmmon, mmitem; +extern int maxreclevel, reclevel; + +string explain3D(ld *param); + +extern int detaillevel; +extern bool quitmainloop; + +enum eGlyphsortorder { + gsoFirstTop, gsoFirstBottom, + gsoLastTop, gsoLastBottom, + gsoLand, gsoValue, + gsoMAX + }; + +extern eGlyphsortorder glyphsortorder; + +#ifdef ROGUEVIZ +namespace rogueviz { + extern bool on; + string describe(shmup::monster *m); + void activate(shmup::monster *m); + void drawVertex(const transmatrix &V, cell *c, shmup::monster *m); + bool virt(shmup::monster *m); + void turn(int delta); + void drawExtra(); + void fixparam(); + int readArgs(); + void close(); + } +#endif + +void explodeMine(cell *c); +bool mayExplodeMine(cell *c, eMonster who); + +int gravityLevel(cell *c); +void fullcenter(); +void movecost(cell* from, cell *to); +void checkmove(); + +transmatrix eumove(int x, int y); + +#ifndef NOSAVE +void loadScores(); +#endif + +int reptilemax(); + +extern bool mousing; +#define IFM(x) (mousing?"":x) + +extern cell *recallCell; + +extern eLand cheatdest; +void cheatMoveTo(eLand l); + +extern int backcolor; + +extern bool overgenerate; +void doOvergenerate(); + +void collectMessage(cell *c2, eItem which); + +namespace quotientspace { + void build(); + void clear(); + extern vector connections; + } + +void killFriendlyIvy(); + +void pushdown(cell *c, int& q, const transmatrix &V, double down, bool rezoom, bool repriority); + +extern bool viewdists; + +void preventbarriers(cell *c); + +bool passable_for(eMonster m, cell *w, cell *from, flagtype extra); + +void beastcrash(cell *c, cell *beast); + +int angledist(int t, int d1, int d2); +int angledist(cell *c, int d1, int d2); + +void setcameraangle(bool b); + +enum eModel { + mdDisk, mdHalfplane, mdBand, mdPolygonal, mdPolynomial, + mdEquidistant, mdEquiarea, mdBall, mdHyperboloid, mdGUARD, mdUnchanged }; + +#define MODELCOUNT ((int) mdGUARD) + +void drawShape(pair* coords, int qty, int color); + +extern eModel pmodel; + +int darkena(int c, int lev, int a); + +#define SHSIZE 16 + +extern cell *shpos[MAXPLAYER][SHSIZE]; +extern int cshpos; + + +namespace arg { +#ifdef USE_COMMANDLINE + extern int argc; extern char **argv; + + inline void lshift() { + argc--; argv++; + } + + inline void shift() { + lshift(); if(!argc) { printf("Missing parameter\n"); exit(1); } + } + + inline char* args() { return *argv; } + inline int argi() { return atoi(*argv); } + inline ld argf() { return atof(*argv); } + inline bool argis(const char *s) { return strcmp(*argv, s) == 0; } + + inline void init(int _argc, char **_argv) { argc=_argc-1; argv=_argv+1; } + + int curphase; + + inline void phaseerror(int x) { + printf("Command line error: cannot read command '%s' from phase %d in phase %d\n", args(), x, curphase); + exit(1); + } + + // returned values: 0 = ok, 1 = not recognized, 2 = shift phase + int readCommon(); + int readLocal(); + +// an useful macro +#define PHASE(x) { if(arg::curphase > x) phaseerror(x); else if(arg::curphase < x) return 2; } + + void read(int phase); + +#else + inline void read(int phase) { } +#endif + } + +extern bool generatingEquidistant; diff --git a/hyperpoint.cpp b/hyperpoint.cpp index 667630e0..806ce0ec 100644 --- a/hyperpoint.cpp +++ b/hyperpoint.cpp @@ -1,29 +1,12 @@ // Hyperbolic Rogue // Copyright (C) 2011-2012 Zeno Rogue, see 'hyper.cpp' for details -// basic utility functions - -#ifdef MOBILE -typedef double ld; -#else -typedef long double ld; -#endif - -template int size(const T& x) {return int(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; } -string fts4(float x) { char buf[64]; sprintf(buf, "%6.4f", x); return buf; } -string cts(char c) { char buf[8]; buf[0] = c; buf[1] = 0; return buf; } -string llts(long long i) { - // sprintf does not work on Windows IIRC - if(i < 0) return "-" + llts(-i); - if(i < 10) return its((int) i); - return llts(i/10) + its(i%10); -} -string itsh(int i) {static char buf[16]; sprintf(buf, "%03X", i); return buf; } - -// for the Euclidean mode... -bool euclid = false; +enum eGeometry {gNormal, gEuclid, gSphere, gElliptic, gQuotient, gQuotient2, gGUARD}; +eGeometry geometry, targetgeometry = gEuclid; +#define euclid (geometry == gEuclid) +#define sphere (geometry == gSphere || geometry == gElliptic) +#define elliptic (geometry == gElliptic) +#define quotient (geometry == gQuotient ? 1 : geometry == gQuotient2 ? 2 : 0) // for the pure heptagonal grid bool purehepta = false; @@ -34,13 +17,31 @@ bool purehepta = false; //=========================== #ifdef SINHCOSH -ld sinh(ld alpha) { return (exp(alpha) - exp(-alpha)) / 2; } -ld cosh(ld alpha) { return (exp(alpha) + exp(-alpha)) / 2; } +// ld sinh(ld alpha) { return (exp(alpha) - exp(-alpha)) / 2; } +// ld cosh(ld alpha) { return (exp(alpha) + exp(-alpha)) / 2; } + +/* ld inverse_sinh(ld z) { + return log(z+sqrt(1+z*z)); + } + +double inverse_cos(double c) { + double s = sqrt(1-c*c); + double r = atan(s/c); + if(r < 0) r = -r; + return r; + } + +// ld tanh(ld x) { return sinh(x) / cosh(x); } +ld inverse_tanh(ld x) { return log((1+x)/(1-x)) / 2; } */ + +#endif +#ifndef M_PI +#define M_PI 3.14159265358979 #endif ld squar(ld x) { return x*x; } -int sig(int z) { return z<2?1:-1; } +int sig(int z) { return (sphere || z<2)?1:-1; } // hyperbolic point: //=================== @@ -62,17 +63,17 @@ hyperpoint hpxyz(ld x, ld y, ld z) { hyperpoint hpxy(ld x, ld y) { // EUCLIDEAN - return hpxyz(x,y, euclid ? 1 : sqrt(1+x*x+y*y)); + return hpxyz(x,y, euclid ? 1 : sphere ? sqrt(1-x*x-y*y) : sqrt(1+x*x+y*y)); } // center of the pseudosphere -hyperpoint Hypc = { {0,0,0} }; +const hyperpoint Hypc = { {0,0,0} }; // origin of the hyperbolic plane -hyperpoint C0 = { {0,0,1} }; +const hyperpoint C0 = { {0,0,1} }; // a point (I hope this number needs no comments ;) ) -hyperpoint Cx1 = { {1,0,1.41421356237} }; +const 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, @@ -80,7 +81,17 @@ hyperpoint Cx1 = { {1,0,1.41421356237} }; // 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]); + return squar(h1[0]-h2[0]) + squar(h1[1]-h2[1]) + (sphere?1:euclid?0:-1) * squar(h1[2]-h2[2]); + } + +ld intvalxy(const hyperpoint &h1, const hyperpoint &h2) { + return squar(h1[0]-h2[0]) + squar(h1[1]-h2[1]); + } + +ld zlevel(const hyperpoint &h) { + if(euclid) return h[2]; + else if(sphere) return sqrt(intval(h, Hypc)); + else return sqrt(-intval(h, Hypc)); } // display a hyperbolic point @@ -100,7 +111,8 @@ hyperpoint mid(const hyperpoint& H1, const hyperpoint& H2) { ld Z = 2; - if(!euclid) { + if(sphere) Z = sqrt(intval(H3, Hypc)); + else if(!euclid) { Z = intval(H3, Hypc); Z = sqrt(-Z); } @@ -110,42 +122,20 @@ hyperpoint mid(const hyperpoint& H1, const hyperpoint& H2) { return H3; } -hyperpoint mid3(const hyperpoint& H1, const hyperpoint& H2, const hyperpoint& H3) { +// like mid, but take 3D into account +hyperpoint midz(const hyperpoint& H1, const hyperpoint& H2) { - hyperpoint Hx; - Hx[0] = H1[0] + H2[0] + H3[0]; - Hx[1] = H1[1] + H2[1] + H3[1]; - Hx[2] = H1[2] + H2[2] + H3[2]; + 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(Hx, Hypc); - Z = sqrt(-Z); - } + if(sphere || !euclid) Z = zlevel(H3) * 2 / (zlevel(H1) + zlevel(H2)); + for(int c=0; c<3; c++) H3[c] /= Z; - for(int c=0; c<3; c++) Hx[c] /= Z; - - return Hx; - } - -hyperpoint mid4(const hyperpoint& H1, const hyperpoint& H2, const hyperpoint& H3, const hyperpoint& H4) { - - hyperpoint Hx; - Hx[0] = H1[0] + H2[0] + H3[0] + H4[0]; - Hx[1] = H1[1] + H2[1] + H3[1] + H4[1]; - Hx[2] = H1[2] + H2[2] + H3[2] + H4[2]; - - ld Z = 2; - - if(!euclid) { - Z = intval(Hx, Hypc); - Z = sqrt(-Z); - } - - for(int c=0; c<3; c++) Hx[c] /= Z; - - return Hx; + return H3; } // matrices @@ -161,9 +151,13 @@ struct transmatrix { }; // identity matrix -transmatrix Id = {{{1,0,0}, {0,1,0}, {0,0,1}}}; +const transmatrix Id = {{{1,0,0}, {0,1,0}, {0,0,1}}}; -transmatrix Mirror = {{{1,0,0}, {0,-1,0}, {0,0,1}}}; +// mirror image +const transmatrix Mirror = {{{1,0,0}, {0,-1,0}, {0,0,1}}}; + +// rotate by PI +const transmatrix pispin = {{{-1,0,0}, {0,-1,0}, {0,0,1}}}; hyperpoint operator * (const transmatrix& T, const hyperpoint& H) { hyperpoint z; @@ -174,11 +168,18 @@ hyperpoint operator * (const transmatrix& T, const hyperpoint& H) { return z; } -transmatrix operator * (const transmatrix& T, const transmatrix& U) { +// T * C0, optimized +inline hyperpoint tC0(const transmatrix &T) { + hyperpoint z; + z[0] = T[0][2]; z[1] = T[1][2]; z[2] = T[2][2]; + return z; + } + +inline 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]; + // 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][0] * U[0][j] + T[i][1] * U[1][j] + T[i][2] * U[2][j]; return R; } @@ -202,26 +203,52 @@ transmatrix eupush(ld x, ld y) { 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); + if(sphere) { + T[0][0] = +cos(alpha); T[0][2] = +sin(alpha); + T[2][0] = -sin(alpha); T[2][2] = +cos(alpha); + } + else { + T[0][0] = +cosh(alpha); T[0][2] = +sinh(alpha); + T[2][0] = +sinh(alpha); T[2][2] = +cosh(alpha); + } return T; } -double inverse_sinh(ld z) { - return log(z+sqrt(1+z*z)); +inline hyperpoint xpush0(ld x) { + hyperpoint h; + if(euclid) return hpxy(x, 0); + else if(sphere) h[0] = sin(x), h[1] = 0, h[2] = cos(x); + else h[0] = sinh(x), h[1] = 0, h[2] = cosh(x); + return h; } - + +inline hyperpoint xspinpush0(ld alpha, ld x) { + // return spin(alpha)*xpush0(x); + ld s; + hyperpoint h; + if(euclid) return hpxy(x*cos(alpha), -x*sin(alpha)); + else if(sphere) s=sin(x), h[0] = s*cos(alpha), h[1] = -s*sin(alpha), h[2] = cos(x); + else s=sinh(x), h[0] = s*cos(alpha), h[1] = -s*sin(alpha), h[2] = cosh(x); + return h; + } + // 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); + if(sphere) { + T[1][1] = +cos(alpha); T[1][2] = +sin(alpha); + T[2][1] = -sin(alpha); T[2][2] = +cos(alpha); + } + else { + 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 spintox(const hyperpoint& H) { transmatrix T = Id; ld R = sqrt(H[0] * H[0] + H[1] * H[1]); if(R >= 1e-12) { @@ -232,7 +259,7 @@ transmatrix spintox(hyperpoint H) { } // reverse of spintox(H) -transmatrix rspintox(hyperpoint H) { +transmatrix rspintox(const hyperpoint& H) { transmatrix T = Id; ld R = sqrt(H[0] * H[0] + H[1] * H[1]); if(R >= 1e-12) { @@ -243,30 +270,42 @@ transmatrix rspintox(hyperpoint H) { } // for H such that H[1] == 0, this matrix pushes H to C0 -transmatrix pushxto0(hyperpoint H) { +transmatrix pushxto0(const 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]; + if(sphere) { + T[0][0] = +H[2]; T[0][2] = -H[0]; + T[2][0] = +H[0]; T[2][2] = +H[2]; + } + else { + 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) { +transmatrix rpushxto0(const 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]; + if(sphere) { + T[0][0] = +H[2]; T[0][2] = +H[0]; + T[2][0] = -H[0]; T[2][2] = +H[2]; + } + else { + 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) { +transmatrix gpushxto0(const hyperpoint& H) { hyperpoint H2 = spintox(H) * H; return rspintox(H) * pushxto0(H2) * spintox(H); } -transmatrix rgpushxto0(hyperpoint H) { +transmatrix rgpushxto0(const hyperpoint& H) { hyperpoint H2 = spintox(H) * H; return rspintox(H) * rpushxto0(H2) * spintox(H); } @@ -305,18 +344,22 @@ void fixmatrix(transmatrix& T) { 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(" -> %10.7f\n", double(squar(T[y][0]) + squar(T[y][1]) + sig(2) * 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++) printf("%10.7f", double(squar(T[0][x]) + squar(T[1][x]) + sig(2) * 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("%10.7f", double(T[0][x]*T[0][y] + T[1][x]*T[1][y] + sig(2) * T[2][x]*T[2][y])); } printf("\n\n"); } transmatrix inverse(transmatrix T) { + profile_start(7); ld det = 0; for(int i=0; i<3; i++) det += T[0][i] * T[1][(i+1)%3] * T[2][(i+2)%3]; @@ -330,12 +373,22 @@ transmatrix inverse(transmatrix T) { 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; + profile_stop(7); return T2; } -double hdist(hyperpoint h1, hyperpoint h2) { - hyperpoint mh = gpushxto0(h1) * h2; - return inverse_sinh(sqrt(mh[0]*mh[0]+mh[1]*mh[1])); +// distance between mh and 0 +double hdist0(const hyperpoint& mh) { + if(sphere) return mh[2] >= 1 ? 0 : mh[2] <= -1 ? M_PI : acos(mh[2]); + if(!euclid && mh[2] > 1.5) return acosh(mh[2]); + ld d = sqrt(mh[0]*mh[0]+mh[1]*mh[1]); + if(euclid) return d; + return asinh(d); + } + +// distance between two points +double hdist(const hyperpoint& h1, const hyperpoint& h2) { + return hdist0(gpushxto0(h1) * h2); } namespace hyperpoint_vec { @@ -361,3 +414,49 @@ namespace hyperpoint_vec { } } + +hyperpoint mscale(const hyperpoint& t, double fac) { + hyperpoint res; + for(int i=0; i<3; i++) + res[i] = t[i] * fac; + return res; + } + +transmatrix mscale(const transmatrix& t, double fac) { + transmatrix res; + for(int i=0; i<3; i++) for(int j=0; j<3; j++) + res[i][j] = t[i][j] * fac; + return res; + } + +transmatrix xyscale(const transmatrix& t, double fac) { + transmatrix res; + for(int i=0; i<3; i++) for(int j=0; j<2; j++) + res[i][j] = t[i][j] * fac; + return res; + } + +transmatrix xyzscale(const transmatrix& t, double fac, double facz) { + transmatrix res; + for(int i=0; i<3; i++) for(int j=0; j<2; j++) + res[i][j] = t[i][j] * fac; + for(int i=0; i<3; i++) + res[i][2] = t[i][2] * facz; + return res; + } + +// double downspin_zivory; + +transmatrix mzscale(const transmatrix& t, double fac) { + // take only the spin + transmatrix tcentered = gpushxto0(tC0(t)) * t; + // tcentered = tcentered * spin(downspin_zivory); + fac -= 1; + transmatrix res = t * inverse(tcentered) * ypush(-fac) * tcentered; + fac *= .2; + fac += 1; + for(int i=0; i<3; i++) for(int j=0; j<3; j++) + res[i][j] = res[i][j] * fac; + return res; + } + diff --git a/hyperweb.cpp b/hyperweb.cpp new file mode 100644 index 00000000..b70927a4 --- /dev/null +++ b/hyperweb.cpp @@ -0,0 +1,173 @@ +#define WEB +#define MOBWEB +#define MINI +#define NOAUDIO +#define NOGFX +#define NOPNG +#define DEMO + +#ifdef FAKEWEB +void mainloopiter(); +template void emscripten_set_main_loop(A a, B b, C c) { while(true) mainloopiter(); } +#else +#include +#endif + +void initweb(); + +void loadCompressedChar(int &otwidth, int &otheight, int *tpix); + +#include "hyper.cpp" + +void playSound(cell *c, const string& fname, int vol) { } + +void initweb() { + toggleanim(false); + cmode = emMenu; + } + +unsigned char fonttable[] = { +43,13,0,255,0,255,0,49, +43,16,0,133,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,236,255,4,235,0,10,206,255,4,205,0,10,176,255,4,175,0,10,146,255,4,145,0,10,116,255,4,115,0,10,86,255,4,85,0,10,55,255,4,55,0,58,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,149, +43,19,0,155,255,4,0,4,255,4,0,7,255,4,0,4,255,4,0,7,255,4,0,4,255,4,0,7,255,4,0,4,255,4,0,7,255,4,0,4,255,4,0,7,255,4,0,4,255,4,0,7,255,4,0,4,255,4,0,7,255,4,0,4,255,4,0,7,255,4,0,4,255,4,0,7,255,4,0,4,255,4,0,255,0,224, +43,30,0,251,152,255,3,72,0,3,28,255,3,188,0,17,223,255,2,247,9,0,3,100,255,3,116,0,16,38,255,3,185,0,4,172,255,3,44,0,16,110,255,3,114,0,3,3,239,255,2,227,0,17,181,255,3,42,0,3,60,255,3,156,0,16,7,245,255,2,227,0,4,132,255,3,84,0,11,255,23,0,7,255,23,0,7,255,23,0,7,255,23,0,11,51,255,3,172,0,4,187,255,3,37,0,16,114,255,3,110,0,3,5,244,255,2,230,0,17,176,255,3,47,0,3,56,255,3,169,0,16,1,237,255,2,239,2,0,3,119,255,3,108,0,16,45,255,3,179,0,4,181,255,3,46,0,11,255,23,0,7,255,23,0,7,255,23,0,7,255,23,0,11,70,255,3,157,0,4,207,255,2,252,12,0,16,131,255,3,95,0,3,15,253,255,2,203,0,17,192,255,3,34,0,3,73,255,3,142,0,16,5,246,255,2,229,0,4,134,255,3,81,0,16,57,255,3,168,0,4,195,255,3,21,0,16,117,255,3,107,0,3,7,248,255,2,215,0,17,178,255,3,46,0,3,61,255,3,154,0,255,0,27, +43,25,0,186,252,255,2,0,22,253,255,2,0,22,254,255,2,0,22,255,3,0,17,24,102,157,193,216,255,3,233,188,130,64,3,0,10,29,169,252,255,11,236,153,51,0,7,58,240,255,16,0,6,21,236,255,17,0,6,137,255,5,238,111,39,255,3,15,46,97,159,238,255,2,0,6,214,255,5,71,0,2,255,3,0,4,5,83,198,0,6,245,255,5,13,0,2,255,3,0,13,244,255,5,85,0,2,255,3,0,13,209,255,5,244,133,59,255,3,0,13,130,255,11,166,119,71,12,0,9,16,227,255,13,249,186,81,2,0,7,38,213,255,15,196,28,0,7,7,106,212,255,14,215,9,0,9,36,105,157,204,255,11,116,0,13,255,3,121,185,255,6,199,0,13,255,3,0,2,106,255,5,238,0,13,255,3,0,2,15,255,5,248,0,4,206,106,18,0,6,255,3,0,2,40,255,5,224,0,4,255,2,252,196,127,79,35,9,0,1,255,3,14,62,204,255,5,158,0,4,255,8,245,255,10,250,44,0,4,255,18,253,97,0,5,255,17,204,60,0,6,21,62,103,145,181,199,215,232,248,255,3,240,215,178,124,49,0,16,4,255,3,0,21,3,255,3,0,21,2,255,3,0,21,1,255,3,0,22,255,3,0,111, +43,36,0,255,0,37,62,163,221,246,246,221,162,62,0,11,11,224,255,2,159,0,10,8,162,255,8,159,7,0,9,141,255,2,235,19,0,10,163,255,10,159,0,8,49,252,255,2,94,0,10,65,255,3,251,109,16,15,109,252,255,3,61,0,6,2,203,255,2,189,0,11,164,255,3,145,0,4,145,255,3,163,0,6,111,255,2,247,37,0,11,223,255,3,47,0,4,48,255,3,222,0,5,28,243,255,2,125,0,12,247,255,3,14,0,4,14,255,3,246,0,5,176,255,2,214,6,0,12,246,255,3,14,0,4,15,255,3,245,0,4,81,255,2,254,61,0,13,222,255,3,48,0,4,49,255,3,221,0,3,13,227,255,2,155,0,14,163,255,3,146,0,4,146,255,3,161,0,3,146,255,2,233,17,0,14,63,255,3,251,108,15,14,107,251,255,3,60,0,2,53,253,255,2,90,0,16,163,255,10,159,0,2,4,207,255,2,185,0,17,8,162,255,8,160,7,0,2,116,255,2,246,35,0,3,60,161,221,245,246,222,163,63,0,8,63,163,222,246,246,222,162,62,0,3,31,244,255,2,121,0,2,7,159,255,8,162,8,0,17,181,255,2,211,5,0,2,160,255,10,162,0,16,86,255,2,254,58,0,2,63,255,3,251,110,16,15,109,252,255,3,64,0,14,15,230,255,2,152,0,3,164,255,3,145,0,4,145,255,3,164,0,14,151,255,2,231,16,0,3,223,255,3,47,0,4,48,255,3,222,0,13,57,254,255,2,87,0,4,247,255,3,14,0,4,14,255,3,246,0,12,5,211,255,2,182,0,5,246,255,3,14,0,4,15,255,3,245,0,12,121,255,2,245,32,0,5,221,255,3,47,0,4,47,255,3,220,0,11,34,246,255,2,117,0,6,161,255,3,144,0,4,141,255,3,161,0,11,185,255,2,208,4,0,6,60,255,3,251,107,15,14,103,250,255,3,59,0,10,91,255,2,253,55,0,8,159,255,10,158,0,10,17,233,255,2,148,0,9,7,159,255,8,160,7,0,10,156,255,2,229,14,0,11,60,161,221,246,246,222,163,62,0,255,0,73, +43,31,0,255,0,2,32,124,187,229,246,251,241,218,186,142,91,30,0,17,6,143,251,255,11,0,16,3,187,255,13,0,16,112,255,14,0,16,209,255,5,186,52,10,14,44,107,197,255,2,0,16,246,255,5,22,0,6,50,187,0,16,239,255,5,43,0,24,179,255,5,184,1,0,23,59,254,255,5,147,0,23,36,228,255,6,146,0,21,86,244,255,8,150,1,0,6,20,255,4,237,0,6,109,254,255,10,155,1,0,5,63,255,4,201,0,5,81,255,13,159,2,0,4,145,255,4,164,0,4,15,235,255,4,241,84,209,255,7,164,3,0,2,21,238,255,4,102,0,4,115,255,5,105,0,1,19,206,255,7,168,4,1,172,255,5,28,0,4,194,255,5,24,0,2,17,204,255,7,172,155,255,5,199,0,5,238,255,5,5,0,3,15,201,255,13,78,0,5,251,255,5,38,0,4,14,198,255,11,202,1,0,5,231,255,5,136,0,5,12,194,255,9,242,37,0,6,190,255,5,250,72,0,5,11,191,255,8,142,0,7,108,255,6,251,131,22,0,2,1,65,200,255,8,244,37,0,6,11,226,255,7,253,224,215,242,255,11,202,4,0,6,67,249,255,22,133,0,7,76,242,255,15,229,227,255,4,253,61,0,7,31,178,254,255,10,249,186,87,4,53,251,255,4,224,15,0,8,36,124,183,224,242,251,238,220,182,137,81,13,0,4,123,255,5,165,0,255,0,25, +43,11,0,91,255,4,0,7,255,4,0,7,255,4,0,7,255,4,0,7,255,4,0,7,255,4,0,7,255,4,0,7,255,4,0,7,255,4,0,7,255,4,0,255,0,24, +43,16,0,119,20,238,255,4,183,0,9,155,255,4,252,42,0,8,45,253,255,4,157,0,9,177,255,4,251,34,0,8,48,255,5,165,0,9,163,255,5,57,0,8,21,250,255,4,211,0,9,111,255,5,121,0,9,199,255,5,38,0,8,20,253,255,4,223,0,9,85,255,5,160,0,9,139,255,5,109,0,9,184,255,5,66,0,9,217,255,5,36,0,9,240,255,5,15,0,9,251,255,5,4,0,9,251,255,5,4,0,9,240,255,5,14,0,9,219,255,5,36,0,9,186,255,5,66,0,9,141,255,5,109,0,9,88,255,5,159,0,9,23,254,255,4,221,0,10,203,255,5,35,0,9,114,255,5,118,0,9,23,251,255,4,208,0,10,165,255,5,54,0,9,50,255,5,163,0,10,178,255,4,250,33,0,9,46,254,255,4,155,0,10,156,255,4,251,41,0,9,21,238,255,4,183,0,66, +43,16,0,114,184,255,4,238,20,0,9,43,252,255,4,153,0,10,157,255,4,253,44,0,9,34,251,255,4,176,0,10,166,255,5,48,0,9,58,255,5,162,0,10,211,255,4,249,20,0,9,122,255,5,109,0,9,39,255,5,198,0,10,224,255,4,253,19,0,9,161,255,5,84,0,9,110,255,5,138,0,9,67,255,5,183,0,9,37,255,5,216,0,9,15,255,5,238,0,9,5,255,5,249,0,9,6,255,5,250,0,9,15,255,5,239,0,9,38,255,5,217,0,9,68,255,5,184,0,9,111,255,5,139,0,9,162,255,5,85,0,9,225,255,4,253,20,0,8,40,255,5,198,0,9,123,255,5,110,0,9,212,255,4,250,21,0,8,59,255,5,162,0,9,167,255,5,48,0,8,35,251,255,4,176,0,9,158,255,4,253,45,0,8,43,252,255,4,154,0,9,184,255,4,238,20,0,71, +43,19,0,160,255,3,0,16,255,3,0,16,255,3,0,9,12,198,83,0,4,255,3,0,4,83,198,12,0,2,126,255,2,196,54,0,2,255,3,0,2,54,196,255,2,125,0,2,173,255,3,254,166,31,255,3,31,166,254,255,3,173,0,3,67,201,255,3,247,255,3,248,255,3,204,69,0,6,82,214,255,7,218,87,1,0,8,8,189,255,5,199,11,0,8,1,86,218,255,7,223,91,1,0,5,69,204,255,3,245,255,3,245,255,3,207,71,0,3,173,255,3,254,161,27,255,3,27,161,254,255,3,174,0,2,126,255,2,193,51,0,2,255,3,0,2,51,193,255,2,125,0,2,12,199,82,0,4,255,3,0,4,83,198,12,0,9,255,3,0,16,255,3,0,16,255,3,0,255,0,95, +43,30,0,255,0,118,255,4,0,26,255,4,0,26,255,4,0,26,255,4,0,26,255,4,0,26,255,4,0,26,255,4,0,26,255,4,0,26,255,4,0,17,255,22,0,8,255,22,0,8,255,22,0,8,255,22,0,17,255,4,0,26,255,4,0,26,255,4,0,26,255,4,0,26,255,4,0,26,255,4,0,26,255,4,0,26,255,4,0,26,255,4,0,255,0,28, +43,14,0,255,0,127,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,7,14,255,5,227,0,7,86,255,5,83,0,7,164,255,4,184,0,7,3,237,255,3,248,36,0,7,63,255,4,130,0,8,140,255,3,222,9,0,8,217,255,3,77,0,63, +43,15,0,255,0,62,255,11,0,4,255,11,0,4,255,11,0,4,255,11,0,4,255,11,0,255,0,2, +43,14,0,255,0,127,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,130, +43,13,0,112,11,247,255,2,216,0,8,81,255,3,138,0,8,159,255,3,60,0,7,2,234,255,2,235,2,0,7,59,255,3,160,0,8,137,255,3,82,0,8,215,255,2,248,11,0,7,36,255,3,181,0,8,115,255,3,103,0,8,193,255,2,254,26,0,7,18,252,255,2,203,0,8,93,255,3,125,0,8,171,255,3,47,0,7,6,242,255,2,224,0,8,71,255,3,147,0,8,149,255,3,68,0,8,226,255,2,240,5,0,7,49,255,3,168,0,8,127,255,3,90,0,8,205,255,2,251,16,0,7,27,255,3,190,0,8,105,255,3,112,0,8,183,255,3,33,0,7,12,248,255,2,211,0,8,83,255,3,133,0,8,161,255,3,55,0,7,3,235,255,2,231,1,0,7,61,255,3,155,0,8,139,255,3,77,0,8,217,255,2,245,9,0,73, +43,25,0,208,47,125,193,222,244,245,222,193,125,47,0,13,35,174,255,10,173,34,0,10,62,241,255,12,240,59,0,8,56,246,255,14,245,53,0,6,9,217,255,16,214,8,0,5,123,255,5,254,151,51,12,11,50,148,253,255,5,120,0,4,2,225,255,5,106,0,6,102,255,5,223,2,0,3,68,255,5,208,0,8,207,255,5,66,0,3,127,255,5,122,0,8,122,255,5,125,0,3,181,255,5,65,0,8,65,255,5,179,0,3,217,255,5,31,0,8,32,255,5,216,0,3,233,255,5,12,0,8,14,255,5,232,0,3,248,255,5,3,0,8,4,255,5,247,0,3,248,255,5,4,0,8,5,255,5,248,0,3,233,255,5,13,0,8,14,255,5,232,0,3,218,255,5,33,0,8,34,255,5,216,0,3,182,255,5,67,0,8,68,255,5,180,0,3,128,255,5,125,0,8,126,255,5,126,0,3,69,255,5,211,1,0,6,1,212,255,5,67,0,3,3,226,255,5,109,0,6,109,255,5,224,2,0,4,125,255,5,254,152,51,11,11,50,152,254,255,5,121,0,5,10,219,255,16,216,8,0,6,58,247,255,14,246,55,0,8,64,242,255,12,241,62,0,10,38,177,255,10,177,36,0,13,48,127,194,223,245,245,224,194,127,48,0,232, +43,25,0,204,21,63,106,149,191,234,255,6,0,13,255,12,0,13,255,12,0,13,255,12,0,13,255,12,0,13,235,192,149,107,64,21,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,13,255,18,0,7,255,18,0,7,255,18,0,7,255,18,0,7,255,18,0,228, +43,25,0,205,1,49,116,165,209,233,248,248,233,208,159,95,12,0,10,48,144,232,255,11,244,123,6,0,8,255,16,196,14,0,7,255,17,183,0,7,255,18,78,0,6,255,3,254,186,109,45,16,7,37,113,233,255,6,170,0,6,255,1,250,150,37,0,7,25,220,255,5,224,0,6,182,39,0,10,78,255,5,248,0,18,12,255,5,241,0,18,21,255,5,222,0,18,90,255,5,183,0,17,14,218,255,5,130,0,16,10,184,255,6,61,0,15,27,199,255,6,207,0,15,75,236,255,6,253,60,0,13,5,141,254,255,7,129,0,13,33,202,255,8,172,2,0,12,84,240,255,8,181,7,0,11,8,151,255,8,251,120,1,0,11,40,209,255,8,225,57,0,12,94,244,255,8,175,17,0,13,255,19,0,6,255,19,0,6,255,19,0,6,255,19,0,6,255,19,0,228, +43,25,0,203,28,84,132,172,204,229,244,252,248,237,218,183,138,66,4,0,10,255,14,222,92,0,9,255,16,140,0,8,255,17,76,0,7,255,17,183,0,7,224,161,111,67,38,15,5,4,22,64,149,251,255,5,233,0,18,85,255,5,246,0,18,9,255,5,217,0,18,81,255,5,162,0,14,4,21,60,139,247,255,4,253,46,0,11,255,11,251,89,0,12,255,10,175,39,0,13,255,10,252,171,34,0,12,255,12,240,60,0,11,255,13,236,20,0,13,7,22,54,113,211,255,6,128,0,17,3,158,255,5,205,0,18,23,255,5,242,0,18,21,255,5,248,0,5,201,92,8,0,9,2,155,255,5,222,0,5,255,2,244,172,106,58,24,8,3,17,49,107,207,255,6,165,0,5,255,19,65,0,5,255,18,154,0,6,255,17,150,4,0,6,55,166,248,255,12,198,69,0,10,12,84,148,195,229,246,252,241,229,201,161,110,34,0,233, +43,25,0,211,7,212,255,7,0,16,139,255,8,0,15,60,253,255,8,0,14,12,221,255,9,0,14,152,255,3,254,255,6,0,13,72,255,4,143,255,6,0,12,17,229,255,3,215,8,255,6,0,12,165,255,3,251,52,0,1,255,6,0,11,84,255,4,129,0,2,255,6,0,10,23,235,255,3,205,5,0,2,255,6,0,10,179,255,3,248,43,0,3,255,6,0,9,98,255,4,116,0,4,255,6,0,8,31,241,255,3,194,2,0,4,255,6,0,7,1,190,255,3,243,34,0,5,255,6,0,7,111,255,4,102,0,6,255,6,0,7,246,255,3,182,0,7,255,6,0,7,255,22,0,3,255,22,0,3,255,22,0,3,255,22,0,3,255,22,0,15,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,230, +43,25,0,204,255,17,0,8,255,17,0,8,255,17,0,8,255,17,0,8,255,17,0,8,255,5,0,20,255,5,0,20,255,5,0,20,255,5,163,214,239,251,237,219,175,120,35,0,11,255,13,254,181,34,0,9,255,15,244,79,0,8,255,16,248,64,0,7,255,17,219,6,0,6,255,2,238,158,93,40,15,4,21,66,150,249,255,6,94,0,6,199,86,5,0,8,45,232,255,5,172,0,18,89,255,5,221,0,18,14,255,5,244,0,18,14,255,5,244,0,18,89,255,5,220,0,5,206,106,17,0,9,43,232,255,5,168,0,5,255,2,251,193,123,74,32,12,3,19,63,146,248,255,6,86,0,5,255,18,206,2,0,5,255,17,239,47,0,6,255,16,230,54,0,7,52,155,242,255,11,246,150,16,0,10,6,66,133,178,217,237,250,247,231,207,158,100,15,0,232, +43,25,0,209,55,128,193,220,241,249,231,192,134,47,0,13,64,200,255,10,199,67,0,9,1,137,255,14,0,9,163,255,15,0,8,112,255,16,0,7,41,249,255,5,232,131,60,20,5,15,47,110,201,255,2,0,7,158,255,5,177,13,0,8,53,188,0,6,22,248,255,4,212,7,0,17,92,255,5,92,14,111,185,232,250,240,218,165,92,6,0,8,151,255,5,119,239,255,8,228,81,0,7,202,255,17,128,0,6,224,255,18,99,0,5,242,255,18,239,18,0,4,251,255,7,179,58,18,9,51,164,255,6,118,0,4,238,255,6,179,0,6,179,255,5,192,0,4,222,255,6,58,0,6,58,255,5,230,0,4,193,255,6,18,0,6,18,255,5,249,0,4,141,255,6,18,0,6,18,255,5,236,0,4,84,255,6,58,0,6,58,255,5,210,0,4,10,239,255,5,179,0,6,179,255,5,147,0,5,145,255,6,178,57,17,8,51,164,255,6,63,0,5,21,234,255,16,187,0,7,80,254,255,14,232,33,0,8,90,250,255,12,228,50,0,10,55,198,255,9,252,165,20,0,13,64,141,203,229,249,243,224,184,121,33,0,232, +43,25,0,202,255,20,0,5,255,20,0,5,255,20,0,5,255,19,253,0,5,255,19,169,0,17,108,255,6,50,0,16,3,223,255,5,187,0,17,90,255,6,68,0,17,208,255,5,205,0,17,73,255,6,86,0,17,192,255,5,219,2,0,16,55,255,6,103,0,17,174,255,5,232,8,0,16,40,253,255,5,121,0,17,157,255,5,242,16,0,16,26,249,255,5,138,0,17,139,255,5,249,26,0,16,16,242,255,5,156,0,17,122,255,5,253,39,0,16,8,232,255,5,174,0,17,104,255,6,55,0,16,3,220,255,5,191,0,17,87,255,6,72,0,17,205,255,5,208,0,17,69,255,6,90,0,17,188,255,5,223,4,0,237, +43,25,0,206,6,73,149,194,229,243,252,243,229,194,149,73,5,0,11,86,225,255,11,223,83,0,9,121,255,15,119,0,7,58,253,255,15,253,57,0,6,167,255,17,166,0,6,228,255,5,234,101,28,7,27,97,232,255,5,227,0,6,250,255,5,70,0,5,69,255,5,249,0,6,239,255,5,13,0,5,13,255,5,238,0,6,191,255,5,69,0,5,70,255,5,189,0,6,94,255,5,232,98,27,6,27,97,232,255,5,91,0,6,2,181,255,15,165,1,0,7,6,143,251,255,11,245,133,2,0,9,18,136,254,255,9,253,129,18,0,9,113,242,255,13,242,113,0,7,142,255,17,142,0,5,68,255,6,158,61,16,4,26,64,164,254,255,5,67,0,4,176,255,5,122,0,7,120,255,5,175,0,4,233,255,5,16,0,7,17,255,5,232,0,4,251,255,5,16,0,7,17,255,5,250,0,4,234,255,5,119,0,7,118,255,5,232,0,4,193,255,5,254,154,59,15,4,24,62,161,254,255,5,192,0,4,109,255,19,109,0,4,8,217,255,17,217,8,0,5,40,225,255,15,226,40,0,7,21,151,251,255,11,252,154,22,0,10,19,98,159,202,231,244,253,244,231,203,161,101,21,0,231, +43,25,0,207,34,121,184,223,242,248,227,201,137,61,0,13,20,165,252,255,9,193,50,0,10,50,228,255,12,248,81,0,8,32,232,255,14,252,74,0,7,187,255,16,230,17,0,5,63,255,6,167,52,9,9,53,170,255,6,140,0,5,147,255,5,181,0,6,170,255,5,237,8,0,4,210,255,5,58,0,6,53,255,6,82,0,4,237,255,5,18,0,6,9,255,6,138,0,4,250,255,5,18,0,6,9,255,6,191,0,4,232,255,5,58,0,6,52,255,6,220,0,4,194,255,5,179,0,6,167,255,6,237,0,4,121,255,6,179,58,18,18,58,181,255,7,251,0,4,21,242,255,18,241,0,5,106,255,18,223,0,6,136,255,17,201,0,7,88,231,255,8,236,115,255,5,150,0,8,7,95,167,220,241,250,232,185,108,11,99,255,5,90,0,17,10,218,255,4,248,21,0,6,186,50,0,8,17,185,255,5,156,0,7,255,2,195,106,44,13,4,20,61,134,235,255,5,248,40,0,7,255,16,110,0,8,255,15,162,0,9,255,14,135,1,0,9,69,203,255,10,198,63,0,13,53,138,196,234,250,241,220,192,127,54,0,234, +43,14,0,200,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,92,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,130, +43,14,0,200,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,92,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,8,255,6,0,7,14,255,5,227,0,7,86,255,5,83,0,7,164,255,4,184,0,7,3,237,255,3,248,36,0,7,63,255,4,130,0,8,140,255,3,222,9,0,8,217,255,3,77,0,63, +43,30,0,255,0,129,34,122,211,0,24,26,112,201,255,3,0,21,19,102,191,253,255,5,0,18,13,92,181,250,255,8,0,15,8,82,171,246,255,9,203,118,0,12,4,72,161,241,255,9,201,116,32,0,11,1,62,151,235,255,8,254,199,114,30,0,12,52,140,227,255,8,254,197,112,28,0,15,255,8,253,195,110,27,0,18,255,5,253,193,108,25,0,21,255,5,251,184,100,20,0,21,255,8,252,188,103,22,0,18,52,141,228,255,8,252,191,107,25,0,17,1,63,152,235,255,8,253,195,111,28,0,17,4,73,162,241,255,8,254,198,114,30,0,17,8,83,172,246,255,9,202,118,0,18,13,93,182,250,255,8,0,21,19,103,192,253,255,5,0,24,26,113,202,255,3,0,27,35,123,212,0,255,0,78, +43,30,0,255,0,255,0,4,255,23,0,7,255,23,0,7,255,23,0,7,255,23,0,127,255,23,0,7,255,23,0,7,255,23,0,7,255,23,0,255,0,168, +43,30,0,255,0,109,211,122,34,0,27,255,3,200,112,26,0,24,255,5,253,190,102,19,0,21,255,8,250,180,92,13,0,18,118,203,255,9,246,170,82,8,0,17,32,116,201,255,9,241,160,72,4,0,17,30,114,199,254,255,8,235,150,62,1,0,17,28,112,196,254,255,8,227,140,52,0,18,26,110,194,253,255,8,0,21,24,107,192,253,255,5,0,21,19,99,183,250,255,5,0,18,22,103,187,251,255,8,0,15,25,107,191,252,255,8,228,141,52,0,12,27,110,194,253,255,8,235,151,63,1,0,11,31,114,198,254,255,8,241,161,73,4,0,12,118,202,255,9,246,171,83,8,0,15,255,8,250,181,93,13,0,18,255,5,253,191,103,19,0,21,255,3,201,113,26,0,24,212,123,34,0,255,0,98, +43,21,0,170,28,84,133,175,209,235,248,251,238,216,171,103,18,0,8,255,12,242,114,0,7,255,14,145,0,6,255,15,67,0,5,255,15,171,0,5,255,2,223,136,72,26,6,23,88,227,255,5,229,0,5,194,71,1,0,6,59,255,5,249,0,14,12,255,5,231,0,14,85,255,5,181,0,13,36,233,255,5,82,0,12,53,232,255,5,180,0,12,80,246,255,5,209,17,0,11,87,252,255,5,206,23,0,11,51,249,255,5,179,10,0,12,174,255,5,183,4,0,13,236,255,5,38,0,14,255,6,1,0,77,255,6,0,15,255,6,0,15,255,6,0,15,255,6,0,15,255,6,0,15,255,6,0,198, +43,36,0,255,0,45,19,97,161,207,238,251,246,233,201,159,94,22,0,22,37,166,250,255,10,250,171,50,0,18,18,154,251,255,14,254,158,14,0,15,43,220,255,4,204,123,68,28,11,5,21,58,116,195,255,4,221,40,0,13,70,242,255,3,179,46,0,10,44,176,255,3,235,46,0,11,42,242,255,2,237,77,0,14,86,243,255,2,226,24,0,9,17,220,255,2,224,34,0,16,52,239,255,2,181,0,9,154,255,2,236,33,0,18,67,253,255,2,88,0,7,37,251,255,2,73,0,5,41,155,224,248,231,173,57,0,1,255,4,0,3,143,255,2,210,0,7,167,255,2,170,0,5,101,250,255,5,252,93,255,4,0,3,14,243,255,2,62,0,5,20,251,255,2,43,0,4,81,254,255,7,243,255,4,0,4,153,255,2,135,0,5,97,255,2,195,0,4,12,234,255,3,188,45,7,49,194,255,5,0,4,83,255,2,195,0,5,160,255,2,117,0,4,104,255,3,220,8,0,3,11,225,255,4,0,4,31,255,2,227,0,5,205,255,2,61,0,4,179,255,3,107,0,5,111,255,4,0,4,11,255,2,246,0,5,237,255,2,22,0,4,228,255,3,42,0,5,45,255,4,0,4,7,255,2,244,0,5,250,255,2,6,0,4,247,255,3,13,0,5,14,255,4,0,4,28,255,2,222,0,5,250,255,2,11,0,4,248,255,3,14,0,5,16,255,4,0,4,78,255,2,187,0,5,238,255,2,34,0,4,229,255,3,43,0,5,45,255,4,0,4,164,255,2,118,0,5,208,255,2,61,0,4,181,255,3,109,0,5,113,255,4,0,3,46,252,255,1,250,33,0,5,164,255,2,125,0,4,107,255,3,220,8,0,3,10,224,255,4,0,2,29,220,255,2,149,0,6,105,255,2,198,0,4,13,237,255,3,187,45,6,47,192,255,5,30,115,236,255,2,208,13,0,6,27,253,255,1,253,41,0,4,87,255,8,240,255,8,206,26,0,8,184,255,2,171,0,5,107,251,255,5,251,89,255,6,242,133,6,0,9,66,255,2,254,64,0,5,44,158,225,249,232,174,56,0,1,255,2,250,223,168,103,14,0,12,178,255,2,234,30,0,12,23,12,0,17,27,235,255,2,210,28,0,31,67,249,255,2,231,57,0,13,15,165,91,0,15,81,248,255,2,252,158,29,0,9,10,107,227,255,1,241,32,0,15,64,234,255,3,249,185,109,52,28,6,18,41,91,161,236,255,4,188,0,16,25,174,255,16,176,32,0,18,63,184,253,255,10,248,165,59,0,22,27,105,166,209,239,251,245,225,201,147,86,18,0,120, +43,28,0,233,77,255,8,76,0,18,174,255,8,173,0,17,21,250,255,8,250,20,0,16,113,255,10,112,0,16,210,255,10,209,0,15,52,255,12,51,0,14,149,255,5,214,214,255,5,148,0,13,8,238,255,5,117,118,255,5,238,7,0,12,88,255,5,252,24,25,252,255,5,87,0,12,185,255,5,179,0,2,180,255,5,184,0,11,29,253,255,5,81,0,2,82,255,5,253,28,0,10,124,255,5,235,5,0,2,5,235,255,5,123,0,10,220,255,5,143,0,4,144,255,5,219,0,9,63,255,6,46,0,4,47,255,6,62,0,8,160,255,5,205,0,6,206,255,5,159,0,7,13,244,255,5,108,0,6,109,255,5,244,12,0,6,99,255,20,98,0,6,196,255,20,195,0,5,38,255,22,37,0,4,135,255,22,134,0,3,3,229,255,22,228,3,0,2,74,255,6,36,0,10,36,255,6,73,0,2,171,255,5,193,0,12,194,255,5,170,0,1,19,249,255,5,95,0,12,96,255,5,248,18,110,255,5,242,11,0,12,11,243,255,5,109,207,255,5,156,0,14,157,255,5,207,0,252, +43,27,0,219,255,10,253,244,233,209,174,126,55,1,0,9,255,17,211,74,0,8,255,18,254,108,0,7,255,19,250,46,0,6,255,20,151,0,6,255,6,0,5,16,43,123,240,255,5,218,0,6,255,6,0,8,69,255,5,244,0,6,255,6,0,8,7,255,5,239,0,6,255,6,0,8,67,255,5,208,0,6,255,6,0,5,15,42,120,238,255,5,125,0,6,255,19,226,20,0,6,255,18,213,44,0,7,255,18,143,17,0,7,255,19,228,41,0,6,255,20,218,7,0,5,255,6,0,5,4,21,67,159,255,6,104,0,5,255,6,0,9,119,255,5,189,0,5,255,6,0,9,16,255,5,235,0,5,255,6,0,9,17,255,5,251,0,5,255,6,0,9,122,255,5,235,0,5,255,6,0,5,4,21,67,161,255,6,200,0,5,255,21,122,0,5,255,20,231,16,0,5,255,19,240,60,0,6,255,18,180,38,0,7,255,12,248,236,213,171,117,36,0,249, +43,26,0,218,47,110,171,209,228,246,248,233,205,158,99,21,0,11,3,86,203,255,11,253,179,62,0,8,30,197,255,16,0,7,62,237,255,17,0,6,41,239,255,18,0,5,9,221,255,6,252,167,88,33,12,6,21,47,95,155,234,255,2,0,5,118,255,6,211,41,0,9,3,80,197,0,4,11,237,255,5,209,12,0,17,77,255,5,251,37,0,18,149,255,5,162,0,19,202,255,5,83,0,19,224,255,5,30,0,19,245,255,5,9,0,19,246,255,5,8,0,19,225,255,5,29,0,19,203,255,5,81,0,19,150,255,5,161,0,19,78,255,5,251,36,0,18,11,238,255,5,207,11,0,18,119,255,6,208,38,0,9,3,79,197,0,5,9,222,255,6,252,166,86,32,11,5,20,46,94,155,234,255,2,0,6,42,239,255,18,0,7,63,237,255,17,0,8,30,198,255,16,0,9,3,87,204,255,11,253,180,63,0,12,48,111,172,210,229,247,249,234,206,159,99,22,0,238, +43,30,0,243,255,7,250,245,239,224,208,184,152,111,57,4,0,13,255,16,235,147,34,0,11,255,18,251,136,7,0,9,255,20,202,25,0,8,255,21,216,21,0,7,255,6,0,3,9,23,48,96,157,241,255,7,192,2,0,6,255,6,0,8,9,124,247,255,6,104,0,6,255,6,0,10,52,239,255,5,228,5,0,5,255,6,0,11,71,255,6,66,0,5,255,6,0,12,180,255,5,141,0,5,255,6,0,12,92,255,5,199,0,5,255,6,0,12,34,255,5,223,0,5,255,6,0,12,11,255,5,245,0,5,255,6,0,12,12,255,5,244,0,5,255,6,0,12,36,255,5,222,0,5,255,6,0,12,95,255,5,198,0,5,255,6,0,12,184,255,5,140,0,5,255,6,0,11,75,255,6,66,0,5,255,6,0,10,55,241,255,5,228,5,0,5,255,6,0,8,10,126,248,255,6,104,0,6,255,6,0,3,8,22,47,95,158,242,255,7,192,2,0,6,255,21,217,22,0,7,255,20,202,25,0,8,255,18,250,135,7,0,9,255,16,234,145,32,0,11,255,7,252,247,240,225,208,183,152,110,55,3,0,255,0,25, +43,25,0,203,255,19,0,6,255,19,0,6,255,19,0,6,255,19,0,6,255,19,0,6,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,18,0,7,255,18,0,7,255,18,0,7,255,18,0,7,255,18,0,7,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,19,0,6,255,19,0,6,255,19,0,6,255,19,0,6,255,19,0,228, +43,25,0,203,255,18,0,7,255,18,0,7,255,18,0,7,255,18,0,7,255,18,0,7,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,17,0,8,255,17,0,8,255,17,0,8,255,17,0,8,255,17,0,8,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,241, +43,30,0,250,29,92,151,200,220,237,251,243,230,206,166,124,59,4,0,14,66,178,254,255,12,241,155,54,0,10,18,174,255,18,0,9,48,228,255,19,0,8,32,231,255,20,0,7,5,214,255,7,198,110,53,23,6,10,24,48,90,137,205,254,255,2,0,7,110,255,6,223,63,0,11,21,110,207,0,6,9,235,255,5,217,19,0,21,74,255,5,252,43,0,22,147,255,5,165,0,23,201,255,5,84,0,23,224,255,5,30,0,8,255,10,0,5,245,255,5,10,0,8,255,10,0,5,245,255,5,9,0,8,255,10,0,5,224,255,5,31,0,8,255,10,0,5,202,255,5,85,0,8,255,10,0,5,148,255,5,168,0,12,255,6,0,5,76,255,5,253,48,0,11,255,6,0,5,10,236,255,5,222,24,0,10,255,6,0,6,114,255,6,228,72,0,9,255,6,0,6,7,218,255,7,205,115,59,24,8,5,18,45,97,255,6,0,7,37,235,255,21,0,8,55,233,255,20,0,9,24,187,255,17,253,179,0,10,1,79,194,255,13,221,140,35,0,14,40,104,165,207,226,245,251,238,224,194,151,107,41,0,255,0,22, +43,30,0,243,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,24,0,6,255,24,0,6,255,24,0,6,255,24,0,6,255,24,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,6,255,6,0,12,255,6,0,255,0,18, +43,13,0,107,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,121, +43,15,0,125,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,9,255,6,0,8,2,255,5,253,0,8,22,255,5,237,0,8,79,255,5,217,0,7,4,198,255,5,167,0,5,9,60,189,255,6,104,0,4,255,9,240,13,0,4,255,9,115,0,5,255,8,162,0,6,255,6,239,112,0,7,255,1,252,236,215,165,101,12,0,38, +43,29,0,235,255,6,0,9,38,223,255,6,254,115,0,4,255,6,0,8,54,235,255,6,250,91,0,5,255,6,0,7,73,244,255,6,243,70,0,6,255,6,0,6,95,251,255,6,234,52,0,7,255,6,0,5,120,255,7,222,37,0,8,255,6,0,3,1,145,255,7,207,24,0,9,255,6,0,2,6,168,255,7,190,14,0,10,255,6,0,1,14,189,255,7,170,6,0,11,255,6,24,207,255,7,147,2,0,12,255,6,222,255,7,122,0,14,255,12,251,97,0,15,255,11,245,76,0,16,255,11,169,1,0,16,255,12,157,3,0,15,255,13,171,6,0,14,255,6,248,255,7,184,10,0,13,255,6,66,243,255,7,197,16,0,12,255,6,0,1,53,236,255,7,208,23,0,11,255,6,0,2,41,227,255,7,218,31,0,10,255,6,0,3,31,217,255,7,227,40,0,9,255,6,0,4,22,206,255,7,234,50,0,8,255,6,0,5,14,193,255,7,241,61,0,7,255,6,0,6,8,178,255,7,246,74,0,6,255,6,0,7,4,162,255,7,250,88,0,5,255,6,0,8,1,145,255,7,253,103,0,4,255,6,0,10,127,255,8,119,0,255,0,6, +43,23,0,187,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,6,0,17,255,19,0,4,255,19,0,4,255,19,0,4,255,19,0,4,255,19,0,208, +43,36,0,255,0,36,255,7,121,0,13,121,255,7,0,7,255,7,229,5,0,11,6,230,255,7,0,7,255,8,94,0,11,95,255,8,0,7,255,8,208,0,11,209,255,8,0,7,255,9,68,0,9,69,255,9,0,7,255,9,183,0,9,184,255,9,0,7,255,10,43,0,7,44,255,10,0,7,255,6,197,255,3,157,0,7,158,255,3,196,255,6,0,7,255,6,82,255,3,248,23,0,5,23,248,255,3,81,255,6,0,7,255,6,2,221,255,3,130,0,5,131,255,3,220,2,255,6,0,7,255,6,0,1,109,255,3,235,9,0,3,9,236,255,3,108,0,1,255,6,0,7,255,6,0,1,11,238,255,3,104,0,3,105,255,3,238,11,0,1,255,6,0,7,255,6,0,2,135,255,3,217,1,0,1,2,218,255,3,134,0,2,255,6,0,7,255,6,0,2,26,250,255,3,78,0,1,79,255,3,250,26,0,2,255,6,0,7,255,6,0,3,162,255,3,193,0,1,194,255,3,161,0,3,255,6,0,7,255,6,0,3,47,255,4,105,255,4,47,0,3,255,6,0,7,255,6,0,4,188,255,3,253,255,3,187,0,4,255,6,0,7,255,6,0,4,74,255,7,73,0,4,255,6,0,7,255,6,0,4,1,214,255,5,213,1,0,4,255,6,0,7,255,6,0,5,100,255,5,99,0,5,255,6,0,7,255,6,0,5,8,233,255,3,233,7,0,5,255,6,0,7,255,6,0,17,255,6,0,7,255,6,0,17,255,6,0,7,255,6,0,17,255,6,0,7,255,6,0,17,255,6,0,7,255,6,0,17,255,6,0,255,0,73, +43,30,0,243,255,7,139,0,10,255,6,0,6,255,7,246,26,0,9,255,6,0,6,255,8,152,0,9,255,6,0,6,255,8,251,35,0,8,255,6,0,6,255,9,165,0,8,255,6,0,6,255,9,254,46,0,7,255,6,0,6,255,6,207,255,3,179,0,7,255,6,0,6,255,6,71,255,4,57,0,6,255,6,0,6,255,6,0,1,188,255,3,192,0,6,255,6,0,6,255,6,0,1,51,254,255,3,71,0,5,255,6,0,6,255,6,0,2,168,255,3,204,1,0,4,255,6,0,6,255,6,0,2,35,250,255,3,84,0,4,255,6,0,6,255,6,0,3,147,255,3,215,3,0,3,255,6,0,6,255,6,0,3,22,242,255,3,97,0,3,255,6,0,6,255,6,0,4,127,255,3,225,7,0,2,255,6,0,6,255,6,0,4,12,232,255,3,111,0,2,255,6,0,6,255,6,0,5,106,255,3,233,12,0,1,255,6,0,6,255,6,0,5,4,219,255,3,124,0,1,255,6,0,6,255,6,0,6,85,255,3,240,18,255,6,0,6,255,6,0,7,202,255,3,137,255,6,0,6,255,6,0,7,65,255,3,246,255,6,0,6,255,6,0,8,183,255,9,0,6,255,6,0,8,46,253,255,8,0,6,255,6,0,9,162,255,8,0,6,255,6,0,9,31,248,255,7,0,6,255,6,0,10,141,255,7,0,255,0,18, +43,31,0,255,0,2,9,73,136,193,218,236,251,236,218,194,136,73,10,0,16,17,125,235,255,11,235,125,17,0,13,63,231,255,15,231,62,0,11,101,252,255,17,251,101,0,9,71,252,255,19,252,69,0,7,23,240,255,6,232,126,55,20,5,21,56,127,233,255,6,239,22,0,6,142,255,6,163,10,0,7,11,167,255,6,141,0,5,19,246,255,5,169,0,10,1,173,255,5,246,19,0,4,89,255,5,240,16,0,11,17,242,255,5,87,0,4,156,255,5,143,0,13,145,255,5,154,0,4,206,255,5,73,0,13,75,255,5,204,0,4,226,255,5,26,0,13,28,255,5,225,0,4,246,255,5,8,0,13,10,255,5,245,0,4,246,255,5,8,0,13,8,255,5,246,0,4,227,255,5,26,0,13,27,255,5,225,0,4,206,255,5,72,0,13,74,255,5,205,0,4,158,255,5,142,0,13,144,255,5,156,0,4,90,255,5,239,14,0,11,16,241,255,5,88,0,4,20,247,255,5,166,0,11,170,255,5,247,20,0,5,144,255,6,160,9,0,7,10,164,255,6,143,0,6,24,241,255,6,230,124,54,19,4,19,55,125,232,255,6,240,23,0,7,74,253,255,19,252,73,0,9,105,252,255,17,252,104,0,11,66,233,255,15,233,66,0,13,18,128,236,255,11,236,128,19,0,16,10,75,138,195,219,238,251,238,220,195,138,75,10,0,255,0,33, +43,26,0,211,255,12,243,227,195,144,73,2,0,8,255,17,225,85,0,7,255,19,152,1,0,5,255,20,129,0,5,255,20,249,35,0,4,255,6,0,5,9,38,101,213,255,6,132,0,4,255,6,0,8,7,184,255,5,202,0,4,255,6,0,9,44,255,5,233,0,4,255,6,0,9,7,255,5,250,0,4,255,6,0,9,45,255,5,233,0,4,255,6,0,8,7,185,255,5,202,0,4,255,6,0,5,8,37,100,213,255,6,131,0,4,255,20,249,35,0,4,255,20,129,0,5,255,19,152,1,0,5,255,17,226,87,0,7,255,12,244,229,197,146,73,2,0,8,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,251, +43,31,0,255,0,2,9,73,136,193,218,236,251,237,219,195,139,78,12,0,16,17,125,235,255,11,239,134,23,0,13,63,231,255,15,238,75,0,11,101,252,255,17,254,118,0,9,71,252,255,19,254,85,0,7,23,240,255,6,232,126,55,20,5,21,56,127,233,255,6,246,30,0,6,142,255,6,163,10,0,7,11,167,255,6,153,0,5,19,246,255,5,169,0,10,1,173,255,5,250,24,0,4,88,255,5,240,16,0,11,17,242,255,5,93,0,4,156,255,5,143,0,13,145,255,5,160,0,4,205,255,5,73,0,13,75,255,5,207,0,4,226,255,5,26,0,13,28,255,5,226,0,4,246,255,5,8,0,13,10,255,5,246,0,4,246,255,5,7,0,13,8,255,5,248,0,4,227,255,5,24,0,13,27,255,5,235,0,4,207,255,5,68,0,13,74,255,5,208,0,4,160,255,5,134,0,13,144,255,5,166,0,4,92,255,5,233,9,0,11,16,241,255,5,104,0,4,22,248,255,5,150,0,11,170,255,5,253,28,0,5,148,255,6,142,5,0,7,10,164,255,6,175,0,6,27,243,255,6,223,117,50,18,4,19,55,125,232,255,6,252,49,0,7,79,253,255,20,136,0,9,108,253,255,18,177,3,0,10,67,233,255,16,156,6,0,12,18,126,233,255,12,229,78,0,16,8,71,132,190,217,235,251,255,6,241,36,0,23,78,253,255,5,207,8,0,23,108,255,6,154,0,24,140,255,6,91,0,23,1,170,255,5,244,40,0,23,8,196,255,5,212,10,0,127, +43,28,0,227,255,10,251,240,226,191,147,74,7,0,11,255,16,227,90,0,10,255,18,121,0,9,255,18,252,50,0,8,255,19,154,0,8,255,6,0,3,2,14,49,127,247,255,5,219,0,8,255,6,0,7,93,255,5,244,0,8,255,6,0,7,12,255,5,248,0,8,255,6,0,7,14,255,5,222,0,8,255,6,0,7,97,255,5,162,0,8,255,6,0,3,1,13,49,129,248,255,5,58,0,8,255,18,143,0,9,255,16,248,123,1,0,9,255,15,231,51,0,11,255,16,242,66,0,10,255,17,244,38,0,9,255,6,0,2,7,33,95,208,255,6,186,0,9,255,6,0,5,6,183,255,6,62,0,8,255,6,0,6,19,234,255,5,179,0,8,255,6,0,7,112,255,5,254,37,0,7,255,6,0,7,12,240,255,5,148,0,7,255,6,0,8,140,255,5,244,16,0,6,255,6,0,8,31,252,255,5,117,0,6,255,6,0,9,170,255,5,225,3,0,5,255,6,0,9,57,255,6,86,0,5,255,6,0,10,200,255,5,199,0,254, +43,26,0,215,27,115,172,217,237,250,249,239,221,193,160,115,68,13,0,10,21,159,253,255,12,252,203,0,8,34,224,255,16,0,7,5,212,255,17,0,7,101,255,18,0,7,186,255,5,243,127,51,15,3,12,30,72,121,190,250,255,2,0,7,231,255,5,73,0,9,15,103,205,0,7,250,255,5,11,0,19,236,255,5,97,0,19,188,255,5,253,172,91,34,1,0,15,96,255,9,244,206,166,120,59,6,0,10,3,192,255,13,239,155,42,0,9,16,182,255,14,253,143,5,0,8,1,84,204,255,14,164,0,11,40,111,171,219,253,255,10,71,0,14,9,51,106,174,248,255,6,170,0,18,22,195,255,5,226,0,19,40,255,5,248,0,5,193,67,0,12,12,255,5,241,0,5,255,2,199,97,13,0,9,110,255,5,217,0,5,255,4,245,176,117,61,31,11,3,21,67,159,254,255,5,158,0,5,255,20,70,0,5,255,19,181,0,6,255,18,201,15,0,6,62,145,225,255,13,243,126,8,0,10,33,93,144,181,216,235,247,252,241,227,195,151,85,9,0,240, +43,25,0,200,255,24,0,1,255,24,0,1,255,24,0,1,255,24,0,1,255,24,0,10,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,235, +43,29,0,235,255,6,0,11,255,6,0,6,255,6,0,11,255,6,0,6,255,6,0,11,255,6,0,6,255,6,0,11,255,6,0,6,255,6,0,11,255,6,0,6,255,6,0,11,255,6,0,6,255,6,0,11,255,6,0,6,255,6,0,11,255,6,0,6,255,6,0,11,255,6,0,6,255,6,0,11,255,6,0,6,255,6,0,11,255,6,0,6,255,6,0,11,255,6,0,6,255,6,0,11,255,6,0,6,255,6,0,11,255,6,0,6,255,6,0,11,255,6,0,6,250,255,5,4,0,9,4,255,5,249,0,6,233,255,5,20,0,9,21,255,5,232,0,6,213,255,5,63,0,9,65,255,5,212,0,6,164,255,5,146,0,9,149,255,5,162,0,6,101,255,5,249,57,0,7,60,250,255,5,101,0,6,18,244,255,5,247,139,56,15,3,15,57,141,248,255,5,243,18,0,7,139,255,19,138,0,8,9,205,255,17,203,9,0,9,26,201,255,15,201,26,0,11,5,125,234,255,11,233,125,5,0,14,7,84,144,198,226,241,252,241,225,197,144,84,6,0,255,0,14, +43,28,0,224,207,255,5,156,0,14,157,255,5,207,110,255,5,242,10,0,12,11,243,255,5,109,19,249,255,5,94,0,12,95,255,5,249,19,0,1,171,255,5,192,0,12,193,255,5,170,0,2,74,255,5,254,34,0,10,35,254,255,5,73,0,2,3,229,255,5,130,0,10,131,255,5,228,3,0,3,135,255,5,225,2,0,8,2,226,255,5,134,0,4,38,255,6,69,0,8,70,255,6,37,0,5,196,255,5,166,0,8,167,255,5,195,0,6,99,255,5,247,16,0,6,17,247,255,5,98,0,6,13,244,255,5,105,0,6,106,255,5,244,12,0,7,160,255,5,202,0,6,203,255,5,159,0,8,63,255,6,43,0,4,44,255,6,62,0,8,1,220,255,5,141,0,4,142,255,5,220,1,0,9,124,255,5,233,4,0,2,5,234,255,5,123,0,10,29,253,255,5,79,0,2,80,255,5,253,28,0,11,185,255,5,177,0,2,178,255,5,184,0,12,88,255,5,251,23,23,251,255,5,87,0,12,8,238,255,5,115,116,255,5,238,7,0,13,149,255,5,212,213,255,5,148,0,14,52,255,12,51,0,15,210,255,10,209,0,16,113,255,10,112,0,16,21,250,255,8,250,21,0,17,174,255,8,173,0,18,77,255,8,76,0,255,0,6, +43,40,0,255,0,66,225,255,5,75,0,8,103,255,6,114,0,8,75,255,5,225,0,2,163,255,5,137,0,8,165,255,6,176,0,8,137,255,5,163,0,2,101,255,5,199,0,8,226,255,6,236,1,0,7,199,255,5,101,0,2,38,255,5,250,10,0,6,32,255,3,245,240,255,3,44,0,6,10,250,255,5,39,0,3,231,255,5,68,0,6,94,255,3,188,181,255,3,105,0,6,66,255,5,233,0,4,170,255,5,130,0,6,156,255,3,127,119,255,3,167,0,6,128,255,5,172,0,4,107,255,5,192,0,6,217,255,3,65,57,255,3,228,0,6,190,255,5,110,0,4,45,255,5,247,7,0,4,23,255,3,250,9,5,246,255,3,35,0,4,5,246,255,5,49,0,4,1,237,255,5,61,0,4,85,255,3,198,0,2,190,255,3,96,0,4,57,255,5,240,2,0,5,176,255,5,123,0,4,146,255,3,137,0,2,128,255,3,158,0,4,119,255,5,181,0,6,114,255,5,185,0,4,208,255,3,76,0,2,66,255,3,220,0,4,181,255,5,120,0,6,52,255,5,243,4,0,2,16,253,255,2,253,16,0,2,10,250,255,3,26,0,2,2,240,255,5,58,0,6,3,242,255,5,54,0,2,75,255,3,209,0,4,199,255,3,87,0,2,48,255,5,246,6,0,7,183,255,5,116,0,2,137,255,3,147,0,4,137,255,3,149,0,2,110,255,5,191,0,8,121,255,5,178,0,2,199,255,3,86,0,4,75,255,3,211,0,2,172,255,5,129,0,8,59,255,5,238,2,9,250,255,3,24,0,4,15,253,255,2,254,18,0,1,233,255,5,67,0,8,6,246,255,5,47,66,255,3,219,0,6,207,255,3,79,40,255,5,250,10,0,9,190,255,5,109,128,255,3,157,0,6,146,255,3,140,101,255,5,200,0,10,128,255,5,171,189,255,3,96,0,6,84,255,3,202,163,255,5,138,0,10,65,255,5,234,245,255,3,35,0,6,22,255,3,251,230,255,5,76,0,10,9,249,255,8,229,0,8,216,255,8,253,17,0,11,197,255,8,168,0,8,154,255,8,209,0,12,134,255,8,106,0,8,93,255,8,147,0,12,72,255,8,45,0,8,31,255,8,86,0,12,13,252,255,6,237,1,0,9,225,255,7,24,0,13,203,255,6,178,0,10,163,255,6,218,0,255,0,112, +43,28,0,225,167,255,6,168,0,10,169,255,6,166,0,2,17,228,255,6,91,0,8,92,255,6,227,16,0,3,68,254,255,5,240,29,0,6,30,240,255,5,254,67,0,5,144,255,6,190,1,0,4,2,191,255,6,143,0,6,8,213,255,6,115,0,4,116,255,6,212,8,0,7,48,250,255,5,248,44,0,2,45,248,255,5,249,48,0,9,121,255,6,209,6,6,209,255,6,120,0,10,2,195,255,6,138,139,255,6,195,2,0,11,33,242,255,5,253,253,255,5,242,32,0,13,97,255,12,96,0,15,174,255,10,174,0,16,20,232,255,8,231,20,0,17,86,255,8,86,0,18,163,255,8,162,0,17,78,255,10,78,0,15,18,231,255,10,231,18,0,14,165,255,12,164,0,13,80,255,6,218,219,255,6,79,0,11,19,232,255,5,253,60,60,253,255,5,231,18,0,10,167,255,6,142,0,2,143,255,6,166,0,9,82,255,6,217,9,0,2,9,218,255,6,81,0,7,20,233,255,5,253,57,0,4,60,253,255,5,232,19,0,6,169,255,6,139,0,6,142,255,6,168,0,5,84,255,6,215,8,0,6,9,218,255,6,83,0,3,21,234,255,5,252,55,0,8,59,253,255,5,233,20,0,2,170,255,6,137,0,10,141,255,6,170,0,253, +43,26,0,208,171,255,6,136,0,10,137,255,6,170,21,235,255,5,252,53,0,8,54,252,255,5,234,21,0,1,87,255,6,212,6,0,6,7,213,255,6,86,0,3,173,255,6,134,0,6,135,255,6,172,0,4,22,236,255,5,251,51,0,4,52,252,255,5,235,22,0,5,89,255,6,211,6,0,2,6,212,255,6,88,0,7,175,255,6,132,0,2,133,255,6,174,0,8,23,236,255,5,251,50,51,251,255,5,236,23,0,9,90,255,6,210,210,255,6,89,0,11,176,255,12,175,0,12,24,237,255,10,237,23,0,13,92,255,10,91,0,15,178,255,8,177,0,16,25,238,255,6,238,24,0,17,93,255,6,92,0,19,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,244, +43,26,0,210,255,23,0,3,255,23,0,3,255,23,0,3,255,23,0,3,255,22,163,0,16,50,245,255,6,195,6,0,15,27,229,255,6,221,20,0,15,10,205,255,6,239,40,0,15,1,174,255,6,251,67,0,16,136,255,7,101,0,16,95,255,7,140,0,16,61,249,255,6,177,2,0,15,34,235,255,6,206,11,0,15,15,214,255,6,229,27,0,15,4,186,255,6,245,50,0,16,150,255,6,253,80,0,16,110,255,7,117,0,16,73,252,255,6,155,0,16,43,241,255,6,189,5,0,15,22,223,255,6,216,17,0,15,7,197,255,6,236,36,0,16,164,255,22,0,3,255,23,0,3,255,23,0,3,255,23,0,3,255,23,0,235, +43,16,0,115,255,11,0,5,255,11,0,5,255,11,0,5,255,11,0,5,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,11,0,5,255,11,0,5,255,11,0,5,255,11,0,66, +43,13,0,104,217,255,2,245,9,0,8,139,255,3,77,0,8,61,255,3,155,0,8,3,235,255,2,231,1,0,8,161,255,3,55,0,8,83,255,3,133,0,8,12,248,255,2,211,0,9,183,255,3,33,0,8,105,255,3,112,0,8,27,255,3,190,0,9,205,255,2,251,16,0,8,127,255,3,90,0,8,49,255,3,168,0,9,226,255,2,240,5,0,8,149,255,3,68,0,8,71,255,3,147,0,8,6,242,255,2,224,0,9,171,255,3,47,0,8,93,255,3,125,0,8,18,252,255,2,203,0,9,193,255,2,254,26,0,8,115,255,3,103,0,8,37,255,3,181,0,9,215,255,2,248,11,0,8,137,255,3,82,0,8,59,255,3,160,0,8,2,234,255,2,235,2,0,8,159,255,3,60,0,8,81,255,3,138,0,8,11,247,255,2,216,0,65, +43,16,0,114,255,11,0,5,255,11,0,5,255,11,0,5,255,11,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,5,255,11,0,5,255,11,0,5,255,11,0,5,255,11,0,67, +43,30,0,252,50,241,255,3,241,49,0,22,39,234,255,5,234,39,0,20,30,226,255,7,226,30,0,18,22,217,255,9,217,22,0,16,15,207,255,5,200,255,5,206,15,0,14,9,195,255,4,242,88,0,1,87,242,255,4,195,9,0,12,5,182,255,4,207,37,0,3,36,205,255,4,181,5,0,10,2,168,255,4,151,7,0,5,7,148,255,4,167,2,0,9,153,255,3,241,86,0,9,83,240,255,3,152,0,8,137,255,3,206,36,0,11,33,202,255,3,136,0,255,0,255,0,243, +43,18,0,255,0,255,0,192,255,54,0,18, +43,18,0,92,137,255,4,95,0,13,154,255,3,242,34,0,12,2,169,255,3,198,3,0,12,5,182,255,3,129,0,13,9,195,255,2,252,59,0,13,15,207,255,2,223,14,0,13,22,217,255,2,164,0,255,0,255,0,54, +43,24,0,255,0,85,23,71,119,167,202,228,243,252,247,234,216,173,122,36,0,10,255,14,180,31,0,8,255,15,235,39,0,7,255,16,211,2,0,6,255,2,201,117,64,26,10,4,19,57,138,249,255,5,84,0,6,185,47,0,9,90,255,5,161,0,17,8,255,5,216,0,7,10,80,150,192,226,240,251,255,9,238,0,6,107,234,255,15,253,0,5,147,255,18,0,4,71,255,19,0,4,173,255,5,241,119,44,11,0,3,1,255,6,0,4,228,255,5,72,0,6,25,255,6,0,4,249,255,5,9,0,6,108,255,6,0,4,236,255,5,67,0,5,46,239,255,6,0,4,185,255,5,231,93,25,5,35,122,244,255,7,0,4,85,255,19,0,5,174,255,11,124,255,6,0,5,8,160,255,8,238,99,0,1,255,6,0,7,57,158,219,245,247,226,189,115,21,0,2,255,6,0,218, +43,26,0,185,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,2,21,116,194,229,249,232,194,113,14,0,9,255,6,0,1,98,238,255,7,229,73,0,8,255,6,124,255,10,251,84,0,7,255,18,245,38,0,6,255,19,189,0,6,255,7,229,99,26,5,25,96,227,255,6,48,0,5,255,6,234,29,0,5,27,233,255,5,134,0,5,255,6,113,0,7,113,255,5,193,0,5,255,6,38,0,7,38,255,5,228,0,5,255,6,7,0,7,8,255,5,246,0,5,255,6,7,0,7,8,255,5,246,0,5,255,6,38,0,7,38,255,5,228,0,5,255,6,112,0,7,113,255,5,193,0,5,255,6,233,27,0,5,27,233,255,5,134,0,5,255,7,227,98,25,4,24,95,226,255,6,48,0,5,255,19,189,0,6,255,18,245,38,0,6,255,6,125,255,10,251,84,0,7,255,6,0,1,100,240,255,7,230,74,0,8,255,6,0,2,23,120,195,231,250,233,195,114,14,0,240, +43,21,0,255,0,46,6,84,147,202,227,245,246,221,164,79,2,0,8,2,114,231,255,9,219,76,0,6,20,192,255,13,0,5,10,200,255,14,0,5,144,255,15,0,4,31,249,255,6,173,74,23,5,19,64,145,243,255,1,0,4,118,255,5,253,93,0,7,20,169,0,4,188,255,5,151,0,14,224,255,5,51,0,14,245,255,5,10,0,14,245,255,5,10,0,14,224,255,5,51,0,14,188,255,5,150,0,14,118,255,5,253,89,0,7,14,163,0,4,31,249,255,6,170,73,21,4,18,58,134,238,255,1,0,5,144,255,15,0,5,10,201,255,14,0,6,20,193,255,13,0,7,2,116,231,255,9,221,77,0,9,6,85,149,204,229,246,247,222,165,80,3,0,192, +43,26,0,199,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,9,14,113,193,232,249,230,194,116,21,0,2,255,6,0,8,75,230,255,7,238,98,0,1,255,6,0,7,87,251,255,10,123,255,6,0,6,41,247,255,18,0,6,192,255,19,0,5,50,255,6,227,97,25,5,26,98,228,255,7,0,5,136,255,5,233,27,0,5,27,233,255,6,0,5,195,255,5,112,0,7,113,255,6,0,5,230,255,5,37,0,7,38,255,6,0,5,247,255,5,7,0,7,8,255,6,0,5,247,255,5,7,0,7,8,255,6,0,5,230,255,5,37,0,7,38,255,6,0,5,195,255,5,112,0,7,113,255,6,0,5,136,255,5,233,27,0,5,27,233,255,6,0,5,50,255,6,227,96,24,4,24,97,227,255,7,0,6,193,255,19,0,6,41,247,255,18,0,7,87,251,255,10,125,255,6,0,8,76,231,255,7,240,101,0,1,255,6,0,9,14,114,195,233,250,231,196,120,23,0,2,255,6,0,237, +43,24,0,255,0,88,9,92,155,209,231,248,242,221,179,112,24,0,11,3,122,236,255,9,246,140,8,0,8,22,195,255,13,202,25,0,6,10,201,255,15,202,8,0,5,145,255,5,246,129,41,7,14,74,206,255,5,140,0,4,31,249,255,4,252,67,0,5,9,203,255,4,246,24,0,3,118,255,5,146,0,7,69,255,5,112,0,3,188,255,5,60,0,7,11,255,5,179,0,3,225,255,19,221,0,3,246,255,19,242,0,3,246,255,20,0,3,225,255,20,0,3,190,255,5,25,0,17,120,255,5,109,0,17,34,251,255,4,238,45,0,9,15,103,205,0,5,150,255,5,245,137,55,15,3,15,33,75,123,191,251,255,2,0,5,13,206,255,17,0,6,24,198,255,16,0,7,3,120,233,255,14,0,9,6,85,148,202,228,245,253,246,236,216,192,159,122,76,25,0,218, +43,16,0,118,29,129,194,233,249,255,5,0,5,90,245,255,9,0,4,49,252,255,10,0,4,163,255,11,0,4,225,255,5,133,19,0,8,249,255,5,12,0,9,255,6,0,7,255,14,0,2,255,14,0,2,255,14,0,2,255,14,0,2,255,14,0,5,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,10,255,6,0,150, +43,26,0,255,0,115,11,110,192,232,249,230,196,119,23,0,2,255,6,0,8,67,226,255,7,240,101,0,1,255,6,0,7,78,249,255,10,125,255,6,0,6,35,243,255,18,0,6,186,255,19,0,5,46,255,6,233,103,26,5,27,104,233,255,7,0,5,133,255,5,238,35,0,5,35,239,255,6,0,5,193,255,5,119,0,7,119,255,6,0,5,229,255,5,40,0,7,41,255,6,0,5,247,255,5,8,0,7,9,255,6,0,5,247,255,5,7,0,7,8,255,6,0,5,229,255,5,36,0,7,40,255,6,0,5,192,255,5,108,0,7,118,255,6,0,5,132,255,5,229,23,0,5,33,237,255,6,0,5,45,255,6,224,94,23,4,26,102,232,255,7,0,6,185,255,19,0,6,35,243,255,18,0,7,78,249,255,10,124,255,6,0,8,68,226,255,7,240,100,5,255,5,248,0,9,12,110,193,233,250,231,196,120,23,0,1,26,255,5,227,0,19,79,255,5,198,0,19,183,255,5,129,0,7,189,58,0,9,120,255,5,254,45,0,7,255,2,209,120,61,22,5,10,36,92,193,255,6,160,0,8,255,16,212,19,0,8,255,15,200,24,0,9,255,13,225,110,2,0,10,32,95,146,189,219,241,251,250,236,221,184,133,68,2,0,34, +43,26,0,185,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,2,7,96,177,223,247,240,212,147,40,0,9,255,6,0,1,74,222,255,7,252,119,0,8,255,6,112,253,255,10,104,0,7,255,18,244,18,0,6,255,19,111,0,6,255,7,249,133,40,7,26,121,251,255,5,178,0,6,255,6,254,74,0,5,121,255,5,224,0,6,255,6,160,0,6,42,255,5,242,0,6,255,6,67,0,6,14,255,6,0,6,255,6,19,0,6,4,255,6,0,6,255,6,2,0,6,1,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,237, +43,12,0,87,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,30,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,111, +43,13,0,95,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,33,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,7,255,6,0,6,6,255,5,246,0,6,27,255,5,229,0,6,86,255,5,186,0,4,8,64,222,255,5,123,0,3,255,8,249,26,0,3,255,8,121,0,4,255,6,254,137,1,0,4,255,3,245,219,157,52,0,19, +43,25,0,178,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,19,255,6,0,6,38,228,255,6,118,0,4,255,6,0,5,40,230,255,5,252,100,0,5,255,6,0,4,43,232,255,5,249,83,0,6,255,6,0,3,45,234,255,5,243,68,0,7,255,6,0,2,47,235,255,5,236,54,0,8,255,6,0,1,50,237,255,5,228,42,0,9,255,6,53,239,255,5,218,31,0,10,255,6,240,255,5,207,22,0,11,255,11,194,15,0,12,255,11,116,0,13,255,12,110,0,12,255,6,241,255,6,112,0,11,255,6,60,244,255,6,114,0,10,255,6,0,1,66,247,255,6,115,0,9,255,6,0,2,74,249,255,6,117,0,8,255,6,0,3,82,251,255,6,119,0,7,255,6,0,4,89,253,255,6,121,0,6,255,6,0,5,98,254,255,6,123,0,5,255,6,0,6,106,255,7,125,0,4,255,6,0,7,115,255,7,127,0,225, +43,12,0,87,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,6,255,6,0,111, +43,38,0,255,0,255,0,25,255,6,0,2,30,128,206,237,244,214,156,52,0,5,48,149,212,243,242,216,151,45,0,9,255,6,0,1,109,244,255,7,142,4,0,1,8,147,255,7,253,124,0,8,255,6,130,255,10,149,5,189,255,10,108,0,7,255,17,254,193,255,11,245,19,0,6,255,31,112,0,6,255,7,228,90,18,15,77,233,255,7,229,91,18,16,83,236,255,5,179,0,6,255,6,242,35,0,4,99,255,6,245,37,0,4,93,255,5,224,0,6,255,6,134,0,5,40,255,6,139,0,5,20,255,5,243,0,6,255,6,55,0,5,13,255,6,60,0,6,252,255,5,0,6,255,6,15,0,5,4,255,6,18,0,6,252,255,5,0,6,255,6,2,0,6,255,6,2,0,6,255,6,0,6,255,6,0,7,255,6,0,7,255,6,0,6,255,6,0,7,255,6,0,7,255,6,0,6,255,6,0,7,255,6,0,7,255,6,0,6,255,6,0,7,255,6,0,7,255,6,0,6,255,6,0,7,255,6,0,7,255,6,0,6,255,6,0,7,255,6,0,7,255,6,0,6,255,6,0,7,255,6,0,7,255,6,0,6,255,6,0,7,255,6,0,7,255,6,0,6,255,6,0,7,255,6,0,7,255,6,0,255,0,90, +43,26,0,255,0,112,255,6,0,2,7,96,177,223,247,240,212,147,40,0,9,255,6,0,1,74,222,255,7,252,119,0,8,255,6,112,253,255,10,104,0,7,255,18,244,18,0,6,255,19,111,0,6,255,7,249,133,40,7,26,121,251,255,5,178,0,6,255,6,254,74,0,5,121,255,5,224,0,6,255,6,160,0,6,42,255,5,242,0,6,255,6,67,0,6,14,255,6,0,6,255,6,19,0,6,4,255,6,0,6,255,6,2,0,6,1,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,237, +43,25,0,255,0,102,6,86,149,204,228,246,247,229,206,151,89,8,0,11,2,116,232,255,10,233,119,3,0,8,20,192,255,14,194,22,0,6,9,200,255,16,201,9,0,5,144,255,18,145,0,4,31,249,255,5,244,127,44,10,8,42,126,243,255,5,249,31,0,3,118,255,5,243,48,0,6,48,244,255,5,117,0,3,188,255,5,126,0,8,126,255,5,187,0,3,225,255,5,42,0,8,43,255,5,223,0,3,246,255,5,8,0,8,9,255,5,245,0,3,246,255,5,8,0,8,9,255,5,245,0,3,225,255,5,42,0,8,43,255,5,223,0,3,188,255,5,125,0,8,125,255,5,187,0,3,118,255,5,243,46,0,6,45,243,255,5,117,0,3,31,249,255,5,243,125,43,8,8,41,123,242,255,5,249,31,0,4,145,255,18,145,0,5,10,201,255,16,201,10,0,6,20,192,255,14,195,22,0,8,2,117,232,255,10,234,120,3,0,11,7,86,150,205,230,247,248,231,207,153,90,8,0,231, +43,26,0,255,0,112,255,6,0,2,21,116,194,229,249,232,194,113,14,0,9,255,6,0,1,98,238,255,7,229,73,0,8,255,6,124,255,10,251,84,0,7,255,18,245,38,0,6,255,19,189,0,6,255,7,229,99,26,5,25,96,227,255,6,48,0,5,255,6,234,29,0,5,27,233,255,5,134,0,5,255,6,113,0,7,113,255,5,193,0,5,255,6,38,0,7,38,255,5,228,0,5,255,6,7,0,7,8,255,5,246,0,5,255,6,7,0,7,8,255,5,246,0,5,255,6,38,0,7,38,255,5,228,0,5,255,6,112,0,7,113,255,5,193,0,5,255,6,233,27,0,5,27,233,255,5,134,0,5,255,7,227,98,25,4,24,95,226,255,6,48,0,5,255,19,189,0,6,255,18,245,38,0,6,255,6,125,255,10,251,84,0,7,255,6,0,1,100,240,255,7,230,74,0,8,255,6,0,2,23,120,195,231,250,233,195,114,14,0,9,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,43, +43,26,0,255,0,115,14,113,193,232,249,230,196,119,23,0,2,255,6,0,8,75,230,255,7,240,101,0,1,255,6,0,7,87,251,255,10,125,255,6,0,6,41,247,255,18,0,6,193,255,19,0,5,51,255,6,227,97,25,5,26,98,228,255,7,0,5,136,255,5,233,27,0,5,27,233,255,6,0,5,195,255,5,112,0,7,113,255,6,0,5,230,255,5,37,0,7,38,255,6,0,5,247,255,5,7,0,7,8,255,6,0,5,247,255,5,7,0,7,8,255,6,0,5,230,255,5,37,0,7,38,255,6,0,5,194,255,5,112,0,7,113,255,6,0,5,134,255,5,233,27,0,5,27,233,255,6,0,5,50,255,6,227,96,24,4,24,97,227,255,7,0,6,192,255,19,0,6,41,247,255,18,0,7,87,251,255,10,125,255,6,0,8,76,231,255,7,240,101,0,1,255,6,0,9,14,114,195,233,250,231,196,120,23,0,2,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,20,255,6,0,29, +43,18,0,255,255,6,0,3,57,156,211,239,255,1,252,0,3,255,6,0,1,15,177,255,5,252,0,3,255,6,12,204,255,6,253,0,3,255,6,166,255,7,254,0,3,255,14,254,0,3,255,8,188,77,21,5,27,89,191,0,3,255,7,139,0,10,255,6,204,2,0,10,255,6,97,0,11,255,6,36,0,11,255,6,9,0,11,255,6,0,12,255,6,0,12,255,6,0,12,255,6,0,12,255,6,0,12,255,6,0,12,255,6,0,12,255,6,0,12,255,6,0,171, +43,21,0,255,0,44,44,125,181,218,239,250,250,241,221,196,162,123,76,25,0,5,20,176,255,14,0,4,11,216,255,15,0,4,129,255,16,0,4,212,255,5,212,80,25,5,18,46,98,163,240,255,2,0,4,245,255,5,35,0,7,6,87,200,0,4,244,255,4,254,14,0,14,206,255,5,178,53,6,0,12,118,255,7,251,223,187,148,99,39,0,7,8,198,255,12,215,108,4,0,5,11,136,241,255,12,189,7,0,6,10,81,144,193,231,255,9,117,0,11,10,45,100,211,255,5,206,0,14,31,255,5,244,0,3,199,85,5,0,8,38,255,5,242,0,3,255,2,240,167,104,57,25,9,6,26,85,215,255,5,202,0,3,255,17,108,0,3,255,16,186,3,0,3,255,14,246,138,5,0,4,25,76,122,160,194,218,238,248,253,245,233,204,164,98,21,0,193, +43,17,0,157,255,6,0,11,255,6,0,11,255,6,0,11,255,6,0,11,255,6,0,8,255,15,0,2,255,15,0,2,255,15,0,2,255,15,0,2,255,15,0,5,255,6,0,11,255,6,0,11,255,6,0,11,255,6,0,11,255,6,0,11,255,6,0,11,255,6,0,11,255,6,0,11,247,255,5,23,0,10,232,255,5,153,26,2,0,8,192,255,11,0,5,129,255,11,0,5,31,246,255,10,0,6,89,249,255,9,0,7,38,144,207,240,253,255,5,0,154, +43,26,0,255,0,112,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,6,0,8,255,6,0,6,255,5,254,0,8,255,6,0,6,255,5,253,0,8,255,6,0,6,255,5,252,0,8,255,6,0,6,255,5,250,0,8,255,6,0,6,255,5,248,0,8,255,6,0,6,255,5,248,0,8,255,6,0,6,255,5,248,0,7,2,255,6,0,6,255,5,253,0,7,19,255,6,0,6,255,6,9,0,6,66,255,6,0,6,244,255,5,40,0,6,158,255,6,0,6,225,255,5,122,0,5,71,253,255,6,0,6,179,255,5,250,118,25,6,38,130,248,255,7,0,6,112,255,19,0,6,19,244,255,18,0,7,105,255,10,254,113,255,6,0,8,119,252,255,7,225,77,0,1,255,6,0,9,39,146,212,241,248,226,180,101,9,0,2,255,6,0,237, +43,23,0,255,0,68,207,255,5,156,0,8,157,255,5,207,0,1,110,255,5,242,10,0,6,11,243,255,5,109,0,1,19,249,255,5,94,0,6,95,255,5,249,19,0,2,172,255,5,191,0,6,192,255,5,170,0,3,75,255,5,254,33,0,4,34,254,255,5,73,0,3,3,230,255,5,129,0,4,130,255,5,228,3,0,4,136,255,5,224,2,0,2,2,225,255,5,134,0,5,40,255,6,68,0,2,69,255,6,37,0,6,198,255,5,165,0,2,166,255,5,195,0,7,101,255,5,246,15,16,247,255,5,98,0,7,14,245,255,5,103,104,255,5,244,12,0,8,162,255,5,200,201,255,5,159,0,9,65,255,12,62,0,9,1,223,255,10,219,0,11,127,255,10,123,0,11,31,254,255,8,253,28,0,12,188,255,8,184,0,13,91,255,8,87,0,13,9,241,255,6,238,7,0,14,153,255,6,148,0,214, +43,33,0,255,0,208,222,255,5,85,0,5,71,255,5,71,0,5,86,255,5,222,0,2,155,255,5,151,0,5,134,255,5,133,0,5,153,255,5,155,0,2,88,255,5,218,0,5,196,255,5,195,0,5,220,255,5,88,0,2,22,254,255,5,29,0,3,9,249,255,5,249,8,0,3,31,255,5,254,22,0,3,210,255,5,96,0,3,65,255,7,64,0,3,98,255,5,210,0,4,143,255,5,163,0,3,127,255,7,127,0,3,165,255,5,143,0,4,76,255,5,229,0,3,189,255,3,192,255,3,189,0,3,231,255,5,76,0,4,13,251,255,5,40,0,1,5,245,255,3,67,255,3,246,5,0,1,43,255,5,251,13,0,5,198,255,5,107,0,1,58,255,3,227,0,1,227,255,3,58,0,1,110,255,5,198,0,6,131,255,5,174,0,1,120,255,3,165,0,1,165,255,3,121,0,1,177,255,5,131,0,6,64,255,5,238,2,182,255,3,102,0,1,102,255,3,183,3,240,255,5,64,0,6,7,246,255,5,54,241,255,3,40,0,1,40,255,3,242,59,255,5,246,7,0,7,186,255,5,169,255,3,233,0,3,232,255,3,175,255,5,186,0,8,119,255,5,254,255,3,172,0,3,171,255,9,119,0,8,52,255,9,110,0,3,108,255,9,52,0,8,2,238,255,8,47,0,3,46,255,8,238,2,0,9,174,255,7,239,2,0,3,1,237,255,7,174,0,10,107,255,7,179,0,5,177,255,7,107,0,10,40,255,7,117,0,5,114,255,7,40,0,11,229,255,6,55,0,5,51,255,6,229,0,255,0,48, +43,23,0,255,0,68,160,255,6,210,9,0,4,10,213,255,6,160,0,1,11,214,255,6,154,0,4,160,255,6,213,10,0,2,44,246,255,6,89,0,2,96,255,6,245,42,0,4,99,255,6,242,37,42,245,255,6,96,0,6,163,255,6,207,213,255,6,160,0,7,12,216,255,12,213,10,0,8,46,247,255,10,245,42,0,10,102,255,10,96,0,12,166,255,8,160,0,13,13,228,255,6,227,10,0,13,61,252,255,6,253,64,0,12,15,224,255,8,227,17,0,11,167,255,10,170,0,10,94,255,12,97,0,8,34,242,255,6,254,255,5,243,35,0,6,3,198,255,6,150,144,255,6,200,4,0,5,129,255,6,214,9,7,210,255,6,131,0,4,59,252,255,5,248,47,0,2,44,247,255,5,252,60,0,2,14,223,255,6,113,0,4,110,255,6,223,15,0,1,165,255,6,185,1,0,4,1,182,255,6,165,0,207, +43,23,0,255,0,67,203,255,5,176,0,8,147,255,5,209,0,1,98,255,5,252,29,0,6,5,236,255,5,115,0,1,9,238,255,5,132,0,6,80,255,5,252,24,0,2,142,255,5,232,5,0,5,174,255,5,183,0,3,37,254,255,5,88,0,4,18,249,255,5,89,0,4,186,255,5,194,0,4,107,255,5,241,9,0,4,81,255,6,44,0,3,201,255,5,157,0,5,3,227,255,5,150,0,2,39,255,6,63,0,6,125,255,5,242,13,0,1,133,255,5,223,1,0,6,24,250,255,5,105,1,225,255,5,131,0,8,169,255,5,210,66,255,6,37,0,8,64,255,6,210,255,5,199,0,10,213,255,11,105,0,10,108,255,10,249,17,0,10,14,243,255,9,173,0,12,152,255,9,79,0,12,47,255,8,235,5,0,13,197,255,7,147,0,14,91,255,7,53,0,14,6,234,255,5,214,0,15,3,231,255,5,121,0,15,77,255,5,253,29,0,14,2,193,255,5,183,0,13,14,51,169,255,6,72,0,12,228,255,8,194,0,13,228,255,7,244,39,0,13,228,255,6,238,65,0,14,228,255,3,243,207,133,23,0,35, +43,21,0,255,0,41,255,18,0,3,255,18,0,3,255,18,0,3,255,18,0,3,255,17,209,0,11,12,203,255,6,232,30,0,10,5,184,255,6,247,55,0,10,1,163,255,7,88,0,11,140,255,7,128,0,11,115,255,7,167,0,11,91,254,255,6,200,8,0,10,70,250,255,6,225,23,0,10,51,243,255,6,243,46,0,10,36,233,255,6,253,77,0,10,23,221,255,7,115,0,11,206,255,17,0,3,255,18,0,3,255,18,0,3,255,18,0,3,255,18,0,190, +43,26,0,194,13,104,179,225,244,255,4,0,16,54,227,255,8,0,15,20,234,255,9,0,15,127,255,10,0,15,199,255,5,228,85,23,4,0,16,238,255,5,89,0,19,253,255,5,27,0,19,255,6,5,0,19,255,6,0,20,255,6,0,19,3,255,6,0,19,18,255,5,251,0,19,61,255,5,237,0,19,155,255,5,200,0,16,7,42,143,255,6,128,0,15,255,9,217,17,0,15,255,7,234,136,17,0,16,255,7,254,193,52,0,16,255,9,245,41,0,16,8,40,133,252,255,5,156,0,19,138,255,5,217,0,19,47,255,5,245,0,19,11,255,5,254,0,20,255,6,0,20,255,6,0,20,255,6,3,0,19,254,255,5,21,0,19,240,255,5,77,0,19,206,255,5,221,77,21,3,0,16,136,255,10,0,15,25,240,255,9,0,16,62,232,255,8,0,17,16,110,182,226,245,255,4,0,83, +43,13,0,83,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,9,255,4,0,17, +43,26,0,187,255,4,244,224,179,104,13,0,17,255,8,226,54,0,16,255,9,234,19,0,15,255,10,126,0,16,4,23,85,229,255,5,199,0,19,90,255,5,237,0,19,28,255,5,253,0,19,7,255,6,0,20,255,6,0,20,255,6,0,20,255,6,3,0,19,252,255,5,17,0,19,238,255,5,59,0,19,201,255,5,151,0,19,129,255,5,254,141,42,7,0,16,17,218,255,9,0,16,17,135,234,255,7,0,16,51,192,254,255,7,0,15,41,244,255,9,0,15,157,255,5,252,132,39,8,0,16,218,255,5,135,0,19,246,255,5,46,0,19,255,6,9,0,19,255,6,0,20,255,6,0,19,3,255,6,0,19,22,255,5,254,0,19,78,255,5,239,0,16,3,21,77,221,255,5,206,0,15,255,10,135,0,15,255,9,240,25,0,15,255,8,232,62,0,16,255,4,245,226,182,110,16,0,90, +43,30,0,255,0,255,0,56,33,0,9,20,113,181,228,248,238,212,162,95,25,0,8,10,121,246,0,7,21,150,248,255,8,253,198,124,65,21,4,21,63,136,235,255,2,0,7,241,255,22,0,7,255,22,241,0,7,255,2,249,157,72,25,5,23,65,126,199,253,255,8,249,152,22,0,7,255,1,179,34,0,8,25,96,163,212,239,249,229,184,117,22,0,9,113,0,255,0,255,0,25, +43,22,0,200,255,18,0,4,255,18,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,2,0,14,255,2,0,4,255,18,0,4,255,18,0,46, +43,18,0,131,9,116,204,243,243,198,106,6,0,9,31,212,255,6,207,28,0,7,10,213,255,8,208,7,0,6,117,255,2,249,117,24,24,119,250,255,2,108,0,6,205,255,2,117,0,4,120,255,2,199,0,6,245,255,2,24,0,4,25,255,2,243,0,6,245,255,2,22,0,4,23,255,2,243,0,6,208,255,2,111,0,4,114,255,2,201,0,6,123,255,2,247,112,23,23,114,248,255,2,112,0,6,12,219,255,8,213,9,0,7,37,219,255,6,212,32,0,9,12,123,208,245,244,201,111,7,0,255,0,182, +43,24,0,134,95,255,4,136,0,17,34,242,255,3,153,0,17,4,199,255,3,168,2,0,17,130,255,3,182,5,0,17,60,252,255,2,194,9,0,17,15,223,255,2,206,15,0,18,165,255,2,217,21,0,64,9,92,155,209,231,248,242,221,179,112,24,0,11,3,122,236,255,9,246,140,8,0,8,22,195,255,13,202,25,0,6,10,201,255,15,202,8,0,5,145,255,5,246,129,41,7,14,74,206,255,5,140,0,4,31,249,255,4,252,67,0,5,9,203,255,4,246,24,0,3,118,255,5,146,0,7,69,255,5,112,0,3,188,255,5,60,0,7,11,255,5,179,0,3,225,255,19,221,0,3,246,255,19,242,0,3,246,255,20,0,3,225,255,20,0,3,190,255,5,25,0,17,120,255,5,109,0,17,34,251,255,4,238,45,0,9,15,103,205,0,5,150,255,5,245,137,55,15,3,15,33,75,123,191,251,255,2,0,5,13,206,255,17,0,6,24,198,255,16,0,7,3,120,233,255,14,0,9,6,85,148,202,228,245,253,246,236,216,192,159,122,76,25,0,218, +43,24,0,133,95,255,4,136,0,17,34,242,255,3,153,0,17,4,199,255,3,168,2,0,17,130,255,3,182,5,0,17,60,252,255,2,194,9,0,17,15,223,255,2,206,15,0,18,165,255,2,217,21,0,62,23,71,119,167,202,228,243,252,247,234,216,173,122,36,0,10,255,14,180,31,0,8,255,15,235,39,0,7,255,16,211,2,0,6,255,2,201,117,64,26,10,4,19,57,138,249,255,5,84,0,6,185,47,0,9,90,255,5,161,0,17,8,255,5,216,0,7,10,80,150,192,226,240,251,255,9,238,0,6,107,234,255,15,253,0,5,147,255,18,0,4,71,255,19,0,4,173,255,5,241,119,44,11,0,3,1,255,6,0,4,228,255,5,72,0,6,25,255,6,0,4,249,255,5,9,0,6,108,255,6,0,4,236,255,5,67,0,5,46,239,255,6,0,4,185,255,5,231,93,25,5,35,122,244,255,7,0,4,85,255,19,0,5,174,255,11,124,255,6,0,5,8,160,255,8,238,99,0,1,255,6,0,7,57,158,219,245,247,226,189,115,21,0,2,255,6,0,218, +}; + +unsigned char *ftv = fonttable; + +/* fonttable is obtained with: + + if(ch >= 32) { + printf("%d,%d,", txt->h, txt->w); + int rle_last = -1, rle_q = 0; + for(int j=0; j h;j++) { + for(int i=0; i < txt->w; i++) { + int c = (unsigned char) (qpixel(txt, i, j) >> 24); + if(c == rle_last && rle_q < 255) rle_q++; + else { + if(rle_last != -1) printf("%d,%d,", rle_last, rle_q); + if(c == 0 || c == 255) rle_last = c, rle_q = 1; + else { rle_last = -1; printf("%d,", c); } + } + } + } + if(rle_last != -1) printf("%d,%d,", rle_last, rle_q); + printf("\n"); + } + +*/ + +void loadCompressedChar(int &otwidth, int &otheight, int *tpix) { + otheight = *(ftv++); + otwidth = *(ftv++); + int left = otwidth * otheight; + while(left) { + int x = *(ftv++); + if(x == 0 || x == 255) { + x = x * 0x1010101; + int q = *(ftv++); + left -= q; + while(q--) *(tpix++) = x; + } + else { + *(tpix++) = (x << 24) | 0xFFFFFF; + left--; + } + } + } + diff --git a/init.cpp b/init.cpp new file mode 100644 index 00000000..f48e6bf6 --- /dev/null +++ b/init.cpp @@ -0,0 +1,541 @@ +#define VER "9.4c" +#define VERNUM 9403 +#define VERNUM_HEX 0x9403 + +#define GEN_M 0 +#define GEN_F 1 +#define GEN_N 2 +#define GEN_O 3 + +#ifdef MOBILE +#define MOBWEB +#endif + +#ifdef WEB +#define MOBWEB +#define ONEGRAPH +#endif + +#ifdef IOS +#define ONEGRAPH +#endif + +#ifdef MOBWEB +#define NORUG +#define NOEDIT +#define NOMODEL +#endif + +#ifdef MINI +#define NORUG +#define NOEDIT +#define NOMODEL +#define NOSAVE +#define NOCONFIG +#define NOTRANS +#endif + + +#ifdef MOBILE +#define EXTRALICENSE "\n\nHyperRogue 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/" +#undef XEXTRALICENSE + +#ifndef FAKEMOBILE +#define SDLK_F1 (123001) +#define SDLK_F2 (123002) +#define SDLK_F3 (123003) +#define SDLK_F4 (123004) +#define SDLK_F5 (123005) +#define SDLK_F6 (123006) +#define SDLK_F7 (123007) +#define SDLK_F10 (123010) +#define SDLK_ESCAPE (123099) +#define SDLK_F12 (123012) +#define SDLK_HOME (123013) +#define SDLK_LEFT (123014) +#define SDLK_RIGHT (123015) +#define MIX_MAX_VOLUME 128 +#define SDLK_UP (123021) +#define SDLK_DOWN (123022) +#define SDLK_PAGEUP (123023) +#define SDLK_PAGEDOWN (123024) +#define SDLK_RETURN (123025) +#define SDLK_KP1 (123031) +#define SDLK_KP2 (123032) +#define SDLK_KP3 (123033) +#define SDLK_KP4 (123034) +#define SDLK_KP5 (123035) +#define SDLK_KP6 (123036) +#define SDLK_KP7 (123037) +#define SDLK_KP8 (123038) +#define SDLK_KP9 (123039) +#define SDLK_KP_PERIOD (123051) +#define SDLK_DELETE (123052) +#define SDLK_DELETE (123052) +#endif + +int fontscale = 100; +bool buttonclicked; +void gdpush(int t); +#endif + +// desktop + +#include + +#ifdef USE_SDL +#include + +#ifndef MAC +#undef main +#endif + +#include +#endif + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef USE_UNORDERED_MAP +#include +#else +#define unordered_map map +#endif + +using namespace std; + +string s0; +void addMessage(string s, char spamtype = 0); + +#ifdef ANDROID +FILE *debfile; +#endif + +FILE *debugfile; +int debugflags; + +#ifdef USE_COMMANDLINE +const char *scorefile = "hyperrogue.log"; +const char *conffile = "hyperrogue.ini"; +string levelfile = "hyperrogue.lev"; +string picfile = "hyperrogue.pic"; +const char *musicfile = ""; +const char *loadlevel = NULL; +#endif + +#define S7 (sphere?5:7) +#define S42 (S7*6) +#define S14 (S7*2) +#define S21 (S7*3) +#define S28 (S7*4) +#define S84 (S7*12) + +#include "util.cpp" +#include "hyperpoint.cpp" +#include "patterns.cpp" +#include "classes.cpp" +#include "fieldpattern.cpp" +#include "heptagon.cpp" +#include "language.cpp" +#include "hyper.h" +#include "cell.cpp" +#include "flags.cpp" +#include "yendor.cpp" +#include "complex.cpp" +#include "game.cpp" +#include "landgen.cpp" +#include "orbs.cpp" +#include "system.cpp" +#include "geometry.cpp" +#include "polygons.cpp" +#include "mapeditor.cpp" +#ifndef MOBILE +#include "netgen.cpp" +#endif +#include "graph.cpp" +#include "sound.cpp" +#include "achievement.cpp" +#ifndef MOBILE +#include +#endif + +bool fixseed = false; + +void initAll() { + ca::init(); + arg::read(1); + srand(time(NULL)); + shrand(fixseed ? 0 : time(NULL)); + + achievement_init(); // not in ANDROID + + eLand f = firstland; + + // initlanguage(); + initgraph(); +#ifndef NOSAVE + loadsave(); +#endif + resetGeometry(); + initcells(); + + shmup::safety = safety; + initgame(); + restartGraph(); + + if(!shmup::on) { + restoreGolems(items[itOrbLife], moGolem); items[itOrbLife] = 0; + restoreGolems(items[itOrbFriend], moTameBomberbird); items[itOrbFriend] = 0; + restoreGolems(kills[moPrincessMoved], moPrincess, princess::saveHP); kills[moPrincessMoved] = 0; + restoreGolems(kills[moPrincessArmedMoved], moPrincessArmed, princess::saveArmedHP); kills[moPrincessArmedMoved] = 0; + } + + firstland = f; + } + +void finishAll() { + achievement_final(!items[itOrbSafety]); + +#ifndef NOSAVE + saveStats(); +#endif + offscreen.clear(); + clearMemory(); +#ifndef MOBILE + cleargraph(); +#endif + + achievement_close(); + } + +#ifdef ANDROID +string buildScoreDescription() { + string s; + time_t timer; + timer = time(NULL); + char buf[128]; strftime(buf, 128, "%c", localtime(&timer)); + char buf2[128]; + + s += XLAT("HyperRogue for Android"); + s += " ("VER"), http://www.roguetemple.com/z/hyper.php\n"; + s += XLAT("Date: %1 time: %2 s ", buf, its(savetime + time(NULL) - timerstart)); + s += XLAT("distance: %1\n", its(celldist(cwt.c))); + // s += buf2; + if(cheater) s += XLAT("Cheats: ") + its(cheater) + "\n"; + s += XLAT("Score: ") + its(gold()); + + for(int i=0; i 0) { + using namespace shmupballs; + int dx = mousex - xmove; + int dy = mousey - yb; + int h = hypot(dx, dy); + if(h < rad) { + if(h < rad*SKIPFAC) movepcto(MD_WAIT); + else { + double d = revcontrol ? -1 : 1; + mouseh = hpxy(dx * d / rad, dy * d / rad); + mousemovement(); + } + getcstat = 0; + return; + } + } + + if(buttonclicked || outofmap(mouseh)) { + + if(andmode == 0 && getcstat == 'g' && !shmup::on) { + movepcto(MD_DROP); + getcstat = 0; + } + + else if(getcstat != SDLK_F1) { + int px = mousex < vid.xcenter ? 0 : 1; + int py = mousey < vid.ycenter ? 0 : 1; + + if(cmode == (canmove ? emNormal : emQuit)) { + if(px == 0 && py == 1) { + if(andmode == 0 && shmup::on) ; + else andmode = 10; + } + if(px == 1 && py == 1) { + if(andmode == 0 && shmup::on) ; // just fire, do not change modes + else { + if(andmode == 1) { + centerpc(INF); + View = Id; + viewctr.h = cwt.c->master; + } + andmode = 11; + } + } + if(px == 0 && py == 0) andmode = 22; + if(px == 1 && py == 0) andmode = 13; + } + } + + else { + if(andmode == 0 && help != "@") { + addMessage(mouseovers); + showHelp(MOBPAR_ACTUAL, help); + andmode = 10; + getcstat = 0; + return; + } + } + } + + if(andmode == 0 && cmode == (canmove ? emNormal : emQuit) && !outofmap(mouseh)) { + + bool forcetarget = longclick; + + if(mouseover && targetclick && targetRangedOrb(mouseover, forcetarget ? roMouseForce : roMouse)) { + ; + } + else if(!forcetarget) movepcto(mousedest); + } + + if(andmode == 10) { + if(!playerfound) { + centerpc(INF); + View = Id; + viewctr.h = cwt.c->master; + } + playermoved = true; + } + + if(andmode >= 10) andmode -= 10; + + if(andmode == 3) cmode = emMenu, andmode = 0; + } + +int touchedAt; + +int getticks(); + +void mobile_draw(MOBPAR_FORMAL) { + + optimizeview(); + + int lastt = ticks; ticks = getticks(); + if(lastt > ticks) lastt = ticks; + int tdiff = ticks - lastt; + + if(playermoved && vid.sspeed > -4.99) + centerpc(tdiff / 1000.0 * exp(vid.sspeed)); + + if(shmup::on && (andmode == 0 || andmode == 10) && cmode == emNormal) + shmup::turn(tdiff); + + safety = false; + vid.fsize = (min(vid.xres, vid.yres) * fontscale + 50) / 3200; + + hyperpoint mouseoh = mouseh; + gtouched = mousepressed = clicked; + + longclick = lclicked && ticks > touchedAt + 500; + useRangedOrb = + longclick || (!(vid.shifttarget & 2) && haveRangedOrb() && lmouseover && lmouseover->cpdist > 1); + + targetclick = ((vid.shifttarget & 2) && !shmup::on) ? longclick : true; + + if(shmup::on) { + using namespace shmupballs; + if(hypot(mousex - xfire, mousey - yb) < rad) targetclick = false; + if(hypot(mousex - xmove, mousey - yb) < rad) targetclick = false; + } + + if(cmode == emNormal) { + lmouseover = (gtouched && lclicked) ? mouseover : NULL; + if(!shmup::on && !useRangedOrb && vid.mobilecompasssize) { + using namespace shmupballs; + int dx = mousex - xmove; + int dy = mousey - yb; + int h = hypot(dx, dy); + if(h < rad) { + if(h < rad*SKIPFAC) { lmouseover = cwt.c; mousedest.d = -1; } + else { + double d = revcontrol ? -1 : 1; + mouseh = hpxy(dx * d / rad, dy * d / rad); + calcMousedest(); + } + } + } + if(andmode == 0 && !useRangedOrb && gtouched && lclicked) { + lmouseover = mousedest.d >= 0 ? cwt.c->mov[(cwt.spin + mousedest.d) % cwt.c->type] : cwt.c; + } + } + mouseh = gethyper(mousex, mousey); + +// if(debfile) fprintf(debfile, "d1\n"), fflush(debfile); + frames++; + if(conformal::on) conformal::apply(); + + if(ticks > lastt) tortoise::updateVals(ticks - lastt); + + if(clicked && !lclicked) touchedAt = ticks; + + graphdata.clear(); + getcstat = 0; shiftmul = 1; getcshift = 1; + drawscreen(); + shiftmul = getcshift; + calcMousedest(); + + if(lclicked && !clicked && !inmenu) handleclick(MOBPAR_ACTUAL); + + if(inmenu && !clicked && !lclicked) inmenu = false; + + bool keyreact = lclicked && !clicked; + + if(cmode == emOverview || cmode == emTactic) { + using namespace dialog::zoom; + if(zoomoff || (cmode != emOverview && cmode != emTactic)) { + zoomf = 1; shiftx = shifty = 0; zoomoff = false; return; + } + if(clicked && !lclicked) { + zoomf = 3; + } + if(zoomf == 3) { + shiftx = -2*mousex; + shifty = -2*mousey; + } + if(!clicked && zoomf > 1) { zoomoff = true; } + } + + if(inslider) keyreact = true; + +#ifdef ANDROID + if(getcstat == 's'-96) { + cmode = canmove ? emQuit : emNormal; + shareScore(MOBPAR_ACTUAL); + cmode = emNormal; + } +#endif + + if(andmode == 2 && cmode != emNormal) andmode = 12; + + if((cmode == emQuit && !canmove && keyreact && lclicked && !clicked) && !buttonclicked) { + cmode = emNormal; printf("back to quit\n"); + } + else if(cmode == emScores) handleScoreKeys(0); + else if(getcstat && keyreact) { + + if(cmode == (canmove ? emQuit : emNormal)) + handleQuit(getcstat, getcstat); + + else { + if(cmode != emNormal && cmode != emQuit) inmenu = true; + if(cmode == emMenu && getcstat == 'q') openURL(); + else { extra ex; handlekey(getcstat, getcstat, ex); } + } + + } + + #ifdef IOS + displayTexts(); + #endif + + if((cmode != emVisual1 && cmode != emScores)) { + + if(clicked && lclicked && andmode == 1 && !inmenu) { + if(!outofmap(mouseoh) && !outofmap(mouseh) && mouseoh[2] < 50 && mouseh[2] < 50) { + panning(mouseoh, mouseh); + } + } + + if(andmode == 1 && lclicked && !clicked && !inmenu && mouseover) + performMarkCommand(mouseover); + + if(clicked && andmode == 2 && (mouseover != lmouseover || mouseovers != lmouseovers) && cmode == emNormal) { + addMessage(mouseovers); + lmouseovers = mouseovers; + } + + if(andmode == 10 && clicked != lclicked) andmode = 0; + if(andmode == 20 && clicked != lclicked) andmode = 10; + + if(andmode == 2 && lclicked && !clicked) { + if(cmode == emNormal) + showHelp(MOBPAR_ACTUAL, help); + else if(cmode != emScores && cmode != emPickScores) + cmode = emNormal; + } + + else if(andmode == 4) { + achievement_final(false); + } + + if(clicked && andmode == 2 && (mouseover != lmouseover || mouseovers != lmouseovers)) { + addMessage(mouseovers); + showHelp(MOBPAR_ACTUAL, help); + lmouseovers = mouseovers; + } + + } + + if(clicked != lclicked) + flashMessages(); + + // END + lclicked = clicked; + +#ifdef IOS + controlMusic(ticks - lastt); + #endif + } + +#ifdef MOBILE +#include "google-games.cpp" +#endif + +#endif diff --git a/landgen.cpp b/landgen.cpp index 28e0b7fa..8cc7f3cd 100644 --- a/landgen.cpp +++ b/landgen.cpp @@ -26,7 +26,7 @@ bool safety = false; eLand firstland = laIce, euclidland = laIce; -bool timerghost; +bool timerghost = true; eLand lastland; int lastexplore; @@ -38,6 +38,177 @@ bool chaosmode = false; bool chaosUnlocked = false; bool chaosAchieved = false; +// returns: 2 = treasure increaser, 1 = just appears, 0 = does not appear +int isNative(eLand l, eMonster m) { + switch(l) { + case laIce: + return (m == moWolf || m == moWolfMoved || m == moYeti) ? 2 : 0; + + case laJungle: + return (m == moIvyRoot || m == moMonkey) ? 2 : + (isIvy(m) || m == moEagle || m == moMonkey) ? 1 : 0; + + case laCaves: + return (m == moGoblin || m == moTroll) ? 2 : m == moSeep ? 1 : 0; + + case laDesert: + return (m == moDesertman || m == moWorm) ? 2 : 0; + + case laAlchemist: + return (m == moSlime) ? 2 : 0; + + case laMirror: + return (m == moEagle || m == moRanger || m == moMirror || m == moMirage) ? 1 : 0; + + case laMotion: + return (m == moRunDog) ? 2 : 0; + + case laGraveyard: + return (m == moZombie || m == moNecromancer) ? 2 : + m == moGhost ? 1 : 0; + + case laRlyeh: + return + (m == moCultist || m == moTentacle || m == moPyroCultist) ? 2 : + (m == moCultistLeader || isIvy(m)) ? 1 : 0; + + case laDryForest: + return (m == moHedge || m == moFireFairy) ? 2 : 0; + + case laHell: + return (m == moLesser) ? 2 : 0; + + case laCocytus: + return (m == moShark || m == moGreaterShark || m == moCrystalSage) ? 2 : + m == moYeti ? 1 : 0; + + case laCrossroads: case laCrossroads2: case laCrossroads3: case laCrossroads4: + case laCrossroads5: + case laNone: case laBarrier: case laOceanWall: case laCanvas: return 0; + + case laEmerald: + return (m == moFlailer || m == moLancer || m == moMiner) ? 2 : + m == moHedge ? 1 : 0; + + case laWineyard: + return (m == moVineSpirit || m == moVineBeast) ? 2 : 0; + + case laHive: + return isBug(m) ? 1 : 0; + + case laDeadCaves: + return (m == moEarthElemental || m == moDarkTroll) ? 2 : + m == moGoblin ? 1 : 0; + + case laPower: + return (isWitch(m) || m == moEvilGolem) ? 1 : 0; + + case laCamelot: + return (m == moKnight || m == moHedge || m == moFlailer || m == moLancer) ? 1 : 0; + + case laTemple: + return (m == moTentacle || m == moCultist || m == moPyroCultist || m == moCultistLeader) ? 1 : 0; + + case laCaribbean: + return (m == moPirate || m == moParrot || m == moCShark) ? 1 : 0; + + case laRedRock: return (m == moRedTroll || m == moHexSnake) ? 2 : 0; + + case laMinefield: + return (m == moBomberbird || m == moTameBomberbird) ? 1 : 0; + + case laOcean: + return (m == moAlbatross) ? 2 : (m == moPirate || m == moCShark) ? 1 : 0; + + case laWhirlpool: + return (m == moPirate || m == moCShark) ? 1 : 0; + + case laPalace: case laPrincessQuest: + return (m == moPalace || m == moFatGuard || m == moSkeleton || m == moVizier) ? 2 : + m == moSkeleton ? 1 : 0; + + case laLivefjord: + return m == moViking ? 2 : (m == moFjordTroll || m == moWaterElemental) ? 1 : 0; + + case laIvoryTower: + return (m == moFamiliar || m == moGargoyle) ? 2 : 0; + + case laZebra: return (m == moOrangeDog) ? 2 : 0; + + case laEAir: case laEEarth: case laEWater: case laEFire: + case laElementalWall: + if(m == elementalOf(l)) return 2; + return (m == moAirElemental || m == moEarthElemental || m == moWaterElemental || m == moFireElemental) ? 1 : 0; + + case laStorms: + return (m == moMetalBeast || m == moMetalBeast2 || m == moStormTroll) ? 1 : 0; + + case laOvergrown: + return (m == moMutant || m == moForestTroll) ? 1 : 0; + + case laWildWest: + return (m == moOutlaw) ? 2 : 0; + + case laHalloween: + return 1; + + case laClearing: + return (m == moMutant || m == moRedFox) ? 1 : 0; + + case laHaunted: case laHauntedWall: case laHauntedBorder: + return (m == moGhost || m == moFriendlyGhost) ? 1 : 0; + + case laWhirlwind: + return (m == moAirElemental || m == moWindCrow) ? 2 : 0; + + case laRose: + return (m == moFalsePrincess || m == moRoseBeauty || m == moRoseLady) ? 2 : 0; + + case laWarpCoast: case laWarpSea: + return m == moRatling ? 2 : m == moRatlingAvenger ? 1 : 0; + + case laDragon: + return (isDragon(m) || m == moFireElemental) ? 1 : 0; + + case laEndorian: + return (m == moResearcher || m == moSparrowhawk) ? 2 : 0; + + case laTortoise: + return m == moTortoise ? 1 : 0; + + case laTrollheim: + return isTroll(m) ? 1 : 0; + + case laKraken: + return m == moKrakenH ? 2 : (m == moViking || m == moKrakenT) ? 1 : 0; + + case laBurial: + return m == moDraugr ? 1 : 0; + + case laDungeon: + return + m == moBat ? 2 : + m == moSkeleton || m == moGhost ? 1 : + 0; + + case laMountain: + return + m == moEagle || m == moMonkey || isIvy(m) || m == moFriendlyIvy ? 1 : 0; + + case laReptile: + return m == moReptile ? 1 : 0; + + case laBull: + return (m == moSleepBull || m == moRagingBull || m == moButterfly || m == moGadfly) ? 1 : 0; + + case laPrairie: + return (m == moRagingBull || m == moHerdBull || m == moGadfly) ? 1 : 0; + + case laCA: return false; + } + return false; + } + eItem treasureType(eLand l) { switch(l) { case laIce: return itDiamond; @@ -59,6 +230,7 @@ eItem treasureType(eLand l) { case laCrossroads2: return itHyperstone; case laCrossroads3: return itHyperstone; case laCrossroads4: return itHyperstone; + case laCrossroads5: return itHyperstone; case laNone: return itNone; case laBarrier: return itNone; @@ -82,7 +254,7 @@ eItem treasureType(eLand l) { case laPalace: return itPalace; case laLivefjord: return itFjord; - case laIvoryTower: return itEdge; + case laIvoryTower: return itIvory; case laZebra: return itZebra; case laEAir: case laEEarth: case laEWater: case laEFire: @@ -93,21 +265,52 @@ eItem treasureType(eLand l) { case laStorms: return itFulgurite; case laOvergrown: return itMutant; case laWildWest: return itBounty; + case laHalloween: return itTreat; case laClearing: return itMutant2; case laHaunted: case laHauntedWall: case laHauntedBorder: return itLotus; case laWhirlwind: return itWindstone; case laRose: return itRose; - case laGridCoast: case laGridSea: return itCoral; + case laWarpCoast: case laWarpSea: return itCoral; case laDragon: return itDragon; case laEndorian: return itApple; case laTortoise: return itBabyTortoise; + + case laTrollheim: return itTrollEgg; + case laKraken: return itKraken; + case laBurial: return itBarrow; + + case laDungeon: return itSlime; + case laMountain: return itAmethyst; + case laReptile: return itDodeca; + + case laBull: return itBull; + case laPrairie: return itGreenGrass; + + case laCA: return itNone; } return itNone; } -#define ORBLINES 46 +eItem wanderingTreasure(cell *c) { + eLand l = c->land; + if(l == laEFire) return itFireShard; + if(l == laEWater) return itWaterShard; + if(l == laEAir) return itAirShard; + if(l == laEEarth) return itEarthShard; + if(l == laElementalWall) return itNone; + if(l == laMirror && c->type != 6) return itNone; + if(l == laEmerald) { + forCellEx(c2, c) if(c2->wall == waCavewall) return itNone; + } + if(l == laMinefield && c->wall == waMineMine) return itNone; + if(l == laBurial && hrand(2)) return itOrbSword; + if(l == laKraken) return itOrbFish; + return treasureType(l); + } + +#define ORBLINES 54 struct orbinfo { eLand l; @@ -132,22 +335,22 @@ orbinfo orbinfos[ORBLINES] = { {laDryForest, 2500, 0, itOrbWinter}, {laCocytus, 1500, 1500, itOrbWinter}, {laCaves, 1200, 0, itOrbDigging}, - {laDryForest, 500, 2500, itOrbThorns}, + {laDryForest, 500, 4500, itOrbThorns}, {laDeadCaves, 1800, 0, itGreenStone}, {laDeadCaves, 1800, 1500, itOrbDigging}, {laEmerald, 1500, 3500, itOrbPsi}, - {laWineyard, 900, 1200, itOrbGhost}, + {laWineyard, 900, 1200, itOrbAether}, {laHive, 800, 1200, itOrbInvis}, {laPower, 0, 3000, itOrbFire}, {laMinefield, 0, 3500, itOrbFriend}, {laTemple, 0, 3000, itOrbDragon}, - {laCaribbean, 0, 3500, itOrbPreserve}, - {laRedRock, 0, 2500, itOrbTelekinesis}, + {laCaribbean, 0, 3500, itOrbTime}, + {laRedRock, 0, 2500, itOrbSpace}, {laCamelot, 1000, 1500, itOrbIllusion}, {laOcean, 0, 3000, itOrbEmpathy}, {laOcean, 0, 0, itOrbAir}, {laPalace, 0, 4000, itOrbDiscord}, - {laZebra, 500, 1500, itOrbFrog}, + {laZebra, 500, 2100, itOrbFrog}, {laLivefjord, 0, 1800, itOrbFish}, {laPrincessQuest, 0, 200, itOrbLove}, {laIvoryTower, 500, 4000, itOrbMatter}, @@ -157,12 +360,20 @@ orbinfo orbinfos[ORBLINES] = { {laWhirlwind, 1250, 3000, itOrbAir}, {laHaunted, 1000, 5000, itOrbUndeath}, {laClearing, 5000, 5000, itOrbFreedom}, - {laRose, 2000, 8000, itOrbSkunk}, - {laGridCoast, 2000, 8000, itOrb37}, + {laRose, 2000, 8000, itOrbBeauty}, + {laWarpCoast, 2000, 8000, itOrb37}, {laDragon, 500, 5000, itOrbDomination}, {laTortoise, 2500, 1500, itOrbShell}, {laEndorian, 150, 2500, itOrbEnergy}, {laEndorian, 450, 0, itOrbTeleport}, + {laKraken, 500, 2500, itOrbSword}, + {laBurial, 500, 2500, itOrbSword2}, + {laTrollheim, 750, 1800, itOrbStone}, + {laMountain, 400, 3500, itOrbNature}, + {laDungeon, 120, 2500, itOrbRecall}, + {laReptile, 500, 2100, itOrbDash}, + {laBull, 720, 3000, itOrbHorns}, + {laPrairie, 0, 3500, itOrbBull}, {laWhirlpool, 0, 2000, itOrbWater}, // must be last because it generates a boat }; @@ -205,8 +416,8 @@ string olrDescriptions[] = { "this Orb is never unlocked globally (only hubs)", "collect 25 %2 to unlock it in %the1", "collect 3 %2 to unlock it in %the1", - "native in %the1 (collect 10 %2)", - "native in %the1 (collect 1 %2)", + "native to %the1 (collect 10 %2)", + "native to %the1 (collect 1 %2)", "secondary in %the1 (collect 10 %3, or 25 %2)", "the native Orb of %the1", "this Orb appears on floors and is used by witches", @@ -223,35 +434,55 @@ eOrbLandRelation getOLR(eItem it, eLand l) { if(it == itOrbFire) return olrPNative; if( - it == itOrbFlash || it == itOrbSpeed || it == itOrbWinter || it == itOrbGhost || + it == itOrbFlash || it == itOrbSpeed || it == itOrbWinter || it == itOrbAether || it == itOrbLife) return olrPBasic; if( it == itOrbLightning || it == itOrbThorns || it == itOrbInvis || it == itOrbShield || it == itOrbTeleport || it == itOrbPsi || - it == itOrbDragon || it == itOrbIllusion || it == itOrbPreserve) + it == itOrbDragon || it == itOrbIllusion || it == itOrbTime) return olrPPrized; return olrPNever; } + if(it == itOrbSafety && l == laCrossroads5) return olrDangerous; + if(it == itOrbFire && l == laKraken) return olrUseless; + if(it == itOrbDragon && l == laKraken) return olrUseless; + if(it == itOrbDigging && l == laKraken) return olrUseless; + if(it == itOrbIllusion && l == laKraken) return olrUseless; + // if(it == itOrbYendor && l == laWhirlpool) return olrUseless; if(it == itOrbYendor && l == laWhirlwind) return olrUseless; + if(it == itOrbLife && (l == laKraken)) return olrUseless; + if(it == itOrbAir && l == laAlchemist) return olrUseless; // if(it == itOrbShield && l == laMotion) return olrUseless; if(it == itOrbIllusion && l == laCamelot) return olrNative1; if(it == itOrbLove) return olrNoPrizeOrb; if(orbType(l) == it) return olrNative; - if(it == itOrbWinter && (l == laIce || l == laDryForest)) + if(it == itOrbWinter && (l == laIce || l == laDryForest || l == laDragon)) return olrGuest; if(it == itOrbLuck && l == laIvoryTower) return olrUseless; if(it == itOrbLuck && l == laEndorian) return olrUseless; + if(it == itOrbLuck && l == laDungeon) + return olrUseless; + if(it == itOrbWater && l == laRedRock) + return olrUseless; if(it == itOrbLuck && l == laTortoise) return olrUseless; + if(it == itOrbLuck && l == laMountain) + return olrUseless; + if(it == itOrbLuck && l == laCamelot) + return olrUseless; + if(it == itOrbLuck && l == laHaunted) + return olrUseless; + if(it == itOrbNature && l == laWineyard) + return olrDangerous; if(it == itOrbDigging && l == laCaves) return olrGuest; if(it == itOrbFrog && (l == laPalace || l == laPrincessQuest)) @@ -261,6 +492,10 @@ eOrbLandRelation getOLR(eItem it, eLand l) { if(it == itOrbSafety && l == laWhirlpool) return olrAlways; + if(it == itOrbSafety && l == laPrairie) + return olrAlways; + if(it == itGreenStone && isHaunted(l)) + return olrAlways; if(it == itOrbWater && l == laLivefjord) return olrMonster; if(isCrossroads(l) || l == laOcean) @@ -281,7 +516,7 @@ eOrbLandRelation getOLR(eItem it, eLand l) { l == laMinefield || l == laCocytus) return olrUseless; if(l == laPrincessQuest) - if(it == itOrbGhost || it == itOrbFlash || it == itOrbTeleport || it == itOrbSummon || it == itOrbFreedom) + if(it == itOrbAether || it == itOrbFlash || it == itOrbTeleport || it == itOrbSummon || it == itOrbFreedom) return olrForbidden; if(l == laTemple) @@ -289,7 +524,8 @@ eOrbLandRelation getOLR(eItem it, eLand l) { if(it == itOrbDigging) { if(l == laCaves || l == laOcean || l == laLivefjord || l == laEmerald || - l == laDesert || l == laDeadCaves || l == laRedRock || l == laCaribbean || l == laGraveyard) + l == laDesert || l == laDeadCaves || l == laRedRock || l == laCaribbean || l == laGraveyard || + l == laMountain) return olrPrize25; return olrUseless; } @@ -298,13 +534,16 @@ eOrbLandRelation getOLR(eItem it, eLand l) { if(l == laDesert || l == laIce || l == laJungle || l == laGraveyard || l == laRlyeh || l == laHell || l == laDryForest || l == laWineyard || l == laHive || l == laCamelot || l == laRedRock || l == laPalace || - l == laLivefjord || l == laZebra || isElemental(l) || l == laPrincessQuest) + l == laLivefjord || l == laZebra || isElemental(l) || l == laPrincessQuest || + l == laDragon || l == laTortoise || l == laBurial || l == laTrollheim || + l == laOcean || l == laHaunted || l == laWarpCoast || l == laRose) return olrPrize25; return olrForbidden; } if(it == itOrbWater) - if(l == laMotion || l == laZebra || l == laIvoryTower || l == laEndorian) + if(l == laMotion || l == laZebra || l == laIvoryTower || l == laEndorian || + l == laMountain || l == laReptile || l == laDungeon) return olrUseless; if(it == itOrbWinter && l != laRlyeh && l != laTemple) @@ -313,11 +552,17 @@ eOrbLandRelation getOLR(eItem it, eLand l) { if(it == itOrbLife && l == laMotion) return olrUseless; + if(it == itOrbFish && l == laKraken) + return olrAlways; + + if(it == itOrbSword && l == laBurial) + return olrAlways; + if(it == itOrbFish && l != laOcean && l != laLivefjord && l != laWhirlpool && l != laCamelot && l != laTortoise) return olrUseless; - if(it == itOrbDragon && l != laOcean && l != laRedRock && l != laDesert && + if(it == itOrbDomination && l != laOcean && l != laRedRock && l != laDesert && l != laRlyeh && l != laDragon) return olrUseless; @@ -329,8 +574,15 @@ eOrbLandRelation getOLR(eItem it, eLand l) { return olrForbidden; if(l == laEndorian) - if(it == itOrbDragon || it == itOrbFire) + if(it == itOrbDragon || it == itOrbFire || it == itOrbLightning) return olrDangerous; + + if(l == laDungeon) { + if(it == itOrbSafety || it == itOrbFrog || + it == itOrbTeleport || it == itOrbMatter || it == itOrbNature || + it == itOrbAether || it == itOrbSummon || it == itOrbStone) + return olrForbidden; + } return olrPrize25; } @@ -363,13 +615,13 @@ bool landUnlocked(eLand l) { case laStorms: case laWhirlwind: return gold() >= 60; - case laWildWest: + case laWildWest: case laHalloween: return false; case laIce: case laJungle: case laCaves: case laDesert: case laMotion: case laCrossroads: case laAlchemist: return true; - + case laMirror: case laMinefield: case laPalace: case laOcean: case laLivefjord: return gold() >= 30; @@ -413,37 +665,62 @@ bool landUnlocked(eLand l) { case laClearing: return items[itMutant] >= 5; - case laIvoryTower: return gold() >= 30 && items[itElixir] >= 10; + case laIvoryTower: return gold() >= 30; case laZebra: return gold() >= 30 && items[itFeather] >= 10; case laEAir: case laEEarth: case laEWater: case laEFire: case laElementalWall: return elementalUnlocked(); - case laBarrier: case laNone: case laOceanWall: case laCanvas: + case laBarrier: case laNone: case laOceanWall: case laCanvas: case laCA: return false; case laHaunted: case laHauntedWall: case laHauntedBorder: return items[itBone] >= 10; - case laPrincessQuest: return kills[moVizier]; + case laPrincessQuest: return kills[moVizier] && !shmup::on && multi::players == 1; case laRose: return gold() >= 60; - case laGridCoast: case laGridSea: + case laWarpCoast: case laWarpSea: return gold() >= 30; case laCrossroads4: return gold() >= 200; case laEndorian: - return items[itEdge] >= 10; + return items[itIvory] >= 10; case laTortoise: return tortoise::seek(); case laDragon: return killtypes() >= 20; + + case laKraken: + return items[itFjord] >= 10; + + case laBurial: + return items[itKraken] >= 10; + + case laTrollheim: + return trollUnlocked(); + + case laDungeon: + return items[itPalace] >= 5 && items[itIvory] >= 5; + + case laMountain: + return items[itRuby] >= 5 && items[itIvory] >= 5; + + case laReptile: + return gold() >= 30 && items[itElixir] >= 10; + + case laPrairie: + case laBull: + return gold() >= 90; + + case laCrossroads5: + return gold() >= 300; } return false; } @@ -463,7 +740,8 @@ bool hellUnlocked() { void countHyperstoneQuest(int& i1, int& i2) { i1 = 0; i2 = 0; for(int t=1; t= 10) i1++; } } @@ -506,6 +784,14 @@ bool checkBarriersFront(cellwalker bb, int q=5, bool cross = false) { return checkBarriersBack(bb, q); } +bool hasbardir(cell *c) { + return c->bardir != NODIR && c->bardir != NOBARRIERS; + } + +void preventbarriers(cell *c) { + if(c && c->bardir == NODIR) c->bardir = NOBARRIERS; + } + bool checkBarriersBack(cellwalker bb, int q, bool cross) { // printf("back, %p, s%d\n", bb.c, bb.spin); @@ -579,7 +865,15 @@ bool checkBarriersNowall(cellwalker bb, int q, int dir, eLand l1=laNone, eLand l bool isSealand(eLand l) { return l == laOcean || l == laCaribbean || l == laWhirlpool || l == laLivefjord || - l == laOceanWall || l == laGridSea; + l == laOceanWall || l == laWarpSea || l == laKraken; + } + +bool isCoastal(eLand l) { + return l == laWarpSea || l == laWarpCoast || l == laLivefjord || l == laOcean; + } + +bool isPureSealand(eLand l) { + return l == laCaribbean || l == laKraken; } bool isElemental(eLand l) { @@ -599,9 +893,18 @@ eWall getElementalWall(eLand l) { return waNone; } +bool isTrollLand(eLand l) { + return l == laCaves || l == laStorms || l == laOvergrown || + l == laDeadCaves || l == laLivefjord || l == laRedRock; + } + void setbarrier(cell *c) { if(isSealand(c->barleft) && isSealand(c->barright)) { - c->wall = c->type == 7 ? waBarrier : waSea; + bool setbar = c->type == 7; + if(c->barleft == laKraken || c->barright == laKraken) + if(c->barleft != laWarpSea && c->barright != laWarpSea) + setbar = !setbar; + c->wall = setbar ? waBarrier : waSea; c->land = laOceanWall; } else if(isElemental(c->barleft) && isElemental(c->barright)) { @@ -635,14 +938,14 @@ int buildIvy(cell *c, int children, int minleaf) { 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]; + c->mov[i]->mondir = c->spn(i); } } leaf += leafchild; if(leaf < minleaf) { - if(child) killIvy(child); - killIvy(c); + if(child) killIvy(child, moNone); + killIvy(c, moNone); return 0; } else return leaf; @@ -657,7 +960,7 @@ int euclidAlt(short x, short y) { if(euclidland == laTemple || euclidland == laClearing) { return max(int(x), x+y); } - else if(euclidland == laCaribbean || euclidland == laWhirlpool) { + else if(euclidland == laCaribbean || euclidland == laWhirlpool || euclidland == laMountain) { return min( min(max(int(-x), -x-y) + 3, @@ -672,17 +975,31 @@ int euclidAlt(short x, short y) { bool isCrossroads(eLand l) { return l == laCrossroads || l == laCrossroads2 || l == laCrossroads3 || - l == laCrossroads4; + l == laCrossroads4 || l == laCrossroads5; } bool bearsCamelot(eLand l) { - return isCrossroads(l) && l != laCrossroads2; + return isCrossroads(l) && l != laCrossroads2 && l != laCrossroads5; } -void buildPrizeMirror(cell *c) { - if(c->type == 7 && !purehepta) return; - if(items[itShard] < 25) return; +ld orbprizefun(int tr) { + if(tr < 10) return 0; + return .6 + .4 * log(tr/25.) / log(2); + } + +ld orbcrossfun(int tr) { + if(tr < 10) return 0; + if(tr > 25) return 1; + return (tr*2 + 50) / 100.; + } + +bool buildPrizeMirror(cell *c, int freq) { + if(c->type == 7 && !purehepta) return false; + if(items[itShard] < 25) return false; + if(freq && hrand(freq * 100 / orbprizefun(items[itShard])) >= 100) + return false; c->wall = hrand(2) ? waCloud : waMirror; + return true; } void placePrizeOrb(cell *c) { @@ -692,6 +1009,7 @@ void placePrizeOrb(cell *c) { // these two lands would have too much orbs according to normal rules if(l == laPalace && hrand(100) >= 20) return; if(l == laGraveyard && hrand(100) >= 15) return; + if(l == laBurial && hrand(100) >= 10) return; if(l == laLivefjord && hrand(100) >= 35) return; if(l == laMinefield && hrand(100) >= 25) return; if(l == laElementalWall && hrand(100) >= 25) return; @@ -702,15 +1020,18 @@ void placePrizeOrb(cell *c) { orbinfo& oi(orbinfos[i]); eOrbLandRelation olr = getOLR(oi.orb, l); if(olr != olrPrize25 && olr != olrPrize3) continue; - if(olr == olrPrize25 || olr == olrGuest || olr == olrMonster || olr == olrAlways) { - if(items[treasureType(oi.l)] < 25) continue; + int treas = items[treasureType(oi.l)]; + if(olr == olrPrize3) treas *= 10; + if(olr == olrPrize25 || olr == olrPrize3 || olr == olrGuest || olr == olrMonster || olr == olrAlways) { + if(treas < 25) continue; } - else if(olr == olrPrize3) { if(items[treasureType(oi.l)] < 3) continue; } else continue; - if(!oi.gchance) continue; - if(hrand(oi.gchance) >= 60) continue; - if(oi.orb == itOrbWater && c->land != laOcean) { + int gch = oi.gchance; + if(!gch) continue; + gch = int(gch / orbprizefun(treas)); + if(hrand(gch) >= 60) continue; + if(oi.orb == itOrbWater && c->land != laOcean && c->land != laKraken) { if(cellHalfvine(c)) continue; c->item = oi.orb; c->wall = waStrandedBoat; @@ -742,9 +1063,9 @@ void placeLocalOrbs(cell *c) { if(oi.orb == itOrbWater && c->land != laOcean) c->wall = waStrandedBoat; return; } - else if(oi.gchance && ch == 1 && getOLR(itShard, l) == olrPrize25 && l != laRedRock && l != laWhirlwind) - buildPrizeMirror(c); - else if(oi.gchance && (ch >= 2 && ch < 2+PRIZEMUL)) + else if(oi.gchance && (ch >= 1 && ch < 11) && getOLR(itShard, l) == olrPrize25 && l != laRedRock && l != laWhirlwind) + buildPrizeMirror(c, 10); + else if(oi.gchance && (ch >= 11 && ch < 11+PRIZEMUL)) placePrizeOrb(c); } } @@ -753,14 +1074,19 @@ void placeCrossroadOrbs(cell *c) { for(int i=0; iland == laCrossroads5) continue; + int mul = c->land == laCrossroads5 ? 10 : 1; + int gch = oi.gchance; + gch /= orbcrossfun(treas); + if(hrand(gch) >= mul) continue; if(hrand(50+items[itHyperstone]) >= 50) continue; c->item = oi.orb; if(oi.orb == itOrbWater && c->land != laOcean) c->wall = waStrandedBoat; @@ -772,6 +1098,7 @@ void placeOceanOrbs(cell *c) { orbinfo& oi(orbinfos[i]); if(items[treasureType(oi.l)] * landMultiplier(oi.l) < 10) continue; if(!oi.gchance) continue; + if(oi.orb == itOrbLife) continue; // useless if(hrand(oi.gchance) >= 20) continue; c->item = oi.orb; } @@ -786,15 +1113,19 @@ void describeCell(cell *c) { printf("%-15s", winf[c->wall].name); printf("%-15s", iinf[c->item].name); printf("%-15s", minf[c->monst].name); - printf("%3d", c->landparam); - printf("%3d", c->mpdist); - printf("%3d", c->mondir); + printf("LP%08x", c->landparam); + printf("D%3d", c->mpdist); + printf("MON%3d", c->mondir); printf("\n"); } +#ifdef BACKTRACE +#include +#endif + void raiseBuggyGeneration(cell *c, const char *s) { - printf("procgen error in: %s\n", s); + printf("procgen error (%p): %s\n", c, s); if(!errorReported) { addMessage(string("something strange happened in: ") + s); @@ -812,19 +1143,39 @@ void raiseBuggyGeneration(cell *c, const char *s) { #else c->item = itBuggy; #endif + +#ifdef BACKTRACE + void *array[1000]; + size_t size; + + // get void*'s for all entries on the stack + size = backtrace(array, 1000); + + // print out all the frames to stderr + backtrace_symbols_fd(array, size, STDERR_FILENO); +#endif } void setland(cell *c, eLand l) { - if(c->land != l) + if(c->land != l) { c->landparam = 0; + } if(l == laNone) { printf("setland\n"); // NONEDEBUG } c->land = l; } +void extendcheck(cell *c) { + return; + if(!purehepta && c->landparam == 0 && c->barleft != NOWALLSEP) { + raiseBuggyGeneration(c, "extend error"); + } + } + void extendBarrierFront(cell *c) { int ht = c->landparam; + extendcheck(c); cellwalker bb(c, c->bardir); setbarrier(bb.c); cwstep(bb); @@ -832,19 +1183,20 @@ void extendBarrierFront(cell *c) { if(!purehepta) { bb.c->barleft = c->barleft; bb.c->barright = c->barright; + setbarrier(bb.c); bb.c->landparam = (ht-4); //printf("[A heat %d]\n", ht-4); - setbarrier(bb.c); + cwspin(bb, 2); cwstep(bb); setland(bb.c, c->barleft); cwstep(bb); cwspin(bb, 2); cwstep(bb); setland(bb.c, c->barright); cwstep(bb); cwspin(bb, 2); cwspin(bb, 3); cwstep(bb); - bb.c->landparam = (ht-4)^2; - //printf("[B heat %d]\n", (ht-4)^2); bb.c->barleft = c->barright; bb.c->barright = c->barleft; setbarrier(bb.c); + bb.c->landparam = (ht-4)^2; + //printf("[B heat %d]\n", (ht-4)^2); cwspin(bb, 3); cwstep(bb); bb.c->landparam = ht ^ 2; @@ -855,6 +1207,7 @@ void extendBarrierFront(cell *c) { bb.c->barleft = c->barright; bb.c->barright = c->barleft; // printf("#1\n"); + extendcheck(bb.c); extendBarrier(bb.c); for(int a=-3; a<=3; a++) if(a) { @@ -865,13 +1218,17 @@ void extendBarrierFront(cell *c) { void extendBarrierBack(cell *c) { int ht = c->landparam; + extendcheck(c); cellwalker bb(c, c->bardir); setbarrier(bb.c); - cwspin(bb, 3); cwstep(bb); cwspin(bb, purehepta?5:4); setland(bb.c, purehepta ? c->barleft : c->barright); cwstep(bb); cwspin(bb, 3); + cwspin(bb, 3); cwstep(bb); cwspin(bb, purehepta?5:4); + setland(bb.c, purehepta ? c->barleft : c->barright); + cwstep(bb); cwspin(bb, 3); bb.c->bardir = bb.spin; bb.c->barleft = c->barright; bb.c->barright = c->barleft; bb.c->landparam = ht ^ 11; + extendcheck(bb.c); //printf("[D heat %d]\n", (ht^11)); // needed for CR2 to work @@ -897,6 +1254,7 @@ eLand oppositeElement(eLand l) { } void extendNowall(cell *c) { + c->barleft = NOWALLSEP_USED; cellwalker cw(c, c->bardir); @@ -922,10 +1280,11 @@ void extendNowall(cell *c) { if(c->barright == laNone) { printf("barright\n"); }// NONEDEBUG - cw.c->land = c->barright; + setland(cw.c, c->barright); if(!purehepta) cwspin(cw, i); cw.c->bardir = cw.spin; if(!purehepta) cwspin(cw, -i); + extendcheck(cw.c); extendBarrier(cw.c); } if(purehepta) { @@ -940,13 +1299,48 @@ void extendNowall(cell *c) { } } +eLand getNewLand(eLand old); + +bool gotit = false; + +void extendCR5(cell *c) { + if(purehepta) return; +// if(c->barright == laCrossroads5) extendCR5(c); + eLand forbidden = c->barleft; + eLand forbidden2 = laNone; + cellwalker cw(c, c->bardir); + for(int u=0; u<2; u++) { + // if(gotit) break; + cwspin(cw, 2); + cwstep(cw); + cwspin(cw, 2); + cwstep(cw); + cwspin(cw, 5); + if(cw.c->bardir == NODIR) { + cw.c->landparam = 40; + cw.c->bardir = cw.spin; + cw.c->barright = laCrossroads5; + eLand nland = forbidden; + for(int i=0; i<10 && (nland == forbidden || nland == forbidden2); i++) + nland = getNewLand(laCrossroads5); + cw.c->barleft = forbidden2 = nland; + landcount[nland]++; + extendBarrier(cw.c); + gotit = true; + } + else forbidden2 = cw.c->barleft; + } + } + void extendBarrier(cell *c) { if(buggyGeneration) return; if(c->barleft == NOWALLSEP_USED) return; + + extendcheck(c); // printf("build barrier at %p", c); - if(c->wall == waBarrier || c->land == laElementalWall || c->land == laHauntedWall) { + if(c->land == laBarrier || c->land == laElementalWall || c->land == laHauntedWall || c->land == laOceanWall) { // printf("-> ready\n"); return; } @@ -959,7 +1353,7 @@ void extendBarrier(cell *c) { if(c->barleft == NOWALLSEP) { extendNowall(c); return; - } + } if(((c->barleft == laCrossroads3 || c->barright == laCrossroads3) && hrand(100) < 66) || (isElemental(c->barleft) && isElemental(c->barright) && hrand(100) < 25)) { @@ -993,6 +1387,8 @@ void extendBarrier(cell *c) { extendBarrierFront(c); extendBarrierBack(c); + + if(c->barright == laCrossroads5) extendCR5(c); } void chasmify(cell *c) { @@ -1027,7 +1423,7 @@ void chasmifyEarth(cell *c) { cell *c4 = createMov(c3, i); earthFloor(c4); } - c3->mondir = c->spn[d]; + c3->mondir = c->spn(d); } earthWall(c); c->item = itNone; } @@ -1050,7 +1446,7 @@ void chasmifyElemental(cell *c) { cell *c4 = createMov(c3, i); if(c4->wall != waBarrier) c4->wall = waNone; } - c3->mondir = c->spn[d]; + c3->mondir = c->spn(d); } } c->wall = getElementalWall(c->land); @@ -1071,6 +1467,7 @@ bool incompatible1(eLand l1, eLand l2) { if(l1 == laGraveyard && l2 == laEmerald) return true; if(l1 == laDeadCaves && l2 == laEmerald) return true; if(l1 == laDeadCaves && l2 == laCaves) return true; + if(l1 == laWarpSea && l2 == laKraken) return true; if(isElemental(l1) && isElemental(l2)) return true; return false; } @@ -1091,6 +1488,12 @@ bool elementalUnlocked() { kills[moAirElemental] && kills[moWaterElemental] && kills[moEarthElemental] && kills[moFireElemental]; } +bool trollUnlocked() { + return + kills[moTroll] && kills[moDarkTroll] && kills[moRedTroll] && + kills[moStormTroll] && kills[moForestTroll] && kills[moFjordTroll]; + } + eLand randomElementalLandWeighted() { int i = hrand(elementalKills()); i -= kills[moAirElemental]; if(i<0) return laEAir; @@ -1149,12 +1552,15 @@ bool noChaos(eLand l) { if(l == laOcean || l == laTemple) return false; return isCrossroads(l) || isCyclic(l) || isHaunted(l) || - l == laCaribbean || isGravityLand(l) || l == laPrincessQuest; + l == laCaribbean || isGravityLand(l) || l == laPrincessQuest || + l == laPrairie; } eLand getNewSealand(eLand old) { while(true) { - eLand p = pick(laOcean, pick(laCaribbean, laLivefjord, laGridSea)); + eLand p = pick(laOcean, pick(laCaribbean, laLivefjord, laWarpSea, laKraken)); + if(p == laKraken && !landUnlocked(p)) continue; + if(incompatible(old, p)) continue; if(p == old) continue; if(chaosmode && noChaos(p)) continue; return p; @@ -1163,15 +1569,39 @@ eLand getNewSealand(eLand old) { bool doEndorian = false; +int whichnow=0; + eLand getNewLand(eLand old) { + /* eLand landtab[10] = { + laWhirlwind, laRose, laEndorian, laRlyeh, + laPalace, laOcean, laEmerald, laStorms, + laGraveyard, laAlchemist + }; */ + + // return landtab[items[itStrongWind]++ % 10]; + // if(old != laPrairie) return laRiver; + #ifdef LOCAL extern bool doAutoplay; if(doAutoplay) - return pick(laOcean, laLivefjord, laGridSea, laGridCoast); + return pick(laOcean, laLivefjord, laWarpSea, laWarpCoast); + extern bool doCross; + if(doCross) { + whichnow++; + eLand tabb[30] = { + laIce, laRedRock, laCaribbean, laWarpCoast, laWhirlwind, laPower, + laMirror, laPalace, laLivefjord, laAlchemist, laCocytus, + laHell, laJungle, laCaves, laDesert, laRlyeh, laStorms, + laGraveyard, laMotion, laDryForest, laDragon, laZebra, laIvoryTower, + laTrollheim, laOvergrown, laBurial, laRose, laHive, laEmerald, + laEmerald + }; + return tabb[whichnow%30]; + } #endif - if(items[itEdge] == 12345) return laEndorian; + if(cheatdest != old) if(!isCyclic(cheatdest) && !isTechnicalLand(cheatdest)) return cheatdest; if(old == laTortoise) return laDragon; @@ -1209,8 +1639,9 @@ eLand getNewLand(eLand old) { if(yendor::on && (yendor::clev().flags & YF_WALLS)) { if(old != yendor::clev().l) return yendor::clev().l; + else if(old == laOcean) return pick(laLivefjord, laCaribbean); } - + if(yendor::on && yendor::nexttostart) { eLand l = yendor::nexttostart; if(!(yendor::clev().flags & YF_REPEAT)) @@ -1221,9 +1652,9 @@ eLand getNewLand(eLand old) { if(old == laDragon && tortoise::seek() && hrand(100) < 50) return laTortoise; - if(isWarped(old) && (hrand(100) < 25) && chaosmode) return eLand(old ^ laGridCoast ^ laGridSea); + if(isWarped(old) && (hrand(100) < 25) && chaosmode) return eLand(old ^ laWarpCoast ^ laWarpSea); - if(old == laGridSea || old == laCaribbean || + if(old == laWarpSea || old == laCaribbean || old == laKraken || (old == laLivefjord && hrand(2)) || (old == laOcean && (chaosmode ? hrand(2) : !generatingEquidistant))) return getNewSealand(old); @@ -1265,14 +1696,23 @@ eLand getNewLand(eLand old) { tab[cnt++] = laLivefjord; tab[cnt++] = laMinefield; tab[cnt++] = laPalace; + if(old == laDragon) LIKELY tab[cnt++] = laReptile; if(kills[moVizier]) tab[cnt++] = laEmerald; if(items[itFeather] >= 10) tab[cnt++] = laZebra; - tab[cnt++] = laGridCoast; - if(euclid) tab[cnt++] = laGridSea; + tab[cnt++] = laWarpCoast; + if(euclid) tab[cnt++] = laWarpSea; // Ivory Tower tends to crash while generating equidistant - if(items[itElixir] >= 10 && !generatingEquidistant) tab[cnt++] = laIvoryTower; - if(items[itEdge] >= 10 && !generatingEquidistant) tab[cnt++] = laEndorian; + if(!generatingEquidistant) tab[cnt++] = laIvoryTower; + if(items[itElixir] >= 10) tab[cnt++] = laReptile; + if(items[itIvory] >= 10 && !generatingEquidistant) tab[cnt++] = laEndorian; + + if(items[itKraken] >= 10) tab[cnt++] = laBurial; } + + if(landUnlocked(laDungeon)) { + tab[cnt++] = laDungeon; + if(old == laPalace) LIKELY tab[cnt++] = laDungeon; + } // the advanced lands if(gold() >= 60) { @@ -1285,7 +1725,7 @@ eLand getNewLand(eLand old) { if(old == laJungle) LIKELY tab[cnt++] = laOvergrown; } if(rlyehComplete()) tab[cnt++] = laRlyeh; - else if(chaosmode && (old == laGridCoast || old == laLivefjord || old == laOcean)) + else if(chaosmode && (old == laWarpCoast || old == laLivefjord || old == laOcean)) tab[cnt++] = laRlyeh; if(items[itStatue] >= 5 && chaosmode) tab[cnt++] = laTemple; @@ -1306,19 +1746,53 @@ eLand getNewLand(eLand old) { tab[cnt++] = laRose; } + if(gold() >= 90) { + if(!chaosmode) tab[cnt++] = laPrairie; + if(old == laPrairie) LIKELY tab[cnt++] = laBull; + tab[cnt++] = laBull; + if(old == laBull && !chaosmode) LIKELY tab[cnt++] = laPrairie; + } + + if(gold() >= 300) + tab[cnt++] = laCrossroads5; + if(tkills() >= 100) { tab[cnt++] = laGraveyard; if(gold() >= 60) tab[cnt++] = laHive; } - if(killtypes() >= 20) + if(killtypes() >= 20) { tab[cnt++] = laDragon; + if(old == laReptile) LIKELY tab[cnt++] = laDragon; + } - if(elementalUnlocked()) + if(trollUnlocked()) { + tab[cnt++] = laTrollheim; + if(isTrollLand(old)) LIKELY tab[cnt++] = laTrollheim; + if(old == laTrollheim) for(int i=0; ibardir = d; eLand oldland = c->land; + if(oldland == laNone) { + raiseBuggyGeneration(c, "oldland is NONE"); + return; + } eLand newland = l ? l : getNewLand(oldland); if(showoff) newland = showlist[(showid++) % 10]; landcount[newland]++; - - if(d == 5) c->barleft = oldland, c->barright = newland; + if(d == 4 || d == 5 || d == 6) c->barleft = oldland, c->barright = newland; else c->barleft = newland, c->barright = oldland; c->landparam = 40; + extendcheck(c); } } @@ -1541,6 +2019,7 @@ void buildBarrierStrong(cell *c, int d, bool oldleft) { if(oldleft) c->barleft = oldland, c->barright = newland; else c->barleft = newland, c->barright = oldland; + extendcheck(bb.c); } void buildCrossroads2(cell *c) { @@ -1553,7 +2032,7 @@ void buildCrossroads2(cell *c) { if(c->mov[i] && !c->mov[i]->landparam && c->mov[i]->mpdist < c->mpdist) buildCrossroads2(c->mov[i]); - if(c->bardir != NODIR && c->bardir != NOBARRIERS) + if(hasbardir(c)) extendBarrier(c); if(c->land != laCrossroads2) return; @@ -1569,13 +2048,16 @@ void buildCrossroads2(cell *c) { int h2 = c2->landparam; int h3 = c3->landparam; + if(h2 > 100) { raiseBuggyGeneration(c2, "bad c2 landparam"); return; } + if(h3 > 100) { raiseBuggyGeneration(c3, "bad c3 landparam"); return; } + // ambiguous if(h2/4 == 1 && h3/4 == 3) continue; if(h2/4 == 3 && h3/4 == 1) continue; for(int d=0; dtype; d++) if(emeraldtable[h2][d] == h3) { - int nh = emeraldtable[h2][(42+d + c->spn[i] - j) % c2->type]; + int nh = emeraldtable[h2][(42+d + c->spn(i) - j) % c2->type]; if(c->landparam>0 && c->landparam != nh) { printf("CONFLICT\n"); raiseBuggyGeneration(c, "CONFLICT"); @@ -1589,8 +2071,6 @@ void buildCrossroads2(cell *c) { } if(c->landparam == 0) { printf("H2 = %d\n", c2->landparam); - describeCell(c2); - for(int i=0; itype; i++) describeCell(c2->mov[i]); // halted = true; // c->heat = -1; raiseBuggyGeneration(c, "buildCrossroads2x"); @@ -1626,7 +2106,8 @@ void buildCrossroads2(cell *c) { if(c->mov[(i+j)%7] && c->mov[(i+j)%7]->mpdist < c->mpdist) oldleft = false; - buildBarrierStrong(c, i, oldleft); + c->landparam = h; + buildBarrierStrong(c, i, oldleft); c->landparam = h; extendBarrier(c); } @@ -1664,6 +2145,14 @@ void buildAnotherEquidistant(cell *c) { generatingEquidistant = false; return; } + if(cw.c->bardir != NODIR) { + generatingEquidistant = false; + return; + } + /* forCellEx(c2, cw.c) if(c2->bardir != NODIR) { + generatingEquidistant = false; + return; + } */ coastpath.push_back(cw.c); if(cw.c->land == laNone && cw.c->mpdist <= 7) { raiseBuggyGeneration(cw.c, "landNone 1"); @@ -1680,13 +2169,19 @@ void buildAnotherEquidistant(cell *c) { for(int i=1; iland == laNone) { raiseBuggyGeneration(cwt.c, "landNone 3"); - for(int i=0; i<10; i++) printf("%d ", mpd[i]); printf("\n"); + {for(int i=0; i<10; i++) printf("%d ", mpd[i]);} printf("\n"); for(int i=0; iitem = itPirate; return; } setdist(coastpath[i], BARLEV, coastpath[i-1]); setdist(coastpath[i], BARLEV-1, coastpath[i-1]); + if(i < size(coastpath) - 5) { + coastpath[i]->bardir = NOBARRIERS; +// coastpath[i]->item = itSapphire; +// forCellEx(c2, coastpath[i]) c2->bardir = NOBARRIERS; + } } + //printf("building barrier\n"); cell *c2 = coastpath[coastpath.size() - 1]; int bd = 2 + (hrand(2)) * 3; @@ -1699,6 +2194,11 @@ void buildAnotherEquidistant(cell *c) { return; } + if(c2->land != c->land) { + generatingEquidistant = false; + return; // prevent gravity anomalies + } + // else if(c->type == 7 && hrand(10000) < 20 && !isCrossroads(c->land) && gold() >= 200) if(c2->type == 7 && gold() >= 200 && hrand(10) < 2 && buildBarrierNowall(c2, laCrossroads4, true)) { nowall = true; @@ -1707,8 +2207,7 @@ void buildAnotherEquidistant(cell *c) { } else buildBarrier(c2, bd); //printf("building barrier II\n"); - if(c2->bardir != NODIR && c2->bardir != NOBARRIERS) - extendBarrier(c2); + if(hasbardir(c2)) extendBarrier(c2); for(int i=size(coastpath)-(nowall?1:2); i>=0; i--) { for(int j=BARLEV; j>=6; j--) @@ -1732,7 +2231,7 @@ int coastval(cell *c, eLand base) { } else { if(c->land == laOceanWall || c->land == laCaribbean || c->land == laWhirlpool || - c->land == laLivefjord || c->land == laGridSea) + c->land == laLivefjord || c->land == laWarpSea || c->land == laKraken) return 30; if(c->land != laOcean && !isGravityLand(c->land) && c->land != laHaunted) { return 0; @@ -1819,11 +2318,11 @@ void buildEquidistant(cell *c) { qcv++, sid = i; // if(generatingEquidistant) printf("qcv=%d mcv=%d\n", qcv, mcv); - if(qcv >= 2) c->landparam = mcv+1; + if(qcv >= 2) c->landparam = mcv+1; // (mcv == UNKNOWN ? UNKNOWN : mcv+1); else { // if(qcv != 1) { printf("qcv = %d\n", qcv); exit(1); } cell *c2 = c->mov[sid]; - int bsid = c->spn[sid]; + int bsid = c->spn(sid); for(int j=0; j<7; j++) { int q = (bsid+j+42) % c2->type; cell *c3 = c2->mov[q]; @@ -1851,7 +2350,6 @@ void buildEquidistant(cell *c) { /* if(c->heat < 3) raiseBuggyGeneration(c, "low heat"); */ - } } @@ -1911,7 +2409,7 @@ void buildEquidistant(cell *c) { else for(int j=0; jtype; j++) { cell *c3 = c2->mov[j]; if(c3 && c3->landparam < c2->landparam && c3->landflags) - if(c->spn[i] == (j+3)%c2->type || c->spn[i] == (j+c2->type-3)%c2->type) + if(c->spn(i) == (j+3)%c2->type || c->spn(i) == (j+c2->type-3)%c2->type) ok = true; } if(ok) { @@ -2018,8 +2516,10 @@ void buildRedWall(cell *c, int gemchance) { c->wall = waRed3; if(hrand(100+ki) < gemchance + ki) c->item = itRedGem; - if(items[itRedGem] >= 10 && hrand(18000) < gemchance) - c->item = itOrbTelekinesis; + if(items[itRedGem] >= 10 && hrand(8000) < gemchance) + c->item = itOrbSpace; + else if(hrand(8000) < gemchance * PRIZEMUL) + placePrizeOrb(c); } int palaceHP() { @@ -2073,7 +2573,8 @@ cell *chosenDown(cell *c, int which, int bonus, cellfunction* cf) { } int edgeDepth(cell *c) { - if(c->land == laIvoryTower || c->land == laEndorian) return coastvalEdge(c); + if(c->land == laIvoryTower || c->land == laEndorian || c->land == laDungeon) + return coastvalEdge(c); else if(c->land != laBarrier) { for(int i=0; itype; i++) if(c->mov[i] && c->mov[i]->land == laBarrier) return -20+c->cpdist; @@ -2120,8 +2621,12 @@ int towerval(cell *c, cellfunction* cf) { return (c->type-6) + 2*(cp1->type-6) + 4*under; } +int hardness_empty() { + return yendor::hardness() * (yendor::hardness() * 3/5 - 2); + } + int hivehard() { - return items[itRoyalJelly] + yendor::hardness() * (yendor::hardness() * 3/5 - 2); + return items[itRoyalJelly] + hardness_empty(); // 0, 5, 40, 135 } @@ -2144,6 +2649,7 @@ bool buildBarrierNowall(cell *c, eLand l2, bool force) { int dtab[3] = {0,1,6}; if(c->land == laNone) { + printf("barrier nowall! [%p]\n", c); raiseBuggyGeneration(c, "barrier nowall!"); return false; } @@ -2165,12 +2671,52 @@ bool buildBarrierNowall(cell *c, eLand l2, bool force) { return false; } +void setLandQuotient(cell *c) { + int fv = zebra40(c); + if(fv/4 == 4 || fv/4 == 6 || fv/4 == 5 || fv/4 == 10) fv ^= 2; + if(euclidland == laWarpCoast) + if(fv%4==0 || fv%4 == 2) setland(c, laWarpSea); + if(euclidland == laElementalWall) + setland(c, eLand(laEFire + (fv%4))); + } + +void setLandSphere(cell *c) { + if(euclidland == laWarpCoast) + setland(c, getHemisphere(c, 0) > 0 ? laWarpCoast : laWarpSea); + if(euclidland == laElementalWall) { + int x = getHemisphere(c, 1); + int y = getHemisphere(c, 2); + if(x > 0 && y > 0) setland(c, laEFire); + else if(x > 0 && y < 0) setland(c, laEAir); + else if(x < 0 && y < 0) setland(c, laEWater); + else if(x < 0 && y > 0) setland(c, laEEarth); + else if(x > 0) + c->land = laElementalWall, c->barleft = laEAir, c->barright = laEFire; + else if(x < 0) + c->land = laElementalWall, c->barleft = laEEarth, c->barright = laEWater; + else if(y > 0) + c->land = laElementalWall, c->barleft = laEEarth, c->barright = laEFire; + else if(y < 0) + c->land = laElementalWall, c->barleft = laEAir, c->barright = laEWater; + if(c->land == laElementalWall && c->type != 6) + c->wall = getElementalWall(hrand(2) ? c->barleft : c->barright); + } + if(euclidland == laCrossroads || euclidland == laCrossroads2 || euclidland == laCrossroads3) { + int x = getHemisphere(c, 1); + if(x == 0 || (euclidland == laCrossroads3 && getHemisphere(c, 2) == 0)) + setland(c, laBarrier), c->wall = waBarrier; + else setland(c, euclidland); + if(euclidland == laCrossroads3 && c->type != 6 && c->master->fiftyval == 1) + c->wall = waBigTree; + } + } + void setLandEuclid(cell *c) { - c->land = euclidland; + setland(c, euclidland); if(euclidland == laCrossroads) { eucoord x, y; decodeMaster(c->master, x, y); - c->land = getEuclidLand(y+2*x); + setland(c, getEuclidLand(y+2*x)); } if(euclidland == laCrossroads4) { eucoord x, y; @@ -2181,24 +2727,23 @@ void setLandEuclid(cell *c) { c->land = laOcean; c->landparam = 99; } - if(euclidland == laPrincessQuest) - c->land = laPalace; + if(euclidland == laPrincessQuest) setland(c, laPalace); if(euclidland == laOcean) { eucoord x, y; decodeMaster(c->master, x, y); int y0 = y; if(y>50000) y0 -= 65536; y0 += 10; if(y0 == 0) - {c->land = laBarrier; if(ishept(c)) c->land = laRlyeh; } - else if(y0<0) c->land = laRlyeh; + { setland(c, laBarrier); if(ishept(c)) c->land = laRlyeh; } + else if(y0<0) setland(c, laRlyeh); else c->landparam = y0; } - if(euclidland == laIvoryTower) { + if(euclidland == laIvoryTower || euclidland == laDungeon) { eucoord x, y; decodeMaster(c->master, x, y); int y0 = y; if(y>50000) y0 -= 65536; y0 = -y0; y0 -= 5; if(y0 == 0) - {c->land = laBarrier; if(ishept(c)) c->land = laAlchemist; } - else if(y0<0) c->land = laAlchemist; + {setland(c, laBarrier); if(ishept(c)) setland(c, laAlchemist); } + else if(y0<0) setland(c, laAlchemist); else { c->landparam = y0; } @@ -2220,7 +2765,7 @@ void setLandEuclid(cell *c) { if(y0&16) id2 += 2; if(x0&16) id2 += 1; - c->land = eLand(laEFire + id); + setland(c, eLand(laEFire + id)); if(((y0&15) == 15 && (x0&1)) || ((x0&15) == 0 && ((y0+1)&1))) { if(c->land == laEFire) c->wall = waEternalFire; @@ -2229,7 +2774,7 @@ void setLandEuclid(cell *c) { if(c->land == laEEarth) c->wall = waStone; c->barright = c->land; c->barleft = eLand(laEFire+id2); - c->land = laElementalWall; + setland(c, laElementalWall); } } if(euclidland == laCrossroads3) { @@ -2244,34 +2789,38 @@ void setLandEuclid(cell *c) { if(y0&16) id ^= 1; if(x0&16) id ^= 1; - c->land = id ? laCrossroads3 : laDesert; + setland(c, id ? laCrossroads3 : laDesert); if(((y0&15) == 15 && (x0&1)) || ((x0&15) == 0 && ((y0+1)&1))) { + setland(c, laBarrier); c->wall = waBarrier; - c->land = laBarrier; } } - if(euclidland == laGridCoast) { + if(euclidland == laWarpCoast) { eucoord x, y; decodeMaster(c->master, x, y); int zz = 2*short(x)+short(y) + 10; zz %= 30; if(zz<0) zz += 30; if(zz >= 15) - c->land = laGridSea; + setland(c, laWarpSea); else - c->land = laGridCoast; + setland(c, laWarpCoast); } } void buildBigStuff(cell *c, cell *from) { + if(sphere || quotient) return; bool deepOcean = false; if(c->land == laOcean) { if(!from) deepOcean = true; else for(int i=0; itype; i++) { cell *c2 = from->mov[i]; - if(c2 && c2->landparam > 30) + if(c2 && c2->land == laOcean && c2->landparam > 30) { + deepOcean = true; + } + if(c2) forCellEx(c3, c2) if(c3 && c3->land == laOcean && c3->landparam > 30) deepOcean = true; } } @@ -2290,30 +2839,44 @@ void buildBigStuff(cell *c, cell *from) { // buildgreatwalls if(chaosmode) { - if(c->type == 7 && hrand(10000) < 9000 && buildBarrierNowall(c, getNewLand(c->land))) - ; + if(c->type == 7 && hrand(10000) < 9000 && c->land && buildBarrierNowall(c, getNewLand(c->land))) + {} } - else if(c->type == 7 && isWarped(c->land) && hrand(10000) < 3000 && - buildBarrierNowall(c, eLand(c->land ^ laGridSea ^ laGridCoast))) ; + else if(c->type == 7 && isWarped(c->land) && hrand(10000) < 3000 && c->land && + buildBarrierNowall(c, eLand(c->land ^ laWarpSea ^ laWarpCoast))) ; - else if(c->type == 7 && c->land == laCrossroads4 && hrand(10000) < 7000 && + else if(c->type == 7 && c->land == laCrossroads4 && hrand(10000) < 7000 && c->land && buildBarrierNowall(c, getNewLand(laCrossroads4))) ; else if(c->type == 7 && hrand(10000) < 20 && !generatingEquidistant && !yendor::on && !tactic::on && !isCrossroads(c->land) && gold() >= 200 && !isSealand(c->land) && !isHaunted(c->land) && !isGravityLand(c->land) && - c->land != laTortoise && + (c->land != laRlyeh || rlyehComplete()) && + c->land != laTortoise && c->land != laPrairie && c->land && !(c->land == laGraveyard && !deepOcean) - ) + ) { buildBarrierNowall(c, laCrossroads4) ; + } else if(c->land == laCrossroads2 && !purehepta) buildCrossroads2(c); + + else if(c->land == laPrairie && c->LHU.fi.walldist == 0) { + for(int bd=0; bd<7; bd++) { + int fval2 = createStep(c->master, bd)->fieldval; + int wd = fp43.gmul(fval2, fp43.inverses[c->fval-1]); + if(fp43.distwall[wd] == 0) { + buildBarrier(c, bd); + break; + } + } + } - else if(c->type == 7 && hrand(10000) < ( + else if(c->type == 7 && c->land && hrand(10000) < ( showoff ? (cwt.c->mpdist > 7 ? 0 : 10000) : isGravityLand(c->land) ? 0 : generatingEquidistant ? 0 : + c->land == laPrairie ? 0 : (yendor::on && yendor::nexttostart) ? 10000 : princess::challenge ? 0 : isElemental(c->land) ? 4000 : @@ -2321,10 +2884,11 @@ void buildBigStuff(cell *c, cell *from) { c->land == laCrossroads3 ? 10000 : c->land == laCrossroads ? 5000 : c->land == laCrossroads2 ? 10000 : + c->land == laCrossroads5 ? 10000 : c->land == laCrossroads4 ? 0 : (tactic::on && !tactic::trailer) ? 0 : c->land == laCaribbean ? 500 : - (c->land == laGridSea || c->land == laGridCoast) ? 500 : + (c->land == laWarpSea || c->land == laWarpCoast) ? 500 : c->land == laStorms ? 250 : c->land == laCanvas ? 0 : c->land == laHaunted ? 0 : @@ -2343,7 +2907,9 @@ void buildBigStuff(cell *c, cell *from) { buildBarrier4(c, bd, 0, getNewLand(c->land), c->land); */ } - if((!chaosmode) && bearsCamelot(c->land) && c->type == 7 && hrand(2000) < 200 && items[itEmerald] >= 5 && !tactic::on) { + if((!chaosmode) && bearsCamelot(c->land) && c->type == 7 && + (cheatdest == laCamelot || (hrand(2000) < 200 && + items[itEmerald] >= 5 && !tactic::on))) { int rtr = newRoundTableRadius(); heptagon *alt = createAlternateMap(c, rtr+14, hsOrigin); if(alt) { @@ -2356,14 +2922,21 @@ void buildBigStuff(cell *c, cell *from) { // buildbigstuff - if(c->land == laRlyeh && c->type == 7 && hrand(2000) < 100 && + if(c->land == laRlyeh && c->type == 7 && + (cheatdest == laTemple || (hrand(2000) < 100 && items[itStatue] >= 5 && !randomPatternsMode && - !tactic::on && !yendor::on) + !tactic::on && !yendor::on))) createAlternateMap(c, 2, hsA); - if(c->land == laOvergrown && c->type == 7 && hrand(2000) < 25 && + if(c->land == laJungle && c->type == 7 && + (cheatdest == laMountain || (hrand(2000) < 100 && + !randomPatternsMode && !tactic::on && !yendor::on && landUnlocked(laMountain)))) + createAlternateMap(c, 2, hsA); + + if(c->land == laOvergrown && c->type == 7 && + (cheatdest == laClearing || (hrand(2000) < 25 && !randomPatternsMode && items[itMutant] >= 5 && - !tactic::on && !yendor::on) { + !tactic::on && !yendor::on))) { heptagon *h = createAlternateMap(c, 2, hsA); if(h) clearing::bpdata[h].root = NULL; } @@ -2373,21 +2946,22 @@ void buildBigStuff(cell *c, cell *from) { if(h) h->alt->emeraldval = hrand(2); } - if(c->land == laOcean && c->type == 7 && hrand(2000) < (purehepta ? 500 : 1000) && deepOcean && !tactic::on && !yendor::on && !generatingEquidistant) + if(c->land == laOcean && c->type == 7 && deepOcean && !generatingEquidistant && + (cheatdest == laWhirlpool || ( + hrand(2000) < (purehepta ? 500 : 1000) && !tactic::on && !yendor::on))) createAlternateMap(c, 2, hsA); if(c->land == laCaribbean && c->type == 7) createAlternateMap(c, 2, hsA); - if(c->land == laPalace && c->type == 7 && !princess::generating && !shmup::on && + if(c->land == laPalace && c->type == 7 && !princess::generating && !shmup::on && multi::players == 1 && (princess::forceMouse ? (from && from->pathdist != INF) : (hrand(2000) < 20)) && !c->master->alt && (princess::challenge || kills[moVizier]) && !tactic::on && !yendor::on) createAlternateMap(c, 141, hsOrigin, waPalace); } - if(c->bardir != NODIR && c->bardir != NOBARRIERS) - extendBarrier(c); + if(hasbardir(c)) extendBarrier(c); } void buildIvoryTower(cell *c) { @@ -2470,12 +3044,201 @@ void buildIvoryTower(cell *c) { else c->wall = waCIsland; } +int dungeonFlags(cell *c) { + if(!c) return 0; + buildEquidistant(c); + bool rdepths[5]; + + cell *c2 = c; + cell *c3 = c; + + int switchcount = 0; + for(int i=0; i<5; i++) { + if(coastvalEdge(c2) == 0) { + rdepths[i] = false; + } + else { + cell *c4 = c2; + if(c2 != c3 && !isNeighbor(c2, c3)) { + for(int i=0; itype; i++) if(c2->mov[i] && isNeighbor(c2->mov[i], c3)) + c4 = c2->mov[i]; + } + rdepths[i] = c2 && c3 && c4 && (c2->landflags == 3 || c3->landflags == 3 || c4->landflags == 3); + if((c2&&c2->landflags == 1) || (c3&&c3->landflags == 1) || (c4&&c4->landflags == 1)) + switchcount++; + c2 = chosenDown(c2, 1, 0); // if(!c2) break; + c3 = chosenDown(c3, -1, 0); + if(!c2) { raiseBuggyGeneration(c, "ivory c2"); return 0; } + if(!c3) { raiseBuggyGeneration(c, "ivory c3"); return 0; } + } + } + + int res = 0; + + if(rdepths[3]) res |= 1; + if(rdepths[2]) res |= 2; + if(switchcount&1) res |= 4; + + return res; + } + +void placeGate(cell *c, eWall w) { + if(w == waOpenGate) { + c->wall = waClosedGate; + toggleGates(c, waOpenPlate, 0); + } + if(w == waClosedGate) { + c->wall = waOpenGate; + toggleGates(c, waClosePlate, 0); + } + } + +bool isGate(eWall w) { + return w == waOpenGate || w == waClosedGate; + } + +void placeRandomGate(cell *c) { + placeGate(c, hrand(2) ? waOpenGate : waClosedGate); + } + +void buildDungeon(cell *c) { + /* if(int(c->landparam) % 5 == 0) + c->wall = waCamelot; + */ + + if(true) { + + if(coastvalEdge(c) == 1) forCellEx(c2, c) + if(c2->land != laBarrier && c2->land != laDungeon) { + c->wall = waLadder; + c->wparam = 3; + } + + int df = dungeonFlags(c); + + if(df&1) { + int df1 = dungeonFlags(chosenDown(c,1,1)); + int df2 = dungeonFlags(chosenDown(c,-1,-1)); + + c->wparam = 0; + if(hrand(100) < (c->landparam % 5 == 0 ? 80 : 20)) { + if(!(df1&1)) c->wparam = 1; + if(!(df2&1)) c->wparam = 2; + } + + if(df&4) + placeRandomGate(c); + else if(c->wparam == 0 && c->landparam % 5 == 0 && hrand(100) < 10) { + c->wall = waLadder; + c->wparam = 3 + hrand(2); + } + else + c->wall = waPlatform; + } + + if(c->wparam) { + /* int q = 0; + cell* downs[7]; + forCellEx(c2, c) { + buildEquidistant(c2); + if(coastvalEdge(c2) > coastvalEdge(c)) downs[q++] = c2; + } + if(q) downs[hrand(q)]->wall = waLadder; + */ + cell *c2 = + c->wparam == 1 ? chosenDown(c, 1, 2) : + c->wparam == 2 ? chosenDown(c, -1, -2) : + c->wparam == 3 ? chosenDown(c, 1, 3) : + c->wparam == 4 ? chosenDown(c, -1, -3) : + NULL; + + if(c2) { + c2->wall = c->wall, c2->wparam = c->wparam; + if(c2->wall == waPlatform && hrand(10) < 2) + placeRandomGate(c2); + if(isGate(c2->wall) && hrand(10) < 2) + c2->wall = waPlatform; + } + } + } + else c->wall = waCIsland; + } + +bool is46(int i) { return i == 4 || i == 6; } + +void buildDungeonPlates(cell *c) { + if(c->wall) return; + int neargate = 0; + int neargateDown = 0; + int neargateEq = 0; + int qup = 0; + forCellEx(c2, c) { + int d = coastvalEdge(c2) - coastvalEdge(c); + if(isGate(c2->wall)) { + neargate++; + if(d>0) neargateDown++; + if(d==0) neargateEq = 0; + } + if(d<0) qup++; + } + + if(!neargate) return; + + int hr = 99; + + if(neargate == neargateDown && qup == 1) + hr = hrand(12); + else if((zebra40(c) >= 40) && !(neargateEq && neargateDown)) + hr = hrand(36); + else if(zebra40(c) >= 40) + hr = hrand(5000); + + if(hr < 5) c->wall = waClosePlate; + else if(hr < 10) c->wall = waOpenPlate; + } + +bool redtrolls(cell *c) { + return false; /* + int cd = getCdata(c, 2); + cd &= 63; + return cd < 32; */ + } + +eMonster pickTroll(cell *c) { + if(redtrolls(c)) + return pick(moTroll,moDarkTroll,moRedTroll); + else + return pick(moForestTroll, moStormTroll, moFjordTroll); + } + +void dieTroll(cell *c, eMonster m) { + if(m == moTroll) c->wall = waDeadTroll; + else if(m == moDarkTroll) c->wall = waDeadfloor2; + else if(m == moRedTroll) c->wall = waRed1; + else c->wall = waDeadTroll2, c->wparam = m; + } + +namespace halloween { + cell *dragoncells[4]; + } + +int reptilemax() { + int i = items[itDodeca] + yendor::hardness(); + if(i >= 245) return 5; + int r = (250 - i); + if(shmup::on) r = (r+2) / 3; + return r; + } + +bool is02(int i) { return i == 0 || i == 2; } + // This function generates all lands. Warning: it's very long! void setdist(cell *c, int d, cell *from) { if(signed(c->mpdist) <= d) return; if(c->mpdist > d+1 && d != BARLEV) setdist(c, d+1, from); c->mpdist = d; + // printf("setdist %p %d [%p]\n", c, d, from); if(d <= 3) lastexplore = shmup::on ? shmup::curtime : turncount; @@ -2488,26 +3251,39 @@ void setdist(cell *c, int d, cell *from) { } if(d >= BARLEV) { - - if(!c->land && from->land != laElementalWall && from->land != laHauntedWall) c->land = from->land; - if(c->land == laTemple && !tactic::on && !chaosmode) c->land = laRlyeh; - if(c->land == laClearing && !tactic::on) c->land = laOvergrown; - if(c->land == laWhirlpool && !tactic::on && !yendor::on) c->land = laOcean; - if(c->land == laCamelot && !tactic::on) c->land = laCrossroads; + + if(!c->land && from->land != laElementalWall && from->land != laHauntedWall && from->land != laOceanWall && + from->land != laBarrier) { + if(!hasbardir(c)) setland(c, from->land); + } + if(c->land == laTemple && !tactic::on && !chaosmode) setland(c, laRlyeh); + if(c->land == laMountain && !tactic::on && !chaosmode) setland(c, laJungle); + if(c->land == laClearing && !tactic::on) setland(c, laOvergrown); + if(c->land == laWhirlpool && !tactic::on && !yendor::on) setland(c, laOcean); + if(c->land == laCamelot && !tactic::on) setland(c, laCrossroads); if(euclid) setLandEuclid(c); + if(sphere) setLandSphere(c); + if(quotient) { setland(c, euclidland); setLandQuotient(c); } - // if(chaosmode) c->land = getCLand(c); + // if(chaosmode) setland(c, getCLand(c)); } -#ifndef MOBILE if(d == BARLEV && c->land == laCanvas) { c->landparam = mapeditor::generateCanvas(c); } -#endif int hard = yendor::hardness(); + if(d >= BARLEV-1 && c->land == laPrairie) + prairie::spread(c, from); + + if(d < BARLEV && c->land == laPrairie && !c->landparam) { + printf("d=%d/%d\n", d, BARLEV); + raiseBuggyGeneration(c, "No landparam set"); + return; + } + if(d == BARLEV && !euclid && c != cwt.c) buildBigStuff(c, from); @@ -2526,6 +3302,21 @@ void setdist(cell *c, int d, cell *from) { if(d == BARLEV-2 && (c->land == laGraveyard || c->land == laHauntedBorder || c->land == laHaunted)) buildEquidistant(c); + if(d == 7 && c->land == laPrairie) { + if(prairie::isriver(c)) { + if(shmup::on) prairie::beaststogen.push_back(c); + else prairie::generateBeast(c); + } + else if(!prairie::nearriver(c) && c->LHU.fi.flowerdist > 7) { + if(hrand(3000) < items[itGreenGrass] - (prairie::isriver(cwt.c) ? 14 : 0)) + c->monst = moGadfly; + else buildPrizeMirror(c, 1000); + } + } + + if(d == 7) + prairie::generateTreasure(c); + if(d <= 7 && (c->land == laGraveyard || c->land == laHauntedBorder)) { c->land = (c->landparam >= 1 && c->landparam <= HAUNTED_RADIUS) ? laHauntedBorder : laGraveyard; } @@ -2534,23 +3325,49 @@ void setdist(cell *c, int d, cell *from) { buildEquidistant(c); } - if(d == 8 && c->land == laIvoryTower && !euclid) { + if(d == 8 && (c->land == laIvoryTower || c->land == laDungeon) && !euclid) { - if(hrand(1000) < 75 && // chosenDown(c, 1, 0)->landflags != 3 && chosenDown(c,-1,0)->landflags != 3 && - (c->landparam&1) ) { + if(hrand(1000) < 75 && (c->landparam & 1) ) { c->landflags = 3; } else c->landflags = 0; } + if(d == 8 && c->land == laDungeon && !euclid) { + + if(hrand(1000) < 240 && is02(c->landparam%5) ) { + c->landflags = 3; + } + else if(hrand(1000) < 90) + c->landflags = 1; + else c->landflags = 0; + } + if(d == 7 && c->land == laIvoryTower) buildIvoryTower(c); + if(d == 8 && c->land == laDungeon) buildDungeon(c); + if(d == 7 && c->land == laDungeon) buildDungeonPlates(c); if(d == 9 && c->land == laPalace) { if(cdist50(c) == 3 && polarb50(c) == 1) c->wall = waPalace; } - if(d == 8 && c->land == laPalace) { + if(d == 8 && c->land == laPalace && sphere) { + if(getHemisphere(c,0) == 1) + c->wall = waPalace; + if(getHemisphere(c,0) == 3) + c->wall = purehepta ? waOpenGate : waClosedGate; + if(getHemisphere(c,0) == 4 && hrand(100) < 40) + c->wall = waClosePlate; + if(getHemisphere(c,0) == 6) + c->wall = waOpenPlate; + if(getHemisphere(c,0) == -3) + c->wall = pick(waClosePlate, waOpenPlate); + if(getHemisphere(c,0) == -6) + c->wall = waTrapdoor; + } + + if(d == 8 && c->land == laPalace && !sphere) { // note: Princess Challenge brings back the normal Palace generation bool lookingForPrincess = !euclid && c->master->alt && !princess::challenge; @@ -2560,7 +3377,7 @@ void setdist(cell *c, int d, cell *from) { int i = fiftyval049(c); if(i >= 8 && i <= 14 && !polarb50(c)) pgate = true; } - + if(pgate) { switch(princess::generating ? 0 : hrand(2)) { case 0: @@ -2614,6 +3431,11 @@ void setdist(cell *c, int d, cell *from) { if(((y-2)&7) < 4) c->wall = waCavewall; else c->wall = waCavefloor; } + else if(sphere) { + if(getHemisphere(c, 0) < 0) + c->wall = waCavewall; + else c->wall = waCavefloor; + } else if(purehepta) { if(polarb50(c)) c->wall = waCavewall; @@ -2652,7 +3474,7 @@ void setdist(cell *c, int d, cell *from) { itOrbLightning, itOrbLightning, itOrbThorns, itOrbThorns, itOrbInvis, itOrbInvis, itOrbShield, itOrbTeleport, itOrbPsi, - itOrbDragon, itOrbIllusion, itOrbPreserve + itOrbDragon, itOrbIllusion, itOrbTime }; c->item = protectedItems[hrand(18)]; } @@ -2715,11 +3537,96 @@ void setdist(cell *c, int d, cell *from) { // mapgen9 if(isWarped(c->land) && randomPatternsMode) - c->land = RANDPAT ? laGridCoast : laGridSea; - + setland(c, RANDPAT ? laWarpCoast : laWarpSea); + // if(c->land == laIce && ((celldist(c) % 10) + 10) % 10 == 5) // c->wall = waColumn; - + + if(c->land == laReptile) { + int i = zebra40(c); + if(i < 40) { + int cd = getCdata(c, 3); + cd &= 15; + if(cd >= 4 && cd < 12) c->wall = waChasm; + else { + int ch = hrand(200); + c->wall = ch < (50 + items[itDodeca] + hard) ? waReptile : + waNone; + } + c->wparam = 1 + hrand(reptilemax()); + } + } + + if(c->land == laBurial) { + if(hrand(5000) < 25 && celldist(c) >= 5 && !safety) { + c->item = itBarrow; + c->landparam = 2 + hrand(2); + c->wall = waBarrowDig; + forCellCM(c2, c) c2->wall = waBarrowWall, c2->item = itNone; + cell *c2 = createMov(c, hrand(c->type)); + c2->wall = waBarrowDig; + forCellCM(c3, c2) { + if(c3 == c || isNeighbor(c3, c)) continue; + bool adj = false; + forCellEx(c4, c) + if(c4 != c2 && isNeighborCM(c3, c4)) adj = true; + if(adj) + c3->wall = waBarrowDig; + else + c3->wall = waBarrowWall, c3->item = itNone; + } + } + + /* if(hrand(25000) < PT(25 + 2 * kills[moDraugr], 40) && notDippingFor(itBarrow)) { + c->item = itBarrow; + c->wall = waBarrowCenter; + forCellCM(c2, c) c2->wall = waBarrowCenter, c2->item = itBarrow; + forCellCM(c2, c) forCellCM(c3, c2) if(!isNeighbor(c3, c) && c3 != c) + c3->wall = waBarrowWall, c3->item = itNone; + int i = hrand(c->type); + cell *c2, *c3; + while(true) { + c2 = createMov(c, i); + c3 = createMov(c2, hrand(c2->type)); + if(c3 != c && !isNeighbor(c3, c)) break; + } + c3->wall = waBarrowCenter; + forCellCM(c4, c3) { + if(c4 == c2 || isNeighbor(c4, c2)) continue; + bool adj = false; + forCellEx(c5, c2) if(c5 != c3 && isNeighbor(c5, c4)) adj = true; + if(adj) + c4->wall = waBarrowCenter; + else + c4->wall = waBarrowWall, c4->item = itNone; + } + } */ + } + + if(c->land == laTrollheim) { + if(hrand(50000) < (chaosmode?1000:5) && c->wall != waBarrier && celldist(c) >= 7 && !safety) { + bool okay = true; + forCellCM(c2, c) forCellCM(c3, c2) forCellCM(c4, c3) forCellCM(c5, c4) { + cell *cx = chaosmode ? c3 : c5; + if(cx->land != laTrollheim && cx->land != laNone) + okay = false; + if(cx->bardir != NODIR) okay = false; + } + if(okay) { + forCellCM(c2, c) forCellCM(c3, c2) forCellCM(c4, c3) forCellCM(c5, c4) { + cell *cx = chaosmode ? c3 : c5; + cx->bardir = NOBARRIERS; + setland(cx, laTrollheim); + } + c->item = itTrollEgg; + forCellCM(c2, c) forCellCM(c3, c2) { + c3->monst = pickTroll(c); + c2->item = itTrollEgg; + } + } + } + } + if(c->land == laIce) { if(randomPatternsMode) c->wall = RANDPAT ? waIcewall : waNone; else if(hrand(100) < 5 && c->wall != waBarrier) { @@ -2752,6 +3659,9 @@ void setdist(cell *c, int d, cell *from) { if(c->land == laCaves) c->wall = (randomPatternsMode ? RANDPAT3(1) : hrand(100) < 55) ? waCavewall : waCavefloor; + if(c->land == laCA) + c->wall = (hrand(1000000) < ca::prob * 1000000) ? waFloorA : waNone; + if(c->land == laLivefjord) { int die = (randomPatternsMode ? (RANDPAT3(2)?100:0) : hrand(100)); if(die < 50) @@ -2788,6 +3698,39 @@ void setdist(cell *c, int d, cell *from) { (hrand(50+items[itMutant]/2+hard) < 30) ? (hrand(100) < 50 ? waBigTree : waSmallTree) : waNone; } + if(c->land == laHalloween) { + if(purehepta) { + int fv = c->master->fiftyval; + if(fv == 1 || fv == 4 || fv == 2) + c->wall = waChasm; + if(fv == 3) c->item = itTreat; + } + else { + if(c->type == 5) { + int fv = c->master->fiftyval; + if(fv == 3 || fv == 4 || fv == 2 || fv == 5) + c->wall = waChasm; + if(fv == 2) halloween::dragoncells[0] = c; + if(fv == 5) halloween::dragoncells[3] = c; + if(fv == 1) c->item = itTreat; + } + if(c->type == 6 && !euclid) { + int fvset = 0; + for(int i=0; i<6; i+=2) fvset |= 1 << createMov(c, i)->master->fiftyval; + if(fvset == 35 || fvset == 7) c->wall = waChasm; + if(fvset == 7) halloween::dragoncells[1] = c; + if(fvset == 35) halloween::dragoncells[2] = c; + } + } + if(quotient && zebra40(c) == 7) { + c->item = itTreat; + halloween::dragoncells[0] = NULL; + } + if(quotient && zebra40(c) == 5) { + c->wall = waChasm; + } + } + if(c->land == laWildWest) { if(randomPatternsMode) c->wall = RANDPAT ? waNone : waSaloon; @@ -2800,11 +3743,14 @@ void setdist(cell *c, int d, cell *from) { } if(c->land == laWhirlwind) { - if(!euclid && zebra3(c) == 0) c->wall = waFan; + if(sphere) + c->wall = (pseudohept(c) && (c->master->fiftyval == 0 || c->master->fiftyval == 6)) ? + waFan : waNone; + else if(!euclid && zebra3(c) == 0) c->wall = waFan; else if(pseudohept(c) && hrand(2000) < 150) c->wall = waChasm; - else if(d == 7 && hrand(1000) == 0) - buildPrizeMirror(c); + else if(c->type == 6 && buildPrizeMirror(c, 1000)) + {} else whirlwind::switchTreasure(c); @@ -2836,6 +3782,13 @@ void setdist(cell *c, int d, cell *from) { c->wall = waSandstone; } } + else if(sphere) { + if(c->type != 6) { + int id = c->master->fiftyval; + if(id == 1) c->wall = waCharged; + if(id == (elliptic && !purehepta ? 3 : 9)) c->wall = waGrounded; + } + } else if(purehepta) { int i = zebra40(c); if((i == 5 || i == 8) && hrand(100) < 20) c->wall = hrand(2) ? waCharged : waGrounded; @@ -2925,12 +3878,12 @@ void setdist(cell *c, int d, cell *from) { if(d <= 150) generateAlts(c->master); } - if((bearsCamelot(c->land) && !euclid) || c->land == laCamelot) + if((bearsCamelot(c->land) && !euclid && !quotient) || c->land == laCamelot) if(euclid || c->master->alt) { int d = celldistAltRelative(c); if(tactic::on || (d <= 14 && roundTableRadius(c) > 20)) { if(!euclid) generateAlts(c->master); - c->bardir = NOBARRIERS; + preventbarriers(c); if(d == 10) { if(pseudohept(c)) buildCamelotWall(c); else { @@ -2944,8 +3897,16 @@ void setdist(cell *c, int d, cell *from) { // towers of Camelot if(q == 0 && !purehepta) { c->monst = moKnight; - for(int i=0; i<6; i++) - buildCamelotWall(c->mov[i]); + c->wall = waTower; + forCellEx(c2, c) { + int cr = celldistAltRelative(c2); + if(cr == 9) ; + else { + buildCamelotWall(c2); + if(c2->type == 6) + c2->wall = waTower, c2->wparam = 1; + } + } for(int i=0; itype; i++) if(celldistAltRelative(c->mov[i]) < d) c->mondir = i; } @@ -2972,7 +3933,7 @@ void setdist(cell *c, int d, cell *from) { c->item = itGreenStone; if(d <= 10) c->land = laCamelot; if(d > 10 && !euclid && !tactic::on) { - c->land = eLand(c->master->alt->alt->fiftyval); + setland(c, eLand(c->master->alt->alt->fiftyval)); if(c->land == laNone) printf("Camelot\n"); // NONEDEBUG } } @@ -2981,7 +3942,7 @@ void setdist(cell *c, int d, cell *from) { if((c->land == laStorms && !euclid)) { if(c->master->alt && c->master->alt->distance <= 2) { generateAlts(c->master); - c->bardir = NOBARRIERS; + preventbarriers(c); int d = celldistAlt(c); if(d <= -2) { c->wall = (c->master->alt->alt->emeraldval & 1) ? waCharged : waGrounded; @@ -3004,7 +3965,7 @@ void setdist(cell *c, int d, cell *from) { else if((c->land == laRlyeh && !euclid) || c->land == laTemple) { if(euclid || (c->master->alt && (tactic::on || c->master->alt->distance <= 2))) { if(!euclid && !chaosmode) generateAlts(c->master); - c->bardir = NOBARRIERS; + preventbarriers(c); int d = celldistAlt(c); if(d <= 0) { c->land = laTemple, c->wall = waNone, c->monst = moNone, c->item = itNone; @@ -3025,19 +3986,30 @@ void setdist(cell *c, int d, cell *from) { } } - if((c->land == laOvergrown && !euclid) || c->land == laClearing) { + if((c->land == laOvergrown && !euclid) || c->land == laClearing) { if(euclid || (c->master->alt && (tactic::on || c->master->alt->distance <= 2))) { if(!euclid) generateAlts(c->master); - c->bardir = NOBARRIERS; + preventbarriers(c); int d = celldistAlt(c); if(d <= 0) { c->land = laClearing, c->wall = waNone; // , c->monst = moNone, c->item = itNone; } - else if(d == 1 && !tactic::on) + else if(d == 1 && !tactic::on && !euclid) c->wall = waSmallTree, c->monst = moNone, c->item = itNone; } } + if((c->land == laJungle && !euclid) || c->land == laMountain) { + if(euclid || (c->master->alt && (tactic::on || c->master->alt->distance <= 2))) { + if(!euclid) generateAlts(c->master); + preventbarriers(c); + int d = celldistAlt(c); + if(d <= 0 || (firstland == laMountain && tactic::on)) { + c->land = laMountain, c->wall = waNone; // , c->monst = moNone, c->item = itNone; + } + } + } + if(c->land == laOcean || c->land == laWhirlpool) { bool fullwhirlpool = false; if(tactic::on && tactic::lasttactic == laWhirlpool) @@ -3046,7 +4018,7 @@ void setdist(cell *c, int d, cell *from) { fullwhirlpool = true; if(euclid || (c->master->alt && (fullwhirlpool || c->master->alt->distance <= 2))) { if(!euclid) generateAlts(c->master); - c->bardir = NOBARRIERS; + preventbarriers(c); int dd = celldistAlt(c); if(dd <= 0 || fullwhirlpool) { c->land = laWhirlpool, c->wall = waSea, c->monst = moNone, c->item = itNone; @@ -3054,11 +4026,15 @@ void setdist(cell *c, int d, cell *from) { } } + if(c->land == laKraken) { + c->wall = waSea; + } + if(c->land == laCaribbean) { if(!euclid) { if(c->master->alt && c->master->alt->distance <= 2) { if(!euclid) generateAlts(c->master); - c->bardir = NOBARRIERS; + preventbarriers(c); int d = celldistAlt(c); if(d <= 0) // c->wall = waChasm; @@ -3103,7 +4079,7 @@ void setdist(cell *c, int d, cell *from) { #undef PSH } } - + if(isHive(c->land) && hrand(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) @@ -3163,18 +4139,86 @@ void setdist(cell *c, int d, cell *from) { int i = hrand(7); buildRedWall(createMovR(c, i), 33); if(hrand(2) == 0) - buildRedWall(createMovR(createMovR(c, i), c->spn[i]+(hrand(2)?2:4)), 20); + buildRedWall(createMovR(createMovR(c, i), c->spn(i)+(hrand(2)?2:4)), 20); i += 3 + hrand(2); if(hrand(6) < 4) buildRedWall(createMovR(c, i), 33); if(hrand(2) == 0) - buildRedWall(createMovR(createMovR(c, i), c->spn[i]+(hrand(2)?2:4)), 20); + buildRedWall(createMovR(createMovR(c, i), c->spn(i)+(hrand(2)?2:4)), 20); + } + } + } + + if(d == 7 && c->land == laReptile && c->wall != waChasm) { + bool nonchasm = false; + forCellEx(c2, c) if(c2->wall != waChasm) nonchasm = true; + if(!nonchasm) c->item = itDodeca; + } + + + if(d == 7 && c->land == laKraken && c->wall == waSea && !c->item && !c->monst && !safety) { + if(hrand(2000) < 3) { + c->wall = waBoat; + c->item = itOrbFish; + c->monst = moViking; + } + else if(hrand(2000) < 16) c->item = itOrbFish; + else if(hrand(500) < kills[moKrakenH]) c->item = itKraken; + else placeLocalOrbs(c); + } + + if(d == 7 && c->land == laTrollheim && !c->monst && !c->item && celldist(c) >= 3) { + int cd = getCdata(c, 3); + cd %= 16; + if(cd<0) cd += 16; + if(cd >= 4 && cd < 10 && hrand(100) < 40) + dieTroll(c, pickTroll(c)); + } + + if(d == 8 && c->land == laBull && !c->monst && !c->item && celldist(c) >= 3) { + /* int cd = getCdata(c, 3); + cd &= 15; + int ce = getCdata(c, 2); + ce &= 15; + if(cd >= 8 && ce >= 8) */ + if(hrand(100) < 25) + c->wall = safety ? pick(waBigTree, waSmallTree) : pick(waStone, waBigTree, waSmallTree); + } + + if(d == 7 && c->land == laBull && c->wall == waNone && !safety) { + if(hrand(1000) < 20) c->monst = moSleepBull; + else if(hrand(2500) < items[itBull] + hard - 10) c->monst = moGadfly; + else if(hrand(1000) < 50) { + int nearwall = 0; + int walls[8]; + forCellIdEx(c2, i, c) if(c2->wall) walls[nearwall++] = i; + if(nearwall && nearwall < c->type) { + c->monst = moButterfly; + c->mondir = walls[hrand(nearwall)]; } } } if(d == 8) { - if(c->land == laGridCoast) { + + if(c->land == laKraken && !pseudohept(c) && hrand(20000) < 10 + 3 * items[itKraken] + 2 * hard && c->wall == waSea && !c->item && !c->monst && !safety) { + bool ok = true; + forCellEx(c2, c) + if(c2->wall != waSea || c2->item || c2->monst) + ok = false; + + if(ok) { + c->monst = moKrakenH; + forCellIdEx(c2, i, c) { + c2->monst = moKrakenT; + c2->hitpoints = 1; + c2->mondir = c->spn(i); + } + playSound(c, "seen-kraken"); + } + } + + if(c->land == laWarpCoast) { if(hrand(1000) < 150 && celldist(c) >= 3 && !pseudohept(c)) c->wall = waSmallTree; int q = 0; @@ -3184,7 +4228,7 @@ void setdist(cell *c, int d, cell *from) { if(q == 1) c->wall = waWarpGate; } - if(c->land == laGridSea) { + if(c->land == laWarpSea) { c->wall = waSea; int q = 0; if(!purehepta && !chaosmode) for(int i=0; itype; i++) @@ -3193,6 +4237,11 @@ void setdist(cell *c, int d, cell *from) { } } + if(d == 8 && c->land == laKraken && pseudohept(c) && hrand(1000) <= 2) { + c->wall = waNone; + forCellEx(c2, c) c2->wall = waNone; + } + if(d == 8 && c->land == laOvergrown) { if(hrand(15000) < 20 + (2 * items[itMutant] + hard) && !safety) { c->item = itMutant; @@ -3246,7 +4295,7 @@ void setdist(cell *c, int d, cell *from) { if(hrand(5000) < 30) c->item = itGreenStone; - if(hrand(4000) < 10 + items[itLotus] && !safety) + if(hrand(4000) < 10 + items[itLotus] + hard && !safety) c->monst = moGhost; int depth = getHauntedDepth(c); @@ -3256,9 +4305,9 @@ void setdist(cell *c, int d, cell *from) { } } - if(d == 7 && c->land == laRedRock && c->wall == waNone && hrand(1000) == 0) - buildPrizeMirror(c); - + if(d == 7 && c->land == laRedRock && c->wall == waNone) + buildPrizeMirror(c, 1000); + if(d == 8 && c->land == laRose && hrand(2000) < 100 && !c->wall && !c->item && !c->monst) { int nww = 0; for(int i=0; itype; i++) { @@ -3287,7 +4336,7 @@ void setdist(cell *c, int d, cell *from) { if(coast && hrand(10) < 5) { c->wall = waBoat; if(items[itPirate] >= 10 && hrand(100) < 2 && !safety) - c->item = itOrbPreserve; + c->item = itOrbTime; else if(hrand(100) < 2*PRIZEMUL && !safety) placePrizeOrb(c); } @@ -3320,8 +4369,8 @@ void setdist(cell *c, int d, cell *from) { if(d == 7 && c->land == laLivefjord && hrand(2000) < 2*PRIZEMUL) placePrizeOrb(c); - if(d == 7 && c->land == laLivefjord && hrand(2000) < 2) - buildPrizeMirror(c); + if(d == 7 && c->land == laLivefjord) + buildPrizeMirror(c, 1000); if(d == 7 && c->land == laEmerald && c->wall == waCavewall && hrand(5000) < items[itEmerald] + hard && !safety) c->monst = moSeep; @@ -3333,7 +4382,28 @@ void setdist(cell *c, int d, cell *from) { c->monst = moVineSpirit; if(d == 7 && c->land == laOcean && !safety) { - if((c->landparam >= 1 && c->landparam <= 25) || chaosmode) { + bool placecolumn = false; + if(c->landparam % TEMPLE_EACH == 0 && c->landparam <= 24) { + int q = 0; + forCellEx(c2, c) + if(c2->landparam % TEMPLE_EACH == 0 && !ishept(c2)) q++; + placecolumn = q == 2; + if(placecolumn) { + placecolumn = false; + cell *c2 = c; + seek: + forCellEx(c3, c2) if(c3->landparam < c2->landparam) { + c2 = c3; goto seek; + } + forCellEx(c3, c2) forCellEx(c4, c3) + if(c4->barleft == laRlyeh || c4->barright == laRlyeh || c4->land == laRlyeh) + placecolumn = true; + } + } + + if(placecolumn) + c->wall = waColumn; + else if((c->landparam >= 1 && c->landparam <= 25) || chaosmode) { if(hrand(1000) < 5) c->wall = waBoat; if(hrand(1000) < PT(50 + kills[moAlbatross]/2, 150)) @@ -3346,8 +4416,7 @@ void setdist(cell *c, int d, cell *from) { c->item = itOrbEmpathy; if(hrand(10000) < 5*PRIZEMUL) placePrizeOrb(c); - if(hrand(10000) < 5) - buildPrizeMirror(c); + buildPrizeMirror(c, 2000); } else if(c->landparam > 25) { int amberbonus = items[itCoast] - 50; @@ -3365,7 +4434,11 @@ void setdist(cell *c, int d, cell *from) { } } - if(d == 7 && c->land == laGridSea && c->wall == waSea && !safety) { + if(d == 7 && c->land == laWarpSea && c->wall == waSea && !safety) { + if(sphere && c->type != 6 && c->master->fiftyval == 6) { + c->wall = waBoat; + c->monst = moRatling; + } if(hrand(12000) < 30 + 2 * items[itCoral] + hard) { c->wall = waBoat; c->monst = moRatling; @@ -3411,7 +4484,7 @@ void setdist(cell *c, int d, cell *from) { // 1250: at 25 int minefreq = 0; int treas = items[itBombEgg]; - if(treas <= 10) minefreq = 250 + 30 * treas; + // if(treas <= 10) minefreq = 250 + 30 * treas; if(treas <= 110) minefreq = 550 + 10 * (treas-10); else minefreq = 1550 + (treas - 110); @@ -3472,7 +4545,7 @@ void setdist(cell *c, int d, cell *from) { if(!onwall) for(int i=0; i<7; i++) { cell *c2 = c->mov[i]; if(!c2) continue; - cell *c3 = c2->mov[(c->spn[i] + 3) % 6]; + cell *c3 = c2->mov[(c->spn(i) + 3) % 6]; if(c3->land != laPower && c3->land != laBarrier) if(c2->wall != waFire && c2->wall != waGlass) { if(isFire(c)) c->monst = moWitchWinter; @@ -3532,23 +4605,40 @@ void setdist(cell *c, int d, cell *from) { if(d == 7 && passable(c, NULL, 0) && !safety) { if(c->land == laIvoryTower) { - if(hrand(20000) < 20 + items[itEdge] + hard) { + if(hrand(20000) < 20 + items[itIvory] + hard) { if(cellEdgeUnstable(c)) c->monst = moGargoyle; else - c->monst = moEdgeMonkey; + c->monst = moFamiliar; } - else if(c->landparam >= 14 && hrand(2000) < PT(50+kills[moGargoyle]+kills[moEdgeMonkey], 150) && !cellEdgeUnstable(c) ) { - c->item = itEdge; + else if(c->landparam >= 14 && hrand(2000) < PT(50+kills[moGargoyle]+kills[moFamiliar], 150) && !cellEdgeUnstable(c) ) { + c->item = itIvory; + } + } + + if(c->land == laDungeon) { + int lp = c->landparam * c->landparam; + if(lp > 100) lp = 100; + if(c->landparam >= 10 && hrand(20000) < 5*lp + items[itSlime] + hard && !cellEdgeUnstable(c)) { + c->monst = moSkeleton, c->hitpoints = 3; + } + else if(c->landparam >= 10 && hrand(50000) < lp/2 + items[itSlime] + hard) { + c->monst = moGhost; + } + else if(c->landparam >= 10 && hrand(50000) < 3*(100-lp) + 80 + items[itSlime]/3 + hard) { + c->monst = moBat; + } + else if(c->landparam >= 15 && hrand(800) < min(PT(150 + 2 * kills[moBat], 250), 250) && !cellEdgeUnstable(c) && c->wall != waOpenGate) { + c->item = itSlime; } } if(c->land == laEndorian) { if(c->wall == waNone && coastval(c, laEndorian) >= 10 && hrand(5000) < 10 + 2 * (items[itApple] + hard)) - c->monst = moKestrel; + c->monst = moSparrowhawk; else if(c->wall != waNone && hrand(5000) < 10 + 2 * (items[itApple] + hard)) - c->monst = moLemur; - else if(c->wall == waCanopy && !checkInTree(c, 3) && hrand(5000) < PT(300 + 5 * (kills[moKestrel] + kills[moLemur]), 750)) + c->monst = moResearcher; + else if(c->wall == waCanopy && !checkInTree(c, 3) && hrand(5000) < PT(300 + 5 * (kills[moSparrowhawk] + kills[moResearcher]), 750)) c->item = itApple; } @@ -3559,8 +4649,13 @@ void setdist(cell *c, int d, cell *from) { c->monst = hrand(2) ? moYeti : moWolf; } + if(c->land == laTrollheim && !safety) { + if(hrand(8000) < items[itTrollEgg] + hardness_empty()) + c->monst = pickTroll(c); + } + if(c->land == laTortoise) { - if(hrand(4000) < 50 + items[itBabyTortoise]*2 && !safety) { + if(hrand(4000) < 50 + items[itBabyTortoise]*2 + hard * 6 && !safety) { c->monst = moTortoise; c->hitpoints = 3; } @@ -3582,13 +4677,13 @@ void setdist(cell *c, int d, cell *from) { c->item = hrand(100) < 80 ? itOrbFrog : itOrbDiscord; if(hrand(5000) < 20*PRIZEMUL && c->wall != waOpenGate) placePrizeOrb(c); - if(hrand(5000) < 20 && c->wall == waNone) - buildPrizeMirror(c); - if(c->land == laPalace && (euclid || c->master->alt) && celldistAlt(c) <= 150 && !havemouse && !princess::generating && + if(c->wall == waNone) buildPrizeMirror(c, 250); + if(c->land == laPalace && (euclid || c->master->alt) && celldistAlt(c) <= 150 && !(havewhat&HF_MOUSE) && !princess::generating && princess::getPrisonInfo(c) && (euclid || (princess::getPrisonInfo(c)->bestdist < 6 && princess::getPrisonInfo(c)->princess))) { c->monst = moMouse; addMessage(XLAT("You hear a distant squeak!")); + playSound(c, "mousesqueak"); drawBigFlash(c); /* { cell *c2= c; @@ -3600,7 +4695,7 @@ void setdist(cell *c, int d, cell *from) { goto z; } } */ - havemouse = true; + havewhat |= HF_MOUSE; } else if(hrand(15000) < 10 + hardness) { c->monst = moPalace; @@ -3664,16 +4759,16 @@ void setdist(cell *c, int d, cell *from) { i = t; if(i != -1) { c->monst = moHexSnake; - c->bardir = NOBARRIERS; + preventbarriers(c); int len = purehepta ? 2 : ROCKSNAKELENGTH; cell *c2 = c; vector rocksnake; while(--len) { rocksnake.push_back(c2); - c2->bardir = NOBARRIERS; + preventbarriers(c2); c2->mondir = i; createMov(c2, i); - int j = c2->spn[i]; + int j = c2->spn(i); cell *c3 = c2->mov[i]; if(c3->monst || c3->bardir != NODIR || c3->wall) break; c2 = c3; @@ -3695,14 +4790,14 @@ void setdist(cell *c, int d, cell *from) { // 40 is the usual rate of dragon generation int dchance = 40; // but it grows to 400 if no Dragons in sight, to make it faster - if(cwt.c->land == laDragon && !havedragon) + if(cwt.c->land == laDragon && !(havewhat&HF_DRAGON)) dchance = 400; // also, don't generate additional Dragons for newbies - else if(havedragon && items[itDragon] < 10) + else if((havewhat&HF_DRAGON) && items[itDragon] < 10) dchance = 5; if(hrand(150000) < dchance && !c->monst && (!c->wall || c->wall == waChasm)) { - havedragon = true; + havewhat |= HF_DRAGON; // printf("dragon generated with dchance = %d\n", dchance); vector possi; for(int t=0; t<6; t++) if(c->mov[t]->mpdist > c->mpdist) possi.push_back(t); @@ -3710,16 +4805,16 @@ void setdist(cell *c, int d, cell *from) { int i = possi[hrand(size(possi))]; int dragonlength = 6 + items[itDragon] / 2; c->monst = moDragonHead; c->hitpoints = 1; - c->bardir = NOBARRIERS; + preventbarriers(c); cell *c2 = c; int len = dragonlength; vector dragon; while(--len) { dragon.push_back(c2); - c2->bardir = NOBARRIERS; + preventbarriers(c2); c2->mondir = i; createMov(c2, i); - int j = c2->spn[i]; + int j = c2->spn(i); cell *c3 = c2->mov[i]; if(c3->monst || c3->bardir != NODIR || c3->wall || c3->mpdist <= 7) break; c2 = c3; @@ -3753,7 +4848,7 @@ void setdist(cell *c, int d, cell *from) { c->monst = moOrangeDog; } if(isElemental(c->land) && c->land != laElementalWall) { - eItem localshard = eItem(itFireShard + (c->land - laEFire)); + eItem localshard = localshardof(c->land); int danger = 5 * items[localshard] * items[localshard]; eMonster elof = elementalOf(c->land); int elkills = PT(kills[elof], 25); @@ -3782,6 +4877,16 @@ void setdist(cell *c, int d, cell *from) { static eMonster emeraldmonsters[4] = { moHedge, moLancer, moFlailer, moMiner }; c->monst = emeraldmonsters[hrand(4)]; } + if(sphere && c->type != 6 && c->master->fiftyval == 5) { + c->monst = moMiner; + c->stuntime = 7; + } + } + if(c->land == laBurial && !safety) { + if(hrand(15000) < 5 + 3 * items[itBarrow] + 4 * hard) + c->monst = moDraugr; + else if(hrand(5000) < 20) + c->item = itOrbSword; } if(c->land == laJungle) { if(hrand(5000) < PT(25 + 2 * (kills[moIvyRoot] + kills[moMonkey]), 40) && notDippingFor(itRuby)) @@ -3798,6 +4903,22 @@ void setdist(cell *c, int d, cell *from) { c->item = itRuby; } } + if(c->land == laMountain) { + if(hrand(50000) < 100) + buildIvy(c, 0, 3); + else if(hrand(125000) < 100 - celldistAlt(c)) + c->monst = moMonkey; + else if(hrand(200000) < min(100, -10*celldistAlt(c)) - celldistAlt(c)) + c->monst = moEagle; + else if(hrand(100) < 5) + c->wall = waPlatform; + else if(hrand(100) < 20) + c->wall = waBigBush; + else if(hrand(100) < 12) + c->wall = waSmallBush; + else if(hrand(500) < -celldistAlt(c) / 5 - items[itAmethyst]) + c->item = itAmethyst; + } if(c->land == laWhirlwind) { if(hrand(4500) < items[itWindstone] + hard) c->monst = moWindCrow; @@ -3847,7 +4968,7 @@ void setdist(cell *c, int d, cell *from) { if(c->land == laPower) { if(hrand(5000+50*items[itPower]) < 1200) { eItem powerorbs[6] = { - itOrbFlash, itOrbSpeed, itOrbFire, itOrbWinter, itOrbGhost, itOrbLife}; + itOrbFlash, itOrbSpeed, itOrbFire, itOrbWinter, itOrbAether, itOrbLife}; c->item = powerorbs[hrand(6)]; } else if(c->type == 6 && hrand(5000) < 10) @@ -3856,12 +4977,16 @@ void setdist(cell *c, int d, cell *from) { c->monst = eMonster(moWitch + hrand(NUMWITCH)); } if(isCrossroads(c->land)) { - if(c->type == 6 && hrand(8000) < 120 && (tactic::on && (isCrossroads(tactic::lasttactic) || items[itShard] >= 10))) + if(purehepta && c->land == laCrossroads5 && hrand(100) < 60) + c->wall = waBarrier; + else if(c->type == 6 && items[itShard] >= 10 && hrand(8000) < 120*orbcrossfun(items[itShard])) + c->wall = hrand(2) ? waMirror : waCloud; + else if(c->type == 6 && tactic::on && isCrossroads(tactic::lasttactic) && hrand(8000) < 120) c->wall = hrand(2) ? waMirror : waCloud; else if(c->land == laCrossroads4 && hrand(24000) < 10 && tactic::on) c->wall = waRose; else { - if(hyperstonesUnlocked() && hrand(25000) < PT(tkills(), 2000) && notDippingFor(itHyperstone)) + if(hyperstonesUnlocked() && hrand(25000) < min(PT(tkills(), 2000), 5000) && notDippingFor(itHyperstone)) c->item = itHyperstone; if(hrand(4000) < items[itHyperstone] + 2 * items[itHolyGrail] && !c->monst) { // only interesting monsters here! @@ -3885,7 +5010,7 @@ void setdist(cell *c, int d, cell *from) { c->monst = moEagle; } if(c->land == laGraveyard) { - if(hrand(5000) < PT(30 + 2 * (kills[moZombie] + kills[moGhost] + kills[moNecromancer]), 120) && notDippingFor(itBone)) + if(hrand(5000) < PT(30 + 4*kills[moZombie] + 6*kills[moNecromancer], 120) && notDippingFor(itBone)) c->item = itBone; if(hrand(20000) < 10 + items[itBone] + hard) { eMonster grm[6] = { moZombie, moZombie, moZombie, moGhost, moGhost, moNecromancer}; @@ -3930,7 +5055,7 @@ void setdist(cell *c, int d, cell *from) { c->monst = moCultistLeader; else if(hrand(5000) < 250) c->item = itGrimoire; - else if(hrand(5000) < 10 && -d > TEMPLE_EACH * 10) + else if(hrand(5000) < 10 && (chaosmode ? items[itGrimoire] >= 10 : -d > TEMPLE_EACH * 10)) c->item = itOrbDragon; } } @@ -3961,7 +5086,7 @@ void setdist(cell *c, int d, cell *from) { else if(hrand(24000) < 40 + items[itHell] * (chaosmode?4:1) + hard) c->monst = moGreater; } - if(c->land == laGridCoast) { + if(c->land == laWarpCoast) { if(hrand(12000) < 20 + 2 * items[itCoral] + hard) { c->monst = moRatling; c->stuntime = hrand(2); @@ -4015,7 +5140,9 @@ void setdist(cell *c, int d, cell *from) { } } -#ifndef MOBILE + if(d == 7) playSeenSound(c); + +#ifndef NOEDIT if(d >= 7 && mapeditor::whichPattern) mapeditor::applyModelcell(c); #endif @@ -4090,29 +5217,82 @@ bool canReachPlayer(cell *cf, eMonster m) { return false; } +bool haveOrbPower() { + for(int i=0; iitem) == IC_ORB) return true; + } + else if(sphere) for(int i=0; iitem) == IC_ORB) return true; + forCellEx(c2, c) if(itemclass(c2->item) == IC_ORB) return true; + } + return false; + } + +bool haveKraken() { + for(int i=0; imonst == moKrakenH || c->monst == moKrakenT) return true; + } + return false; + } + void wandering() { if(!canmove) return; int seepcount = getSeepcount(); int ghostcount = getGhostcount(); + if(cwt.c->land == laCA) ghostcount = 0; if(cwt.c->land == laZebra && cwt.c->wall == waNone && wchance(items[itZebra], 20)) wanderingZebra(cwt.c); + + if(sphere || quotient == 1) { + int maxdist = 0; + for(int i=0; icpdist > maxdist) maxdist = dcal[i]->cpdist; + for(int i=0; icpdist >= maxdist-1) { first7 = i; break; } + + if(hrand(5) == 0) { + // spawn treasure + } + } while(first7 < size(dcal)) { int i = first7 + hrand(size(dcal) - first7); cell *c = dcal[i]; + + if((sphere || quotient == 1) && !c->item && hrand(5) == 0 && c->land != laHalloween) { + if(passable(c, NULL, 0) || euclidland == laKraken) { + if(!haveOrbPower() && euclidland != laHell) for(int it=0; it<1000 && !c->item; it++) + placeLocalOrbs(c); + if(!c->item) c->item = wanderingTreasure(c); + if(c->item == itShard) { + c->item = itNone, c->wall = hrand(2) ? waMirror : waCloud; + } + if(c->item == itFulgurite) { + c->item = itNone, c->wall = waSandstone; + } + if(c->item == itBarrow) + c->landparam = 2 + hrand(2), + c->wall = waBarrowDig; + } + } + if(!c->monst) c->stuntime = 0; - if(timerghost) { + if(timerghost && !sphere && quotient != 1) { // wandering seeps & ghosts if(seepcount && c->wall == waCavewall && !c->monst && canReachPlayer(c, moSlime)) { c->monst = moSeep; + playSeenSound(c); seepcount--; continue; } if(ghostcount && !c->monst) { c->monst = moGhost; + playSeenSound(c); ghostcount--; continue; } @@ -4121,53 +5301,85 @@ void wandering() { if((c->wall == waCavewall || c->wall == waDeadwall) && !c->monst && wchance(items[treasureType(c->land)], 10) && canReachPlayer(c, moSlime)) { c->monst = moSeep; + playSeenSound(c); continue; } else if(c->wall == waCTree && !c->monst && wchance(items[itPirate], 15) && canReachPlayer(c, moSlime)) { c->monst = moParrot; + playSeenSound(c); continue; } else if(c->land == laEndorian && c->wall == waNone && wchance(items[itApple], 50)) { - c->monst = moKestrel; + c->monst = moSparrowhawk; + playSeenSound(c); continue; } else if(c->wall == waSea && !c->monst) { if(c->land == laCaribbean && wchance(items[itPirate], 15) && canReachPlayer(c, moPirate)) { c->monst = moCShark; + playSeenSound(c); continue; } - if(c->land == laGridSea && avengers && canReachPlayer(c, moPirate)) { + if(c->land == laWarpSea && avengers && canReachPlayer(c, moPirate)) { c->monst = moRatlingAvenger; c->wall = waBoat; avengers--; if(cheater) printf("avenger comes\n"); + playSeenSound(c); continue; } - if(c->land == laGridSea && wchance(items[itCoral], 25) && canReachPlayer(c, moPirate)) { + if(c->land == laWarpSea && wchance(items[itCoral], 25) && canReachPlayer(c, moPirate)) { c->monst = moRatling; c->wall = waBoat; + playSeenSound(c); continue; } if(c->land == laOcean && (items[itCoast] > 50 || (cwt.c->landparam < 25 && c->landparam < 25)) && wchance(items[itCoast], 25) && canReachPlayer(c, moEagle)) { c->monst = moAlbatross; + playSeenSound(c); continue; } if(c->land == laLivefjord && wchance(items[itFjord], 80) && items[itFjord] >= 10 && canReachPlayer(c, moWaterElemental)) { c->monst = moWaterElemental; + playSeenSound(c); continue; } + if(c->land == laKraken && ((sphere && !hrand(15)) || wchance(items[itKraken], 240)) && !pseudohept(c)) { + bool b = canReachPlayer(c, moKrakenH); + if(sphere && (haveKraken() || !items[itOrbFish])) { + c->monst = moViking; c->wall = waBoat; c->item = itOrbFish; + playSeenSound(c); + continue; + } + if(b) forCellEx(c2, c) if((sphere || c2->cpdist > 7) && !pseudohept(c2)) { + forCellCM(c3, c2) if(c3->monst || c3->wall != waSea) + goto notfound; + c2->monst = moKrakenH; + playSeenSound(c2); + for(int i=0; itype; i++) { + c2->mov[i]->monst = moKrakenT; + c2->mov[i]->hitpoints = 1; + c2->mov[i]->mondir = c2->spn(i); + } + goto found; + } + goto notfound; + found: + continue; + } + notfound: break; } - else if(c->monst || c->pathdist == INFD) break; + else if(c->monst || c->pathdist == PINFD) break; - else if(c->land == laClearing && wchance(items[itMutant2], 150) && items[itMutant2] >= 15 && !c->monst && c->type == 7) + else if(c->land == laClearing && wchance(items[itMutant2], 150) && items[itMutant2] >= 15 && !c->monst && c->type == 7) c->monst = moRedFox; - else if(hrand(50) < statuecount * statuecount) + else if(hrand(50) < statuecount * statuecount) c->monst = moCultistLeader; else if(c->land == laIce && wchance(items[itDiamond], 10)) @@ -4185,6 +5397,15 @@ void wandering() { else if(c->land == laCaves && wchance(items[itGold], 5)) c->monst = hrand(3) ? moTroll : moGoblin; + else if(c->land == laBull && wchance(items[itBull], 40)) + c->monst = moGadfly; + + else if(items[itBull] >= 50 && size(butterflies) && wchance(items[itBull]-49, 25)) + c->monst = moGadfly; + + else if(c->land == laPrairie && cwt.c->LHU.fi.flowerdist > 3 && wchance(items[itGreenGrass], prairie::isriver(cwt.c) ? 150 : 40)) + c->monst = moGadfly; + else if(c->land == laHive && wchance(hivehard(), 25)) c->monst = randomHyperbug(); @@ -4197,9 +5418,15 @@ void wandering() { else if(c->land == laMirror && wchance(items[itShard], 15)) c->monst = hrand(10) ? moRanger : moEagle; - else if(c->land == laGridCoast && wchance(items[itCoral], 40)) + else if(c->land == laWarpCoast && wchance(items[itCoral], 40)) c->monst = moRatling; + else if(c->land == laBurial && wchance(items[itBarrow], 250)) + c->monst = moDraugr; + + else if(c->land == laTrollheim && wchance(items[itTrollEgg], 150)) + c->monst = pickTroll(c); + else if(c->land == laRose && wchance(items[itRose], 25)) c->monst = moFalsePrincess; @@ -4218,7 +5445,7 @@ void wandering() { c->monst = moOutlaw; else if(c->land == laEndorian && c->wall == waTrunk && wchance(items[itApple], 30)) - c->monst = moLemur; + c->monst = moResearcher; else if(c->land == laOvergrown && wchance(items[itMutant], 50)) c->monst = moForestTroll; @@ -4248,11 +5475,8 @@ void wandering() { else if(isElemental(c->land) && wchance(items[itElemental], 20)) c->monst = elementalOf(c->land); - else if(c->land == laIvoryTower && wchance(items[itEdge], 20)) - c->monst = moGargoyle; - - else if(c->land == laIvoryTower && wchance(items[itApple], 20)) - c->monst = c->wall == waTrunk ? moLemur : moGargoyle; + else if(c->land == laIvoryTower && wchance(items[itIvory], 20)) + c->monst = cellEdgeUnstable(c) ? moGargoyle : moFamiliar; else if(c->land == laMinefield && wchance(items[itBombEgg]-20, 400)) c->monst = moBomberbird; @@ -4278,7 +5502,7 @@ void wandering() { } else if(c->land == laLivefjord && wchance(items[itFjord], 10)) { - c->monst = moViking; + c->monst = sphere ? pick(moViking, moWaterElemental, moFjordTroll) : moViking; } else if(c->land == laOcean && wchance(items[itCoast], 100)) { @@ -4301,6 +5525,7 @@ void wandering() { else break; + playSeenSound(c); if(c->monst == moWorm || c->monst == moHexSnake) c->mondir = NODIR; // laMotion -> no respawn! @@ -4309,26 +5534,29 @@ void wandering() { 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; + preventbarriers(h->c7); + for(int i=0; i<7; i++) preventbarriers(h->c7->mov[i]); 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++) { + if(quotient) relspin = 0; + else { + for(int j=0; j<7; j++) for(int i=0; i<7; i++) { createStep(h, i); - if(h->move[i]->alt == h->alt->move[j]) + if(h->move[i]->alt == h->alt->move[j]) { relspin = (i-j+7) % 7; + break; + } } - if(relspin == -4) { + if(relspin == -4 && quotient != 2) { 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"); + {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++) { @@ -4339,7 +5567,7 @@ void generateAlts(heptagon *h) { // h, i, h->alt, ir, // ho, hm); if(ho->alt && ho->alt != hm) { - if(ho->alt->alt == hm->alt) { + if(ho->alt->alt == hm->alt && !quotient) { printf("ERROR: alt cross! [%d -> %d]\n", ho->alt->distance, hm->distance); // exit(1); } @@ -4402,7 +5630,7 @@ heptagon *createAlternateMap(cell *c, int rad, hstate firststate, int special) { for(int d=rad; d>=0; d--) { generateAlts(cx[d]->master); - cx[d]->bardir = NOBARRIERS; + preventbarriers(cx[d]); } if(special == waPalace) { @@ -4425,3 +5653,252 @@ heptagon *createAlternateMap(cell *c, int rad, hstate firststate, int special) { //for(int d=rad; d>=0; d--) printf("%3d. %p {%d}\n", d, cx[d]->master, cx[d]->master->alt->distance); } +namespace halloween { + vector srch; + + cell *farempty(bool lastresort = false) { + int maxdist = 0; + cell* validcells[100]; + int qvc = 0; + int firstfar1 = 0; + for(int i=1; iitem == itNone && dcal[i]->monst == moNone && dcal[i]->wall == waNone; + if(lastresort) + okay = dcal[i]->wall != waChasm && !isMultitile(dcal[i]->monst); + if(okay) { + if(dcal[i]->cpdist > maxdist) + firstfar1 = qvc, + maxdist = dcal[i]->cpdist; + validcells[qvc++] = dcal[i]; + } + } + + if(qvc == firstfar1) return farempty(true); + + return validcells[firstfar1 + hrand(qvc - firstfar1)]; + } + + int demoncount; + int swordpower; + int dragoncount; + + void reset() { + demoncount = 0; + swordpower = 0; + dragoncount = 0; + } + + eMonster nextDemon() { + demoncount++; + if(demoncount % 9 == 0) return moGreater; + return moLesser; + } + + void putMonster(eMonster m) { + cell *c = farempty(); + c->hitpoints = 3; + c->monst = m; + playSeenSound(c); + if(!kills[m]) switch(m) { + case moGhost: + addMessage(XLAT("Ghosts can move through chasms!")); + break; + case moSkeleton: + addMessage(XLAT("Push Skeletons into the holes!")); + break; + case moDraugr: + addMessage(XLAT("You'll need your magical sword against the Draugar!")); + break; + case moLesser: + addMessage(XLAT("Demons are slow, but you'll need the experience against stronger ones...")); + break; + case moGreater: + addMessage(XLAT("You will need more experience to defeat the Greater Demon!")); + break; + case moPyroCultist: + addMessage(XLAT("Cultists throw fire at you from distance!")); + break; + case moFlailer: + addMessage(XLAT("Defeat Flail Guards by moving away from them.")); + break; + case moVampire: + addMessage(XLAT("Vampire Bats drain your magical powers!")); + break; + default: break; + } + c->stuntime = 2; + } + + void getTreat(cell *where) { + if(!items[itTreat]) reset(); + gainItem(itTreat); + farempty()->item = itTreat; + int itr = items[itTreat]; + items[itOrbTime] += 30; + int monpower = 19 + itr + hrand(itr); + int mcount = 0; + while(monpower > 0) { + int id = hrand(10000); + +#define CHANCE(x) (id -= x, id < 0) + if(CHANCE(400) && itr >= 5) { + putMonster(moGhost); + monpower -= 30; + mcount++; + } + else if(CHANCE(400)) { + putMonster(pick(moWitch, moZombie)); + monpower -= 20; + mcount++; + } + else if(CHANCE(400) && itr >= 5) { + putMonster(nextDemon()); + monpower -= 10; + mcount++; + } + else if(CHANCE(100) && itr >= 12) { + putMonster(moVampire); + monpower -= 10; + swordpower -= 5; + if(swordpower < 0) swordpower = 0; + mcount++; + } + else if(CHANCE(400) && swordpower > 0 && !mcount && itr >= 15) { + putMonster(moDraugr); + swordpower -= 3; + monpower -= 100; + mcount++; + } + else if(CHANCE(400) && itr >= 10 && !mcount && itr >= 10) { + putMonster(moSkeleton); + monpower -= 60; + mcount++; + } + else if(CHANCE(10) && itr >= 15) { + putMonster(moWitchFire); + monpower -= 35; + mcount++; + } + else if(CHANCE(100) && itr >= 5) { + putMonster(moFlailer); + monpower -= 35; + mcount++; + } + else if(CHANCE(100) && itr >= 5) { + putMonster(moPyroCultist); + monpower -= 35; + mcount++; + } + else if(CHANCE(5) && itr >= 20 && kills[moSkeleton]) { + putMonster(moFatGuard); + monpower -= 35; + mcount++; + } + else if(CHANCE(5) && itr >= 30 && kills[moFlailer]) { + putMonster(moHedge); + monpower -= 35; + mcount++; + } + else if(CHANCE(5) && itr >= 40 && kills[moHedge]) { + putMonster(moLancer); + monpower -= 60; + mcount++; + } + else if(CHANCE(1) && itr >= 50 && kills[moHedge]) { + putMonster(moFireFairy); + monpower -= 40; + mcount++; + } + else if(CHANCE(5) && itr >= 60) { + putMonster(moBomberbird); + monpower -= 25; + mcount++; + } + else if(CHANCE(5) && itr >= 60) { + putMonster(moRatlingAvenger); + monpower -= 30; + mcount++; + } + else if(CHANCE(5) && itr >= 60) { + putMonster(moVineBeast); + monpower -= 30; + mcount++; + } + else if(CHANCE(5) && itr >= 60) { + dragoncount++; + } + else if(dragoncount && !purehepta && !mcount) { + bool fill = false; + for(int i=0; i<4; i++) + if(!dragoncells[i] || dragoncells[i]->monst) + fill = true; + swap(dragoncells[0], dragoncells[3]); + swap(dragoncells[1], dragoncells[2]); + if(fill) continue; + for(int i=0; i<4; i++) { + dragoncells[i]->monst = i ? moDragonTail : moDragonHead; + dragoncells[i]->mondir = i==3 ? NODIR : neighborId(dragoncells[i], dragoncells[i+1]); + dragoncells[i]->hitpoints = 1; + dragoncells[i]->stuntime = 1; + playSeenSound(dragoncells[i]); + } + monpower -= 200; + mcount++; + dragoncount--; + } + } + int id = hrand(100); + if(items[itTreat] == 1) { +#ifndef MOBILE + addMessage(XLAT("Hint: use arrow keys to scroll.")); +#endif + } + else if(items[itTreat] == 2) { +#ifndef MOBILE + addMessage(XLAT("Hint: press 1 2 3 4 to change the projection.")); +#endif + } + else if(items[itTreat] == 3) { + items[itOrbShell] += 20; + addMessage(XLAT("You gain a protective Shell!")); + } + else if(items[itTreat] == 4) { + items[itOrbShell] += 10; + addMessage(XLAT("Hint: magical powers from Treats expire after a number of uses.")); + } + else if(kills[moSkeleton] && CHANCE(10)) { + addMessage(XLAT("A magical energy sword. Don't waste its power!")); + items[itOrbSword] += 5; // todo facing + swordpower += 5; + } + else if(kills[moDraugr] && items[itOrbSword] && CHANCE(10)) { + addMessage(XLAT("Another energy sword!")); + items[itOrbSword2] += 5; + swordpower += 5; + } + else if(kills[moFlailer] && CHANCE(10)) { + items[itOrbThorns] += 5; + addMessage(XLAT("You got Thorns! Stab monsters by moving around them.")); + } + else if(kills[moGhost] && CHANCE(10)) { + items[itOrbAether] += 5; + addMessage(XLAT("Aethereal powers let you go through the holes.")); + } + else { + if(items[itOrbShell] > ORBBASE) + addMessage(XLAT("The tasty treat increases your protection.")); + else + addMessage(XLAT("You gain your protective Shell back!")); + items[itOrbShell] += 5; + } + } + } + +void doOvergenerate() { + int dcs = size(dcal); + for(int i=0; icpdist <= sightrange-6) setdist(c, 1, NULL); + } + } diff --git a/langen.cpp b/langen.cpp index 80b27a6a..52e91fcc 100644 --- a/langen.cpp +++ b/langen.cpp @@ -136,10 +136,13 @@ void setstats(set& s, const char* bn) { int main() { nothe.insert("R'Lyeh"); + nothe.insert("Camelot"); plural.insert("Crossroads"); plural.insert("Crossroads II"); plural.insert("Crossroads III"); plural.insert("Elemental Planes"); + plural.insert("Crossroads IV"); + plural.insert("Kraken Depths"); #define S(a,b) d[1].add(a,b); #define N(a,b,c,d,e,f) \ @@ -188,8 +191,8 @@ int main() { string mis = ""; for(int i=1; i