1 // Hyperbolic Rogue -- control
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file control.cpp
5  *  \brief Routines related to controlling the game
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 EX int frames;
12 EX bool outoffocus = false;
13 
14 EX int mousex, mousey;
15 EX shiftpoint mouseh, mouseoh;
16 
17 EX bool pandora_leftclick, pandora_rightclick;
18 
19 EX bool lshiftclick, rshiftclick, lctrlclick, rctrlclick, anyshiftclick, anyctrlclick, wheelclick;
20 
21 EX bool targetclick, hiliteclick, forcetarget, numlock_on;
22 EX bool gtouched;
23 
24 EX bool holdmouse;
25 
26 EX int getcstat, lgetcstat;
27 EX ld getcshift;
28 EX bool inslider;
29 EX int slider_x;
30 
__anonf312e99e0102(int sym, int uni) 31 EX function <void(int sym, int uni)> keyhandler = [] (int sym, int uni) {};
__anonf312e99e0202(SDL_Event &ev) 32 EX function <bool(SDL_Event &ev)> joyhandler = [] (SDL_Event &ev) {return false;};
33 
34 #if HDR
35 // what part of the compass does 'skip turn'
36 static const auto SKIPFAC = .4;
37 #endif
38 
39 // is the player using mouse? (used for auto-cross)
40 EX bool mousing = true;
41 
42 /** /brief 0 for the system pointer, or VR controller ID */
43 
44 EX int which_pointer = 0;
45 
46 // is the mouse button pressed?
47 EX bool mousepressed = false;
48 EX bool mousemoved = false;
49 EX bool actonrelease = false;
50 
51 EX bool mousepan, oldmousepan;
52 #if CAP_MOUSEGRAB
53 EX ld mouseaim_x, mouseaim_y;
54 #endif
55 EX ld mouseaim_sensitivity = 0.01;
56 
57 EX int timetowait;
58 
59 #if CAP_SDLJOY
60 EX int joyx, joyy, panjoyx, panjoyy;
61 EX movedir joydir;
62 #endif
63 
64 EX movedir mousedest;
65 EX ld shiftmul = 1;
66 
67 EX cell *mouseover, *mouseover2, *lmouseover, *lmouseover_distant;
68 EX ld modist, modist2;
69 
70 EX int lastt;
71 
mouseout()72 EX bool mouseout() {
73   if((getcstat != '-' && getcstat) || (lgetcstat && lgetcstat != '-')) return true;
74   return outofmap(mouseh.h);
75   }
76 
mouseout2()77 EX bool mouseout2() {
78   if((getcstat && getcstat != '-') || (lgetcstat && lgetcstat != '-')) return true;
79   return outofmap(mouseh.h) || outofmap(mouseoh.h);
80   }
81 
vectodir(hyperpoint P)82 EX movedir vectodir(hyperpoint P) {
83 
84   transmatrix U = unshift(ggmatrix(cwt.at));
85   if(GDIM == 3 && WDIM == 2)  U = radar_transform * U;
86 
87   P = direct_exp(lp_iapply(P));
88 
89   hyperpoint H = sphereflip * tC0(U);
90   transmatrix Centered = sphereflip * rgpushxto0(H);
91 
92   ld binv = 99;
93 
94   vector<ld> dirdist(cwt.at->type);
95 
96   for(int i=0; i<cwt.at->type; i++) {
97     transmatrix T = currentmap->adj(cwt.at, (cwt + i).spin);
98     ld d1 = geo_dist(U * T * C0, Centered * P);
99     ld d2 = geo_dist(U * T * C0, Centered * C0);
100     dirdist[i] = d1 - d2;
101     //xspinpush0(-i * 2 * M_PI /cwt.at->type, .5), P);
102     }
103 
104   movedir res;
105   res.d = -1;
106 
107   for(int i=0; i<cwt.at->type; i++) {
108     if(dirdist[i] < binv) {
109       binv = dirdist[i];
110       res.d = i;
111       res.subdir = dirdist[(i+1)%cwt.at->type] < dirdist[(i+cwt.at->type-1)%cwt.at->type] ? 1 : -1;
112       }
113     }
114 
115   // if(euclid) bdir = (bdir + 3) % 6;
116   return res;
117   }
118 
remission()119 EX void remission() {
120   if(!canmove && (cmode & sm::NORMAL)) showMissionScreen();
121  }
122 
move_destination_vec(int d)123 EX hyperpoint move_destination_vec(int d) {
124   if(WDIM == 2) return spin(-d * M_PI/4) * smalltangent();
125   // else if(WDIM == 2 && pmodel == mdPerspective) return cspin(0, 2, d * M_PI/4) * tC0(pushone());
126   // else if(WDIM == 2) return spin(-d * M_PI/4) * tC0(pushone());
127   else if(d&1) return cspin(0, 1, d > 4 ? M_PI/2 : -M_PI/2) * smalltangent();
128   else return cspin(0, 2, d * M_PI/4) * smalltangent();
129   }
130 
movepckeydir(int d)131 EX void movepckeydir(int d) {
132   DEBB(DF_GRAPH, ("movepckeydir\n"));
133   // EUCLIDEAN
134 
135   if(protect_memory()) return;
136 
137   movedir md = vectodir(move_destination_vec(d));
138 
139   if(!canmove) movepcto(md), remission(); else movepcto(md);
140   }
141 
movevrdir(hyperpoint vec)142 EX void movevrdir(hyperpoint vec) {
143   movedir md = vectodir(vec);
144   if(!canmove) movepcto(md), remission(); else movepcto(md);
145   }
146 
calcMousedest()147 EX void calcMousedest() {
148   if(mouseout()) return;
149   if(vid.revcontrol == true) { mouseh[0] = -mouseh[0]; mouseh[1] = -mouseh[1]; }
150   ld mousedist = hdist(mouseh, tC0(ggmatrix(cwt.at)));
151   mousedest.d = -1;
152 
153   cellwalker bcwt = cwt;
154 
155   vector<ld> dists(cwt.at->type);
156 
157   shiftmatrix U = ggmatrix(cwt.at);
158 
159   for(int i=0; i<cwt.at->type; i++) {
160     transmatrix T = currentmap->adj(cwt.at, i);
161     dists[i] = hdist(mouseh, U * T * C0);
162     }
163 
164   for(int i=0; i<cwt.at->type; i++) if(dists[i] < mousedist) {
165     mousedist = dists[i];
166     mousedest.d = cwt.at->c.fix(i - cwt.spin);
167 
168     mousedest.subdir =
169        dists[cwt.at->c.fix(i+1)] < dists[cwt.at->c.fix(i-1)] ? 1 : -1;
170 
171     if(cwt.mirrored)
172       mousedest.d = cwt.at->c.fix(-mousedest.d),
173       mousedest.subdir = -mousedest.subdir;
174     }
175 
176   if(vid.revcontrol == true) { mouseh[0] = -mouseh[0]; mouseh[1] = -mouseh[1]; }
177   cwt = bcwt;
178   }
179 
mousemovement()180 EX void mousemovement() {
181 
182   #if CAP_VR
183   if(WDIM == 3 && vrhr::active() && which_pointer) {
184     movevrdir(vrhr::vr_direction);
185     return;
186     }
187   #endif
188 
189   if(GDIM == 3 && !which_pointer) {
190     if(WDIM == 2) {
191       if(View[2][2] < -0.75)
192         movepcto(MD_DROP, 1);
193       else if(View[2][2] > 0.75)
194         movepcto(-1, 1);
195       else
196         movepckeydir(6);
197       return;
198       }
199     movepckeydir(6);
200     return;
201     }
202 
203   if(protect_memory()) return;
204 
205   calcMousedest();
206     if(!canmove) movepcto(mousedest), remission(); else movepcto(mousedest);
207   lmouseover = NULL;
208   }
209 
210 #if CAP_SDLJOY
211 EX SDL_Joystick* sticks[8];
212 EX int numsticks;
213 
initJoysticks()214 EX void initJoysticks() {
215 
216   if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) == -1)
217   {
218     printf("Failed to initialize joysticks.\n");
219     numsticks = 0;
220     return;
221   }
222 
223   DEBB(DF_INIT, ("init joysticks"));
224   numsticks = SDL_NumJoysticks();
225   if(numsticks > 8) numsticks = 8;
226   for(int i=0; i<numsticks; i++) {
227     sticks[i] = SDL_JoystickOpen(i);
228     /* printf("axes = %d, balls = %d, buttons = %d, hats = %d\n",
229       SDL_JoystickNumAxes(sticks[i]),
230       SDL_JoystickNumBalls(sticks[i]),
231       SDL_JoystickNumButtons(sticks[i]),
232       SDL_JoystickNumHats(sticks[i])
233       ); */
234     }
235   }
236 
closeJoysticks()237 EX void closeJoysticks() {
238   DEBB(DF_INIT, ("close joysticks"));
239   for(int i=0; i<numsticks; i++) {
240     SDL_JoystickClose(sticks[i]), sticks[i] = NULL;
241     }
242   numsticks = 0;
243   }
244 
245 int joytime;
246 EX bool joy_ignore_next = false;
247 
checkjoy()248 EX void checkjoy() {
249   DEBB(DF_GRAPH, ("check joy"));
250   if(!DEFAULTCONTROL) return;
251   ld joyvalue1 = sqr(vid.joyvalue);
252   ld joyvalue2 = sqr(vid.joyvalue2);
253 
254   ld jx = joyx;
255   ld jy = joyy;
256   ld sq = jx*jx+jy*jy;
257 
258   static int laststate = 0;
259   int curstate = sq < joyvalue1 ? 0 : sq < joyvalue2 ? 1 : 2;
260   if(curstate != laststate) flashMessages(), laststate = curstate;
261 
262   static int lt = ticks;
263   int delta = ticks - lt;
264   lt = ticks;
265 
266   if(autojoy) {
267     if(sq < joyvalue1) { if(joydir.d >= 0 && !joy_ignore_next) movepcto(joydir); joydir.d = -1; joytime = 0; joy_ignore_next = false; return; }
268     if(sq < joyvalue2 && joydir.d == -1) return;
269     }
270   else {
271     if(sq < joyvalue1) { joydir.d = -1; return; }
272     }
273 
274   auto new_joydir = vectodir(tangent_length(point2(jx, jy), 0.01));
275   if(new_joydir.d == joydir.d && new_joydir.subdir == joydir.subdir) {
276     joytime += delta;
277     if(joytime > vid.joysmooth) joytime = vid.joysmooth;
278     }
279   else {
280     joytime -= delta;
281     if(joytime < 0) joytime = -joytime, joydir = new_joydir;
282     }
283   }
284 
checkpanjoy(double t)285 EX void checkpanjoy(double t) {
286   if(shmup::on) return;
287 
288   if(vid.joypanspeed < 1e-7) return;
289 
290   if(sqr(panjoyx) + sqr(panjoyy) < sqr(vid.joypanthreshold))
291     return;
292 
293   ld jx = panjoyx * t * vid.joypanspeed;
294   ld jy = panjoyy * t * vid.joypanspeed;
295 
296   playermoved = false;
297   View = gpushxto0(hpxy(jx, jy)) * View;
298   }
299 
300 #endif
301 
302 EX bool quitmainloop = false;
303 
doexiton(int sym,int uni)304 EX bool doexiton(int sym, int uni) {
305   if(sym == SDLK_ESCAPE) return true;
306   if(sym == SDLK_F10) return true;
307   if(sym == PSEUDOKEY_EXIT) return true;
308   if(sym == PSEUDOKEY_RELEASE) return false;
309   #ifndef FAKE_SDL
310   if(sym == SDLK_LSHIFT) return false;
311   if(sym == SDLK_RSHIFT) return false;
312   if(sym == SDLK_LCTRL) return false;
313   if(sym == SDLK_RCTRL) return false;
314   if(sym == SDLK_LALT) return false;
315   if(sym == SDLK_RALT) return false;
316   #endif
317   if(uni != 0) return true;
318   return false;
319   }
320 
321 EX bool didsomething;
322 
323 typedef SDL_Event eventtype;
324 
325 EX bool smooth_scrolling = false;
326 
zforward_push(ld z)327 transmatrix zforward_push(ld z) {
328   if(!sl2) return zpush(z);
329   transmatrix T = Id;
330   T[2][3] = z;
331   return T;
332   }
333 
zoom_or_fov(ld t)334 EX void zoom_or_fov(ld t) {
335   if(in_perspective_v()) {
336     vid.fov *= 180 / max_fov_angle();
337     auto tanfov = tan(vid.fov * degree / 2);
338     tanfov *= t;
339     vid.fov = atan(tanfov) * 2 / degree;
340     vid.fov *= max_fov_angle() / 180;
341     }
342   else
343     vpconf.scale *= t;
344   }
345 
346 EX ld camera_speed = 1;
347 EX ld camera_rot_speed = 1;
348 
full_forward_camera(ld t)349 EX void full_forward_camera(ld t) {
350   if(anyshiftclick)
351     zoom_or_fov(exp(-t/10.));
352   else if(GDIM == 3) {
353     shift_view(ctangent(2, t * camera_speed));
354     didsomething = true;
355     playermoved = false;
356     }
357   }
358 
full_strafe_camera(ld t)359 EX void full_strafe_camera(ld t) {
360   if(GDIM == 3) {
361     shift_view(ctangent(0, t * camera_speed));
362     didsomething = true;
363     playermoved = false;
364     }
365   }
366 
full_rotate_camera(int dir,ld val)367 EX void full_rotate_camera(int dir, ld val) {
368   if(rug::rug_control() && lshiftclick) {
369     val *= camera_rot_speed;
370     hyperpoint h;
371     if(nonisotropic) {
372       transmatrix T2 = eupush( tC0(view_inverse(View)) );
373       transmatrix nlp = View * T2;
374       auto rV = ortho_inverse(nlp) * View;
375       h = nlp * inverse_exp(shiftless(tC0(rV)));
376       }
377     else h = inverse_exp(shiftless(tC0(View)));
378     shift_view(-h);
379     rotate_view(cspin(dir, 2, val));
380     shift_view(h);
381     }
382   else if(history::on)
383     history::lvspeed += (dir?1:-1) * val / 2;
384   else if(GDIM == 3 && rshiftclick)
385     shift_view(ctangent(dir, -val * camera_speed)), didsomething = true, playermoved = false; /* -val because shift reverses */
386   #if CAP_CRYSTAL && CAP_RUG
387   else if(rug::rug_control() && rug::in_crystal())
388     crystal::apply_rotation(cspin(dir, 2, val * camera_rot_speed));
389   #endif
390   else if(GDIM == 3) {
391     val *= camera_rot_speed;
392     if(keep_vertical()) {
393       hyperpoint vv = vertical_vector();
394       ld alpha = -atan2(vv[2], vv[1]);
395       rotate_view(cspin(2, 1, alpha));
396       ld max_angle = quarter_circle - 1e-4;
397       if(dir == 1 && alpha + val > max_angle)
398         val = max_angle - alpha;
399       if(dir == 1 && alpha + val < -max_angle)
400         val = -max_angle - alpha;
401       rotate_view(cspin(dir, 2, val));
402       rotate_view(cspin(1, 2, alpha));
403       }
404     else
405       rotate_view(cspin(dir, 2, val));
406     if(!rug::rug_control()) didsomething = true;
407     }
408   else
409     shift_view(ctangent(dir, val * camera_speed)), playermoved = false, didsomething = true;
410   }
411 
full_rotate_view(ld h,ld v)412 EX void full_rotate_view(ld h, ld v) {
413   if(history::on && !rug::rug_control())
414     models::rotation += h * camera_rot_speed;
415   else {
416     rotate_view(spin(v * camera_rot_speed));
417     didsomething = true;
418     if(isGravityLand(cwt.at->land) && !rug::rug_control())
419       playermoved = false;
420     }
421   }
422 
handlePanning(int sym,int uni)423 EX void handlePanning(int sym, int uni) {
424   if(mousepan && dual::split([=] { handlePanning(sym, uni); })) return;
425   if(GDIM == 3) {
426     if(sym == PSEUDOKEY_WHEELUP) shift_view(ztangent(-0.05*shiftmul) * camera_speed), didsomething = true, playermoved = false;
427     if(sym == PSEUDOKEY_WHEELDOWN) shift_view(ztangent(0.05*shiftmul) * camera_speed), didsomething = true, playermoved = false;
428     }
429 
430   #if CAP_RUG
431   rug::using_rugview urv;
432   #endif
433 
434 #if !ISPANDORA
435   if(!smooth_scrolling) {
436     if(sym == SDLK_END) full_forward_camera(-0.2*shiftmul);
437     if(sym == SDLK_HOME) full_forward_camera(0.2*shiftmul);
438     if(sym == SDLK_RIGHT) full_rotate_camera(0, -0.2*shiftmul);
439     if(sym == SDLK_LEFT) full_rotate_camera(0, 0.2*shiftmul);
440     if(sym == SDLK_UP) full_rotate_camera(1, 0.2*shiftmul);
441     if(sym == SDLK_DOWN) full_rotate_camera(1, -0.2*shiftmul);
442     }
443 #endif
444   if(!smooth_scrolling) {
445     if(sym == SDLK_PAGEUP) full_rotate_view(1, M_PI/cgi.S21/2*shiftmul);
446     if(sym == SDLK_PAGEDOWN) full_rotate_view(-1, -M_PI/cgi.S21/2*shiftmul);
447     if(sym == SDLK_PAGEUP || sym == SDLK_PAGEDOWN)
448       if(isGravityLand(cwt.at->land) && !rug::rug_control()) playermoved = false;
449     }
450 
451   if(sym == PSEUDOKEY_WHEELUP && GDIM == 2) {
452     ld jx = (mousex - current_display->xcenter - .0) / current_display->radius / 10;
453     ld jy = (mousey - current_display->ycenter - .0) / current_display->radius / 10;
454     playermoved = false;
455     rotate_view(gpushxto0(hpxy(jx * camera_speed, jy * camera_speed)));
456     sym = 1;
457     }
458   }
459 
460 #ifdef SCALETUNER
461 double tunev = .1;
462 
handleTune(int sym,int uni)463 bool handleTune(int sym, int uni) {
464   if(uni == 'q') { tunev *= .5; return true; }
465   else if(uni == 'e') { tunev *= 2; return true; }
466   else if(uni == 'w') bscale7 += tunev*shiftmul;
467   else if(uni == 's') bscale7 -= tunev*shiftmul;
468   else if(uni == 'a') brot7 -= tunev*shiftmul;
469   else if(uni == 'd') brot7 += tunev*shiftmul;
470   else if(uni == 'i') bscale6 += tunev*shiftmul;
471   else if(uni == 'k') bscale6 -= tunev*shiftmul;
472   else if(uni == 'j') brot6 -= tunev*shiftmul;
473   else if(uni == 'l') brot6 += tunev*shiftmul;
474   else if(uni == 'z')
475     bscale7 = bscale6 = 1, brot7 = brot6 = 0;
476   else return false;
477   println(hlog, spaced(bscale7, brot7, bscale6, brot6));
478   return true;
479   }
480 #endif
481 
482 EX purehookset hooks_fixticks;
483 
484 EX array<int, 8> keys_vi = {{'l', 'n', 'j', 'b', 'h', 'y', 'k', 'u'}};
485 EX array<int, 8> keys_wasd = {{'d', 'c', 'x', 'z', 'a', 'q', 'w', 'e'}};
486 EX array<int, 8> keys_numpad = {{SDLK_KP6, SDLK_KP3, SDLK_KP2, SDLK_KP1, SDLK_KP4, SDLK_KP7, SDLK_KP8, SDLK_KP9}};
487 
handleKeyNormal(int sym,int uni)488 EX void handleKeyNormal(int sym, int uni) {
489 
490   if(cheater && sym < 256 && sym > 0) {
491     if(applyCheat(uni, mouseover))
492       uni = sym = 0;
493     }
494 
495   #if CAP_SHOT
496   if(uni == 'A') { pushScreen(shot::menu); uni = sym = 0; }
497   #endif
498 
499   if(DEFAULTNOR(sym)) handlePanning(sym, uni);
500 
501 #ifdef SCALETUNER
502   if(handleTune(sym, uni)) return;
503 #endif
504 
505   if(!(uni >= 'A' && uni <= 'Z') && DEFAULTCONTROL) {
506     for(int i=0; i<8; i++)
507       if(among(sym, keys_vi[i], keys_wasd[i], keys_numpad[i]))
508         movepckeydir(i);
509     }
510 
511 #if ISPANDORA
512   if(DEFAULTCONTROL) {
513     if(sym == SDLK_RIGHT) movepckeydir(0);
514     if(sym == SDLK_LEFT) movepckeydir(4);
515     if(sym == SDLK_DOWN) movepckeydir(2 + (pandora_leftclick?1:0) - (pandora_rightclick?1:0));
516     if(sym == SDLK_UP) movepckeydir(6 - (pandora_leftclick?1:0) + (pandora_rightclick?1:0));
517     }
518 #endif
519 
520   #if CAP_COMPLEX2
521   if(DEFAULTNOR(sym)) {
522     gmodekeys(sym, uni);
523     if(uni == 'm' && canmove && (centerover == cwt.at ? mouseover : centerover))
524       mine::performMarkCommand(mouseover);
525     }
526   #endif
527 
528   if(DEFAULTCONTROL) {
529     if(sym == '.' || sym == 's') movepcto(-1, 1);
530     if((sym == SDLK_DELETE || sym == SDLK_KP_PERIOD || sym == 'g') && uni != 'G' && uni != 'G'-64)
531       movepcto(MD_DROP, 1);
532     if(sym == 't' && uni != 'T' && uni != 'T'-64 && canmove) {
533       cell *target = GDIM == 3 ? mouseover : centerover;
534       if(playermoved && items[itStrongWind]) {
535         cell *c = whirlwind::jumpDestination(cwt.at);
536         if(c) target = c;
537         }
538       targetRangedOrb(target, roKeyboard);
539       sym = 0; uni = 0;
540       }
541     }
542 
543   if(sym == SDLK_KP5 && DEFAULTCONTROL) movepcto(-1, 1);
544 
545   if(sym == SDLK_F5) {
546     #if CAP_DAILY
547     if(daily::on) daily::handleQuit(1);
548     else
549     #endif
550     if(needConfirmation())
551       pushScreen(showMission);
552     else restart_game();
553     }
554 
555   if(sym == SDLK_ESCAPE) {
556     if(viewdists)
557       viewdists = false;
558     else
559       showMissionScreen();
560     }
561 
562   if(sym == SDLK_F10) {
563     #if CAP_DAILY
564     if(daily::on) daily::handleQuit(2);
565     else
566     #endif
567     if(needConfirmation()) pushScreen(showMission);
568     else quitmainloop = true;
569     }
570 
571   if(uni == 'o' && DEFAULTNOR(sym)) get_o_key().second();
572 #if CAP_INV
573   if(uni == 'i' && DEFAULTNOR(sym) && inv::on)
574     pushScreen(inv::show);
575 #endif
576 
577   if((sym == SDLK_F3 || sym == ' ') && DEFAULTNOR(sym)) {
578     if(rug::rug_control())
579       rug::reset_view();
580     else
581       fullcenter();
582     }
583 
584   if(sym == 'v' && DEFAULTNOR(sym))
585     pushScreen(showMainMenu);
586 
587   if(sym == PSEUDOKEY_MENU)
588     pushScreen(showMainMenu);
589 
590   if(sym == '-' || sym == PSEUDOKEY_WHEELDOWN) {
591     actonrelease = false;
592 
593     multi::cpid = 0;
594     if(mouseover &&
595       targetclick && (shmup::on ? numplayers() == 1 && !shmup::pc[0]->dead : true) && targetRangedOrb(mouseover, forcetarget ? roMouseForce : roMouse)) {
596       }
597     else if(forcetarget)
598       ;
599     else if(rug::rugged && rug::renderonce)
600       ;
601     else if(!DEFAULTCONTROL) {
602       if(!shmup::on)
603         multi::mousemovement(mouseover);
604       }
605     else if(handleCompass()) ;
606     else
607       mousemovement();
608     }
609 
610   if(sym == SDLK_F1) gotoHelp(help);
611 
612   if(sym == PSEUDOKEY_MEMORY) pushScreen(show_memory_menu);
613   }
614 
615 EX bool need_mouseh = false;
616 
fix_mouseh()617 EX void fix_mouseh() {
618   if(0) ;
619 #if CAP_RUG
620   else if(rug::rugged) {
621     if(need_mouseh || (vrhr::active() && which_pointer))
622       mouseh = rug::gethyper(mousex, mousey);
623     }
624 #endif
625 #if CAP_VR
626   else if(vrhr::active() && which_pointer && !vrhr::targeting_menu)
627     vrhr::compute_point(which_pointer, mouseh, mouseover, vrhr::pointer_distance);
628 #endif
629   else {
630     if(dual::state) {
631       if(cmode & (sm::NORMAL | sm::DRAW | sm::MAP)) {
632         dual::main_side = (mousex >= current_display->xcenter);
633         dual::switch_to(dual::main_side);
634         }
635       dual::in_subscreen([=] () { calcparam(); mouseh = gethyper(mousex, mousey); });
636       }
637     else mouseh = gethyper(mousex, mousey);
638     }
639   need_mouseh = false;
640   }
641 
handlekey(int sym,int uni)642 EX void handlekey(int sym, int uni) {
643 
644   if(callhandlers(false, hooks_handleKey, sym, uni)) return;
645 
646   keyhandler(sym, uni);
647   }
648 
649 EX void resize_screen_to(int x, int y);
650 
651 #if !CAP_SDL
mainloopiter()652 EX void mainloopiter() { printf("(compiled without SDL -- no action)\n"); quitmainloop = true; }
653 #endif
654 
655 #if CAP_SDL
656 
657 // Warning: a very long function! todo: refactor
658 
659 int cframelimit = 1000;
660 
resize_screen_to(int x,int y)661 EX void resize_screen_to(int x, int y) {
662   dual::split_or_do([&] {
663     vid.killreduction = 0;
664     if(vid.want_fullscreen) return;
665     if(vid.relative_window_size) {
666       vid.window_rel_x = x * 1. / vid.xscr;
667       vid.window_rel_y = y * 1. / vid.yscr;
668       }
669     else {
670       vid.window_x = x;
671       vid.window_y = y;
672       }
673     });
674   apply_screen_settings();
675   }
676 
677 int lastframe;
678 
679 EX int sc_ticks;
680 
mouseaiming(bool shmupon)681 EX bool mouseaiming(bool shmupon) {
682   return
683     (GDIM == 3 && !shmupon) || (rug::rugged && (lctrlclick ^ rug::mouse_control_rug));
684   }
685 
mainloopiter()686 EX void mainloopiter() {
687   GLWRAP;
688   DEBB(DF_GRAPH, ("main loop\n"));
689 
690   #if !CAP_SDLGFX && !CAP_GL
691   vid.wallmode = 0;
692   vid.monmode = 0;
693   #endif
694 
695   #if CAP_VR
696   vrhr::vr_shift();
697   #endif
698 
699   optimizeview();
700 
701   models::configure();
702 
703   lastt = ticks;
704   ticks = SDL_GetTicks();
705   callhooks(hooks_fixticks);
706 
707   timetowait = lastframe + 1000 / cframelimit - ticks;
708 
709   cframelimit = vid.framelimit;
710   if(outoffocus && cframelimit > 10) cframelimit = 10;
711 
712   bool normal = cmode & sm::NORMAL;
713 
714   shmup::turn(ticks - lastt);
715 
716   if(!shmup::on && (multi::alwaysuse || multi::players > 1) && normal)
717     timetowait = 0, multi::handleMulti(ticks - lastt);
718 
719   if(vid.sspeed >= 5 && gmatrix.count(cwt.at) && !elliptic && !shmup::on) {
720     cwtV = gmatrix[cwt.at] * ddspin(cwt.at, cwt.spin);
721     if(cwt.mirrored) playerV = playerV * Mirror;
722     }
723 
724   mousepan = cmode & sm::NORMAL;
725   if((cmode & (sm::DRAW | sm::MAP)) && !hiliteclick) mousepan = true;
726   mousepan = mousepan && mouseaiming(false) && mouseaim_sensitivity;
727   if(mousepan != oldmousepan) {
728     oldmousepan = mousepan;
729     #if CAP_MOUSEGRAB
730     if(mousepan) {
731       #if CAP_SDL2
732       SDL_SetRelativeMouseMode(SDL_TRUE);
733       #else
734       SDL_WM_GrabInput(SDL_GRAB_ON);
735       SDL_ShowCursor(SDL_DISABLE);
736       #endif
737       mouseaim_x = mouseaim_y = 0;
738       }
739     else {
740       #if CAP_SDL2
741       SDL_SetRelativeMouseMode(SDL_FALSE);
742       #else
743       SDL_WM_GrabInput( SDL_GRAB_OFF );
744       SDL_ShowCursor(SDL_ENABLE);
745       SDL_WarpMouse(vid.xres/2, vid.yres/2);
746       #endif
747       mouseaim_x = mouseaim_y = 0;
748       }
749     #endif
750     }
751 
752 #if ISWEB
753   timetowait = 0;
754 #endif
755 
756   if(timetowait > 0)
757     SDL_Delay(timetowait);
758   else {
759     ors::check_orientation();
760     if(cmode & sm::CENTER) {
761       ld aspd = (ticks - lastt) / 1000.0 * exp(vid.sspeed);
762       if(playermoved && vid.sspeed > -4.99 && !outoffocus)
763         centerpc(aspd);
764       else if(GDIM == 3)
765         spinEdge(aspd);
766 #if CAP_SDLJOY
767       if(panjoyx || panjoyy)
768         checkpanjoy((ticks - lastt) / 1000.0);
769 #endif
770       }
771     tortoise::updateVals(ticks - lastt);
772     frames++;
773     if(!outoffocus) {
774       drawscreen();
775       need_refresh = false;
776       }
777     lastframe = ticks;
778     }
779 
780   wheelclick = false;
781 
782   getcshift = 1;
783 
784   #if CAP_SDL2
785 
786   const Uint8 *keystate = SDL_GetKeyboardState(NULL);
787 
788   pandora_rightclick = keystate[SDL_SCANCODE_RCTRL];
789   pandora_leftclick = keystate[SDL_SCANCODE_RSHIFT];
790 
791   lshiftclick = keystate[SDL_SCANCODE_LSHIFT];
792   rshiftclick = keystate[SDL_SCANCODE_RSHIFT];
793 
794   lctrlclick = keystate[SDL_SCANCODE_LCTRL];
795   rctrlclick = keystate[SDL_SCANCODE_RCTRL];
796 
797   hiliteclick = keystate[SDL_SCANCODE_LALT] | keystate[SDL_SCANCODE_RALT];
798   if(keystate[SDL_SCANCODE_LSHIFT] || keystate[SDL_SCANCODE_RSHIFT]) getcshift = -1;
799   if(keystate[SDL_SCANCODE_LCTRL] || keystate[SDL_SCANCODE_RCTRL]) getcshift /= 10;
800   if(keystate[SDL_SCANCODE_LALT] || keystate[SDL_SCANCODE_RALT]) getcshift *= 10;
801 
802   #else
803 
804   Uint8 *keystate = SDL_GetKeyState(NULL);
805 
806   pandora_rightclick = keystate[SDLK_RCTRL];
807   pandora_leftclick = keystate[SDLK_RSHIFT];
808 
809   lshiftclick = keystate[SDLK_LSHIFT];
810   rshiftclick = keystate[SDLK_RSHIFT];
811 
812   lctrlclick = keystate[SDLK_LCTRL];
813   rctrlclick = keystate[SDLK_RCTRL];
814 
815   hiliteclick = keystate[SDLK_LALT] | keystate[SDLK_RALT];
816   if(keystate[SDLK_LSHIFT] || keystate[SDLK_RSHIFT]) getcshift = -1;
817   if(keystate[SDLK_LCTRL] || keystate[SDLK_RCTRL]) getcshift /= 10;
818   if(keystate[SDLK_LALT] || keystate[SDLK_RALT]) getcshift *= 10;
819 
820   #endif
821 
822   anyshiftclick = lshiftclick | rshiftclick;
823   anyctrlclick = lctrlclick | rctrlclick;
824 
825   forcetarget = anyshiftclick;
826 
827   didsomething = false;
828 
829   if(vid.shifttarget&1) {
830     #if ISPANDORA
831     targetclick = pandora_leftclick | pandora_rightclick;
832     pandora_leftclick = pandora_rightclick = 0;
833     #else
834     targetclick = keystate[SDLK_RSHIFT] | keystate[SDLK_LSHIFT];
835     #endif
836     }
837   else {
838     targetclick = true;
839     }
840 
841 #if CAP_SDLAUDIO
842   if(audio) handlemusic();
843 #endif
844   apply_memory_reserve();
845   SDL_Event ev;
846   DEBB(DF_GRAPH, ("polling for events\n"));
847 
848   #if CAP_VR
849   if(vrhr::active() && !shmup::on) {
850     static int lastticks = ticks;
851     ld t = (ticks - lastticks) * shiftmul / 400;
852     lastticks = ticks;
853 
854     rug::using_rugview urv;
855     dynamicval<bool> ds(didsomething, didsomething);
856     using namespace vrhr;
857     if(vrhr::hsm == vrhr::eHeadset::model_viewing) {
858       E4;
859       transmatrix T = hmd_at * inverse(hmd_ref_at);
860 
861       T =
862         cspin(0, 2, -vraim_x * camera_speed * t) *
863         cspin(1, 2, vraim_y * camera_speed * t) *
864         cpush(0, -vrgo_x * camera_speed * t) *
865         cpush(2, -vrgo_y * camera_speed * t) *
866         T;
867 
868       hmd_ref_at = inverse(T) * hmd_at;
869       }
870     else if(in_perspective_v()) {
871       if(vraim_x) full_rotate_camera(0, -vraim_x * t);
872       if(vraim_y) full_rotate_camera(1, vraim_y * t);
873       if(vrgo_y) full_forward_camera(-vrgo_y * t);
874       if(vrgo_x) full_strafe_camera(-vrgo_x * t);
875       }
876     }
877   #endif
878 
879   if(mouseaiming(shmup::on)) {
880     #if CAP_MOUSEGRAB
881     rug::using_rugview urv;
882     dynamicval<bool> ds(didsomething, didsomething);
883     full_rotate_camera(0, -mouseaim_x);
884     full_rotate_camera(1, -mouseaim_y);
885     mouseaim_x = mouseaim_y = 0;
886     #endif
887     }
888 
889   if(smooth_scrolling && !shmup::on && (cmode & sm::NORMAL)) {
890     rug::using_rugview urv;
891     auto& lastticks = sc_ticks;
892     ld t = (ticks - lastticks) * shiftmul / 1000.;
893     lastticks = ticks;
894 
895     #if CAP_SDL2
896     const Uint8 *keystate = SDL_GetKeyboardState(NULL);
897 
898     if(keystate[SDL_SCANCODE_END] && GDIM == 3 && DEFAULTNOR(SDL_SCANCODE_END)) full_forward_camera(-t);
899     if(keystate[SDL_SCANCODE_HOME] && GDIM == 3 && DEFAULTNOR(SDL_SCANCODE_HOME)) full_forward_camera(t);
900     if(keystate[SDL_SCANCODE_RIGHT] && DEFAULTNOR(SDL_SCANCODE_RIGHT)) full_rotate_camera(0, -t);
901     if(keystate[SDL_SCANCODE_LEFT] && DEFAULTNOR(SDL_SCANCODE_LEFT)) full_rotate_camera(0, t);
902     if(keystate[SDL_SCANCODE_UP] && DEFAULTNOR(SDL_SCANCODE_UP)) full_rotate_camera(1, t);
903     if(keystate[SDL_SCANCODE_DOWN] && DEFAULTNOR(SDL_SCANCODE_DOWN)) full_rotate_camera(1, -t);
904     if(keystate[SDL_SCANCODE_PAGEUP] && DEFAULTNOR(SDL_SCANCODE_PAGEUP)) full_rotate_view(t * 180 / M_PI, t);
905     if(keystate[SDL_SCANCODE_PAGEDOWN] && DEFAULTNOR(SDL_SCANCODE_PAGEDOWN)) full_rotate_view(-t * 180 / M_PI, t);
906 
907     #else
908     Uint8 *keystate = SDL_GetKeyState(NULL);
909 
910     if(keystate[SDLK_END] && GDIM == 3 && DEFAULTNOR(SDLK_END)) full_forward_camera(-t);
911     if(keystate[SDLK_HOME] && GDIM == 3 && DEFAULTNOR(SDLK_HOME)) full_forward_camera(t);
912     if(keystate[SDLK_RIGHT] && DEFAULTNOR(SDLK_RIGHT)) full_rotate_camera(0, -t);
913     if(keystate[SDLK_LEFT] && DEFAULTNOR(SDLK_LEFT)) full_rotate_camera(0, t);
914     if(keystate[SDLK_UP] && DEFAULTNOR(SDLK_UP)) full_rotate_camera(1, t);
915     if(keystate[SDLK_DOWN] && DEFAULTNOR(SDLK_DOWN)) full_rotate_camera(1, -t);
916     if(keystate[SDLK_PAGEUP] && DEFAULTNOR(SDLK_PAGEUP)) full_rotate_view(t * 180 / M_PI, t);
917     if(keystate[SDLK_PAGEDOWN] && DEFAULTNOR(SDLK_PAGEDOWN)) full_rotate_view(-t * 180 / M_PI, t);
918     #endif
919     }
920   else sc_ticks = ticks;
921 
922   #if CAP_VR
923   vrhr::vr_control();
924   #endif
925   achievement_pump();
926 
927   for(auto d: dialog::key_queue) {
928     println(hlog, "handling key ", d);
929     handlekey(d, d);
930     }
931   dialog::key_queue.clear();
932 
933   while(SDL_PollEvent(&ev)) handle_event(ev);
934   fix_mouseh();
935   #if CAP_SDLJOY
936   if(joydir.d != -1) checkjoy();
937   #endif
938   }
939 
940 EX bool need_refresh;
941 
handle_event(SDL_Event & ev)942 EX void handle_event(SDL_Event& ev) {
943   bool normal = cmode & sm::NORMAL;
944     DEBB(DF_GRAPH, ("got event type #%d\n", ev.type));
945     int sym = 0;
946     int uni = 0;
947     shiftmul = 1;
948 
949 /*    if(ev.type == SDL_JOYDEVICEADDED || ev.type == SDL_JOYDEVICEREMOVED) {
950       joyx = joyy = 0;
951       panjoyx = panjoyy = 0;
952       closeJoysticks();
953       initJoysticks();
954       } */
955 
956     #if CAP_SDL2
957     if(ev.type == SDL_WINDOWEVENT) {
958       auto w = ev.window.event;
959       if(w == SDL_WINDOWEVENT_ENTER)
960         outoffocus = false;
961       if(w == SDL_WINDOWEVENT_LEAVE)
962         outoffocus = true;
963       if(w == SDL_WINDOWEVENT_EXPOSED)
964         drawscreen();
965       if(w == SDL_WINDOWEVENT_RESIZED)
966         resize_screen_to(ev.window.data1, ev.window.data2);
967       }
968 
969     #else
970     if(ev.type == SDL_ACTIVEEVENT) {
971       if(ev.active.state & SDL_APPINPUTFOCUS) {
972         if(ev.active.gain) {
973           outoffocus = false;
974           }
975         else {
976           outoffocus = true;
977           }
978         }
979       }
980 
981     if(ev.type == SDL_VIDEORESIZE)
982       resize_screen_to(ev.resize.w, ev.resize.h);
983 
984     if(ev.type == SDL_VIDEOEXPOSE) {
985       drawscreen();
986       }
987     #endif
988 
989 #if CAP_SDLJOY
990     if(ev.type == SDL_JOYAXISMOTION && normal && DEFAULTCONTROL) {
991       if(ev.jaxis.which == 0) {
992         if(ev.jaxis.axis == 0)
993           joyx = ev.jaxis.value;
994         else if(ev.jaxis.axis == 1)
995           joyy = ev.jaxis.value;
996         else if(ev.jaxis.axis == 3)
997           panjoyx = ev.jaxis.value;
998         else if(ev.jaxis.axis == 4)
999           panjoyy = ev.jaxis.value;
1000         checkjoy();
1001         // printf("panjoy = %d,%d\n", panjoyx, panjoyy);
1002         }
1003       else {
1004         if(ev.jaxis.axis == 0)
1005           panjoyx = ev.jaxis.value;
1006         else
1007           panjoyy = ev.jaxis.value;
1008         }
1009       }
1010 
1011     if(joyhandler && joyhandler(ev)) ;
1012 
1013     else if(ev.type == SDL_JOYHATMOTION && !normal) {
1014       if(ev.jhat.value == SDL_HAT_UP) sym = SDLK_UP;
1015       if(ev.jhat.value == SDL_HAT_DOWN) sym = SDLK_DOWN;
1016       if(ev.jhat.value == SDL_HAT_LEFT) sym = SDLK_LEFT;
1017       if(ev.jhat.value == SDL_HAT_RIGHT) sym = SDLK_RIGHT;
1018       }
1019 
1020     else if(ev.type == SDL_JOYBUTTONDOWN && normal && DEFAULTCONTROL) {
1021       flashMessages();
1022       movepcto(joydir);
1023       joy_ignore_next = true;
1024       checkjoy();
1025       }
1026 
1027     else if(ev.type == SDL_JOYBUTTONDOWN && !normal) {
1028       sym = uni = SDLK_RETURN;
1029       }
1030 #endif
1031 
1032     if(ev.type == SDL_KEYDOWN) {
1033       flashMessages();
1034       mousing = false;
1035       sym = ev.key.keysym.sym;
1036       #if CAP_SDL2
1037       uni = ev.key.keysym.sym;
1038       if(uni >= 'a' && uni <= 'z') {
1039         if(ev.key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) uni -= 32;
1040         else if(ev.key.keysym.mod & (KMOD_LCTRL | KMOD_RCTRL)) uni -= 96;
1041         }
1042       #else
1043       uni = ev.key.keysym.unicode;
1044       if(ev.key.keysym.mod & (KMOD_LSHIFT | KMOD_RSHIFT)) shiftmul = -1;
1045       if(ev.key.keysym.mod & (KMOD_LCTRL | KMOD_RCTRL)) shiftmul /= 10;
1046       #endif
1047       numlock_on = ev.key.keysym.mod & KMOD_NUM;
1048       if(sym == SDLK_RETURN && (ev.key.keysym.mod & (KMOD_LALT | KMOD_RALT))) {
1049         sym = 0; uni = 0;
1050         vid.want_fullscreen = !vid.want_fullscreen;
1051         apply_screen_settings();
1052         }
1053       }
1054 
1055     dialog::handleZooming(ev);
1056 
1057     if(sym == SDLK_F1 && normal && playermoved)
1058       help = "@";
1059 
1060     bool rollchange = (cmode & sm::OVERVIEW) && getcstat >= 2000 && cheater;
1061 
1062     if(ev.type == SDL_MOUSEBUTTONDOWN || ev.type == SDL_MOUSEBUTTONUP SDL12(, || ev.type == SDL_MOUSEWHEEL)) {
1063       mousepressed = ev.type == SDL_MOUSEBUTTONDOWN;
1064       if(mousepressed) flashMessages();
1065       mousing = true;
1066       which_pointer = 0;
1067       bool was_holdmouse = holdmouse;
1068       holdmouse = false;
1069 
1070       bool down = ev.type == SDL_MOUSEBUTTONDOWN SDL12(, || ev.type == SDL_MOUSEWHEEL);
1071       bool up = ev.type == SDL_MOUSEBUTTONUP;
1072 
1073       bool act = false;
1074 
1075       if(vid.quickmouse) {
1076         act = down;
1077         }
1078       else {
1079         act = actonrelease && up;
1080         actonrelease = down;
1081         }
1082 
1083       fix_mouseh();
1084 
1085       if(was_holdmouse && up)
1086         sym = uni = PSEUDOKEY_RELEASE;
1087 
1088       /* simulate RMB and MMB for Mac users etc. */
1089       if(ev.button.button == SDL_BUTTON_LEFT) {
1090         if(ISPANDORA ? pandora_rightclick : lctrlclick)
1091           ev.button.button = SDL_BUTTON_MIDDLE;
1092         else if((ISPANDORA ? pandora_leftclick : lshiftclick) && !(vid.shifttarget&1))
1093           ev.button.button = SDL_BUTTON_RIGHT;
1094         }
1095 
1096       if(!act) ;
1097 
1098       else if(ev.button.button==SDL_BUTTON_RIGHT)
1099         sym = SDLK_F1;
1100       else if(ev.button.button==SDL_BUTTON_MIDDLE)
1101         sym = 1, didsomething = true;
1102       else if(ev.button.button == SDL_BUTTON_LEFT) {
1103         sym = getcstat, uni = getcstat, shiftmul = getcshift;
1104         }
1105 
1106       else if(SDL12(ev.button.button==SDL_BUTTON_WHEELDOWN || ev.button.button == SDL_BUTTON_WHEELUP, ev.type == SDL_MOUSEWHEEL)) {
1107         #if CAP_SDL2
1108         ld dir = ev.wheel.y * 0.25;
1109         #else
1110         ld dir = ev.button.button == SDL_BUTTON_WHEELUP ? 0.25 : -0.25;
1111         #endif
1112         if(lshiftclick && rshiftclick && !rug::rugged && GDIM == 2) {
1113           mapeditor::scaleall(pow(2, dir), lctrlclick);
1114           pconf.alpha *= pow(2, dir);
1115           }
1116         else if(lshiftclick && GDIM == 2)
1117           mapeditor::scaleall(pow(2, dir), lctrlclick);
1118         else if(rshiftclick && !rug::rugged && GDIM == 2)
1119           pconf.alpha -= dir;
1120         else if(lctrlclick) {
1121           if(dir>0) {
1122             pconf.xposition += (.0 + mousex - current_display->xcenter) / vpconf.scale / current_display->scrsize;
1123             pconf.yposition += (.0 + mousey - current_display->ycenter) / vpconf.scale / current_display->scrsize;
1124             }
1125           else
1126             pconf.xposition = pconf.yposition = 0;
1127           }
1128         else if(rollchange) {
1129           sym = getcstat, uni = getcstat, shiftmul = -dir*4*getcshift, wheelclick = true;
1130           }
1131         else {
1132           sym = uni = dir > 0 ? PSEUDOKEY_WHEELUP : PSEUDOKEY_WHEELDOWN;
1133           }
1134         }
1135       }
1136 
1137     if(ev.type == SDL_MOUSEMOTION) {
1138       mouseoh = mouseh;
1139 
1140       int lmousex = mousex, lmousey = mousey;
1141 
1142       mousing = true;
1143       which_pointer = 0;
1144       mousemoved = true;
1145       mousex = ev.motion.x;
1146       mousey = ev.motion.y;
1147 
1148       if(mousepan) {
1149         mousex = vid.xres/2;
1150         mousey = vid.yres/2;
1151         mouseaim_x += ev.motion.xrel * mouseaim_sensitivity;
1152         mouseaim_y += ev.motion.yrel * mouseaim_sensitivity;
1153         }
1154 
1155       need_mouseh = true;
1156 
1157       if(holdmouse && getcstat == '-') sym = uni = getcstat, fix_mouseh();
1158 
1159       if(((SDL_GetMouseState(NULL, NULL) & SDL_BUTTON_MMASK)) && !mouseout2()) {
1160         fix_mouseh();
1161         if(lctrlclick) {
1162           pconf.xposition += (mousex - lmousex) * 1. / current_display->scrsize,
1163           pconf.yposition += (mousey - lmousey) * 1. / current_display->scrsize;
1164           }
1165         else if(mouseh[LDIM] < 50 && mouseoh[LDIM] < 50) {
1166           panning(mouseoh, mouseh);
1167           }
1168         }
1169 
1170 #ifdef SIMULATE_JOYSTICK
1171       // pretend that both joysticks are present
1172       stick = panstick = (SDL_Joystick*) (&vid);
1173       panjoyx = 20 * (mousex - current_display->xcenter);
1174       panjoyy = 20 * (mousey - current_display->ycenter);
1175       checkjoy();
1176 #endif
1177 
1178       if(mousepressed && inslider) {
1179         sym = getcstat, uni = getcstat, shiftmul = getcshift;
1180         }
1181       }
1182 
1183     if(ev.type == SDL_QUIT) {
1184       #if CAP_DAILY
1185       if(daily::on) daily::handleQuit(3);
1186       else
1187       #endif
1188       if(needConfirmation() && !(cmode & sm::MISSION)) showMissionScreen();
1189       else quitmainloop = true;
1190       }
1191 
1192     if(sym == SDLK_F4 && anyshiftclick) {
1193       nomap = !nomap;
1194       sym = 0;
1195       }
1196 
1197     if(sym == SDLK_F2 && anyshiftclick) {
1198       nohud = !nohud;
1199       sym = 0;
1200       }
1201 
1202     if(sym == SDLK_F3 && anyshiftclick) {
1203       nofps = !nofps;
1204       sym = 0;
1205       }
1206 
1207     if(sym || uni) {
1208       if(need_refresh) {
1209         just_refreshing = true;
1210         screens.back()();
1211         just_refreshing = false;
1212         }
1213       need_refresh = true;
1214       }
1215 
1216     handlekey(sym, uni);
1217     }
1218 #endif
1219 
mainloop()1220 EX void mainloop() {
1221   if(noGUI) return;
1222   lastt = 0;
1223 #if ISWEB
1224   initweb();
1225   emscripten_set_main_loop(mainloopiter, 0, true);
1226 #else
1227   while(!quitmainloop) mainloopiter();
1228 #endif
1229   }
1230 
1231 #if ISMOBILE
displayabutton(int px,int py,string s,int col)1232 EX void displayabutton(int px, int py, string s, int col) {
1233   // TMP
1234   int siz = vid.yres > vid.xres ? vid.fsize*2 : vid.fsize * 3/2;
1235   int rad = (int) realradius();
1236   if(vid.stereo_mode == sLR) rad = 99999;
1237   int vrx = min(rad, vid.xres/2 - 40);
1238   int vry = min(rad, min(current_display->ycenter, vid.yres - current_display->ycenter) - 20);
1239   int x = current_display->xcenter + px * vrx;
1240   int y = current_display->ycenter + py * (vry - siz/2);
1241   int vrr = int(hypot(vrx, vry) * sqrt(2.));
1242   if(gtouched && !mouseover
1243     && abs(mousex - current_display->xcenter) < vrr
1244     && abs(mousey - current_display->ycenter) < vrr
1245     && hypot(mousex-current_display->xcenter, mousey-current_display->ycenter) > vrr
1246     && px == (mousex > current_display->xcenter ? 1 : -1)
1247     && py == (mousey > current_display->ycenter ? 1 : -1)
1248     ) col = 0xFF0000;
1249   if(displayfr(x, y, 0, siz, s, col, 8+8*px))
1250     buttonclicked = true;
1251   }
1252 #endif
1253 
interpret_as_direction(int sym,int uni)1254 EX bool interpret_as_direction(int sym, int uni) {
1255   #ifdef FAKE_SDL
1256   return false;
1257   #else
1258   return (sym >= SDLK_KP0 && sym <= SDLK_KP9 && !numlock_on);
1259   #endif
1260   }
1261 
get_direction_key(int sym,int uni)1262 EX int get_direction_key(int sym, int uni) {
1263   if(interpret_as_direction(sym, uni)) {
1264     #ifndef FAKE_SDL
1265     if(sym == SDLK_KP1) return SDLK_END;
1266     if(sym == SDLK_KP2) return SDLK_DOWN;
1267     if(sym == SDLK_KP3) return SDLK_PAGEDOWN;
1268     if(sym == SDLK_KP4) return SDLK_LEFT;
1269     if(sym == SDLK_KP6) return SDLK_RIGHT;
1270     if(sym == SDLK_KP7) return SDLK_HOME;
1271     if(sym == SDLK_KP8) return SDLK_UP;
1272     if(sym == SDLK_KP8) return SDLK_PAGEUP;
1273     #endif
1274     return 0;
1275     }
1276   return sym;
1277   }
1278 
gmodekeys(int sym,int uni)1279 EX bool gmodekeys(int sym, int uni) {
1280   #if CAP_RUG
1281   if(rug::rugged && rug::handlekeys(sym, uni)) return true;
1282   #endif
1283 
1284   if(NUMBERKEY == '6') { vid.grid = !vid.grid; return true; }
1285   if(NUMBERKEY == '7') { vid.darkhepta = !vid.darkhepta; return true; }
1286 
1287   if(NUMBERKEY == '1')
1288     pushScreen(models::quick_model);
1289 
1290   if(GDIM == 2) {
1291     if(NUMBERKEY == '5') { vid.wallmode += 60 + (shiftmul > 0 ? 1 : -1); vid.wallmode %= 7; }
1292     else if((NUMBERKEY == '8' && hiliteclick) || NUMBERKEY == 508) {
1293       vid.highlightmode += 60 + (shiftmul > 0 ? 1 : -1); vid.highlightmode %= 3;
1294       }
1295     else if(NUMBERKEY == '8') {
1296       vid.monmode += 60 + (shiftmul > 0 ? 1 : -1); vid.monmode %= 4;
1297       }
1298 
1299     else if(uni == '%') {
1300       if(vid.wallmode == 0) vid.wallmode = 6;
1301       vid.wallmode--;
1302       }
1303     else return false;
1304     return true;
1305     }
1306   else {
1307     if(NUMBERKEY == '5') { vid.wallmode = vid.wallmode == 5 ? 4 : 5; }
1308     else if(NUMBERKEY == '8') {
1309       if(vid.monmode == 0) vid.monmode = 1;
1310       else if(vid.monmode == 1) vid.monmode = 3;
1311       else vid.monmode = 0;
1312       }
1313     else return false;
1314     return true;
1315     }
1316   }
1317 
haveMobileCompass()1318 EX bool haveMobileCompass() {
1319 #if ISMOBILE
1320   if(longclick) return false;
1321 #else
1322   if(forcetarget) return false;
1323 #endif
1324   if(GDIM == 3) return false;
1325   return canmove && !shmup::on && vid.mobilecompasssize > 0 && isize(screens) == 1;
1326   }
1327 
handleCompass()1328 EX bool handleCompass() {
1329   if(!haveMobileCompass()) return false;
1330 
1331   using namespace shmupballs;
1332 
1333   int dx = mousex - xmove;
1334   int dy = mousey - yb;
1335   int h = hypot(dx, dy);
1336   if(h < rad) {
1337     if(h < rad*SKIPFAC) movepcto(MD_WAIT);
1338     else {
1339       hyperpoint param = tangent_length(point2(dx, dy), .01);
1340 
1341       movedir md = vectodir(param);
1342 
1343       if(!canmove) movepcto(md), remission(); else movepcto(md);
1344       }
1345     getcstat = 0;
1346     return true;
1347     }
1348 
1349   return false;
1350   }
1351 
1352 #if CAP_ORIENTATION
1353 EX transmatrix getOrientation();
1354 #endif
1355 
1356 // orientation sensitivity
1357 EX namespace ors {
1358 
1359 EX int mode;
1360 double sensitivity = 1;
1361 
1362 int when_enabled;
1363 transmatrix last_orientation;
1364 transmatrix relative_matrix = Id;
1365 
1366 EX string choices[3] = {"OFF", "relative", "absolute"};
1367 
1368 #if CAP_ORIENTATION
getOrientation()1369 EX transmatrix getOrientation() {
1370   return MirrorX * hr::getOrientation() * MirrorX;
1371   }
1372 #endif
1373 
reset()1374 EX void reset() {
1375 #if CAP_ORIENTATION
1376   if(mode) last_orientation = getOrientation();
1377   relative_matrix = Id;
1378 #endif
1379   }
1380 
delayed_reset()1381 void delayed_reset() {
1382 #if CAP_ORIENTATION
1383   relative_matrix = Id; when_enabled = ticks;
1384 #endif
1385   }
1386 
show()1387 EX void show() {
1388 #if CAP_ORIENTATION
1389   cmode = sm::SIDE | sm::MAYDARK;
1390   gamescreen(0);
1391 
1392   dialog::init(XLAT("scrolling by device rotation"));
1393 
1394   dialog::addHelp(XLAT(
1395     "This lets you scroll the map by rotating your device. It can be e.g. used to "
1396     "play the spherical mode of HyperRogue in mobile VR goggles -- the \"spherical VR\" "
1397     "button configures this; this VR mode can be disabled by touching the screen for 1 second."));
1398 
1399   dialog::addSelItem(XLAT("mode"), choices[mode], 'm');
1400   dialog::add_action([] () { int m = (mode + 1) % 3; mode = 0; fullcenter(); mode = m; delayed_reset(); });
1401   dialog::addSelItem(XLAT("sensitivity"), fts(sensitivity), 's');
1402   dialog::add_action([] () {
1403     dialog::editNumber(sensitivity, -10, 10, 1, 1, XLAT("sensitivity"),
1404         XLAT("1 means that rotating the device by 1 radian corresponds to scrolling by 1 unit. In spherical geometry, 1 unit = 1 radian."));
1405     });
1406 
1407   dialog::addBreak(100);
1408 
1409   dialog::addItem(XLAT("stereo vision config"), 'e');
1410   dialog::add_action_push(showStereo);
1411 
1412   dialog::addItem(XLAT("experiment with geometry"), 'g');
1413   dialog::add_action([] () { runGeometryExperiments(); });
1414 
1415   dialog::addSelItem(XLAT("projection"), fts(vpconf.alpha), 'p');
1416   dialog::add_action([] () { projectionDialog(); });
1417 
1418   dialog::addSelItem(XLAT("scale factor"), fts(vpconf.scale), 'z');
1419   dialog::add_action([] () { editScale(); });
1420 
1421   dialog::addItem(XLAT("spherical VR"), 'v');
1422   dialog::add_action([] () {
1423     if(!sphere) set_geometry(gSphere), start_game();
1424     mode = 0; fullcenter();
1425     mode = 2; sensitivity = 1;
1426     vid.stereo_mode = sLR; vid.ipd = 0.2;
1427     vpconf.alpha = 0; vpconf.scale = 1;
1428     });
1429 
1430   dialog::addBreak(100);
1431 
1432   dialog::addBack();
1433 
1434   dialog::display();
1435 #endif
1436   }
1437 
relative_apply()1438 void relative_apply() {
1439   if(ors::mode == 1) View = relative_matrix * View;
1440   }
1441 
relative_unapply()1442 void relative_unapply() {
1443   if(ors::mode == 1) View = inverse(relative_matrix) * View;
1444   }
1445 
change_geometry(const transmatrix & T)1446 transmatrix change_geometry(const transmatrix& T) {
1447   if(sphere && sensitivity == 1) return T;
1448   ld alpha, beta, push;
1449 
1450   {
1451   dynamicval<eGeometry> g(geometry, gSphere);
1452   hyperpoint h = T * C0;
1453   push = hdist0(h);
1454   alpha = atan2(h[1], h[0]);
1455   if(push == 0) alpha = 0;
1456   hyperpoint spinpoint = gpushxto0(h) * T * xpush0(1);
1457   beta = atan2(spinpoint[1], spinpoint[0]);
1458   }
1459 
1460   // gpushxto0(h) * T * xpush(1) * C0 == spin(beta) * xpush(1) * C0
1461   // gpushxto0(h) * T == spin(beta)
1462   // T = rgpushxto0(h) * spin(beta)
1463 
1464 
1465   transmatrix U = spin(-alpha) * xpush(push * sensitivity) * spin(-beta+alpha);
1466 
1467   return U;
1468   }
1469 
unrotate(transmatrix & T)1470 EX void unrotate(transmatrix& T) {
1471   if(mode == 1) T = inverse(relative_matrix) * T;
1472   }
1473 
rerotate(transmatrix & T)1474 EX void rerotate(transmatrix& T) {
1475   if(mode == 1) T = (relative_matrix) * T;
1476   }
1477 
1478 int first_check, last_check;
1479 
check_orientation()1480 EX void check_orientation() {
1481 #if CAP_ORIENTATION
1482   if(!mode) return;
1483 
1484   if(ticks > last_check + 2000) first_check = ticks;
1485   last_check = ticks;
1486 
1487   if(ticks < first_check + 500) {
1488     last_orientation = getOrientation();
1489     return;
1490     }
1491   transmatrix next_orientation = MirrorX * getOrientation();
1492   transmatrix T = inverse(next_orientation) * last_orientation;
1493   if(mode == 1) unrotate(View), unrotate(cwtV.T);
1494   relative_matrix = change_geometry(T);
1495   if(mode == 1) rerotate(View), rerotate(cwtV.T);
1496   if(mode == 2) View = relative_matrix * View, last_orientation = next_orientation;
1497 #endif
1498   }
1499 
1500 EX }
1501 
1502 }
1503