1 // Hyperbolic Rogue -- multiplayer features
2 // Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
3 
4 /** \file multi.cpp
5  *  \brief multiplayer features, also input configuration
6  */
7 
8 #include "hyper.h"
9 namespace hr {
10 
11 EX namespace multi {
12 
13   #if HDR
14   static const int MAXJOY = 8;
15   static const int MAXBUTTON = 64;
16   static const int MAXAXE = 16;
17   static const int MAXHAT = 4;
18 
19   struct config {
20     char keyaction[512];
21     char joyaction[MAXJOY][MAXBUTTON];
22     char axeaction[MAXJOY][MAXAXE];
23     char hataction[MAXJOY][MAXHAT][4];
24     int  deadzoneval[MAXJOY][MAXAXE];
25     };
26   #endif
27 
28   EX config scfg;
29   EX charstyle scs[MAXPLAYER];
30 
31   EX int players = 1;
32   EX cellwalker player[MAXPLAYER];
33   EX vector<int> revive_queue; // queue for revival
34 
35   EX cell *origpos[MAXPLAYER], *origtarget[MAXPLAYER];
36 
37   EX bool flipped[MAXPLAYER];
38 
39   // treasure collection, kill, and death statistics
40   EX int treasures[MAXPLAYER], kills[MAXPLAYER], deaths[MAXPLAYER];
41 
42   EX bool alwaysuse = false;
43 
recall()44   EX void recall() {
45     for(int i=0; i<numplayers(); i++) {
46         int idir = (3 * i) % cwt.at->type;
47         cell *c2 = cwt.at->move(idir);
48         makeEmpty(c2);
49         if(!passable(c2, NULL, P_ISPLAYER)) c2 = cwt.at;
50         multi::player[i].at = c2;
51         multi::player[i].spin = 0;
52 
53         multi::flipped[i] = true;
54         multi::whereto[i].d = MD_UNDECIDED;
55         }
56     }
57 
58   EX shiftmatrix whereis[MAXPLAYER];
59   EX shiftmatrix crosscenter[MAXPLAYER];
60   EX double ccdist[MAXPLAYER];
61   EX cell *ccat[MAXPLAYER];
62 
63   bool combo[MAXPLAYER];
64 
65   EX int cpid; // player id -- an extra parameter for player-related functions
66   EX int cpid_edit; // cpid currently being edited
67 
68   EX movedir whereto[MAXPLAYER]; // player's target cell
69 
70   EX double mdx[MAXPLAYER], mdy[MAXPLAYER]; // movement vector for the next move
71 
72   static const int CMDS = 15;
73   static const int CMDS_PAN = 11;
74 
75   vector<string> playercmds_shmup = {
76     "forward", "backward", "turn left", "turn right",
77     "move up", "move right", "move down", "move left",
78     "throw a knife", "face the pointer", "throw at the pointer",
79     "drop Dead Orb", "center the map on me", "Orb power (target: mouse)",
80     "Orb power (target: facing)"
81     };
82 
83   vector<string> playercmds_shmup3 = {
84     "rotate up", "rotate down", "rotate left", "rotate right",
85     "move forward", "strafe right", "move backward", "strafe left",
86     "throw a knife", "face the pointer", "throw at the pointer",
87     "drop Dead Orb", "center the map on me", "Orb power (target: mouse)",
88     "Orb power (target: facing)"
89     };
90 
91   vector<string> playercmds_turn = {
92     "move up-right", "move up-left", "move down-right", "move down-left",
93     "move up", "move right", "move down", "move left",
94     "stay in place (left + right)", "cancel move", "leave the game",
95     "drop Dead Orb (up + down)", "center the map on me", "",
96     ""
97     };
98 
99   vector<string> pancmds = {
100     "pan up", "pan right", "pan down", "pan left",
101     "rotate left", "rotate right", "home",
102     "world overview", "review your quest", "inventory", "main menu"
103     };
104 
105   vector<string> pancmds3 = {
106     "look up", "look right", "look down", "look left",
107     "rotate left", "rotate right", "home",
108     "world overview", "review your quest", "inventory", "main menu",
109     "scroll forward", "scroll backward"
110     };
111 
112 #if HDR
113 #define SHMUPAXES_BASE 4
114 #define SHMUPAXES ((SHMUPAXES_BASE) + 4 * (MAXPLAYER))
115 #define SHMUPAXES_CUR ((SHMUPAXES_BASE) + 4 * playercfg)
116 #endif
117 
118 EX const char* axemodes[SHMUPAXES] = {
119   "do nothing",
120   "rotate view",
121   "panning X",
122   "panning Y",
123   "player 1 X",
124   "player 1 Y",
125   "player 1 go",
126   "player 1 spin",
127   "player 2 X",
128   "player 2 Y",
129   "player 2 go",
130   "player 2 spin",
131   "player 3 X",
132   "player 3 Y",
133   "player 3 go",
134   "player 3 spin",
135   "player 4 X",
136   "player 4 Y",
137   "player 4 go",
138   "player 4 spin",
139   "player 5 X",
140   "player 5 Y",
141   "player 5 go",
142   "player 5 spin",
143   "player 6 X",
144   "player 6 Y",
145   "player 6 go",
146   "player 6 spin",
147   "player 7 X",
148   "player 7 Y",
149   "player 7 go",
150   "player 7 spin"
151   };
152 
153 EX const char* axemodes3[4] = {
154   "do nothing",
155   "camera forward",
156   "camera rotate X",
157   "camera rotate Y"
158   };
159 
160 EX int centerplayer = -1;
161 
162 char* axeconfigs[24]; int numaxeconfigs;
163 int* dzconfigs[24];
164 
listkeys(int id)165 string listkeys(int id) {
166 #if CAP_SDL
167   string lk = "";
168   for(int i=0; i<512; i++)
169     if(scfg.keyaction[i] == id)
170       #if CAP_SDL2
171       lk = lk + " " + SDL_GetScancodeName(SDL_Scancode(i));
172       #else
173       lk = lk + " " + SDL_GetKeyName(SDLKey(i));
174       #endif
175 #if CAP_SDLJOY
176   for(int i=0; i<numsticks; i++) for(int k=0; k<SDL_JoystickNumButtons(sticks[i]) && k<MAXBUTTON; k++)
177     if(scfg.joyaction[i][k] == id) {
178       lk = lk + " " + cts('A'+i)+"-B"+its(k);
179       }
180   for(int i=0; i<numsticks; i++) for(int k=0; k<SDL_JoystickNumHats(sticks[i]) && k<MAXHAT; k++)
181     for(int d=0; d<4; d++)
182       if(scfg.hataction[i][k][d] == id) {
183         lk = lk + " " + cts('A'+i)+"-"+"URDL"[d];
184         }
185 #endif
186   return lk;
187 #else
188   return "";
189 #endif
190   }
191 
192 #define SCJOY 16
193 
dsc(int id)194 string dsc(int id) {
195   char buf[64];
196   sprintf(buf, " (%d $$$, %d kills, %d deaths)",
197     multi::treasures[id],
198     multi::kills[id],
199     multi::deaths[id]
200     );
201   return buf;
202   }
203 
resetScores()204 EX void resetScores() {
205   for(int i=0; i<MAXPLAYER; i++)
206     multi::treasures[i] = multi::kills[i] = multi::deaths[i] = 0;
207   }
208 
209 bool configdead;
210 
211 void handleConfig(int sym, int uni);
212 
player_count_name(int p)213 EX string player_count_name(int p) {
214   return
215     p == 2 ? XLAT("two players") :
216     p == 3 ? XLAT("three players") :
217     p == 4 ? XLAT("four players") :
218     p == 5 ? XLAT("five players") :
219     p == 6 ? XLAT("six players") :
220     p == 7 ? XLAT("seven players") :
221     XLAT("one player");
222   }
223 
224 struct key_configurer {
225 
226   int sc;
227   vector<string>& shmupcmdtable;
228   string caption;
229   int setwhat;
230 
key_configurerhr::multi::key_configurer231   key_configurer(int sc, vector<string>& sct, const string& caption) : sc(sc), shmupcmdtable(sct), caption(caption), setwhat(0) {
232     }
233 
operator ()hr::multi::key_configurer234   void operator() () {
235 
236     dialog::init(caption);
237 
238     getcstat = ' ';
239 
240     for(int i=0; i<isize(shmupcmdtable); i++) if(shmupcmdtable[i][0])
241       dialog::addSelItem(XLAT(shmupcmdtable[i]), listkeys(16*sc+i),
242         setwhat ? (setwhat>1 && i == (setwhat&15) ? '?' : 0) : 'a'+i);
243       else dialog::addBreak(100);
244 
245     if(setwhat == 1)
246       dialog::addItem(XLAT("press a key to unassign"), 0);
247     else if(setwhat)
248       dialog::addItem(XLAT("press a key for '%1'", XLAT(shmupcmdtable[setwhat&15])), 0);
249     else
250       dialog::addItem(XLAT("unassign a key"), 'z');
251 
252     dialog::display();
253 
254     keyhandler = [this] (int sym, int uni) {
255       if(!setwhat) dialog::handleNavigation(sym, uni);
256       if(sym) {
257         if(setwhat) {
258           scfg.keyaction[sym] = setwhat;
259           setwhat = 0;
260           }
261         else if(uni >= 'a' && uni < 'a' + isize(shmupcmdtable) && shmupcmdtable[uni-'a'][0])
262           setwhat = 16*sc+uni - 'a';
263         else if(uni == 'z')
264           setwhat = 1;
265         else if(doexiton(sym, uni))
266           popScreen();
267         }
268       };
269 
270 #if CAP_SDLJOY
271     joyhandler = [this] (SDL_Event& ev) {
272       if(ev.type == SDL_JOYBUTTONDOWN && setwhat) {
273         int joyid = ev.jbutton.which;
274         int button = ev.jbutton.button;
275         if(joyid < 8 && button < 32)
276            scfg.joyaction[joyid][button] = setwhat;
277         setwhat = 0;
278         return true;
279         }
280 
281       else if(ev.type == SDL_JOYHATMOTION && setwhat) {
282         int joyid = ev.jhat.which;
283         int hat = ev.jhat.hat;
284         int dir = 4;
285         if(ev.jhat.value == SDL_HAT_UP) dir = 0;
286         if(ev.jhat.value == SDL_HAT_RIGHT) dir = 1;
287         if(ev.jhat.value == SDL_HAT_DOWN) dir = 2;
288         if(ev.jhat.value == SDL_HAT_LEFT) dir = 3;
289         printf("%d %d %d\n", joyid, hat, dir);
290         if(joyid < 8 && hat < 4 && dir < 4) {
291           scfg.hataction[joyid][hat][dir] = setwhat;
292           setwhat = 0;
293           return true;
294           }
295         }
296       return false;
297       };
298 #endif
299     }
300   };
301 
get_key_configurer(int sc,vector<string> & sct,string caption)302 EX reaction_t get_key_configurer(int sc, vector<string>& sct, string caption) {
303   return key_configurer(sc, sct, caption);
304   }
305 
get_key_configurer(int sc,vector<string> & sct)306 EX reaction_t get_key_configurer(int sc, vector<string>& sct) {
307   return key_configurer(sc, sct, sc == 1 ? XLAT("configure player 1") :
308     sc == 2 ? XLAT("configure player 2") :
309     sc == 3 ? XLAT("configure panning") :
310     sc == 4 ? XLAT("configure player 3") :
311     sc == 5 ? XLAT("configure player 4") :
312     sc == 6 ? XLAT("configure player 5") :
313     sc == 7 ? XLAT("configure player 6") :
314     sc == 8 ? XLAT("configure player 7") : ""
315     );
316   }
317 
318 #if CAP_SDLJOY
319 struct joy_configurer {
320 
321   bool shmupcfg, racecfg;
322   int playercfg;
joy_configurerhr::multi::joy_configurer323   joy_configurer(int playercfg) : playercfg(playercfg) {}
324 
operator ()hr::multi::joy_configurer325   void operator() () {
326     dialog::init();
327     getcstat = ' ';
328     numaxeconfigs = 0;
329     for(int j=0; j<numsticks; j++) {
330       for(int ax=0; ax<SDL_JoystickNumAxes(sticks[j]) && ax < MAXAXE; ax++) if(numaxeconfigs<24) {
331         int y = SDL_JoystickGetAxis(sticks[j], ax);
332         string buf = " ";
333         if(configdead)
334           buf += its(y);
335         else {
336           while(y >  10000) buf += "+", y -= 10000;
337           while(y < -10000) buf += "-", y += 10000;
338           if(y>0) buf += "+";
339           if(y<0) buf += "-";
340           }
341         axeconfigs[numaxeconfigs] = &(scfg.axeaction[j][ax]);
342         dzconfigs[numaxeconfigs] = &(scfg.deadzoneval[j][ax]);
343         char aa = *axeconfigs[numaxeconfigs];
344         string what = configdead ? its(scfg.deadzoneval[j][ax]) :
345           (GDIM == 3 && (aa%SHMUPAXES < 4)) ? XLAT(axemodes3[aa%SHMUPAXES]) :
346           XLAT(axemodes[aa%SHMUPAXES]);
347         dialog::addSelItem(XLAT("Joystick %1, axis %2", cts('A'+j), its(ax)) + buf,
348           what, 'a'+numaxeconfigs);
349         numaxeconfigs++;
350         }
351       }
352 
353     dialog::addBoolItem(XLAT("Configure dead zones"), (configdead), 'z');
354     dialog::display();
355 
356     keyhandler = [this] (int sym, int uni) {
357       dialog::handleNavigation(sym, uni);
358       if(sym) {
359         char xuni = uni | 96;
360         if(xuni >= 'a' && xuni < 'a' + numaxeconfigs) {
361           if(configdead)
362             dialog::editNumber( (*dzconfigs[xuni - 'a']), 0, 65536, 100, 0, XLAT("Configure dead zones"), "");
363           else {
364             int v = (*axeconfigs[xuni - 'a']);
365             v += (shiftmul>0?1:-1);
366             v += SHMUPAXES_CUR;
367             v %= SHMUPAXES_CUR;
368             (*axeconfigs[xuni - 'a']) = v;
369             }
370           }
371         else if(xuni == 'z')
372           configdead = !configdead;
373         else if(doexiton(sym, uni))
374           popScreen();
375         }
376       };
377     }
378   };
379 #endif
380 
381 EX const char *axmodes[7] = {"OFF", "auto", "light", "heavy", "arrows", "WASD keys", "VI keys"};
382 
383 struct shmup_configurer {
384 
operator ()hr::multi::shmup_configurer385   void operator()() {
386   #if CAP_SDL
387     cmode = sm::SHMUPCONFIG;
388     gamescreen(3);
389     dialog::init(XLAT("keyboard & joysticks"));
390 
391     bool haveconfig = shmup::on || players > 1 || multi::alwaysuse;
392 
393     if(haveconfig)
394       dialog::addItem(XLAT("configure player 1"), '1');
395     else
396       dialog::addBreak(100);
397     if(players > 1)
398       dialog::addItem(XLAT("configure player 2"), '2');
399     else if(players == 1 && !shmup::on)
400       dialog::addSelItem(XLAT("input"), multi::alwaysuse ? XLAT("config") : XLAT("default"), 'a');
401     else
402       dialog::addBreak(100);
403     if(players > 2)
404       dialog::addItem(XLAT("configure player 3"), '3');
405   #if CAP_SDLJOY
406     else if(!haveconfig)
407       dialog::addItem(XLAT("old style joystick configuration"), 'b');
408   #endif
409     else dialog::addBreak(100);
410     if(players > 3)
411       dialog::addItem(XLAT("configure player 4"), '4');
412     else if(!shmup::on && !multi::alwaysuse) {
413       dialog::addBoolItem(XLAT("smooth scrolling"), smooth_scrolling, 'c');
414       }
415     else if(alwaysuse)
416       dialog::addInfo(XLAT("note: configured input is designed for"));
417     else dialog::addBreak(100);
418 
419     if(players > 4)
420       dialog::addItem(XLAT("configure player 5"), '5');
421     else if(!shmup::on && !multi::alwaysuse) {
422       if(GDIM == 2) {
423         dialog::addSelItem(XLAT("help for keyboard users"), XLAT(axmodes[vid.axes]), 'h');
424         dialog::add_action([] {vid.axes += 70 + (shiftmul > 0 ? 1 : -1); vid.axes %= 7; } );
425         }
426       else dialog::addBreak(100);
427       }
428     else if(alwaysuse)
429       dialog::addInfo(XLAT("multiplayer and shmup mode; some features"));
430     else dialog::addBreak(100);
431 
432     if(players > 5)
433       dialog::addItem(XLAT("configure player 6"), '6');
434     else if(alwaysuse)
435       dialog::addInfo(XLAT("work worse if you use it."));
436     else dialog::addBreak(100);
437 
438     if(players > 6)
439       dialog::addItem(XLAT("configure player 7"), '7');
440     else dialog::addBreak(100);
441 
442     if(shmup::on || multi::alwaysuse || players > 1)
443       dialog::addItem(XLAT("configure panning and general keys"), 'p');
444     else dialog::addBreak(100);
445 
446   #if CAP_SDLJOY
447     if(numsticks > 0) {
448       if(shmup::on || multi::alwaysuse || players > 1)
449         dialog::addItem(XLAT("configure joystick axes"), 'j');
450       else dialog::addBreak(100);
451       }
452   #endif
453 
454     dialog::addBreak(50);
455 
456     dialog::addHelp();
457 
458     dialog::addBack();
459     dialog::display();
460 
461     keyhandler = [this] (int sym, int uni) { return handleConfig(sym, uni); };
462   #endif
463     }
464 
handleConfighr::multi::shmup_configurer465   void handleConfig(int sym, int uni) {
466     auto& cmdlist = shmup::on ? (WDIM == 3 ? playercmds_shmup3 : playercmds_shmup) : playercmds_turn;
467     dialog::handleNavigation(sym, uni);
468 
469     if(0) ;
470     #if CAP_SDL
471     else if(uni == '1') pushScreen(get_key_configurer(1, cmdlist));
472     else if(uni == '2') pushScreen(get_key_configurer(2, cmdlist));
473     else if(uni == 'p') pushScreen(get_key_configurer(3, GDIM == 3 ? pancmds3 : pancmds));
474     else if(uni == '3') pushScreen(get_key_configurer(4, cmdlist));
475     else if(uni == '4') pushScreen(get_key_configurer(5, cmdlist));
476     else if(uni == '5') pushScreen(get_key_configurer(6, cmdlist));
477     else if(uni == '6') pushScreen(get_key_configurer(7, cmdlist));
478     else if(uni == '7') pushScreen(get_key_configurer(8, cmdlist));
479   #if CAP_SDLJOY
480     else if(uni == 'j') pushScreen(joy_configurer(players));
481   #endif
482     else if(uni == 'a') multi::alwaysuse = !multi::alwaysuse;
483   #if CAP_SDLJOY
484     else if(uni == 'b') pushScreen(showJoyConfig);
485   #endif
486     else if(uni == 'c') smooth_scrolling = !smooth_scrolling;
487     #endif
488     else if(doexiton(sym, uni)) popScreen();
489     }
490   };
491 
configure()492 EX void configure() {
493   pushScreen(shmup_configurer());
494   }
495 
showConfigureMultiplayer()496 EX void showConfigureMultiplayer() {
497   gamescreen(1);
498   dialog::init("multiplayer");
499 
500   for(int i=1; i <= MAXPLAYER; i++) {
501     string s = player_count_name(i);
502     if(i <= players) s += dsc(i-1);
503     dialog::addBoolItem(s, i == multi::players, '0' + i);
504     if(!dual::state) dialog::add_action([i] {
505       dialog::do_if_confirmed([i] {
506         stop_game();
507         players = i;
508         start_game();
509         });
510       });
511     }
512 
513   if(multi::players > 1) {
514     dialog::addItem(XLAT("reset per-player statistics"), 'r');
515     dialog::add_action([] {
516       for(int i=0; i<MAXPLAYER; i++)
517         kills[i] = deaths[i] = treasures[i] = 0;
518       });
519 
520     dialog::addSelItem(XLAT("keyboard & joysticks"), "", 'k');
521     dialog::add_action(multi::configure);
522     }
523   else dialog::addBreak(200);
524 
525   dialog::addBack();
526   dialog::display();
527   }
528 
529 #if HDR
530 #define NUMACT 128
531 
532 enum pcmds {
533   pcForward, pcBackward, pcTurnLeft, pcTurnRight,
534   pcMoveUp, pcMoveRight, pcMoveDown, pcMoveLeft,
535   pcFire, pcFace, pcFaceFire,
536   pcDrop, pcCenter, pcOrbPower, pcOrbKey
537   };
538 #endif
539 
540 EX int actionspressed[NUMACT], axespressed[SHMUPAXES], lactionpressed[NUMACT];
541 
pressaction(int id)542 void pressaction(int id) {
543   if(id >= 0 && id < NUMACT)
544     actionspressed[id]++;
545   }
546 
notremapped(int sym)547 EX bool notremapped(int sym) {
548   int k = scfg.keyaction[sym];
549   if(k == 0) return true;
550   k /= 16;
551   if(k > 3) k--; else if(k==3) k = 0;
552   return k > multi::players;
553   }
554 
initConfig()555 EX void initConfig() {
556 
557   char* t = scfg.keyaction;
558 
559   #if CAP_SDL2
560 
561   t[SDL_SCANCODE_W] = 16 + 4;
562   t[SDL_SCANCODE_D] = 16 + 5;
563   t[SDL_SCANCODE_S] = 16 + 6;
564   t[SDL_SCANCODE_A] = 16 + 7;
565 
566   t[SDL_SCANCODE_KP_8] = 16 + 4;
567   t[SDL_SCANCODE_KP_6] = 16 + 5;
568   t[SDL_SCANCODE_KP_2] = 16 + 6;
569   t[SDL_SCANCODE_KP_4] = 16 + 7;
570 
571   t[SDL_SCANCODE_F] = 16 + pcFire;
572   t[SDL_SCANCODE_G] = 16 + pcFace;
573   t[SDL_SCANCODE_H] = 16 + pcFaceFire;
574   t[SDL_SCANCODE_R] = 16 + pcDrop;
575   t[SDL_SCANCODE_T] = 16 + pcOrbPower;
576   t[SDL_SCANCODE_Y] = 16 + pcCenter;
577 
578   t[SDL_SCANCODE_I] = 32 + 4;
579   t[SDL_SCANCODE_L] = 32 + 5;
580   t[SDL_SCANCODE_K] = 32 + 6;
581   t[SDL_SCANCODE_J] = 32 + 7;
582   t[SDL_SCANCODE_SEMICOLON] = 32 + 8;
583   t[SDL_SCANCODE_APOSTROPHE] = 32 + 9;
584   t[SDL_SCANCODE_P] = 32 + 10;
585   t[SDL_SCANCODE_LEFTBRACKET] = 32 + pcCenter;
586 
587   t[SDL_SCANCODE_UP] = 48 ;
588   t[SDL_SCANCODE_RIGHT] = 48 + 1;
589   t[SDL_SCANCODE_DOWN] = 48 + 2;
590   t[SDL_SCANCODE_LEFT] = 48 + 3;
591   t[SDL_SCANCODE_PAGEUP] = 48 + 4;
592   t[SDL_SCANCODE_PAGEDOWN] = 48 + 5;
593   t[SDL_SCANCODE_HOME] = 48 + 6;
594 
595   #else
596   t[(int)'w'] = 16 + 4;
597   t[(int)'d'] = 16 + 5;
598   t[(int)'s'] = 16 + 6;
599   t[(int)'a'] = 16 + 7;
600 
601 #if !ISMOBILE
602   t[SDLK_KP8] = 16 + 4;
603   t[SDLK_KP6] = 16 + 5;
604   t[SDLK_KP2] = 16 + 6;
605   t[SDLK_KP4] = 16 + 7;
606 #endif
607 
608   t[(int)'f'] = 16 + pcFire;
609   t[(int)'g'] = 16 + pcFace;
610   t[(int)'h'] = 16 + pcFaceFire;
611   t[(int)'r'] = 16 + pcDrop;
612   t[(int)'t'] = 16 + pcOrbPower;
613   t[(int)'y'] = 16 + pcCenter;
614 
615   t[(int)'i'] = 32 + 4;
616   t[(int)'l'] = 32 + 5;
617   t[(int)'k'] = 32 + 6;
618   t[(int)'j'] = 32 + 7;
619   t[(int)';'] = 32 + 8;
620   t[(int)'\''] = 32 + 9;
621   t[(int)'p'] = 32 + 10;
622   t[(int)'['] = 32 + pcCenter;
623 
624 #if !ISMOBILE
625   t[SDLK_UP] = 48 ;
626   t[SDLK_RIGHT] = 48 + 1;
627   t[SDLK_DOWN] = 48 + 2;
628   t[SDLK_LEFT] = 48 + 3;
629   t[SDLK_PAGEUP] = 48 + 4;
630   t[SDLK_PAGEDOWN] = 48 + 5;
631   t[SDLK_HOME] = 48 + 6;
632 #endif
633   #endif
634 
635   scfg.joyaction[0][0] = 16 + pcFire;
636   scfg.joyaction[0][1] = 16 + pcOrbPower;
637   scfg.joyaction[0][2] = 16 + pcDrop;
638   scfg.joyaction[0][3] = 16 + pcCenter;
639   scfg.joyaction[0][4] = 16 + pcFace;
640   scfg.joyaction[0][5] = 16 + pcFaceFire;
641 
642   scfg.joyaction[1][0] = 32 + pcFire;
643   scfg.joyaction[1][1] = 32 + pcOrbPower;
644   scfg.joyaction[1][2] = 32 + pcDrop;
645   scfg.joyaction[1][3] = 32 + pcCenter;
646   scfg.joyaction[1][4] = 32 + pcFace;
647   scfg.joyaction[1][5] = 32 + pcFaceFire;
648 
649   scfg.axeaction[0][0] = 4;
650   scfg.axeaction[0][1] = 5;
651   scfg.axeaction[0][3] = 2;
652   scfg.axeaction[0][4] = 3;
653 
654   scfg.axeaction[1][0] = 8;
655   scfg.axeaction[1][1] = 9;
656 
657   // ULRD
658   scfg.hataction[0][0][0] = 16 + 0;
659   scfg.hataction[0][0][1] = 16 + 3;
660   scfg.hataction[0][0][2] = 16 + 1;
661   scfg.hataction[0][0][3] = 16 + 2;
662   scfg.hataction[0][1][0] = 16 + 4;
663   scfg.hataction[0][1][1] = 16 + 7;
664   scfg.hataction[0][1][2] = 16 + 5;
665   scfg.hataction[0][1][3] = 16 + 6;
666 
667   scfg.hataction[1][0][0] = 32 + 0;
668   scfg.hataction[1][0][1] = 32 + 3;
669   scfg.hataction[1][0][2] = 32 + 1;
670   scfg.hataction[1][0][3] = 32 + 2;
671   scfg.hataction[1][1][0] = 32 + 4;
672   scfg.hataction[1][1][1] = 32 + 7;
673   scfg.hataction[1][1][2] = 32 + 5;
674   scfg.hataction[1][1][3] = 32 + 6;
675 
676   int charidtable[MAXPLAYER] = {0, 1, 4, 6, 2, 3, 0};
677 
678   for(int i=0; i<MAXPLAYER; i++) {
679     initcs(multi::scs[i]);
680     multi::scs[i].charid = charidtable[i];
681     }
682 
683   multi::scs[0].uicolor = 0xC00000FF;
684   multi::scs[1].uicolor = 0x00C000FF;
685   multi::scs[2].uicolor = 0x0000C0FF;
686   multi::scs[3].uicolor = 0xC0C000FF;
687   multi::scs[4].uicolor = 0xC000C0FF;
688   multi::scs[5].uicolor = 0x00C0C0FF;
689   multi::scs[6].uicolor = 0xC0C0C0FF;
690 
691   #if CAP_CONFIG
692   addsaver(multi::players, "mode-number of players");
693   addsaver(alwaysuse, "use configured keys");
694   // unfortunately we cannot use key names here because SDL is not yet initialized
695   for(int i=0; i<512; i++)
696     addsaver(scfg.keyaction[i], string("key:")+its(i));
697   for(int i=0; i<MAXJOY; i++) {
698     string pre = "joystick "+cts('A'+i);
699     for(int j=0; j<MAXBUTTON; j++)
700       addsaver(scfg.joyaction[i][j], pre+"-B"+its(j));
701     for(int j=0; j<MAXAXE; j++) {
702       addsaver(scfg.axeaction[i][j], pre+" axis "+its(j));
703       addsaver(scfg.deadzoneval[i][j], pre+" deadzone "+its(j));
704       }
705     for(int j=0; j<MAXHAT; j++) for(int k=0; k<4; k++) {
706       addsaver(scfg.hataction[i][j][k], pre+" hat "+its(j)+" "+"URDL"[k]);
707       }
708     }
709   for(int i=0; i<7; i++) addsaver(multi::scs[i], "player"+its(i));
710   #endif
711   }
712 
handleInput(int delta)713 EX void handleInput(int delta) {
714 #if CAP_SDL
715   double d = delta / 500.;
716 
717   const Uint8 *keystate = SDL12_GetKeyState(NULL);
718 
719   for(int i=0; i<NUMACT; i++)
720     lactionpressed[i] = actionspressed[i],
721     actionspressed[i] = 0;
722 
723   for(int i=0; i<SHMUPAXES; i++) axespressed[i] = 0;
724 
725   for(int i=0; i<KEYSTATES; i++) if(keystate[i])
726     pressaction(scfg.keyaction[i]);
727 
728 #if CAP_SDLJOY
729   for(int j=0; j<numsticks; j++) {
730 
731     for(int b=0; b<SDL_JoystickNumButtons(sticks[j]) && b<MAXBUTTON; b++)
732       if(SDL_JoystickGetButton(sticks[j], b))
733         pressaction(scfg.joyaction[j][b]);
734 
735     for(int b=0; b<SDL_JoystickNumHats(sticks[j]) && b<MAXHAT; b++) {
736       int stat = SDL_JoystickGetHat(sticks[j], b);
737       if(stat & SDL_HAT_UP) pressaction(scfg.hataction[j][b][0]);
738       if(stat & SDL_HAT_RIGHT) pressaction(scfg.hataction[j][b][1]);
739       if(stat & SDL_HAT_DOWN) pressaction(scfg.hataction[j][b][2]);
740       if(stat & SDL_HAT_LEFT) pressaction(scfg.hataction[j][b][3]);
741       }
742 
743     for(int b=0; b<SDL_JoystickNumAxes(sticks[j]) && b<MAXAXE; b++) {
744       int value = SDL_JoystickGetAxis(sticks[j], b);
745       int dz = scfg.deadzoneval[j][b];
746       if(value > dz) value -= dz; else if(value < -dz) value += dz;
747       else value = 0;
748       axespressed[scfg.axeaction[j][b] % SHMUPAXES] += value;
749       }
750     }
751 #endif
752 
753   if(keystate[SDLK_LCTRL] || keystate[SDLK_RCTRL]) d /= 5;
754 
755   double panx =
756     actionspressed[49] - actionspressed[51] + axespressed[2] / 32000.0;
757   double pany =
758     actionspressed[50] - actionspressed[48] + axespressed[3] / 32000.0;
759 
760   double panspin = actionspressed[52] - actionspressed[53];
761 
762   double panmove = actionspressed[59] - actionspressed[60];
763 
764   if(GDIM == 3)
765     panmove += axespressed[1] / 32000.0;
766   else
767     panspin += axespressed[1] / 32000.0;
768 
769   if(actionspressed[54]) { centerplayer = -1, playermoved = true; centerpc(100); }
770 
771   if(actionspressed[55] && !lactionpressed[55])
772     get_o_key().second();
773 
774   if(actionspressed[56] && !lactionpressed[56])
775     showMissionScreen();
776 
777 #if CAP_INV
778   if(actionspressed[57] && !lactionpressed[57] && inv::on)
779     pushScreen(inv::show);
780 #endif
781 
782   if(actionspressed[58] && !lactionpressed[58])
783     pushScreen(showMainMenu);
784 
785   panx *= d;
786   pany *= d;
787   panspin *= d;
788   panmove *= d;
789 
790   #if CAP_MOUSEGRAB
791   if(lctrlclick) {
792     panx += mouseaim_x / 2;
793     pany += mouseaim_y / 2;
794     mouseaim_x = mouseaim_y = 0;
795     }
796   #endif
797 
798   if(panx || pany || panspin || (GDIM == 3 && panmove)) {
799     if(GDIM == 2) {
800       View = xpush(-panx) * ypush(-pany) * spin(panspin) * View;
801       playermoved = false;
802       }
803     else {
804       View = cspin(0, 2, -panx) * cspin(1, 2, -pany) * spin(panspin) * cpush(2, panmove) * View;
805       if(panmove) playermoved = false;
806       }
807     }
808 #endif
809   }
810 
811   EX int tableid[7] = {1, 2, 4, 5, 6, 7, 8};
812 
leaveGame(int i)813   EX void leaveGame(int i) {
814     multi::player[i].at = NULL;
815     multi::deaths[i]++;
816     revive_queue.push_back(i);
817     checklastmove();
818     }
819 
playerActive(int p)820   EX bool playerActive(int p) {
821     if(multi::players == 1 || shmup::on) return true;
822     return player[p].at;
823     }
824 
activePlayers()825   EX int activePlayers() {
826     int q = 0;
827     for(int i=0; i<players; i++) if(playerActive(i)) q++;
828     return q;
829     }
830 
multiPlayerTarget(int i)831   EX cell *multiPlayerTarget(int i) {
832     cellwalker cwti = multi::player[i];
833     if(!cwti.at) return NULL;
834     int dir = multi::whereto[i].d;
835     if(dir == MD_UNDECIDED) return NULL;
836     if(dir == MD_USE_ORB) return multi::whereto[i].tgt;
837     if(dir >= 0)
838       cwti = cwti + dir + wstep;
839     return cwti.at;
840     }
841 
checklastmove()842   EX void checklastmove() {
843     for(int i: player_indices()) {
844       multi::cpid = i;
845       cwt = multi::player[i]; break;
846       }
847     if(multi::activePlayers() == 1) {
848       multi::checkonly = true;
849       checkmove();
850       multi::checkonly = false;
851       }
852     }
853 
854   bool needinput = true;
855 
handleMulti(int delta)856   EX void handleMulti(int delta) {
857     multi::handleInput(delta);
858 
859     shiftmatrix bcwtV = cwtV;
860     cellwalker bcwt = cwt;
861 
862     bool alldecided = !needinput;
863 
864     if(multi::players == 1) {
865       multi::cpid = 0;
866       multi::whereis[0] = cwtV;
867       multi::player[0] = cwt;
868       }
869 
870     for(int i: player_indices()) {
871 
872       using namespace multi;
873 
874   // todo refactor
875 
876       cpid = i;
877 
878       int b = 16*tableid[cpid];
879       for(int ik=0; ik<8; ik++) if(actionspressed[b+ik]) playermoved = true;
880       for(int ik=0; ik<16; ik++) if(actionspressed[b+ik] && !lactionpressed[b+ik])
881         multi::combo[i] = false;
882 
883       bool anypressed = false;
884 
885       int jb = 4*tableid[cpid];
886       for(int ik=0; ik<4; ik++)
887         if(axespressed[jb+ik])
888           anypressed = true, playermoved = true, multi::combo[i] = false;
889 
890       double mdx =
891         (actionspressed[b+0] + actionspressed[b+2] - actionspressed[b+1] - actionspressed[b+3]) * .7 +
892         actionspressed[b+pcMoveRight] - actionspressed[b+pcMoveLeft] + axespressed[jb]/30000.;
893       double mdy =
894         (actionspressed[b+3] + actionspressed[b+2] - actionspressed[b+1] - actionspressed[b+0]) * .7 +
895         actionspressed[b+pcMoveDown] - actionspressed[b+pcMoveUp] + axespressed[jb+1]/30000.;
896 
897       if((actionspressed[b+pcMoveRight] && actionspressed[b+pcMoveLeft]) ||
898         (actionspressed[b+pcMoveUp] && actionspressed[b+pcMoveDown]))
899           multi::mdx[i] = multi::mdy[i] = 0;
900 
901       multi::mdx[i] = multi::mdx[i] * (1 - delta / 1000.) + mdx * delta / 2000.;
902       multi::mdy[i] = multi::mdy[i] * (1 - delta / 1000.) + mdy * delta / 2000.;
903 
904       if(WDIM == 2) {
905         if(mdx != 0 || mdy != 0) if(!multi::combo[i]) {
906           cwtV = multi::whereis[i]; cwt = multi::player[i];
907           flipplayer = multi::flipped[i];
908           multi::whereto[i] = vectodir(hpxy(multi::mdx[i], multi::mdy[i]));
909           }
910         }
911 
912       if(multi::actionspressed[b+pcFire] ||
913         (multi::actionspressed[b+pcMoveLeft] && multi::actionspressed[b+pcMoveRight]))
914         multi::combo[i] = true, multi::whereto[i].d = MD_WAIT;
915 
916       if(multi::actionspressed[b+pcFace])
917         multi::whereto[i].d = MD_UNDECIDED;
918 
919       cwt.at = multi::player[i].at;
920       if(multi::ccat[i] && !multi::combo[i] && targetRangedOrb(multi::ccat[i], roMultiCheck)) {
921         multi::whereto[i].d = MD_USE_ORB;
922         multi::whereto[i].tgt = multi::ccat[i];
923         }
924 
925       if(multi::actionspressed[b+pcFaceFire] && activePlayers() > 1) {
926         addMessage(XLAT("Left the game."));
927         multi::leaveGame(i);
928         }
929 
930       if(actionspressed[b+pcDrop] ||
931         (multi::actionspressed[b+pcMoveUp] && multi::actionspressed[b+pcMoveDown]))
932         multi::combo[i] = true, multi::whereto[i].d = MD_DROP;
933 
934       if(actionspressed[b+pcCenter]) {
935         centerplayer = cpid; centerpc(100); playermoved = true;
936         }
937 
938       if(multi::whereto[i].d == MD_UNDECIDED) alldecided = false;
939 
940       for(int ik=0; ik<16; ik++) if(actionspressed[b+ik]) anypressed = true;
941 
942       if(anypressed) alldecided = false, needinput = false;
943       else multi::mdx[i] = multi::mdy[i] = 0;
944       }
945 
946     cwtV = bcwtV;
947     cwt = bcwt;
948 
949     if(alldecided) {
950       flashMessages();
951       // check for crashes
952       needinput = true;
953 
954       for(int i: player_indices()) {
955         origpos[i] = player[i].at;
956         origtarget[i] = multiPlayerTarget(i);
957         }
958 
959       for(int i: player_indices())
960       for(int j: player_indices()) if(i != j) {
961         if(origtarget[i] == origtarget[j]) {
962           addMessage("Two players cannot move/attack the same location!");
963           return;
964           }
965 /*      if(multiPlayerTarget(i) == multi::player[j].at) {
966           addMessage("Cannot move into the current location of another player!");
967           return;
968           }
969         if(celldistance(multiPlayerTarget(i), multiPlayerTarget(j)) > 8) {
970           addMessage("Players cannot get that far away!");
971           return;
972           } */
973         }
974 
975       if(multi::players == 1) {
976         if(movepcto(multi::whereto[0]))
977           multi::whereto[0].d = MD_UNDECIDED;
978         return;
979         }
980 
981       multi::cpid = 0;
982       if(multimove()) {
983         multi::aftermove = false;
984         if(shmup::delayed_safety) {
985           activateSafety(shmup::delayed_safety_land);
986           shmup::delayed_safety = false;
987           checklastmove();
988           }
989         else {
990           monstersTurn();
991           checklastmove();
992           }
993         }
994       }
995     }
996 
mousemovement(cell * c)997   EX void mousemovement(cell *c) {
998     if(!c) return;
999     int countplayers = 0;
1000     int countplayers_undecided = 0;
1001     for(int i=0; i<multi::players; i++)
1002       if(multi::playerActive(i) && (playerpos(i) == c || isNeighbor(c, playerpos(i)))) {
1003         countplayers++;
1004         if(multi::whereto[i].d == MD_UNDECIDED) countplayers_undecided++;
1005         }
1006 
1007     for(int i=0; i<multi::players; i++)
1008       if(multi::playerActive(i) && (playerpos(i) == c || isNeighbor(c, playerpos(i)))) {
1009         int& cdir = multi::whereto[i].d;
1010         int scdir = cdir;
1011         bool isUndecided = cdir == MD_UNDECIDED;
1012         if(countplayers_undecided > 0 && ! isUndecided) continue;
1013         if(playerpos(i) == c)
1014           multi::whereto[i].d = MD_WAIT;
1015         else {
1016           for(int d=0; d<playerpos(i)->type; d++) {
1017             cdir = d;
1018             if(multi::multiPlayerTarget(i) == c) break;
1019             cdir = scdir;
1020             cwt = multi::player[i];
1021             calcMousedest();
1022             auto& sd = multi::whereto[i].subdir;
1023             sd = mousedest.subdir;
1024             if(sd == 0) sd = 1;
1025             }
1026           }
1027         }
1028 
1029     needinput =
1030       ((countplayers == 2 && !countplayers_undecided) || countplayers_undecided >= 2);
1031     }
1032 
1033   EX }
1034 
1035 }
1036