// Hyperbolic Rogue -- the conformal/history mode // Copyright (C) 2011-2018 Zeno Rogue, see 'hyper.cpp' for details namespace hr { #if ISMOBWEB typedef double precise; #else typedef long double precise; #endif namespace polygonal { typedef long double xld; typedef complex cxld; int SI = 4; ld STAR = 0; int deg = ISMOBWEB ? 2 : 20; precise matrix[MSI][MSI]; precise ans[MSI]; cxld coef[MSI]; ld coefr[MSI], coefi[MSI]; int maxcoef, coefid; void solve() { if(pmodel == mdPolynomial) { for(int i=0; i 0 ? (1+STAR) : 1) - sin(i0 * (j + 1./SI)) * (STAR > 0 ? STAR : STAR/(1+STAR)); } for(int i=0; i=0; i--) { for(int j=0; j compute(ld x, ld y, int prec) { if(x*x+y*y > 1) { xld r = hypot(x,y); x /= r; y /= r; } if(pmodel == mdPolynomial) { cxld z(x,y); cxld res (0,0); for(int i=maxcoef; i>=0; i--) { res += coef[i]; if(i) res *= z; } return make_pair(real(res), imag(res)); } cxld z(x, y); cxld res (0,0); cxld zp = 1; for(int i=0; i0; i--) { res += ans[i]; res *= zp; } res += ans[0]; res *= z; return make_pair(real(res), imag(res)); } pair compute(ld x, ld y) { return compute(x,y,deg); } } #if CAP_SDL namespace spiral { typedef long double ld; typedef complex cxld; int shiftx, shifty, velx, vely; vector > quickmap; int CX, CY, SX, SY, Yshift; vector band; SDL_Surface *out; bool displayhelp = true; color_t& bandpixel(int x, int y) { int i = 0; while(i < isize(band) && x >= band[i]->w) x -= band[i]->w, i++; return qpixel(band[i], x, y); } void precompute() { CX = 0; for(int i=0; iw; if(CX == 0) { printf("ERROR: no CX\n"); return; } CY = band[0]->h; SX = out->w; SY = out->h; ld k = -2*M_PI*M_PI / log(2.6180339); // cxld mnoznik = cxld(0, M_PI) / cxld(k, M_PI); cxld factor = cxld(0, -CY/2/M_PI/M_PI) * cxld(k, M_PI); Yshift = CY * k / M_PI; quickmap.clear(); double xc = ((SX | 1) - 2) / 2.; double yc = ((SY | 1) - 2) / 2.; for(int y=0; y p = quickmap[c++]; int cx = p.first + shiftx; int cy = p.second + + shifty; int d = cy / CY; cy -= d * CY; cx -= d * Yshift; if(cy<0) cy += CY, cx += Yshift; cx %= CX; if(cx<0) cx += CX; qpixel(out, x, y) = bandpixel(cx, cy); } } void loop(vector _band) { band = _band; out = s; precompute(); if(CX == 0) return; shiftx = shifty = 0; velx=1; vely=1; bool dosave = false; bool saveGL = vid.usingGL; if(saveGL) switchGL(); // { vid.usingGL = false; setvideomode(); } while(true) { time_t timer; timer = time(NULL); char buf[128]; strftime(buf, 128, "spiral-%y%m%d-%H%M%S" IMAGEEXT, localtime(&timer)); SDL_LockSurface(s); draw(); if(dosave) { dosave = false; IMAGESAVE(s, buf); } SDL_UnlockSurface(s); if(displayhelp) { displaystr(SX/2, vid.fsize*2, 0, vid.fsize, "arrows = navigate, ESC = return, h = hide help", forecolor, 8); displaystr(SX/2, SY - vid.fsize*2, 0, vid.fsize, XLAT("s = save to " IMAGEEXT, buf), forecolor, 8); } SDL_UpdateRect(s, 0, 0, 0, 0); shiftx += velx; shifty += vely; SDL_Event event; while(SDL_PollEvent(&event)) switch (event.type) { case SDL_VIDEORESIZE: { resize_screen_to(event.resize.w, event.resize.h); precompute(); break; } case SDL_QUIT: case SDL_MOUSEBUTTONDOWN: goto breakloop; case SDL_KEYDOWN: { int key = event.key.keysym.sym; // int uni = event.key.keysym.unicode; if(key == SDLK_RIGHT) velx++; if(key == SDLK_LEFT) velx--; if(key == SDLK_UP) vely++; if(key == SDLK_DOWN) vely--; if(key == SDLK_ESCAPE) goto breakloop; if(key == 'h') displayhelp = !displayhelp; if(key == 's') dosave = true; } } } breakloop: quickmap.clear(); if(saveGL) switchGL(); // { vid.usingGL = true; setvideomode(); } } } #endif bool isbad(ld z) { return !isfinite(z) || fabs(z) > 1e6; } namespace conformal { string formula = "z^2"; eModel basic_model; void handleKeyC(int sym, int uni); int lastprogress; void progress_screen() { gamescreen(0); mouseovers = ""; } void progress(string str) { #if CAP_SDL int tick = SDL_GetTicks(); if(tick > lastprogress + 250) { lastprogress = tick; msgs.clear(); addMessage(str); drawscreen(); } #endif } bool on; vector v; int llv; double phase; vector > killhistory; vector > findhistory; vector movehistory; bool includeHistory; ld lvspeed = 1; int bandhalf = 200; int bandsegment = 16000; ld rotation = 0; int do_rotate = 1; ld model_orientation, halfplane_scale; ld ocos, osin; ld cos_ball, sin_ball; bool model_straight; ld top_z = 5; ld model_transition = 1; bool autoband = false; bool autobandhistory = false; bool dospiral = true; ld extra_line_steps = 0; void clear() { on = false; int N = isize(v); for(int i=0; iat = Id; m->base = c; v.push_back(m); if(c == currentmap->gamestart()) break; for(int i=0; itype; i++) if(celldist(c->move(i)) < celldist(c)) { c = c->move(i); break; } } reverse(v.begin(), v.end()); int Q = isize(v)-1; // virtualRebase(v[0], false); // virtualRebase(v[Q], false); for(int i=0; i<1000; i++) { progress(XLAT("Preparing the line (%1/1000)...", its(i+1))); for(int j=1; jbase, v[j]->base, C0) * v[j-1]->at * C0; hyperpoint next = calc_relative_matrix(v[j+1]->base, v[j]->base, C0) * v[j+1]->at * C0; hyperpoint hmid = mid(prev, next); transmatrix at = rgpushxto0(hmid); v[j]->at = at * rspintox(inverse(at) * next); fixmatrix(v[j]->at); } } hyperpoint next0 = calc_relative_matrix(v[1]->base, v[0]->base, C0) * v[1]->at * C0; v[0]->at = v[0]->at * rspintox(inverse(v[0]->at) * next0); llv = ticks; phase = 0; if(0) for(int j=0; j<=Q; j++) { hyperpoint cur = v[j]->at * C0; printf("%4d/%3d. %p [%3d] %s\n", j, Q, v[j]->base, celldist(v[j]->base), display(cur)); } } void movetophase() { int ph = int(phase); int siz = isize(v); if(ph<0) ph = 0; if(ph >= siz-1) ph = siz-2; viewctr.at = v[ph]->base->master; viewctr.spin = 0; View = inverse(master_relative(v[ph]->base) * v[ph]->at); hyperpoint now = v[ph]->at * C0; hyperpoint next = calc_relative_matrix(v[ph+1]->base, v[ph]->base, C0) * v[ph+1]->at * C0; View = spin(rotation * degree) * xpush(-(phase-ph) * hdist(now, next)) * View; playermoved = false; centerover.at = v[ph]->base; compute_graphical_distance(); } void apply() { int t = ticks; phase += (t-llv) * lvspeed / 400.; llv = t; int siz = isize(v); while(phase > siz-1 + extra_line_steps) phase -= (siz + 2 * extra_line_steps-1); while(phase < - extra_line_steps) phase += (siz + 2 * extra_line_steps-1); movetophase(); } ld spiral_angle, cos_spiral, sin_spiral; void configure() { ld ball = -vid.ballangle * degree; cos_ball = cos(ball), sin_ball = sin(ball); ocos = cos(model_orientation * degree); osin = sin(model_orientation * degree); model_straight = (ocos > 1 - 1e-9); if(conformal::on) conformal::apply(); ld b = spiral_angle * degree; cos_spiral = cos(b), sin_spiral = sin(b); band_shift = 0; } ld measureLength() { ld r = bandhalf * vid.scale; ld tpixels = 0; int siz = isize(v); for(int j=0; jat) * calc_relative_matrix(v[j+1]->base, v[j]->base, C0) * v[j+1]->at * C0; hyperpoint nextscr; applymodel(next, nextscr); tpixels += nextscr[0] * r; if(j == 0 || j == siz-2) tpixels += nextscr[0] * r * extra_line_steps; } return tpixels; } void restore(); void restoreBack(); #if CAP_SDL void createImage(bool dospiral) { int segid = 1; if(includeHistory) restore(); int bandfull = 2*bandhalf; ld len = measureLength(); time_t timer; timer = time(NULL); char timebuf[128]; strftime(timebuf, 128, "%y%m%d-%H%M%S", localtime(&timer)); vector bands; resetbuffer rbuf; if(1) { // block for RAII dynamicval dv(vid, vid); dynamicval dr(rotation, 0); dynamicval di(inHighQual, true); renderbuffer glbuf(bandfull, bandfull, vid.usingGL); vid.xres = vid.yres = bandfull; glbuf.enable(); vid.radius = bandhalf; calcparam(); stereo::set_viewport(0); ld xpos = 0; int seglen = min(int(len), bandsegment); SDL_Surface *band = SDL_CreateRGBSurface(SDL_SWSURFACE, seglen, bandfull,32,0,0,0,0); if(!band) { addMessage("Could not create an image of that size."); } else { int siz = isize(v); int bonus = ceil(extra_line_steps); cell *last_base = NULL; hyperpoint last_relative; for(int j=-bonus; j bandsegment) { char buf[154]; sprintf(buf, "bandmodel-%s-%03d" IMAGEEXT, timebuf, segid++); IMAGESAVE(band, buf); if(dospiral) bands.push_back(band); else SDL_FreeSurface(band); len -= bandsegment; xpos -= bandsegment; seglen = min(int(len), bandsegment); band = SDL_CreateRGBSurface(SDL_SWSURFACE, seglen, bandfull,32,0,0,0,0); goto drawsegment; } xpos += bwidth; } last_base = viewctr.at->c7; last_relative = inverse(ggmatrix(last_base)) * C0; } } char buf[154]; sprintf(buf, "bandmodel-%s-%03d" IMAGEEXT, timebuf, segid++); IMAGESAVE(band, buf); addMessage(XLAT("Saved the band image as: ") + buf); if(dospiral) bands.push_back(band); else SDL_FreeSurface(band); } rbuf.reset(); stereo::set_viewport(0); if(includeHistory) restoreBack(); if(dospiral) { spiral::loop(bands); for(int i=0; i 1e-3 && pmodel != mdBall && pmodel != mdHyperboloid && pmodel != mdHemisphere && pmodel != mdDisk) { dialog::addBreak(50); dialog::addInfo("NOTE: this works 'correctly' only if the Poincaré model/stereographic projection is used."); dialog::addBreak(50); } if(pmodel == mdDisk || pmodel == mdBall || pmodel == mdHyperboloid) { dialog::addSelItem(XLAT("Projection at the ground level"), fts3(vid.alpha), 'p'); } if(model_has_orientation()) dialog::addSelItem(XLAT("model orientation"), fts(model_orientation), 'l'); if(pmodel == mdPolynomial) { dialog::addSelItem(XLAT("coefficient"), fts4(polygonal::coefr[polygonal::coefid]), 'x'); dialog::addSelItem(XLAT("coefficient (imaginary)"), fts4(polygonal::coefi[polygonal::coefid]), 'y'); dialog::addSelItem(XLAT("which coefficient"), its(polygonal::coefid), 'n'); } if(pmodel == mdHalfplane) { dialog::addSelItem(XLAT("half-plane scale"), fts(halfplane_scale), 'b'); } if(pmodel == mdBall) dialog::addSelItem(XLAT("projection in ball model"), fts3(vid.ballproj), 'x'); if(pmodel == mdPolygonal) { dialog::addSelItem(XLAT("polygon sides"), its(polygonal::SI), 'x'); dialog::addSelItem(XLAT("star factor"), fts(polygonal::STAR), 'y'); dialog::addSelItem(XLAT("degree of the approximation"), its(polygonal::deg), 'n'); } if(pmodel == mdBall || pmodel == mdHyperboloid || pmodel == mdHemisphere) { dialog::addSelItem(XLAT("camera rotation in 3D models"), fts3(vid.ballangle), 'b'); } if(pmodel == mdHyperboloid) dialog::addSelItem(XLAT("maximum z coordinate to show"), fts3(top_z), 'l'); if(model_has_transition()) dialog::addSelItem(XLAT("model transition"), fts3(model_transition), 't'); if(among(pmodel, mdJoukowsky, mdJoukowskyInverted, mdSpiral)) dialog::addSelItem(XLAT("Möbius transformations"), fts3(vid.skiprope), 'S'); if(pmodel == mdHemisphere && euclid) { dialog::addSelItem(XLAT("parameter"), fts3(vid.euclid_to_sphere), 'l'); } if(pmodel == mdTwoPoint) { dialog::addSelItem(XLAT("parameter"), fts3(vid.twopoint_param), 'b'); } dialog::addSelItem(XLAT("vertical stretch"), fts3(vid.stretch), 's'); menuitem_sightrange('R'); dialog::addBreak(100); dialog::addItem(XLAT("history mode"), 'a'); #if CAP_RUG dialog::addItem(XLAT("hypersian rug mode"), 'u'); #endif dialog::addBack(); dialog::display(); mouseovers = XLAT("see http://www.roguetemple.com/z/hyper/models.php"); keyhandler = [] (int sym, int uni) { dialog::handleNavigation(sym, uni); if(uni == 'z') editScale(); else if(uni == 'p') projectionDialog(); #if CAP_RUG else if(uni == 'u') pushScreen(rug::show); #endif else if(uni == 'l' && model_has_orientation()) dialog::editNumber(model_orientation, 0, 360, 90, 0, XLAT("model orientation"), ""); else if(uni == 'l' && pmodel == mdHyperboloid) dialog::editNumber(top_z, 1, 20, 0.25, 4, XLAT("maximum z coordinate to show"), ""); else if(uni == 't') dialog::editNumber(model_transition, 0, 1, 0.1, 1, XLAT("model transition"), "You can change this parameter for a transition from another model to this one." ); else if(uni == 'S') dialog::editNumber(vid.skiprope, 0, 360, 15, 0, XLAT("Möbius transformations"), ""); else if(uni == 'b' && pmodel == mdHalfplane) dialog::editNumber(model_orientation, 0, 2, 0.25, 1, XLAT("halfplane scale"), ""); else if(uni == 's') { dialog::editNumber(vid.stretch, 0, 10, .1, 1, XLAT("vertical stretch"), "Vertical stretch factor." ); dialog::extra_options = [] () { dialog::addBreak(100); if(sphere && pmodel == mdBandEquiarea) { dialog::addBoolItem("Gall-Peters", vid.stretch == 2, 'o'); dialog::add_action([] { vid.stretch = 2; dialog::ne.s = "2"; }); } if(pmodel == mdBandEquiarea) { // y = K * sin(phi) // cos(phi) * cos(phi) = 1/K if(sphere && vid.stretch >= 1) { ld phi = acos(sqrt(1/vid.stretch)); dialog::addInfo(XLAT("The current value makes the map conformal at the latitude of %1 (%2°).", fts(phi), fts(phi / degree))); } else if(hyperbolic && abs(vid.stretch) <= 1 && abs(vid.stretch) >= 1e-9) { ld phi = acosh(abs(sqrt(1/vid.stretch))); dialog::addInfo(XLAT("The current value makes the map conformal %1 units from the main line.", fts(phi))); } else dialog::addInfo(""); } }; } else if(uni == 'a') pushScreen(history_menu); else if(uni == 'l' && pmodel == mdHemisphere && euclid) { dialog::editNumber(vid.euclid_to_sphere, 0, 10, .1, 1, XLAT("parameter"), "Stereographic projection to a sphere. Choose the radius of the sphere." ); dialog::scaleLog(); } else if(uni == 'b' && pmodel == mdTwoPoint) { dialog::editNumber(vid.twopoint_param, 0, 10, .1, 1, XLAT("parameter"), "This model maps the world so that the distances from two points " "are kept. This parameter gives the distance from the two points to " "the center." ); dialog::scaleLog(); } else if(uni == 'b') config_camera_rotation(); else if(uni == 'x' && pmodel == mdBall) dialog::editNumber(vid.ballproj, 0, 100, .1, 0, XLAT("projection in ball model"), "This parameter affects the ball model the same way as the projection parameter affects the disk model."); else if(sym == 'x' && pmodel == mdPolygonal) dialog::editNumber(polygonal::SI, 3, 10, 1, 4, XLAT("polygon sides"), ""); else if(sym == 'y' && pmodel == mdPolygonal) dialog::editNumber(polygonal::STAR, -1, 1, .1, 0, XLAT("star factor"), ""); else if(sym == 'n' && pmodel == mdPolygonal) dialog::editNumber(polygonal::deg, 2, polygonal::MSI-1, 1, 2, XLAT("degree of the approximation"), ""); else if(sym == 'x' && pmodel == mdPolynomial) { polygonal::maxcoef = max(polygonal::maxcoef, polygonal::coefid); int ci = polygonal::coefid + 1; dialog::editNumber(polygonal::coefr[polygonal::coefid], -10, 10, .01/ci/ci, 0, XLAT("coefficient"), ""); } else if(sym == 'y' && pmodel == mdPolynomial) { polygonal::maxcoef = max(polygonal::maxcoef, polygonal::coefid); int ci = polygonal::coefid + 1; dialog::editNumber(polygonal::coefi[polygonal::coefid], -10, 10, .01/ci/ci, 0, XLAT("coefficient (imaginary)"), ""); } else if(sym == 'n' && pmodel == mdPolynomial) dialog::editNumber(polygonal::coefid, 0, polygonal::MSI-1, 1, 0, XLAT("which coefficient"), ""); else if(sym == 'r') { if(rotation < 0) rotation = 0; dialog::editNumber(rotation, 0, 360, 90, 0, XLAT("rotation"), "This controls the automatic rotation of the world. " "It affects the line animation in the history mode, and " "lands which have a special direction. Note that if finding this special direction is a part of the puzzle, " "it works only in the cheat mode."); dialog::dialogflags |= sm::CENTER; dialog::extra_options = [] () { dialog::addBreak(100); dialog::addBoolItem("line animation only", conformal::do_rotate == 0, 'n'); dialog::add_action([] () { conformal::do_rotate = 0; }); dialog::addBoolItem("gravity lands", conformal::do_rotate == 1, 'g'); dialog::add_action([] () { conformal::do_rotate = 1; }); dialog::addBoolItem("all directional lands", conformal::do_rotate == 2, 'd'); dialog::add_action([] () { conformal::do_rotate = 2; }); }; } else if(doexiton(sym, uni)) popScreen(); }; } bool band_renderable_now() { return on && (pmodel == mdBand || pmodel == mdBandEquidistant || pmodel == mdBandEquiarea) && !euclid && !sphere; } void history_menu() { cmode = sm::SIDE | sm::MAYDARK; gamescreen(0); dialog::init(XLAT("history mode")); dialog::addBoolItem(XLAT("include history"), (includeHistory), 'i'); // bool notconformal0 = (pmodel >= 5 && pmodel <= 6) && !euclid; // bool notconformal = notconformal0 || abs(vid.alpha-1) > 1e-3; dialog::addSelItem(XLAT("model used"), get_model_name(pmodel), 'm'); if(!bounded && !euclid) dialog::addBoolItem(XLAT("prepare the line animation"), (on), 'e'); if(on) dialog::addSelItem(XLAT("animation speed"), fts(lvspeed), 'a'); dialog::addSelItem(XLAT("extend the ends"), fts(extra_line_steps), 'p'); #if CAP_SDL dialog::addBoolItem(XLAT("render bands automatically"), (autoband), 'o'); if(autoband) dialog::addBoolItem(XLAT("include history when auto-rendering"), (autobandhistory), 'j'); if(band_renderable_now() || autoband) { dialog::addSelItem(XLAT("band width"), "2*"+its(bandhalf), 'd'); dialog::addSelItem(XLAT("length of a segment"), its(bandsegment), 's'); dialog::addBoolItem(XLAT("spiral on rendering"), (dospiral), 'g'); if(band_renderable_now()) dialog::addItem(XLAT("render now (length: %1)", fts(measureLength())), 'f'); } #endif dialog::addBack(); dialog::display(); mouseovers = XLAT("see http://www.roguetemple.com/z/hyper/models.php"); keyhandler = handleKeyC; } void handleKeyC(int sym, int uni) { dialog::handleNavigation(sym, uni); if(uni == 'e') { if(on) clear(); else { if(canmove && !cheater) { addMessage("Enable cheat mode or GAME OVER to use this"); return; } if(canmove && cheater) cheater++; create(); } } else if(uni == 'o') autoband = !autoband; else if(uni == 'm') pushScreen(model_menu); else if(sym == 'a') dialog::editNumber(lvspeed, -5, 5, .1, 1, XLAT("animation speed"), ""); else if(sym == 'd') dialog::editNumber(bandhalf, 5, 1000, 5, 200, XLAT("band width"), ""); else if(sym == 's') dialog::editNumber(bandsegment, 500, 32000, 500, 16000, XLAT("band segment"), ""); else if(sym == 'p') dialog::editNumber(extra_line_steps, 0, 5, 1, 1, XLAT("extend the ends"), "0 = start at the game start, endat the end position; " "larger numbers give extra space at the ends." ); else if(sym == 'g') { dospiral = !dospiral; } else if(sym == 'i') { if(canmove && !cheater) { addMessage("Enable cheat mode or GAME OVER to use this"); return; } if(canmove && cheater) cheater++; includeHistory = !includeHistory; } #if CAP_SDL else if(uni == 'f' && band_renderable_now()) createImage(dospiral); #endif else if(sym == 'j') { autobandhistory = !autobandhistory; } else if(doexiton(sym, uni)) popScreen(); } set inmovehistory, inkillhistory, infindhistory; void restore() { inmovehistory.clear(); inkillhistory.clear(); infindhistory.clear(); for(int i=0; imonst; killhistory[i].first->monst = m; inkillhistory.insert(killhistory[i].first); } int si = isize(findhistory); for(int i=0; iitem; findhistory[i].first->item = m; infindhistory.insert(findhistory[i].first); } } void restoreBack() { int sk = isize(killhistory); for(int i=sk-1; i>=0; i--) { eMonster m = killhistory[i].second; killhistory[i].second = killhistory[i].first->monst; killhistory[i].first->monst = m; } int si = isize(findhistory); for(int i=si-1; i>=0; i--) { eItem m = findhistory[i].second; findhistory[i].second = findhistory[i].first->item; findhistory[i].first->item = m; } } void renderAutoband() { #if CAP_SDL if(!cwt.at || celldist(cwt.at) <= 7) return; if(!autoband) return; eModel spm = pmodel; bool ih = includeHistory; includeHistory = autobandhistory; pmodel = mdBand; create(); createImage(dospiral); clear(); pmodel = spm; includeHistory = ih; #endif } int readArgs() { using namespace arg; if(0) ; else if(argis("-els")) { shift(); conformal::extra_line_steps = argf(); } else if(argis("-stretch")) { PHASEFROM(2); shift(); vid.stretch = argf(); } else if(argis("-PM")) { PHASEFROM(2); shift(); pmodel = eModel(argi()); if(pmodel == mdFormula) { shift(); basic_model = eModel(argi()); shift(); formula = args(); } } else if(argis("-ballangle")) { PHASEFROM(2); shift(); vid.ballangle = argf(); } else if(argis("-topz")) { PHASEFROM(2); shift(); conformal::top_z = argf(); } else if(argis("-hp")) { PHASEFROM(2); shift(); conformal::halfplane_scale = argf(); } else if(argis("-mori")) { PHASEFROM(2); shift(); conformal::model_orientation = argf(); } else if(argis("-mtrans")) { PHASEFROM(2); shift(); conformal::model_transition = argf(); } else if(argis("-sang")) { PHASEFROM(2); shift(); conformal::spiral_angle = argf(); } else if(argis("-mob")) { PHASEFROM(2); shift(); vid.skiprope = argf(); } else if(argis("-zoom")) { PHASEFROM(2); shift(); vid.scale = argf(); } else if(argis("-alpha")) { PHASEFROM(2); shift(); vid.alpha = argf(); } else return 1; return 0; } auto hooks = addHook(clearmemory, 0, [] () { conformal::renderAutoband(); conformal::on = false; conformal::killhistory.clear(); conformal::findhistory.clear(); conformal::movehistory.clear(); conformal::includeHistory = false; }) + addHook(hooks_args, 100, readArgs); } }