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