diff --git a/archimedean.cpp b/archimedean.cpp index 6f334e26..6a0b6b6f 100644 --- a/archimedean.cpp +++ b/archimedean.cpp @@ -89,8 +89,6 @@ struct archimedean_tiling { }; #endif -#if CAP_ARCM - #if HDR static const int sfPH = 1; static const int sfLINE = 2; @@ -99,6 +97,8 @@ static const int sfTHREE = 8; static const int sfSEMILINE = 16; #endif +#if CAP_ARCM + EX archimedean_tiling current; EX archimedean_tiling fake_current; @@ -841,8 +841,8 @@ void connectHeptagons(heptspin hi, heptspin hs) { /** T and X are supposed to be equal -- move T so that it is closer to X */ void fixup_matrix(transmatrix& T, const transmatrix& X, ld step) { - for(int i=0; i& get_cdata() { return ((arcm::hrmap_archimedean*) (currentmap))->eucdata; } +#endif + EX } } diff --git a/asonov.cpp b/asonov.cpp index df7500a4..420a80dd 100644 --- a/asonov.cpp +++ b/asonov.cpp @@ -136,8 +136,8 @@ EX transmatrix adjmatrix(int i) { } struct hrmap_asonov : hrmap { - unordered_map at; - unordered_map coords; + map at; + map coords; heptagon *getOrigin() override { return get_at(coord(0,0,0)); } diff --git a/attack.cpp b/attack.cpp index 0b8bc352..d9f80c59 100644 --- a/attack.cpp +++ b/attack.cpp @@ -372,7 +372,7 @@ EX bool attackJustStuns(cell *c2, flagtype f, eMonster attacker) { return true; else if((f & AF_SWORD) && c2->monst == moSkeleton) return false; - else if(f & (AF_CRUSH | AF_MAGIC | AF_FALL | AF_EAT | AF_GUN)) + else if(f & (AF_CRUSH | AF_MAGIC | AF_FALL | AF_EAT | AF_GUN | AF_PSI)) return false; else return isStunnable(c2->monst) && c2->hitpoints > 1; @@ -1183,19 +1183,19 @@ EX void killThePlayerAt(eMonster m, cell *c, flagtype flags) { } #if HDR -template void do_swords(cell *mf, cell *mt, eMonster who, const T& f) { +template void do_swords(movei mi, eMonster who, const T& f) { for(int bb=0; bb<2; bb++) if(who == moPlayer && sword::orbcount(bb)) { - cell *sf = sword::pos(mf, sword::dir[multi::cpid], bb); - cell *st = sword::pos(mt, sword::shift(mf, mt, sword::dir[multi::cpid]), bb); + cell *sf = sword::pos(mi.s, sword::dir[multi::cpid], bb); + cell *st = sword::pos(mi.t, sword::shift(mi, sword::dir[multi::cpid]), bb); f(st, bb); if(sf != st && !isNeighbor(sf,st)) { // also attack the in-transit cell if(S3 == 3) { - forCellEx(sb, sf) if(isNeighbor(sb, st) && sb != mf && sb != mt) f(sb, bb); + forCellEx(sb, sf) if(isNeighbor(sb, st) && sb != mi.s && sb != mi.t) f(sb, bb); } else { - forCellEx(sb, mf) if(isNeighbor(sb, st) && sb != mt) f(sb, bb); - forCellEx(sb, mt) if(isNeighbor(sb, sf) && sb != mf) f(sb, bb); + forCellEx(sb, mi.s) if(isNeighbor(sb, st) && sb != mi.t) f(sb, bb); + forCellEx(sb, mi.t) if(isNeighbor(sb, sf) && sb != mi.s) f(sb, bb); } } } @@ -1204,14 +1204,16 @@ template void do_swords(cell *mf, cell *mt, eMonster who, const T& f) { int lastdouble = -3; -EX void stabbingAttack(cell *mf, cell *mt, eMonster who, int bonuskill IS(0)) { +EX void stabbingAttack(movei mi, eMonster who, int bonuskill IS(0)) { int numsh = 0, numflail = 0, numlance = 0, numslash = 0, numbb[2]; numbb[0] = numbb[1] = 0; - int backdir = neighborId(mt, mf); + cell *mf = mi.s; + cell *mt = mi.t; + int backdir = mi.rev_dir(); - do_swords(mf, mt, who, [&] (cell *c, int bb) { if(swordAttack(mt, who, c, bb)) numbb[bb]++, numslash++; }); + do_swords(mi, who, [&] (cell *c, int bb) { if(swordAttack(mt, who, c, bb)) numbb[bb]++, numslash++; }); for(int bb=0; bb<2; bb++) achievement_count("SLASH", numbb[bb], 0); diff --git a/basegraph.cpp b/basegraph.cpp index a564eda5..2792332e 100644 --- a/basegraph.cpp +++ b/basegraph.cpp @@ -22,7 +22,7 @@ struct display_data { /** The view relative to the player character. */ shiftmatrix player_matrix; /** On-screen coordinates for all the visible cells. */ - unordered_map cellmatrices, old_cellmatrices; + map cellmatrices, old_cellmatrices; /** Position of the current map view, relative to the screen (0 to 1). */ ld xmin, ymin, xmax, ymax; /** Position of the current map view, in pixels. */ @@ -50,7 +50,7 @@ struct display_data { /** Which copy of the player cell? */ transmatrix which_copy; /** On-screen coordinates for all the visible cells. */ - unordered_map> all_drawn_copies; + map> all_drawn_copies; }; #define View (::hr::current_display->view_matrix) @@ -79,7 +79,14 @@ int utfsize(char c) { } EX int get_sightrange() { return getDistLimit() + sightrange_bonus; } -EX int get_sightrange_ambush() { return max(get_sightrange(), ambush::distance); } + +EX int get_sightrange_ambush() { + #if CAP_COMPLEX2 + return max(get_sightrange(), ambush::distance); + #else + return get_sightrange(); + #endif + } bool display_data::in_anaglyph() { return vid.stereo_mode == sAnaglyph; } bool display_data::stereo_active() { return vid.stereo_mode != sOFF; } @@ -603,7 +610,9 @@ EX void resetGL() { matched_programs.clear(); glhr::current_glprogram = nullptr; ray::reset_raycaster(); + #if CAP_RUG if(rug::glbuf) rug::close_glbuf(); + #endif } #endif @@ -1142,8 +1151,7 @@ EX void initgraph() { } #if ISWEB - vid.xscr = vid.xres = 1280; - vid.yscr = vid.yres = 900; + get_canvas_size(); #else const SDL_VideoInfo *inf = SDL_GetVideoInfo(); vid.xscr = vid.xres = inf->current_w; @@ -1161,7 +1169,9 @@ EX void initgraph() { #if CAP_CONFIG loadConfig(); #endif +#if CAP_ARCM arcm::current.parse(); +#endif if(hybri) geometry = hybrid::underlying; #if CAP_COMMANDLINE diff --git a/bigstuff.cpp b/bigstuff.cpp index 989e1561..bd1b2e84 100644 --- a/bigstuff.cpp +++ b/bigstuff.cpp @@ -21,6 +21,7 @@ EX int newRoundTableRadius() { return 28 + 2 * items[itHolyGrail]; } +#if CAP_COMPLEX2 EX int getAnthraxData(cell *c, bool b) { int d = celldistAlt(c); int rad = 28 + 3 * camelot::anthraxBonus; @@ -36,10 +37,13 @@ EX int getAnthraxData(cell *c, bool b) { if(b) return rad; return d; } +#endif EX int roundTableRadius(cell *c) { if(eubinary) return 28; + #if CAP_COMPLEX2 if(tactic::on) return getAnthraxData(c, true); + #endif if(!c->master->alt) return 28; return c->master->alt->alt->emeraldval & GRAIL_RADIUS_MASK; } @@ -55,7 +59,9 @@ EX int celldistAltRelative(cell *c) { if(sphere || quotient) { return celldist(c) - 3; } + #if CAP_COMPLEX2 if(tactic::on) return getAnthraxData(c, false); + #endif return celldistAlt(c) - roundTableRadius(c); } @@ -1805,12 +1811,14 @@ EX void moreBigStuff(cell *c) { if(c->master->emeraldval % 2) c->wall = waColumn; } + #if CAP_BT else if(geometry == gHoroTris || geometry == gHoroRec) { if(c->c.spin(bt::updir()) != 0) c->wall = waColumn; } else if(geometry == gKiteDart3) { if(kite::getshape(c->master) == kite::pKite) c->wall = waColumn; } + #endif else if(in_s2xe()) { auto d = hybrid::get_where(c); if(!PIU(pseudohept(d.first))) c->wall = waColumn; diff --git a/binary-tiling.cpp b/binary-tiling.cpp index 0c3f0a01..6a208af5 100644 --- a/binary-tiling.cpp +++ b/binary-tiling.cpp @@ -18,6 +18,10 @@ EX namespace bt { return false; #endif } + +#if !CAP_BT + EX int updir() { return 0; } +#endif #if CAP_BT #if HDR @@ -1031,7 +1035,6 @@ EX int celldistance3(heptagon *c1, heptagon *c2) { } EX int celldistance3(cell *c1, cell *c2) { return celldistance3(c1->master, c2->master); } -#endif EX hyperpoint get_horopoint(ld y, ld x) { return xpush(-y) * bt::parabolic(x) * C0; @@ -1091,6 +1094,7 @@ EX hyperpoint get_corner_horo_coordinates(cell *c, int i) { auto hooksw = addHook(hooks_swapdim, 100, [] { if(bt::in()) build_tmatrix(); }); +#endif } diff --git a/cell.cpp b/cell.cpp index 662dc6fb..5fd578c9 100644 --- a/cell.cpp +++ b/cell.cpp @@ -179,8 +179,11 @@ EX heptagon* hyperbolic_origin() { h.cdata = NULL; h.alt = NULL; h.distance = 0; + #if CAP_IRR if(IRREGULAR) irr::link_start(origin); - else h.c7 = newCell(odegree, origin); + else + #endif + h.c7 = newCell(odegree, origin); return origin; } @@ -292,10 +295,10 @@ EX void initcells() { hrmap* res = callhandlers((hrmap*)nullptr, hooks_newmap); if(res) currentmap = res; - else if(INVERSE) currentmap = gp::new_inverse(); - else if(fake::in()) currentmap = fake::new_map(); else if(asonov::in()) currentmap = asonov::new_map(); else if(nonisotropic || hybri) currentmap = nisot::new_map(); + else if(INVERSE) currentmap = gp::new_inverse(); + else if(fake::in()) currentmap = fake::new_map(); #if CAP_CRYSTAL else if(cryst) currentmap = crystal::new_map(); #endif @@ -405,7 +408,7 @@ EX void clearfrom(heptagon *at) { } int edges = at->degree(); if(bt::in() && WDIM == 2) edges = at->c7->type; - for(int i=0; imove(i)) { + for(int i=0; imove(i) && at->move(i) != at) { if(at->move(i)->alt != &deletion_marker) q.push(at->move(i)); unlink_cdata(at->move(i)); @@ -554,7 +557,9 @@ EX int celldistAlt(cell *c) { /** direction upwards in the tree */ EX int updir(heptagon *h) { + #if CAP_BT if(bt::in()) return bt::updir(); + #endif #if MAXMDIM >= 4 if(WDIM == 3 && reg3::in_rule()) { for(int i=0; imove(i) && h->move(i)->distance < h->distance) @@ -815,10 +820,12 @@ cdata *getHeptagonCdata(heptagon *h) { if(sphere || quotient) h = currentmap->gamestart()->master; bool starting = h->s == hsOrigin; + #if CAP_BT if(bt::in()) { if(bt::mapside(h) == 0) starting = true; for(int i=0; itype; i++) if(bt::mapside(h->cmove(i)) == 0) starting = true; } + #endif if(starting) { h->cdata = new cdata(orig_cdata); @@ -866,7 +873,12 @@ cdata *getHeptagonCdata(heptagon *h) { cdata *getEuclidCdata(gp::loc h) { int x = h.first, y = h.second; + + #if CAP_ARCM auto& data = arcm::in() ? arcm::get_cdata() : euc::get_cdata(); + #else + auto& data = euc::get_cdata(); + #endif // hrmap_euclidean* euc = dynamic_cast (currentmap); if(data.count(h)) return &(data[h]); @@ -918,6 +930,7 @@ int ld_to_int(ld x) { return int(x + 1000000.5) - 1000000; } +#if CAP_ARCM EX gp::loc pseudocoords(cell *c) { transmatrix T = arcm::archimedean_gmatrix[c->master].second; return {ld_to_int(T[0][LDIM]), ld_to_int((spin(60*degree) * T)[0][LDIM])}; @@ -935,19 +948,22 @@ EX cdata *arcmCdata(cell *c) { dynamicval cm(currentmap, arcm::current_altmap); return getHeptagonCdata(h2); } +#endif EX int getCdata(cell *c, int j) { if(fake::in()) return FPIU(getCdata(c, j)); - if(INVERSE) { + if(hybri) { c = hybrid::get_where(c).first; return PIU(getBits(c)); } + else if(INVERSE) { cell *c1 = gp::get_mapped(c); return UIU(getCdata(c1, j)); } - if(hybri) { c = hybrid::get_where(c).first; return PIU(getBits(c)); } else if(euc::in()) return getEuclidCdata(euc2_coordinates(c))->val[j]; +#if CAP_ARCM else if(arcm::in() && euclid) return getEuclidCdata(pseudocoords(c))->val[j]; else if(arcm::in() && hyperbolic) return arcmCdata(c)->val[j]*3; +#endif else if(!geometry_supports_cdata()) return 0; else if(ctof(c)) return getHeptagonCdata(c->master)->val[j]*3; else { @@ -961,16 +977,18 @@ EX int getCdata(cell *c, int j) { EX int getBits(cell *c) { if(fake::in()) return FPIU(getBits(c)); - if(INVERSE) { + if(hybri) { c = hybrid::get_where(c).first; return PIU(getBits(c)); } + else if(INVERSE) { cell *c1 = gp::get_mapped(c); return UIU(getBits(c1)); } - if(hybri) { c = hybrid::get_where(c).first; return PIU(getBits(c)); } else if(euc::in()) return getEuclidCdata(euc2_coordinates(c))->bits; - else if(arcm::in() && euclid) + #if CAP_ARCM + else if(arcm::in() && euclid) return getEuclidCdata(pseudocoords(c))->bits; else if(arcm::in() && (hyperbolic || sl2)) return arcmCdata(c)->bits; + #endif else if(!geometry_supports_cdata()) return 0; else if(c == c->master->c7) return getHeptagonCdata(c->master)->bits; else { @@ -1108,7 +1126,7 @@ EX int celldistance(cell *c1, cell *c2) { if(hybri) return hybrid::celldistance(c1, c2); #if CAP_FIELD - if(geometry == gFieldQuotient) { + if(geometry == gFieldQuotient && (PURE || BITRUNCATED)) { int d = fieldpattern::field_celldistance(c1, c2); if(d != DISTANCE_UNKNOWN) return d; } diff --git a/celldrawer.cpp b/celldrawer.cpp index 9625e551..e549f4b1 100644 --- a/celldrawer.cpp +++ b/celldrawer.cpp @@ -534,11 +534,13 @@ void celldrawer::setcolors() { break; case waMineUnknown: case waMineMine: + #if CAP_COMPLEX2 if(mine::marked_safe(c)) fcol = wcol = gradient(wcol, 0x40FF40, 0, 0.2, 1); else if(mine::marked_mine(c)) fcol = wcol = gradient(wcol, 0xFF4040, -1, sintick(100), 1); // fallthrough + #endif case waMineOpen: if(wmblack || wmascii) { @@ -1352,7 +1354,9 @@ void celldrawer::draw_features() { } case waTerraWarrior: + #if CAP_COMPLEX2 drawTerraWarrior(V, terracotta::randterra ? (c->landparam & 7) : (5 - (c->landparam & 7)), 7, 0); + #endif break; case waBoat: case waStrandedBoat: diff --git a/changelog.txt b/changelog.txt index 7174b01c..47d8e3cc 100644 --- a/changelog.txt +++ b/changelog.txt @@ -4139,3 +4139,33 @@ graphics/UI bugfixes: - fixed the issues with Orb of Yendor - fixed a bug in S2xE, and choosing cells in sphere - fixed Friendly Ghosts + +2020-11-02 00:08 Update 11.4: + +* Panini perspective projection (allows wider vision -- configurable in 3d config -> FOV) +* Changed the default texture_step to 4 +* An option to hide the flat projection in hyperboloid +* improved plain floor shapes in arbitrary tessellations +* fixed the Asonov cat geometry +* Five new projections: Poor Man (hyperbolic only), Panini, retroazimuthal: Craig, Hammer, Littrow (retro-Hammer buggy on sphere) +* Various fixes related to product spaces, especially product+inverse +* Fixed the navigation keys in dialogs +* fixed duals for Euclidean tessellations + +Also, improvements in the Web version -- Backspace not Esc to exit dialogs, auto-resizing, raytracer is available. +Not yet in HyperRogue online, but see: https://zenorogue.itch.io/bringris + +2020-11-05 18:53 Update 11.4a: + +- fixed the aura when camera angle changed +- fixed selecting large regions for copying with mouse +- messages at 30 treasures etc. should no longer appear in PTM +- fixed a bug which allowed changing the generation/game range outside of cheat mode +- fixed drawing of creatures in kill list (visible e.g. for dogs) +- the RGB/RGBA hex value color is now shown with leading 0s +- Orb of the Mind now actually kills reptiles +- Orb of Chaos is now forbidden in the Princess Quest +- highlight mode setting should be saved now +- improved the safe move checking (fixes some minor bugs especially in multiplayer) +- allies use the new safe move checking now (they now know whether attacking hedgehog warriors/pikemen is safe for them and do it if yes) +- golems and bomberbirds now pathfind correctly diff --git a/checkmove.cpp b/checkmove.cpp index 473b6188..51d41f47 100644 --- a/checkmove.cpp +++ b/checkmove.cpp @@ -39,28 +39,47 @@ EX bool hasSafeOrb(cell *c) { } #if HDR -struct stalemate1 { - eMonster who; - cell *moveto; - cell *pushto; - cell *comefrom; +struct player_move_info { + movei mi; cell *swordlast[2], *swordtransit[2], *swordnext[2]; - stalemate1(eMonster w, cell *mt, cell *pt, cell *cf) : who(w), moveto(mt), pushto(pt), comefrom(cf) {} + player_move_info(movei mi); }; #endif +EX vector pmi; +EX vector pushes; + +player_move_info::player_move_info(movei _mi) : mi(_mi) { + for(int b=0; b<2; b++) swordlast[b] = sword::pos(multi::cpid, b); + + dynamicval x7(sword::dir[multi::cpid], sword::shift(mi, sword::dir[multi::cpid])); + + for(int b=0; b<2; b++) { + swordnext[b] = sword::pos(multi::cpid, b); + swordtransit[b] = NULL; + if(swordnext[b] && swordnext[b] != swordlast[b] && !isNeighbor(swordlast[b], swordnext[b])) { + forCellEx(c2, swordnext[b]) + if(c2 != mi.t && c2 != mi.s && isNeighbor(c2, S3==3 ? swordlast[b] : mi.t)) + swordtransit[b] = c2; + if(S3 == 4) + forCellEx(c2, mi.s) + if(c2 != mi.s && isNeighbor(c2, swordlast[b])) + swordtransit[b] = c2; + } + } + } + EX bool krakensafe(cell *c) { return items[itOrbFish] || items[itOrbAether] || (c->item == itOrbFish && c->wall == waBoat) || (c->item == itOrbAether && c->wall == waBoat); } -EX bool monstersnear(stalemate1& sm) { +EX bool monstersnear(cell *c, eMonster who) { - cell *c = sm.moveto; bool eaten = false; - if(hardcore && sm.who == moPlayer) return false; + if(hardcore && who == moPlayer) return false; int res = 0; bool fast = false; @@ -76,9 +95,9 @@ EX bool monstersnear(stalemate1& sm) { who_kills_me = moCrusher; res++; } - if(sm.who == moPlayer || items[itOrbEmpathy]) { + if(who == moPlayer || items[itOrbEmpathy]) { fast = (items[itOrbSpeed] && (items[itOrbSpeed] & 1)); - if(sm.who == moPlayer && sm.moveto->item == itOrbSpeed && !items[itOrbSpeed]) fast = true; + if(who == moPlayer && c->item == itOrbSpeed && !items[itOrbSpeed]) fast = true; } if(havewhat&HF_OUTLAW) { @@ -103,7 +122,7 @@ EX bool monstersnear(stalemate1& sm) { if(!logical_adjacent(c3, c3->monst, c2) || !logical_adjacent(c2, c3->monst, c) || (c3->monst == moWitchSpeed && c2->land != laPower)) continue; if(elec::affected(c3)) continue; - if(c3->stuntime > (sm.who == moPlayer ? 0 : 1)) continue; + if(c3->stuntime > (who == moPlayer ? 0 : 1)) continue; // speedwitches can only attack not-fastened monsters, // others can only attack if the move is not fastened if(c3->monst == moWitchSpeed && items[itOrbSpeed]) continue; @@ -120,19 +139,19 @@ EX bool monstersnear(stalemate1& sm) { // consider normal monsters if(c2 && - isArmedEnemy(c2, sm.who) && - (c2->monst != moLancer || isUnarmed(sm.who) || !logical_adjacent(c, sm.who, c2))) { + isArmedEnemy(c2, who) && + (c2->monst != moLancer || isUnarmed(who) || !logical_adjacent(c, who, c2))) { eMonster m = c2->monst; if(elec::affected(c2)) continue; if(fast && c2->monst != moWitchSpeed) continue; // Krakens just destroy boats - if(c2->monst == moKrakenT && onboat(sm)) { + if(c2->monst == moKrakenT && c->wall == waBoat) { if(krakensafe(c)) continue; else if(warningprotection(XLAT("This move appears dangerous -- are you sure?")) && res == 0) m = moWarning; else continue; } // they cannot attack through vines - if(!canAttack(c2, c2->monst, c, sm.who, AF_NEXTTURN)) continue; + if(!canAttack(c2, c2->monst, c, who, AF_NEXTTURN)) continue; if(c2->monst == moWorm || c2->monst == moTentacle || c2->monst == moHexSnake) { if(passable_for(c2->monst, c, c2, 0)) eaten = true; @@ -142,18 +161,16 @@ EX bool monstersnear(stalemate1& sm) { } } - if(sm.who == moPlayer && res && (markOrb2(itOrbShield) || markOrb2(itOrbShell)) && !eaten) + if(who == moPlayer && res && (markOrb2(itOrbShield) || markOrb2(itOrbShell)) && !eaten) res = 0; - if(sm.who == moPlayer && res && markOrb2(itOrbDomination) && c->monst) + if(who == moPlayer && res && markOrb2(itOrbDomination) && c->monst) res = 0; return !!res; } -EX bool monstersnear2(); - -EX bool monstersnear2() { +EX bool monstersnear_aux() { changes.value_set(passive_switch, (gold() & 1) ? moSwitch1 : moSwitch2); multi::cpid++; bool b = false; @@ -162,22 +179,33 @@ EX bool monstersnear2() { if(multi::cpid == multi::players || multi::players == 1 || multi::checkonly) { if(shmup::delayed_safety) return false; - dynamicval sw(passive_switch, passive_switch); - - for(int i=0; i 8) + if(celldistance(pmi[i].mi.t, pmi[j].mi.t) > 8) { b = true; who_kills_me = moAirball; } } - for(int i=0; !b && i 1) wcw = &multi::player[multi::cpid].at; - - dynamicval x5(*wcw, c); - dynamicval x6(stalemate::nextturn, true); - dynamicval x7(sword::dir[multi::cpid], - who == moPlayer ? sword::shift(comefrom, c, sword::dir[multi::cpid]) : - sword::dir[multi::cpid]); - - for(int b=0; b<2; b++) { - if(who == moPlayer) { - sm.swordnext[b] = sword::pos(multi::cpid, b); - sm.swordtransit[b] = NULL; - if(sm.swordnext[b] && sm.swordnext[b] != sm.swordlast[b] && !isNeighbor(sm.swordlast[b], sm.swordnext[b])) { - forCellEx(c2, sm.swordnext[b]) - if(c2 != c && c2 != comefrom && isNeighbor(c2, S3==3 ? sm.swordlast[b] : *wcw)) - sm.swordtransit[b] = c2; - if(S3 == 4) - forCellEx(c2, c) - if(c2 != comefrom && isNeighbor(c2, sm.swordlast[b])) - sm.swordtransit[b] = c2; - } - } - else { - sm.swordnext[b] = sm.swordtransit[b] = NULL; - } - } - - stalemate::moves.push_back(sm); - - // dynamicval x7(stalemate::who, who); - - bool b; - if(who == moPlayer && c->wall == waBigStatue) { - eWall w = comefrom->wall; - c->wall = waNone; - if(doesnotFall(comefrom)) comefrom->wall = waBigStatue; - b = monstersnear2(); - comefrom->wall = w; - c->wall = waBigStatue; - } - else if(who == moPlayer && isPushable(c->wall)) { - eWall w = c->wall; - c->wall = waNone; - b = monstersnear2(); - c->wall = w; - } - else { - b = monstersnear2(); - } - stalemate::moves.pop_back(); +/** like monstersnear but add the potential moves of other players into account */ +EX bool monstersnear_add_pmi(player_move_info pmi0) { + pmi.push_back(pmi0); + bool b = monstersnear_aux(); + pmi.pop_back(); return b; } -EX namespace stalemate { - EX vector moves; - EX bool nextturn; - - EX bool isMoveto(cell *c) { - for(int i=0; iwall == waBoat) || (cf->wall == waBoat && c->wall == waSea); - } - EX bool multimove() { if(multi::cpid == 0) lastkills = tkills(); - if(!multi::playerActive(multi::cpid)) return !monstersnear2(); + if(!multi::playerActive(multi::cpid)) return !monstersnear_aux(); cellwalker bcwt = cwt; cwt = multi::player[multi::cpid]; bool b = movepcto(multi::whereto[multi::cpid]); @@ -293,11 +244,11 @@ EX namespace multi { EX bool aftermove; EX } -EX bool swordConflict(const stalemate1& sm1, const stalemate1& sm2) { +EX bool swordConflict(const player_move_info& sm1, const player_move_info& 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]) + if(sm1.mi.s == sm2.swordlast[b] || sm1.mi.s == sm2.swordtransit[b] || sm1.mi.s == sm2.swordnext[b]) + if(sm1.mi.t == sm2.swordlast[b] || sm1.mi.t == sm2.swordtransit[b] || sm1.mi.t == sm2.swordnext[b]) return true; return false; } diff --git a/classes.cpp b/classes.cpp index e9dcbee0..bb45f1fe 100644 --- a/classes.cpp +++ b/classes.cpp @@ -980,6 +980,8 @@ enum eModel : int { // 32..38 mdWerner, mdAitoff, mdHammer, mdLoximuthal, mdMiller, mdGallStereographic, mdWinkelTripel, // 39.. + mdPoorMan, mdPanini, mdRetroCraig, mdRetroLittrow, mdRetroHammer, + // 44.. mdGUARD, mdPixel, mdHyperboloidFlat, mdPolynomial, mdManual }; #endif @@ -1031,6 +1033,11 @@ EX vector mdinf = { {X3("Miller projection"), mf::euc_boring | mf::band, DEFAULTS}, // scale latitude 4/5 -> Mercator -> 5/4 {X3("Gall stereographic"), mf::euc_boring | mf::band, DEFAULTS}, // like central cylindrical but stereographic {X3("Winkel tripel"), mf::euc_boring | mf::broken, DEFAULTS}, // mean of equirec and Aitoff + {X3("Poor man's square"), mf::euc_boring, DEFAULTS}, // + {X3("Panini projection"), mf::euc_boring, DEFAULTS}, // + {X3("Craig retroazimuthal"), mf::euc_boring | mf::broken, DEFAULTS}, // retroazimuthal cylindrical + {X3("Littrow retroazimuthal"), mf::euc_boring | mf::broken, DEFAULTS}, // retroazimuthal conformal + {X3("Hammer retroazimuthal"), mf::euc_boring, DEFAULTS}, // retroazimuthal equidistant {X3("guard"), 0, DEFAULTS}, {X3("polynomial"), mf::conformal, DEFAULTS}, }; diff --git a/complex.cpp b/complex.cpp index 69e78dc2..937f1abd 100644 --- a/complex.cpp +++ b/complex.cpp @@ -2800,12 +2800,18 @@ EX namespace sword { EX void determine_sword_angles() { sword_angles = 2; if(SWORDDIM == 3) sword_angles = 1; + #if CAP_IRR else if(IRREGULAR) sword_angles = 840; + #endif + #if CAP_BT else if(bt::in()) sword_angles = 42; + #endif + #if CAP_ARCM else if(arcm::in()) { if(!PURE) possible_divisor((BITRUNCATED ? 2 : 1) * isize(arcm::current.faces)); if(!DUAL) for(int f: arcm::current.faces) possible_divisor(f); } + #endif else { possible_divisor(S7); if(BITRUNCATED) possible_divisor(S3); @@ -2861,10 +2867,13 @@ EX namespace sword { } // from c1 to c2 - EX sworddir shift(cell *c1, cell *c2, sworddir d) { - if(!c1 || !c2) return d; - int s1 = neighborId(c1, c2); - int s2 = neighborId(c2, c1); + EX sworddir shift(movei mi, sworddir d) { + cell *c1 = mi.s; + cell *c2 = mi.t; + if(!mi.proper()) return d; + int s1 = mi.d; + int s2 = mi.rev_dir(); + neighborId(c2, c1); if(s1 < 0 || s2 < 0) return d; if(SWORDDIM == 2) { int sub = (hybri) ? 2 : 0; @@ -3347,7 +3356,7 @@ EX namespace ca { EX eWall wlive = waFloorA; - EX unordered_set changed; + EX set changed; EX void list_adj(cell *c) { changed.insert(c); diff --git a/config.cpp b/config.cpp index d3f5fde4..ae2a4da5 100644 --- a/config.cpp +++ b/config.cpp @@ -1,2735 +1,2801 @@ -// Hyperbolic Rogue -- configuration -// Copyright (C) 2017-2018 Zeno Rogue, see 'hyper.cpp' for details - -/** \file config.cpp - * \brief Configuration -- initial settings, saving/loading ini files, menus, etc. - */ - -#include "hyper.h" -namespace hr { - -#if HDR -enum eCentering { face, edge, vertex }; -#endif - -EX eCentering centering; - -#if HDR -struct supersaver { - string name; - virtual string save() = 0; - virtual void load(const string& s) = 0; - virtual bool dosave() = 0; - virtual void reset() = 0; - virtual ~supersaver() {}; - }; - -typedef vector> saverlist; - -extern saverlist savers; - -#if CAP_CONFIG - -template struct dsaver : supersaver { - T& val; - T dft; - bool dosave() { return val != dft; } - void reset() { val = dft; } - dsaver(T& val) : val(val) { } - }; - -template struct saver : dsaver {}; - -template void addsaver(T& i, U name, V dft) { - auto s = make_shared> (i); - s->dft = dft; - s->name = name; - savers.push_back(s); - } - -template void addsaver(T& i, string name) { - addsaver(i, name, i); - } - -template struct saverenum : supersaver { - T& val; - T dft; - bool dosave() { return val != dft; } - void reset() { val = dft; } - saverenum(T& v) : val(v) { } - string save() { return its(int(val)); } - void load(const string& s) { val = (T) atoi(s.c_str()); } - }; - -template void addsaverenum(T& i, U name, T dft) { - auto s = make_shared> (i); - s->dft = dft; - s->name = name; - savers.push_back(s); - } - -template void addsaverenum(T& i, U name) { - addsaverenum(i, name, i); - } - -template<> struct saver : dsaver { - saver(int& val) : dsaver(val) { } - string save() { return its(val); } - void load(const string& s) { val = atoi(s.c_str()); } - }; - -template<> struct saver : dsaver { - saver(char& val) : dsaver(val) { } - string save() { return its(val); } - void load(const string& s) { val = atoi(s.c_str()); } - }; - -template<> struct saver : dsaver { - saver(bool& val) : dsaver(val) { } - string save() { return val ? "yes" : "no"; } - void load(const string& s) { val = isize(s) && s[0] == 'y'; } - }; - -template<> struct saver : dsaver { - saver(unsigned& val) : dsaver(val) { } - string save() { return itsh(val); } - void load(const string& s) { val = (unsigned) strtoll(s.c_str(), NULL, 16); } - }; - -template<> struct saver : dsaver { - saver(string& val) : dsaver(val) { } - string save() { return val; } - void load(const string& s) { val = s; } - }; - -template<> struct saver : dsaver { - saver(ld& val) : dsaver(val) { } - string save() { return fts(val, 10); } - void load(const string& s) { - if(s == "0.0000000000e+000") ; // ignore! - else val = atof(s.c_str()); - } - }; -#endif - -void addparam(ld& val, const string s); -#endif - -#if CAP_CONFIG -void addparam(ld& val, const string s) { - addsaver(val, s); - params.insert({s, val}); - } -#else -void addparam(ld& val, const string s) { - params.insert({s, val}); - } -#endif - -EX ld bounded_mine_percentage = 0.1; -EX int bounded_mine_quantity, bounded_mine_max; - -EX const char *conffile = "hyperrogue.ini"; - -/* extra space if more geometries are added */ -EX array sightranges; - -EX videopar vid; - -#define DEFAULT_WALLMODE (ISMOBILE ? 3 : 5) -#define DEFAULT_MONMODE (ISMOBILE ? 2 : 4) - -#if ISANDROID -#define ANDROID_SETTINGS settingsChanged = true; -#else -#define ANDROID_SETTINGS ; -#endif - -extern color_t floorcolors[landtypes]; - -EX charstyle& getcs(int id IS(multi::cpid)) { - if(multi::players>1 && id >= 0 && id < multi::players) - return multi::scs[id]; - else - return vid.cs; - } - -struct charstyle_old { - int charid; - color_t skincolor, haircolor, dresscolor, swordcolor, dresscolor2, uicolor; - bool lefthanded; - }; - -EX void hread(hstream& hs, charstyle& cs) { - // before 0xA61A there was no eyecolor - if(hs.get_vernum() < 0xA61A) { - charstyle_old cso; - hread_raw(hs, cso); - cs.charid = cso.charid; - cs.skincolor = cso.skincolor; - cs.haircolor = cso.haircolor; - cs.dresscolor = cso.dresscolor; - cs.swordcolor = cs.eyecolor = cso.swordcolor; - if(cs.charid < 4) cs.eyecolor = 0; - cs.dresscolor2 = cso.dresscolor2; - cs.uicolor = cso.uicolor; - cs.lefthanded = cso.lefthanded; - } - else hread_raw(hs, cs); - } - -EX void hwrite(hstream& hs, const charstyle& cs) { - hwrite_raw(hs, cs); - } - -// void hread(hstream& hs, charstyle& cs) { hread_raw(hs, cs); } -// void hwrite(hstream& hs, const charstyle& cs) { hwrite_raw(hs, cs); } - -EX string csnameid(int id) { - if(id == 0) return XLAT("male"); - if(id == 1) return XLAT("female"); - if(id == 2) return XLAT("Prince"); - 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"); - } - -EX string csname(charstyle& cs) { - return csnameid(cs.charid); - } - -EX int playergender() { - return (getcs().charid >= 0 && (getcs().charid&1)) ? GEN_F : GEN_M; - } -EX int princessgender() { - int g = playergender(); - if(vid.samegender) return g; - return g == GEN_M ? GEN_F : GEN_M; - } - -EX int default_language; - -EX int lang() { - if(vid.language >= 0) - return vid.language; - return default_language; - } - -EX bool autojoy = true; - -#if CAP_CONFIG -saverlist savers; -#endif - -#if !CAP_CONFIG -template void addsaver(T& i, U name, V dft) { - i = dft; - } - -template void addsaver(T& i, U name) {} -template void addsaverenum(T& i, U name) {} -template void addsaverenum(T& i, U name, T dft) {} -#endif - -EX void addsaver(charstyle& cs, string s) { - addsaver(cs.charid, s + ".charid"); - addsaver(cs.skincolor, s + ".skincolor"); - addsaver(cs.eyecolor, s + ".eyecolor"); - addsaver(cs.haircolor, s + ".haircolor"); - addsaver(cs.dresscolor, s + ".dresscolor"); - addsaver(cs.swordcolor, s + ".swordcolor"); - addsaver(cs.dresscolor2, s + ".dresscolor2"); - addsaver(cs.uicolor, s + ".uicolor"); - addsaver(cs.lefthanded, s + ".lefthanded"); - } - -// R:239, G:208, B:207 - -unsigned int skincolors[] = { 7, 0xD0D0D0FF, 0xEFD0C9FF, 0xC77A58FF, 0xA58869FF, 0x602010FF, 0xFFDCB1FF, 0xEDE4C8FF }; -unsigned int haircolors[] = { 8, 0x686868FF, 0x8C684AFF, 0xF2E1AEFF, 0xB55239FF, 0xFFFFFFFF, 0x804000FF, 0x502810FF, 0x301800FF }; -unsigned int dresscolors[] = { 6, 0xC00000FF, 0x00C000FF, 0x0000C0FF, 0xC0C000FF, 0xC0C0C0FF, 0x202020FF }; -unsigned int dresscolors2[] = { 7, 0x8080FFC0, 0x80FF80C0, 0xFF8080C0, 0xFFFF80C0, 0xFF80FFC0, 0x80FFFFC0, 0xFFFFFF80 }; -unsigned int swordcolors[] = { 6, 0xC0C0C0FF, 0xFFFFFFFF, 0xFFC0C0FF, 0xC0C0FFFF, 0x808080FF, 0x202020FF }; -unsigned int eyecolors[] = { 4, 0x00C000FF, 0x0000C0FF, 0xC00000FF, 0xC0C000FF, 0x804010FF, 0x00C000FF }; - -EX void initcs(charstyle &cs) { - cs.charid = 0; - cs.skincolor = 0xD0D0D0FF; - cs.haircolor = 0x686868FF; - cs.dresscolor = 0xC00000FF; - cs.swordcolor = 0xD0D0D0FF; - cs.dresscolor2= 0x8080FFC0; - cs.uicolor = 0xFF0000FF; - cs.eyecolor = 0x603000FF; - cs.lefthanded = false; - } - -EX void savecolortable(colortable& ct, string name) { - for(int i=0; icolor, "lpcolor-" + lp->lpname); - addsaver(lp->multiplier, "lpwidth-" + lp->lpname); - } - - // special graphics - - addsaver(pconf.ballangle, "ball angle", 20); - addsaver(vid.yshift, "Y shift", 0); - addsaver(vid.use_wall_radar, "wallradar", true); - addsaver(vid.fixed_facing, "fixed facing", 0); - addsaver(vid.fixed_facing_dir, "fixed facing dir", 90); - addsaver(vid.fixed_yz, "fixed YZ", true); - addsaver(pconf.camera_angle, "camera angle", 0); - addsaver(pconf.ballproj, "ballproj", 1); - addsaver(vid.monmode, "monster display mode", DEFAULT_MONMODE); - addsaver(vid.wallmode, "wall display mode", DEFAULT_WALLMODE); - - addsaver(vid.depth, "3D depth", 1); - addsaver(vid.camera, "3D camera level", 1); - addsaver(vid.wall_height, "3D wall height", .3); - addsaver(vid.rock_wall_ratio, "3D rock-wall ratio", .9); - addsaver(vid.human_wall_ratio, "3D human-wall ratio", .7); - addsaver(vid.lake_top, "3D lake top", .25); - addsaver(vid.lake_bottom, "3D lake bottom", .9); - addsaver(vid.tc_depth, "3D TC depth", 1); - addsaver(vid.tc_camera, "3D TC camera", 2); - addsaver(vid.tc_alpha, "3D TC alpha", 3); - addsaver(vid.highdetail, "3D highdetail", 8); - addsaver(vid.middetail, "3D middetail", 8); - addsaver(vid.gp_autoscale_heights, "3D Goldberg autoscaling", true); - addsaver(vid.always3, "3D always", false); - - addsaver(vid.eye, "eyelevel", 0); - addsaver(vid.auto_eye, "auto-eyelevel", false); - - addsaver(memory_saving_mode, "memory_saving_mode", (ISMOBILE || ISPANDORA || ISWEB) ? 1 : 0); - addsaver(reserve_limit, "memory_reserve", 128); - addsaver(show_memory_warning, "show_memory_warning"); - - auto& rconf = vid.rug_config; - addsaverenum(rconf.model, "rug-projection", mdEquidistant); - addsaver(rconf.scale, "rug-projection-scale", 1); - addsaver(rconf.alpha, "rug-projection-alpha", 1); - addsaver(rconf.clip_min, "rug-projection-clip-min", -100); - addsaver(rconf.clip_max, "rug-projection-clip-max", +10); - addsaver(rconf.stretch, "rug-projection-stretch", 1); - addsaver(rconf.halfplane_scale, "rug-projection-halfplane scale", 1); - addsaver(rconf.collignon_parameter, "rug-collignon-parameter", 1); - addsaver(rconf.collignon_reflected, "rug-collignon-reflect", false); - addsaver(rconf.euclid_to_sphere, "rug-euclid to sphere projection", 1.5); - addsaver(rconf.twopoint_param, "rug-twopoint parameter", 1); - addsaver(rconf.fisheye_param, "rug-fisheye parameter", 1); - addsaver(rconf.model_transition, "rug-model transition", 1); - addsaver(rug::renderonce, "rug-renderonce"); - addsaver(rug::rendernogl, "rug-rendernogl"); - addsaver(rug::texturesize, "rug-texturesize"); -#if CAP_RUG - addsaver(rug::model_distance, "rug-model-distance"); -#endif - - addsaverenum(pmodel, "used model", mdDisk); - addsaver(polygonal::SI, "polygon sides"); - addsaver(polygonal::STAR, "polygon star factor"); - addsaver(polygonal::deg, "polygonal degree"); - addsaver(history::autobandhistory, "include history"); // check! - addsaver(history::lvspeed, "lineview speed"); - addsaver(history::extra_line_steps, "lineview extension"); - - addsaver(polygonal::maxcoef, "polynomial degree"); - for(int i=0; ireset(); -#endif - } - -EX bool inSpecialMode() { - return chaosmode || !BITRUNCATED || peace::on || - #if CAP_TOUR - tour::on || - #endif - yendor::on || tactic::on || randomPatternsMode || - geometry != gNormal || pmodel != mdDisk || pconf.alpha != 1 || pconf.scale != 1 || - rug::rugged || vid.monmode != DEFAULT_MONMODE || - vid.wallmode != DEFAULT_WALLMODE; - } - -EX bool have_current_settings() { - int modecount = 0; - if(inv::on) modecount++; - if(shmup::on) modecount += 10; -#if CAP_TOUR - if(tour::on) modecount += 10; -#endif - if(chaosmode) modecount += 10; - if(!BITRUNCATED) modecount += 10; - if(peace::on) modecount += 10; - if(yendor::on) modecount += 10; - if(tactic::on) modecount += 10; - if(randomPatternsMode) modecount += 10; - if(geometry != gNormal) modecount += 10; - - if(modecount > 1) - return true; - - return false; - } - -EX bool have_current_graph_settings() { - if(pconf.xposition || pconf.yposition || pconf.alpha != 1 || pconf.scale != 1) - return true; - if(pmodel != mdDisk || vid.monmode != DEFAULT_MONMODE || vid.wallmode != DEFAULT_WALLMODE) - return true; - if(firstland != laIce || multi::players != 1 || rug::rugged) - return true; - - return false; - } - -EX void reset_graph_settings() { - pmodel = mdDisk; pconf.alpha = 1; pconf.scale = 1; - pconf.xposition = pconf.yposition = 0; - #if CAP_RUG - if(rug::rugged) rug::close(); - #endif - - vid.monmode = DEFAULT_MONMODE; - vid.wallmode = DEFAULT_WALLMODE; - } - -EX void resetModes(char leave IS('c')) { - while(game_active || gamestack::pushed()) { - if(game_active) stop_game(); - if(gamestack::pushed()) gamestack::pop(); - } - if(shmup::on != (leave == rg::shmup)) stop_game_and_switch_mode(rg::shmup); - if(inv::on != (leave == rg::inv)) stop_game_and_switch_mode(rg::inv); - if(chaosmode != (leave == rg::chaos)) stop_game_and_switch_mode(rg::chaos); - - if(peace::on != (leave == rg::peace)) stop_game_and_switch_mode(rg::peace); -#if CAP_TOUR - if(tour::on != (leave == rg::tour)) stop_game_and_switch_mode(rg::tour); -#endif - if(yendor::on != (leave == rg::yendor)) stop_game_and_switch_mode(rg::yendor); - if(tactic::on != (leave == rg::tactic)) stop_game_and_switch_mode(rg::tactic); - if(randomPatternsMode != (leave == rg::randpattern)) stop_game_and_switch_mode(rg::randpattern); - if(multi::players != 1) { - stop_game_and_switch_mode(); multi::players = 1; - } - if(firstland != laIce || specialland != laIce) { - stop_game(); - firstland = laIce; specialland = laIce; stop_game_and_switch_mode(); - } - - set_geometry(gNormal); - set_variation(leave == rg::heptagons ? eVariation::pure : eVariation::bitruncated); - - start_game(); - } - -#if CAP_CONFIG -EX void resetConfig() { - dynamicval rx(vid.xres, 0); - dynamicval ry(vid.yres, 0); - dynamicval rf(vid.fsize, 0); - dynamicval rfs(vid.full, false); - for(auto s: savers) - if(s->name.substr(0,5) != "mode-") - s->reset(); - } -#endif - -#if CAP_CONFIG -EX void saveConfig() { - DEBB(DF_INIT, ("save config\n")); - FILE *f = fopen(conffile, "wt"); - if(!f) { - addMessage(s0 + "Could not open the config file: " + conffile); - return; - } - - { - int pt_depth = 0, pt_camera = 0, pt_alpha = 0; - if(vid.tc_depth > vid.tc_camera) pt_depth++; - if(vid.tc_depth < vid.tc_camera) pt_camera++; - if(vid.tc_depth > vid.tc_alpha ) pt_depth++; - if(vid.tc_depth < vid.tc_alpha ) pt_alpha ++; - if(vid.tc_alpha > vid.tc_camera) pt_alpha++; - if(vid.tc_alpha < vid.tc_camera) pt_camera++; - vid.tc_alpha = pt_alpha; - vid.tc_camera = pt_camera; - vid.tc_depth = pt_depth; - } - - for(auto s: savers) if(s->dosave()) - fprintf(f, "%s=%s\n", s->name.c_str(), s->save().c_str()); - - fclose(f); -#if !ISMOBILE - addMessage(s0 + "Configuration saved to: " + conffile); -#else - addMessage(s0 + "Configuration saved"); -#endif - } - -void readf(FILE *f, ld& x) { - double fl = x; - hr::ignore(fscanf(f, "%lf", &fl)); - x = fl; - } - -map > allconfigs; - -EX void parseline(const string& str) { - if(str[0] == '#') return; - for(int i=0; i vid.yres * 1.4 ? sm::SIDE : sm::MAYDARK; - gamescreen(0); - dialog::init(XLAT("extra graphical effects")); - - dialog::addBoolItem_action(XLAT("particles on attack"), (vid.particles), 'p'); - dialog::addBoolItem_action(XLAT("floating bubbles: special"), vid.bubbles_special, 's'); - dialog::addBoolItem_action(XLAT("floating bubbles: treasure thresholds"), vid.bubbles_threshold, 't'); - dialog::addBoolItem_action(XLAT("floating bubbles: all treasures"), vid.bubbles_all, 'a'); - dialog::addBoolItem_action(XLAT("background particle effects"), (vid.backeffects), 'b'); - - dialog::addBreak(50); - dialog::addBack(); - dialog::display(); - } - -EX void showGraphConfig() { - cmode = vid.xres > vid.yres * 1.4 ? sm::SIDE : sm::MAYDARK; - gamescreen(0); - - dialog::init(XLAT("graphics configuration")); - - #if CAP_GLORNOT - dialog::addBoolItem(XLAT("openGL mode"), vid.usingGL, 'o'); - #endif - - if(!vid.usingGL) - dialog::addBoolItem(XLAT("anti-aliasing"), vid.antialias & AA_NOGL, 'O'); - - if(vid.usingGL) - dialog::addSelItem(XLAT("anti-aliasing"), - (vid.antialias & AA_POLY) ? "polygons" : - (vid.antialias & AA_LINES) ? "lines" : - (vid.antialias & AA_MULTI) ? "multisampling" : - "NO", 'O'); - - dialog::addSelItem(XLAT("vector graphics modes"), XLAT("width") + " " + fts(vid.linewidth), 'w'); - - dialog::addSelItem(XLAT("line quality"), its(vid.linequality), 'L'); - - #if CAP_FRAMELIMIT - dialog::addSelItem(XLAT("framerate limit"), its(vid.framelimit), 'l'); - if(getcstat == 'l') - mouseovers = XLAT("Reduce the framerate limit to conserve CPU energy"); - #endif - -#if !ISIOS && !ISWEB - dialog::addBoolItem(XLAT("fullscreen mode"), (vid.full), 'f'); -#endif - - dialog::addSelItem(XLAT("scrolling speed"), fts(vid.sspeed), 'a'); - - dialog::addSelItem(XLAT("camera movement speed"), fts(camera_speed), 'c'); - dialog::add_action([] { - dialog::editNumber(camera_speed, -10, 10, 0.1, 1, XLAT("camera movement speed"), - "This affects:\n\nin 2D: scrolling with arrow keys and Wheel Up\n\nin 3D: camera movement with Home/End." - ); - }); - dialog::addSelItem(XLAT("camera rotation speed"), fts(camera_rot_speed), 'r'); - dialog::add_action([] { - dialog::editNumber(camera_rot_speed, -10, 10, 0.1, 1, XLAT("camera rotation speed"), - "This affects view rotation with Page Up/Down, and in 3D, camera rotation with arrow keys or mouse." - ); - }); - - dialog::addSelItem(XLAT("movement animation speed"), fts(vid.mspeed), 'm'); - - dialog::addItem(XLAT("extra graphical effects"), 'u'); - - dialog::addBreak(50); - dialog::addBack(); - dialog::display(); - - keyhandler = [] (int sym, int uni) { - dialog::handleNavigation(sym, uni); - - if(uni == 'O') uni = 'o', shiftmul = -1; - - char xuni = uni | 96; - - if((uni >= 32 && uni < 64) || uni == 'L' || uni == 'C') xuni = uni; - - if(xuni == 'u') pushScreen(showSpecialEffects); - - else if(xuni == 'a') dialog::editNumber(vid.sspeed, -5, 5, 1, 0, - XLAT("scrolling speed"), - XLAT("+5 = center instantly, -5 = do not center the map") - + "\n\n" + - XLAT("press Space or Home to center on the PC")); - - else if(xuni == 'm') dialog::editNumber(vid.mspeed, -5, 5, 1, 0, - XLAT("movement animation speed"), - XLAT("+5 = move instantly")); - - else if(xuni == 'f') switchFullscreen(); - - #if CAP_GLORNOT - else if(xuni == 'o' && shiftmul > 0) switchGL(); - #endif - - else if(xuni == 'o' && shiftmul < 0) { - if(!vid.usingGL) - vid.antialias ^= AA_NOGL | AA_FONT; - else if(vid.antialias & AA_MULTI) - vid.antialias ^= AA_MULTI; - else if(vid.antialias & AA_POLY) - vid.antialias ^= AA_POLY | AA_LINES | AA_MULTI; - else if(vid.antialias & AA_LINES) - vid.antialias |= AA_POLY; - else - vid.antialias |= AA_LINES; -#if CAP_SDL - setvideomode(); -#endif - } - - // if(xuni == 'b') vid.antialias ^= AA_LINEWIDTH; - - else if(xuni == 'w') { - dialog::editNumber(vid.linewidth, 0, 10, 0.1, 1, XLAT("line width"), - vid.usingGL ? "" : XLAT("Line width setting is only taken into account in OpenGL.")); - dialog::extra_options = [] () { - dialog::addBoolItem("finer lines at the boundary", vid.antialias & AA_LINEWIDTH, 'O'); - dialog::add_action([] () { - vid.antialias ^= AA_LINEWIDTH; - }); - - dialog::addBoolItem("perfect width", perfect_linewidth == 2, 'P'); - if(perfect_linewidth == 1) - dialog::lastItem().value = XLAT("shots only"); - dialog::add_action([] { perfect_linewidth = (1 + perfect_linewidth) % 3; }); - - if(vid.antialias & AA_LINEWIDTH) { - dialog::addSelItem("variable width", fts(precise_width), 'M'); - dialog::add_action([] () { - popScreen(); - dialog::editNumber(precise_width, 0, 2, 0.1, 0.5, - XLAT("variable width"), XLAT("lines longer than this value will be split into shorter lines, with width computed separately for each of them.") - ); - }); - } - else dialog::addBreak(100); - - auto neon_option = [&] (string s, eNeon val, char key) { - dialog::addBoolItem(XLAT(s), neon_mode == val, key); - dialog::add_action([val] { neon_mode = (neon_mode == val) ? eNeon::none : val; }); - }; - - neon_option("neon mode", eNeon::neon, 'B'); - neon_option("no boundary mode", eNeon::no_boundary, 'C'); - neon_option("neon mode II", eNeon::neon2, 'D'); - neon_option("illustration mode", eNeon::illustration, 'E'); - dialog::addBreak(100); - dialog::addInfo(XLAT("hint: press Alt while testing modes")); - dialog::addBreak(100); - dialog::addBoolItem_action(XLAT("disable shadows"), noshadow, 'F'); - dialog::addBoolItem_action(XLAT("bright mode"), bright, 'G'); - dialog::addBoolItem_action(XLAT("colorblind simulation"), cblind, 'H'); - - dialog::addBoolItem_action(XLAT("no fill in neon mode"), neon_nofill, 'N'); - }; - } - - else if(xuni == 'L') { - dialog::editNumber(vid.linequality, -3, 5, 1, 1, XLAT("line quality"), - XLAT("Higher numbers make the curved lines smoother, but reduce the performance.")); - } - - #if CAP_FRAMELIMIT - else if(xuni == 'l') { - dialog::editNumber(vid.framelimit, 5, 300, 10, 300, XLAT("framerate limit"), ""); - dialog::bound_low(5); - } - #endif - - else if(xuni =='p') - vid.backeffects = !vid.backeffects; - - else if(doexiton(sym, uni)) popScreen(); - }; - } - -EX void switchFullscreen() { - vid.full = !vid.full; -#if ISANDROID - addMessage(XLAT("Reenter HyperRogue to apply this setting")); - ANDROID_SETTINGS -#endif -#if CAP_SDL - if(true) { - vid.xres = vid.full ? vid.xscr : 9999; - vid.yres = vid.full ? vid.yscr : 9999; - extern bool setfsize; - setfsize = true; - } - setvideomode(); -#endif - } - -EX void switchGL() { - vid.usingGL = !vid.usingGL; - if(vid.usingGL) addMessage(XLAT("openGL mode enabled")); - if(!vid.usingGL) addMessage(XLAT("openGL mode disabled")); - ANDROID_SETTINGS; -#if CAP_SDL - setvideomode(); - if(vid.usingGL) { - glhr::be_textured(); glhr::be_nontextured(); - } -#endif - } - -EX void edit_whatever(char type, int index) { - if(type == 'f') { - dialog::editNumber(whatever[index], -10, 10, 1, 0, XLAT("whatever"), - "f:" + its(index)); - } - else { - dialog::editNumber(whateveri[index], -10, 10, 1, 0, XLAT("whatever"), - "i:" + its(index)); - } - dialog::extra_options = [type, index] { - dialog::addItem(XLAT("integer"), 'X'); - dialog::add_action( [index] { popScreen(); edit_whatever('i', index); }); - dialog::addItem(XLAT("float"), 'Y'); - dialog::add_action( [index] { popScreen(); edit_whatever('f', index); }); - for(int x=0; x<8; x++) { - dialog::addSelItem(its(x), type == 'i' ? its(whateveri[x]) : fts(whatever[x]), 'A' + x); - dialog::add_action([type,x] { popScreen(); edit_whatever(type, x); }); - } - }; - } - -EX void configureOther() { - gamescreen(3); - - dialog::init(XLAT("other settings")); - -#if ISSTEAM - dialog::addBoolItem(XLAT("send scores to Steam leaderboards"), (vid.steamscore&1), 'x'); - dialog::add_action([] {vid.steamscore = vid.steamscore^1; }); -#endif - - dialog::addBoolItem_action(XLAT("skip the start menu"), vid.skipstart, 'm'); - - dialog::addItem(XLAT("memory configuration"), 'y'); - dialog::add_action_push(show_memory_menu); - - // dialog::addBoolItem_action(XLAT("forget faraway cells"), memory_saving_mode, 'y'); - -#if CAP_AUDIO - dialog::addSelItem(XLAT("background music volume"), its(musicvolume), 'b'); - dialog::add_action([] { - dialog::editNumber(musicvolume, 0, 128, 10, 60, XLAT("background music volume"), ""); - dialog::reaction = [] () { -#if CAP_SDLAUDIO - Mix_VolumeMusic(musicvolume); -#endif -#if ISANDROID - settingsChanged = true; -#endif - }; - dialog::bound_low(0); - dialog::bound_up(MIX_MAX_VOLUME); - dialog::extra_options = [] { -#if CAP_SDLAUDIO - dialog::addBoolItem_action(XLAT("play music when out of focus"), music_out_of_focus, 'A'); -#endif - }; - }); - - dialog::addSelItem(XLAT("sound effects volume"), its(effvolume), 'e'); - dialog::add_action([] { - dialog::editNumber(effvolume, 0, 128, 10, 60, XLAT("sound effects volume"), ""); - dialog::reaction = [] () { -#if ISANDROID - settingsChanged = true; -#endif - }; - dialog::bound_low(0); - dialog::bound_up(MIX_MAX_VOLUME); - }); -#endif - - menuitem_sightrange('r'); - -#ifdef WHATEVER - dialog::addSelItem(XLAT("whatever"), fts(whatever[0]), 'j'); - dialog::add_action([] { edit_whatever('f', 0); }); -#endif - - dialog::addBreak(50); - dialog::addBack(); - - dialog::display(); - } - -EX void configureInterface() { - gamescreen(3); - dialog::init(XLAT("interface")); - -#if CAP_TRANS - dialog::addSelItem(XLAT("language"), XLAT("EN"), 'l'); - dialog::add_action_push(selectLanguageScreen); -#endif - - dialog::addSelItem(XLAT("player character"), numplayers() > 1 ? "" : csname(vid.cs), 'g'); - dialog::add_action_push(showCustomizeChar); - if(getcstat == 'g') mouseovers = XLAT("Affects looks and grammar"); - - dialog::addSelItem(XLAT("message flash time"), its(vid.flashtime), 't'); - dialog::add_action([] { - dialog::editNumber(vid.flashtime, 0, 64, 1, 8, XLAT("message flash time"), - XLAT("How long should the messages stay on the screen.")); - dialog::bound_low(0); - }); - - dialog::addSelItem(XLAT("limit messages shown"), its(vid.msglimit), 'z'); - dialog::add_action([] { - dialog::editNumber(vid.msglimit, 0, 64, 1, 5, XLAT("limit messages shown"), - XLAT("Maximum number of messages on screen.")); - dialog::bound_low(0); - }); - - const char* msgstyles[3] = {"centered", "left-aligned", "line-broken"}; - - dialog::addSelItem(XLAT("message style"), XLAT(msgstyles[vid.msgleft]), 'a'); - dialog::add_action([] { - vid.msgleft = (1+vid.msgleft) % 3; - }); - - dialog::addSelItem(XLAT("font scale"), its(fontscale), 'b'); - dialog::add_action([] { - dialog::editNumber(fontscale, 25, 400, 10, 100, XLAT("font scale"), ""); - const int minfontscale = ISMOBILE ? 50 : 25; - dialog::reaction = [] () { setfsize = true; do_setfsize(); }; - dialog::bound_low(minfontscale); - }); - - const char *glyphsortnames[6] = { - "first on top", "first on bottom", - "last on top", "last on bottom", - "by land", "by number" - }; - dialog::addSelItem(XLAT("inventory/kill sorting"), XLAT(glyphsortnames[glyphsortorder]), 'k'); - dialog::add_action([] { - glyphsortorder = eGlyphsortorder((glyphsortorder+6+(shiftmul>0?1:-1)) % gsoMAX); - }); - - const char *glyphmodenames[3] = {"letters", "auto", "images"}; - dialog::addSelItem(XLAT("inventory/kill mode"), XLAT(glyphmodenames[vid.graphglyph]), 'd'); - dialog::add_action([] { - vid.graphglyph = (1+vid.graphglyph)%3; - }); - - dialog::addSelItem(XLAT("draw crosshair"), crosshair_size > 0 ? fts(crosshair_size) : ONOFF(false), 'x'); - dialog::add_action([] () { - dialog::editNumber(crosshair_size, 0, 100, 1, 10, XLAT("crosshair size"), XLAT( - "Display a targetting reticle in the center of the screen. Might be useful when exploring 3D modes, " - "as it precisely shows the direction we are going. However, the option is available in all modes." - )); - dialog::bound_low(0); - dialog::extra_options = [] { - dialog::addColorItem(XLAT("crosshair color"), crosshair_color, 'X'); - dialog::add_action([] { dialog::openColorDialog(crosshair_color); }); - }; - }); - - dialog::addBreak(50); - dialog::addBack(); - - dialog::display(); - } - -#if CAP_SDLJOY -EX void showJoyConfig() { - gamescreen(4); - - dialog::init(XLAT("joystick configuration")); - - dialog::addSelItem(XLAT("first joystick position (movement)"), its(joyx)+","+its(joyy), 0); - dialog::addSelItem(XLAT("second joystick position (panning)"), its(panjoyx)+","+its(panjoyy), 0); - - dialog::addSelItem(XLAT("joystick mode"), XLAT(autojoy ? "automatic" : "manual"), 'p'); - if(getcstat == 'p') { - if(autojoy) - mouseovers = XLAT("joystick mode: automatic (release the joystick to move)"); - if(!autojoy) - mouseovers = XLAT("joystick mode: manual (press a button to move)"); - } - - dialog::addSelItem(XLAT("first joystick: movement threshold"), its(vid.joyvalue), 'a'); - dialog::addSelItem(XLAT("first joystick: execute movement threshold"), its(vid.joyvalue2), 'b'); - dialog::addSelItem(XLAT("second joystick: pan threshold"), its(vid.joypanthreshold), 'c'); - dialog::addSelItem(XLAT("second joystick: panning speed"), fts(vid.joypanspeed * 1000), 'd'); - dialog::addSelItem(XLAT("smoothen"), its(vid.joysmooth) + " ms", 'e'); - - dialog::addBreak(50); - dialog::addBack(); - dialog::display(); - - keyhandler = [] (int sym, int uni) { - dialog::handleNavigation(sym, uni); - if(uni == 'p') autojoy = !autojoy; - else if(uni == 'a') { - dialog::editNumber(vid.joyvalue, 0, 32768, 100, 4800, XLAT("first joystick: movement threshold"), ""); - dialog::bound_low(0); - } - else if(uni == 'b') { - dialog::editNumber(vid.joyvalue2, 0, 32768, 100, 5600, XLAT("first joystick: execute movement threshold"), ""); - dialog::bound_low(0); - } - else if(uni == 'c') { - dialog::editNumber(vid.joypanthreshold, 0, 32768, 100, 2500, XLAT("second joystick: pan threshold"), ""); - dialog::bound_low(0); - } - else if(uni == 'd') - dialog::editNumber(vid.joypanspeed, 0, 1e-2, 1e-5, 1e-4, XLAT("second joystick: panning speed"), ""); - else if(uni == 'e') - dialog::editNumber(vid.joypanspeed, 0, 2000, 20, 200, XLAT("smoothen"), "large values help if the joystick is imprecise"); - - else if(doexiton(sym, uni)) popScreen(); - }; - } -#endif - -EX void projectionDialog() { - vid.tc_alpha = ticks; - dialog::editNumber(vpconf.alpha, -5, 5, .1, 1, - XLAT("projection"), - XLAT("HyperRogue uses the Minkowski hyperboloid model internally. " - "Klein and Poincaré models can be obtained by perspective, " - "and the Gans model is obtained by orthogonal projection. " -// "This parameter specifies the distance from the hyperboloid center " -// "to the eye. " - "See also the conformal mode (in the special modes menu) " - "for more models.")); - dialog::extra_options = [] () { - dialog::addBreak(100); - if(GDIM == 2) dialog::addHelp(XLAT( - "If we are viewing an equidistant g absolute units below a plane, " - "from a point c absolute units above the plane, this corresponds " - "to viewing a Minkowski hyperboloid from a point " - "tanh(g)/tanh(c) units below the center. This in turn corresponds to " - "the Poincaré model for g=c, and Klein-Beltrami model for g=0.")); - dialog::addSelItem(sphere ? "stereographic" : "Poincaré model", "1", 'P'); - dialog::add_action([] () { *dialog::ne.editwhat = 1; vpconf.scale = 1; dialog::ne.s = "1"; }); - dialog::addSelItem(sphere ? "gnomonic" : "Klein model", "0", 'K'); - dialog::add_action([] () { *dialog::ne.editwhat = 0; vpconf.scale = 1; dialog::ne.s = "0"; }); - if(hyperbolic) { - dialog::addSelItem("inverted Poincaré model", "-1", 'I'); - dialog::add_action([] () { *dialog::ne.editwhat = -1; vpconf.scale = 1; dialog::ne.s = "-1"; }); - } - dialog::addItem(sphere ? "orthographic" : "Gans model", 'O'); - dialog::add_action([] () { vpconf.alpha = vpconf.scale = 999; dialog::ne.s = dialog::disp(vpconf.alpha); }); - dialog::addItem(sphere ? "towards orthographic" : "towards Gans model", 'T'); - dialog::add_action([] () { double d = 1.1; vpconf.alpha *= d; vpconf.scale *= d; dialog::ne.s = dialog::disp(vpconf.alpha); }); - }; - } - -EX void explain_detail() { - dialog::addHelp(XLAT( - "Objects at distance less than %1 absolute units " - "from the center will be displayed with high " - "detail, and at distance at least %2 with low detail.", - fts(vid.highdetail), fts(vid.middetail) - )); - } - -EX void add_edit_fov(char key IS('f')) { - dialog::addSelItem(XLAT("field of view"), fts(vid.fov) + "°", key); - dialog::add_action([] { - dialog::editNumber(vid.fov, 1, 170, 1, 45, "field of view", - XLAT( - "Horizontal field of view, in angles. " - "This affects the Hypersian Rug mode (even when stereo is OFF) " - "and non-disk models.") - ); - dialog::bound_low(1e-8); - dialog::bound_up(179); - }); - } - -bool supported_ods() { - if(!CAP_ODS) return false; - return rug::rugged || (hyperbolic && GDIM == 3); - } - -EX void showStereo() { - cmode = sm::SIDE | sm::MAYDARK; - gamescreen(0); - dialog::init(XLAT("stereo vision config")); - - string modenames[4] = { "OFF", "anaglyph", "side-by-side", "ODS" }; - - dialog::addSelItem(XLAT("stereo mode"), XLAT(modenames[vid.stereo_mode]), 'm'); - dialog::addSelItem(XLAT("pupillary distance"), fts(vid.ipd), 'e'); - - switch(vid.stereo_mode) { - case sAnaglyph: - dialog::addSelItem(XLAT("distance between images"), fts(vid.anaglyph_eyewidth), 'd'); - break; - case sLR: - dialog::addSelItem(XLAT("distance between images"), fts(vid.lr_eyewidth), 'd'); - break; - default: - dialog::addBreak(100); - break; - } - - dialog::addSelItem(XLAT("desaturate colors"), its(vid.desaturate)+"%", 'c'); - dialog::add_action([] { - dialog::editNumber(vid.desaturate, 0, 100, 10, 0, XLAT("desaturate colors"), - XLAT("Make the game colors less saturated. This is useful in the anaglyph mode.") - ); - }); - - add_edit_fov('f'); - - dialog::addBack(); - dialog::display(); - - keyhandler = [] (int sym, int uni) { - dialog::handleNavigation(sym, uni); - - string help3 = XLAT( - "This allows you to view the world of HyperRogue in three dimensions. " - "Best used with the Hypersian Rug mode. When used in the disk model, " - "this lets you look at the Minkowski hyperboloid (which means the " - "depth of terrain features is actually reversed). It also works with non-disk models, " - "from the conformal menu." - ) + " " + XLAT( - "Currently, red-cyan anaglyph glasses and mobile VR googles are supported." - ) + "\n\n"; - - if(uni == 'm') { - vid.stereo_mode = eStereo((1 + vid.stereo_mode) % 4); - if(vid.stereo_mode == sODS && !supported_ods()) vid.stereo_mode = sOFF; - } - - else if(uni == 'e') - dialog::editNumber(vid.ipd, -10, 10, 0.01, 0, XLAT("pupillary distance"), - help3 + - XLAT("The distance between your eyes in the represented 3D object. This is given in absolute units.") - ), dialog::scaleSinh100(); - - else if(uni == 'd' && vid.stereo_mode == sAnaglyph) - dialog::editNumber(vid.anaglyph_eyewidth, -1, 1, 0.01, 0, XLAT("distance between images"), - help3 + - XLAT("The distance between your eyes. 1 is the width of the screen.")); - - else if(uni == 'd' && vid.stereo_mode == sLR) - dialog::editNumber(vid.lr_eyewidth, -1, 1, 0.01, 0, XLAT("distance between images"), - help3 + - XLAT("The distance between your eyes. 1 is the width of the screen.")); - - else if(doexiton(sym, uni)) popScreen(); - }; - } - -EX void config_camera_rotation() { - dialog::editNumber(pconf.ballangle, 0, 90, 5, 0, XLAT("camera rotation in 3D models"), - "Rotate the camera in 3D models (ball model, hyperboloid, and hemisphere). " - "Note that hyperboloid and hemisphere models are also available in the " - "Hypersian Rug surfaces menu, but they are rendered differently there -- " - "by making a flat picture first, then mapping it to a surface. " - "This makes the output better in some ways, but 3D effects are lost. " - "Hypersian Rug model also allows more camera freedom." - ); - } - -EX void add_edit_wall_quality(char c) { - dialog::addSelItem(XLAT("wall quality"), its(vid.texture_step), c); - dialog::add_action([] { - dialog::editNumber(vid.texture_step, 1, 16, 1, 1, XLAT("wall quality"), - XLAT( - "Controls the number of triangles used for wall surfaces. " - "Higher numbers reduce the performance. " - "This has a strong effect when the walls are curved indeed " - "(floors in 2D geometries, honeycombs based on horospheres, and projections other than native perspective), " - "but otherwise, usually it can be set to 1 without significant adverse effects other " - "than slightly incorrect texturing." - ) - ); - dialog::bound_low(1); - dialog::bound_up(128); - dialog::reaction = [] { - #if MAXMDIM >= 4 - if(floor_textures) { - delete floor_textures; - floor_textures = NULL; - } - #endif - }; - }); - } - -EX void edit_levellines(char c) { - if(levellines) - dialog::addSelItem(XLAT("level lines"), fts(levellines), c); - else - dialog::addBoolItem(XLAT("level lines"), false, c); - dialog::add_action([] { - dialog::editNumber(levellines, 0, 100, 0.5, 0, XLAT("level lines"), - XLAT( - "This feature superimposes level lines on the rendered screen. These lines depend on the Z coordinate. In 3D hyperbolic the Z coordinate is taken from the Klein model. " - "Level lines can be used to observe the curvature: circles correspond to positive curvature, while hyperbolas correspond to negative. See e.g. the Hypersian Rug mode.") - ); - dialog::reaction = ray::reset_raycaster; - dialog::extra_options = [] { - dialog::addBoolItem(XLAT("disable textures"), disable_texture, 'T'); - dialog::add_action([] { ray::reset_raycaster(); disable_texture = !disable_texture; }); - dialog::addItem(XLAT("disable level lines"), 'D'); - dialog::add_action([] { ray::reset_raycaster(); levellines = 0; popScreen(); }); - }; - dialog::bound_low(0); - }); - } - -EX void show3D() { - cmode = sm::SIDE | sm::MAYDARK; - gamescreen(0); - dialog::init(XLAT("3D configuration")); - -#if MAXMDIM >= 4 - if(WDIM == 2) { - dialog::addBoolItem(XLAT("use the full 3D models"), vid.always3, 'U'); - dialog::add_action(geom3::switch_always3); - } -#endif - if(vid.use_smart_range == 0 && GDIM == 2) { - dialog::addSelItem(XLAT("High detail range"), fts(vid.highdetail), 'n'); - dialog::addSelItem(XLAT("Mid detail range"), fts(vid.middetail), 'm'); - dialog::addBreak(50); - } - - if(WDIM == 2) { - dialog::addSelItem(XLAT(GDIM == 2 ? "Camera level above the plane" : "Z shift"), fts(vid.camera), 'c'); - if(GDIM == 3) - dialog::addSelItem(XLAT("Eye level"), fts(vid.eye), 'E'); - - dialog::addSelItem(XLAT("Ground level below the plane"), fts(vid.depth), 'g'); - - - if(GDIM == 2) - dialog::addSelItem(XLAT("Projection at the ground level"), fts(pconf.alpha), 'p'); - else if(!in_perspective()) - dialog::addSelItem(XLAT("Projection distance"), fts(pconf.alpha), 'p'); - - dialog::addBreak(50); - dialog::addSelItem(XLAT("Height of walls"), fts(vid.wall_height), 'w'); - - dialog::addSelItem(XLAT("Rock-III to wall ratio"), fts(vid.rock_wall_ratio), 'r'); - dialog::addSelItem(XLAT("Human to wall ratio"), fts(vid.human_wall_ratio), 'h'); - dialog::addSelItem(XLAT("Level of water surface"), fts(vid.lake_top), 'l'); - dialog::addSelItem(XLAT("Level of water bottom"), fts(vid.lake_bottom), 'k'); - if(scale_used()) - dialog::addSelItem(XLAT("Creature scale"), fts(vid.creature_scale), 'C'); - } - else { - dialog::addSelItem(XLAT("Creature scale"), fts(vid.creature_scale), 'c'); - dialog::addSelItem(XLAT("Height to width"), fts(vid.height_width), 'h'); - menuitem_sightrange('s'); - } - - dialog::addBreak(50); - dialog::addSelItem(XLAT(GDIM == 3 && WDIM == 2 ? "Y shift" : "third person perspective"), fts(vid.yshift), 'y'); - if(GDIM == 3) { - dialog::addSelItem(XLAT("mouse aiming sensitivity"), fts(mouseaim_sensitivity), 'a'); - dialog::add_action([] () { - dialog::editNumber(mouseaim_sensitivity, -1, 1, 0.002, 0.01, XLAT("mouse aiming sensitivity"), "set to 0 to disable"); - }); - } - if(true) { - dialog::addSelItem(XLAT("camera rotation"), fts(vpconf.camera_angle), 'S'); - dialog::add_action([] { - dialog::editNumber(vpconf.camera_angle, -180, 180, 5, 0, XLAT("camera rotation"), - XLAT("Rotate the camera. Can be used to obtain a first person perspective, " - "or third person perspective when combined with Y shift.") - ); - }); - } - if(GDIM == 2) { - dialog::addSelItem(XLAT("fixed facing"), vid.fixed_facing ? fts(vid.fixed_facing_dir) : XLAT("OFF"), 'f'); - dialog::add_action([] () { vid.fixed_facing = !vid.fixed_facing; - if(vid.fixed_facing) { - dialog::editNumber(vid.fixed_facing_dir, 0, 360, 15, 90, "", ""); - dialog::dialogflags |= sm::CENTER; - } - }); - } - - if((WDIM == 2 && GDIM == 3) || prod) - dialog::addBoolItem_action(XLAT("fixed Y/Z rotation"), vid.fixed_yz, 'Z'); - - if(true) { - dialog::addBreak(50); - dialog::addSelItem(XLAT("projection"), current_proj_name(), 'M'); - } - #if MAXMDIM >= 4 - if(GDIM == 3) add_edit_fov('f'); - if(GDIM == 3) { - dialog::addSelItem(XLAT("radar size"), fts(vid.radarsize), 'r'); - dialog::add_action([] () { - dialog::editNumber(vid.radarsize, 0, 360, 15, 90, "", XLAT("set to 0 to disable")); - dialog::extra_options = [] () { draw_radar(true); }; - }); - } - - if(WDIM == 3 && sphere && stretch::factor) { - dialog::addItem(XLAT("Berger sphere limit"), berger_limit); - dialog::add_action([] () { - dialog::editNumber(berger_limit, 0, 10, 1, 2, "", - XLAT("Primitive-based rendering of Berger sphere is currently very slow and low quality. " - "Here you can choose how many images to draw.") - ); - }); - } - - #if CAP_RAY - if(GDIM == 3) { - dialog::addItem(XLAT("configure raycasting"), 'A'); - dialog::add_action_push(ray::configure); - } - #endif - - edit_levellines('L'); - - if(WDIM == 3 || (GDIM == 3 && euclid)) { - dialog::addSelItem(XLAT("radar range"), fts(vid.radarrange), 'R'); - dialog::add_action([] () { - dialog::editNumber(vid.radarrange, 0, 10, 0.5, 2, "", XLAT("")); - dialog::extra_options = [] () { draw_radar(true); }; - }); - } - if(GDIM == 3) add_edit_wall_quality('W'); - #endif - - dialog::addBreak(50); - #if CAP_RUG - if(rug::rugged) { - dialog::addBoolItem_action(XLAT("3D monsters/walls on the surface"), rug::spatial_rug, 'S'); - } - #endif - if(GDIM == 2) { - dialog::addBoolItem(XLAT("configure TPP automatically"), pmodel == mdDisk && pconf.camera_angle, 'T'); - dialog::add_action(geom3::switch_tpp); - } - -#if MAXMDIM >=4 - if(WDIM == 2) { - dialog::addBoolItem(XLAT("configure FPP automatically"), GDIM == 3, 'F'); - dialog::add_action(geom3::switch_fpp); - } -#endif - - if(0); - #if CAP_RUG - else if(rug::rugged && !rug::spatial_rug) - dialog::addBreak(100); - #endif - else if(GDIM == 2 && non_spatial_model()) - dialog::addInfo(XLAT("no 3D effects available in this projection"), 0xC00000); - else if(GDIM == 2 && !spatial_graphics) - dialog::addInfo(XLAT("set 3D monsters or walls in basic config first")); - else if(geom3::invalid != "") - dialog::addInfo(XLAT("error: "+geom3::invalid), 0xC00000); - else - dialog::addInfo(XLAT("parameters set correctly")); - dialog::addBreak(50); - dialog::addItem(XLAT("stereo vision config"), 'e'); - dialog::addBack(); - dialog::display(); - - keyhandler = [] (int sym, int uni) { - using namespace geom3; - dialog::handleNavigation(sym, uni); - - if(uni == 'n' && GDIM == 2) { - dialog::editNumber(vid.highdetail, 0, 5, .5, 7, XLAT("High detail range"), ""); - dialog::extra_options = explain_detail; - dialog::reaction = [] () { - if(vid.highdetail > vid.middetail) vid.middetail = vid.highdetail; - }; - } - else if(uni == 'm' && GDIM == 2) { - dialog::editNumber(vid.middetail, 0, 5, .5, 7, XLAT("Mid detail range"), ""); - dialog::extra_options = explain_detail; - dialog::reaction = [] () { - if(vid.highdetail > vid.middetail) vid.highdetail = vid.middetail; - }; - } - else if(uni == 'c' && WDIM == 2) - vid.tc_camera = ticks, - dialog::editNumber(vid.camera, 0, 5, .1, 1, XLAT(GDIM == 2 ? "Camera level above the plane" : "Z shift"), ""), - dialog::extra_options = [] { - dialog::addHelp(GDIM == 2 ? XLAT( - "Camera is placed %1 absolute units above a plane P in a three-dimensional " - "world. Ground level is actually an equidistant surface, %2 absolute units " - "below the plane P. The plane P (as well as the ground level or any " - "other equidistant surface below it) is viewed at an angle of %3 " - "(the tangent of the angle between the point in " - "the center of your vision and a faraway location is 1/cosh(c) = %4).", - fts(vid.camera), - fts(vid.depth), - fts(atan(1/cosh(vid.camera))*2/degree), - fts(1/cosh(vid.camera))) : XLAT("Look from behind.")); - if(GDIM == 3 && pmodel == mdPerspective) dialog::extra_options = [] () { - dialog::addBoolItem_action(XLAT("reduce if walls on the way"), vid.use_wall_radar, 'R'); - }; - }; - else if(uni == 'g' && WDIM == 2) - vid.tc_depth = ticks, - dialog::editNumber(vid.depth, 0, 5, .1, 1, XLAT("Ground level below the plane"), ""), - dialog::extra_options = [] { - help = XLAT( - "Ground level is actually an equidistant surface, " - "%1 absolute units below the plane P. " - "Theoretically, this value affects the world -- " - "for example, eagles could fly %2 times faster by " - "flying above the ground level, on the plane P -- " - "but the actual game mechanics are not affected. ", fts(vid.depth), fts(cosh(vid.depth))); - if(GDIM == 2) - help += XLAT( - "(Distances reported by the vector graphics editor " - "are not about points on the ground level, but " - "about the matching points on the plane P -- " - "divide them by the factor above to get actual " - "distances.)" - ); - if(GDIM == 3 && pmodel == mdPerspective && !euclid) { - ld current_camera_level = hdist0(tC0(radar_transform)); - help += "\n\n"; - if(abs(current_camera_level) < 1e-6) - help += XLAT( - "The camera is currently exactly on the plane P. " - "The horizon is seen as a straight line." - ); - else help += XLAT( - "The camera is currently %1 units above the plane P. " - "This makes you see the floor level as in general perspective projection " - "with parameter %2.", fts(current_camera_level), fts(tan_auto(vid.depth) / tan_auto(current_camera_level))); - } - dialog::addHelp(help); - }; - else if(uni == 'E' && WDIM == 2 && GDIM == 3) - vid.tc_depth = ticks, - dialog::editNumber(vid.eye, -5, 5, .1, 0, XLAT("eye level"), ""), - dialog::dialogflags |= sm::CENTER, - dialog::extra_options = [] { - - dialog::addHelp(XLAT("In the FPP mode, the camera will be set at this altitude (before applying shifts).")); - - dialog::addBoolItem(XLAT("auto-adjust to eyes on the player model"), vid.auto_eye, 'O'); - dialog::reaction = [] { vid.auto_eye = false; }; - dialog::add_action([] () { - vid.auto_eye = !vid.auto_eye; - geom3::do_auto_eye(); - }); - }; - else if(uni == 'p' && WDIM == 2) - projectionDialog(); - else if(uni == 'w' && WDIM == 2) { - dialog::editNumber(vid.wall_height, 0, 1, .1, .3, XLAT("Height of walls"), ""); - dialog::extra_options = [] () { - dialog::addHelp(GDIM == 3 ? "" : XLAT( - "The height of walls, in absolute units. For the current values of g and c, " - "wall height of %1 absolute units corresponds to projection value of %2.", - fts(actual_wall_height()), fts(factor_to_projection(cgi.WALL)))); - dialog::addBoolItem(XLAT("auto-adjust in Goldberg grids"), vid.gp_autoscale_heights, 'O'); - dialog::add_action([] () { - vid.gp_autoscale_heights = !vid.gp_autoscale_heights; - }); - }; - } - else if(uni == 'l' && WDIM == 2) - dialog::editNumber(vid.lake_top, 0, 1, .1, .25, XLAT("Level of water surface"), ""); - else if(uni == 'k' && WDIM == 2) - dialog::editNumber(vid.lake_bottom, 0, 1, .1, .9, XLAT("Level of water bottom"), ""); - else if(uni == 'r' && WDIM == 2) - dialog::editNumber(vid.rock_wall_ratio, 0, 1, .1, .9, XLAT("Rock-III to wall ratio"), ""), - dialog::extra_options = [] { dialog::addHelp(XLAT( - "The ratio of Rock III to walls is %1, so Rock III are %2 absolute units high. " - "Length of paths on the Rock III level is %3 of the corresponding length on the " - "ground level.", - fts(vid.rock_wall_ratio), fts(vid.wall_height * vid.rock_wall_ratio), - fts(cosh(vid.depth - vid.wall_height * vid.rock_wall_ratio) / cosh(vid.depth)))); - }; - else if(uni == 'h' && WDIM == 2) - dialog::editNumber(vid.human_wall_ratio, 0, 1, .1, .7, XLAT("Human to wall ratio"), ""), - dialog::extra_options = [] { dialog::addHelp(XLAT( - "Humans are %1 " - "absolute units high. Your head travels %2 times the distance travelled by your " - "feet.", - fts(vid.wall_height * vid.human_wall_ratio), - fts(cosh(vid.depth - vid.wall_height * vid.human_wall_ratio) / cosh(vid.depth))) - ); - }; - else if(uni == 'h' && WDIM == 3) - dialog::editNumber(vid.height_width, 0, 1, .1, .7, XLAT("Height to width"), ""); - else if(uni == 'c' && WDIM == 3) - dialog::editNumber(vid.creature_scale, 0, 1, .1, .7, XLAT("Creature scale"), ""); - else if(uni == 'C' && WDIM == 2 && scale_used()) - dialog::editNumber(vid.creature_scale, 0, 1, .1, .7, XLAT("Creature scale"), ""); - - else if(uni == 'e') - pushScreen(showStereo); - - else if(uni == 'y') { - dialog::editNumber(vid.yshift, 0, 1, .1, 0, XLAT("Y shift"), - XLAT("Don't center on the player character.") - ); - if(WDIM == 3 && pmodel == mdPerspective) dialog::extra_options = [] () { - dialog::addBoolItem_action(XLAT("reduce if walls on the way"), vid.use_wall_radar, 'R'); - }; - } - else if(uni == 'b') - config_camera_rotation(); - else if(uni == 'M') - pushScreen(models::model_menu); - else if(doexiton(sym, uni)) - popScreen(); - }; - } - -EX void switchcolor(unsigned int& c, unsigned int* cs) { - dialog::openColorDialog(c, cs); - } - -double cc_footphase; -int lmousex, lmousey; - -EX void showCustomizeChar() { - - cc_footphase += hypot(mousex - lmousex, mousey - lmousey); - lmousex = mousex; lmousey = mousey; - - gamescreen(4); - dialog::init(XLAT("Customize character")); - - if(shmup::on || multi::players) multi::cpid = multi::cpid_edit % multi::players; - charstyle& cs = getcs(); - - dialog::addSelItem(XLAT("character"), csname(cs), 'g'); - dialog::addColorItem(XLAT("skin color"), cs.skincolor, 's'); - dialog::addColorItem(XLAT("eye color"), cs.eyecolor, 'e'); - dialog::addColorItem(XLAT("weapon color"), cs.swordcolor, 'w'); - dialog::addColorItem(XLAT("hair color"), cs.haircolor, 'h'); - - if(cs.charid >= 1) dialog::addColorItem(XLAT("dress color"), cs.dresscolor, 'd'); - else dialog::addBreak(100); - if(cs.charid == 3) dialog::addColorItem(XLAT("dress color II"), cs.dresscolor2, 'f'); - else dialog::addBreak(100); - - dialog::addColorItem(XLAT("movement color"), cs.uicolor, 'u'); - - if(!shmup::on && multi::players == 1) dialog::addSelItem(XLAT("save whom"), XLAT1(minf[moPrincess].name), 'p'); - - if(numplayers() > 1) dialog::addSelItem(XLAT("player"), its(multi::cpid+1), 'a'); - - dialog::addBoolItem(XLAT("left-handed"), cs.lefthanded, 'l'); - - dialog::addBreak(50); - dialog::addBack(); - dialog::display(); - - int firsty = dialog::items[0].position / 2; - int scale = firsty - 2 * vid.fsize; - - flat_model_enabler fme; - - initquickqueue(); - transmatrix V = atscreenpos(vid.xres/2, firsty, scale); - - double alpha = atan2(mousex - vid.xres/2, mousey - firsty) - M_PI/2; - V = V * spin(alpha); - drawMonsterType(moPlayer, NULL, shiftless(V), 0, cc_footphase / scale, NOCOLOR); - quickqueue(); - - keyhandler = [] (int sym, int uni) { - dialog::handleNavigation(sym, uni); - - if(shmup::on || multi::players) multi::cpid = multi::cpid_edit % multi::players; - charstyle& cs = getcs(); - bool cat = cs.charid >= 4; - if(uni == 'a') { multi::cpid_edit++; multi::cpid_edit %= 60; } - else if(uni == 'g') { - cs.charid++; - if(cs.charid == 2 && !princess::everSaved && !autocheat) cs.charid = 4; - cs.charid %= 10; - } - else if(uni == 'p') vid.samegender = !vid.samegender; - else if(uni == 's') switchcolor(cs.skincolor, cat ? haircolors : skincolors); - else if(uni == 'h') switchcolor(cs.haircolor, haircolors); - else if(uni == 'w') switchcolor(cs.swordcolor, swordcolors); - else if(uni == 'd') switchcolor(cs.dresscolor, cat ? haircolors : dresscolors); - else if(uni == 'f') switchcolor(cs.dresscolor2, dresscolors2); - else if(uni == 'u') switchcolor(cs.uicolor, eyecolors); - else if(uni == 'e') switchcolor(cs.eyecolor, eyecolors); - else if(uni == 'l') cs.lefthanded = !cs.lefthanded; - else if(doexiton(sym, uni)) popScreen(); - }; - } - -EX void refresh_canvas() { - manual_celllister cl; - cl.add(cwt.at); - - int at = 0; - while(at < isize(cl.lst)) { - cell *c2 = cl.lst[at]; - c2->landparam = patterns::generateCanvas(c2); - at++; - - forCellEx(c3, c2) cl.add(c3); - } - } - -EX void edit_color_table(colortable& ct, const reaction_t& r IS(reaction_t()), bool has_bit IS(false)) { - cmode = sm::SIDE; - gamescreen(0); - dialog::init(XLAT("colors & aura")); - - for(int i=0; iland == laMinefield) { - dialog::addItem(XLAT("minefield colors"), 'm'); - dialog::add_action_push([] { edit_color_table(minecolors); }); - } - - if(viewdists) { - dialog::addItem(XLAT("distance colors"), 'd'); - dialog::add_action_push([] () {edit_color_table(distcolors); }); - } - - #if CAP_CRYSTAL - if(cryst && cheater) { - dialog::addItem(XLAT("crystal coordinate colors"), 'C'); - dialog::add_action([] () { crystal::view_coordinates = true; pushScreen([] () { edit_color_table(crystal::coordcolors); });}); - } - #endif - - if(cwt.at->land == laTortoise) { - dialog::addBoolItem_action(XLAT("Galápagos shading"), tortoise::shading_enabled, 'T'); - } - - dialog::addInfo(XLAT("colors of some game objects can be edited by clicking them.")); - - dialog::addBreak(50); - - dialog::addSelItem(XLAT("aura brightness"), its(vid.aurastr), 'a'); - dialog::add_action([] () { dialog::editNumber(vid.aurastr, 0, 256, 10, 128, XLAT("aura brightness"), ""); dialog::bound_low(0); }); - - dialog::addSelItem(XLAT("aura smoothening factor"), its(vid.aurasmoothen), 's'); - dialog::add_action([] () { dialog::editNumber(vid.aurasmoothen, 1, 180, 1, 5, XLAT("aura smoothening factor"), ""); dialog::bound_low(1); }); - - dialog::addBreak(50); - dialog::addBack(); - dialog::display(); - - keyhandler = [] (int sym, int uni) { - if(uni == '-') { - cell *c = mouseover; - if(!c) return; - else if(c == cwt.at) { - pushScreen(showCustomizeChar); - return; - } - else if(c->monst) - dialog::openColorDialog(minf[c->monst].color); - else if(c->item) - dialog::openColorDialog(iinf[c->item].color); - else if(c->wall) - dialog::openColorDialog(winf[c->wall == waMineMine ? waMineUnknown : c->wall].color); - #if CAP_COMPLEX2 - else if(c->land == laBrownian) - dialog::openColorDialog(brownian::get_color_edit(c->landparam)); - #endif - else - dialog::openColorDialog(floorcolors[c->land]); - dialog::colorAlpha = false; - dialog::dialogflags |= sm::SIDE; - return; - } - else dialog::handleNavigation(sym, uni); - if(doexiton(sym, uni)) popScreen(); - }; - } - -#if CAP_CONFIG -EX void resetConfigMenu() { - dialog::init(XLAT("reset all configuration")); - dialog::addInfo("Are you sure?"); - dialog::addItem("yes, and delete the config file", 'd'); - dialog::addItem("yes", 'y'); - dialog::addItem("cancel", 'n'); - dialog::addItem("reset the special game modes", 'r'); - dialog::display(); - keyhandler = [] (int sym, int uni) { - dialog::handleNavigation(sym, uni); - - if(uni == 'd') { - resetConfig(); - unlink(conffile); - popScreen(); - } - else if(uni == 'y') { - printf("resetting config\n"); - resetConfig(); - printf("config reset\n"); - popScreen(); - } - else if(uni == 'r') - resetModes(); - else if(uni == 'n' || doexiton(sym, uni)) - popScreen(); - - }; - } -#endif - -#if CAP_TRANS -EX void selectLanguageScreen() { - gamescreen(4); - dialog::init("select language"); // intentionally not translated - - int v = vid.language; - dynamicval d(vid.language, -1); - - for(int i=0; i= 1) - dialog::addHelp(XLAT("add credits for your translation here")); - else - dialog::addHelp(XLAT("original language")); - - if(lang() != 0) { - string tw = ""; - string s = XLAT("TRANSLATIONWARNING"); - if(s != "" && s != "TRANSLATIONWARNING") tw += s; - s = XLAT("TRANSLATIONWARNING2"); - if(s != "" && s != "TRANSLATIONWARNING2") { if(tw != "") tw += " "; tw += s; } - if(tw != "") { - dialog::addHelp(tw); - dialog::lastItem().color = 0xFF0000; - } - } - - dialog::display(); - - keyhandler = [] (int sym, int uni) { - dialog::handleNavigation(sym, uni); - - if(uni == '0') { - vid.language = -1; - ANDROID_SETTINGS; - } - - else if(uni >= 'a' && uni < 'a'+NUMLAN) { - vid.language = uni - 'a'; - ANDROID_SETTINGS; - } - - else if(doexiton(sym, uni)) - popScreen(); - }; - } -#endif - -EX void configureMouse() { - gamescreen(1); - dialog::init(XLAT("mouse & touchscreen")); - - dialog::addBoolItem_action(XLAT("reverse pointer control"), (vid.revcontrol), 'r'); - - dialog::addBoolItem_action(XLAT("draw circle around the target"), (vid.drawmousecircle), 'd'); - - if(GDIM == 3) { - dialog::addBoolItem_action(XLAT("highlight the cell forward"), vid.axes3, 'f'); - } - -#if ISMOBILE - dialog::addBoolItem(XLAT("targetting ranged Orbs long-click only"), (vid.shifttarget&2), 'i'); -#else - dialog::addBoolItem(XLAT("targetting ranged Orbs Shift+click only"), (vid.shifttarget&1), 'i'); -#endif - dialog::add_action([] {vid.shifttarget = vid.shifttarget^3; }); - - #if !ISMOBILE - dialog::addBoolItem_action(XLAT("quick mouse"), vid.quickmouse, 'M'); - #endif - - dialog::addSelItem(XLAT("move by clicking on compass"), its(vid.mobilecompasssize), 'C'); - dialog::add_action([] { - dialog::editNumber(vid.mobilecompasssize, 0, 100, 10, 20, XLAT("compass size"), XLAT("0 to disable")); - // we need to check the moves - dialog::reaction = checkmove; - dialog::bound_low(0); - }); - - #if CAP_ORIENTATION - if(GDIM == 2) { - dialog::addSelItem(XLAT("scrolling by device rotation"), ors::choices[ors::mode], '1'); - dialog::add_action_push(ors::show); - } - #endif - - dialog::addBack(); - dialog::display(); - } - -EX void showSettings() { - gamescreen(1); - dialog::init(XLAT("settings")); - - dialog::addItem(XLAT("interface"), 'i'); - dialog::add_action_push(configureInterface); - - dialog::addItem(XLAT("general graphics"), 'g'); - dialog::add_action_push(showGraphConfig); - - dialog::addItem(XLAT("3D configuration"), '9'); - dialog::add_action_push(show3D); - - dialog::addItem(XLAT("quick options"), 'q'); - dialog::add_action_push(showGraphQuickKeys); - - dialog::addItem(XLAT("models & projections"), 'p'); - dialog::add_action_push(models::model_menu); - - dialog::addItem(XLAT("colors & aura"), 'c'); - dialog::add_action_push(show_color_dialog); - -#if CAP_SHMUP && !ISMOBILE - dialog::addSelItem(XLAT("keyboard & joysticks"), "", 'k'); - dialog::add_action(multi::configure); -#endif - - dialog::addSelItem(XLAT("mouse & touchscreen"), "", 'm'); - dialog::add_action_push(configureMouse); - - dialog::addItem(XLAT("other settings"), 'o'); - dialog::add_action_push(configureOther); - - dialog::addBreak(100); - -#if CAP_CONFIG - dialog::addItem(XLAT("save the current config"), 's'); - dialog::add_action(saveConfig); - - dialog::addItem(XLAT("reset all configuration"), 'R'); - dialog::add_action_push(resetConfigMenu); -#endif - - if(getcstat == 's') mouseovers = XLAT("Config file: %1", conffile); - - dialog::addBack(); - dialog::display(); - } - -#if CAP_COMMANDLINE - -EX int read_color_args() { - using namespace arg; - - if(argis("-back")) { - PHASEFROM(2); shift(); backcolor = arghex(); - } - else if(argis("-fillmodel")) { - PHASEFROM(2); shift(); modelcolor = arghex(); - } - else if(argis("-ring")) { - PHASEFROM(2); shift(); ringcolor = arghex(); - } - else if(argis("-ringw")) { - PHASEFROM(2); shift_arg_formula(vid.multiplier_ring); - } - else if(argis("-stdgrid")) { - PHASEFROM(2); shift(); stdgridcolor = arghex(); - } - else if(argis("-gridw")) { - PHASEFROM(2); shift_arg_formula(vid.multiplier_grid); - } - else if(argis("-period")) { - PHASEFROM(2); shift(); periodcolor = arghex(); - } - else if(argis("-crosshair")) { - PHASEFROM(2); shift(); crosshair_color = arghex(); - shift_arg_formula(crosshair_size); - } - else if(argis("-borders")) { - PHASEFROM(2); shift(); bordcolor = arghex(); - } - else if(argis("-fore")) { - PHASEFROM(2); shift(); forecolor = arghex(); - } - else if(argis("-dialog")) { - PHASEFROM(2); shift(); dialog::dialogcolor = arghex(); - } - else if(argis("-d:color")) - launch_dialog(show_color_dialog); - else return 1; - return 0; - } - -EX int read_config_args() { - using namespace arg; - - if(argis("-c")) { PHASE(1); shift(); conffile = argcs(); } -// change the configuration from the command line - else if(argis("-aa")) { PHASEFROM(2); shift(); vid.antialias = argi(); } - else if(argis("-lw")) { PHASEFROM(2); shift_arg_formula(vid.linewidth); } - else if(argis("-wm")) { PHASEFROM(2); shift(); vid.wallmode = argi(); } - else if(argis("-mm")) { PHASEFROM(2); shift(); vid.monmode = argi(); } - - else if(argis("-noshadow")) { noshadow = true; } - else if(argis("-bright")) { bright = true; } - else if(argis("-gridon")) { vid.grid = true; } - else if(argis("-gridoff")) { vid.grid = false; } - -// non-configurable options - else if(argis("-vsync_off")) { - #if CAP_SDL && CAP_GL - vsync_off = true; - if(curphase == 3) setvideomode(); - #endif - } - else if(argis("-aura")) { - PHASEFROM(2); - shift(); vid.aurastr = argi(); - shift(); vid.aurasmoothen = argi(); - } - else if(argis("-nofps")) { - PHASEFROM(2); - nofps = true; - } - else if(argis("-nohud")) { - PHASEFROM(2); - nohud = true; - } - else if(argis("-nomenu")) { - PHASEFROM(2); - nomenukey = true; - } -#if MAXMDIM >= 4 - else if(argis("-switch-fpp")) { - PHASEFROM(2); - geom3::switch_fpp(); - } -#endif - else if(argis("-switch-tpp")) { - PHASEFROM(2); - geom3::switch_tpp(); - } -#if MAXMDIM >= 4 - else if(argis("-switch-3d")) { - PHASEFROM(2); - geom3::switch_always3(); - } -#endif - else if(argis("-nohelp")) { - PHASEFROM(2); - nohelp = true; - } - else if(argis("-dont_face_pc")) { - PHASEFROM(2); - dont_face_pc = true; - } - -#if CAP_TRANS - else if(argis("-lang")) { - PHASEFROM(2); shift(); vid.language = argi(); - } -#endif - else if(argis("-vlq")) { - PHASEFROM(2); shift(); vid.linequality = argi(); - } - else if(argis("-fov")) { - PHASEFROM(2); shift_arg_formula(vid.fov); - } - else if(argis("-r")) { - PHASEFROM(2); - shift(); - int clWidth=0, clHeight=0, clFont=0; - sscanf(argcs(), "%dx%dx%d", &clWidth, &clHeight, &clFont); - if(clWidth) vid.xres = clWidth; - if(clHeight) vid.yres = clHeight; - if(clFont) vid.fsize = clFont; - } - else if(argis("-msm")) { - PHASEFROM(2); memory_saving_mode = true; - } - else if(argis("-mrsv")) { - PHASEFROM(2); shift(); reserve_limit = argi(); apply_memory_reserve(); - } - else if(argis("-yca")) { - PHASEFROM(2); - shift_arg_formula(vid.yshift); - shift_arg_formula(pconf.camera_angle); - } - else if(argis("-pside")) { - PHASEFROM(2); - permaside = true; - } - else if(argis("-xy")) { - PHASEFROM(2); - shift_arg_formula(pconf.xposition); - shift_arg_formula(pconf.yposition); - } - else if(argis("-fixdir")) { - PHASEFROM(2); - vid.fixed_facing = true; - shift_arg_formula(vid.fixed_facing_dir); - } - else if(argis("-fixdiroff")) { - PHASEFROM(2); - vid.fixed_facing = false; - } - else if(argis("-msmoff")) { - PHASEFROM(2); memory_saving_mode = false; - } - else if(argis("-levellines")) { - PHASEFROM(2); shift_arg_formula(levellines); - } - else if(argis("-level-notexture")) { - PHASEFROM(2); disable_texture = true; - } - else if(argis("-level-texture")) { - PHASEFROM(2); disable_texture = false; - } - else if(argis("-msens")) { - PHASEFROM(2); shift_arg_formula(mouseaim_sensitivity); - } - TOGGLE('o', vid.usingGL, switchGL()) - TOGGLE('f', vid.full, switchFullscreen()) - else if(argis("-noshaders")) { - PHASE(1); - glhr::noshaders = true; - } - else if(argis("-d:sight")) { - PHASEFROM(2); launch_dialog(); edit_sightrange(); - } - else if(argis("-d:char")) { - PHASEFROM(2); launch_dialog(showCustomizeChar); - } - else if(argis("-d:3")) { - PHASEFROM(2); launch_dialog(show3D); - } - else if(argis("-d:stereo")) { - PHASEFROM(2); launch_dialog(showStereo); - } - else if(argis("-d:iface")) { - PHASEFROM(2); launch_dialog(configureInterface); - } - else if(argis("-d:graph")) { - PHASEFROM(2); launch_dialog(showGraphConfig); - } - else if(argis("-tstep")) { - PHASEFROM(2); shift(); vid.texture_step = argi(); - } - else if(argis("-csc")) { - PHASEFROM(2); shift_arg_formula(vid.creature_scale); - } - else if(argis("-neon")) { - PHASEFROM(2); - shift(); neon_mode = eNeon(argi()); - } - else if(argis("-smooths")) { - PHASEFROM(2); - shift(); smooth_scrolling = argi(); - } - else if(argis("-via-shader")) { - PHASEFROM(2); - shift(); vid.consider_shader_projection = argi(); - } - else if(argis("-neonnf")) { - PHASEFROM(2); - shift(); neon_nofill = argi(); - } - else if(argis("-precw")) { - PHASEFROM(2); - shift_arg_formula(precise_width); - } - else if(argis("-char")) { - auto& cs = vid.cs; - shift(); cs.charid = argi(); - cs.lefthanded = cs.charid >= 10; - cs.charid %= 10; - } - else return 1; - return 0; - } - -// mode changes: - -EX int read_gamemode_args() { - using namespace arg; - - if(argis("-P")) { - PHASE(2); shift(); - stop_game_and_switch_mode(rg::nothing); - multi::players = argi(); - } - TOGGLE('C', chaosmode, stop_game_and_switch_mode(rg::chaos)) - TOGGLE('S', shmup::on, stop_game_and_switch_mode(rg::shmup)) - TOGGLE('H', hardcore, switchHardcore()) - TOGGLE('R', randomPatternsMode, stop_game_and_switch_mode(rg::randpattern)) - TOGGLE('i', inv::on, stop_game_and_switch_mode(rg::inv)) - - else return 1; - return 0; - } - -auto ah_config = addHook(hooks_args, 0, read_config_args) + addHook(hooks_args, 0, read_gamemode_args) + addHook(hooks_args, 0, read_color_args); -#endif - -EX unordered_map params = { - {"linewidth", vid.linewidth}, - {"patternlinewidth", linepatterns::width}, - {"scale", pconf.scale}, - {"xposition", pconf.xposition}, - {"yposition", pconf.yposition}, - {"projection", pconf.alpha}, - {"sspeed", vid.sspeed}, - {"mspeed", vid.mspeed}, - {"ballangle", pconf.ballangle}, - {"yshift", vid.yshift}, - {"cameraangle", pconf.camera_angle}, - {"eye", vid.eye}, - {"depth", vid.depth}, - {"camera", vid.camera}, - {"wall_height", vid.wall_height}, - {"highdetail", vid.highdetail}, - {"middetail", vid.middetail}, - {"rock_wall_ratio", vid.rock_wall_ratio}, - {"human_wall_ratio", vid.human_wall_ratio}, - {"lake_top", vid.lake_top}, - {"lake_bottom", vid.lake_bottom}, - #if CAP_RUG - {"rug_model_distance", rug::model_distance}, - #endif - {"star", polygonal::STAR}, - {"lvspeed", history::lvspeed}, - {"rotation", models::rotation}, - {"mori", pconf.model_orientation}, - {"mori_yz", pconf.model_orientation_yz}, - {"clipmin", pconf.clip_min}, - {"clipmax", pconf.clip_max}, - {"topz", pconf.top_z}, - {"mtrans", pconf.model_transition}, - {"hp", pconf.halfplane_scale}, - {"back", backbrightness}, - {"ipd", vid.ipd}, - {"lr", vid.lr_eyewidth}, - {"anaglyph", vid.anaglyph_eyewidth}, - {"fov", vid.fov}, - {"ets", pconf.euclid_to_sphere}, - {"stretch", pconf.stretch}, - {"twopoint", pconf.twopoint_param}, - {"fisheye", pconf.fisheye_param}, - {"bwidth", vid.binary_width}, - #if CAP_ANIMATIONS - {"aperiod", anims::period}, - {"acycle", anims::cycle_length}, - {"aparabolic", anims::parabolic_length}, - {"arugangle", anims::rug_angle}, - {"acradius", anims::circle_radius}, - {"acspins", anims::circle_spins}, - {"a", anims::a}, - {"b", anims::b}, - #endif - {"mobius", pconf.skiprope}, - {"sang", pconf.spiral_angle}, - {"spiralx", pconf.spiral_x}, - {"spiraly", pconf.spiral_y}, - #if CAP_CRYSTAL - {"cprob", crystal::compass_probability}, - #endif - #if CAP_SHOT - {"gamma", shot::gamma}, - {"fade", shot::fade}, - {"mgrid", vid.multiplier_grid}, - {"mring", vid.multiplier_ring}, - {"collignon", pconf.collignon_parameter}, - {"aitoff", pconf.aitoff_parameter}, - {"loxidromic", pconf.loximuthal_parameter}, - {"miller", pconf.miller_parameter}, - {"winkel", pconf.winkel_parameter}, - {"camspd", camera_speed}, - {"camrot", camera_rot_speed}, - {"levellines", levellines}, - #endif - }; - -} +// Hyperbolic Rogue -- configuration +// Copyright (C) 2017-2018 Zeno Rogue, see 'hyper.cpp' for details + +/** \file config.cpp + * \brief Configuration -- initial settings, saving/loading ini files, menus, etc. + */ + +#include "hyper.h" +namespace hr { + +#if HDR +enum eCentering { face, edge, vertex }; +#endif + +EX eCentering centering; + +#if HDR +struct supersaver { + string name; + virtual string save() = 0; + virtual void load(const string& s) = 0; + virtual bool dosave() = 0; + virtual void reset() = 0; + virtual ~supersaver() {}; + virtual bool affects(void* v) { return false; } + virtual void set_default() = 0; + }; + +typedef vector> saverlist; + +extern saverlist savers; + +#if CAP_CONFIG + +template struct dsaver : supersaver { + T& val; + T dft; + bool dosave() { return val != dft; } + void reset() { val = dft; } + dsaver(T& val) : val(val) { } + bool affects(void* v) { return v == &val; } + void set_default() { dft = val; } + }; + +template struct saver : dsaver {}; + +template void addsaver(T& i, U name, V dft) { + auto s = make_shared> (i); + s->dft = dft; + s->name = name; + savers.push_back(s); + } + +template void addsaver(T& i, string name) { + addsaver(i, name, i); + } + +template void removesaver(T& val) { + for(int i=0; iaffects(&val)) + savers.erase(savers.begin() + i); + } + +template void set_saver_default(T& val) { + for(auto sav: savers) + if(sav->affects(&val)) + sav->set_default(); + } + +template struct saverenum : supersaver { + T& val; + T dft; + bool dosave() { return val != dft; } + void reset() { val = dft; } + saverenum(T& v) : val(v) { } + string save() { return its(int(val)); } + void load(const string& s) { val = (T) atoi(s.c_str()); } + virtual bool affects(void* v) { return v == &val; } + virtual void set_default() { dft = val; } + }; + +template void addsaverenum(T& i, U name, T dft) { + auto s = make_shared> (i); + s->dft = dft; + s->name = name; + savers.push_back(s); + } + +template void addsaverenum(T& i, U name) { + addsaverenum(i, name, i); + } + +template<> struct saver : dsaver { + saver(int& val) : dsaver(val) { } + string save() { return its(val); } + void load(const string& s) { val = atoi(s.c_str()); } + }; + +template<> struct saver : dsaver { + saver(char& val) : dsaver(val) { } + string save() { return its(val); } + void load(const string& s) { val = atoi(s.c_str()); } + }; + +template<> struct saver : dsaver { + saver(bool& val) : dsaver(val) { } + string save() { return val ? "yes" : "no"; } + void load(const string& s) { val = isize(s) && s[0] == 'y'; } + }; + +template<> struct saver : dsaver { + saver(unsigned& val) : dsaver(val) { } + string save() { return itsh(val); } + void load(const string& s) { val = (unsigned) strtoll(s.c_str(), NULL, 16); } + }; + +template<> struct saver : dsaver { + saver(string& val) : dsaver(val) { } + string save() { return val; } + void load(const string& s) { val = s; } + }; + +template<> struct saver : dsaver { + saver(ld& val) : dsaver(val) { } + string save() { return fts(val, 10); } + void load(const string& s) { + if(s == "0.0000000000e+000") ; // ignore! + else val = atof(s.c_str()); + } + }; +#endif + +void addparam(ld& val, const string s); +#endif + +#if CAP_CONFIG +void addparam(ld& val, const string s) { + addsaver(val, s); + params.insert({s, val}); + } +#else +void addparam(ld& val, const string s) { + params.insert({s, val}); + } +#endif + +EX ld bounded_mine_percentage = 0.1; +EX int bounded_mine_quantity, bounded_mine_max; + +EX const char *conffile = "hyperrogue.ini"; + +/* extra space if more geometries are added */ +EX array sightranges; + +EX videopar vid; + +#define DEFAULT_WALLMODE (ISMOBILE ? 3 : 5) +#define DEFAULT_MONMODE (ISMOBILE ? 2 : 4) + +#if ISANDROID +#define ANDROID_SETTINGS settingsChanged = true; +#else +#define ANDROID_SETTINGS ; +#endif + +extern color_t floorcolors[landtypes]; + +EX charstyle& getcs(int id IS(multi::cpid)) { + if(multi::players>1 && id >= 0 && id < multi::players) + return multi::scs[id]; + else + return vid.cs; + } + +struct charstyle_old { + int charid; + color_t skincolor, haircolor, dresscolor, swordcolor, dresscolor2, uicolor; + bool lefthanded; + }; + +EX void hread(hstream& hs, charstyle& cs) { + // before 0xA61A there was no eyecolor + if(hs.get_vernum() < 0xA61A) { + charstyle_old cso; + hread_raw(hs, cso); + cs.charid = cso.charid; + cs.skincolor = cso.skincolor; + cs.haircolor = cso.haircolor; + cs.dresscolor = cso.dresscolor; + cs.swordcolor = cs.eyecolor = cso.swordcolor; + if(cs.charid < 4) cs.eyecolor = 0; + cs.dresscolor2 = cso.dresscolor2; + cs.uicolor = cso.uicolor; + cs.lefthanded = cso.lefthanded; + } + else hread_raw(hs, cs); + } + +EX void hwrite(hstream& hs, const charstyle& cs) { + hwrite_raw(hs, cs); + } + +// void hread(hstream& hs, charstyle& cs) { hread_raw(hs, cs); } +// void hwrite(hstream& hs, const charstyle& cs) { hwrite_raw(hs, cs); } + +EX string csnameid(int id) { + if(id == 0) return XLAT("male"); + if(id == 1) return XLAT("female"); + if(id == 2) return XLAT("Prince"); + 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"); + } + +EX string csname(charstyle& cs) { + return csnameid(cs.charid); + } + +EX int playergender() { + return (getcs().charid >= 0 && (getcs().charid&1)) ? GEN_F : GEN_M; + } +EX int princessgender() { + int g = playergender(); + if(vid.samegender) return g; + return g == GEN_M ? GEN_F : GEN_M; + } + +EX int default_language; + +EX int lang() { + if(vid.language >= 0) + return vid.language; + return default_language; + } + +EX bool autojoy = true; + +#if CAP_CONFIG +saverlist savers; +#endif + +#if !CAP_CONFIG +template void addsaver(T& i, U name, V dft) { + i = dft; + } + +template void addsaver(T& i, U name) {} +template void addsaverenum(T& i, U name) {} +template void addsaverenum(T& i, U name, T dft) {} +#endif + +EX void addsaver(charstyle& cs, string s) { + addsaver(cs.charid, s + ".charid"); + addsaver(cs.skincolor, s + ".skincolor"); + addsaver(cs.eyecolor, s + ".eyecolor"); + addsaver(cs.haircolor, s + ".haircolor"); + addsaver(cs.dresscolor, s + ".dresscolor"); + addsaver(cs.swordcolor, s + ".swordcolor"); + addsaver(cs.dresscolor2, s + ".dresscolor2"); + addsaver(cs.uicolor, s + ".uicolor"); + addsaver(cs.lefthanded, s + ".lefthanded"); + } + +// R:239, G:208, B:207 + +unsigned int skincolors[] = { 7, 0xD0D0D0FF, 0xEFD0C9FF, 0xC77A58FF, 0xA58869FF, 0x602010FF, 0xFFDCB1FF, 0xEDE4C8FF }; +unsigned int haircolors[] = { 8, 0x686868FF, 0x8C684AFF, 0xF2E1AEFF, 0xB55239FF, 0xFFFFFFFF, 0x804000FF, 0x502810FF, 0x301800FF }; +unsigned int dresscolors[] = { 6, 0xC00000FF, 0x00C000FF, 0x0000C0FF, 0xC0C000FF, 0xC0C0C0FF, 0x202020FF }; +unsigned int dresscolors2[] = { 7, 0x8080FFC0, 0x80FF80C0, 0xFF8080C0, 0xFFFF80C0, 0xFF80FFC0, 0x80FFFFC0, 0xFFFFFF80 }; +unsigned int swordcolors[] = { 6, 0xC0C0C0FF, 0xFFFFFFFF, 0xFFC0C0FF, 0xC0C0FFFF, 0x808080FF, 0x202020FF }; +unsigned int eyecolors[] = { 4, 0x00C000FF, 0x0000C0FF, 0xC00000FF, 0xC0C000FF, 0x804010FF, 0x00C000FF }; + +EX void initcs(charstyle &cs) { + cs.charid = 0; + cs.skincolor = 0xD0D0D0FF; + cs.haircolor = 0x686868FF; + cs.dresscolor = 0xC00000FF; + cs.swordcolor = 0xD0D0D0FF; + cs.dresscolor2= 0x8080FFC0; + cs.uicolor = 0xFF0000FF; + cs.eyecolor = 0x603000FF; + cs.lefthanded = false; + } + +EX void savecolortable(colortable& ct, string name) { + for(int i=0; icolor, "lpcolor-" + lp->lpname); + addsaver(lp->multiplier, "lpwidth-" + lp->lpname); + } + + // special graphics + + addsaver(pconf.ballangle, "ball angle", 20); + addsaver(vid.yshift, "Y shift", 0); + addsaver(vid.use_wall_radar, "wallradar", true); + addsaver(vid.fixed_facing, "fixed facing", 0); + addsaver(vid.fixed_facing_dir, "fixed facing dir", 90); + addsaver(vid.fixed_yz, "fixed YZ", true); + addsaver(pconf.camera_angle, "camera angle", 0); + addsaver(pconf.ballproj, "ballproj", 1); + addsaver(vid.monmode, "monster display mode", DEFAULT_MONMODE); + addsaver(vid.wallmode, "wall display mode", DEFAULT_WALLMODE); + addsaver(vid.highlightmode, "highlightmode"); + + addsaver(vid.depth, "3D depth", 1); + addsaver(vid.camera, "3D camera level", 1); + addsaver(vid.wall_height, "3D wall height", .3); + addsaver(vid.rock_wall_ratio, "3D rock-wall ratio", .9); + addsaver(vid.human_wall_ratio, "3D human-wall ratio", .7); + addsaver(vid.lake_top, "3D lake top", .25); + addsaver(vid.lake_bottom, "3D lake bottom", .9); + addsaver(vid.tc_depth, "3D TC depth", 1); + addsaver(vid.tc_camera, "3D TC camera", 2); + addsaver(vid.tc_alpha, "3D TC alpha", 3); + addsaver(vid.highdetail, "3D highdetail", 8); + addsaver(vid.middetail, "3D middetail", 8); + addsaver(vid.gp_autoscale_heights, "3D Goldberg autoscaling", true); + addsaver(vid.always3, "3D always", false); + + addsaver(vid.eye, "eyelevel", 0); + addsaver(vid.auto_eye, "auto-eyelevel", false); + + addsaver(memory_saving_mode, "memory_saving_mode", (ISMOBILE || ISPANDORA || ISWEB) ? 1 : 0); + addsaver(reserve_limit, "memory_reserve", 128); + addsaver(show_memory_warning, "show_memory_warning"); + + auto& rconf = vid.rug_config; + addsaverenum(rconf.model, "rug-projection", mdEquidistant); + addsaver(rconf.scale, "rug-projection-scale", 1); + addsaver(rconf.alpha, "rug-projection-alpha", 1); + addsaver(rconf.clip_min, "rug-projection-clip-min", -100); + addsaver(rconf.clip_max, "rug-projection-clip-max", +10); + addsaver(rconf.stretch, "rug-projection-stretch", 1); + addsaver(rconf.halfplane_scale, "rug-projection-halfplane scale", 1); + addsaver(rconf.collignon_parameter, "rug-collignon-parameter", 1); + addsaver(rconf.collignon_reflected, "rug-collignon-reflect", false); + addsaver(rconf.euclid_to_sphere, "rug-euclid to sphere projection", 1.5); + addsaver(rconf.twopoint_param, "rug-twopoint parameter", 1); + addsaver(rconf.fisheye_param, "rug-fisheye parameter", 1); + addsaver(rconf.model_transition, "rug-model transition", 1); + addsaver(rug::renderonce, "rug-renderonce"); + addsaver(rug::rendernogl, "rug-rendernogl"); + addsaver(rug::texturesize, "rug-texturesize"); +#if CAP_RUG + addsaver(rug::model_distance, "rug-model-distance"); +#endif + + addsaverenum(pmodel, "used model", mdDisk); + addsaver(polygonal::SI, "polygon sides"); + addsaver(polygonal::STAR, "polygon star factor"); + addsaver(polygonal::deg, "polygonal degree"); + addsaver(history::autobandhistory, "include history"); // check! + addsaver(history::lvspeed, "lineview speed"); + addsaver(history::extra_line_steps, "lineview extension"); + + addsaver(polygonal::maxcoef, "polynomial degree"); + for(int i=0; ireset(); +#endif + } + +EX bool inSpecialMode() { + return chaosmode || !BITRUNCATED || peace::on || + #if CAP_TOUR + tour::on || + #endif + yendor::on || tactic::on || randomPatternsMode || + geometry != gNormal || pmodel != mdDisk || pconf.alpha != 1 || pconf.scale != 1 || + rug::rugged || vid.monmode != DEFAULT_MONMODE || + vid.wallmode != DEFAULT_WALLMODE; + } + +EX bool have_current_settings() { + int modecount = 0; + if(inv::on) modecount++; + if(shmup::on) modecount += 10; +#if CAP_TOUR + if(tour::on) modecount += 10; +#endif + if(chaosmode) modecount += 10; + if(!BITRUNCATED) modecount += 10; + if(peace::on) modecount += 10; + if(yendor::on) modecount += 10; + if(tactic::on) modecount += 10; + if(randomPatternsMode) modecount += 10; + if(geometry != gNormal) modecount += 10; + + if(modecount > 1) + return true; + + return false; + } + +EX bool have_current_graph_settings() { + if(pconf.xposition || pconf.yposition || pconf.alpha != 1 || pconf.scale != 1) + return true; + if(pmodel != mdDisk || vid.monmode != DEFAULT_MONMODE || vid.wallmode != DEFAULT_WALLMODE) + return true; + if(firstland != laIce || multi::players != 1 || rug::rugged) + return true; + + return false; + } + +EX void reset_graph_settings() { + pmodel = mdDisk; pconf.alpha = 1; pconf.scale = 1; + pconf.xposition = pconf.yposition = 0; + #if CAP_RUG + if(rug::rugged) rug::close(); + #endif + + vid.monmode = DEFAULT_MONMODE; + vid.wallmode = DEFAULT_WALLMODE; + } + +EX void resetModes(char leave IS('c')) { + while(game_active || gamestack::pushed()) { + if(game_active) stop_game(); + if(gamestack::pushed()) gamestack::pop(); + } + if(shmup::on != (leave == rg::shmup)) stop_game_and_switch_mode(rg::shmup); + if(inv::on != (leave == rg::inv)) stop_game_and_switch_mode(rg::inv); + if(chaosmode != (leave == rg::chaos)) stop_game_and_switch_mode(rg::chaos); + + if(peace::on != (leave == rg::peace)) stop_game_and_switch_mode(rg::peace); +#if CAP_TOUR + if(tour::on != (leave == rg::tour)) stop_game_and_switch_mode(rg::tour); +#endif + if(yendor::on != (leave == rg::yendor)) stop_game_and_switch_mode(rg::yendor); + if(tactic::on != (leave == rg::tactic)) stop_game_and_switch_mode(rg::tactic); + if(randomPatternsMode != (leave == rg::randpattern)) stop_game_and_switch_mode(rg::randpattern); + if(multi::players != 1) { + stop_game_and_switch_mode(); multi::players = 1; + } + if(firstland != laIce || specialland != laIce) { + stop_game(); + firstland = laIce; specialland = laIce; stop_game_and_switch_mode(); + } + + set_geometry(gNormal); + set_variation(leave == rg::heptagons ? eVariation::pure : eVariation::bitruncated); + + start_game(); + } + +#if CAP_CONFIG +EX void resetConfig() { + dynamicval rx(vid.xres, 0); + dynamicval ry(vid.yres, 0); + dynamicval rf(vid.fsize, 0); + dynamicval rfs(vid.full, false); + for(auto s: savers) + if(s->name.substr(0,5) != "mode-") + s->reset(); + } +#endif + +#if CAP_CONFIG +EX void saveConfig() { + DEBB(DF_INIT, ("save config\n")); + FILE *f = fopen(conffile, "wt"); + if(!f) { + addMessage(s0 + "Could not open the config file: " + conffile); + return; + } + + { + int pt_depth = 0, pt_camera = 0, pt_alpha = 0; + if(vid.tc_depth > vid.tc_camera) pt_depth++; + if(vid.tc_depth < vid.tc_camera) pt_camera++; + if(vid.tc_depth > vid.tc_alpha ) pt_depth++; + if(vid.tc_depth < vid.tc_alpha ) pt_alpha ++; + if(vid.tc_alpha > vid.tc_camera) pt_alpha++; + if(vid.tc_alpha < vid.tc_camera) pt_camera++; + vid.tc_alpha = pt_alpha; + vid.tc_camera = pt_camera; + vid.tc_depth = pt_depth; + } + + for(auto s: savers) if(s->dosave()) + fprintf(f, "%s=%s\n", s->name.c_str(), s->save().c_str()); + + fclose(f); +#if !ISMOBILE + addMessage(s0 + "Configuration saved to: " + conffile); +#else + addMessage(s0 + "Configuration saved"); +#endif + } + +void readf(FILE *f, ld& x) { + double fl = x; + hr::ignore(fscanf(f, "%lf", &fl)); + x = fl; + } + +map > allconfigs; + +EX void parseline(const string& str) { + if(str[0] == '#') return; + for(int i=0; i vid.yres * 1.4 ? sm::SIDE : sm::MAYDARK; + gamescreen(0); + dialog::init(XLAT("extra graphical effects")); + + dialog::addBoolItem_action(XLAT("particles on attack"), (vid.particles), 'p'); + dialog::addBoolItem_action(XLAT("floating bubbles: special"), vid.bubbles_special, 's'); + dialog::addBoolItem_action(XLAT("floating bubbles: treasure thresholds"), vid.bubbles_threshold, 't'); + dialog::addBoolItem_action(XLAT("floating bubbles: all treasures"), vid.bubbles_all, 'a'); + dialog::addBoolItem_action(XLAT("background particle effects"), (vid.backeffects), 'b'); + + dialog::addBreak(50); + dialog::addBack(); + dialog::display(); + } + +EX void showGraphConfig() { + cmode = vid.xres > vid.yres * 1.4 ? sm::SIDE : sm::MAYDARK; + gamescreen(0); + + dialog::init(XLAT("graphics configuration")); + + #if CAP_GLORNOT + dialog::addBoolItem(XLAT("openGL mode"), vid.usingGL, 'o'); + #endif + + if(!vid.usingGL) + dialog::addBoolItem(XLAT("anti-aliasing"), vid.antialias & AA_NOGL, 'O'); + + if(vid.usingGL) + dialog::addSelItem(XLAT("anti-aliasing"), + (vid.antialias & AA_POLY) ? "polygons" : + (vid.antialias & AA_LINES) ? "lines" : + (vid.antialias & AA_MULTI) ? "multisampling" : + "NO", 'O'); + + dialog::addSelItem(XLAT("vector graphics modes"), XLAT("width") + " " + fts(vid.linewidth), 'w'); + + dialog::addSelItem(XLAT("line quality"), its(vid.linequality), 'L'); + + #if CAP_FRAMELIMIT + dialog::addSelItem(XLAT("framerate limit"), its(vid.framelimit), 'l'); + if(getcstat == 'l') + mouseovers = XLAT("Reduce the framerate limit to conserve CPU energy"); + #endif + +#if !ISIOS && !ISWEB + dialog::addBoolItem(XLAT("fullscreen mode"), (vid.full), 'f'); +#endif + + dialog::addSelItem(XLAT("scrolling speed"), fts(vid.sspeed), 'a'); + + dialog::addSelItem(XLAT("camera movement speed"), fts(camera_speed), 'c'); + dialog::add_action([] { + dialog::editNumber(camera_speed, -10, 10, 0.1, 1, XLAT("camera movement speed"), + "This affects:\n\nin 2D: scrolling with arrow keys and Wheel Up\n\nin 3D: camera movement with Home/End." + ); + }); + dialog::addSelItem(XLAT("camera rotation speed"), fts(camera_rot_speed), 'r'); + dialog::add_action([] { + dialog::editNumber(camera_rot_speed, -10, 10, 0.1, 1, XLAT("camera rotation speed"), + "This affects view rotation with Page Up/Down, and in 3D, camera rotation with arrow keys or mouse." + ); + }); + + dialog::addSelItem(XLAT("movement animation speed"), fts(vid.mspeed), 'm'); + + dialog::addItem(XLAT("extra graphical effects"), 'u'); + + dialog::addBreak(50); + dialog::addBack(); + dialog::display(); + + keyhandler = [] (int sym, int uni) { + dialog::handleNavigation(sym, uni); + + if(uni == 'O') uni = 'o', shiftmul = -1; + + char xuni = uni | 96; + + if((uni >= 32 && uni < 64) || uni == 'L' || uni == 'C') xuni = uni; + + if(xuni == 'u') pushScreen(showSpecialEffects); + + else if(xuni == 'a') dialog::editNumber(vid.sspeed, -5, 5, 1, 0, + XLAT("scrolling speed"), + XLAT("+5 = center instantly, -5 = do not center the map") + + "\n\n" + + XLAT("press Space or Home to center on the PC")); + + else if(xuni == 'm') dialog::editNumber(vid.mspeed, -5, 5, 1, 0, + XLAT("movement animation speed"), + XLAT("+5 = move instantly")); + + else if(xuni == 'f') switchFullscreen(); + + #if CAP_GLORNOT + else if(xuni == 'o' && shiftmul > 0) switchGL(); + #endif + + else if(xuni == 'o' && shiftmul < 0) { + if(!vid.usingGL) + vid.antialias ^= AA_NOGL | AA_FONT; + else if(vid.antialias & AA_MULTI) + vid.antialias ^= AA_MULTI; + else if(vid.antialias & AA_POLY) + vid.antialias ^= AA_POLY | AA_LINES | AA_MULTI; + else if(vid.antialias & AA_LINES) + vid.antialias |= AA_POLY; + else + vid.antialias |= AA_LINES; +#if CAP_SDL + setvideomode(); +#endif + } + + // if(xuni == 'b') vid.antialias ^= AA_LINEWIDTH; + + else if(xuni == 'w') { + dialog::editNumber(vid.linewidth, 0, 10, 0.1, 1, XLAT("line width"), + vid.usingGL ? "" : XLAT("Line width setting is only taken into account in OpenGL.")); + dialog::extra_options = [] () { + dialog::addBoolItem("finer lines at the boundary", vid.antialias & AA_LINEWIDTH, 'O'); + dialog::add_action([] () { + vid.antialias ^= AA_LINEWIDTH; + }); + + dialog::addBoolItem("perfect width", perfect_linewidth == 2, 'P'); + if(perfect_linewidth == 1) + dialog::lastItem().value = XLAT("shots only"); + dialog::add_action([] { perfect_linewidth = (1 + perfect_linewidth) % 3; }); + + if(vid.antialias & AA_LINEWIDTH) { + dialog::addSelItem("variable width", fts(precise_width), 'M'); + dialog::add_action([] () { + popScreen(); + dialog::editNumber(precise_width, 0, 2, 0.1, 0.5, + XLAT("variable width"), XLAT("lines longer than this value will be split into shorter lines, with width computed separately for each of them.") + ); + }); + } + else dialog::addBreak(100); + + auto neon_option = [&] (string s, eNeon val, char key) { + dialog::addBoolItem(XLAT(s), neon_mode == val, key); + dialog::add_action([val] { neon_mode = (neon_mode == val) ? eNeon::none : val; }); + }; + + neon_option("neon mode", eNeon::neon, 'B'); + neon_option("no boundary mode", eNeon::no_boundary, 'C'); + neon_option("neon mode II", eNeon::neon2, 'D'); + neon_option("illustration mode", eNeon::illustration, 'E'); + dialog::addBreak(100); + dialog::addInfo(XLAT("hint: press Alt while testing modes")); + dialog::addBreak(100); + dialog::addBoolItem_action(XLAT("disable shadows"), noshadow, 'F'); + dialog::addBoolItem_action(XLAT("bright mode"), bright, 'G'); + dialog::addBoolItem_action(XLAT("colorblind simulation"), cblind, 'H'); + + dialog::addBoolItem_action(XLAT("no fill in neon mode"), neon_nofill, 'N'); + }; + } + + else if(xuni == 'L') { + dialog::editNumber(vid.linequality, -3, 5, 1, 1, XLAT("line quality"), + XLAT("Higher numbers make the curved lines smoother, but reduce the performance.")); + } + + #if CAP_FRAMELIMIT + else if(xuni == 'l') { + dialog::editNumber(vid.framelimit, 5, 300, 10, 300, XLAT("framerate limit"), ""); + dialog::bound_low(5); + } + #endif + + else if(xuni =='p') + vid.backeffects = !vid.backeffects; + + else if(doexiton(sym, uni)) popScreen(); + }; + } + +EX void switchFullscreen() { + vid.full = !vid.full; +#if ISANDROID + addMessage(XLAT("Reenter HyperRogue to apply this setting")); + ANDROID_SETTINGS +#endif +#if CAP_SDL + if(true) { + vid.xres = vid.full ? vid.xscr : 9999; + vid.yres = vid.full ? vid.yscr : 9999; + extern bool setfsize; + setfsize = true; + } + setvideomode(); +#endif + } + +EX void switchGL() { + vid.usingGL = !vid.usingGL; + if(vid.usingGL) addMessage(XLAT("openGL mode enabled")); + if(!vid.usingGL) addMessage(XLAT("openGL mode disabled")); + ANDROID_SETTINGS; +#if CAP_SDL + setvideomode(); + if(vid.usingGL) { + glhr::be_textured(); glhr::be_nontextured(); + } +#endif + } + +EX void edit_whatever(char type, int index) { + if(type == 'f') { + dialog::editNumber(whatever[index], -10, 10, 1, 0, XLAT("whatever"), + "f:" + its(index)); + } + else { + dialog::editNumber(whateveri[index], -10, 10, 1, 0, XLAT("whatever"), + "i:" + its(index)); + } + dialog::extra_options = [type, index] { + dialog::addItem(XLAT("integer"), 'X'); + dialog::add_action( [index] { popScreen(); edit_whatever('i', index); }); + dialog::addItem(XLAT("float"), 'Y'); + dialog::add_action( [index] { popScreen(); edit_whatever('f', index); }); + for(int x=0; x<8; x++) { + dialog::addSelItem(its(x), type == 'i' ? its(whateveri[x]) : fts(whatever[x]), 'A' + x); + dialog::add_action([type,x] { popScreen(); edit_whatever(type, x); }); + } + }; + } + +EX void configureOther() { + gamescreen(3); + + dialog::init(XLAT("other settings")); + +#if ISSTEAM + dialog::addBoolItem(XLAT("send scores to Steam leaderboards"), (vid.steamscore&1), 'x'); + dialog::add_action([] {vid.steamscore = vid.steamscore^1; }); +#endif + + dialog::addBoolItem_action(XLAT("skip the start menu"), vid.skipstart, 'm'); + + dialog::addItem(XLAT("memory configuration"), 'y'); + dialog::add_action_push(show_memory_menu); + + // dialog::addBoolItem_action(XLAT("forget faraway cells"), memory_saving_mode, 'y'); + +#if CAP_AUDIO + menuitem_music_volume(); + menuitem_sfx_volume(); +#endif + + menuitem_sightrange('r'); + +#ifdef WHATEVER + dialog::addSelItem(XLAT("whatever"), fts(whatever[0]), 'j'); + dialog::add_action([] { edit_whatever('f', 0); }); +#endif + + dialog::addBreak(50); + dialog::addBack(); + + dialog::display(); + } + +EX void configureInterface() { + gamescreen(3); + dialog::init(XLAT("interface")); + +#if CAP_TRANS + dialog::addSelItem(XLAT("language"), XLAT("EN"), 'l'); + dialog::add_action_push(selectLanguageScreen); +#endif + + dialog::addSelItem(XLAT("player character"), numplayers() > 1 ? "" : csname(vid.cs), 'g'); + dialog::add_action_push(showCustomizeChar); + if(getcstat == 'g') mouseovers = XLAT("Affects looks and grammar"); + + dialog::addSelItem(XLAT("message flash time"), its(vid.flashtime), 't'); + dialog::add_action([] { + dialog::editNumber(vid.flashtime, 0, 64, 1, 8, XLAT("message flash time"), + XLAT("How long should the messages stay on the screen.")); + dialog::bound_low(0); + }); + + dialog::addSelItem(XLAT("limit messages shown"), its(vid.msglimit), 'z'); + dialog::add_action([] { + dialog::editNumber(vid.msglimit, 0, 64, 1, 5, XLAT("limit messages shown"), + XLAT("Maximum number of messages on screen.")); + dialog::bound_low(0); + }); + + const char* msgstyles[3] = {"centered", "left-aligned", "line-broken"}; + + dialog::addSelItem(XLAT("message style"), XLAT(msgstyles[vid.msgleft]), 'a'); + dialog::add_action([] { + vid.msgleft = (1+vid.msgleft) % 3; + }); + + dialog::addSelItem(XLAT("font scale"), its(fontscale), 'b'); + dialog::add_action([] { + dialog::editNumber(fontscale, 25, 400, 10, 100, XLAT("font scale"), ""); + const int minfontscale = ISMOBILE ? 50 : 25; + dialog::reaction = [] () { setfsize = true; do_setfsize(); }; + dialog::bound_low(minfontscale); + }); + + const char *glyphsortnames[6] = { + "first on top", "first on bottom", + "last on top", "last on bottom", + "by land", "by number" + }; + dialog::addSelItem(XLAT("inventory/kill sorting"), XLAT(glyphsortnames[glyphsortorder]), 'k'); + dialog::add_action([] { + glyphsortorder = eGlyphsortorder((glyphsortorder+6+(shiftmul>0?1:-1)) % gsoMAX); + }); + + const char *glyphmodenames[3] = {"letters", "auto", "images"}; + dialog::addSelItem(XLAT("inventory/kill mode"), XLAT(glyphmodenames[vid.graphglyph]), 'd'); + dialog::add_action([] { + vid.graphglyph = (1+vid.graphglyph)%3; + }); + + dialog::addSelItem(XLAT("draw crosshair"), crosshair_size > 0 ? fts(crosshair_size) : ONOFF(false), 'x'); + dialog::add_action([] () { + dialog::editNumber(crosshair_size, 0, 100, 1, 10, XLAT("crosshair size"), XLAT( + "Display a targetting reticle in the center of the screen. Might be useful when exploring 3D modes, " + "as it precisely shows the direction we are going. However, the option is available in all modes." + )); + dialog::bound_low(0); + dialog::extra_options = [] { + dialog::addColorItem(XLAT("crosshair color"), crosshair_color, 'X'); + dialog::add_action([] { dialog::openColorDialog(crosshair_color); }); + }; + }); + + dialog::addBreak(50); + dialog::addBack(); + + dialog::display(); + } + +#if CAP_SDLJOY +EX void showJoyConfig() { + gamescreen(4); + + dialog::init(XLAT("joystick configuration")); + + dialog::addSelItem(XLAT("first joystick position (movement)"), its(joyx)+","+its(joyy), 0); + dialog::addSelItem(XLAT("second joystick position (panning)"), its(panjoyx)+","+its(panjoyy), 0); + + dialog::addSelItem(XLAT("joystick mode"), XLAT(autojoy ? "automatic" : "manual"), 'p'); + if(getcstat == 'p') { + if(autojoy) + mouseovers = XLAT("joystick mode: automatic (release the joystick to move)"); + if(!autojoy) + mouseovers = XLAT("joystick mode: manual (press a button to move)"); + } + + dialog::addSelItem(XLAT("first joystick: movement threshold"), its(vid.joyvalue), 'a'); + dialog::addSelItem(XLAT("first joystick: execute movement threshold"), its(vid.joyvalue2), 'b'); + dialog::addSelItem(XLAT("second joystick: pan threshold"), its(vid.joypanthreshold), 'c'); + dialog::addSelItem(XLAT("second joystick: panning speed"), fts(vid.joypanspeed * 1000), 'd'); + dialog::addSelItem(XLAT("smoothen"), its(vid.joysmooth) + " ms", 'e'); + + dialog::addBreak(50); + dialog::addBack(); + dialog::display(); + + keyhandler = [] (int sym, int uni) { + dialog::handleNavigation(sym, uni); + if(uni == 'p') autojoy = !autojoy; + else if(uni == 'a') { + dialog::editNumber(vid.joyvalue, 0, 32768, 100, 4800, XLAT("first joystick: movement threshold"), ""); + dialog::bound_low(0); + } + else if(uni == 'b') { + dialog::editNumber(vid.joyvalue2, 0, 32768, 100, 5600, XLAT("first joystick: execute movement threshold"), ""); + dialog::bound_low(0); + } + else if(uni == 'c') { + dialog::editNumber(vid.joypanthreshold, 0, 32768, 100, 2500, XLAT("second joystick: pan threshold"), ""); + dialog::bound_low(0); + } + else if(uni == 'd') + dialog::editNumber(vid.joypanspeed, 0, 1e-2, 1e-5, 1e-4, XLAT("second joystick: panning speed"), ""); + else if(uni == 'e') + dialog::editNumber(vid.joypanspeed, 0, 2000, 20, 200, XLAT("smoothen"), "large values help if the joystick is imprecise"); + + else if(doexiton(sym, uni)) popScreen(); + }; + } +#endif + +EX void projectionDialog() { + vid.tc_alpha = ticks; + dialog::editNumber(vpconf.alpha, -5, 5, .1, 1, + XLAT("projection"), + XLAT("HyperRogue uses the Minkowski hyperboloid model internally. " + "Klein and Poincaré models can be obtained by perspective, " + "and the Gans model is obtained by orthogonal projection. " +// "This parameter specifies the distance from the hyperboloid center " +// "to the eye. " + "See also the conformal mode (in the special modes menu) " + "for more models.")); + dialog::extra_options = [] () { + dialog::addBreak(100); + if(GDIM == 2) dialog::addHelp(XLAT( + "If we are viewing an equidistant g absolute units below a plane, " + "from a point c absolute units above the plane, this corresponds " + "to viewing a Minkowski hyperboloid from a point " + "tanh(g)/tanh(c) units below the center. This in turn corresponds to " + "the Poincaré model for g=c, and Klein-Beltrami model for g=0.")); + dialog::addSelItem(sphere ? "stereographic" : "Poincaré model", "1", 'P'); + dialog::add_action([] () { *dialog::ne.editwhat = 1; vpconf.scale = 1; dialog::ne.s = "1"; }); + dialog::addSelItem(sphere ? "gnomonic" : "Klein model", "0", 'K'); + dialog::add_action([] () { *dialog::ne.editwhat = 0; vpconf.scale = 1; dialog::ne.s = "0"; }); + if(hyperbolic) { + dialog::addSelItem("inverted Poincaré model", "-1", 'I'); + dialog::add_action([] () { *dialog::ne.editwhat = -1; vpconf.scale = 1; dialog::ne.s = "-1"; }); + } + dialog::addItem(sphere ? "orthographic" : "Gans model", 'O'); + dialog::add_action([] () { vpconf.alpha = vpconf.scale = 999; dialog::ne.s = dialog::disp(vpconf.alpha); }); + dialog::addItem(sphere ? "towards orthographic" : "towards Gans model", 'T'); + dialog::add_action([] () { double d = 1.1; vpconf.alpha *= d; vpconf.scale *= d; dialog::ne.s = dialog::disp(vpconf.alpha); }); + }; + } + +EX void explain_detail() { + dialog::addHelp(XLAT( + "Objects at distance less than %1 absolute units " + "from the center will be displayed with high " + "detail, and at distance at least %2 with low detail.", + fts(vid.highdetail), fts(vid.middetail) + )); + } + +EX ld max_fov_angle() { + if(panini_alpha >= 1 || panini_alpha <= -1) return 360; + return acos(-panini_alpha) * 2 / degree; + } + +EX void add_edit_fov(char key IS('f'), bool pop IS(false)) { + + string sfov = fts(vid.fov) + "°"; + if(panini_alpha) { + sfov += " / " + fts(max_fov_angle()) + "°"; + } + dialog::addSelItem(XLAT("field of view"), sfov, key); + dialog::add_action([=] { + if(pop) popScreen(); + dialog::editNumber(vid.fov, 1, max_fov_angle(), 1, 45, "field of view", + XLAT( + "Horizontal field of view, in angles. " + "This affects the Hypersian Rug mode (even when stereo is OFF) " + "and non-disk models.") + "\n\n" + + XLAT( + "Must be less than %1°. Panini projection can be used to get higher values.", + fts(max_fov_angle()) + ) + ); + dialog::bound_low(1e-8); + dialog::bound_up(max_fov_angle() - 0.01); + dialog::extra_options = [] { + dialog::addSelItem(XLAT("Panini projection"), fts(panini_alpha), 'P'); + dialog::add_action([] { + popScreen(); + dialog::editNumber(panini_alpha, 0, 1, 0.1, 0, "Panini parameter", + XLAT( + "The Panini projection is an alternative perspective projection " + "which allows very wide field-of-view values. HyperRogue uses " + "a quick implementation, so parameter values too close to 1 may " + "be buggy; try e.g. 0.9 instead.") + ); + dialog::reaction = ray::reset_raycaster; + dialog::extra_options = [] { + add_edit_fov('F', true); + }; + }); + }; + }); + } + +bool supported_ods() { + if(!CAP_ODS) return false; + return rug::rugged || (hyperbolic && GDIM == 3); + } + +EX void showStereo() { + cmode = sm::SIDE | sm::MAYDARK; + gamescreen(0); + dialog::init(XLAT("stereo vision config")); + + string modenames[4] = { "OFF", "anaglyph", "side-by-side", "ODS" }; + + dialog::addSelItem(XLAT("stereo mode"), XLAT(modenames[vid.stereo_mode]), 'm'); + dialog::addSelItem(XLAT("pupillary distance"), fts(vid.ipd), 'e'); + + switch(vid.stereo_mode) { + case sAnaglyph: + dialog::addSelItem(XLAT("distance between images"), fts(vid.anaglyph_eyewidth), 'd'); + break; + case sLR: + dialog::addSelItem(XLAT("distance between images"), fts(vid.lr_eyewidth), 'd'); + break; + default: + dialog::addBreak(100); + break; + } + + dialog::addSelItem(XLAT("desaturate colors"), its(vid.desaturate)+"%", 'c'); + dialog::add_action([] { + dialog::editNumber(vid.desaturate, 0, 100, 10, 0, XLAT("desaturate colors"), + XLAT("Make the game colors less saturated. This is useful in the anaglyph mode.") + ); + }); + + add_edit_fov('f'); + + dialog::addBack(); + dialog::display(); + + keyhandler = [] (int sym, int uni) { + dialog::handleNavigation(sym, uni); + + string help3 = XLAT( + "This allows you to view the world of HyperRogue in three dimensions. " + "Best used with the Hypersian Rug mode. When used in the disk model, " + "this lets you look at the Minkowski hyperboloid (which means the " + "depth of terrain features is actually reversed). It also works with non-disk models, " + "from the conformal menu." + ) + " " + XLAT( + "Currently, red-cyan anaglyph glasses and mobile VR googles are supported." + ) + "\n\n"; + + if(uni == 'm') { + vid.stereo_mode = eStereo((1 + vid.stereo_mode) % 4); + if(vid.stereo_mode == sODS && !supported_ods()) vid.stereo_mode = sOFF; + } + + else if(uni == 'e') + dialog::editNumber(vid.ipd, -10, 10, 0.01, 0, XLAT("pupillary distance"), + help3 + + XLAT("The distance between your eyes in the represented 3D object. This is given in absolute units.") + ), dialog::scaleSinh100(); + + else if(uni == 'd' && vid.stereo_mode == sAnaglyph) + dialog::editNumber(vid.anaglyph_eyewidth, -1, 1, 0.01, 0, XLAT("distance between images"), + help3 + + XLAT("The distance between your eyes. 1 is the width of the screen.")); + + else if(uni == 'd' && vid.stereo_mode == sLR) + dialog::editNumber(vid.lr_eyewidth, -1, 1, 0.01, 0, XLAT("distance between images"), + help3 + + XLAT("The distance between your eyes. 1 is the width of the screen.")); + + else if(doexiton(sym, uni)) popScreen(); + }; + } + +EX void config_camera_rotation() { + dialog::editNumber(pconf.ballangle, 0, 90, 5, 0, XLAT("camera rotation in 3D models"), + "Rotate the camera in 3D models (ball model, hyperboloid, and hemisphere). " + "Note that hyperboloid and hemisphere models are also available in the " + "Hypersian Rug surfaces menu, but they are rendered differently there -- " + "by making a flat picture first, then mapping it to a surface. " + "This makes the output better in some ways, but 3D effects are lost. " + "Hypersian Rug model also allows more camera freedom." + ); + } + +EX void add_edit_wall_quality(char c) { + dialog::addSelItem(XLAT("wall quality"), its(vid.texture_step), c); + dialog::add_action([] { + dialog::editNumber(vid.texture_step, 1, 16, 1, 1, XLAT("wall quality"), + XLAT( + "Controls the number of triangles used for wall surfaces. " + "Higher numbers reduce the performance. " + "This has a strong effect when the walls are curved indeed " + "(floors in 2D geometries, honeycombs based on horospheres, and projections other than native perspective), " + "but otherwise, usually it can be set to 1 without significant adverse effects other " + "than slightly incorrect texturing." + ) + ); + dialog::bound_low(1); + dialog::bound_up(128); + dialog::reaction = [] { + #if MAXMDIM >= 4 + if(floor_textures) { + delete floor_textures; + floor_textures = NULL; + } + #endif + }; + }); + } + +EX void edit_levellines(char c) { + if(levellines) + dialog::addSelItem(XLAT("level lines"), fts(levellines), c); + else + dialog::addBoolItem(XLAT("level lines"), false, c); + dialog::add_action([] { + dialog::editNumber(levellines, 0, 100, 0.5, 0, XLAT("level lines"), + XLAT( + "This feature superimposes level lines on the rendered screen. These lines depend on the Z coordinate. In 3D hyperbolic the Z coordinate is taken from the Klein model. " + "Level lines can be used to observe the curvature: circles correspond to positive curvature, while hyperbolas correspond to negative. See e.g. the Hypersian Rug mode.") + ); + dialog::reaction = ray::reset_raycaster; + dialog::extra_options = [] { + dialog::addBoolItem(XLAT("disable textures"), disable_texture, 'T'); + dialog::add_action([] { ray::reset_raycaster(); disable_texture = !disable_texture; }); + dialog::addItem(XLAT("disable level lines"), 'D'); + dialog::add_action([] { ray::reset_raycaster(); levellines = 0; popScreen(); }); + }; + dialog::bound_low(0); + }); + } + +EX void show3D() { + cmode = sm::SIDE | sm::MAYDARK; + gamescreen(0); + dialog::init(XLAT("3D configuration")); + +#if MAXMDIM >= 4 + if(WDIM == 2) { + dialog::addBoolItem(XLAT("use the full 3D models"), vid.always3, 'U'); + dialog::add_action(geom3::switch_always3); + } +#endif + if(vid.use_smart_range == 0 && GDIM == 2) { + dialog::addSelItem(XLAT("High detail range"), fts(vid.highdetail), 'n'); + dialog::addSelItem(XLAT("Mid detail range"), fts(vid.middetail), 'm'); + dialog::addBreak(50); + } + + if(WDIM == 2) { + dialog::addSelItem(XLAT(GDIM == 2 ? "Camera level above the plane" : "Z shift"), fts(vid.camera), 'c'); + if(GDIM == 3) + dialog::addSelItem(XLAT("Eye level"), fts(vid.eye), 'E'); + + dialog::addSelItem(XLAT("Ground level below the plane"), fts(vid.depth), 'g'); + + + if(GDIM == 2) + dialog::addSelItem(XLAT("Projection at the ground level"), fts(pconf.alpha), 'p'); + else if(!in_perspective()) + dialog::addSelItem(XLAT("Projection distance"), fts(pconf.alpha), 'p'); + + dialog::addBreak(50); + dialog::addSelItem(XLAT("Height of walls"), fts(vid.wall_height), 'w'); + + dialog::addSelItem(XLAT("Rock-III to wall ratio"), fts(vid.rock_wall_ratio), 'r'); + dialog::addSelItem(XLAT("Human to wall ratio"), fts(vid.human_wall_ratio), 'h'); + dialog::addSelItem(XLAT("Level of water surface"), fts(vid.lake_top), 'l'); + dialog::addSelItem(XLAT("Level of water bottom"), fts(vid.lake_bottom), 'k'); + if(scale_used()) + dialog::addSelItem(XLAT("Creature scale"), fts(vid.creature_scale), 'C'); + } + else { + dialog::addSelItem(XLAT("Creature scale"), fts(vid.creature_scale), 'c'); + dialog::addSelItem(XLAT("Height to width"), fts(vid.height_width), 'h'); + menuitem_sightrange('s'); + } + + dialog::addBreak(50); + dialog::addSelItem(XLAT(GDIM == 3 && WDIM == 2 ? "Y shift" : "third person perspective"), fts(vid.yshift), 'y'); + if(GDIM == 3) { + dialog::addSelItem(XLAT("mouse aiming sensitivity"), fts(mouseaim_sensitivity), 'a'); + dialog::add_action([] () { + dialog::editNumber(mouseaim_sensitivity, -1, 1, 0.002, 0.01, XLAT("mouse aiming sensitivity"), "set to 0 to disable"); + }); + } + if(true) { + dialog::addSelItem(XLAT("camera rotation"), fts(vpconf.camera_angle), 'S'); + dialog::add_action([] { + dialog::editNumber(vpconf.camera_angle, -180, 180, 5, 0, XLAT("camera rotation"), + XLAT("Rotate the camera. Can be used to obtain a first person perspective, " + "or third person perspective when combined with Y shift.") + ); + }); + } + if(GDIM == 2) { + dialog::addSelItem(XLAT("fixed facing"), vid.fixed_facing ? fts(vid.fixed_facing_dir) : XLAT("OFF"), 'f'); + dialog::add_action([] () { vid.fixed_facing = !vid.fixed_facing; + if(vid.fixed_facing) { + dialog::editNumber(vid.fixed_facing_dir, 0, 360, 15, 90, "", ""); + dialog::dialogflags |= sm::CENTER; + } + }); + } + + if((WDIM == 2 && GDIM == 3) || prod) + dialog::addBoolItem_action(XLAT("fixed Y/Z rotation"), vid.fixed_yz, 'Z'); + + if(true) { + dialog::addBreak(50); + dialog::addSelItem(XLAT("projection"), current_proj_name(), 'M'); + } + #if MAXMDIM >= 4 + if(GDIM == 3) add_edit_fov('f'); + if(GDIM == 3) { + dialog::addSelItem(XLAT("radar size"), fts(vid.radarsize), 'r'); + dialog::add_action([] () { + dialog::editNumber(vid.radarsize, 0, 360, 15, 90, "", XLAT("set to 0 to disable")); + dialog::extra_options = [] () { draw_radar(true); }; + }); + } + + if(WDIM == 3 && sphere && stretch::factor) { + dialog::addItem(XLAT("Berger sphere limit"), berger_limit); + dialog::add_action([] () { + dialog::editNumber(berger_limit, 0, 10, 1, 2, "", + XLAT("Primitive-based rendering of Berger sphere is currently very slow and low quality. " + "Here you can choose how many images to draw.") + ); + }); + } + + #if CAP_RAY + if(GDIM == 3) { + dialog::addItem(XLAT("configure raycasting"), 'A'); + dialog::add_action_push(ray::configure); + } + #endif + + edit_levellines('L'); + + if(WDIM == 3 || (GDIM == 3 && euclid)) { + dialog::addSelItem(XLAT("radar range"), fts(vid.radarrange), 'R'); + dialog::add_action([] () { + dialog::editNumber(vid.radarrange, 0, 10, 0.5, 2, "", XLAT("")); + dialog::extra_options = [] () { draw_radar(true); }; + }); + } + if(GDIM == 3) add_edit_wall_quality('W'); + #endif + + dialog::addBreak(50); + #if CAP_RUG + if(rug::rugged) { + dialog::addBoolItem_action(XLAT("3D monsters/walls on the surface"), rug::spatial_rug, 'S'); + } + #endif + if(GDIM == 2) { + dialog::addBoolItem(XLAT("configure TPP automatically"), pmodel == mdDisk && pconf.camera_angle, 'T'); + dialog::add_action(geom3::switch_tpp); + } + +#if MAXMDIM >=4 + if(WDIM == 2) { + dialog::addBoolItem(XLAT("configure FPP automatically"), GDIM == 3, 'F'); + dialog::add_action(geom3::switch_fpp); + } +#endif + + if(0); + #if CAP_RUG + else if(rug::rugged && !rug::spatial_rug) + dialog::addBreak(100); + #endif + else if(GDIM == 2 && non_spatial_model()) + dialog::addInfo(XLAT("no 3D effects available in this projection"), 0xC00000); + else if(GDIM == 2 && !spatial_graphics) + dialog::addInfo(XLAT("set 3D monsters or walls in basic config first")); + else if(geom3::invalid != "") + dialog::addInfo(XLAT("error: "+geom3::invalid), 0xC00000); + else + dialog::addInfo(XLAT("parameters set correctly")); + dialog::addBreak(50); + dialog::addItem(XLAT("stereo vision config"), 'e'); + dialog::addBack(); + dialog::display(); + + keyhandler = [] (int sym, int uni) { + using namespace geom3; + dialog::handleNavigation(sym, uni); + + if(uni == 'n' && GDIM == 2) { + dialog::editNumber(vid.highdetail, 0, 5, .5, 7, XLAT("High detail range"), ""); + dialog::extra_options = explain_detail; + dialog::reaction = [] () { + if(vid.highdetail > vid.middetail) vid.middetail = vid.highdetail; + }; + } + else if(uni == 'm' && GDIM == 2) { + dialog::editNumber(vid.middetail, 0, 5, .5, 7, XLAT("Mid detail range"), ""); + dialog::extra_options = explain_detail; + dialog::reaction = [] () { + if(vid.highdetail > vid.middetail) vid.highdetail = vid.middetail; + }; + } + else if(uni == 'c' && WDIM == 2) + vid.tc_camera = ticks, + dialog::editNumber(vid.camera, 0, 5, .1, 1, XLAT(GDIM == 2 ? "Camera level above the plane" : "Z shift"), ""), + dialog::extra_options = [] { + dialog::addHelp(GDIM == 2 ? XLAT( + "Camera is placed %1 absolute units above a plane P in a three-dimensional " + "world. Ground level is actually an equidistant surface, %2 absolute units " + "below the plane P. The plane P (as well as the ground level or any " + "other equidistant surface below it) is viewed at an angle of %3 " + "(the tangent of the angle between the point in " + "the center of your vision and a faraway location is 1/cosh(c) = %4).", + fts(vid.camera), + fts(vid.depth), + fts(atan(1/cosh(vid.camera))*2/degree), + fts(1/cosh(vid.camera))) : XLAT("Look from behind.")); + if(GDIM == 3 && pmodel == mdPerspective) dialog::extra_options = [] () { + dialog::addBoolItem_action(XLAT("reduce if walls on the way"), vid.use_wall_radar, 'R'); + }; + }; + else if(uni == 'g' && WDIM == 2) + vid.tc_depth = ticks, + dialog::editNumber(vid.depth, 0, 5, .1, 1, XLAT("Ground level below the plane"), ""), + dialog::extra_options = [] { + help = XLAT( + "Ground level is actually an equidistant surface, " + "%1 absolute units below the plane P. " + "Theoretically, this value affects the world -- " + "for example, eagles could fly %2 times faster by " + "flying above the ground level, on the plane P -- " + "but the actual game mechanics are not affected. ", fts(vid.depth), fts(cosh(vid.depth))); + if(GDIM == 2) + help += XLAT( + "(Distances reported by the vector graphics editor " + "are not about points on the ground level, but " + "about the matching points on the plane P -- " + "divide them by the factor above to get actual " + "distances.)" + ); + if(GDIM == 3 && pmodel == mdPerspective && !euclid) { + ld current_camera_level = hdist0(tC0(radar_transform)); + help += "\n\n"; + if(abs(current_camera_level) < 1e-6) + help += XLAT( + "The camera is currently exactly on the plane P. " + "The horizon is seen as a straight line." + ); + else help += XLAT( + "The camera is currently %1 units above the plane P. " + "This makes you see the floor level as in general perspective projection " + "with parameter %2.", fts(current_camera_level), fts(tan_auto(vid.depth) / tan_auto(current_camera_level))); + } + dialog::addHelp(help); + }; + else if(uni == 'E' && WDIM == 2 && GDIM == 3) + vid.tc_depth = ticks, + dialog::editNumber(vid.eye, -5, 5, .1, 0, XLAT("eye level"), ""), + dialog::dialogflags |= sm::CENTER, + dialog::extra_options = [] { + + dialog::addHelp(XLAT("In the FPP mode, the camera will be set at this altitude (before applying shifts).")); + + dialog::addBoolItem(XLAT("auto-adjust to eyes on the player model"), vid.auto_eye, 'O'); + dialog::reaction = [] { vid.auto_eye = false; }; + dialog::add_action([] () { + vid.auto_eye = !vid.auto_eye; + geom3::do_auto_eye(); + }); + }; + else if(uni == 'p' && WDIM == 2) + projectionDialog(); + else if(uni == 'w' && WDIM == 2) { + dialog::editNumber(vid.wall_height, 0, 1, .1, .3, XLAT("Height of walls"), ""); + dialog::extra_options = [] () { + dialog::addHelp(GDIM == 3 ? "" : XLAT( + "The height of walls, in absolute units. For the current values of g and c, " + "wall height of %1 absolute units corresponds to projection value of %2.", + fts(actual_wall_height()), fts(factor_to_projection(cgi.WALL)))); + dialog::addBoolItem(XLAT("auto-adjust in Goldberg grids"), vid.gp_autoscale_heights, 'O'); + dialog::add_action([] () { + vid.gp_autoscale_heights = !vid.gp_autoscale_heights; + }); + }; + } + else if(uni == 'l' && WDIM == 2) + dialog::editNumber(vid.lake_top, 0, 1, .1, .25, XLAT("Level of water surface"), ""); + else if(uni == 'k' && WDIM == 2) + dialog::editNumber(vid.lake_bottom, 0, 1, .1, .9, XLAT("Level of water bottom"), ""); + else if(uni == 'r' && WDIM == 2) + dialog::editNumber(vid.rock_wall_ratio, 0, 1, .1, .9, XLAT("Rock-III to wall ratio"), ""), + dialog::extra_options = [] { dialog::addHelp(XLAT( + "The ratio of Rock III to walls is %1, so Rock III are %2 absolute units high. " + "Length of paths on the Rock III level is %3 of the corresponding length on the " + "ground level.", + fts(vid.rock_wall_ratio), fts(vid.wall_height * vid.rock_wall_ratio), + fts(cosh(vid.depth - vid.wall_height * vid.rock_wall_ratio) / cosh(vid.depth)))); + }; + else if(uni == 'h' && WDIM == 2) + dialog::editNumber(vid.human_wall_ratio, 0, 1, .1, .7, XLAT("Human to wall ratio"), ""), + dialog::extra_options = [] { dialog::addHelp(XLAT( + "Humans are %1 " + "absolute units high. Your head travels %2 times the distance travelled by your " + "feet.", + fts(vid.wall_height * vid.human_wall_ratio), + fts(cosh(vid.depth - vid.wall_height * vid.human_wall_ratio) / cosh(vid.depth))) + ); + }; + else if(uni == 'h' && WDIM == 3) + dialog::editNumber(vid.height_width, 0, 1, .1, .7, XLAT("Height to width"), ""); + else if(uni == 'c' && WDIM == 3) + dialog::editNumber(vid.creature_scale, 0, 1, .1, .7, XLAT("Creature scale"), ""); + else if(uni == 'C' && WDIM == 2 && scale_used()) + dialog::editNumber(vid.creature_scale, 0, 1, .1, .7, XLAT("Creature scale"), ""); + + else if(uni == 'e') + pushScreen(showStereo); + + else if(uni == 'y') { + dialog::editNumber(vid.yshift, 0, 1, .1, 0, XLAT("Y shift"), + XLAT("Don't center on the player character.") + ); + if(WDIM == 3 && pmodel == mdPerspective) dialog::extra_options = [] () { + dialog::addBoolItem_action(XLAT("reduce if walls on the way"), vid.use_wall_radar, 'R'); + }; + } + else if(uni == 'b') + config_camera_rotation(); + else if(uni == 'M') + pushScreen(models::model_menu); + else if(doexiton(sym, uni)) + popScreen(); + }; + } + +EX void switchcolor(unsigned int& c, unsigned int* cs) { + dialog::openColorDialog(c, cs); + } + +double cc_footphase; +int lmousex, lmousey; + +EX void showCustomizeChar() { + + cc_footphase += hypot(mousex - lmousex, mousey - lmousey); + lmousex = mousex; lmousey = mousey; + + gamescreen(4); + dialog::init(XLAT("Customize character")); + + if(shmup::on || multi::players) multi::cpid = multi::cpid_edit % multi::players; + charstyle& cs = getcs(); + + dialog::addSelItem(XLAT("character"), csname(cs), 'g'); + dialog::addColorItem(XLAT("skin color"), cs.skincolor, 's'); + dialog::addColorItem(XLAT("eye color"), cs.eyecolor, 'e'); + dialog::addColorItem(XLAT("weapon color"), cs.swordcolor, 'w'); + dialog::addColorItem(XLAT("hair color"), cs.haircolor, 'h'); + + if(cs.charid >= 1) dialog::addColorItem(XLAT("dress color"), cs.dresscolor, 'd'); + else dialog::addBreak(100); + if(cs.charid == 3) dialog::addColorItem(XLAT("dress color II"), cs.dresscolor2, 'f'); + else dialog::addBreak(100); + + dialog::addColorItem(XLAT("movement color"), cs.uicolor, 'u'); + + if(!shmup::on && multi::players == 1) dialog::addSelItem(XLAT("save whom"), XLAT1(minf[moPrincess].name), 'p'); + + if(numplayers() > 1) dialog::addSelItem(XLAT("player"), its(multi::cpid+1), 'a'); + + dialog::addBoolItem(XLAT("left-handed"), cs.lefthanded, 'l'); + + dialog::addBreak(50); + dialog::addBack(); + dialog::display(); + + int firsty = dialog::items[0].position / 2; + int scale = firsty - 2 * vid.fsize; + + flat_model_enabler fme; + + initquickqueue(); + transmatrix V = atscreenpos(vid.xres/2, firsty, scale); + + double alpha = atan2(mousex - vid.xres/2, mousey - firsty) - M_PI/2; + V = V * spin(alpha); + drawMonsterType(moPlayer, NULL, shiftless(V), 0, cc_footphase / scale, NOCOLOR); + quickqueue(); + + keyhandler = [] (int sym, int uni) { + dialog::handleNavigation(sym, uni); + + if(shmup::on || multi::players) multi::cpid = multi::cpid_edit % multi::players; + charstyle& cs = getcs(); + bool cat = cs.charid >= 4; + if(uni == 'a') { multi::cpid_edit++; multi::cpid_edit %= 60; } + else if(uni == 'g') { + cs.charid++; + if(cs.charid == 2 && !princess::everSaved && !autocheat) cs.charid = 4; + cs.charid %= 10; + } + else if(uni == 'p') vid.samegender = !vid.samegender; + else if(uni == 's') switchcolor(cs.skincolor, cat ? haircolors : skincolors); + else if(uni == 'h') switchcolor(cs.haircolor, haircolors); + else if(uni == 'w') switchcolor(cs.swordcolor, swordcolors); + else if(uni == 'd') switchcolor(cs.dresscolor, cat ? haircolors : dresscolors); + else if(uni == 'f') switchcolor(cs.dresscolor2, dresscolors2); + else if(uni == 'u') switchcolor(cs.uicolor, eyecolors); + else if(uni == 'e') switchcolor(cs.eyecolor, eyecolors); + else if(uni == 'l') cs.lefthanded = !cs.lefthanded; + else if(doexiton(sym, uni)) popScreen(); + }; + } + +EX void refresh_canvas() { + manual_celllister cl; + cl.add(cwt.at); + + int at = 0; + while(at < isize(cl.lst)) { + cell *c2 = cl.lst[at]; + c2->landparam = patterns::generateCanvas(c2); + at++; + + forCellEx(c3, c2) cl.add(c3); + } + } + +EX void edit_color_table(colortable& ct, const reaction_t& r IS(reaction_t()), bool has_bit IS(false)) { + cmode = sm::SIDE; + gamescreen(0); + dialog::init(XLAT("colors & aura")); + + for(int i=0; iland == laMinefield) { + dialog::addItem(XLAT("minefield colors"), 'm'); + dialog::add_action_push([] { edit_color_table(minecolors); }); + } + + if(viewdists) { + dialog::addItem(XLAT("distance colors"), 'd'); + dialog::add_action_push([] () {edit_color_table(distcolors); }); + } + + #if CAP_CRYSTAL + if(cryst && cheater) { + dialog::addItem(XLAT("crystal coordinate colors"), 'C'); + dialog::add_action([] () { crystal::view_coordinates = true; pushScreen([] () { edit_color_table(crystal::coordcolors); });}); + } + #endif + + if(cwt.at->land == laTortoise) { + dialog::addBoolItem_action(XLAT("Galápagos shading"), tortoise::shading_enabled, 'T'); + } + + dialog::addInfo(XLAT("colors of some game objects can be edited by clicking them.")); + + dialog::addBreak(50); + + dialog::addSelItem(XLAT("aura brightness"), its(vid.aurastr), 'a'); + dialog::add_action([] () { dialog::editNumber(vid.aurastr, 0, 256, 10, 128, XLAT("aura brightness"), ""); dialog::bound_low(0); }); + + dialog::addSelItem(XLAT("aura smoothening factor"), its(vid.aurasmoothen), 's'); + dialog::add_action([] () { dialog::editNumber(vid.aurasmoothen, 1, 180, 1, 5, XLAT("aura smoothening factor"), ""); dialog::bound_low(1); }); + + dialog::addBreak(50); + dialog::addBack(); + dialog::display(); + + keyhandler = [] (int sym, int uni) { + if(uni == '-') { + cell *c = mouseover; + if(!c) return; + else if(c == cwt.at) { + pushScreen(showCustomizeChar); + return; + } + else if(c->monst) + dialog::openColorDialog(minf[c->monst].color); + else if(c->item) + dialog::openColorDialog(iinf[c->item].color); + else if(c->wall) + dialog::openColorDialog(winf[c->wall == waMineMine ? waMineUnknown : c->wall].color); + #if CAP_COMPLEX2 + else if(c->land == laBrownian) + dialog::openColorDialog(brownian::get_color_edit(c->landparam)); + #endif + else + dialog::openColorDialog(floorcolors[c->land]); + dialog::colorAlpha = false; + dialog::dialogflags |= sm::SIDE; + return; + } + else dialog::handleNavigation(sym, uni); + if(doexiton(sym, uni)) popScreen(); + }; + } + +#if CAP_CONFIG +EX void resetConfigMenu() { + dialog::init(XLAT("reset all configuration")); + dialog::addInfo("Are you sure?"); + dialog::addItem("yes, and delete the config file", 'd'); + dialog::addItem("yes", 'y'); + dialog::addItem("cancel", 'n'); + dialog::addItem("reset the special game modes", 'r'); + dialog::display(); + keyhandler = [] (int sym, int uni) { + dialog::handleNavigation(sym, uni); + + if(uni == 'd') { + resetConfig(); + unlink(conffile); + popScreen(); + } + else if(uni == 'y') { + printf("resetting config\n"); + resetConfig(); + printf("config reset\n"); + popScreen(); + } + else if(uni == 'r') + resetModes(); + else if(uni == 'n' || doexiton(sym, uni)) + popScreen(); + + }; + } +#endif + +#if CAP_TRANS +EX void selectLanguageScreen() { + gamescreen(4); + dialog::init("select language"); // intentionally not translated + + int v = vid.language; + dynamicval d(vid.language, -1); + + for(int i=0; i= 1) + dialog::addHelp(XLAT("add credits for your translation here")); + else + dialog::addHelp(XLAT("original language")); + + if(lang() != 0) { + string tw = ""; + string s = XLAT("TRANSLATIONWARNING"); + if(s != "" && s != "TRANSLATIONWARNING") tw += s; + s = XLAT("TRANSLATIONWARNING2"); + if(s != "" && s != "TRANSLATIONWARNING2") { if(tw != "") tw += " "; tw += s; } + if(tw != "") { + dialog::addHelp(tw); + dialog::lastItem().color = 0xFF0000; + } + } + + dialog::display(); + + keyhandler = [] (int sym, int uni) { + dialog::handleNavigation(sym, uni); + + if(uni == '0') { + vid.language = -1; + ANDROID_SETTINGS; + } + + else if(uni >= 'a' && uni < 'a'+NUMLAN) { + vid.language = uni - 'a'; + ANDROID_SETTINGS; + } + + else if(doexiton(sym, uni)) + popScreen(); + }; + } +#endif + +EX void configureMouse() { + gamescreen(1); + dialog::init(XLAT("mouse & touchscreen")); + + dialog::addBoolItem_action(XLAT("reverse pointer control"), (vid.revcontrol), 'r'); + + dialog::addBoolItem_action(XLAT("draw circle around the target"), (vid.drawmousecircle), 'd'); + + if(GDIM == 3) { + dialog::addBoolItem_action(XLAT("highlight the cell forward"), vid.axes3, 'f'); + } + +#if ISMOBILE + dialog::addBoolItem(XLAT("targetting ranged Orbs long-click only"), (vid.shifttarget&2), 'i'); +#else + dialog::addBoolItem(XLAT("targetting ranged Orbs Shift+click only"), (vid.shifttarget&1), 'i'); +#endif + dialog::add_action([] {vid.shifttarget = vid.shifttarget^3; }); + + #if !ISMOBILE + dialog::addBoolItem_action(XLAT("quick mouse"), vid.quickmouse, 'M'); + #endif + + dialog::addSelItem(XLAT("move by clicking on compass"), its(vid.mobilecompasssize), 'C'); + dialog::add_action([] { + dialog::editNumber(vid.mobilecompasssize, 0, 100, 10, 20, XLAT("compass size"), XLAT("0 to disable")); + // we need to check the moves + dialog::reaction = checkmove; + dialog::bound_low(0); + }); + + #if CAP_ORIENTATION + if(GDIM == 2) { + dialog::addSelItem(XLAT("scrolling by device rotation"), ors::choices[ors::mode], '1'); + dialog::add_action_push(ors::show); + } + #endif + + dialog::addBack(); + dialog::display(); + } + +EX void showSettings() { + gamescreen(1); + dialog::init(XLAT("settings")); + + dialog::addItem(XLAT("interface"), 'i'); + dialog::add_action_push(configureInterface); + + dialog::addItem(XLAT("general graphics"), 'g'); + dialog::add_action_push(showGraphConfig); + + dialog::addItem(XLAT("3D configuration"), '9'); + dialog::add_action_push(show3D); + + dialog::addItem(XLAT("quick options"), 'q'); + dialog::add_action_push(showGraphQuickKeys); + + dialog::addItem(XLAT("models & projections"), 'p'); + dialog::add_action_push(models::model_menu); + + dialog::addItem(XLAT("colors & aura"), 'c'); + dialog::add_action_push(show_color_dialog); + +#if CAP_SHMUP && !ISMOBILE + dialog::addSelItem(XLAT("keyboard & joysticks"), "", 'k'); + dialog::add_action(multi::configure); +#endif + + dialog::addSelItem(XLAT("mouse & touchscreen"), "", 'm'); + dialog::add_action_push(configureMouse); + + dialog::addItem(XLAT("other settings"), 'o'); + dialog::add_action_push(configureOther); + + dialog::addBreak(100); + +#if CAP_CONFIG + dialog::addItem(XLAT("save the current config"), 's'); + dialog::add_action(saveConfig); + + dialog::addItem(XLAT("reset all configuration"), 'R'); + dialog::add_action_push(resetConfigMenu); +#endif + + if(getcstat == 's') mouseovers = XLAT("Config file: %1", conffile); + + dialog::addBack(); + dialog::display(); + } + +#if CAP_COMMANDLINE + +EX int read_color_args() { + using namespace arg; + + if(argis("-back")) { + PHASEFROM(2); shift(); backcolor = arghex(); + } + else if(argis("-fillmodel")) { + PHASEFROM(2); shift(); modelcolor = arghex(); + } + else if(argis("-ring")) { + PHASEFROM(2); shift(); ringcolor = arghex(); + } + else if(argis("-ringw")) { + PHASEFROM(2); shift_arg_formula(vid.multiplier_ring); + } + else if(argis("-stdgrid")) { + PHASEFROM(2); shift(); stdgridcolor = arghex(); + } + else if(argis("-gridw")) { + PHASEFROM(2); shift_arg_formula(vid.multiplier_grid); + } + else if(argis("-period")) { + PHASEFROM(2); shift(); periodcolor = arghex(); + } + else if(argis("-crosshair")) { + PHASEFROM(2); shift(); crosshair_color = arghex(); + shift_arg_formula(crosshair_size); + } + else if(argis("-borders")) { + PHASEFROM(2); shift(); bordcolor = arghex(); + } + else if(argis("-fore")) { + PHASEFROM(2); shift(); forecolor = arghex(); + } + else if(argis("-dialog")) { + PHASEFROM(2); shift(); dialog::dialogcolor = arghex(); + } + else if(argis("-d:color")) + launch_dialog(show_color_dialog); + else return 1; + return 0; + } + +EX int read_config_args() { + using namespace arg; + + if(argis("-c")) { PHASE(1); shift(); conffile = argcs(); } +// change the configuration from the command line + else if(argis("-aa")) { PHASEFROM(2); shift(); vid.antialias = argi(); } + else if(argis("-lw")) { PHASEFROM(2); shift_arg_formula(vid.linewidth); } + else if(argis("-wm")) { PHASEFROM(2); shift(); vid.wallmode = argi(); } + else if(argis("-mm")) { PHASEFROM(2); shift(); vid.monmode = argi(); } + + else if(argis("-noshadow")) { noshadow = true; } + else if(argis("-bright")) { bright = true; } + else if(argis("-gridon")) { vid.grid = true; } + else if(argis("-gridoff")) { vid.grid = false; } + +// non-configurable options + else if(argis("-vsync_off")) { + #if CAP_SDL && CAP_GL + vsync_off = true; + if(curphase == 3) setvideomode(); + #endif + } + else if(argis("-aura")) { + PHASEFROM(2); + shift(); vid.aurastr = argi(); + shift(); vid.aurasmoothen = argi(); + } + else if(argis("-nofps")) { + PHASEFROM(2); + nofps = true; + } + else if(argis("-nohud")) { + PHASEFROM(2); + nohud = true; + } + else if(argis("-nomenu")) { + PHASEFROM(2); + nomenukey = true; + } +#if MAXMDIM >= 4 + else if(argis("-switch-fpp")) { + PHASEFROM(2); + geom3::switch_fpp(); + } +#endif + else if(argis("-switch-tpp")) { + PHASEFROM(2); + geom3::switch_tpp(); + } +#if MAXMDIM >= 4 + else if(argis("-switch-3d")) { + PHASEFROM(2); + geom3::switch_always3(); + } +#endif + else if(argis("-nohelp")) { + PHASEFROM(2); + nohelp = true; + } + else if(argis("-dont_face_pc")) { + PHASEFROM(2); + dont_face_pc = true; + } + +#if CAP_TRANS + else if(argis("-lang")) { + PHASEFROM(2); shift(); vid.language = argi(); + } +#endif + else if(argis("-vlq")) { + PHASEFROM(2); shift(); vid.linequality = argi(); + } + else if(argis("-fov")) { + PHASEFROM(2); shift_arg_formula(vid.fov); + } + else if(argis("-r")) { + PHASEFROM(2); + shift(); + int clWidth=0, clHeight=0, clFont=0; + sscanf(argcs(), "%dx%dx%d", &clWidth, &clHeight, &clFont); + if(clWidth) vid.xres = clWidth; + if(clHeight) vid.yres = clHeight; + if(clFont) vid.fsize = clFont; + } + else if(argis("-msm")) { + PHASEFROM(2); memory_saving_mode = true; + } + else if(argis("-mrsv")) { + PHASEFROM(2); shift(); reserve_limit = argi(); apply_memory_reserve(); + } + else if(argis("-yca")) { + PHASEFROM(2); + shift_arg_formula(vid.yshift); + shift_arg_formula(pconf.camera_angle); + } + else if(argis("-pside")) { + PHASEFROM(2); + permaside = true; + } + else if(argis("-xy")) { + PHASEFROM(2); + shift_arg_formula(pconf.xposition); + shift_arg_formula(pconf.yposition); + } + else if(argis("-fixdir")) { + PHASEFROM(2); + vid.fixed_facing = true; + shift_arg_formula(vid.fixed_facing_dir); + } + else if(argis("-fixdiroff")) { + PHASEFROM(2); + vid.fixed_facing = false; + } + else if(argis("-msmoff")) { + PHASEFROM(2); memory_saving_mode = false; + } + else if(argis("-levellines")) { + PHASEFROM(2); shift_arg_formula(levellines); + } + else if(argis("-level-notexture")) { + PHASEFROM(2); disable_texture = true; + } + else if(argis("-level-texture")) { + PHASEFROM(2); disable_texture = false; + } + else if(argis("-msens")) { + PHASEFROM(2); shift_arg_formula(mouseaim_sensitivity); + } + TOGGLE('o', vid.usingGL, switchGL()) + TOGGLE('f', vid.full, switchFullscreen()) + else if(argis("-noshaders")) { + PHASE(1); + glhr::noshaders = true; + } + else if(argis("-d:sight")) { + PHASEFROM(2); launch_dialog(); edit_sightrange(); + } + else if(argis("-d:char")) { + PHASEFROM(2); launch_dialog(showCustomizeChar); + } + else if(argis("-d:3")) { + PHASEFROM(2); launch_dialog(show3D); + } + else if(argis("-d:stereo")) { + PHASEFROM(2); launch_dialog(showStereo); + } + else if(argis("-d:iface")) { + PHASEFROM(2); launch_dialog(configureInterface); + } + else if(argis("-d:graph")) { + PHASEFROM(2); launch_dialog(showGraphConfig); + } + else if(argis("-tstep")) { + PHASEFROM(2); shift(); vid.texture_step = argi(); + } + else if(argis("-csc")) { + PHASEFROM(2); shift_arg_formula(vid.creature_scale); + } + else if(argis("-neon")) { + PHASEFROM(2); + shift(); neon_mode = eNeon(argi()); + } + else if(argis("-smooths")) { + PHASEFROM(2); + shift(); smooth_scrolling = argi(); + } + else if(argis("-via-shader")) { + PHASEFROM(2); + shift(); vid.consider_shader_projection = argi(); + } + else if(argis("-neonnf")) { + PHASEFROM(2); + shift(); neon_nofill = argi(); + } + else if(argis("-precw")) { + PHASEFROM(2); + shift_arg_formula(precise_width); + } + else if(argis("-char")) { + auto& cs = vid.cs; + shift(); cs.charid = argi(); + cs.lefthanded = cs.charid >= 10; + cs.charid %= 10; + } + else return 1; + return 0; + } + +// mode changes: + +EX int read_gamemode_args() { + using namespace arg; + + if(argis("-P")) { + PHASE(2); shift(); + stop_game_and_switch_mode(rg::nothing); + multi::players = argi(); + } + TOGGLE('C', chaosmode, stop_game_and_switch_mode(rg::chaos)) + TOGGLE('S', shmup::on, stop_game_and_switch_mode(rg::shmup)) + TOGGLE('H', hardcore, switchHardcore()) + TOGGLE('R', randomPatternsMode, stop_game_and_switch_mode(rg::randpattern)) + TOGGLE('i', inv::on, stop_game_and_switch_mode(rg::inv)) + + else return 1; + return 0; + } + +auto ah_config = addHook(hooks_args, 0, read_config_args) + addHook(hooks_args, 0, read_gamemode_args) + addHook(hooks_args, 0, read_color_args); +#endif + +EX map params = { + {"linewidth", vid.linewidth}, + {"patternlinewidth", linepatterns::width}, + {"scale", pconf.scale}, + {"xposition", pconf.xposition}, + {"yposition", pconf.yposition}, + {"projection", pconf.alpha}, + {"sspeed", vid.sspeed}, + {"mspeed", vid.mspeed}, + {"ballangle", pconf.ballangle}, + {"yshift", vid.yshift}, + {"cameraangle", pconf.camera_angle}, + {"eye", vid.eye}, + {"depth", vid.depth}, + {"camera", vid.camera}, + {"wall_height", vid.wall_height}, + {"highdetail", vid.highdetail}, + {"middetail", vid.middetail}, + {"rock_wall_ratio", vid.rock_wall_ratio}, + {"human_wall_ratio", vid.human_wall_ratio}, + {"lake_top", vid.lake_top}, + {"lake_bottom", vid.lake_bottom}, + #if CAP_RUG + {"rug_model_distance", rug::model_distance}, + #endif + {"star", polygonal::STAR}, + {"lvspeed", history::lvspeed}, + {"rotation", models::rotation}, + {"mori", pconf.model_orientation}, + {"mori_yz", pconf.model_orientation_yz}, + {"clipmin", pconf.clip_min}, + {"clipmax", pconf.clip_max}, + {"topz", pconf.top_z}, + {"mtrans", pconf.model_transition}, + {"hp", pconf.halfplane_scale}, + {"back", backbrightness}, + {"ipd", vid.ipd}, + {"lr", vid.lr_eyewidth}, + {"anaglyph", vid.anaglyph_eyewidth}, + {"fov", vid.fov}, + {"ets", pconf.euclid_to_sphere}, + {"stretch", pconf.stretch}, + {"twopoint", pconf.twopoint_param}, + {"fisheye", pconf.fisheye_param}, + {"bwidth", vid.binary_width}, + #if CAP_ANIMATIONS + {"aperiod", anims::period}, + {"acycle", anims::cycle_length}, + {"aparabolic", anims::parabolic_length}, + {"arugangle", anims::rug_angle}, + {"acradius", anims::circle_radius}, + {"acspins", anims::circle_spins}, + {"a", anims::a}, + {"b", anims::b}, + #endif + {"mobius", pconf.skiprope}, + {"sang", pconf.spiral_angle}, + {"spiralx", pconf.spiral_x}, + {"spiraly", pconf.spiral_y}, + #if CAP_CRYSTAL + {"cprob", crystal::compass_probability}, + #endif + #if CAP_SHOT + {"gamma", shot::gamma}, + {"fade", shot::fade}, + {"mgrid", vid.multiplier_grid}, + {"mring", vid.multiplier_ring}, + {"collignon", pconf.collignon_parameter}, + {"aitoff", pconf.aitoff_parameter}, + {"loxidromic", pconf.loximuthal_parameter}, + {"miller", pconf.miller_parameter}, + {"winkel", pconf.winkel_parameter}, + {"camspd", camera_speed}, + {"camrot", camera_rot_speed}, + {"levellines", levellines}, + #endif + }; + +} diff --git a/control.cpp b/control.cpp index 9996ece3..591eac65 100644 --- a/control.cpp +++ b/control.cpp @@ -480,11 +480,13 @@ EX void handleKeyNormal(int sym, int uni) { } #endif + #if CAP_COMPLEX2 if(DEFAULTNOR(sym)) { gmodekeys(sym, uni); if(uni == 'm' && canmove && (centerover == cwt.at ? mouseover : centerover)) mine::performMarkCommand(mouseover); } + #endif if(DEFAULTCONTROL) { if(sym == '.' || sym == 's') movepcto(-1, 1); diff --git a/crystal.cpp b/crystal.cpp index 5da8bbd7..e703a4d3 100644 --- a/crystal.cpp +++ b/crystal.cpp @@ -9,7 +9,6 @@ namespace hr { EX namespace crystal { -#if CAP_CRYSTAL #if HDR static const int MAXDIM = 7; @@ -34,6 +33,8 @@ struct ldcoord : public array { static const ldcoord ldc0 = {}; #endif +#if CAP_CRYSTAL + /** Crystal can be bitruncated either by changing variation to bitruncated. * In case of the 4D Crystal, the standard HyperRogue bitruncation becomes * confused by having both the original and new vertices of degree 8. @@ -470,7 +471,7 @@ struct hrmap_crystal : hrmap_standard { map heptagon_at; map landmemo; map landmemo4; - unordered_map> distmemo; + map> distmemo; map sgc; cell *camelot_center; ldcoord camelot_coord; diff --git a/debug.cpp b/debug.cpp index 0652ad8e..97b9feb2 100644 --- a/debug.cpp +++ b/debug.cpp @@ -721,12 +721,14 @@ int read_cheat_args() { inv::compute(); } #endif +#if CAP_COMPLEX2 else if(argis("-ambush")) { // make all ambushes use the given number of dogs // example: hyper -W Hunt -IP Shield 1 -ambush 60 PHASE(3) cheat(); shift(); ambush::fixed_size = argi(); } +#endif else if(argis("-testdistances")) { PHASE(3); shift(); test_distances(argi()); } @@ -875,6 +877,11 @@ int read_cheat_args() { cheat(); gen_wandering = false; } + else if(argis("-hroll")) { + shift(); + int i = argi(); + while(i>0) i--, hrand(10); + } else if(argis("-W")) { PHASEFROM(2); shift(); diff --git a/dialogs.cpp b/dialogs.cpp index 7b749df8..57db6dfb 100644 --- a/dialogs.cpp +++ b/dialogs.cpp @@ -64,7 +64,10 @@ EX namespace dialog { EX color_t dialogcolor = 0xC0C0C0; EX void addBack() { - addItem(XLAT("go back"), ISWEB ? SDLK_BACKSPACE : SDLK_ESCAPE); + addItem(XLAT("go back"), + (cmode & sm::NUMBER) ? SDLK_RETURN : + ISWEB ? SDLK_BACKSPACE : + SDLK_ESCAPE); } EX void addHelp() { @@ -522,21 +525,38 @@ EX namespace dialog { bool isitem(item& it) { return it.type == diItem || it.type == diBigItem; } + + EX void handle_actions(int &sym, int &uni) { + if(key_actions.count(uni)) { + key_actions[uni](); + sym = uni = 0; + return; + } + if(key_actions.count(sym)) { + key_actions[sym](); + sym = uni = 0; + return; + } + } EX void handleNavigation(int &sym, int &uni) { - if(uni == '\n' || uni == '\r' || DIRECTIONKEY == SDLK_KP5) + if(uni == '\n' || uni == '\r' || DIRECTIONKEY == SDLK_KP5) { for(int i=0; i> (colorAlpha ? ash : 0)); + displayColorButton(dcenter, vid.yres/2+vid.fsize * 6, XLAT("select this color") + " : " + format(colorAlpha ? "%08X" : "%06X", color), ' ', 8, 0, color >> (colorAlpha ? ash : 0)); if(extra_options) extra_options(); diff --git a/dpgen.cpp b/dpgen.cpp index e69356c3..5c846cbb 100644 --- a/dpgen.cpp +++ b/dpgen.cpp @@ -93,9 +93,11 @@ void launch(int seed, int elimit, int hlimit) { cl1 = cl.lst; for(cell *c: cl.lst) { c->wall = waNone, c->land = laCanvas; + #if CAP_ARCM int id = arcm::current.tilegroup[arcm::id_of(c->master)]; color_t yellows[5] = { 0x80C080, 0x80C0C0, 0x8080C0, 0xC080C0, 0xC0C080 }; c->landparam = yellows[id]; + #endif } println(hlog, "c1 size = ", isize(cl.lst)); } diff --git a/drawing.cpp b/drawing.cpp index a0bc789c..581478fa 100644 --- a/drawing.cpp +++ b/drawing.cpp @@ -293,6 +293,8 @@ EX bool two_sided_model() { if(pmodel == mdHyperboloid) return !euclid; // if(pmodel == mdHemisphere) return true; if(pmodel == mdDisk) return sphere; + if(pmodel == mdRetroLittrow) return sphere; + if(pmodel == mdRetroHammer) return sphere; if(pmodel == mdHemisphere) return true; if(pmodel == mdRotatedHyperboles) return true; if(pmodel == mdSpiral && pconf.spiral_cone < 360) return true; @@ -305,6 +307,12 @@ EX int get_side(const hyperpoint& H) { double horizon = curnorm / pconf.alpha; return (H[2] <= -horizon) ? -1 : 1; } + if(pmodel == mdRetroLittrow && sphere) { + return H[2] >= 0 ? 1 : -1; + } + if(pmodel == mdRetroHammer && sphere) { + return H[2] >= 0 ? 1 : -1; + } if(pmodel == mdRotatedHyperboles) return H[1] > 0 ? -1 : 1; if(pmodel == mdHyperboloid && hyperbolic) @@ -2111,7 +2119,7 @@ EX void sort_drawqueue() { int siz = isize(ptds); #if MINIMIZE_GL_CALLS - unordered_map>> subqueue; + map>> subqueue; for(auto& p: ptds) subqueue[(p->prio == PPR::CIRCLE || p->prio == PPR::OUTCIRCLE) ? 0 : p->outline_group()].push_back(move(p)); ptds.clear(); for(auto& p: subqueue) for(auto& r: p.second) ptds.push_back(move(r)); @@ -2156,7 +2164,7 @@ EX void reverse_side_priorities() { // on the sphere, parts on the back are drawn first EX void draw_backside() { DEBBI(DF_GRAPH, ("draw_backside")); - if(pmodel == mdHyperboloid && hyperbolic) { + if(pmodel == mdHyperboloid && hyperbolic && pconf.show_hyperboloid_flat) { dynamicval dv (pmodel, mdHyperboloidFlat); for(auto& ptd: ptds) if(!among(ptd->prio, PPR::MOBILE_ARROW, PPR::OUTCIRCLE, PPR::CIRCLE)) diff --git a/environment.cpp b/environment.cpp index 548ccca5..bd251bcd 100644 --- a/environment.cpp +++ b/environment.cpp @@ -129,10 +129,11 @@ EX void compute_graphical_distance() { } } -EX void computePathdist(eMonster param) { +EX void computePathdist(eMonster param, bool include_allies IS(true)) { for(cell *c: targets) - onpath(c, isPlayerOn(c) ? 0 : 1, hrand(c->type)); + if(include_allies || isPlayerOn(c)) + onpath(c, isPlayerOn(c) ? 0 : 1, hrand(c->type)); int qtarg = isize(targets); @@ -157,9 +158,12 @@ EX void computePathdist(eMonster param) { int i = (fd+j) % c->type; // printf("i=%d cd=%d\n", i, c->move(i)->cpdist); cell *c2 = c->move(i); + + flagtype f = P_MONSTER | P_REVDIR; + if(param == moTameBomberbird) f |= P_FLYING; if(c2 && c2->pathdist == PINFD && - passable(c2, (qb= qtarg) { if(param == moTortoise && nogoSlow(c, c2)) continue; @@ -186,9 +190,9 @@ struct pathdata { pathlock--; clear_pathdata(); } - pathdata(eMonster m) { + pathdata(eMonster m, bool include_allies IS(true)) { checklock(); - computePathdist(m); + computePathdist(m, include_allies); } pathdata(int i) { checklock(); diff --git a/euclid.cpp b/euclid.cpp index 372a3313..2fb30fd5 100644 --- a/euclid.cpp +++ b/euclid.cpp @@ -100,7 +100,7 @@ EX namespace euc { /** ? */ intmatrix inverse_axes; /** for canonicalization on tori */ - unordered_map hash; + map hash; vector seq; int index; @@ -150,11 +150,13 @@ EX namespace euc { tmatrix[i] = eumove(shifttable[i]); camelot_center = NULL; build_torus3(geometry); + #if CAP_IRR if(!valid_irr_torus()) { addMessage(XLAT("Error: period mismatch")); eu_input = irr::base_config; build_torus3(geometry); } + #endif } heptagon *getOrigin() override { @@ -168,6 +170,7 @@ EX namespace euc { auto h = tailored_alloc (S7); if(!IRREGULAR) h->c7 = newCell(S7, h); + #if CAP_IRR else { coord m0 = shifttable[0]; transmatrix dummy; @@ -180,6 +183,7 @@ EX namespace euc { break; } } + #endif h->distance = 0; h->cdata = NULL; h->alt = NULL; @@ -534,6 +538,7 @@ EX namespace euc { } EX bool valid_irr_torus() { + #if CAP_IRR if(!IRREGULAR) return true; if(eu.twisted) return false; for(int i=0; i<2; i++) { @@ -547,6 +552,7 @@ EX namespace euc { eu.canonicalize(x0, dm0, dummy, mirr); if(x0 != euzero || dm0 != eutester) return false; } + #endif return true; } diff --git a/fake.cpp b/fake.cpp index 846b5840..d60bdc00 100644 --- a/fake.cpp +++ b/fake.cpp @@ -81,7 +81,9 @@ EX namespace fake { transmatrix S1, S2; ld dist; in_underlying([c, d, &S1, &S2, &dist] { + #if CAP_ARCM dynamicval u(arcm::use_gmatrix, false); + #endif transmatrix T = currentmap->adj(c, d); S1 = rspintox(tC0(T)); transmatrix T1 = spintox(tC0(T)) * T; @@ -89,12 +91,16 @@ EX namespace fake { S2 = xpush(-dist) * T1; }); + #if CAP_ARCM if(arcm::in()) { int t = arcm::id_of(c->master); int t2 = arcm::id_of(c->move(d)->master); auto& cof = arcm::current_or_fake(); cgi.adjcheck = cof.inradius[t/2] + cof.inradius[t2/2]; } + #else + if(0) ; + #endif else if(WDIM == 2) { @@ -400,7 +406,9 @@ EX ld around; /** @brief the value of 'around' which makes the tiling Euclidean */ EX ld compute_euclidean() { + #if CAP_ARCM if(arcm::in()) return arcm::current.N * 2 / arcm::current.euclidean_angle_sum; + #endif if(WDIM == 2) return 4 / (S7-2.) + 2; if(underlying == gRhombic3) return 3; @@ -411,8 +419,10 @@ EX ld compute_euclidean() { } EX ld around_orig() { + #if CAP_ARCM if(arcm::in()) return arcm::current.N; + #endif if(WDIM == 2) return S3; if(underlying == gRhombic3) diff --git a/floorshapes.cpp b/floorshapes.cpp index 37ebf35c..39280ab5 100644 --- a/floorshapes.cpp +++ b/floorshapes.cpp @@ -423,6 +423,48 @@ void geometry_information::generate_floorshapes_for(int id, cell *c, int siid, i for(int i=0; i actual; + for(int j=0; j 1e-6 && dist < min_dist) + min_dist = dist; + } + + ld dist = min_dist * (1 - 3 / sca); + + ld area = 0; + for(int j=0; j c(currentmap, &stdmap); + // cell model; model.type = S6; generate_floorshapes_for(0, &model, 0, 0); model.type = S7; generate_floorshapes_for(1, &model, bt::in() ? 0 : 1, 0); } @@ -886,24 +930,30 @@ EX int shvid(cell *c) { } else if(GOLDBERG_INV) return gp::get_plainshape_id(c); + #if CAP_IRR else if(IRREGULAR) return irr::cellindex[c]; + #endif + #if CAP_ARCM else if(arcm::in()) { auto& ac = arcm::current; int id = arcm::id_of(c->master); if(ac.regular && id>=2 && id < 2*ac.N) id &= 1; return id; } + #endif else if(arb::in()) return arb::id_of(c->master); else if(geosupport_football() == 2) return pseudohept(c); + #if CAP_BT else if(geometry == gBinaryTiling) return c->type-6; else if(kite::in()) return kite::getshape(c->master); else if(geometry == gBinary4 || geometry == gTernary) return c->master->zebraval; + #endif else if(inforder::mixed()) { int t = c->type; static vector computed; diff --git a/game.cpp b/game.cpp index 4fb9aaee..9dd82110 100644 --- a/game.cpp +++ b/game.cpp @@ -182,7 +182,7 @@ EX bool activateRecall() { killFriendlyIvy(); movecost(cwt.at, recallCell.at, 3); - playerMoveEffects(cwt.at, recallCell.at); + playerMoveEffects(movei(cwt.at, recallCell.at, TELEPORT)); mirror::destroyAll(); sword::reset(); diff --git a/geom-exp.cpp b/geom-exp.cpp index 7bc4af78..d2c3b729 100644 --- a/geom-exp.cpp +++ b/geom-exp.cpp @@ -300,6 +300,7 @@ void set_or_configure_geometry(eGeometry g) { addMessage(XLAT("Only works with (semi-)regular tilings")); return; } + #if CAP_ARCM if(arcm::in()) { int steps, single_step; if(!arcm::current.get_step_values(steps, single_step)) { @@ -307,6 +308,7 @@ void set_or_configure_geometry(eGeometry g) { return; } } + #endif } } dual::may_split_or_do([g] { set_geometry(g); }); @@ -384,7 +386,9 @@ void ge_select_tiling() { EX string current_proj_name() { bool h = hyperbolic || sn::in(); - if(vpconf.model != mdDisk) + if(vpconf.model == mdPanini && vpconf.alpha == 1) + return XLAT("stereographic Panini"); + else if(vpconf.model != mdDisk) return models::get_model_name(vpconf.model); else if(h && vpconf.alpha == 1) return XLAT("Poincaré model"); @@ -540,8 +544,10 @@ EX void select_quotient_screen() { } else if(g == gFieldQuotient) pushScreen(showQuotientConfig); + #if CAP_CRYSTAL else if(g == gCrystal) pushScreen(crystal::show); + #endif else { dual::may_split_or_do([g] { set_geometry(g); }); start_game(); @@ -707,7 +713,9 @@ EX void showEuclideanMenu() { worldsize = euc::eu.det; if(BITRUNCATED) worldsize *= (a4 ? 2 : 3); if(GOLDBERG) worldsize *= cgi.gpdata->area; + #if CAP_IRR if(IRREGULAR) worldsize *= isize(irr::cells) / isize(irr::cells_of_heptagon); + #endif } else worldsize = denom ? nom / denom : 0; @@ -794,15 +802,19 @@ EX void showEuclideanMenu() { dialog::add_action(select_quotient); + #if CAP_ARCM if(arcm::in()) { dialog::addItem(XLAT("advanced parameters"), '4'); dialog::add_action_push(arcm::show); } + #endif + #if CAP_CRYSTAL if(cryst) { dialog::addItem(XLAT("advanced parameters"), '4'); dialog::add_action_push(crystal::show); } + #endif if(fake::available()) { dialog::addItem(XLAT("fake curvature"), '4'); diff --git a/geometry.cpp b/geometry.cpp index aa9d752d..8cf49153 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -673,12 +673,17 @@ void geometry_information::prepare_basics() { plevel = vid.plevel_factor * scalefactor; single_step = 1; if(hybri && !prod) { + #if CAP_ARCM if(hybrid::underlying == gArchimedean) arcm::current.get_step_values(psl_steps, single_step); + #else + if(0) ; + #endif else { single_step = S3 * S7 - 2 * S7 - 2 * S3; psl_steps = 2 * S7; if(BITRUNCATED) psl_steps *= S3; + if(inv) psl_steps = 2 * S3; if(single_step < 0) single_step = -single_step; } DEBB(DF_GEOM | DF_POLY, ("steps = ", psl_steps, " / ", single_step)); @@ -913,7 +918,7 @@ EX void apply_always3() { #if MAXMDIM >= 4 EX void switch_always3() { if(dual::split(switch_always3)) return; - #if CAP_GL + #if CAP_GL && CAP_RUG if(rug::rugged) rug::close(); #endif vid.always3 = !vid.always3; @@ -946,7 +951,7 @@ EX void switch_always3() { EX void switch_fpp() { #if MAXMDIM >= 4 - #if CAP_GL + #if CAP_GL && CAP_RUG if(rug::rugged) rug::close(); #endif if(dual::split(switch_fpp)) return; @@ -1026,7 +1031,9 @@ EX string cgi_string() { if(GOLDBERG_INV) V("GP", its(gp::param.first) + "," + its(gp::param.second)); if(IRREGULAR) V("IRR", its(irr::irrid)); + #if CAP_ARCM if(arcm::in()) V("ARCM", arcm::current.symbol); + #endif if(arb::in()) V("ARB", its(arb::current.order)); @@ -1034,7 +1041,10 @@ EX string cgi_string() { if(bt::in() || GDIM == 3) V("WQ", its(vid.texture_step)); - if(hybri) V("U", its(int(hybrid::underlying))); + if(hybri) { + V("U", PIU(cgi_string())); + // its(int(hybrid::underlying))); + } if(prod) V("PL", fts(vid.plevel_factor)); diff --git a/geometry2.cpp b/geometry2.cpp index 7858264b..8d7c46b7 100644 --- a/geometry2.cpp +++ b/geometry2.cpp @@ -12,14 +12,14 @@ shiftmatrix &ggmatrix(cell *c); EX void fixelliptic(transmatrix& at) { if(elliptic && at[LDIM][LDIM] < 0) { - for(int i=0; imaster->c7 && (S7 % 2 == 0) && BITRUNCATED) hexshift = cgi.hexshift + 2*M_PI/c->type; else if(cgi.hexshift && c == c->master->c7) hexshift = cgi.hexshift; + #if CAP_IRR if(IRREGULAR) { auto id = irr::cellindex[c]; auto& vs = irr::cells[id]; @@ -405,8 +406,8 @@ ld hrmap_standard::spin_angle(cell *c, int d) { auto& p = vs.jpoints[vs.neid[d]]; return -atan2(p[1], p[0]) - hexshift; } - else - return M_PI - d * 2 * M_PI / c->type - hexshift; + #endif + return M_PI - d * 2 * M_PI / c->type - hexshift; } EX transmatrix ddspin(cell *c, int d, ld bonus IS(0)) { return currentmap->spin_to(c, d, bonus); } @@ -594,6 +595,7 @@ EX bool approx_nearcorner = false; EX hyperpoint nearcorner(cell *c, int i) { if(GOLDBERG_INV) { + i = gmod(i, c->type); cellwalker cw(c, i); cw += wstep; transmatrix cwm = currentmap->adj(c, i); diff --git a/glhr.cpp b/glhr.cpp index 74686cb7..e5e2d03f 100644 --- a/glhr.cpp +++ b/glhr.cpp @@ -154,7 +154,7 @@ void display(const glmatrix& m) { printf("\n"); } -glmatrix operator * (glmatrix m1, glmatrix m2) { +EX glmatrix operator * (glmatrix m1, glmatrix m2) { glmatrix res; for(int i=0; i<4; i++) for(int j=0; j<4; j++) { @@ -694,6 +694,10 @@ template void bindbuffer(T& v) { #define PTR(attrib, q, field) \ glVertexAttribPointer(attrib, q, GL_FLOAT, GL_FALSE, sizeof(v[0]), (void*) ((char*) &v[0].field - (char*) &v[0])); +EX void bindbuffer_vertex(vector& v) { + bindbuffer(v); + } + #endif EX void vertices(const vector& v, int vshift IS(0)) { diff --git a/goldberg.cpp b/goldberg.cpp index ef86e6ac..6ed4de46 100644 --- a/goldberg.cpp +++ b/goldberg.cpp @@ -806,6 +806,7 @@ EX namespace gp { dialog::addBreak(100); + #if CAP_IRR if(irr::supports(geometry)) { dialog::addBoolItem(XLAT("irregular"), IRREGULAR, 'i'); dialog::add_action(dialog::add_confirmation([=] () { @@ -817,6 +818,7 @@ EX namespace gp { if(!IRREGULAR) irr::visual_creator(); })); } + #endif dialog::addBreak(100); int style = 0; @@ -1126,6 +1128,8 @@ EX namespace gp { } transmatrix relative_matrix(cell *c2, cell *c1, const hyperpoint& hint) override { + c1 = mapping[c1]; + c2 = mapping[c2]; return in_underlying([&] { return currentmap->relative_matrix(c2, c1, hint); }); } diff --git a/graph.cpp b/graph.cpp index 68823441..edd029db 100644 --- a/graph.cpp +++ b/graph.cpp @@ -22,6 +22,8 @@ EX bool spatial_graphics; EX bool wmspatial, wmescher, wmplain, wmblack, wmascii, wmascii3; EX bool mmspatial, mmhigh, mmmon, mmitem; +EX ld panini_alpha = 0; + EX int detaillevel = 0; EX bool first_cell_to_draw = true; @@ -1020,7 +1022,11 @@ EX void drawTerraWarrior(const shiftmatrix& V, int t, int hp, double footphase) EX void drawPlayer(eMonster m, cell *where, const shiftmatrix& V, color_t col, double footphase, bool stop IS(false)) { charstyle& cs = getcs(); + #if CAP_COMPLEX2 auto& knighted = camelot::knighted; + #else + const bool knighted = false; + #endif if(mapeditor::drawplayer && !mapeditor::drawUserShape(V, mapeditor::sgPlayer, cs.charid, cs.skincolor, where)) { @@ -1264,8 +1270,10 @@ void drawMimic(eMonster m, cell *where, const shiftmatrix& V, color_t col, doubl else if(!where || shmup::curtime >= shmup::getPlayer()->nextshot) queuepoly(VBODY * VBS, cgi.shPKnife, darkena(col, 0, 0XC0)); + #if CAP_COMPLEX2 if(camelot::knighted) queuepoly(VBODY3 * VBS, cgi.shKnightCloak, darkena(col, 1, 0xC0)); + #endif queuepoly(VHEAD1, (cs.charid&1) ? cgi.shFemaleHair : cgi.shPHead, darkena(col, 1, 0XC0)); queuepoly(VHEAD, cgi.shPFace, darkena(col, 0, 0XC0)); @@ -3070,8 +3078,22 @@ EX void drawaura() { else rad0 *= m; } - cx[r][z][0] = rad0 * c; - cx[r][z][1] = rad0 * s * pconf.stretch; + ld x = rad0 * c; + ld y = rad0 * s; + + if(pconf.camera_angle) { + ld z = rad0; + + ld cam = pconf.camera_angle * degree; + GLfloat cc = cos(cam); + GLfloat ss = sin(cam); + + tie(y, z) = make_pair(y * cc - z * ss, z * cc + y * ss); + x *= rad0 / z; + y *= rad0 / z; + } + cx[r][z][0] = x; + cx[r][z][1] = y * pconf.stretch; for(int u=0; u<3; u++) cx[r][z][u+2] = bak[u] + (aurac[rm][u] / (aurac[rm][3]+.1) - bak[u]) * cmul[z]; @@ -3410,7 +3432,11 @@ EX bool placeSidewall(cell *c, int i, int sidepar, const shiftmatrix& V, color_t #endif bool openorsafe(cell *c) { + #if CAP_COMPLEX2 return c->wall == waMineOpen || mine::marked_safe(c); + #else + return false; + #endif } #define Dark(x) darkena(x,0,0xFF) @@ -3693,7 +3719,7 @@ EX bool frustum_culling = true; void make_clipping_planes() { #if MAXMDIM >= 4 clipping_planes.clear(); - if(!frustum_culling || PIU(sphere) || experimental || vid.stereo_mode == sODS) return; + if(!frustum_culling || PIU(sphere) || experimental || vid.stereo_mode == sODS || panini_alpha) return; auto add_clipping_plane = [] (ld x1, ld y1, ld x2, ld y2) { ld z1 = 1, z2 = 1; hyperpoint sx = point3(y1 * z2 - y2 * z1, z1 * x2 - z2 * x1, x1 * y2 - x2 * y1); @@ -3788,7 +3814,9 @@ EX void gridline(const shiftmatrix& V, const hyperpoint h1, const hyperpoint h2, EX int wall_offset(cell *c) { if(hybri || WDIM == 2) return hybrid::wall_offset(c); + #if CAP_BT if(kite::in() && kite::getshape(c->master) == kite::pKite) return 10; + #endif return 0; } @@ -4455,7 +4483,6 @@ EX bool allowChangeRange() { if(tour::on) return true; #endif if(racing::on) return true; - if(sightrange_bonus >= 0) return true; if(arcm::in() || arb::in()) return true; if(WDIM == 3) return true; return false; @@ -4866,7 +4893,8 @@ EX void calcparam() { cd->xcenter += cd->scrsize * pconf.xposition; cd->ycenter += cd->scrsize * pconf.yposition; - cd->tanfov = tan(vid.fov * degree / 2); + ld fov = vid.fov * degree / 2; + cd->tanfov = sin(fov) / (cos(fov) + panini_alpha); callhooks(hooks_calcparam); reset_projection(); @@ -4966,13 +4994,22 @@ EX void gamescreen(int _darken) { return; } + auto cdc = *current_display; + auto gx = vid.xres; + auto gy = vid.yres; + if(dual::split([=] () { + *current_display = cdc; + vid.xres = gx; + vid.yres = gy; dual::in_subscreen([=] () { gamescreen(_darken); }); })) { calcparam(); return; } + calcparam(); + if((cmode & sm::MAYDARK) && !current_display->sidescreen) { _darken += 2; } diff --git a/help.cpp b/help.cpp index a4b3d820..59384a42 100644 --- a/help.cpp +++ b/help.cpp @@ -232,7 +232,7 @@ EX void buildCredits() { "Kojiguchi Kazuki, baconcow, Alan, SurelyYouJest, hotdogPi, DivisionByZero, xXxWeedGokuxXx, jpystynen, Dmitry Marakasov, Alexandre Moine, Arthur O'Dwyer, " "Triple_Agent_AAA, bluetailedgnat, Allalinor, Shitford, KittyTac, Christopher King, KosGD, TravelDemon, Bubbles, rdococ, frozenlake, MagmaMcFry, " "Snakebird Priestess, roaringdragon2, Stopping Dog, bengineer8, Sir Light IJIJ, ShadeBlade, Saplou, shnourok, Ralith, madasa, 6% remaining, Chimera245, Remik Pi, alien foxcat thing, " - "Piotr Grochowski" + "Piotr Grochowski, Ann, still-flow" ); #ifdef EXTRALICENSE help += EXTRALICENSE; @@ -928,8 +928,12 @@ EX void describeMouseover() { if(isReptile(c->wall)) out += " [" + turnstring((unsigned char) c->wparam) + "]"; + #if CAP_COMPLEX2 if(c->monst == moKnight) out += XLAT(", %1 the Knight", camelot::knight_name(c)); + #else + if(0) ; + #endif else if(c->monst) { out += ", "; out += XLAT1(minf[c->monst].name); @@ -950,10 +954,12 @@ EX void describeMouseover() { out += ", "; out += XLAT1(iinf[c->item].name); if(c->item == itBarrow) out += " (x" + its(c->landparam) + ")"; - if(c->land == laHunting) { + #if CAP_COMPLEX2 + if(c->land == laHunting) { int i = ambush::size(c, c->item); if(i) out += " (" + XLAT("ambush:") + " " + its(i) + ")"; } + #endif if(c->item == itBabyTortoise && tortoise::seek()) out += " " + tortoise::measure(tortoise::babymap[c]); if(!c->monst) set_help_to(c->item); @@ -1000,12 +1006,14 @@ EX void describeMouseover() { callhooks(hooks_mouseover, c); if(mousey < vid.fsize * 3/2 && getcstat == '-' && !instat) getcstat = SDLK_F1; + #if CAP_TOUR if(tour::on && !tour::texts) { if(tour::slides[tour::currentslide].flags & tour::NOTITLE) mouseovers = ""; else mouseovers = XLAT(tour::slides[tour::currentslide].name); } + #endif } EX void showHelp() { diff --git a/heptagon.cpp b/heptagon.cpp index 810ae44e..ca3581c1 100644 --- a/heptagon.cpp +++ b/heptagon.cpp @@ -279,6 +279,12 @@ heptagon *hrmap_standard::create_step(heptagon *h, int d) { else buildHeptagon(h, d, transition(h->s, d)); } + else if(S3 > 4 && quotient) { + /* this branch may be used for some >4-valent quotient spaces outside of standard HyperRogue */ + /* this is wrong, but we don't care in quotient */ + h->move(d) = h; + // buildHeptagon(h, d, transition(h->s, d)); + } else if(d == 1) { addSpin(h, d, h->move(0), h->c.spin(0)-1, -1); } diff --git a/hud.cpp b/hud.cpp index 802d1ded..135c951d 100644 --- a/hud.cpp +++ b/hud.cpp @@ -228,6 +228,7 @@ bool displayglyph(int cx, int cy, int buttonsize, char glyph, color_t color, int glyphphase[id] * 2; drawItemType(it, NULL, shiftless(V), icol, t, false); } + sortquickqueue(); quickqueue(); } else if(glyph == '*') diff --git a/hyper.h b/hyper.h index 992a3efd..1df8fcca 100644 --- a/hyper.h +++ b/hyper.h @@ -13,8 +13,8 @@ #define _HYPER_H_ // version numbers -#define VER "11.3ż" -#define VERNUM_HEX 0xA83B +#define VER "11.4a" +#define VERNUM_HEX 0xA841 #include "sysconfig.h" @@ -48,7 +48,6 @@ template bool among(T x, V y, U... u) { return x== using std::vector; using std::map; using std::array; -using std::unordered_map; using std::sort; using std::multimap; using std::set; @@ -242,6 +241,7 @@ struct projection_configuration { ld model_orientation, halfplane_scale, model_orientation_yz; ld collignon_parameter; ld aitoff_parameter, miller_parameter, loximuthal_parameter, winkel_parameter; + bool show_hyperboloid_flat; bool collignon_reflected; string formula; eModel basic_model; @@ -272,6 +272,7 @@ struct projection_configuration { miller_parameter = .8; loximuthal_parameter = 0; winkel_parameter = .5; + show_hyperboloid_flat = true; } }; @@ -383,16 +384,23 @@ struct videopar { extern videopar vid; +/** \brief How many dimensional is the gameplay. In the FPP mode of a 2D geometry, WDIM is 2 */ #define WDIM cginf.g.gameplay_dimension +/** \brief How many dimensional is the graphical representation. In the FPP mode of a 2D geometry, MDIM is 3 */ #define GDIM cginf.g.graphical_dimension +/** \brief How many dimensions of the matrix representation are used. It is usually 3 in 2D geometries (not FPP) and in product geometries, 4 in 3D geometries */ #define MDIM (MAXMDIM == 3 ? 3 : cginf.g.homogeneous_dimension) +/** \brief What dimension of matrices is used in loops (the 'extra' dimensions have values 0 or 1 as in Id) + * Even if MDIM==3, it may be faster to keep 4x4 matrices and perform computations using them (rather than having another condition due to the variable loop size). + * The experiments on my computer show it to be the case, but the effect is not significant, and it may be different on another computer. + */ +#define MXDIM (CAP_MDIM_FIXED ? MAXMDIM : MDIM) +/** \brief The 'homogeneous' dimension index */ #define LDIM (MDIM-1) #define cclass g.kind #define self (*this) -// #define MODFIXER (2*10090080*17) - #define BUGCOLORS 3 #define big_unlock (inv::on && !chaosmode) diff --git a/hyperpoint.cpp b/hyperpoint.cpp index 7e88d6fc..185ccca7 100644 --- a/hyperpoint.cpp +++ b/hyperpoint.cpp @@ -59,22 +59,22 @@ struct hyperpoint : array { #endif inline hyperpoint& operator *= (ld d) { - for(int i=0; i { // inner product inline friend ld operator | (hyperpoint h1, hyperpoint h2) { ld sum = 0; - for(int i=0; i eps) return false; return true; @@ -702,7 +702,7 @@ EX transmatrix parabolic13(ld u, ld v) { } EX hyperpoint parabolic10(hyperpoint h) { - if(euclid) { h[MDIM] = 1; return h; } + if(euclid) { h[LDIM] = 1; return h; } else if(MDIM == 4) return hyperpoint(sinh(h[0]), h[1]/exp(h[0]), h[2]/exp(h[0]), cosh(h[0])); else return hyperpoint(sinh(h[0]), h[1]/exp(h[0]), cosh(h[0]), 0); } @@ -768,18 +768,19 @@ EX transmatrix pushxto0(const hyperpoint& H) { /** set the i-th column of T to H */ EX void set_column(transmatrix& T, int i, const hyperpoint& H) { - for(int j=0; j 1e-3 && abs(v) > 1e-3) { + ld r2 = u*u+v*v; + ld scale = sqrt((-r2+sqrt(r2*(r2+4*u*u*v*v*(r2-2))))/(2*(r2-2))) / u / v; + if(u*v<0) scale = -scale; + H = scale * H; + } + ret = H; + ret[2] = 0; + ret[3] = 1; + + models::apply_orientation(ret[1], ret[0]); + models::apply_orientation_yz(ret[2], ret[1]); + break; + } + case mdGUARD: case mdManual: break; } @@ -1462,6 +1532,10 @@ void hrmap::draw_at(cell *at, const shiftmatrix& where) { } void hrmap_standard::draw_at(cell *at, const shiftmatrix& where) { + if(S3 > 4) { + hrmap::draw_at(at, where); + return; + } drawn_cells.clear(); drawn_cells.emplace_back(at->master, hsOrigin, where * master_relative(at, true)); for(int i=0; iitem == itNone) { + changes.ccell(c); + changes.value_keep(items[itGreenStone]); items[itGreenStone]--; if(false) { c->item = itNone; @@ -484,21 +486,23 @@ EX void gainItem(eItem it) { #define IF(x) if(g < (x) && g2 >= x && !peace::on) - IF(R60/4) - addMessage(XLAT("Collect treasure to access more different lands...")); - IF(R30) - addMessage(XLAT("You feel that you have enough treasure to access new lands!")); - IF(R30*3/2) - addMessage(XLAT("Collect more treasures, there are still more lands waiting...")); - IF(R60) - addMessage(XLAT("You feel that the stars are right, and you can access R'Lyeh!")); - IF(R30*5/2) - addMessage(XLAT("Kill monsters and collect treasures, and you may get access to Hell...")); - IF(R10 * 9) - addMessage(XLAT("To access Hell, collect %1 treasures each of 9 kinds...", its(R10))); - if(landUnlocked(laHell) && !lhu) { - addMessage(XLAT("Abandon all hope, the gates of Hell are opened!")); - addMessage(XLAT("And the Orbs of Yendor await!")); + if(in_full_game()) { + IF(R60/4) + addMessage(XLAT("Collect treasure to access more different lands...")); + IF(R30) + addMessage(XLAT("You feel that you have enough treasure to access new lands!")); + IF(R30*3/2) + addMessage(XLAT("Collect more treasures, there are still more lands waiting...")); + IF(R60) + addMessage(XLAT("You feel that the stars are right, and you can access R'Lyeh!")); + IF(R30*5/2) + addMessage(XLAT("Kill monsters and collect treasures, and you may get access to Hell...")); + IF(R10 * 9) + addMessage(XLAT("To access Hell, collect %1 treasures each of 9 kinds...", its(R10))); + if(landUnlocked(laHell) && !lhu) { + addMessage(XLAT("Abandon all hope, the gates of Hell are opened!")); + addMessage(XLAT("And the Orbs of Yendor await!")); + } } } diff --git a/landgen.cpp b/landgen.cpp index bc4f5a59..31170661 100644 --- a/landgen.cpp +++ b/landgen.cpp @@ -2430,8 +2430,10 @@ EX void giantLandSwitch(cell *c, int d, cell *from) { } break; - case laBrownian: + case laBrownian: + #if CAP_COMPLEX2 brownian::build(c, d); + #endif break; case laMirrored: @@ -2807,6 +2809,7 @@ EX void setdist(cell *c, int d, cell *from) { if(d >= BARLEV) { + #if CAP_BT if(bt::in() && WDIM == 3 && !c->land && !sn::in()) { ld z = vid.binary_width; cell *cseek = c; @@ -2816,6 +2819,7 @@ EX void setdist(cell *c, int d, cell *from) { while(z < 3.999 && step < 10) cseek = cseek->cmove(bt::updir()), z *= scale; if(cseek->master->emeraldval) setland(c, eLand(cseek->master->emeraldval)); } + #endif if(!c->land && from && (WDIM == 3 || !among(from->land, laBarrier, laElementalWall, laHauntedWall, laOceanWall)) && !quotient && !(chaosmode > 1)) { if(!hasbardir(c)) setland(c, from->land); diff --git a/language.cpp b/language.cpp index 36738b28..0cf886eb 100644 --- a/language.cpp +++ b/language.cpp @@ -62,8 +62,11 @@ struct fullnoun { }; #if !CAP_TRANS -#define NUMEXTRA 11 -const char* natchars[NUMEXTRA] = {"°","é","á", "²", "½", "Θ", "δ", "π", "ϕ", "ᵈ", "∞"}; +#if HDR +#define NUMEXTRA 12 +extern const char* natchars[NUMEXTRA]; +#endif +const char* natchars[NUMEXTRA] = {"°","é","á", "²", "½", "Θ", "δ", "π", "ϕ", "ᵈ", "∞", "⌫"}; #endif #if CAP_TRANS diff --git a/locations.cpp b/locations.cpp index 2dc5bd10..e52e3cb5 100644 --- a/locations.cpp +++ b/locations.cpp @@ -459,6 +459,7 @@ constexpr int FALL = 98; constexpr int NO_SPACE = 97; constexpr int TELEPORT = 96; constexpr int JUMP = 95; +constexpr int STAY = 94; namespace whirlwind { cell *jumpDestination(cell*); } diff --git a/mapeditor.cpp b/mapeditor.cpp index f9aab990..c8301e6b 100644 --- a/mapeditor.cpp +++ b/mapeditor.cpp @@ -1095,8 +1095,8 @@ EX namespace mapeditor { displayButton(8, vid.yres-8-fs*2, XLAT("ESC = return to the game"), SDLK_ESCAPE, 0); } - EX unordered_set affected; - EX unordered_set affected_id; + EX set affected; + EX set affected_id; EX void showMapEditor() { cmode = sm::MAP; @@ -1276,6 +1276,7 @@ EX namespace mapeditor { void list_spill(cellwalker tgt, cellwalker src, manual_celllister& cl) { spill_list.clear(); spill_list.emplace_back(tgt, src); + if(painttype == 7) return; int crad = 0, nextstepat = 0; for(int i=0; i v(vpconf.model, vpconf.model); + if(vpmodel == mdHyperboloid) vpconf.model = mdDisk; dialog::addSelItem(XLAT("projection distance"), fts(vpconf.alpha) + " (" + current_proj_name() + ")", 'p'); dialog::add_action(projectionDialog); } @@ -613,6 +618,10 @@ EX namespace models { dialog::scaleLog(); }); } + + if(vpmodel == mdHyperboloid) { + dialog::addBoolItem_action(XLAT("show flat"), pconf.show_hyperboloid_flat, 'b'); + } if(vpmodel == mdCollignon) { dialog::addSelItem(XLAT("parameter"), fts(vpconf.collignon_parameter) + (vpconf.collignon_reflected ? " (r)" : ""), 'b'); @@ -636,13 +645,17 @@ EX namespace models { }); } - if(vpmodel == mdLoximuthal) { + if(among(vpmodel, mdLoximuthal, mdRetroHammer, mdRetroCraig)) { dialog::addSelItem(XLAT("parameter"), fts(vpconf.loximuthal_parameter), 'b'); - dialog::add_action([](){ + dialog::add_action([vpmodel](){ dialog::editNumber(vpconf.loximuthal_parameter, -M_PI/2, M_PI/2, .1, 0, XLAT("parameter"), + (vpmodel == mdLoximuthal ? "This model is similar to azimuthal equidistant, but based on loxodromes (lines of constant geographic direction) rather than geodesics. " "The loximuthal projection maps (the shortest) loxodromes to straight lines of the same length, going through the starting point. " - "This setting changes the latitude of the starting point." + "This setting changes the latitude of the starting point." : + "In retroazimuthal projections, a point is drawn at such a point that the azimuth *from* that point to the chosen central point is correct. " + "For example, if you should move east, the point is drawn to the right. This parameter is the latitude of the central point.") + + string(hyperbolic ? "\n\n(In hyperbolic geometry directions are assigned according to the Lobachevsky coordinates.)" : "") ); }); } @@ -827,7 +840,7 @@ EX namespace models { PHASEFROM(2); if(pmodel == mdCollignon) shift_arg_formula(vpconf.collignon_parameter); else if(pmodel == mdMiller) shift_arg_formula(vpconf.miller_parameter); - else if(pmodel == mdLoximuthal) shift_arg_formula(vpconf.loximuthal_parameter); + else if(among(pmodel, mdLoximuthal, mdRetroCraig, mdRetroHammer)) shift_arg_formula(vpconf.loximuthal_parameter); else if(among(pmodel, mdAitoff, mdHammer, mdWinkelTripel)) shift_arg_formula(vpconf.aitoff_parameter); if(pmodel == mdWinkelTripel) shift_arg_formula(vpconf.winkel_parameter); } @@ -856,6 +869,10 @@ EX namespace models { PHASEFROM(2); shift_arg_formula(vpconf.skiprope); } + else if(argis("-palpha")) { + PHASEFROM(2); + shift_arg_formula(panini_alpha, reset_all_shaders); + } else if(argis("-zoom")) { PHASEFROM(2); shift_arg_formula(vpconf.scale); } diff --git a/monstergen.cpp b/monstergen.cpp index 116ce29e..ed0fe5f5 100644 --- a/monstergen.cpp +++ b/monstergen.cpp @@ -318,10 +318,12 @@ EX eItem wanderingTreasure(cell *c) { /** generate the wandering monsters */ EX void wandering() { + #if CAP_COMPLEX2 if(mine::in_minesweeper()) { mine::count_status(); return; } + #endif if(!canmove) return; if(!gen_wandering) return; if(racing::on) return; diff --git a/monstermove.cpp b/monstermove.cpp index 4a7564da..1092f462 100644 --- a/monstermove.cpp +++ b/monstermove.cpp @@ -108,6 +108,8 @@ EX void moveMonster(const movei& mi) { auto& cf = mi.s; auto& ct = mi.t; eMonster m = cf->monst; + changes.ccell(cf); + changes.ccell(ct); bool fri = isFriendly(cf); if(isDragon(m)) { printf("called for Dragon\n"); @@ -117,6 +119,7 @@ EX void moveMonster(const movei& mi) { // the following line is necessary because otherwise plates disappear only inside the sight range if(cellUnstable(cf) && !ignoresPlates(m)) { fallingFloorAnimation(cf); + changes.ccell(cf); cf->wall = waChasm; } moveEffect(mi, m); @@ -124,6 +127,7 @@ EX void moveMonster(const movei& mi) { (m == moShark || m == moCShark || m == moGreaterShark)) achievement_gain_once("MOATSHARK"); if(m == moTentacleGhost) { + changes.ccell(cf); cf->monst = moTentacletail; m = moGhost; } @@ -156,10 +160,11 @@ EX void moveMonster(const movei& mi) { } } - if(fri || isBug(m) || items[itOrbDiscord]) stabbingAttack(cf, ct, m); + if(fri || isBug(m) || items[itOrbDiscord]) stabbingAttack(mi, m); if(mi.d == JUMP && m == moVaulter) { cell *cm = common_neighbor(cf, ct); + changes.ccell(cm); if(cm->wall == waShrub) cm->wall = waNone; if(cm->wall == waSmallTree) cm->wall = waNone; if(cm->wall == waBigTree) cm->wall = waSmallTree; @@ -1518,7 +1523,10 @@ EX int stayvalue(eMonster m, cell *c) { } /** for an ally m at c, evaluate moving to c2 */ -EX int movevalue(eMonster m, cell *c, cell *c2, flagtype flags) { +EX int movevalue(eMonster m, cell *c, int dir, flagtype flags) { + + auto mi = movei(c, dir); + auto& c2 = mi.t; int val = 0; if(isPlayerOn(c2)) val = -3000; @@ -1537,7 +1545,6 @@ EX int movevalue(eMonster m, cell *c, cell *c2, flagtype flags) { isInactiveEnemy(c2,m) ? 1000 : -500; - else if(monstersnear(c2, m, NULL, c)) val = 50; // linked with mouse suicide! else if(passable_for(m, c2, c, 0)) { #if CAP_COMPLEX2 if(mine::marked_mine(c2) && !ignoresPlates(m)) @@ -1545,12 +1552,22 @@ EX int movevalue(eMonster m, cell *c, cell *c2, flagtype flags) { else #endif val = 4000; + + int tk = tkills(); + changes.init(true); + moveMonster(mi); + int tk2 = tkills(); + bool b = monstersnear(mi.t, m); + changes.rollback(); + if(b) val = 50; + else if(tk2 > tk) val += 1000 + 200 * (tk2 - tk); } else if(passable_for(m, c2, c, P_DEADLY)) val = -1100; else val = -1750; - if(c->monst == moGolem ) - val -= c2->cpdist; + if(c->monst == moGolem ) { + val -= c2->pathdist; + } else if(c->monst == moFriendlyGhost ) val += c2->cpdist - 40; else if(c->monst == moMouse) { @@ -1599,9 +1616,9 @@ EX int movevalue(eMonster m, cell *c, cell *c2, flagtype flags) { val -= 5; } if(c->monst == moTameBomberbird) { - int d = c2->cpdist; - if(d == 1 && c->cpdist > 1) d = 5; - if(d == 2 && c->cpdist > 2) d = 4; + int d = c2->pathdist; + if(d == 1 && c->pathdist > 1) d = 5; + if(d == 2 && c->pathdist > 2) d = 4; val -= d; } if(c->monst == moKnight && (eubinary || c2->master->alt)) { @@ -1616,11 +1633,11 @@ EX int movevalue(eMonster m, cell *c, cell *c2, flagtype flags) { EX void movegolems(flagtype flags) { if(items[itOrbEmpathy] && items[itOrbSlaying]) flags |= AF_CRUSH; - pathdata pd(moMouse); int qg = 0; for(int i=0; imonst; + eMonster m = c->monst; + pathdata pd(m, false); if(c->stuntime) continue; if(m == moGolem || m == moKnight || m == moTameBomberbird || m == moPrincess || m == moPrincessArmed || m == moMouse || m == moFriendlyGhost) { @@ -1636,8 +1653,7 @@ EX void movegolems(flagtype flags) { DEBB(DF_TURN, ("moveval")); for(int k=0; ktype; k++) if(c->move(k)) { - cell *c2 = c->move(k); - int val = movevalue(m, c, c2, flags); + int val = movevalue(m, c, k, flags); if(val > bestv) bestv = val, bdirs.clear(); if(val == bestv) bdirs.push_back(k); @@ -1646,7 +1662,7 @@ EX void movegolems(flagtype flags) { if(m == moTameBomberbird) { cell *c2 = whirlwind::jumpDestination(c); if(c2 && !c2->monst) { - int val = movevalue(m, c, c2, flags); + int val = movevalue(m, c, STRONGWIND, flags); // printf("val = %d bestv = %d\n", if(val > bestv) bestv = val, bdirs.clear(); if(val == bestv) bdirs.push_back(STRONGWIND); diff --git a/multi.cpp b/multi.cpp index 2b8d3525..e7847060 100644 --- a/multi.cpp +++ b/multi.cpp @@ -221,22 +221,15 @@ struct key_configurer { int sc; vector& shmupcmdtable; + string caption; int setwhat; - key_configurer(int sc, vector& sct) : sc(sc), shmupcmdtable(sct), setwhat(0) {} + key_configurer(int sc, vector& sct, const string& caption) : sc(sc), shmupcmdtable(sct), caption(caption), setwhat(0) { + } void operator() () { - dialog::init( - XLAT(sc == 1 ? "configure player 1" : - sc == 2 ? "configure player 2" : - sc == 3 ? "configure panning" : - sc == 4 ? "configure player 3" : - sc == 5 ? "configure player 4" : - sc == 6 ? "configure player 5" : - sc == 7 ? "configure player 6" : - sc == 8 ? "configure player 7" : "" - )); + dialog::init(caption); getcstat = ' '; @@ -302,7 +295,21 @@ struct key_configurer { } }; -EX reaction_t get_key_configurer(int sc, vector& sct) { return key_configurer(sc, sct); } +EX reaction_t get_key_configurer(int sc, vector& sct, string caption) { + return key_configurer(sc, sct, caption); + } + +EX reaction_t get_key_configurer(int sc, vector& sct) { + return key_configurer(sc, sct, XLAT(sc == 1 ? "configure player 1" : + sc == 2 ? "configure player 2" : + sc == 3 ? "configure panning" : + sc == 4 ? "configure player 3" : + sc == 5 ? "configure player 4" : + sc == 6 ? "configure player 5" : + sc == 7 ? "configure player 6" : + sc == 8 ? "configure player 7" : "" + )); + } #if CAP_SDLJOY struct joy_configurer { @@ -456,14 +463,14 @@ struct shmup_configurer { if(0) ; #if CAP_SDL - else if(uni == '1') pushScreen(key_configurer(1, cmdlist)); - else if(uni == '2') pushScreen(key_configurer(2, cmdlist)); - else if(uni == 'p') pushScreen(key_configurer(3, GDIM == 3 ? pancmds3 : pancmds)); - else if(uni == '3') pushScreen(key_configurer(4, cmdlist)); - else if(uni == '4') pushScreen(key_configurer(5, cmdlist)); - else if(uni == '5') pushScreen(key_configurer(6, cmdlist)); - else if(uni == '6') pushScreen(key_configurer(7, cmdlist)); - else if(uni == '7') pushScreen(key_configurer(8, cmdlist)); + else if(uni == '1') pushScreen(get_key_configurer(1, cmdlist)); + else if(uni == '2') pushScreen(get_key_configurer(2, cmdlist)); + else if(uni == 'p') pushScreen(get_key_configurer(3, GDIM == 3 ? pancmds3 : pancmds)); + else if(uni == '3') pushScreen(get_key_configurer(4, cmdlist)); + else if(uni == '4') pushScreen(get_key_configurer(5, cmdlist)); + else if(uni == '5') pushScreen(get_key_configurer(6, cmdlist)); + else if(uni == '6') pushScreen(get_key_configurer(7, cmdlist)); + else if(uni == '7') pushScreen(get_key_configurer(8, cmdlist)); #if CAP_SDLJOY else if(uni == 'j') pushScreen(joy_configurer(players)); #endif @@ -638,6 +645,7 @@ EX void initConfig() { multi::scs[5].uicolor = 0x00C0C0FF; multi::scs[6].uicolor = 0xC0C0C0FF; + #if CAP_CONFIG addsaver(multi::players, "mode-number of players"); addsaver(alwaysuse, "use configured keys"); // unfortunately we cannot use key names here because SDL is not yet initialized @@ -656,6 +664,7 @@ EX void initConfig() { } } for(int i=0; i<7; i++) addsaver(multi::scs[i], "player"+its(i)); + #endif } EX void handleInput(int delta) { diff --git a/multigame.cpp b/multigame.cpp index 0f4b853a..181171b0 100644 --- a/multigame.cpp +++ b/multigame.cpp @@ -295,11 +295,17 @@ EX namespace dual { } else { variation = eVariation::pure; + #if CAP_ARCM geometry = s == 0 ? gEuclidSquare : gArchimedean; + #else + geometry = gEuclidSquare; + #endif } firstland = specialland = laCrossroads4; + #if CAP_ARCM if(geometry == gArchimedean) arcm::current.parse("4,4,4,4,4"); + #endif check_cgi(); cgi.require_basics(); dgd[s].storegame(); diff --git a/mymake.cpp b/mymake.cpp index e3a72988..19c66297 100644 --- a/mymake.cpp +++ b/mymake.cpp @@ -60,6 +60,19 @@ void set_mingw64() { setvbuf(stdout, NULL, _IONBF, 0); // MinGW is quirky with output buffering } +void set_web() { + preprocessor = "/usr/lib/emscripten/em++ -E"; + compiler = "/usr/lib/emscripten/em++ -c"; + default_standard = standard = " -std=c++17"; + opts = "-DISWEB=1"; + linker = + "/usr/lib/emscripten/em++ -s USE_ZLIB=1 -s DISABLE_DEPRECATED_FIND_EVENT_TARGET_BEHAVIOR=0 -s TOTAL_MEMORY=512MB " + "-s EXTRA_EXPORTED_RUNTIME_METHODS='[\"FS\",\"ccall\"]' " + "-s EXPORTED_FUNCTIONS=\"['_main', '_use_file']\" " + "-s DISABLE_EXCEPTION_CATCHING=0 -o mhyper.html"; + libs = ""; + } + vector modules; time_t get_file_time(const string s) { @@ -123,6 +136,12 @@ int main(int argc, char **argv) { obj_dir += "/linux"; setdir += "../"; } + else if(s == "-web") { + set_web(); + modules.push_back("hyperweb"); + obj_dir += "/web"; + setdir += "../"; + } else if(s.substr(0, 2) == "-f") { opts += " " + s; obj_dir += "/"; diff --git a/nofont.cpp b/nofont.cpp index 90ebeb78..de994a34 100644 --- a/nofont.cpp +++ b/nofont.cpp @@ -127,11 +127,11 @@ unsigned char fonttable[] = { unsigned char *ftv = fonttable; -void resetTabFont() { +EX void resetTabFont() { ftv = fonttable; } -void loadCompressedChar(int &otwidth, int &otheight, unsigned char *tpix) { +EX void loadCompressedChar(int &otwidth, int &otheight, unsigned char *tpix) { if(*ftv == 255) { fprintf(stderr, "There is something wrong with the font table\n"); exit(1); diff --git a/nonisotropic.cpp b/nonisotropic.cpp index a24b87eb..4fbbb98d 100644 --- a/nonisotropic.cpp +++ b/nonisotropic.cpp @@ -236,8 +236,8 @@ EX namespace sn { struct hrmap_solnih : hrmap { hrmap *binary_map; hrmap *ternary_map; /* nih only */ - unordered_map, heptagon*> at; - unordered_map> coords; + map, heptagon*> at; + map> coords; heptagon *origin; @@ -872,8 +872,8 @@ EX namespace nilv { } struct hrmap_nil : hrmap { - unordered_map at; - unordered_map coords; + map at; + map coords; heptagon *getOrigin() override { return get_at(mvec_zero); } @@ -1231,6 +1231,8 @@ EX namespace hybrid { dynamicval gpm(pmap, this); dynamicval gag(actual_geometry, geometry); dynamicval g(geometry, underlying); + dynamicval gss(underlying_cgip->single_step, cgi.single_step); + dynamicval gsp(underlying_cgip->psl_steps, cgi.psl_steps); dynamicval gc(cgip, underlying_cgip); dynamicval gu(currentmap, underlying_map); return t(); @@ -1308,6 +1310,8 @@ EX namespace hybrid { if(!hybri) return f(); dynamicval g(geometry, underlying); dynamicval gag(actual_geometry, geometry); + dynamicval gss(underlying_cgip->single_step, cgi.single_step); + dynamicval gsp(underlying_cgip->psl_steps, cgi.psl_steps); dynamicval gc(cgip, underlying_cgip); dynamicval gpm(pmap, currentmap); dynamicval gm(currentmap, get_umap()); @@ -1345,13 +1349,17 @@ EX namespace hybrid { } EX int wall_offset(cell *c) { - if(GOLDBERG) { + if(GOLDBERG || INVERSE) { /* a bit slow... */ cell *c1 = WDIM == 2 ? c : get_where(c).first; gp::draw_li = WDIM == 2 ? gp::get_local_info(c1) : PIU(gp::get_local_info(c1)); } auto ugeometry = hybri ? hybrid::underlying : geometry; + #if CAP_ARCM int id = ugeometry == gArchimedean ? arcm::id_of(c->master) + 20 * arcm::parent_index_of(c->master) : shvid(c); + #else + int id = shvid(c); + #endif if(isize(cgi.walloffsets) <= id) cgi.walloffsets.resize(id+1, {-1, nullptr}); auto &wop = cgi.walloffsets[id]; int &wo = wop.first; @@ -2057,14 +2065,19 @@ EX namespace rots { return spin(beta) * uxpush(distance/2) * spin(-beta+alpha); } - std::unordered_map saved_matrices_ray; + std::map saved_matrices_ray; EX transmatrix ray_iadj(cell *c1, int i) { if(i == c1->type-1) return uzpush(-cgi.plevel) * spin(-2*cgi.plevel); if(i == c1->type-2) return uzpush(+cgi.plevel) * spin(+2*cgi.plevel); cell *c2 = c1->cmove(i); + #if CAP_ARCM int id1 = hybrid::underlying == gArchimedean ? arcm::id_of(c1->master) + 20 * arcm::parent_index_of(c1->master) : shvid(c1); int id2 = hybrid::underlying == gArchimedean ? arcm::id_of(c2->master) + 20 * arcm::parent_index_of(c2->master) : shvid(c2); + #else + int id1 = shvid(c1); + int id2 = shvid(c2); + #endif int j = c1->c.spin(i); int id = id1 + (id2 << 10) + (i << 20) + (j << 26); auto &M = saved_matrices_ray[id]; @@ -2084,14 +2097,19 @@ EX namespace rots { struct hrmap_rotation_space : hybrid::hrmap_hybrid { - std::unordered_map saved_matrices; + std::map saved_matrices; transmatrix adj(cell *c1, int i) override { if(i == c1->type-2) return uzpush(-cgi.plevel) * spin(-2*cgi.plevel); if(i == c1->type-1) return uzpush(+cgi.plevel) * spin(+2*cgi.plevel); cell *c2 = c1->cmove(i); + #if CAP_ARCM int id1 = hybrid::underlying == gArchimedean ? arcm::id_of(c1->master) + 20 * arcm::parent_index_of(c1->master) : shvid(c1); int id2 = hybrid::underlying == gArchimedean ? arcm::id_of(c2->master) + 20 * arcm::parent_index_of(c2->master) : shvid(c2); + #else + int id1 = shvid(c1); + int id2 = shvid(c2); + #endif int j = c1->c.spin(i); int id = id1 + (id2 << 10) + (i << 20) + (j << 26); auto &M = saved_matrices[id]; diff --git a/orbgen.cpp b/orbgen.cpp index 3d2167c2..a19b47c8 100644 --- a/orbgen.cpp +++ b/orbgen.cpp @@ -309,7 +309,7 @@ EX eOrbLandRelation getOLR(eItem it, eLand l) { return olrUseless; if(l == laPrincessQuest) - if(among(it, itOrbAether, itOrbFlash, itOrbTeleport, itOrbSummon, itOrbFreedom, itOrbFriend, itOrbPhasing)) + if(among(it, itOrbAether, itOrbFlash, itOrbTeleport, itOrbSummon, itOrbFreedom, itOrbFriend, itOrbPhasing, itOrbChaos)) return olrForbidden; if(l == laTemple) diff --git a/orbs.cpp b/orbs.cpp index 014c5a64..f81d9a33 100644 --- a/orbs.cpp +++ b/orbs.cpp @@ -88,7 +88,9 @@ EX bool reduceOrbPower(eItem it, int cap) { return true; } if(items[it] > cap && timerghost) items[it] = cap; + #if CAP_COMPLEX2 mine::auto_teleport_charges(); + #endif return false; } @@ -565,8 +567,8 @@ EX void teleportTo(cell *dest) { if(b) { killFriendlyIvy(); drainOrb(itOrbTeleport); - movecost(cwt.at, dest, 3); - playerMoveEffects(cwt.at, dest); + movecost(cwt.at, dest, 3); + playerMoveEffects(movei(cwt.at, dest, TELEPORT)); afterplayermoved(); bfs(); } @@ -578,7 +580,7 @@ EX void teleportTo(cell *dest) { killFriendlyIvy(); cell *from = cwt.at; movecost(from, dest, 1); - playerMoveEffects(cwt.at, dest); + playerMoveEffects(movei(cwt.at, dest, TELEPORT)); current_display->which_copy = unshift(ggmatrix(dest)); cwt.at = dest; cwt.spin = hrand(dest->type); flipplayer = !!(hrand(2)); drainOrb(itOrbTeleport); @@ -596,7 +598,9 @@ EX void teleportTo(cell *dest) { checkmoveO(); movecost(from, dest, 2); + #if CAP_COMPLEX2 mine::auto_teleport_charges(); + #endif } EX bool jumpTo(orbAction a, cell *dest, eItem byWhat, int bonuskill IS(0), eMonster dashmon IS(moNone)) { @@ -634,8 +638,9 @@ EX bool jumpTo(orbAction a, cell *dest, eItem byWhat, int bonuskill IS(0), eMons } sword::reset(); - stabbingAttack(c1, dest, moPlayer, bonuskill); - playerMoveEffects(c1, dest); + auto mi = movei(c1, dest, JUMP); + stabbingAttack(mi, moPlayer, bonuskill); + playerMoveEffects(mi); if(itemclass(byWhat) == IC_ORB) apply_impact(dest); @@ -654,7 +659,7 @@ EX bool jumpTo(orbAction a, cell *dest, eItem byWhat, int bonuskill IS(0), eMons mirror::destroyAll(); - if(monstersnearO(a, dest, moPlayer, NULL, c1)) { + if(monstersnearO(a, dest)) { changes.rollback(); return false; } @@ -935,7 +940,7 @@ bool gun_attack(orbAction a, cell *dest) { attackMonster(dest, AF_GUN, moNone); apply_impact(dest); - if(monstersnearO(a, cwt.at, moPlayer, NULL, cwt.at)) { + if(monstersnearO(a, cwt.at)) { changes.rollback(); return false; } @@ -1064,13 +1069,13 @@ void useOrbOfDragon(cell *c) { checkmoveO(); } -EX bool monstersnearO(orbAction a, cell *c, eMonster who, cell *pushto, cell *comefrom) { +EX bool monstersnearO(orbAction a, cell *c) { // printf("[a = %d] ", a); if(shmup::on) return false; if(a == roCheck && multi::players > 1) return true; else if(a == roMultiCheck) return false; - else return monstersnear(c, who, pushto, comefrom); + else return monstersnear(c, moPlayer); } EX bool isCheck(orbAction a) { return a == roCheck || a == roMultiCheck; } @@ -1237,12 +1242,12 @@ EX eItem targetRangedOrb(cell *c, orbAction a) { if(c->monst) { if(!canAttack(cf, moFriendlyIvy, c, c->monst, 0)) continue; - if(monstersnear(cwt.at, moPlayer, NULL, cwt.at)) continue; + if(monstersnear(cwt.at, moPlayer)) continue; } else { if(!passable(c, cf, P_ISPLAYER | P_MONSTER)) continue; if(strictlyAgainstGravity(c, cf, false, MF_IVY)) continue; - if(monstersnear(cwt.at, moPlayer, c, cwt.at)) continue; + if(monstersnear(cwt.at, moPlayer)) continue; } dirs.push_back(d); } diff --git a/pattern2.cpp b/pattern2.cpp index 14bfd2a8..351276ec 100644 --- a/pattern2.cpp +++ b/pattern2.cpp @@ -422,7 +422,7 @@ EX int fieldval_uniq(cell *c) { } else if(euc::in(2)) { auto p = euc2_coordinates(c); - if(bounded) return p.first + (p.second << 16); + if(bounded) return p.first + p.second * (1 << 16); return gmod(p.first - 22 * p.second, 3*127); } else if(euc::in(3)) { @@ -1682,11 +1682,13 @@ EX namespace patterns { ep.extra_params["ey"] = y; if(S7 == 6) ep.extra_params["ez"] = -x-y; } + #if CAP_CRYSTAL if(cryst) { crystal::ldcoord co = crystal::get_ldcoord(c); for(int i=0; imaster); ep.extra_params["ax"] = szgmod(co[0], asonov::period_xy); diff --git a/pcmove.cpp b/pcmove.cpp index e6d30f6d..f68a99b3 100644 --- a/pcmove.cpp +++ b/pcmove.cpp @@ -357,8 +357,8 @@ bool pcmove::swing() { sideAttack(cwt.at, d, moPlayer, 0); mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK); - - if(monstersnear(cwt.at, moPlayer, nullptr, cwt.at)) { + + if(monstersnear_add_pmi(movei(cwt.at, STAY))) { if(vmsg()) wouldkill("You would be killed by %the1!"); return false; @@ -510,6 +510,13 @@ struct changes_t { void at_rollback(reaction_t act) { if(on) rollbacks.emplace_back(act); } + + void push_push(cell *tgt) { + pushes.push_back(tgt); + auto v = [] { pushes.pop_back(); }; + rollbacks.push_back(v); + commits.push_back(v); + } }; #endif @@ -601,7 +608,7 @@ bool pcmove::actual_move() { return false; } - if(items[itOrbDomination] > ORBBASE && isMountable(c2->monst) && !monstersnear2() && fmsMove) { + if(items[itOrbDomination] > ORBBASE && isMountable(c2->monst) && fmsMove) { if(checkonly) { nextmovetype = lmMove; return true; } if(!isMountable(cwt.at->monst)) dragon::target = NULL; movecost(cwt.at, c2, 3); @@ -629,6 +636,7 @@ bool pcmove::actual_move() { addMessage(XLAT("You push %the1.", c2->wall)); lastmovetype = lmPush; lastmove = cwt.at; pushThumper(mip); + changes.push_push(mip.t); return perform_actual_move(); } @@ -646,8 +654,6 @@ bool pcmove::actual_move() { if(!c2->monst && cwt.at->wall == waBoat && cwt.at->item != itOrbYendor && boatGoesThrough(c2) && markOrb(itOrbWater) && !nonAdjacentPlayer(c2, cwt.at) && fmsMove) { - if(havePushConflict(cwt.at, checkonly)) return false; - if(c2->item && !cwt.at->item) moveItem(c2, cwt.at, false), boatmove = true; placeWater(c2, cwt.at); moveBoat(mi); @@ -663,7 +669,6 @@ bool pcmove::actual_move() { bool pcmove::boat_move() { cell *& c2 = mi.t; - if(havePushConflict(cwt.at, checkonly)) return false; if(againstWind(c2, cwt.at)) { if(vmsg()) addMessage(XLAT(airdist(c2) < 3 ? "The Air Elemental blows you away!" : "You cannot go against the wind!")); @@ -744,8 +749,6 @@ bool pcmove::after_escape() { return false; } - if(havePushConflict(cwt.at, checkonly)) return false; - changes.ccell(c2); changes.ccell(cwt.at); @@ -756,6 +759,7 @@ bool pcmove::after_escape() { nextmovetype = lmMove; addMessage(XLAT("You push %the1 behind you!", waBigStatue)); animateMovement(mi.rev(), LAYER_BOAT); + changes.push_push(cwt.at); return perform_actual_move(); } @@ -849,7 +853,7 @@ bool pcmove::move_if_okay() { if(switchplace_prevent(cwt.at, c2, checkonly)) return false; - if(!checkonly && warningprotection_hit(do_we_stab_a_friend(cwt.at, c2, moPlayer))) + if(!checkonly && warningprotection_hit(do_we_stab_a_friend(mi, moPlayer))) return false; nextmovetype = lmMove; @@ -899,7 +903,7 @@ bool pcmove::attack() { if(!ca) { if(forcedmovetype == fmAttack) { - if(monstersnear(cwt.at,moPlayer,NULL,cwt.at)) { + if(monstersnear_add_pmi(movei(cwt.at, STAY))) { if(vmsg()) wouldkill("%The1 would get you!"); return false; } @@ -922,10 +926,9 @@ bool pcmove::attack() { else mip.t = c2; if(mip.t) changes.ccell(mip.t); + changes.push_push(mip.t); } - if(havePushConflict(mip.t, checkonly)) return false; - if(!(isWatery(cwt.at) && c2->monst == moWaterElemental) && checkNeedMove(checkonly, true)) return false; @@ -982,7 +985,7 @@ bool pcmove::attack() { lastmovetype = lmAttack; lastmove = c2; swordAttackStatic(); - if(monstersnear(cwt.at, moPlayer, nullptr, cwt.at)) { + if(monstersnear_add_pmi(movei(cwt.at, STAY))) { if(vmsg()) wouldkill("You would be killed by %the1!"); return false; } @@ -1090,18 +1093,18 @@ bool pcmove::perform_move_or_jump() { lastmovetype = lmMove; lastmove = cwt.at; apply_chaos(); - stabbingAttack(cwt.at, mi.t, moPlayer); - cell *c1 = cwt.at; + stabbingAttack(mi, moPlayer); changes.value_keep(cwt); cwt += wstep; mirror::act(origd, mirror::SPINMULTI | mirror::ATTACK | mirror::GO); - - playerMoveEffects(c1, mi.t); + + auto pmi = player_move_info(mi); + playerMoveEffects(mi); if(mi.t->monst == moFriendlyIvy) changes.ccell(mi.t), mi.t->monst = moNone; - if(monstersnear(cwt.at, moPlayer, nullptr, c1)) { + if(monstersnear_add_pmi(pmi)) { if(vmsg()) wouldkill("%The1 would kill you there!"); return false; } @@ -1135,19 +1138,21 @@ bool pcmove::stay() { return false; swordAttackStatic(); nextmovetype = lmSkip; - if(monstersnear(cwt.at, moPlayer, nullptr, cwt.at)) { + + mi = movei(cwt.at, STAY); + if(last_gravity_state && !gravity_state) + playerMoveEffects(mi); + if(d == -2) + dropGreenStone(cwt.at); + + if(monstersnear_add_pmi(mi)) { if(vmsg()) wouldkill("%The1 would get you!"); return false; } if(checkonly) return true; if(changes.on) changes.commit(); - if(d == -2) - dropGreenStone(cwt.at); if(cellUnstable(cwt.at) && !markOrb(itOrbAether)) - doesFallSound(cwt.at); - - if(last_gravity_state && !gravity_state) - playerMoveEffects(cwt.at, cwt.at); + doesFallSound(cwt.at); return after_move(); } @@ -1246,12 +1251,14 @@ EX bool playerInPower() { return false; } -EX void playerMoveEffects(cell *c1, cell *c2) { +EX void playerMoveEffects(movei mi) { + cell *c1 = mi.s; + cell *c2 = mi.t; if(peace::on) items[itOrbSword] = c2->land == laBurial ? 100 : 0; changes.value_keep(sword::dir[multi::cpid]); - sword::dir[multi::cpid] = sword::shift(c1, c2, sword::dir[multi::cpid]); + sword::dir[multi::cpid] = sword::shift(mi, sword::dir[multi::cpid]); destroyWeakBranch(c1, c2, moPlayer); @@ -1457,20 +1464,20 @@ EX void sideAttack(cell *mf, int dir, eMonster who, int bonuskill) { } } -EX eMonster do_we_stab_a_friend(cell *mf, cell *mt, eMonster who) { +EX eMonster do_we_stab_a_friend(movei mi, eMonster who) { eMonster m = moNone; - do_swords(mf, mt, who, [&] (cell *c, int bb) { - if(!peace::on && canAttack(mt, who, c, c->monst, AF_SWORD) && c->monst && isFriendly(c)) m = c->monst; + do_swords(mi, who, [&] (cell *c, int bb) { + if(!peace::on && canAttack(mi.t, who, c, c->monst, AF_SWORD) && c->monst && isFriendly(c)) m = c->monst; }); - for(int t=0; ttype; t++) { - cell *c = mf->move(t); + for(int t=0; ttype; t++) { + cell *c = mi.s->move(t); if(!c) continue; bool stabthere = false; - if(logical_adjacent(mt, who, c)) stabthere = true; + if(logical_adjacent(mi.t, who, c)) stabthere = true; - if(stabthere && canAttack(mt,who,c,c->monst,AF_STAB) && isFriendly(c)) + if(stabthere && canAttack(mi.t,who,c,c->monst,AF_STAB) && isFriendly(c)) return c->monst; } @@ -1484,27 +1491,14 @@ EX void wouldkill(const char *msg) { addMessage(XLAT("Cannot move into the current location of another player!")); else if(who_kills_me == moAirball) addMessage(XLAT("Players cannot get that far away!")); + else if(who_kills_me == moTongue) + addMessage(XLAT("Cannot push into another player!")); + else if(who_kills_me == moCrushball) + addMessage(XLAT("Cannot push into the same location!")); else addMessage(XLAT(msg, who_kills_me)); } -EX bool havePushConflict(cell *pushto, bool checkonly) { - if(pushto && multi::activePlayers() > 1) { - for(int i=0; iland == laPower && to->land != laPower && (phase & 1)) { int n=0; diff --git a/polygons.cpp b/polygons.cpp index 20ed5023..6f9c383f 100644 --- a/polygons.cpp +++ b/polygons.cpp @@ -718,24 +718,26 @@ void geometry_information::procedural_shapes() { vector equal_weights(1000, 1); -#if !(CAP_BT && MAXMDIM >= 4) +#if MAXMDIM < 4 void geometry_information::make_wall(int id, vector vertices, vector weights) { } void geometry_information::reserve_wall3d(int i) { } void geometry_information::create_wall3d() { } void geometry_information::compute_cornerbonus() { } #endif -#if CAP_BT && MAXMDIM >= 4 +#if MAXMDIM >= 4 // Make a wall hyperpoint ray_kleinize(hyperpoint h, int id, ld pz) { if(geometry == gNil && among(id, 2, 5)) h[2] = 0; + #if CAP_BT if(hyperbolic && bt::in()) { // ld co = vid.binary_width / log(2) / 4; // hyperpoint res = point31(h[2]*log(2)/2, h[0]*co, h[1]*co); return deparabolic10(bt::parabolic3(h[0], h[1]) * xpush0(log(2)/2*h[2])); } + #endif if(prod) { return point3(h[0]/h[2], h[1]/h[2], pz); } @@ -756,7 +758,9 @@ void geometry_information::make_wall(int id, vector vertices, vector reverse(vertices.begin(), vertices.end()), reverse(weights.begin(), weights.end()); + #if CAP_BT ld yy = log(2) / 2; + #endif bshape(shWall3D[id], PPR::WALL); last->flags |= POLY_TRIANGLES | POLY_PRINTABLE; @@ -815,8 +819,13 @@ void geometry_information::make_wall(int id, vector vertices, vector hpcpush(h); return; } if(sn::in() || !bt::in()) { hpcpush(ultra_normalize(h)); return; } - hyperpoint res = bt::parabolic3(h[0], h[1]) * xpush0(yy*h[2]); - hpcpush(res); + #if CAP_BT + if(bt::in()) { + hyperpoint res = bt::parabolic3(h[0], h[1]) * xpush0(yy*h[2]); + hpcpush(res); + } + #endif + hpcpush(h); }); } @@ -833,8 +842,13 @@ void geometry_information::make_wall(int id, vector vertices, vector if(nil) h = nilv::on_geodesic(vertices[a], vertices[(a+1)%n], y * 1. / STEP); if(sn::in() || !bt::in()) { hpcpush(ultra_normalize(h)); continue; } - hyperpoint res = bt::parabolic3(h[0], h[1]) * xpush0(yy*h[2]); - hpcpush(res); + #if CAP_BT + if(bt::in()) { + hyperpoint res = bt::parabolic3(h[0], h[1]) * xpush0(yy*h[2]); + hpcpush(res); + } + #endif + hpcpush(h); } hpcpush(hpc[last->s]); } @@ -875,6 +889,8 @@ void geometry_information::reserve_wall3d(int i) { void geometry_information::create_wall3d() { if(WDIM == 2) return; reserve_wall3d(kite::in() ? 22 : hybri ? 0 : S7); + + #if CAP_BT if(GDIM == 3 && bt::in() && geometry == gBinary3) { hyperpoint h00 = point3(-1,-1,-1); hyperpoint h01 = point3(-1,0,-1); @@ -970,6 +986,7 @@ void geometry_information::create_wall3d() { make_wall(12, {point3(3*h,r3,z), point3(0,2*r3,z), point3(-3*h,r3,z)}); make_wall(13, {point3(3*h,r3,z), point3(3*h,-r3,z), point3(0,-2*r3,z), point3(-3*h,-r3,z), point3(-3*h,r3,z)}); } + #endif if(prod) { walloffsets.clear(); @@ -1060,6 +1077,7 @@ void geometry_information::create_wall3d() { } } + #if CAP_BT if(kite::in()) { auto kv = kite::make_walls(); for(auto& v: kv.first) for(auto& h: v) { @@ -1068,6 +1086,7 @@ void geometry_information::create_wall3d() { } for(int i=0; i connections; diff --git a/racing.cpp b/racing.cpp index 3047f944..a42f0e1e 100644 --- a/racing.cpp +++ b/racing.cpp @@ -755,7 +755,7 @@ EX transmatrix track_matrix(int at, int dir) { transmatrix res = unshift(ggmatrix(racing::track[at])); while(true) { if(at+dir < 0 || at+dir >= isize(racing::track)) return res; - for(int x=0; x 10000) return res; cell *cur = racing::track[at]; at += dir; diff --git a/raycaster.cpp b/raycaster.cpp index fa0f6d6d..defcfb9a 100644 --- a/raycaster.cpp +++ b/raycaster.cpp @@ -27,7 +27,11 @@ EX ld exp_start = 1; EX ld exp_decay_exp = 4; EX ld exp_decay_poly = 10; +#ifdef GLES_ONLY +const int gms_limit = 16; /* enough for Bringris -- need to do better */ +#else const int gms_limit = 110; +#endif EX ld maxstep_sol = .05; EX ld maxstep_nil = .1; @@ -39,7 +43,8 @@ static const int NO_LIMIT = 999999; EX ld hard_limit = NO_LIMIT; -EX int max_iter_sol = 600, max_iter_iso = 60; +EX int max_iter_sol = 600; +EX int max_iter_iso = 60; EX int max_cells = 2048; EX bool rays_generate = true; @@ -170,6 +175,8 @@ int deg, irays; #ifdef GLES_ONLY void add(string& tgt, string type, string name, int min_index, int max_index) { + if(min_index >= max_index) ; + else if(min_index + 1 == max_index) tgt += "{ return " + name + "[" + its(min_index) + "]; }"; else { @@ -193,6 +200,11 @@ string build_getter(string type, string name, int index) { #define GET(array, index) array "[" index "]" #endif +void replace_str(string& s, string a, string b) { + while(s.find(a) != string::npos) + s.replace(s.find(a), isize(a), b); + } + EX hookset hooks_rayshader; EX hookset)> hooks_rayset; @@ -216,6 +228,7 @@ void enable_raycaster() { bool use_reflect = reflect_val && !nil && !levellines; bool bi = arcm::in() || kite::in() || arb::in() || !PURE; + bi = false; string vsh = "attribute mediump vec4 aPosition;\n" @@ -269,11 +282,13 @@ void enable_raycaster() { int flat1 = 0, flat2 = deg; if(prod || rotspace) flat2 -= 2; +#if CAP_BT if(hyperbolic && bt::in()) { fsh += "uniform mediump float uBLevel;\n"; flat1 = bt::dirs_outer(); flat2 -= bt::dirs_inner(); } +#endif if(hyperbolic) fsh += @@ -355,14 +370,34 @@ void enable_raycaster() { " mediump mat4 vw = uStart * xzspin(-lambda) * xpush(eye) * yzspin(phi);\n" " mediump vec4 at0 = vec4(0., 0., 1., 0.);\n"; - else fmain += - " mediump mat4 vw = uStart;\n" - " mediump vec4 at0 = at;\n" - " gl_FragColor = vec4(0,0,0,1);\n" - " mediump float left = 1.;\n" - " at0.y = -at.y;\n" - " at0.w = 0.;\n" - " at0.xyz = at0.xyz / length(at0.xyz);\n"; + else { + fmain += + " mediump mat4 vw = uStart;\n" + " mediump vec4 at0 = at;\n" + " gl_FragColor = vec4(0,0,0,1);\n" + " mediump float left = 1.;\n" + " at0.y = -at.y;\n" + " at0.w = 0.;\n"; + + if(panini_alpha) fmain += + "mediump float hr = at0.x*at0.x;\n" + "mediump float alpha = " + to_glsl(panini_alpha) + ";\n" + "mediump float A = 1. + hr;\n" + "mediump float B = -2.*hr*alpha;\n" + "mediump float C = 1. - hr*alpha*alpha;\n" + "B /= A; C /= A;\n" + + "mediump float hz = B / 2. + sqrt(C + B*B/4.);\n" + "if(abs(hz) > 1e-3) {" + "at0.xyz *= hz+alpha;\n" + "at0.z = hz;\n}" + " else at0.z = 0.;\n" +"\n" + ; + + fmain += + " at0.xyz = at0.xyz / length(at0.xyz);\n"; + } if(hyperbolic) fsh += " mediump float len(mediump vec4 x) { return x[3]; }\n"; else if(sphere && rotspace) fsh += " mediump float len(mediump vec4 x) { return 1.+x.x*x.x+x.y*x.y-x.z*x.z-x.w*x.w; }\n"; @@ -454,7 +489,7 @@ void enable_raycaster() { fmain += "for(int i="+its(flat1)+"; i<"+(prod ? "sides-2" : WDIM == 2 ? "sides" : its(flat2))+"; i++) {\n"; - fmain += "int woi = walloffset+i;\n"; + // fmain += "int woi = walloffset+i;\n"; if(in_h2xe()) fmain += " mediump float v = ((position - uM[woi] * position)[2] / (uM[woi] * tangent - tangent)[2]);\n" @@ -495,6 +530,8 @@ void enable_raycaster() { " if(d < 0.) continue;\n" " mediump vec4 next_position = position + d * tangent;\n" " if(dot(next_position, tangent) < dot(uM[woi]*next_position, uM[woi]*tangent)) continue;\n"; + + replace_str(fmain, "[woi]", "[walloffset+i]"); fmain += " if(d < dist) { dist = d; which = i; }\n" @@ -964,6 +1001,11 @@ void enable_raycaster() { "tangent -= dot(vec4(-position.xyz, position.w), tangent) * position;\n" "tangent /= sqrt(dot(tangent.xyz, tangent.xyz) - tangent.w*tangent.w);\n"; + if(in_h2xe()) fmain += + "position /= sqrt(position.z*position.z - dot(position.xy, position.xy));\n" + "tangent -= dot(vec3(-position.xy, position.z), tangent.xyz) * position;\n" + "tangent /= sqrt(dot(tangent.xy, tangent.xy) - tangent.z*tangent.z);\n"; + if(hyperbolic && bt::in()) { fmain += "if(which == 20) {\n" @@ -1054,11 +1096,12 @@ void enable_raycaster() { " if(col.w == 1.) {\n"; if(hyperbolic) fmain += - " mediump float z = at0.z * sinh(go);\n" - " mediump float w = 1.;\n"; + " mediump vec4 t = at0 * sinh(go);\n"; else fmain += - " mediump float z = at0.z * go;\n" - " mediump float w = 1.;\n"; + " mediump vec4 t = at0 * go;\n"; + + fmain += + " t.w = 1.;\n"; if(levellines) { if(hyperbolic) @@ -1067,10 +1110,13 @@ void enable_raycaster() { fmain += "gl_FragColor.xyz *= 0.5 + 0.5 * cos(z * uLevelLines * 2. * PI);\n"; fsh += "uniform mediump float uLevelLines;\n"; } + + if(panini_alpha) + fmain += panini_shader(); #ifndef GLES_ONLY fmain += - " gl_FragDepth = (" + to_glsl(-vnear-vfar)+"+w*" + to_glsl(2*vnear*vfar)+"/z)/" + to_glsl(vnear-vfar)+";\n" + " gl_FragDepth = (" + to_glsl(-vnear-vfar)+"+t.w*" + to_glsl(2*vnear*vfar)+"/t.z)/" + to_glsl(vnear-vfar)+";\n" " gl_FragDepth = (gl_FragDepth + 1.) / 2.;\n"; #endif @@ -1195,12 +1241,20 @@ void bind_array(vector>& v, GLint t, GLuint& tx, int id) { if(tx == 0) glGenTextures(1, &tx); glActiveTexture(GL_TEXTURE0 + id); + GLERR("activeTexture"); + glBindTexture(GL_TEXTURE_2D, tx); + GLERR("bindTexture"); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST); glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST); + GLERR("texParameteri"); + #ifdef GLES_ONLY + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, length, isize(v)/length, 0, GL_RGBA, GL_FLOAT, &v[0]); + #else glTexImage2D(GL_TEXTURE_2D, 0, 0x8814 /* GL_RGBA32F */, length, isize(v)/length, 0, GL_RGBA, GL_FLOAT, &v[0]); + #endif GLERR("bind_array"); } @@ -1533,8 +1587,11 @@ EX void cast() { } if(o->uPLevel != -1) glUniform1f(o->uPLevel, cgi.plevel / 2); + + #if CAP_BT if(o->uBLevel != -1) glUniform1f(o->uBLevel, log(bt::expansion()) / 2); + #endif if(o->uLinearSightRange != -1) glUniform1f(o->uLinearSightRange, sightranges[geometry]); @@ -1560,7 +1617,13 @@ EX void cast() { } + #if CAP_VERTEXBUFFER + glhr::bindbuffer_vertex(screen); + glVertexAttribPointer(hr::aPosition, 4, GL_FLOAT, GL_FALSE, sizeof(glvertex), 0); + #else glVertexAttribPointer(hr::aPosition, 4, GL_FLOAT, GL_FALSE, sizeof(glvertex), &screen[0]); + #endif + if(ray::comparison_mode) glhr::set_depthtest(false); else { @@ -1572,6 +1635,7 @@ EX void cast() { glActiveTexture(GL_TEXTURE0 + 0); glBindTexture(GL_TEXTURE_2D, floor_textures->renderedTexture); + GLERR("bind"); glDrawArrays(GL_TRIANGLES, 0, 6); GLERR("finish"); } diff --git a/reg3.cpp b/reg3.cpp index 82c00678..7ccdd64a 100644 --- a/reg3.cpp +++ b/reg3.cpp @@ -13,13 +13,6 @@ namespace hr { #if MAXMDIM >= 4 -namespace binary { - void build_tmatrix(); - void virtualRebaseSimple(heptagon*& base, transmatrix& at); - int celldistance3(heptagon *c1, heptagon *c2); - hyperpoint deparabolic3(hyperpoint h); - } - /** \brief regular three-dimensional tessellations */ EX namespace reg3 { @@ -608,8 +601,8 @@ EX namespace reg3 { hrmap *binary_map; hrmap_quotient3 *quotient_map; - unordered_map> reg_gmatrix; - unordered_map > > altmap; + map> reg_gmatrix; + map > > altmap; vector spherecells; @@ -641,10 +634,13 @@ EX namespace reg3 { quotient_map = nullptr; #if CAP_FIELD + #if CAP_CRYSTAL if(geometry == gSpace344) { quotient_map = new hrmap_from_crystal; } - else if(geometry == gSpace535) { + else + #endif + if(geometry == gSpace535) { quotient_map = new seifert_weber::hrmap_seifert_cover; } else if(hyperbolic) { @@ -653,6 +649,7 @@ EX namespace reg3 { #endif h.zebraval = quotient_map ? quotient_map->allh[0]->zebraval : 0; + #if CAP_BT if(hyperbolic) { dynamicval g(geometry, gBinary3); bt::build_tmatrix(); @@ -667,6 +664,7 @@ EX namespace reg3 { binary_map = bt::new_alt_map(alt); T = xpush(.01241) * spin(1.4117) * xpush(0.1241) * cspin(0, 2, 1.1249) * xpush(0.07) * Id; } + #endif reg_gmatrix[origin] = make_pair(alt, T); altmap[alt].emplace_back(origin, T); @@ -730,12 +728,14 @@ EX namespace reg3 { println(hlog, "FAIL"); exit(3); } + #if CAP_BT if(steps) { dynamicval g(geometry, gBinary3); dynamicval cm(currentmap, binary_map); for(int i=0; itype; i++) verify_neighbors(alt->cmove(i), steps-1, currentmap->iadj(alt, i) * hT); } + #endif } heptagon *create_step(heptagon *parent, int d) override { @@ -748,11 +748,13 @@ EX namespace reg3 { transmatrix T = p1.second * cgi.adjmoves[d]; #endif transmatrix T1 = T; + #if CAP_BT if(hyperbolic) { dynamicval g(geometry, gBinary3); dynamicval cm(currentmap, binary_map); binary_map->virtualRebase(alt, T); } + #endif fixmatrix(T); auto hT = tC0(T); @@ -852,10 +854,12 @@ EX namespace reg3 { } ~hrmap_reg3() { + #if CAP_BT if(binary_map) { dynamicval g(geometry, gBinary3); delete binary_map; } + #endif if(quotient_map) delete quotient_map; clearfrom(origin); } @@ -901,12 +905,14 @@ EX namespace reg3 { auto p1 = reg_gmatrix[h1]; auto p2 = reg_gmatrix[h2]; transmatrix T = Id; + #if CAP_BT if(hyperbolic) { dynamicval g(geometry, gBinary3); dynamicval cm(currentmap, binary_map); T = binary_map->relative_matrix(p2.first, p1.first, hint); } - T = inverse(p1.second) * T * p2.second; + #endif + T = inverse(p1.second) * T * p2.second; if(elliptic && T[LDIM][LDIM] < 0) T = centralsym * T; return T; } @@ -1343,7 +1349,11 @@ EX int celldistance(cell *c1, cell *c2) { return clueless_celldistance(c1, c2); dynamicval g(geometry, gBinary3); + #if CAP_BT return 20 + bt::celldistance3(r->reg_gmatrix[c1->master].first, r->reg_gmatrix[c2->master].first); + #else + return 20; + #endif } EX bool pseudohept(cell *c) { @@ -1429,7 +1439,7 @@ ld adistance(cell *c) { return regmap()->reg_gmatrix[c->master].first->distance * log(2) - h[0]; } -unordered_map, int> memo; +map, int> memo; bool cdd; diff --git a/renderbuffer.cpp b/renderbuffer.cpp index 11b8ecde..33641944 100644 --- a/renderbuffer.cpp +++ b/renderbuffer.cpp @@ -85,7 +85,9 @@ renderbuffer::renderbuffer(int x, int y, bool gl) : x(x), y(y) { # if CAP_GL if(gl) { + GLERR("renderbuffer init"); resetbuffer rb; + GLERR("after resetbuffer"); tx = next_p2(x); ty = next_p2(y); @@ -119,7 +121,7 @@ renderbuffer::renderbuffer(int x, int y, bool gl) : x(x), y(y) { glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, tx, ty); bool has_depth = true; if(glGetError() != GL_NO_ERROR) { - printf("Could not create: GL_DEPTH24_STENCIL8"); + println(hlog, "Could not create: GL_DEPTH24_STENCIL8"); glRenderbufferStorage(GL_RENDERBUFFER, GL_STENCIL_INDEX8, tx, ty); has_depth = false; } @@ -243,7 +245,9 @@ resetbuffer::resetbuffer() { #if CAP_GL drawFboId = 0, readFboId = 0; glGetIntegerv(GL_DRAW_FRAMEBUFFER_BINDING, &drawFboId); + GLERR("getInteger a"); glGetIntegerv(GL_READ_FRAMEBUFFER_BINDING, &readFboId); + GLERR("getInteger b"); #endif #if CAP_SDL sreset = s; diff --git a/rogueviz/bringris.cpp b/rogueviz/bringris.cpp new file mode 100644 index 00000000..482a3e39 --- /dev/null +++ b/rogueviz/bringris.cpp @@ -0,0 +1,1802 @@ +// non-Euclidean falling block game, implemented using the HyperRogue engine +// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details + +#ifdef BRINGRIS + +#define CUSTOM_CAPTION "Bringris 1.3" + +#define MAXMDIM 4 + +#define CAP_INV 0 +#define CAP_COMPLEX2 0 +#define CAP_EDIT 0 + +#ifdef BWEB +#define CAP_BT 0 +#define CAP_SOLV 0 +#endif + +#define CAP_THREAD 0 +// #define CAP_ZLIB 0 +#define CAP_FILES 1 +#define CAP_CONFIG 1 +// #define CAP_ANIMATIONS 0 + +#define CAP_RUG 0 +#define CAP_SHOT 0 +#define CAP_SVG 0 +#define CAP_PNG 0 +#define CAP_WRL 0 +#define CAP_TOUR 0 +#define CAP_IRR 0 +#define CAP_CRYSTAL 0 +#define CAP_ARCM 0 +#define CAP_HISTORY 0 +#define CAP_STARTANIM 0 +#define CAP_SAVE 0 +#define CAP_TRANS 0 + +#define MINIMIZE_GL_CALLS 0 // minimize does not currently work correctly + +#ifdef BWEB +#include "../hyperweb.cpp" +#else +#include "../hyper.cpp" +#endif + +#include "subquotient.cpp" +#define SUBQ +#endif + +#include "../hyper.h" + +#define solnil (nil || sol) + +namespace hr { + +namespace subquotient { + #ifndef SUBQ + void create_subquotient(int qty = -1, int id = 0); + #endif + extern eGeometry gSubquotient; + } + +namespace bringris { + +struct bgeometry { + string name; + string cap; + flagtype flags; + reaction_t create; + }; + +enum eBringrisMove { bmDown, bmLeft, bmUp, bmRight, bmTurnLeft, bmTurnRight, bmDrop, bmFullDrop, bmPause, bmNothing, bmLast }; + +vector move_names = { "move down", "move left", "move up", "move right", "turn left", "turn right", "drop by one", "full drop", "pause", "do nothing" }; + +int camera_level; + +ld ang = 0, cur_ang = 0; + +int lti; + +int bgeom = 0; + +int max_piece; +bool rotate_allowed = false; + +bool use_raycaster = true; + +int last_adjust, when_t; +transmatrix tView, pView; +cell* ncenter; + +cell *well_center; + +vector level; + +vector out_level; + +map center_distance; + +bool pro_game; + +int well_size = 10; +int camera = 3; + +int facing_mod = 0; + +int draw_per_level = 500; + +int shape_id, next_shape_id; + +cellwalker at; + +int move_started; +int move_at; + +int completed; +int bricks, cubes; + +ld score; + +bool paused; +bool explore; + +enum eState { + tsPreGame, tsFalling, tsBetween, tsCollect, tsGameover + }; + +eState state = tsPreGame; + +constexpr flagtype HYPERBOLIC = 1; +constexpr flagtype ORBIFOLD = 2; +constexpr flagtype SECRET = 4; +constexpr flagtype NONORIENTABLE = 8; +constexpr flagtype SPHERICAL = 16; +constexpr flagtype EUCLIDEAN = 32; +constexpr flagtype SUBQUOTIENT = 64; +constexpr flagtype HDUAL = 128; +constexpr flagtype BOUNDED_WELL = 256; +constexpr flagtype ASYMMETRIC_ONLY = 512; +constexpr flagtype FLAT_ONLY = 1024; + +cell *get_center(); +cell *shift_block_target(int dir); +void shift_block(int dir, bool camera_only = false); +void rotate_block(int dir, bool camera_only = false); + +vector bgeoms = { + {"Bring surface", "the original Bringris geometry", HYPERBOLIC, [] { + using namespace fieldpattern; + current_extra = 2; + auto& gxcur = fgeomextras[current_extra]; + while(isize(gxcur.primes) < 1) nextPrime(gxcur); + fgeomextras[current_extra].current_prime_id = 0; + enableFieldChange(); + set_geometry(gFieldQuotient); + + gp::param = gp::loc(1, 1); + set_variation(eVariation::unrectified); + + set_geometry(gProduct); + max_piece = 4; + rotate_allowed = false; + }}, + + {"torus", "Euclidean level geometry", EUCLIDEAN, [] { + auto& T0 = euc::eu_input.user_axes; + T0[0][0] = 5; + T0[0][1] = 0; + T0[1][0] = 0; + T0[1][1] = 5; + euc::eu_input.twisted = 0; + set_geometry(gEuclidSquare); + set_variation(eVariation::pure); + set_geometry(gProduct); + max_piece = 4; + rotate_allowed = true; + }}, + + {"Cube", "spherical level geometry", SPHERICAL, [] { + set_geometry(gSmallSphere); + set_variation(eVariation::pure); + set_geometry(gProduct); + max_piece = 3; + rotate_allowed = false; + }}, + + {"Klein bottle", "non-orientable manifold", EUCLIDEAN | NONORIENTABLE, [] { + auto& T0 = euc::eu_input.user_axes; + T0[0][0] = 5; + T0[0][1] = 0; + T0[1][0] = 0; + T0[1][1] = 5; + euc::eu_input.twisted = 8; + set_geometry(gEuclidSquare); + set_variation(eVariation::pure); + set_geometry(gProduct); + max_piece = 4; + rotate_allowed = true; + }}, + + {"pentagons", "different tiles on the Bring surface", HYPERBOLIC | HDUAL, [] { + using namespace fieldpattern; + current_extra = 2; + auto& gxcur = fgeomextras[current_extra]; + while(isize(gxcur.primes) < 1) nextPrime(gxcur); + fgeomextras[current_extra].current_prime_id = 0; + enableFieldChange(); + set_geometry(gFieldQuotient); + set_variation(eVariation::pure); + set_geometry(gProduct); + max_piece = 4; + rotate_allowed = false; + }}, + + {"double cube", "six squares around a vertex", HYPERBOLIC, [] { + using namespace fieldpattern; + current_extra = 3; + auto& gxcur = fgeomextras[current_extra]; + while(isize(gxcur.primes) < 1) nextPrime(gxcur); + fgeomextras[current_extra].current_prime_id = 0; + enableFieldChange(); + set_geometry(gFieldQuotient); + + gp::param = gp::loc(1, 1); + set_variation(eVariation::unrectified); + + set_geometry(gProduct); + max_piece = 3; + rotate_allowed = true; + }}, + + {"30/6", "six squares around a vertex", HYPERBOLIC, [] { + using namespace fieldpattern; + current_extra = 3; + auto& gxcur = fgeomextras[current_extra]; + while(isize(gxcur.primes) < 1) nextPrime(gxcur); + fgeomextras[current_extra].current_prime_id = 2; + enableFieldChange(); + set_geometry(gFieldQuotient); + + gp::param = gp::loc(1, 1); + set_variation(eVariation::unrectified); + + set_geometry(gProduct); + max_piece = 4; + rotate_allowed = true; + }}, + + {"42", "seven squares around a vertex", HYPERBOLIC, [] { + using namespace fieldpattern; + current_extra = 4; + auto& gxcur = fgeomextras[current_extra]; + while(isize(gxcur.primes) < 1) nextPrime(gxcur); + fgeomextras[current_extra].current_prime_id = 0; + enableFieldChange(); + set_geometry(gFieldQuotient); + + gp::param = gp::loc(1, 1); + set_variation(eVariation::unrectified); + + set_geometry(gProduct); + max_piece = 4; + rotate_allowed = false; + }}, + + {"bounded well", "five squares around a vertex", BOUNDED_WELL, [] { + set_geometry(g45); + gp::param = gp::loc(1, 1); + set_variation(eVariation::unrectified); + set_geometry(gProduct); + + max_piece = 4; + rotate_allowed = false; + }}, + + {"mirrored Bring", "hyperbolic and non-orientable", HYPERBOLIC | NONORIENTABLE | ASYMMETRIC_ONLY, [] { + set_geometry(gBring); + gp::param = gp::loc(1, 1); + set_variation(eVariation::unrectified); + start_game(); + subquotient::create_subquotient(2); + set_geometry(gProduct); + max_piece = 4; + rotate_allowed = false; + }}, + + {"giant", "like mirrored Bring but much larger", HYPERBOLIC | NONORIENTABLE, [] { + using namespace fieldpattern; + current_extra = 2; + auto& gxcur = fgeomextras[current_extra]; + while(isize(gxcur.primes) < 1) nextPrime(gxcur); + fgeomextras[current_extra].current_prime_id = 1; + enableFieldChange(); + set_geometry(gFieldQuotient); + + gp::param = gp::loc(1, 1); + set_variation(eVariation::unrectified); + subquotient::create_subquotient(2); + + set_geometry(gProduct); + max_piece = 5; + rotate_allowed = false; + well_size = 6; + }}, + + {"orbifold", "one fifth of the giant", HYPERBOLIC | NONORIENTABLE | ORBIFOLD, [] { + using namespace fieldpattern; + current_extra = 2; + auto& gxcur = fgeomextras[current_extra]; + while(isize(gxcur.primes) < 1) nextPrime(gxcur); + fgeomextras[current_extra].current_prime_id = 1; + enableFieldChange(); + set_geometry(gFieldQuotient); + + gp::param = gp::loc(1, 1); + set_variation(eVariation::unrectified); + subquotient::create_subquotient(10); + + set_geometry(gProduct); + max_piece = 4; + rotate_allowed = false; + }}, + + {"torus: shear", "Nil geometry: are you sure you want this?", SECRET, [] { + nilv::nilperiod = make_array(5, 0, 5); + // nilv::set_flags(); + set_geometry(gNil); + max_piece = 4; + rotate_allowed = false; + }}, + +#if CAP_SOLV + {"torus: Arnold's Cat", "Solv geometry: flat shapes are crazy enough", SECRET | FLAT_ONLY, [] { + asonov::period_xy = 5; + asonov::period_z = 0; + asonov::set_flags(); + set_geometry(gArnoldCat); + max_piece = 2; + rotate_allowed = false; + }}, +#endif + }; + +void create_game(); + +void enable_bgeom() { + stop_game_and_switch_mode(rg::nothing); + well_size = 10; + bgeoms[bgeom].create(); + start_game(); + create_game(); + state = tsPreGame; + } + +void enable_bgeom(int b) { + bgeom = b; + enable_bgeom(); + } + +flagtype bflags() { return bgeoms[bgeom].flags; } + +using code_t = vector>; + +struct piecedata { + code_t code; + int multi; + int penalty; + int symmetries; + int count; + }; + +vector piecelist; + +map, int> seen_blocks; + +bool listed(const vector& v, cell* c) { + for(auto cw: v) if(cw.at == c) return true; + return false; + } + +int down_dir() { return nil ? 4 : sol ? 0 : cwt.at->type-1; } +int up_dir() { return nil ? 1 : sol ? 6 : cwt.at->type-2; } + +cell *get_at(cell *lev, int z) { + if(prod) + return hybrid::get_at(lev, z); + else { + // auto co = nilv::get_coord(lev->master); + // co[2] += z * co[0]; + // co[1] = z; + // return nilv::get_heptagon_at(co)->c7; + while(z<0) z++, lev=lev->cmove(up_dir()); + while(z>0) z--, lev=lev->cmove(down_dir()); + return lev; + } + } + +int get_z(cell* c) { + if(prod) + return hybrid::get_where(c).second; + else if(nil) + return nilv::get_coord(c->master)[1]; + else if(sol) + return asonov::get_coord(c->master)[2]; + else + exit(1); + } + +pair get_where(cell *what) { + if(prod) + return hybrid::get_where(what); + else { + int z = get_z(what); + int zm = z; + while(zm>0) what = what->cmove(up_dir()), zm--; + while(zm<0) what = what->cmove(down_dir()), zm++; + return {what, z}; + // co[2] -= co[1] * co[0]; + // co[1] = 0; + // cell *c = nilv::get_heptagon_at(co)->c7; + // return {c, z}; + } + } + +set as_set(const vector& shape) { + set res; + for(auto sh: shape) res.insert(sh.at); + return res; + } + +bool same(const vector& shape, const vector& shape2) { + set allcells; + if(isize(shape) != isize(shape2)) return false; + for(auto sh: shape) allcells.insert(sh.at); + for(auto sh: shape2) allcells.erase(sh.at); + return allcells.empty(); + } + +cellwalker flatspin(cellwalker cw, int i) { + if(solnil) + cw.spin = i; + else + cw.spin = gmod(cw.spin + (cw.mirrored ? -i : i), cw.at->type - (hybri ? 2 : 0)); + return cw; + } + +int add_dir(cellwalker orig, int i, int sym = 0) { + if(prod) { + if(i >= orig.at->type-2) { + if(sym&2) i ^= (orig.at->type-1) ^ (orig.at->type-2); + return i; + } + else { + if(sym&1) i = -i; + return flatspin(orig, i).spin; + } + } + return i; + } + +cellwalker add(cellwalker orig, int i, int sym = 0) { + if(prod) { + if(i >= orig.at->type-2) { + if(sym&2) i ^= (orig.at->type-1) ^ (orig.at->type-2); + orig.at = orig.at->cmove(i); + return orig; + } + else { + if(sym&1) i = -i; + return flatspin(orig, i) + wstep; + } + } + return orig + i + wstep; + } + +vector build_from(const code_t& code, cellwalker start, int sym = 0) { + vector all = {start}; + for(auto c: code) + all.push_back(add(all[c.first], c.second, sym)); + return all; + } + +vector build_shape_matrices(const code_t& code, cellwalker start, int sym = 0) { + vector all = {start}; + vector allm = {Id}; + for(auto c: code) { + all.push_back(add(all[c.first], c.second, sym)); + allm.push_back(allm[c.first] * currentmap->adj(all[c.first].at, add_dir(all[c.first], c.second, sym))); + } + return allm; + } + +int penalty(const vector& shape, const code_t& code) { + int p = 0; + if(prod) { + int bad = shape[0].at->type-1; + for(auto co: code) if(co.second == bad) + p += 1000; + } + map dists; + for(auto cw: shape) dists[cw.at] = 99; + dists[shape[0].at] = 0; + for(int i=0; i get_z(shape[0].at)) + p += 10000; + return p; + } + +bool builds(const vector& shape, const code_t& code, int sym = 0, int eliminate = -1) { + if(isize(shape) != isize(code)+1) return false; + int ori = (solnil) ? 1 : prod ? shape[0].at->type-2 : shape[0].at->type; + for(auto sh: shape) for(int i=0; i shape2 = build_from(code, cellwalker(sh.at, i), sym); + if(eliminate != -1) seen_blocks.emplace(as_set(shape2), eliminate); + if(same(shape, shape2)) + return true; + } + return false; + } + +void generate_shapes_rec(vector& sofar, code_t& code, int cnt) { + if(isize(sofar) == cnt) { + int p = penalty(sofar, code); + auto ass = as_set(sofar); + + if(seen_blocks.count(ass)) { + auto& pc = piecelist[seen_blocks[ass]]; + if(p < pc.penalty) pc.penalty = p, pc.code = code; + pc.multi++; + return; + } + + int id = 0; + for(auto& pc: piecelist) { + if(builds(sofar, pc.code, 0, id)) { + if(p < pc.penalty) pc.penalty = p, pc.code = code; + pc.multi++; + return; + } + id++; + } + int syms = 0; + bool invalid = false; + for(int i: {0,1,2,3}) if(builds(sofar, code, i)) { + syms++; + if((bflags() & ASYMMETRIC_ONLY) && i == 1 && cnt >= 4) + invalid = true; + } + if(invalid) return; + bool vertical = true; + for(auto c: code) if(c.second < 4) vertical = false; + if(vertical) syms *= 2; + if(isize(code) == 0) syms = 1; + piecelist.push_back(piecedata{code, 1, p, syms, 0}); + return; + } + for(int i=0; itype; t++) { + if(sol && !among(t, 4, 5, 10, 11)) continue; + cellwalker ncw = add(sofar[i], t); + if(listed(sofar, ncw.at)) continue; + code.emplace_back(i, t); + sofar.push_back(ncw); + generate_shapes_rec(sofar, code, cnt); + sofar.pop_back(); + code.pop_back(); + } + } + +void generate_shapes(int cnt) { + vector cws = { get_at(get_center(), -well_size - 1) }; + code_t co = {}; + generate_shapes_rec(cws, co, cnt); + } + +void list_all() { + println(hlog, "number of shapes = ", isize(piecelist)); + // for(auto sh: piecelist) println(hlog, "multi=", sh.multi, " penalty=", sh.penalty, " syms=", sh.symmetries, " => ", sh.code); + } + +void water_shape() { + auto shape = build_from(piecelist[shape_id].code, at); + for(auto c: shape) + c.at->wall = waSea; + } + +set to_disappear; + +color_t hipso[] = { + 0x3030C0, + 0x6060FF, + 0x9090FF, + 0xFFFFFF, + 0x008000, + 0x40FF00, + 0xFFFF00, + 0xFF8000, + 0xFF0000, + 0x800000, + 0x400040, + 0x500050, + 0x600060 + }; + +color_t get_hipso(ld y) { + y += 12; + if(well_size <= 5) y *= 2; + return hipso[gmod(y, 13)]; + } + +void draw_shape() { + auto shape = build_from(piecelist[shape_id].code, at); + for(auto c: shape) { + int y = -get_z(c.at); + c.at->wall = waWaxWall, c.at->landparam = get_hipso(y); + } + } + +void remove_shape() { + auto shape = build_from(piecelist[shape_id].code, at); + for(auto c: shape) + c.at->wall = waNone; + } + +bool shape_conflict(cellwalker cw) { + auto shape = build_from(piecelist[shape_id].code, cw); + + /* self-conflict possible in the orbifold */ + for(int i=0; iwall) + return true; + return false; + } + +ld current_move_time_limit() { + return 50000 * pow(.9, completed) + 10000. / (1 + completed); + } + +int turn_animation = 500; +int collect_animation = 300; + +int choose_piece() { + ld total = 0; + int sel = 0; + ld lowest = 100000; + for(auto &p: piecelist) + lowest = min(lowest, p.count * 1. / p.symmetries); + + vector> probs; + + for(int i=0; i by_level; + +bool expert = true; + +void find_lines() { + by_level.clear(); + // println(hlog, "Removing levels"); + + for(int z=1; z<=well_size; z++) { + int ct = 0; + for(auto lev: level) { + cell *c = get_at(lev, -z); + ct += (c->wall ? 1 : 0); + } + by_level.push_back(ct); + } + // println(hlog, by_level, " / ", isize(level)); + + int points = 0; + + if(expert) { + for(int z=1; z<=well_size; z++) if(by_level[z-1] >= isize(level)) { + points++; + for(auto lev: level) { + cell *c = get_at(lev, -z); + to_disappear.insert(c); + } + } + } + else { + // int lines_found = 0; + for(int z=1; z<=well_size; z++) { + for(auto lev: level) for(int d=0; dtype; d++) { + cellwalker cw(get_at(lev, -z), d); + cellwalker cw0 = cw; + bool filled = true; + do { + if(!cw.at->wall) filled = false; + cw += wstep; + cw = flatspin(cw, lev->type/2); + } + while(cw != cw0); + if(filled) { + // lines_found = true; + do { + to_disappear.insert(cw.at); + cw += wstep; + cw = flatspin(cw, lev->type/2); + } + while(cw != cw0); + } + } + } + // println(hlog, "lines found = ", lines_found); + } + if(!to_disappear.empty()) { + move_at = ticks + collect_animation; + state = tsCollect; + score += 10000000. * points * (points+1.) / current_move_time_limit(); + completed += points; + playSound(cwt.at, points == 1 ? "pickup-gold" : "orb-mind"); + } + } + +void disappear_lines() { + for(auto lev: level) { + int nz = 1; + for(int z=1; z<=well_size; z++) { + cell *c1 = get_at(lev, -z); + if(!to_disappear.count(c1)) { + cell *c0 = get_at(lev, -nz); + c0->wall = c1->wall; + c0->landparam = get_hipso(nz); + nz++; + } + } + while(nz <= camera_level) { + cell *c0 = get_at(lev, -nz); + c0->wall = waNone; + nz++; + } + } + to_disappear.clear(); + state = tsBetween; + } + +void state_loop() { + find_lines(); + if(to_disappear.empty()) new_piece(); + } + +void fallen() { + draw_shape(); + bricks++; + cubes += isize(piecelist[shape_id].code)+1; + state = tsBetween; + playSound(cwt.at, "closegate"); + score += 20000000. / (current_move_time_limit() * 3 + ticks - move_started); + } + +void drop() { + remove_shape(); + cellwalker fall = at; + fall.at = fall.at->cmove(down_dir()); + if(shape_conflict(fall)) + fallen(); + else { + at = fall; + draw_shape(); + } + move_at = ticks + current_move_time_limit(); + if(solnil) { + pView = pView * currentmap->adj(cwt.at, down_dir()); + when_t = ticks + turn_animation; + } + } + +void fulldrop() { + remove_shape(); + cellwalker fall = at; + int no = 0; + auto last = fall; + while(true) { + last = fall; + fall.at = fall.at->cmove(down_dir()); + if(shape_conflict(fall)) break; + no++; + } + playSound(cwt.at, "hit-crush2"); + // println(hlog, "dropped by ", no); + fall = last; + at = fall; + draw_shape(); + if(!no) fallen(); + } + +void verify_matrix(transmatrix T) { + vector ver; + for(int i=0; i<3; i++) + for(int j=0; j<3; j++) { + ld a = T[0][i] * T[0][j] + T[1][i]*T[1][j] - T[2][i] * T[2][j]; + ver.push_back(kz(a)); + } + println(hlog, ver); + } + +ld move_dist; + +void set_view() { + move_dist = hdist0(currentmap->adj(cwt.at, 0) * C0); + + if(in_h2xe() && PURE && S3 == 4) { + ld dist = PIU(hdist0(get_corner_position(currentmap->gamestart(), 0))); + dist -= 1e-4; + move_dist = PIU(hdist(get_corner_position(currentmap->gamestart(), 0), get_corner_position(currentmap->gamestart(), 1))); + tView = xpush(-dist) * tView; + tView = spin(135*degree) * tView; + } + if(in_h2xe() && UNRECTIFIED) + tView = spin(135*degree) * tView; + if(in_h2xe() && S7 == 4) + tView = spin(90*degree) * tView; + if(in_s2xe()) + tView = spin(90*degree) * tView; + if(in_e2xe()) + tView = spin(90*degree) * tView; + } + +void set_tview(transmatrix T) { + + View = T * tView; + + transmatrix rel = pView * inverse(View); + fixmatrix(rel); + + centerover = ncenter; + optimizeview(); + ncenter = centerover; + // tView = View; + if(bgeom == 4) + tView = spin(72*degree*at.spin); + else + tView = spin(90*degree*at.spin); + if(at.mirrored) + tView = MirrorY * tView; + // tView = spin(90*degree*at.spin); + set_view(); + + pView = rel * tView; + when_t = ticks + turn_animation; + } + +void rotate_block(int d, bool camera_only) { + if(!rotate_allowed && !camera_only) { + playSound(cwt.at, "hit-crush3"); + return; + } + if(!camera_only) remove_shape(); + cellwalker at1 = flatspin(at, d); + if(camera_only || !shape_conflict(at1)) { + at = at1; + set_tview(spin(d*90*degree)); + } + else playSound(cwt.at, "hit-crush3"); + if(!camera_only) draw_shape(); + } + +int nilmap(int dir) { + if(nil) { + int nm[4] = {3, 2, 0, 5}; + return nm[dir]; + } + if(sol) { + int nm[4] = {11, 10, 5, 4}; + return nm[dir]; + } + exit(1); + } + +cell *shift_block_target(int dir) { + return flatspin(at, dir).cpeek(); + } + +void shift_block(int dir, bool camera_only) { + int t = currentmap->gamestart()->type; + if(prod) t -= 2; + + if(!camera_only) remove_shape(); + + cellwalker at1; + + if(solnil) { + at1.at = at.at->cmove(nilmap(dir)); + } + else if(t&1) { + if(dir == 3) + at1 = flatspin(flatspin(at, 4) + wstep, 3); + if(dir == 1) + at1 = flatspin(flatspin(at, 2) + wstep, 1); + if(dir == 2) + at1 = flatspin(flatspin(at, 3) + wstep, 4); + if(dir == 0) + at1 = flatspin(flatspin(at, 1) + wstep, 2); + } + else { + int kspin = (t/2) - dir; + at1 = flatspin(at, dir); + at1 = flatspin(at1 + wstep, kspin); + } + + ld angle = dir * 90 * degree; + + if(camera_only || !shape_conflict(at1)) { + // playSound(cwt.at, "hit-crush1"); + at = at1; + if(solnil) { + pView = pView * currentmap->adj(cwt.at, nilmap(dir)); + when_t = ticks + turn_animation; + } + else + set_tview(spin(-angle) * ypush(-move_dist) * spin(angle)); + if(!camera_only) draw_shape(); + } + else playSound(cwt.at, "hit-crush3"); + } + +void bringris_action(int k) { + if(k < 4) shift_block(k); + if(k == 4) rotate_block(1); + if(k == 5) rotate_block(-1); + if(k == 6) drop(); + if(k == 7) fulldrop(); + if(k == 8) paused = true; + } + +void create_matrices() { + dq::clear_all(); + make_actual_view(); + dq::enqueue_by_matrix_c(centerover, cview()); + current_display->all_drawn_copies.clear(); + int id = 0; + while(!dq::drawqueue_c.empty()) { + auto& p = dq::drawqueue_c.front(); + cell*& c = p.first; + shiftmatrix& V = p.second; + current_display->all_drawn_copies[c].push_back(V); + gmatrix[p.first] = p.second; + if(id < draw_per_level) { + auto go = [&] (int i) { + cell *c1 = c->cmove(i); + dq::enqueue_by_matrix_c(c1, optimized_shift(V * currentmap->adj(c, i))); + }; + if(prod) { + for(int i=0; itype-2; i++) go(i); + } + else if(sol) { + go(4); go(5); go(10); go(11); + } + else if(nil) { + go(0); go(2); go(3); go(5); + } + } + dq::drawqueue_c.pop(); + id++; + } + } + +transmatrix smooth; + +void change_depth(shiftmatrix& V, int newlevel, int zlev) { + if(solnil) { + while(newlevel > zlev) zlev++, V = V * currentmap->adj(cwt.at, down_dir()); + while(newlevel < zlev) zlev--, V = V * currentmap->adj(cwt.at, up_dir()); + } + else if(in_h2xe()) + V = shiftless(V.T, cgi.plevel * (newlevel - zlev)); + else + V = shiftless(V.T * zpush(cgi.plevel * (newlevel - zlev))); + } + +void draw_wirecube_at(cell *c, const transmatrix& rel, int zlev, color_t col) { + + auto where_c = get_where(c); + auto c_camera = get_at(where_c.first, zlev); + + for(shiftmatrix V: current_display->all_drawn_copies[c_camera]) { + change_depth(V, where_c.second, zlev); + for(int i=0; itype; i++) + queuepolyat(V * rel, cgi.shWireframe3D[i], 0, PPR::SUPERLINE).outline = col; + } + } + +void draw_piece(int zlev, int id) { + sightranges[geometry] *= 100; + initquickqueue(); + auto shape = build_from(piecelist[id].code, at); + auto matrices = build_shape_matrices(piecelist[id].code, at); + + auto where_at = get_where(at.at); + + vid.linewidth *= 3; + + int mid = 0; + + for(auto c: shape) { + auto where_c = get_where(c.at); + color_t levels[5] = {color_t(0xFFFFFFFFF), color_t(0xFFFF00FF), color_t(0xFF8000FF), color_t(0xFF0000FF), color_t(0xC000C0FF) }; + draw_wirecube_at(at.at, smooth * matrices[mid++], zlev, levels[where_at.second-where_c.second]); + } + + vid.linewidth /= 3; + + quickqueue(); + glflush(); + sightranges[geometry] /= 100; + } + +void draw_holes(int zlev) { + sightranges[geometry] *= 100; + initquickqueue(); + if(state == tsFalling) remove_shape(); + for(auto lev: level) { + bool covered = false; + for(int z=well_size; z>=1; z--) { + cell *c1 = get_at(lev, -z); + if(c1->wall) covered = true; + else if(covered) { + vid.linewidth *= 4; + draw_wirecube_at(c1, Id, zlev, (get_hipso(z) << 8) | 0xFF); + vid.linewidth /= 4; + } + } + } + if(state == tsFalling) draw_shape(); + quickqueue(); + sightranges[geometry] /= 100; + } + +void draw_all_noray(int zlev) { + sightranges[geometry] *= 100; + initquickqueue(); + for(auto lev: level) { + for(int z=0; z<=camera_level+1; z++) { + cell *c1 = get_at(lev, -z); + + if(c1->wall) { + + auto c_camera = get_at(lev, zlev); + + for(shiftmatrix V: current_display->all_drawn_copies[c_camera]) { + change_depth(V, -z, zlev); + + color_t wcol = c1->landparam; + if(c1->wall == waBarrier) wcol = winf[waBarrier].color; + + int d = (wcol & 0xF0F0F0) >> 4; + + forCellIdCM(c2, i, c1) + if(!c2->wall) { + color_t col = darkena(wcol - d * get_darkval(c1, i), 0, 0xFF); + auto &q = queuepolyat(V, cgi.shWall3D[i], col, PPR::WALL); + q.tinf = &floor_texture_vertices[cgi.shFloor.id]; + ensure_vertex_number(*q.tinf, q.cnt); + } + } + } + } + } + quickqueue(); + sightranges[geometry] /= 100; + } + +void start_new_game(); + +void draw_screen(int xstart, bool show_next) { + int steps = camera_level - (-get_z(at.at)); + if(state != tsFalling) steps = camera_level - (well_size + 1); + + dynamicval ccd(*current_display); + current_display->xmax = xstart * 1. / vid.xres; + ray::max_cells = (isize(level) + isize(out_level)) * (camera_level+2); + + if(explore) { + gamescreen(0); + mouseaim_sensitivity = 0.01; + camera_speed = 2; + smooth_scrolling = true; + } + else { + mouseaim_sensitivity = 0; + NLP = Id; + View = pView; + if(nil) { + centerover = at.at; + rotate_view(cspin(1, 2, -90*degree)); + shift_view(ztangent(3 * nilv::nilwidth)); + rotate_view(cspin(0, 1, -90*degree)); + anims::moved(); + } + else if(sol) { + centerover = at.at; + rotate_view(cspin(1, 2, 180*degree)); + shift_view(ztangent(1)); + rotate_view(cspin(0, 1, -90*degree)); + anims::moved(); + } + else { + ld lv = -cgi.plevel * steps; + shift_view(ztangent(lv)); + rotate_view(cspin(1, 2, cur_ang)); + shift_view(ztangent(cgi.plevel * (2 + max_piece))); + centerover = ncenter; + anims::moved(); + } + int zlev = get_z(centerover); + + // make_actual_view(); + // anims::moved(); + + if(state == tsCollect) for(cell *c: to_disappear) c->landparam = rand() & 0xFFFFFF; + + if(state == tsFalling && !explore && !cur_ang && !lctrlclick) remove_shape(); + // just_gmatrix = true; + + ray::want_use = use_raycaster ? 2 : 0; + if(1) { + gamescreen(0); + create_matrices(); + } + + if(!use_raycaster) + draw_all_noray(zlev); + + if(state == tsFalling && !explore && !cur_ang) draw_shape(); + + if(anyshiftclick) draw_holes(zlev); + + if(state == tsFalling && !explore && !cur_ang) draw_piece(zlev, shape_id); + + if(show_next) { + dynamicval ccd(*current_display); + current_display->xmin = (xstart + vid.fsize) * 1. / vid.xres; + current_display->xmax = (vid.xres - vid.fsize) * 1. / vid.xres; + current_display->ymin = (vid.fsize * 18) * 1. / vid.yres; + current_display->ymax = (vid.fsize * (18+8)) * 1. / vid.yres; + calcparam(); + draw_piece(zlev, next_shape_id); + } + + if(state == tsBetween) state_loop(); + + if(state == tsCollect && ticks >= move_at) + disappear_lines(); + + if(ticks >= move_at && state == tsFalling && pro_game) { + drop(); + } + + View = pView; + centerover = ncenter; + NLP = Id; + } + } + +void create_game(); + +void geometry_menu() { + clearMessages(); + dialog::init("Bringris geometries"); + dialog::addBreak(100); + for(int i=0; i g(geometry, gNormal); + saveConfig(); + }); + #endif + + dialog::addBack(); + dialog::display(); + } + +void run() { + + clearMessages(); + dialog::init(); + + if(ang < cur_ang) { + cur_ang -= (ticks - lti) / 1000.; + if(cur_ang < ang) cur_ang = ang; + } + + if(ang > cur_ang) { + cur_ang += (ticks - lti) / 1000.; + if(cur_ang > ang) cur_ang = ang; + } + + lti = ticks; + + if(explore) ; + else if(ticks > when_t) { + pView = tView; + View = pView; + smooth = Id; + } + else if(solnil) { + ld part = (ticks - last_adjust) * 1. / (when_t - last_adjust); + hyperpoint sh = pView * C0; + sh = lerp(C0, sh, 1-part); + pView = eupush(sh); + smooth = inverse(pView); + } + else { + ld part = (ticks - last_adjust) * 1. / (when_t - last_adjust); + transmatrix T = pView * inverse(tView); + hyperpoint vec = inverse_exp(shiftless(tC0(T))); + transmatrix Tspin = gpushxto0(tC0(T)) * T; + ld alpha = atan2(Tspin*xpush0(1)); + pView = spin(alpha * part) * gpushxto0(direct_exp(vec*part)) * pView; + fixmatrix(pView); + View = tView; + smooth = inverse(pView) * cview().T; + // println(hlog, "smooth = ", smooth); + } + last_adjust = ticks; + + ray::want_use = 2; + sightranges[geometry] = 50; + if(!solnil) vid.cells_drawn_limit = 1; + else vid.cells_drawn_limit = 2000; + + cmode = sm::NORMAL | sm::CENTER; + + int xstart = vid.xres - vid.fsize * 10; + + getcstat = '-'; + + bool show_next = state != tsGameover && state != tsPreGame && !paused; + + draw_screen(xstart, show_next); + + calcparam(); + for(int i=0; i 3) ax = 3; + int ay = mousey * 3 / vid.yres; + if(ay > 2) ay = 2; + int id = ay * 4 + ax; + eBringrisMove moves[12] = { + bmTurnLeft, bmUp, bmTurnRight, bmPause, + bmLeft, bmDrop, bmRight, bmFullDrop, + bmNothing, bmDown, bmNothing, bmFullDrop + }; + eBringrisMove mov = moves[id]; + if((state == tsFalling && !paused) || mov == bmPause) + bringris_action(mov); + return; + } + + // if(sym == 'k') ang = 0; + // if(sym == 'l') ang = 45 * degree; + if(sym == 'p' || sym == 'c' || (sym == SDLK_ESCAPE && !ISWEB)) { + if(!paused) move_at = move_at - ticks; + paused = !paused; + if(!paused) move_at = move_at - ticks; + explore = false; + } + if(sym == 't' && state == tsGameover) { + const vector emoji = + {"😀","😎","👽","🤖","😺","🎩","🎓","👑","💍","🐯","🦁","🐮","🐷","🐽","🐸","🐙","🐵","🐦","🐧","🐔","🐒","🙉","🙈","🐣","🐥","🐺","🐗","🐴","🦄","🐝","🐛","🐢","🦀","🦂","🕷","🐜","🐞","🐌","🐠","🐟","🐡","🐬","🐋","🐊","🐆","🐘","🐫","🐪","🐄","🐂","🐃","🐏","🐑","🐀","🐁","🐓","🦃","🐉","🐾","🐿","🐇","🐈","🐩","🐕","🐲","🌵","🍁","🌻","🌎","⭐️","⚡️","🔥","❄️","☔️","☂️","💧","🍏","🍎","🍐","🍋","🍌","🍉","🍇","🌶","🍅","🍍","🍑","🍈","🍓","🌽","🍠","🍯","🍞","🍗","🧀","🍖","🍤","🌯","🌮","🍝","🍕","🌭","🍟","🍔","⚽️","🎱","🏆","🎪","🎲","🎳","🚗","🚕","🚙","🏎","⛺️","⛩","🕹","💾","☎️","⏱","🔦","💡","💰","💎","🔨","💣","🔑","❤️","🔔"}; + + string out; + if(pro_game) { + out = "Got " + its(score) + " points for completing " + its(completed) + " levels in #Bringris!"; + } + else if(completed) { + out = "Used " + its(bricks) + " blocks to complete " + its(completed) + " levels in #Bringris!"; + } + else { + out = "Dropped " + its(bricks) + " blocks in #Bringris!"; + } + if(bgeom || max_piece != 4) out += " (" + bgeoms[bgeom].name + "/" + its(max_piece) + ")"; + unsigned hash = time(NULL) / 600; + for(char c: out) hash = 171 * hash + c; + std::mt19937 invr; + invr.seed(hash); + out += " "; + for(int i=0; i<4; i++) + out += emoji[invr() % isize(emoji)]; + // println(hlog, out); + #if ISWEB + EM_ASM({ + var tweetbegin = 'https://twitter.com/intent/tweet?text='; + var tweettxt = UTF8ToString($0, $1); + var finaltweet = tweetbegin +encodeURIComponent(tweettxt) + "&url=https://zenorogue.itch.io/bringris"; + window.open(finaltweet,'_blank'); + }, out.c_str(), isize(out)); + #endif + } + if(in_menu && sym == 'e') { + explore = !explore; + } + if(in_menu && sym == 'n') { + start_new_game(); + paused = false; + explore = false; + pro_game = false; + playSound(cwt.at, "elementalgem"); + } + if(in_menu && sym == 's') { + pushScreen(settings_menu); + } + if(in_menu && sym == 'x') { + start_new_game(); + paused = false; + explore = false; + pro_game = true; + playSound(cwt.at, "elementalgem"); + } + #if CAP_SHOT + if(sym == ']') { + static int id = 0; + perfect_linewidth = 0; + shot::shot_aa = 2; + vid.linewidth *= 2; + shot::take(format("bringris-%04d.png", id++), [] { draw_screen(vid.xres, false); }); + vid.linewidth /= 2; + } + #endif + }; + } + +cell *get_center() { + return well_center ? well_center : level[0]; + } + +void reset_view() { + centerover = get_at(get_center(), -camera_level); + cwt.at = centerover; + ncenter = get_at(get_center(), -camera_level); + + NLP = Id; + tView = Id; + + set_view(); + pView = tView; + } + +void start_new_game() { + + for(auto& p: piecelist) p.count = 0; + + for(auto lev: level) for(int z=0; z<=camera_level+1; z++) { + cell *c = get_at(lev, -z); + setdist(c, 7, nullptr); + c->item = itNone; + c->land = laCanvas; + if(z == 0) + c->wall = waBarrier, c->land = laBarrier; + else if(z <= camera_level) + c->wall = waNone; + else + c->wall = waWaxWall, c->land = laCanvas, c->landparam = 0xC000C0; + } + + for(auto lev: out_level) for(int z=1; z<=camera_level; z++) { + cell *c = get_at(lev, -z); + c->item = itNone; + c->land = laCanvas; + c->wall = waWaxWall; + c->landparam = (get_hipso(z) & 0xFCFCFC) >> 2; + } + + at = get_at(get_center(), -well_size - 1); + next_shape_id = choose_piece(); + + state = tsBetween; + + reset_view(); + + // reset_view(); + + completed = 0; + bricks = 0; + cubes = 0; + score = 0; + } + +void get_level() { + if(bflags() & BOUNDED_WELL) { + set all; + well_center = currentmap->gamestart(); + all.insert(well_center); + for(int i=0; i<4; i++) + for(int l: {-1, 0, 1}) + for(int j=0; j<4; j++) + for(int k: {-1, 1}) { + cellwalker cw(well_center, i); + cw += wstep; + all.insert(cw.at); + if(l) { + cw += l; + cw += wstep; + all.insert(cw.at); + } + cw += j; + cw += wstep; + all.insert(cw.at); + cw += k; + cw += wstep; + all.insert(cw.at); + } + set all_ext; + for(cell *c: all) + forCellCM(d, c) + if(!all.count(d)) + all_ext.insert(d); + + level.clear(); + for(auto c: all) + level.push_back(c); + for(auto c: all_ext) + out_level.push_back(c); + } + else { + level = currentmap->allcells(); + if(bflags() & ORBIFOLD) { + vector clist; + set visited; + auto visit = [&] (cell *c) { + if(!visited.count(c)) + visited.insert(c), + clist.push_back(c); + }; + for(auto c: level) if(isNeighbor(c, c)) visit(c); + for(int i=0; itype; j++) + visit(clist[i]->cmove(j)); + well_center = clist.back(); + } + } + if(well_center) { + vector visited; + set all; + for(auto l: level) all.insert(l); + auto visit = [&] (cell *c, int d) { + if(all.count(c) && !center_distance.count(c)) + center_distance[c] = d, + visited.push_back(c); + }; + visit(well_center, 0); + for(int i=0; itype; j++) + visit(visited[i]->move(j), center_distance[visited[i]] + 1); + } + } + +void create_game() { + level.clear(); + out_level.clear(); + well_center = nullptr; + + if(!prod && !solnil) { + println(hlog, "need product or Solnil geometry"); + exit(1); + } + if(nil) { + for(int x=0; x<5; x++) + for(int y=0; y<5; y++) + level.push_back(nilv::get_heptagon_at(nilv::mvec(x, 0, y))->c7); + } + else if(sol) { + level.clear(); + for(int x=0; x<5; x++) + for(int y=0; y<5; y++) + level.push_back(asonov::get_at(asonov::coord(x, y, 0))->c7); + } + else + PIU(get_level()); + piecelist.clear(); + piecelist.reserve(2000); + seen_blocks.clear(); + for(int ps=1; ps<=max_piece; ps++) + generate_shapes(ps); + list_all(); + // println(hlog, "level size = ", isize(level)); + + camera_level = well_size + max_piece + camera; + + playermoved = false; + ray::want_use = 2; + ray::exp_decay_poly = 200; + ray::max_iter_current() = solnil ? 600 : 200; + mapeditor::drawplayer = false; + // sightranges[geometry] = 1; + + + vid.fov = 90; + vid.plevel_factor = 0.5; + // vid.grid = true; + + mouseaim_sensitivity = 0; + + start_new_game(); + state = tsPreGame; + + vid.axes3 = false; + } + +void init_all() { + enable_bgeom(); + vid.texture_step = 8; + showstartmenu = false; + pushScreen(run); + } + +int args() { + using namespace arg; + + if(0) ; + + else if(argis("-list")) { + PHASEFROM(3); + start_game(); + shift(); int i = argi(); + generate_shapes(i); + list_all(); + } + + else if(argis("-bringris0")) { + PHASEFROM(3); + start_game(); + create_game(); + } + + else if(argis("-bgeo")) { + PHASEFROM(2); + shift(); + enable_bgeom(argi()); + } + + else if(argis("-bringris")) { + PHASEFROM(2); + init_all(); + } + + else if(argis("-ray-off")) + use_raycaster = false; + + else if(argis("-ray-on")) + use_raycaster = true; + + else return 1; + return 0; + } + +void change_default_key(int key, int val) { + char* t = multi::scfg.keyaction; + t[key] = val; + set_saver_default(t[key]); + } + +void default_config() { + for(int i=0; i<512; i++) + if(multi::scfg.keyaction[i] >= 16 && multi::scfg.keyaction[i] < 32) + change_default_key(i, 0); + + change_default_key('s', 16 + 0); + change_default_key('a', 16 + 1); + change_default_key('w', 16 + 2); + change_default_key('d', 16 + 3); + change_default_key('q', 16 + 4); + change_default_key('e', 16 + 5); + change_default_key(' ', 16 + 6); + change_default_key('\r',16 + 7); + change_default_key('p', 16 + 8); + + addsaver(bgeom, "bringris-geometry"); + addsaver(use_raycaster, "bringris-ray"); + addsaver(draw_per_level, "draw-per-level"); + } + +auto hooks = + addHook(hooks_args, 100, args) + + addHook(hooks_configfile, 100, default_config) + + addHook(dialog::hooks_display_dialog, 100, [] () { + if(dialog::items[0].body == "Bringris keys") { + dialog::addBreak(200); + if(!rotate_allowed) + dialog::addHelp("note: rotation keys only available when necessary"); + dialog::addHelp("press SHIFT to highlight the holes"); + dialog::addHelp("mouse control by pressing parts of the game screen"); + } + }); + +#ifdef BRINGRIS +auto hook1= + addHook(hooks_config, 100, [] { + if(arg::curphase == 1) + conffile = "bringris.ini"; + if(arg::curphase == 2) init_all(); + }); +#endif + +} +} diff --git a/rogueviz/janko.cpp b/rogueviz/janko.cpp index 5bcf64f0..79a8939a 100644 --- a/rogueviz/janko.cpp +++ b/rogueviz/janko.cpp @@ -29,7 +29,7 @@ struct jmatrix : array, 7> { }; vector jms; -std::unordered_map ids; +std::map ids; jmatrix J, Z, id; diff --git a/rogueviz/magmahep.cpp b/rogueviz/magmahep.cpp index 3258470f..6b45175d 100644 --- a/rogueviz/magmahep.cpp +++ b/rogueviz/magmahep.cpp @@ -74,7 +74,7 @@ void make() { for(int i=0; i, int> counts; + map, int> counts; int big = v - 2; diff --git a/rogueviz/newconf.cpp b/rogueviz/newconf.cpp index 1a3b1a36..e7d84b97 100644 --- a/rogueviz/newconf.cpp +++ b/rogueviz/newconf.cpp @@ -11,7 +11,7 @@ #endif #define main nconf_main -#undef unordered_map +#undef map #undef self #include "nconf.cpp" #undef main diff --git a/rogueviz/rogueviz.cpp b/rogueviz/rogueviz.cpp index e4be7ce9..4cfa6b6f 100644 --- a/rogueviz/rogueviz.cpp +++ b/rogueviz/rogueviz.cpp @@ -546,7 +546,7 @@ void queuedisk(const shiftmatrix& V, const colorpair& cp, bool legend, const str } } -unordered_map, int> drawn_edges; +map, int> drawn_edges; map, transmatrix> relmatrices; diff --git a/rogueviz/starbattle.cpp b/rogueviz/starbattle.cpp index 03e8fd94..bc163832 100644 --- a/rogueviz/starbattle.cpp +++ b/rogueviz/starbattle.cpp @@ -360,7 +360,7 @@ void starbattle_puzzle() { } keyhandler = [] (int sym, int uni) { - handlePanning(sym, uni); + if(!dialog_shown) handlePanning(sym, uni); dialog::handleNavigation(sym, uni); if(among(sym, '-', SDLK_F1) && !holdmouse) push_stop(); diff --git a/rogueviz/subquotient.cpp b/rogueviz/subquotient.cpp new file mode 100644 index 00000000..da2b0577 --- /dev/null +++ b/rogueviz/subquotient.cpp @@ -0,0 +1,179 @@ +#include "../hyper.h" +#include +#include +#include + +namespace hr { + +namespace subquotient { + +eGeometry gSubquotient(eGeometry(-1)); + +vector connections; + +void create_subquotient(int qty = -1, int id = 0) { + start_game(); + auto ac = currentmap->allcells(); + auto ca = currentmap->gamestart(); + + for(auto cb: ac) for(int i=0; itype; i++) for(int m=0; m<2; m++) { + vector visited; + map vmap; + + auto visit = [&] (cell *da, cellwalker db) { + if(vmap.count(da)) { + // println(hlog, da, " -> ", db, " [old]"); + return; + } + // println(hlog, da, " -> ", db, " [new]"); + vmap[da] = db; + visited.emplace_back(da); + }; + + visit(ca, cellwalker(cb, i, m)); + + for(int i=0; itype; j++) { + cellwalker wa(visited[i], 0); + cellwalker wb(vmap[visited[i]]); + wa += j; + wb += j; + wa += wstep; + wb += wstep; + int r = wa.spin; + wa -= r; + wb -= r; + // println(hlog, wa, " -> ", wb); + setdist(wa.at, 7, nullptr); + wa.at->item = itGold; + visit(wa.at, wb); + } + } + + int vertex = 0, edge = 0, badcycle = 0; + + map by_cycle; + + for(auto swb: vmap) { + auto& s = swb.first; + auto& wb = swb.second; + if(s == wb.at) { vertex++; continue; } + bool is_edge = false; + for(int j=0; jtype; j++) if(s->move(j) == wb.at && (wb+j).peek() == s) + is_edge = true; + if(is_edge) { edge++; continue; } + int cs = 0; + cell *sx = s; + auto cw = cellwalker(s, 0); + vector lst; + do { + int sp = cw.spin; + bool mirr = cw.mirrored; + if(cw.mirrored) sp = -sp; + cw -= sp; + lst.push_back(sx); + cw = vmap[sx]; + if(mirr) cw += wmirror; + sx = cw.at; + cw += sp; + cs++; + if(cs >= 100) break; + } + while(sx != s); + if(cw.spin) badcycle++; + by_cycle[cs]++; + } + + if(vertex || edge || badcycle || m == 0) continue; + + vector> bcp; + for(auto b: by_cycle) bcp.push_back(b); + + if(qty == -1) + println(hlog, "m=", m, " vertex/edge = ", tie(vertex, edge), " badcycle = ", badcycle, " by_cycle = ", bcp); + + if(by_cycle[qty] == isize(vmap)) { + if(id > 0) {id--; continue; } + + map ids; + int next_id = 0; + vector by_id; + + set visited; + + for(auto s: ac) if(!visited.count(s)) { + by_id.push_back(s); + ids[s] = next_id; + auto sx = s; + do { + visited.insert(sx); + sx = vmap[sx].at; + } + while(sx != s); + next_id++; + } + + println(hlog, "ids = ", next_id); + connections.clear(); + if(int(gSubquotient) == -1) { + ginf.push_back(ginf[geometry]); + gSubquotient = eGeometry(isize(ginf) - 1); + } + + ginf[gSubquotient] = ginf[geometry]; + /* we need to be 'pure', unrectified may not work */ + if(UNRECTIFIED) swap(ginf[gSubquotient].sides, ginf[gSubquotient].vertex); + + for(int i=0; itype; j++) { + cellwalker cw(s, j); + cw += wstep; + int res; + while(!ids.count(cw.at)) { + int sp = cw.spin; + bool flip = cw.mirrored; + if(flip) sp = -sp; + cw -= sp; + if(cw.spin) println(hlog, "bad spin"); + cw = vmap[cw.at]; + if(flip) cw += wmirror; + cw += sp; + } + res = ids[cw.at] * s->type + cw.spin; + if(cw.mirrored) res |= quotientspace::symmask; + connections.push_back(res); + } + } + + stop_game(); + set_geometry(gSubquotient); + variation = eVariation::pure; + println(hlog, "variation = ", int(variation)); + start_game(); + println(hlog, "started"); + return; + } + } + } + +int readArgs() { + using namespace arg; + + if(0) ; + else if(argis("-subquotient")) { start_game(); shift(); create_subquotient(argi()); } + + else return 1; + return 0; + } + +auto fundamentalhook = addHook(hooks_args, 100, readArgs) + + addHook(hooks_newmap, 0, [] { + if(geometry == gSubquotient) + return (hrmap*) new quotientspace::hrmap_quotient(connections); + return (hrmap*) nullptr; + }); + +} + +} \ No newline at end of file diff --git a/rug.cpp b/rug.cpp index 0f994779..33b1ad6b 100644 --- a/rug.cpp +++ b/rug.cpp @@ -631,7 +631,7 @@ bool force(rugpoint& m1, rugpoint& m2, double rd, bool is_anticusp=false, double transmatrix iT = rgpushxto0(m1.native); - for(int i=0; i dm(pmodel, mdJoukowskyInverted); dynamicval dt(pconf.model_orientation, ticks / 25.); @@ -1874,6 +1875,7 @@ startanim spin_around { "spinning around", no_init, [] { dynamicval dv(View, spin(-cos_auto(circle_radius)*alpha) * xpush(circle_radius) * spin(alpha) * View); gamescreen(2); }}; +#endif reaction_t add_to_frame; diff --git a/shaders.cpp b/shaders.cpp index b44f9a23..39daf1e2 100644 --- a/shaders.cpp +++ b/shaders.cpp @@ -60,6 +60,23 @@ glhr::glmatrix model_orientation_gl() { return s; } +EX void reset_all_shaders() { + ray::reset_raycaster(); + compiled_programs.clear(); + matched_programs.clear(); + } + +EX string panini_shader() { + return + "t.w += 1.; t *= 2. / t.w; t.w -= 1.;\n" + "float s = t.z;\n" + "float l = length(t.xyz);\n" + "t /= max(length(t.xz), 1e-2);\n" + "t.z += " + glhr::to_glsl(panini_alpha) + ";\n" + "t *= l;\n" + "t.w = 1.;\n"; + } + shared_ptr write_shader(flagtype shader_flags) { string varying, vsh, fsh, vmain = "void main() {\n", fmain = "void main() {\n"; @@ -332,6 +349,14 @@ shared_ptr write_shader(flagtype shader_flags) { } if(shader_flags & GF_LEVELS) vmain += "vPos = t;\n"; if(treset) vmain += "t[3] = 1.0;\n"; + + if(WDIM == 3 && panini_alpha) { + vmain += "t = uPP * t;", vsh += "uniform mediump mat4 uPP;"; + /* panini */ + vmain += panini_shader(); + shader_flags |= SF_ORIENT; + } + vmain += "gl_Position = uP * t;\n"; } @@ -485,7 +510,8 @@ void display_data::set_projection(int ed, ld shift) { for(int i=0; i<3; i++) NLP[3][i] = NLP[i][3] = 0; NLP[3][3] = 1; } - glhr::projection_multiply(glhr::tmtogl_transpose(NLP)); + if(!(shader_flags & SF_ORIENT)) + glhr::projection_multiply(glhr::tmtogl_transpose(NLP)); } if(ed) { glhr::using_eyeshift = true; @@ -520,6 +546,9 @@ void display_data::set_projection(int ed, ld shift) { if(get_shader_flags() & SF_USE_ALPHA) pp[3][2] = GLfloat(pconf.alpha); + if(nisot::local_perspective_used()) + pp = glhr::tmtogl_transpose(NLP) * pp; + if(get_shader_flags() & SF_ORIENT) { if(GDIM == 3) for(int a=0; a<4; a++) models::apply_orientation_yz(pp[a][1], pp[a][2]); @@ -547,6 +576,10 @@ void display_data::set_projection(int ed, ld shift) { glhr::projection_multiply(glhr::translate(shift, 0, 0)); } + if(in_h2xe() || in_s2xe()) { + glhr::projection_multiply(glhr::translate(0, 0, shift)); + } + if(selected->shader_flags & SF_HALFPLANE) { glhr::projection_multiply(glhr::translate(0, 1, 0)); glhr::projection_multiply(glhr::scale(-1, 1, 1)); @@ -608,7 +641,7 @@ EX void glapplymatrix(const transmatrix& V) { GLfloat mat[16]; int id = 0; - if(MDIM == 3) { + if(MXDIM == 3) { for(int y=0; y<3; y++) { for(int x=0; x<3; x++) mat[id++] = V[x][y]; mat[id++] = 0; diff --git a/shmup.cpp b/shmup.cpp index 0f6f7c2c..3db3c7f5 100644 --- a/shmup.cpp +++ b/shmup.cpp @@ -1167,8 +1167,10 @@ void movePlayer(monster *m, int delta) { cwt.at = c2; afterplayermoved(); if(c2->item && c2->land == laAlchemist) c2->wall = m->base->wall; + #if CAP_COMPLEX2 if(m->base->wall == waRoundTable) camelot::roundTableMessage(c2); + #endif if(c2->wall == waCloud || c2->wall == waMirror) { visibleFor(500); cellwalker cw(c2, 0, false); @@ -1189,7 +1191,9 @@ void movePlayer(monster *m, int delta) { items[itOrbLife] = 0; m->dead = true; } + #if CAP_COMPLEX2 mine::uncover_full(c2); + #endif if(isWatery(c2) && isWatery(m->base) && m->inBoat) moveItem(m->base, c2, true); @@ -2523,7 +2527,7 @@ EX void turn(int delta) { if(doall) for(cell *c: currentmap->allcells()) activateMonstersAt(c); else - for(unordered_map::iterator it = gmatrix.begin(); it != gmatrix.end(); it++) + for(map::iterator it = gmatrix.begin(); it != gmatrix.end(); it++) activateMonstersAt(it->first); /* printf("size: gmatrix = %ld, active = %ld, monstersAt = %ld, delta = %d\n", @@ -2635,7 +2639,9 @@ EX void turn(int delta) { #if CAP_INV if(inv::on) inv::compute(); #endif + #if CAP_COMPLEX2 terracotta::check(); + #endif heat::processfires(); if(havewhat&HF_WHIRLPOOL) whirlpool::move(); if(havewhat&HF_WHIRLWIND) whirlwind::move(); diff --git a/sky.cpp b/sky.cpp index 0e01a12a..286aae33 100644 --- a/sky.cpp +++ b/sky.cpp @@ -55,10 +55,7 @@ void dqi_sky::draw() { int sk = get_skybrightness(); - unordered_map> colors; - #ifdef USE_UNORDERED_MAP - colors.reserve(isize(sky)); - #endif + map> colors; for(sky_item& si: sky) colors[si.c] = make_pair(darkena(gradient(0, si.color, 0, sk, 255), 0, 0xFF), darkena(si.skycolor, 0, 0xFF) @@ -238,6 +235,7 @@ void celldrawer::draw_ceiling() { break; case laVariant: { + #if CAP_COMPLEX2 int b = getBits(c); col = 0x404040; for(int a=0; a<21; a++) @@ -245,6 +243,7 @@ void celldrawer::draw_ceiling() { col += variant::features[a].color_change; col = col & 0x00FF00; skycol = col; + #endif break; } diff --git a/sphere.cpp b/sphere.cpp index 987484f9..67d31abd 100644 --- a/sphere.cpp +++ b/sphere.cpp @@ -161,7 +161,9 @@ struct hrmap_spherical : hrmap_standard { transmatrix relative_matrix(cell *c2, cell *c1, const hyperpoint& hint) { if(!gmatrix0.count(c2) || !gmatrix0.count(c1)) { + #if !ISWEB printf("building gmatrix0 (size=%d)\n", isize(gmatrix0)); + #endif #if CAP_GP auto bak = gp::draw_li; #endif diff --git a/sysconfig.h b/sysconfig.h index 075579ce..eedff12a 100644 --- a/sysconfig.h +++ b/sysconfig.h @@ -185,6 +185,10 @@ #define MAXMDIM 4 #endif +#ifndef CAP_MDIM_FIXED +#define CAP_MDIM_FIXED 0 +#endif + #ifndef CAP_TEXTURE #define CAP_TEXTURE (CAP_GL && (CAP_PNG || CAP_SDL_IMG) && !ISMINI) #endif @@ -206,7 +210,7 @@ #endif #ifndef CAP_TOUR -#define CAP_TOUR (!ISWEB && !ISMINI) +#define CAP_TOUR (!ISMINI) #endif #ifndef CAP_ROGUEVIZ @@ -285,7 +289,7 @@ #endif #ifndef CAP_SHMUP -#define CAP_SHMUP 1 +#define CAP_SHMUP (!ISWEB) #endif #ifndef CAP_BITFIELD @@ -403,6 +407,10 @@ extern "C" { #define CAP_GLEW (CAP_GL && !ISMOBILE && !ISMAC && !ISLINUX && !ISWEB) #endif +#if ISWEB +#define GLES_ONLY +#endif + #if CAP_GL #if CAP_GLEW #include @@ -462,6 +470,11 @@ typedef unsigned GLuint; #include #endif +#if ISWEB +#include +#include +#endif + #if CAP_GMP #include #endif @@ -478,14 +491,6 @@ typedef unsigned GLuint; #endif #endif -#ifdef USE_UNORDERED_MAP -#include -#include -#else -#define unordered_map map -#define unordered_set set -#endif - #include #if ISWINDOWS @@ -573,7 +578,7 @@ union SDL_Event; #endif #ifndef CAP_RAY -#define CAP_RAY (MAXMDIM >= 4 && !ISWEB && !ISMOBILE && CAP_GL) +#define CAP_RAY (MAXMDIM >= 4 && CAP_GL) #endif #ifndef CAP_MEMORY_RESERVE diff --git a/system.cpp b/system.cpp index 2e1d4536..942cc744 100644 --- a/system.cpp +++ b/system.cpp @@ -194,7 +194,9 @@ EX void initgame() { cwt.at = currentmap->gamestart(); cwt.spin = 0; cwt.mirrored = false; cwt.at->land = firstland; + #if CAP_COMPLEX2 if(firstland == laBrownian) brownian::init(cwt.at); + #endif chaosAchieved = false; @@ -214,7 +216,9 @@ EX void initgame() { } if((tactic::on || yendor::on || peace::on) && isCyclic(firstland)) { + #if CAP_COMPLEX2 camelot::anthraxBonus = items[itHolyGrail]; + #endif cwt.at->move(0)->land = firstland; if(firstland == laWhirlpool) cwt.at->move(0)->wall = waSea; @@ -366,7 +370,9 @@ EX void initgame() { #if CAP_INV if(inv::on) inv::init(); #endif +#if CAP_COMPLEX2 mine::auto_teleport_charges(); +#endif if(!use_special_land) { if(firstland != (princess::challenge ? laPalace : laIce)) cheater++; } @@ -1265,7 +1271,9 @@ EX void stop_game() { princess::reviveAt = 0; princess::forceVizier = false; princess::forceMouse = false; + #if CAP_COMPLEX2 camelot::knighted = 0; + #endif // items[itGreenStone] = 100; clearMemory(); game_active = false; @@ -1287,7 +1295,6 @@ EX void set_geometry(eGeometry target) { bool was_default = pmodel == default_model(); callhooks(hooks_on_geometry_change); if(geometry != target) { - int old_DIM = GDIM; stop_game(); ors::reset(); if(among(target, gProduct, gRotSpace)) { @@ -1318,14 +1325,11 @@ EX void set_geometry(eGeometry target) { if(bt::in() || WDIM == 3 || kite::in() || arb::in()) if(!hybri) variation = eVariation::pure; #endif if(S3 >= OINF) variation = eVariation::pure; - if(INVERSE) variation = gp::variation_for(gp::param); + if(INVERSE && !hybri) variation = gp::variation_for(gp::param); if(ginf[target].default_variation == eVariation::pure && geometry != gArchimedean) variation = eVariation::pure; if(was_default) pmodel = default_model(); - if(nonisotropic && old_DIM == 2 && vid.texture_step < 4) vid.texture_step = 4; if(WDIM == 2 && (cgflags & qIDEAL) && vid.always3 && vid.texture_step < 32) vid.texture_step = 32; - if(prod) { pmodel = mdPerspective; if(vid.texture_step < 4) vid.texture_step = 4; } - if(WDIM == 3 && (cgflags & qIDEAL) && vid.texture_step < 4) vid.texture_step = 4; if(sl2) nisot::geodesic_movement = true; if(rotspace) { @@ -1495,7 +1499,9 @@ EX void start_game() { ignored_memory_warning = false; check_cgi(); cgi.require_basics(); + #if CAP_ARCM arcm::current.compute_geometry(); + #endif initcells(); expansion.reset(); diff --git a/tour.cpp b/tour.cpp index f7c1ec05..415218b2 100644 --- a/tour.cpp +++ b/tour.cpp @@ -7,6 +7,13 @@ #include "hyper.h" namespace hr { + +#if !CAP_TOUR +EX namespace tour { + EX always_false on; +EX } +#endif + #if CAP_TOUR /** \brief Variables and function related to Guided Tour and other presentations. */ diff --git a/util.cpp b/util.cpp index 2055dd2a..32748e62 100644 --- a/util.cpp +++ b/util.cpp @@ -263,6 +263,7 @@ cld exp_parser::parse(int prio) { force_eat(")"); res = edge_of_triangle_with_angles(M_PI/2, M_PI/a, M_PI/b); } + #if CAP_ARCM else if(eat("arcmedge(")) { vector vals; vals.push_back(iparse(0)); @@ -280,6 +281,7 @@ cld exp_parser::parse(int prio) { if(extra_params.count("distunit")) res /= extra_params["distunit"]; } + #endif else if(eat("regangle(")) { cld edgelen = parse(0); if(extra_params.count("distunit")) { @@ -373,6 +375,7 @@ cld exp_parser::parse(int prio) { else if(number == "psl_steps") res = cgi.psl_steps; else if(number == "single_step") res = cgi.single_step; else if(number == "step") res = hdist0(tC0(currentmap->adj(cwt.at, 0))); + else if(number == "edgelen") res = hdist(get_corner_position(cwt.at, 0), get_corner_position(cwt.at, 1)); else if(number == "mousey") res = mousey; else if(number == "random") res = randd(); else if(number == "mousez") res = cld(mousex - current_display->xcenter, mousey - current_display->ycenter) / cld(current_display->radius, 0); diff --git a/yendor.cpp b/yendor.cpp index bf50b872..5865ff91 100644 --- a/yendor.cpp +++ b/yendor.cpp @@ -1063,7 +1063,11 @@ void save_mode_data(hstream& f) { f.write(chaosmode); f.write(shmup::on); f.write(inv::on); + #if CAP_TOUR f.write(tour::on); + #else + f.write(false); + #endif f.write(peace::on); f.write(peace::otherpuzzles); f.write(peace::explore_other);