diff --git a/achievement.cpp b/achievement.cpp index 21d2e79c..0a6aaeb5 100644 --- a/achievement.cpp +++ b/achievement.cpp @@ -307,7 +307,7 @@ EX void achievement_collection2(eItem it, int q) { if(it == itHolyGrail) { if(q == 1) achievement_gain("GRAIL2"); if(PURE && geometry == gNormal) - achievement_gain("GRAILH", rg::special_geometry); + achievement_gain("GRAILH", rg::special_geometry_nicewalls); #if CAP_CRYSTAL if(PURE && cryst && ginf[gCrystal].sides == 8 && ginf[gCrystal].vertex == 4 && !crystal::used_compass_inside) achievement_gain("GRAIL4D", rg::special_geometry); diff --git a/bigstuff.cpp b/bigstuff.cpp index c319500d..a9dd50ee 100644 --- a/bigstuff.cpp +++ b/bigstuff.cpp @@ -24,6 +24,11 @@ EX int newRoundTableRadius() { } #if CAP_COMPLEX2 +/** should we generate 'Castle Anthrax' instead of Camelot (an infinite sequence of horocyclic Camelot-likes */ +EX bool anthrax() { + return ls::single() && hyperbolic && !cryst; + } + EX int getAnthraxData(cell *c, bool b) { int d = celldistAlt(c); int rad = 28 + 3 * camelot::anthraxBonus; @@ -62,7 +67,7 @@ EX int celldistAltRelative(cell *c) { return celldist(c) - 3; } #if CAP_COMPLEX2 - if(ls::single()) return getAnthraxData(c, false); + if(anthrax()) return getAnthraxData(c, false); #endif return celldistAlt(c) - roundTableRadius(c); } @@ -1777,7 +1782,7 @@ EX eMonster camelot_monster() { EX void buildCamelot(cell *c) { int d = celldistAltRelative(c); - if(ls::single() || (d <= 14 && roundTableRadius(c) > 20)) { + if(anthrax() || (d <= 14 && roundTableRadius(c) > 20)) { gen_alt(c); preventbarriers(c); if(d == 10) { @@ -1817,7 +1822,8 @@ EX void buildCamelot(cell *c) { } } if(d == 0) c->wall = waRoundTable; - if(celldistAlt(c) == 0 && !ls::single()) c->item = itHolyGrail; + if(celldistAlt(c) == 0 && !anthrax()) println(hlog, "placed Holy Grail on ", c); + if(celldistAlt(c) == 0 && !anthrax()) c->item = itHolyGrail; if(d < 0 && hrand(7000) <= 10 + items[itHolyGrail] * 5) c->monst = camelot_monster(); if(d == 1) { @@ -1829,12 +1835,12 @@ EX void buildCamelot(cell *c) { if(c->move(i) && celldistAltRelative(c->move(i)) < d) c->mondir = (i+3) % 6; } - if(ls::single() && d >= 2 && d <= 8 && hrand(1000) < 10) + if(anthrax() && d >= 2 && d <= 8 && hrand(1000) < 10) c->item = itOrbSafety; - if(d == 5 && ls::single()) + if(d == 5 && anthrax()) c->item = itGreenStone; if(d <= 10) c->land = laCamelot; - if(d > 10 && !eubinary && !ls::single()) { + if(d > 10 && !eubinary && !anthrax()) { setland(c, eLand(altmap::orig_land(c->master->alt->alt))); if(c->land == laNone) printf("Camelot\n"); // NONEDEBUG } diff --git a/cell.cpp b/cell.cpp index 2f64e3bb..5d07129b 100644 --- a/cell.cpp +++ b/cell.cpp @@ -1293,6 +1293,12 @@ EX vector build_shortest_path(cell *c1, cell *c2) { } EX void clearCellMemory() { + #if MAXMDIM >= 4 + if(intra::in) { + intra::erase_all_maps(); + return; + } + #endif for(int i=0; iis_editable = true; this->min_value = min_value; this->max_value = max_value; this->menu_item_name = menu_item_name; @@ -2890,7 +2891,7 @@ void list_setting::show_edit_option(char key) { int q = isize(options); for(int i=0; i max_angle) walking::eye_angle = max_angle; + if(walking::eye_angle < -max_angle) walking::eye_angle = -max_angle; + } + else if(keep_vertical()) { hyperpoint vv = vertical_vector(); ld alpha = -atan2(vv[2], vv[1]); rotate_view(cspin(2, 1, alpha)); - ld max_angle = quarter_circle - 1e-4; if(dir == 1 && alpha + val > max_angle) val = max_angle - alpha; if(dir == 1 && alpha + val < -max_angle) diff --git a/crystal.cpp b/crystal.cpp index b3336630..732a506b 100644 --- a/crystal.cpp +++ b/crystal.cpp @@ -686,8 +686,8 @@ struct hrmap_crystal : hrmap_standard { } transmatrix relative_matrixh(heptagon *h2, heptagon *h1, const hyperpoint& hint) override { - if(!crystal3()) return hrmap::relative_matrixh(h2, h1, hint); - return relative_matrix(h2->c7, h1->c7, hint); + if(!crystal3()) return hrmap_standard::relative_matrixh(h2, h1, hint); + return relative_matrixc(h2->c7, h1->c7, hint); } #endif }; diff --git a/fake.cpp b/fake.cpp index 2e6f9ce4..9a3f4174 100644 --- a/fake.cpp +++ b/fake.cpp @@ -373,12 +373,16 @@ EX namespace fake { transmatrix ray_iadj(cell *c, int i) override { if(WDIM == 2) return to_other_side(get_corner(c, i), get_corner(c, i+1)); + #if MAXMDIM >= 4 if(PURE) return iadj(c, i); auto& v = get_cellshape(c).faces_local[i]; hyperpoint h = project_on_triangle(v[0], v[1], v[2]); transmatrix T = rspintox(h); return T * xpush(-2*hdist0(h)) * spintox(h); + #else + return Id; + #endif } }; diff --git a/geom-exp.cpp b/geom-exp.cpp index 44769f69..ec9924d5 100644 --- a/geom-exp.cpp +++ b/geom-exp.cpp @@ -1270,6 +1270,7 @@ int read_geom_args() { PHASEFROM(2); set_variation(eVariation::warped); } + #if MAXMDIM >= 4 else if(argis("-subcubes")) { PHASEFROM(2); stop_game(); @@ -1301,6 +1302,7 @@ int read_geom_args() { shift(); reg3::coxeter_param = argi(); } #endif + #endif #if CAP_FIELD else if(argis("-fi")) { fieldpattern::info(); diff --git a/geometry.cpp b/geometry.cpp index 3a8a8b7c..f9432e9a 100644 --- a/geometry.cpp +++ b/geometry.cpp @@ -744,10 +744,12 @@ void geometry_information::prepare_basics() { base_distlimit = arb::current.range; } + #if MAXMDIM >= 4 if(is_subcube_based(variation)) { scalefactor /= reg3::subcube_count; orbsize /= reg3::subcube_count; } + #endif if(scale_used()) { scalefactor *= vid.creature_scale; @@ -1142,8 +1144,10 @@ 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 MAXMDIM >= 4 if(is_subcube_based(variation)) V("SC", its(reg3::subcube_count)); if(variation == eVariation::coxeter) V("COX", its(reg3::coxeter_param)); + #endif #if CAP_ARCM if(arcm::in()) V("ARCM", arcm::current.symbol); @@ -1198,6 +1202,12 @@ EX string cgi_string() { return s; } +#if MAXMDIM >= 4 +#define IFINTRA(x,y) x +#else +#define IFINTRA(x,y) y +#endif + EX void check_cgi() { string s = cgi_string(); @@ -1207,7 +1217,7 @@ EX void check_cgi() { if(fake::in()) fake::underlying_cgip->timestamp = ntimestamp; if(arcm::alt_cgip) arcm::alt_cgip->timestamp = ntimestamp; - if(isize(cgis) > 4 && intra::data.empty()) { + if(isize(cgis) > 4 && IFINTRA(intra::data.empty(), true)) { vector> timestamps; for(auto& t: cgis) timestamps.emplace_back(-t.second.timestamp, t.first); sort(timestamps.begin(), timestamps.end()); diff --git a/goldberg.cpp b/goldberg.cpp index cc26d9c5..d3cc3d6e 100644 --- a/goldberg.cpp +++ b/goldberg.cpp @@ -1162,6 +1162,7 @@ EX namespace gp { return S3 == 3 ? XLAT("chamfered") : XLAT("expanded"); else if(GOLDBERG && param == loc(3, 0) && S3 == 3) return XLAT("2x bitruncated"); + #if MAXMDIM >= 4 else if(variation == eVariation::subcubes) return XLAT("subcubed") + "(" + its(reg3::subcube_count) + ")"; else if(variation == eVariation::dual_subcubes) @@ -1170,6 +1171,7 @@ EX namespace gp { return XLAT("bitruncated-subcubed") + "(" + its(reg3::subcube_count) + ")"; else if(variation == eVariation::coxeter) return XLAT("subdivided") + "(" + its(reg3::coxeter_param) + ")"; + #endif else { auto p = human_representation(param); string s = "GP(" + its(p.first) + "," + its(p.second) + ")"; diff --git a/graph.cpp b/graph.cpp index 9d48710e..16ecd733 100644 --- a/graph.cpp +++ b/graph.cpp @@ -3808,10 +3808,12 @@ EX int get_darkval(cell *c, int d) { const int darkval_kite[12] = {0, 2, 0, 2, 4, 4, 6, 6, 6, 6, 6, 6}; const int darkval_nil[8] = {6,6,0,3,6,6,0,3}; const int darkval_nih[11] = {0,2,0,2,4,6,6,6,6,6,6}; + #if MAXMDIM >= 4 if(among(variation, eVariation::dual_subcubes, eVariation::bch, eVariation::bch_oct, eVariation::coxeter)) { int v = reg3::get_face_vertex_count(c, d); return v-3; } + #endif if(sphere) return darkval_s12[d]; if(euclid && S7 == 6) return darkval_e6[d]; if(euclid && S7 == 12) return darkval_e12[d]; diff --git a/hyper.h b/hyper.h index edc8c652..beaf7b6c 100644 --- a/hyper.h +++ b/hyper.h @@ -13,8 +13,8 @@ #define _HYPER_H_ // version numbers -#define VER "12.0j" -#define VERNUM_HEX 0xA90A +#define VER "12.0k" +#define VERNUM_HEX 0xA90B #include "sysconfig.h" diff --git a/hyperpoint.cpp b/hyperpoint.cpp index 3947a2ce..0a550e80 100644 --- a/hyperpoint.cpp +++ b/hyperpoint.cpp @@ -466,8 +466,8 @@ EX transmatrix to_other_side(hyperpoint h1, hyperpoint h2) { /** @brief positive for a material vertex, 0 for ideal vertex, negative for ultra-ideal vertex */ EX ld material(const hyperpoint& h) { - if(sphere) return intval(h, Hypc); - else if(hyperbolic) return -intval(h, Hypc); + if(sphere || in_s2xe()) return intval(h, Hypc); + else if(hyperbolic || in_h2xe()) return -intval(h, Hypc); else if(sl2) return h[2]*h[2] + h[3]*h[3] - h[0]*h[0] - h[1]*h[1]; else return h[LDIM]; } @@ -505,7 +505,7 @@ EX hyperpoint normalize(hyperpoint H) { /** like normalize but makes (ultra)ideal points material */ EX hyperpoint ultra_normalize(hyperpoint H) { if(material(H) <= 0) { - H[LDIM] = hypot_d(LDIM, H) + 1e-6; + H[LDIM] = hypot_d(LDIM, H) + 1e-10; } return normalize(H); } @@ -760,7 +760,7 @@ EX hyperpoint parabolic13(hyperpoint h) { EX transmatrix spintoc(const hyperpoint& H, int t, int f) { transmatrix T = Id; ld R = hypot(H[f], H[t]); - if(R >= 1e-12) { + if(R >= 1e-15) { T[t][t] = +H[t]/R; T[t][f] = +H[f]/R; T[f][t] = -H[f]/R; T[f][f] = +H[t]/R; } @@ -774,7 +774,7 @@ EX transmatrix spintoc(const hyperpoint& H, int t, int f) { EX transmatrix rspintoc(const hyperpoint& H, int t, int f) { transmatrix T = Id; ld R = hypot(H[f], H[t]); - if(R >= 1e-12) { + if(R >= 1e-15) { T[t][t] = +H[t]/R; T[t][f] = -H[f]/R; T[f][t] = +H[f]/R; T[f][f] = +H[t]/R; } @@ -853,7 +853,7 @@ EX transmatrix ggpushxto0(const hyperpoint& H, ld co) { return mscale(PIU(ggpushxto0(d.second, co)), d.first * co); } transmatrix res = Id; - if(sqhypot_d(GDIM, H) < 1e-12) return res; + if(sqhypot_d(GDIM, H) < 1e-16) return res; ld fac = -curvature()/(H[LDIM]+1); for(int i=0; i 1-1e-6 && zlev < 1+1e-6) return 1; + if(zlev > 1-1e-9 && zlev < 1+1e-9) return 1; H /= zlev; return zlev; } @@ -628,9 +628,9 @@ EX void apply_other_model(shiftpoint H_orig, hyperpoint& ret, eModel md) { ret[0] = -models::osin - H[0]; ld height = 0; if(zlev != 1) { - if(abs(models::ocos) > 1e-5) + if(abs(models::ocos) > 1e-9) height += H[1] * (pow(zlev, models::ocos) - 1); - if(abs(models::ocos) > 1e-5 && models::osin) + if(abs(models::ocos) > 1e-9 && models::osin) height += H[0] * models::osin * (pow(zlev, models::ocos) - 1) / models::ocos; else if(models::osin) height += H[0] * models::osin * log(zlev); @@ -2013,6 +2013,8 @@ EX void optimizeview() { fixmatrix(View); callhooks(hooks_postoptimize); + walking::handle(); + if(is_boundary(centerover)) centerover = c, View = oView; else @@ -2756,7 +2758,7 @@ EX bool do_draw(cell *c) { // do not display not fully generated cells, unless changing range allowed if(c->mpdist > 7 && !allowChangeRange()) return false; // in the Yendor Challenge, scrolling back is forbidden - if(c->cpdist > 7 && (yendor::on || isHaunted(cwt.at->land)) && !cheater && !autocheat) return false; + if(c->cpdist > get_sightrange() && (yendor::on || isHaunted(cwt.at->land)) && !cheater && !autocheat) return false; return true; } @@ -2907,7 +2909,9 @@ EX void shift_view(hyperpoint H) { static bool recursive = false; if(!recursive && intra::in) { dynamicval r(recursive, true); + #if MAXMDIM >= 4 intra::shift_view_portal(H); + #endif return; } View = get_shift_view_of(H, View); diff --git a/intra.cpp b/intra.cpp index 02f5bcc0..9215ab43 100644 --- a/intra.cpp +++ b/intra.cpp @@ -6,6 +6,7 @@ EX namespace intra { EX bool in; +#if MAXMDIM >= 4 #if HDR /** information per every space connected with intra-portals */ struct intra_data { @@ -68,9 +69,9 @@ hyperpoint portal_data::to_poco(hyperpoint h) const { ld z = product_decompose(h).first; h /= exp(z); auto h1 = h; - h[0] = asin_auto(h1[1]); + h[2] = asin_auto_clamp(h1[0]); h[1] = z; - h[2] = asin_auto_clamp(h1[0] / cos_auto(h[0])); + h[0] = asin_auto_clamp(h1[1] / cos_auto(h[2])); h[3] = 1; return h; } @@ -90,6 +91,10 @@ hyperpoint portal_data::to_poco(hyperpoint h) const { else { h = T * h; h /= h[3]; + if(sphere) + h[2] /= sqrt(1+h[0]*h[0]+h[1]*h[1]); + if(hyperbolic) + h[2] /= sqrt(1-h[0]*h[0]-h[1]*h[1]); return h; } } @@ -108,8 +113,8 @@ hyperpoint portal_data::from_poco(hyperpoint h) const { } else if(prod && kind == 0) { auto h0 = h; - h[0] = sin_auto(h0[2]) * cos_auto(h0[0]); - h[1] = sin_auto(h0[0]); + h[0] = sin_auto(h0[2]); + h[1] = sin_auto(h0[0]) * cos_auto(h0[2]); h[2] = cos_auto(h0[0]) * cos_auto(h0[2]); h[3] = 1; return iT * h * exp(h0[1]); @@ -125,13 +130,17 @@ hyperpoint portal_data::from_poco(hyperpoint h) const { } else { h[3] = 1; + if(sphere) + h[2] *= sqrt(1+h[0]*h[0]+h[1]*h[1]); + if(hyperbolic) + h[2] *= sqrt(1-h[0]*h[0]-h[1]*h[1]); return normalize(iT * h); } } EX portal_data make_portal(cellwalker cw, int spin) { - if(debug_portal & 33) - println(hlog, "working in: ", full_geometry_name()); + if(debug_portal & 289) + println(hlog, "working in: ", full_geometry_name(), " wall no ", cw.spin, "/", cw.at->type); auto& ss = currentmap->get_cellshape(cw.at); auto fac = ss.faces_local[cw.spin]; portal_data id; @@ -252,6 +261,23 @@ EX portal_data make_portal(cellwalker cw, int spin) { println(hlog, "chosen edge is ", first, "--", second); } + if(debug_portal & 256) { + println(hlog, "portal scale = ", id.scale); + auto res = [&] (ld x, ld y, ld z) { + hyperpoint h = hyperpoint(x, y, z, 1); + return id.from_poco(h); + }; + for(int x=0; x<5; x++) { + println(hlog, "horizontal ", x, " = ", hdist(res(x*.1,0,0), res(x*.1+.001,0,0))); + println(hlog, "vertical ", x, " = ", hdist(res(x*.1,0,0), res(x*.1,0.001,0))); + println(hlog, "deep ", x, " = ", hdist(res(x*.1,0,0), res(x*.1,0,0.001))); + } + hyperpoint a = hyperpoint(.4, .2, .1, 1); + println(hlog, "a = ", a); + println(hlog, "b = ", id.from_poco(a)); + println(hlog, "c = ", id.to_poco(id.from_poco(a))); + } + if(debug_portal & 1) { for(auto p: fac) { auto p2 = id.to_poco(p); @@ -497,7 +523,7 @@ EX void shift_view_portal(hyperpoint H) { println(hlog, "maxv = ", maxv); shift_view(H * maxv); check_portal_movement(); - shift_view(H * (1 - maxv)); + shift_view_portal(H * (1 - maxv)); } EX const connection_data* through_portal() { @@ -515,11 +541,9 @@ EX const connection_data* through_portal() { EX void check_portal_movement() { auto p = through_portal(); - ld c = camera_speed; + if(p) { ld eps = 1e-5; - c /= p->id1.scale; - anims::cycle_length /= p->id1.scale; ld ss = pow(eps, -2); array ds; /* camera, forward, upward */ @@ -579,9 +603,17 @@ EX void check_portal_movement() { println(hlog, "goal: at = ", xds[0], " det = ", dsdet(xds), " bt = ", bt::minkowski_to_bt(xds[0])); } - c *= p->id2.scale; - anims::cycle_length *= p->id2.scale; - camera_speed = c; + ld scale = p->id2.scale / p->id1.scale; + + camera_speed *= scale; + anims::cycle_length *= scale; + #if CAP_VR + vrhr::absolute_unit_in_meters *= scale; + #endif + if(walking::eye_level != -1) walking::eye_level *= scale; + + walking::floor_dir = -1; + walking::on_floor_of = nullptr; } } @@ -595,7 +627,7 @@ void erase_unconnected(cellwalker cw) { int edit_spin; -void show_portals() { +EX void show_portals() { gamescreen(1); dialog::init(XLAT("manage portals")); @@ -665,6 +697,8 @@ void show_portals() { } } + walking::add_options(); + dialog::display(); } @@ -729,6 +763,22 @@ EX void kill(int id) { println(hlog, isize(to_remove), " connections and ", isize(to_erase_cell), " cells erased"); } +EX void erase_all_maps() { + println(hlog, "erase_all_maps called"); + data[current].gd.storegame(); + in = false; + for(int i=0; i need_to_save; EX void prepare_need_to_save() { @@ -764,8 +814,232 @@ auto hooks1 = arg::shift(); int i = arg::argi(); be_ratio_edge(i); }) + arg::add3("-debug-portal", [] { arg::shift(); debug_portal = arg::argi(); }); - - +#endif EX } -} \ No newline at end of file +EX namespace walking { + +EX bool on; + +EX bool auto_eyelevel; + +EX int floor_dir = -1; +EX cell *on_floor_of = nullptr; +EX ld eye_level = 0.2174492; +EX ld eye_angle = 0; +EX ld eye_angle_scale = 1; + +int ticks_end, ticks_last; + +EX set colors_of_floors; + +EX bool isFloor(cell *c) { + if(!isWall(c)) return false; + if(colors_of_floors.empty()) return true; + if(c->wall != waWaxWall) return false; + return colors_of_floors.count(c->landparam); + } + +EX void handle() { + if(!on) return; + + if(floor_dir == -1 || on_floor_of != centerover) { + vector choices; + for(int i=0; itype; i++) + if(isFloor(centerover->cmove(i))) + choices.push_back(i); + + if(sol && isize(choices) == 2) choices.pop_back(); + + if(isize(choices) == 1) { + on_floor_of = centerover; + floor_dir = choices[0]; + } + else if(colors_of_floors.empty() && sn::in()) { + on_floor_of = centerover; + auto z = inverse(View) * C0; + switch(geometry) { + case gSol: + floor_dir = (z[2] > 0) ? 2 : 6; + return; + case gNIH: + floor_dir = (z[2] > 0) ? 5 : 4; + return; + case gSolN: + floor_dir = (z[2] > 0) ? 4 : 6; + return; + default: throw hr_exception("not solnihv"); + } + } + else if(colors_of_floors.empty() && hyperbolic && bt::in()) { + auto z = bt::minkowski_to_bt(inverse(View) * C0); + on_floor_of = centerover; + floor_dir = z[2] > 0 ? bt::updir() : 0; + println(hlog, "set floor_dir to ", floor_dir); + } + else { + println(hlog, "there are ", isize(choices), " choices for floor_dir"); + if(!on_floor_of) return; + } + } + + struct face { + hyperpoint h0, hx, hy; + }; + + transmatrix ToOld = currentmap->relative_matrix(on_floor_of, centerover, C0); + auto& csh = currentmap->get_cellshape(on_floor_of); + face f; + f.h0 = ToOld * csh.faces_local[floor_dir][0]; + f.hx = ToOld * csh.faces_local[floor_dir][1]; + f.hy = ToOld * csh.faces_local[floor_dir][2]; + + auto find_nearest = [&] (const face& fac, hyperpoint at) { + if(sol) { at[2] = fac.h0[2]; return at; } + else if(hyperbolic && bt::in()) { + auto z = bt::minkowski_to_bt(at); + z[2] = bt::minkowski_to_bt(fac.h0)[2]; + return bt::bt_to_minkowski(z); + } + else if(prod && bt::in()) { + auto dec = product_decompose(at); + hyperpoint dep = PIU( deparabolic13(dec.second) ); + hyperpoint h = product_decompose(fac.h0).second; + h = PIU( deparabolic13(h) ); + dep[0] = h[0]; + return zshift(PIU(parabolic13(dep)), dec.first); + } + else { + transmatrix M = ray::mirrorize(currentmap->ray_iadj(on_floor_of, floor_dir)); + M = ToOld * M * inverse(ToOld); + return mid(at, M * at); + } + }; + + hyperpoint at = tC0(inverse(View)); + if(invalid_point(at)) { + println(hlog, "at is invalid!"); + on = false; + return; + } + + auto wallpt = find_nearest(f, at); + + ld view_eps = 1e-5; + + transmatrix spin_T; + bool use_T = false; + + if(eye_angle) use_T = true, spin_T = cspin(1, 2, -eye_angle * degree); + #if CAP_VR + if(vrhr::active() && !vrhr::first && vrhr::hsm != vrhr::eHeadset::none) { + use_T = true; + spin_T = vrhr::hmd_ref_at; + // print(hlog, "HMD seems to be at altitude ", spin_T[1][3], " depth ", spin_T[2][3], " zeros are ", spin_T[3][1], " and ", spin_T[3][2]); + dynamicval g(geometry, gCubeTiling); + spin_T = vrhr::sm * inverse(spin_T); + eye_level = -spin_T[1][3] / vrhr::absolute_unit_in_meters; + if(eye_level < .001) eye_level = 0.001; + vrhr::be_33(spin_T); + } + #endif + + if(use_T) rotate_view(spin_T); + hyperpoint front = inverse(get_shift_view_of(ctangent(2, -view_eps), View)) * C0; + hyperpoint up = inverse(get_shift_view_of(ctangent(1, +view_eps), View)) * C0; + + auto fwallpt = find_nearest(f, front); + + transmatrix T = nonisotropic ? nisot::translate(wallpt, -1) : gpushxto0(wallpt); + hyperpoint dx = inverse_exp(shiftless(T * at)); + + transmatrix Tf = nonisotropic ? nisot::translate(fwallpt, -1) : gpushxto0(fwallpt); + hyperpoint dxf = inverse_exp(shiftless(Tf * front)); + + if(eye_level == -1) eye_level = hypot_d(3, dx); + + auto smooth = [&] (hyperpoint h1, hyperpoint h2) { + if(ticks < ticks_end) { + ld last_t = ilerp(ticks_end-1000, ticks_end, ticks_last); + ld curr_t = ilerp(ticks_end-1000, ticks_end, ticks); + last_t = last_t * last_t * (3-2*last_t); + curr_t = curr_t * curr_t * (3-2*curr_t); + ld t = ilerp(last_t, 1, curr_t); + return lerp(h1, h2, t); + } + return h2; + }; + + auto oView = View; + set_view( + smooth(at, inverse(T) * direct_exp(dx / hypot_d(3, dx) * eye_level)), + smooth(front, inverse(Tf) * direct_exp(dxf / hypot_d(3, dxf) * eye_level)), + smooth(up, inverse(T) * direct_exp(dx / hypot_d(3, dx) * (eye_level + view_eps))) + ); + if(use_T) rotate_view(inverse(spin_T)); + playermoved = false; + + auto nat = tC0(inverse(View)); + if(invalid_point(nat)) { + println(hlog, "at is invalid after fixing!"); + View = oView; + return; + } + + ticks_last = ticks; + } + +EX void add_options() { + dialog::addBoolItem("walking mode", on, 'w'); + dialog::add_action([] { + on = !on; + if(on && auto_eyelevel) eye_level = -1; + floor_dir = -1; + on_floor_of = nullptr; + ticks_last = ticks; + ticks_end = ticks + 1000; + }); + add_edit(eye_level); + add_edit(eye_angle); + if(point_direction >= 0 && point_direction < centerover->type) { + cell *c = centerover->move(point_direction); + if(c && c->wall == waWaxWall) { + color_t col = c->landparam; + dialog::addBoolItem("we are facing floor (color " + format("%06X", col) + ")", colors_of_floors.count(col), 'n'); + dialog::add_action([col] { + if(colors_of_floors.count(col)) colors_of_floors.erase(col); + else colors_of_floors.insert(col); + }); + } + } + } + +auto a = addHook(hooks_configfile, 100, [] { + param_b(auto_eyelevel, "auto_eyelevel") + -> editable("keep eye level when walking enabled", 'L'); + param_f(eye_level, "eye_level") + -> editable(0, 5, .1, "walking eye level", + "Distance from the floor to the eye in the walking mode, in absolute units. In VR this is adjusted automatically.", + 'e') + ->set_extra([] { add_edit(auto_eyelevel); }); + param_f(eye_angle, "eye_angle") + -> editable(-90, 90, 15, "walking eye angle", + "0 = looking forward, 90 = looking upward. In VR this is adjusted automatically.", + 'k') + ->set_extra([] { add_edit(eye_angle_scale); }); + param_f(eye_angle_scale, "eye_angle_scale") + -> editable(-2, 0, 2, "eye angle scale", + "1 = the angle can be changed with keyboard or mouse movements, 0 = the angle is fixed", + 'k'); + }) + + addHook(hooks_clearmemory, 40, [] { on_floor_of = nullptr; floor_dir = -1; }) + + arg::add3("-walk-on", [] { + on = true; + if(auto_eyelevel) eye_level = -1; + floor_dir = -1; + on_floor_of = nullptr; + ticks_last = ticks_end = ticks; + }); + +EX } +} diff --git a/landgen.cpp b/landgen.cpp index 5aac0a28..d94cb9f4 100644 --- a/landgen.cpp +++ b/landgen.cpp @@ -2588,7 +2588,7 @@ EX void giantLandSwitch(cell *c, int d, cell *from) { bool locked = true; forCellEx(c1, c) if(!c1->wall) locked = false; - if(locked) c->item = itEclectic; + if(locked && !safety) c->item = itEclectic; if(c->wall == waNone && hrand_monster(2500) < 30 + items[itEclectic] + yendor::hardness() && !safety) gen_eclectic_monster(c); diff --git a/legacy.cpp b/legacy.cpp index 56253264..9961abb3 100644 --- a/legacy.cpp +++ b/legacy.cpp @@ -253,7 +253,7 @@ EX bool legacy_racing() { EX bool rcheck(string which, int qty, int x) { return hrand(qty) < x; - }; + } EX int wallchance_legacy(cell *c, bool deepOcean) { eLand l = c->land; diff --git a/mapeditor.cpp b/mapeditor.cpp index 2aebea61..3c7442ed 100644 --- a/mapeditor.cpp +++ b/mapeditor.cpp @@ -449,12 +449,14 @@ EX namespace mapstream { f.write(gp::param.second); } #endif + #if MAXMDIM >= 4 if(variation == eVariation::coxeter) { f.write(reg3::coxeter_param); } if(is_subcube_based(variation )) { f.write(reg3::subcube_count); } + #endif #if CAP_FIELD if(geometry == gFieldQuotient) { using namespace fieldpattern; @@ -541,12 +543,14 @@ EX namespace mapstream { f.read(gp::param.second); } #endif + #if MAXMDIM >= 4 if(variation == eVariation::coxeter && vernum >= 0xA908) { f.read(reg3::coxeter_param); } if(is_subcube_based(variation) && vernum >= 0xA908) { f.read(reg3::subcube_count); } + #endif #if CAP_CRYSTAL if(cryst && vernum >= 10504) { int sides; @@ -702,7 +706,9 @@ EX namespace mapstream { } addToQueue(save_start()); + #if MAXMDIM >= 4 if(intra::in) intra::prepare_need_to_save(); + #endif for(int i=0; iwparam); f.write(c->landparam); f.write_char(c->stuntime); f.write_char(c->hitpoints); bool blocked = false; + #if MAXMDIM >= 4 if(intra::in && isWall3(c) && !intra::need_to_save.count(c)) blocked = true; + #endif if(!blocked) for(int j=0; jtype; j++) { cell *c2 = c->move(j); @@ -756,6 +764,12 @@ EX namespace mapstream { int32_t n = -1; f.write(n); int32_t id = cellids.count(cwt.at) ? cellids[cwt.at] : -1; f.write(id); + + if(f.vernum >= 0xA90C) { + vector v; + for(auto c: walking::colors_of_floors) v.push_back(c); + f.write(v); + } save_drawing_tool(f); @@ -771,6 +785,7 @@ EX namespace mapstream { for(int i=0; i= 4 if(intra::in) { for(int i=0; i(0); } + #endif callhooks(hooks_savemap, f); f.write(0); @@ -970,6 +986,13 @@ EX namespace mapstream { savecount = 0; savetime = 0; cheater = 1; + if(f.vernum >= 0xA90C) { + vector v; + f.read(v); + walking::colors_of_floors.clear(); + for(auto c: v) walking::colors_of_floors.insert(c); + } + load_drawing_tool(f); dynamicval a3(vid.always3, vid.always3); @@ -1002,6 +1025,7 @@ EX namespace mapstream { } } + #if MAXMDIM >= 4 if(intra::in) { while(true) { char k = f.get(); @@ -1015,6 +1039,7 @@ EX namespace mapstream { cw.spin = fixspin(relspin[id], spin, cw.at->type, f.vernum); } } + #endif if(f.vernum >= 0xA848) { int i; @@ -1060,9 +1085,14 @@ EX namespace mapstream { if(!f.f) return false; f.write(f.vernum); f.write(dual::state); + #if MAXMDIM >= 4 int q = intra::in ? isize(intra::data) : 0; f.write(q); + #else + int q = 0; + #endif if(q) { + #if MAXMDIM >= 4 intra::prepare_to_save(); int qp = isize(intra::portals_to_save); f.write(qp); @@ -1072,6 +1102,7 @@ EX namespace mapstream { } intra::resetter ir; for(int i=0; i= 4 intra::portals_to_save.resize(qp); for(auto& ps: intra::portals_to_save) { f.read(ps.spin); @@ -1110,12 +1142,14 @@ EX namespace mapstream { } for(int i=0; icpdist == 1 && (items[itOrb37] || !nonAdjacent(cf,ct)) && markOrb(itOrbBeauty) && !isFriendly(ct)) + adj = true; + + if(!adj && items[itOrbEmpathy] && items[itOrbBeauty] && !isFriendly(ct)) { + for(int i=0; itype; i++) if(ct->move(i) && isFriendly(ct->move(i))) + adj = true, markOrb(itOrbEmpathy), markOrb(itOrbBeauty); + } + + if(adj && ct->stuntime == 0 && !isMimic(m)) { + ct->stuntime = 2; + checkStunKill(ct); + } + } + EX void moveMonster(const movei& mi) { auto& cf = mi.s; auto& ct = mi.t; @@ -254,20 +270,8 @@ EX void moveMonster(const movei& mi) { if(m == moWitchFire) makeflame(cf, 10, false); if(m == moFireElemental) { makeflame(cf, 20, false); if(cf->wparam < 20) cf->wparam = 20; } - bool adj = false; - if(ct->cpdist == 1 && (items[itOrb37] || !nonAdjacent(cf,ct)) && markOrb(itOrbBeauty) && !isFriendly(ct)) - adj = true; - - if(!adj && items[itOrbEmpathy] && items[itOrbBeauty] && !isFriendly(ct)) { - for(int i=0; itype; i++) if(ct->move(i) && isFriendly(ct->move(i))) - adj = true, markOrb(itOrbEmpathy), markOrb(itOrbBeauty); - } + check_beauty(ct, cf, m); - if(adj && ct->stuntime == 0 && !isMimic(m)) { - ct->stuntime = 2; - checkStunKill(ct); - } - if(!cellEdgeUnstable(ct)) { if(isMetalBeast(m)) ct->stuntime += 2; if(m == moTortoise) ct->stuntime += 3; @@ -1487,6 +1491,8 @@ EX void moveshadow() { where->stuntime = 0; // the Shadow sets off the mines and stuff moveEffect(movei(where, where, NODIR), moShadow); + // Beauty kills the Shadow + check_beauty(where, where, moShadow); } } } diff --git a/multigame.cpp b/multigame.cpp index 2fc692ce..d4730eb3 100644 --- a/multigame.cpp +++ b/multigame.cpp @@ -64,7 +64,11 @@ void gamedata_all(gamedata& gd) { gd.store(hybrid::underlying); gd.store(hybrid::csteps); gd.store(hybrid::underlying_cgip); - gd.store_ptr(vid); + gd.store_ptr(vid.projection_config); + gd.store_ptr(vid.rug_config); + gd.store(vid.yshift); + gd.store(vid.plevel_factor); + gd.store(vid.binary_width); gd.store(sightrange_bonus); gd.store(genrange_bonus); gd.store(gamerange_bonus); @@ -179,8 +183,7 @@ EX namespace dual { dynamicval dm(dual::state, 2); int cg = currently_loaded; - bool orbusedbak[ittypes]; - for(int i=0; i orbused; +EX array lastorbused; EX bool markOrb(eItem it) { if(!items[it]) return false; @@ -314,6 +316,7 @@ EX bool distanceBound(cell *c1, cell *c2, int d) { EX void checkFreedom(cell *cf) { manual_celllister cl; + dynamicval d(orbused); cl.add(cf); for(int i=0; imonst == moShadow) { + addMessage(XLAT("%The1 is destroyed!", dest->monst)); + killMonster(dest, moNone); + } /* if(!isPermanentFlying(dest->monst) && cellEdgeUnstable(dest)) { addMessage(XLAT("%The1 falls!", dest->monst)); fallMonster(dest); @@ -1598,6 +1605,15 @@ EX eItem targetRangedOrb(cell *c, orbAction a) { return itNone; } +bool isValentines() { + const time_t now = time(NULL); + const struct tm *datetime = localtime(&now); + + // 0-indexed tm_mon, 1-indexed tm_mday + // So this is February (2nd month), and the 14th day. + return datetime->tm_mon == 1 && datetime->tm_mday == 14; +} + EX int orbcharges(eItem it) { switch(it) { case itRevolver: //pickup-key @@ -1607,6 +1623,7 @@ EX int orbcharges(eItem it) { case itOrbDiscord: return inv::on ? 46 : 23; case itOrbLove: + return isValentines() ? 31 : 30; case itOrbUndeath: case itOrbSpeed: //"pickup-speed"); case itOrbInvis: diff --git a/pcmove.cpp b/pcmove.cpp index 4a61912c..4ad53de0 100644 --- a/pcmove.cpp +++ b/pcmove.cpp @@ -576,6 +576,9 @@ void apply_chaos() { if (cb->wall == waStone) destroyTrapsAround(cb); changes.ccell(ca); changes.ccell(cb); + /* needs to be called separately for Shadows */ + if(ca->monst == moShadow) checkStunKill(ca); + if(cb->monst == moShadow) checkStunKill(cb); gcell coa = *ca; gcell cob = *cb; if(ca->monst != cb->monst) @@ -588,10 +591,14 @@ void apply_chaos() { copy_metadata(cb, &coa); if(!switch_lhu_in(ca->land)) ca->LHU = coa.LHU; if(!switch_lhu_in(cb->land)) cb->LHU = cob.LHU; - if(ca->monst && !(isFriendly(ca) && markOrb(itOrbEmpathy))) + if(ca->monst && !(isFriendly(ca) && markOrb(itOrbEmpathy))) { ca->stuntime = min(ca->stuntime + 3, 15), markOrb(itOrbChaos); - if(cb->monst && !(isFriendly(cb) && markOrb(itOrbEmpathy))) + checkStunKill(ca); + } + if(cb->monst && !(isFriendly(cb) && markOrb(itOrbEmpathy))) { cb->stuntime = min(cb->stuntime + 3, 15), markOrb(itOrbChaos); + checkStunKill(cb); + } ca->monmirror = !ca->monmirror; cb->monmirror = !cb->monmirror; ca->mondir = chaos_mirror_dir(ca->mondir, wb, wa); @@ -855,7 +862,7 @@ bool pcmove::after_escape() { if(attackable && fmsAttack && !dont_attack && !items[itCurseWeakness]) { if(checkNeedMove(checkonly, true)) return false; nextmovetype = nm ? lmAttack : lmSkip; - if(c2->wall == waSmallTree) { + if(c2->wall == waSmallTree || (c2->wall == waBigTree && markOrb(itOrbSlaying))) { drawParticles(c2, winf[c2->wall].color, 4); addMessage(XLAT("You chop down the tree.")); playSound(c2, "hit-axe" + pick123()); diff --git a/raycaster.cpp b/raycaster.cpp index 5256bf2c..066bf7db 100644 --- a/raycaster.cpp +++ b/raycaster.cpp @@ -451,6 +451,7 @@ void raygen::compute_which_and_dist(int flat1, int flat2) { "mediump float zsgn = (Mt > 0. ? -sgn : sgn);\n" "mediump float u = sqrt(b*b-c)*zsgn + b;\n" "mediump float v = -(Mp*u-1.) / Mt;\n" + "if(a < 1e-5) v = (1.-Mp*Mp) / (2. * Mt);\n" "mediump float d = asinh(v);\n"; if(prod) fmain += "d /= xspeed;\n"; fmain += @@ -1575,22 +1576,28 @@ void raygen::add_functions() { add_if("to_poco_h3", "mediump vec4 to_poco_h3(mediump vec4 pos) {\n" - " return pos / pos[3];\n" + " pos = pos / pos[3];\n" + " pos[2] /= sqrt(1.-pos.x*pos.x-pos.y*pos.y);\n" + " return pos;\n" " }\n\n"); add_if("from_poco_h3", "mediump vec4 from_poco_h3(mediump vec4 pos) {\n" + " pos[2] *= sqrt(1.-pos.x*pos.x-pos.y*pos.y);\n" " float s = 1. - dot(pos.xyz, pos.xyz);\n" " return pos / sqrt(s);\n" " }\n\n"); add_if("to_poco_s3", "mediump vec4 to_poco_s3(mediump vec4 pos) {\n" - " return pos / pos[3];\n" + " pos = pos / pos[3];\n" + " pos[2] /= sqrt(1.+pos.x*pos.x+pos.y*pos.y);\n" + " return pos;\n" " }\n\n"); add_if("from_poco_s3", "mediump vec4 from_poco_s3(mediump vec4 pos) {\n" + " pos[2] *= sqrt(1.+pos.x*pos.x+pos.y*pos.y);\n" " float s = 1. + dot(pos.xyz, pos.xyz);\n" " return pos / sqrt(s);\n" " }\n\n"); @@ -1623,13 +1630,13 @@ void raygen::add_functions() { add_if("from_poco_h2xr_e", "mediump vec4 from_poco_h2xr_e(mediump vec4 pos) {\n" - " return vec4(sinh(pos[2]) * cosh(pos[0]), sinh(pos[0]), cosh(pos[0]) * cosh(pos[2]), 0);\n" + " return vec4(sinh(pos[2]), sinh(pos[0]) * cosh(pos[2]), cosh(pos[0]) * cosh(pos[2]), 0);\n" " }\n\n"); add_if("to_poco_h2xr_e", "mediump vec4 to_poco_h2xr_e(mediump vec4 pos) {\n" - " mediump float x = asinh(pos[1]);\n" - " return vec4(x, 0, asinh(pos[0] / cosh(x)), 1);\n" + " mediump float x = asinh(pos[0]);\n" + " return vec4(asinh(pos[1] / cosh(x)), 0, x, 1);\n" " }\n\n"); add_if("from_poco_s2xr_s", @@ -1648,13 +1655,13 @@ void raygen::add_functions() { add_if("from_poco_s2xr_e", "mediump vec4 from_poco_s2xr_e(mediump vec4 pos) {\n" - " return vec4(sin(pos[2]) * cos(pos[0]), sin(pos[0]), cos(pos[0]) * cos(pos[2]), 0);\n" + " return vec4(sin(pos[2]), sin(pos[0]) * cos(pos[2]), cos(pos[0]) * cos(pos[2]), 0);\n" " }\n\n"); add_if("to_poco_s2xr_e", "mediump vec4 to_poco_s2xr_e(mediump vec4 pos) {\n" - " mediump float x = asin_clamp(pos[1]);\n" - " return vec4(x, 0, asin_clamp(pos[0] / cos(x)), 1);\n" + " mediump float x = asin_clamp(pos[0]);\n" + " return vec4(asin_clamp(pos[1] / cos(x)), 0, x, 1);\n" " }\n\n"); add_if("deparabolic12", @@ -2059,7 +2066,7 @@ void uniform2(GLint id, array fl) { color_t color_out_of_range = 0x0F0800FF; -transmatrix get_ms(cell *c, int a, bool mirror) { +EX transmatrix get_ms(cell *c, int a, bool mirror) { int z = a ? 1 : -1; if(c->type == 3) { @@ -2089,7 +2096,7 @@ transmatrix get_ms(cell *c, int a, bool mirror) { int nesting; -transmatrix mirrorize(transmatrix T) { +EX transmatrix mirrorize(transmatrix T) { T = inverse(T); hyperpoint h = tC0(T); ld d = hdist0(h); @@ -2486,24 +2493,27 @@ EX void cast() { #if CAP_VR if(o->uEyeShift != -1) { + dynamicval g(geometry, gCubeTiling); transmatrix T = vrhr::eyeshift; if(nonisotropic) T = inverse(NLP) * T; - glUniformMatrix4fv(o->uEyeShift, 1, 0, glhr::tmtogl_transpose3(T).as_array()); + glUniformMatrix4fv(o->uEyeShift, 1, 0, glhr::tmtogl_transpose(T).as_array()); glUniform1f(o->uAbsUnit, vrhr::absolute_unit_in_meters); } if(vrhr::rendering_eye()) { - glUniformMatrix4fv(o->uProjection, 1, 0, glhr::tmtogl_transpose3(vrhr::eyeproj).as_array()); + dynamicval g(geometry, gCubeTiling); + glUniformMatrix4fv(o->uProjection, 1, 0, glhr::tmtogl_transpose(vrhr::eyeproj).as_array()); } #else if(0) ; #endif else { + dynamicval g(geometry, gCubeTiling); transmatrix proj = Id; proj = eupush(-global_projection * d, 0) * proj; proj = euscale(cd->tanfov / (vid.stereo_mode == sLR ? 2 : 1), cd->tanfov * cd->ysize / cd->xsize) * proj; proj = eupush(-((cd->xcenter-cd->xtop)*2./cd->xsize - 1), -((cd->ycenter-cd->ytop)*2./cd->ysize - 1)) * proj; - glUniformMatrix4fv(o->uProjection, 1, 0, glhr::tmtogl_transpose3(proj).as_array()); + glUniformMatrix4fv(o->uProjection, 1, 0, glhr::tmtogl_transpose(proj).as_array()); } if(!callhandlers(false, hooks_rayset, o)) { diff --git a/rogueviz/gobot.cpp b/rogueviz/gobot.cpp index 41a2debb..92aba055 100644 --- a/rogueviz/gobot.cpp +++ b/rogueviz/gobot.cpp @@ -18,7 +18,7 @@ #include #endif -#include "../hyper.h" +#include "rogueviz.h" namespace hr { @@ -44,6 +44,8 @@ int labels_value = 1; vector history; +bool draw_go(cell *c, const shiftmatrix& V); + void init_go() { ac = currentmap->allcells(); current.taken.resize(isize(ac), 2); @@ -52,6 +54,7 @@ void init_go() { current.captures[1] = 0; for(int i=0; i& v) { + println(hlog, "called with s='", s, "'"); + if(s != "portal") return; + + using namespace tour; + auto load = [] (string s, ld x, int y) { + return [s, x, y] { + slide_backup(vid.cells_drawn_limit, 100); + slide_backup(smooth_scrolling, true); + slide_backup(ray::max_cells, 999999); + slide_backup(walking::on, true); + slide_backup(walking::eye_level, x); + mapstream::loadMap(s); + slide_backup(ray::fixed_map, true); + slide_backup(ray::max_iter_intra, y); + }; + }; + + auto add = [&] (string s, string desc, string youtube, string twitter, reaction_t loader) { + v.push_back(tour::slide{ + s, 10, tour::LEGAL::NONE | tour::QUICKSKIP | tour::QUICKGEO, desc, + [=] (tour::presmode mode) { + setCanvas(mode, '0'); + if(youtube != "") + slide_url(mode, 'y', "YouTube link", youtube); + if(twitter != "") + slide_url(mode, 't', "Twitter link", twitter); + slide_action(mode, 'r', "run this visualization", loader); + if(mode == tour::pmKey) pushScreen(intra::show_portals); + } + }); + }; + + add("inter-geometric portals", + "In this world we can find portals between six different geometries. The camera is in 'walking mode' i.e. restricted to keep close to the floor (this can be disabled with '5').", + "https://youtu.be/yqUv2JO2BCs", "https://twitter.com/ZenoRogue/status/1496867204419452935", + load("portalscene3.lev", 0.2174492, 600) + ); + add("curved landscape", + "Here we create portals between Solv and H3 geometries, resulting in a scene looking a bit like a curved landscape.", + "", "https://twitter.com/ZenoRogue/status/1446127100516130826", + load("solv-h3-scene.lev", 0.05, 3000)); + })); } diff --git a/rogueviz/notknot.cpp b/rogueviz/notknot.cpp index c1a6ef30..ad5d918d 100644 --- a/rogueviz/notknot.cpp +++ b/rogueviz/notknot.cpp @@ -1417,6 +1417,85 @@ void nk_launch() { #endif } +void portal_slideshow(tour::ss::slideshow_callback cb) { + + using namespace rogueviz::pres; + using namespace tour; + static vector portal_slides; + + if(portal_slides.empty()) { + + portal_slides.emplace_back(slide{"portal collection", 100, LEGAL::NONE | QUICKSKIP, + "This is a collection of portals. We start with knotted portals in Euclidean geometry, " + "then we visit portals in other geometries, and finally, we explore portals between different " + "geometries.\n\nLoading these may take some time, so you need to press 'r' to run them. In most slides you can also press '5' to change various parameters.", + [] (presmode mode) {} + }); + + auto add = [&] (string s, string text, string youtube, reaction_t act) { + + portal_slides.emplace_back( + tour::slide{s, 100, LEGAL::NONE | QUICKGEO | QUICKSKIP, text, + [=] (presmode mode) { + setCanvas(mode, '0'); + if(youtube != "") + slide_url(mode, 'y', "YouTube link", youtube); + slide_action(mode, 'r', "run", [=] { + slide_backup(margin); + slide_backup(mapeditor::drawplayer); + slide_backup(firstland); + slide_backup(specialland); + slide_backup(ray::max_cells, 600000); + slide_backup(smooth_scrolling, 1); + slide_backup(camera_speed, 10); + slide_backup(ray::exp_decay_poly, 30); + slide_backup(ray::fixed_map, true); + slide_backup(ray::max_iter_iso, 80); + #if CAP_VR + slide_backup(vrhr::hsm); + slide_backup(vrhr::eyes); + slide_backup(vrhr::cscr); + slide_backup(vrhr::absolute_unit_in_meters); + #endif + + on_restore([] { nilv::set_flags(); asonov::set_flags(); }); + + slide_backup(nilv::nilwidth); + slide_backup(nilv::nilperiod); + + slide_backup(vid.binary_width); + slide_backup(asonov::period_xy); + slide_backup(asonov::period_z); + + act(); + start_game(); + loop = 2; + }); + + if(mode == tour::pmKey) pushScreen(show); + }}); + }; + + auto launch_euc_with = [] (bool b) { + return [b] { + self_hiding = b; + launch_euc(); + }; + }; + add("knotted portal", "This is a knotted portal in Euclidean space.", "https://www.youtube.com/watch?v=eb2DhCcGH7U", launch_euc_with(false)); + add("self-hiding portal", "This knotted portal is 'self-hiding'. It appears that the portal enters itself and disappears!", "https://www.youtube.com/watch?v=vFLZ2NGtuGw", launch_euc_with(true)); + add("non-Euclidean portal in Nil", "A portal in Nil geometry.", "https://www.youtube.com/watch?v=2K-v8tK68AE", launch_nil); + add("spherical portal", "A portal in spherical geometry. Such a portal lets us create a space with spherical geometry that has more volume than the sphere.", "https://www.youtube.com/watch?v=PerPeQFu5gw", launch_sphereknot); + add("Cat Portal in Solv", "A portal in Solv geometry. The honeycomb is based on the mapping torus of Arnold's cat mapping.", "https://www.youtube.com/watch?v=CGiSxC9B6i0", launch_solv); + + callhooks(rogueviz::pres::hooks_build_rvtour, "portal", portal_slides); + + add_end(portal_slides); + } + + cb(XLAT("portal collection"), &portal_slides[0], 'p'); + } + auto shot_hooks = addHook(hooks_initialize, 100, create_notknot) + addHook(hooks_welcome_message, 100, [] { if(geometry == gNotKnot) { @@ -1496,59 +1575,9 @@ auto shot_hooks = addHook(hooks_initialize, 100, create_notknot) param_i(loop_any, "nk_loopany"); }) #ifndef NOTKNOT - + addHook_rvslides(180, [] (string s, vector& v) { - if(s != "mixed") return; - v.push_back(tour::slide{ - "weird portals", 10, tour::LEGAL::NONE | tour::QUICKSKIP, - "Some experiments with weird portals. Press '5' to change between available experiments.\n" - , - [] (tour::presmode mode) { - slide_url(mode, 'k', "knotted portal (YouTube)", "https://www.youtube.com/watch?v=eb2DhCcGH7U"); - slide_url(mode, 'h', "self-hiding knot portal (YouTube)", "https://www.youtube.com/watch?v=vFLZ2NGtuGw"); - slide_url(mode, 'n', "non-Euclidean portal in Nil (YouTube)", "https://www.youtube.com/watch?v=2K-v8tK68AE"); - slide_url(mode, 's', "spherical portal (YouTube)", "https://www.youtube.com/watch?v=PerPeQFu5gw"); - slide_url(mode, 'c', "Cat Portal in Solv (YouTube)", "https://www.youtube.com/watch?v=CGiSxC9B6i0"); - setCanvas(mode, '0'); - using namespace tour; - if(mode == pmStart) { - slide_backup(margin); - slide_backup(mapeditor::drawplayer); - slide_backup(firstland); - slide_backup(specialland); - slide_backup(ray::max_cells); - slide_backup(smooth_scrolling); - slide_backup(camera_speed); - slide_backup(ray::exp_decay_poly); - slide_backup(ray::fixed_map); - slide_backup(ray::max_iter_iso); - #if CAP_VR - slide_backup(vrhr::hsm); - slide_backup(vrhr::eyes); - slide_backup(vrhr::cscr); - slide_backup(vrhr::absolute_unit_in_meters); - #endif - - on_restore([] { nilv::set_flags(); asonov::set_flags(); }); - - slide_backup(nilv::nilwidth); - slide_backup(nilv::nilperiod); - - slide_backup(vid.binary_width); - slide_backup(asonov::period_xy); - slide_backup(asonov::period_z); - - nk_launch(); - start_game(); - loop = 2; - } - if(mode == tour::pmKey) { - pushScreen(show); - } - } - }); - }) ++ addHook_slideshows(120, portal_slideshow) #endif - ; + ; #ifdef NOTKNOT auto hook1= diff --git a/rogueviz/platformer.cpp b/rogueviz/platformer.cpp index f966fb23..272c67a7 100644 --- a/rogueviz/platformer.cpp +++ b/rogueviz/platformer.cpp @@ -13,6 +13,7 @@ g -- generate the current room (buggy) m -- see the map (toggle) p -- pause (toggle) z -- screenshot menu +v -- HyperRogue settings q -- quit s -- save to platformer.lev 1 -- place a small block under the mouse @@ -151,7 +152,6 @@ struct room { void initial() { int ylev = where->master->distance; - println(hlog, "ylev = ", ylev); if(ylev <= 0) for(int y=room_y-6; ytextureid; - println(hlog, "offset = ", p.offset, " texture_offset = ", p.offset_texture); + // println(hlog, "offset = ", p.offset, " texture_offset = ", p.offset_texture); auto render_at = [&] (GLuint texid, double px0, double py0, double px1, double py1, double tx0, double ty0, double tx1, double ty1) { @@ -632,6 +632,7 @@ void render_room(room *r) { template void render_room_objects(room *r, R render_at) { auto pb = get_pixel_bbox(); if(r != current_room) return; + create_sprite_texture(); render_at(sprite_texture->textureid, pb.minx, pb.miny, pb.maxx, pb.maxy, 0, 0, man_x/256., man_y/256.); } @@ -704,14 +705,29 @@ void run() { dialog::add_key_action('m', [] { map_on = !map_on; }); + dialog::add_key_action('v', [] { + pushScreen(showSettings); + }); dialog::add_key_action('p', [] { paused = !paused; }); dialog::add_key_action('z', [] { pushScreen(shot::menu); }); - dialog::add_key_action('q', [] { exit(0); }); - dialog::add_key_action('s', [] { + dialog::add_key_action('q', [] { + if(tour::on) tour::next_slide(); + else exit(0); + }); + dialog::add_key_action('o', [] { + if(tour::on) tour::next_slide(); + }); + dialog::add_key_action(SDLK_ESCAPE, [] { + if(tour::on) tour::next_slide(); + }); + dialog::add_key_action(SDLK_F10, [] { + if(tour::on) tour::next_slide(); + }); + dialog::add_key_action('s', [] { mapstream::saveMap("platformer.lev"); }); @@ -801,6 +817,24 @@ void add_platf_hooks() { } auto chk = arg::add3("-platformer", enable) + + addHook_rvslides(195, [] (string s, vector& v) { + if(s != "mixed") return; + v.push_back(tour::slide{ + "platformer", 10, tour::LEGAL::NONE | tour::QUICKSKIP | tour::QUICKGEO, + "A non-Euclidean platformer.\n\nPress up/left/right to move the guy.\n\nM to see the map\n\nP to pause\n\nV to change HyperRogue settings.\n\nPress Q when you are done.\n" + , + [] (tour::presmode mode) { + slide_url(mode, 'y', "non-Euclidean platformer (YouTube)", "https://www.youtube.com/watch?v=eb2DhCcGH7U"); + slide_url(mode, 't', "non-Euclidean platformer (Twitter)", "https://twitter.com/ZenoRogue/status/1467233150380089345"); + slide_url(mode, 'g', "how to edit this", "https://github.com/zenorogue/hyperrogue/blob/master/rogueviz/platformer.cpp"); + setCanvas(mode, '0'); + using namespace tour; + if(mode == pmStart) { + mapstream::loadMap("platformer.lev"); + } + } + }); + }) + addHook(mapstream::hooks_loadmap, 100, [] (fhstream& f, int id) { if(id == 66) { println(hlog, "loading platformer"); diff --git a/rogueviz/rogueviz-all.cpp b/rogueviz/rogueviz-all.cpp index 54c89ffa..410dfd85 100644 --- a/rogueviz/rogueviz-all.cpp +++ b/rogueviz/rogueviz-all.cpp @@ -59,6 +59,7 @@ #include "highdim-demo.cpp" #include "horo63.cpp" #include "platformer.cpp" +#include "intra-demos.cpp" #include "gobot.cpp" #include "kohonen.cpp" diff --git a/rogueviz/starbattle.cpp b/rogueviz/starbattle.cpp index bc163832..384a07fd 100644 --- a/rogueviz/starbattle.cpp +++ b/rogueviz/starbattle.cpp @@ -488,6 +488,7 @@ void launch() { showstartmenu = false; mapeditor::drawplayer = false; + rv::hook(hooks_drawcell, 100, draw_star); } #if CAP_COMMANDLINE @@ -515,7 +516,6 @@ int rugArgs() { auto starbattle_hook = addHook(hooks_args, 100, rugArgs) + - addHook(hooks_drawcell, 100, draw_star) + addHook(mapstream::hooks_savemap, 100, [] (fhstream& f) { f.write(isize(sdata)); for(auto& sd: sdata) { diff --git a/tour.cpp b/tour.cpp index 93131d74..49816ceb 100644 --- a/tour.cpp +++ b/tour.cpp @@ -103,6 +103,11 @@ EX void slide_url(presmode mode, char key, string text, string url) { }}); } +EX void slide_action(presmode mode, char key, string text, reaction_t act) { + if(mode == pmHelpEx) + help_extensions.push_back(help_extension{key, text, act}); + } + /** \brief an auxiliary function to enable a visualization in the Canvas land */ EX void setCanvas(presmode mode, char canv) { if(mode == pmStart) { @@ -206,25 +211,29 @@ void return_geometry() { addMessage(XLAT("Returned to your game.")); } +EX bool next_slide() { + flagtype flags = slides[currentslide].flags; + popScreenAll(); + if(gamestack::pushed()) { + return_geometry(); + if(!(flags & QUICKGEO)) return true; + } + if(flags & FINALSLIDE) return true; + presentation(pmStop); + slide_restore_all(); + currentslide++; + presentation(pmStart); + slidehelp(); + return true; + } + bool handleKeyTour(int sym, int uni) { if(!tour::on) return false; if(!(cmode & sm::DOTOUR)) return false; bool inhelp = cmode & sm::HELP; flagtype flags = slides[currentslide].flags; - if((sym == SDLK_RETURN || sym == SDLK_KP_ENTER) && (!inhelp || (flags & QUICKSKIP))) { - popScreenAll(); - if(gamestack::pushed()) { - return_geometry(); - if(!(flags & QUICKGEO)) return true; - } - if(flags & FINALSLIDE) return true; - presentation(pmStop); - slide_restore_all(); - currentslide++; - presentation(pmStart); - slidehelp(); - return true; - } + if((sym == SDLK_RETURN || sym == SDLK_KP_ENTER) && (!inhelp || (flags & QUICKSKIP))) + return next_slide(); if(sym == SDLK_BACKSPACE) { if(gamestack::pushed()) { gamestack::pop(); diff --git a/util.cpp b/util.cpp index 1c75f6b5..57d03020 100644 --- a/util.cpp +++ b/util.cpp @@ -732,6 +732,11 @@ EX void open_url(string s) { const char *urlhex = "0123456789ABCDEF"; EX void open_wiki(const char *title) { + // Since "Crossroads" is ambiguous, we use the direct link to Crossroads I. + if (!strcmp(title, "Crossroads")) { + title = "Crossroads (Land)"; + } + string url = "https://hyperrogue.miraheze.org/wiki/"; unsigned char c; for (size_t i = 0; (c = title[i]); ++i) { diff --git a/vr.cpp b/vr.cpp index 6ef99d7d..d39fcf84 100644 --- a/vr.cpp +++ b/vr.cpp @@ -794,6 +794,24 @@ EX void track_actions() { } } +EX void get_eyes() { + for(int a=0; a<2; a++) { + auto eye = vr::EVREye(a); + E4; + vrdata.proj[a] = + vr_to_hr(vrdata.vr->GetProjectionMatrix(eye, 0.01, 300)); + + vrdata.iproj[a] = MirrorZ * inverse(vrdata.proj[a]); + + // println(hlog, "projection = ", vrdata.proj[a]); + + vrdata.eyepos[a] = + vr_to_hr(vrdata.vr->GetEyeToHeadTransform(eye)); + + // println(hlog, "eye-to-head = ", vrdata.eyepos[a]); + } + } + EX void start_vr() { if(true) { sm = Id; sm[1][1] = sm[2][2] = -1; } @@ -830,23 +848,12 @@ EX void start_vr() { println(hlog, "recommended size: ", int(vrdata.xsize), " x ", int(vrdata.ysize)); for(int a=0; a<2; a++) { - auto eye = vr::EVREye(a); vrdata.eyes[a] = new vr_framebuffer(vrdata.xsize, vrdata.ysize); println(hlog, "eye ", a, " : ", vrdata.eyes[a]->ok ? "OK" : "Error"); - - vrdata.proj[a] = - vr_to_hr(vrdata.vr->GetProjectionMatrix(eye, 0.01, 300)); - - vrdata.iproj[a] = MirrorZ * inverse(vrdata.proj[a]); - - println(hlog, "projection = ", vrdata.proj[a]); - - vrdata.eyepos[a] = - vr_to_hr(vrdata.vr->GetEyeToHeadTransform(eye)); - - println(hlog, "eye-to-head = ", vrdata.eyepos[a]); } - + + get_eyes(); + //CreateFrameBuffer( m_nRenderWidth, m_nRenderHeight, leftEyeDesc ); //CreateFrameBuffer( m_nRenderWidth, m_nRenderHeight, rightEyeDesc ); @@ -1021,7 +1028,8 @@ EX void gen_mv() { EX shiftmatrix master_cview; EX void render() { - track_poses(); + track_poses(); + get_eyes(); resetbuffer rb; state = 2; vrhr::frusta.clear(); @@ -1033,11 +1041,31 @@ EX void render() { if(1) { make_actual_view(); master_cview = cview(); + + /* unfortunately we need to backup everything that could change by shift_view... */ dynamicval tN(NLP, NLP); dynamicval tV(View, View); dynamicval tC(current_display->which_copy, current_display->which_copy); dynamicval trt(radar_transform); + /* changed in intra */ + dynamicval tcs(camera_speed); + dynamicval tcl(anims::cycle_length); + dynamicval tau(vrhr::absolute_unit_in_meters); + dynamicval tel(walking::eye_level); + dynamicval tfd(walking::floor_dir); + dynamicval tof(walking::on_floor_of); + + int id = intra::current; + cell *co = centerover; + finalizer fin([&] { + if(intra::current != id) { + println(hlog, "rendering via portal"); + intra::switch_to(id); + centerover = co; + } + }); + if(hsm == eHeadset::rotation_only) { transmatrix T = hmd_at; be_33(T);