hyperrogue/multi.cpp

1122 lines
33 KiB
C++

// Hyperbolic Rogue -- multiplayer features
// Copyright (C) 2011-2019 Zeno Rogue, see 'hyper.cpp' for details
/** \file multi.cpp
* \brief multiplayer features, also input configuration
*/
#include "hyper.h"
namespace hr {
EX namespace multi {
#if HDR
static constexpr int SCANCODES = 512;
static constexpr int MAXJOY = 8;
static constexpr int MAXBUTTON = 64;
static constexpr int MAXAXE = 16;
static constexpr int MAXHAT = 4;
struct config {
char keyaction[SCANCODES];
char joyaction[MAXJOY][MAXBUTTON];
char axeaction[MAXJOY][MAXAXE];
char hataction[MAXJOY][MAXHAT][4];
int deadzoneval[MAXJOY][MAXAXE];
};
#endif
EX config scfg_default;
EX charstyle scs[MAXPLAYER];
EX bool split_screen;
EX bool pvp_mode;
EX bool friendly_fire = true;
EX bool self_hits;
EX bool two_focus;
EX int players = 1;
EX cellwalker player[MAXPLAYER];
EX vector<int> revive_queue; // queue for revival
EX cell *origpos[MAXPLAYER], *origtarget[MAXPLAYER];
EX bool flipped[MAXPLAYER];
// treasure collection, kill, and death statistics
EX int treasures[MAXPLAYER], kills[MAXPLAYER], deaths[MAXPLAYER], pkills[MAXPLAYER], suicides[MAXPLAYER];
EX bool alwaysuse = false;
EX void recall() {
for(int i=0; i<numplayers(); i++) {
int idir = (3 * i) % cwt.at->type;
cell *c2 = cwt.at->move(idir);
makeEmpty(c2);
if(!passable(c2, NULL, P_ISPLAYER)) c2 = cwt.at;
multi::player[i].at = c2;
multi::player[i].spin = 0;
multi::flipped[i] = true;
multi::whereto[i].d = MD_UNDECIDED;
}
}
EX shiftmatrix whereis[MAXPLAYER];
EX shiftmatrix crosscenter[MAXPLAYER];
EX double ccdist[MAXPLAYER];
EX cell *ccat[MAXPLAYER];
bool combo[MAXPLAYER];
EX int cpid; // player id -- an extra parameter for player-related functions
EX int cpid_edit; // cpid currently being edited
EX movedir whereto[MAXPLAYER]; // player's target cell
EX double mdx[MAXPLAYER], mdy[MAXPLAYER]; // movement vector for the next move
static constexpr int CMDS = 15;
static constexpr int CMDS_PAN = 11;
vector<string> playercmds_shmup = {
"forward", "backward", "turn left", "turn right",
"move up", "move right", "move down", "move left",
"throw a knife", "face the pointer", "throw at the pointer",
"drop Dead Orb", "center the map on me", "Orb power (target: mouse)",
"Orb power (target: facing)"
};
vector<string> playercmds_shmup3 = {
"rotate up", "rotate down", "rotate left", "rotate right",
"move forward", "strafe right", "move backward", "strafe left",
"throw a knife", "face the pointer", "throw at the pointer",
"drop Dead Orb", "center the map on me", "Orb power (target: mouse)",
"Orb power (target: facing)"
};
vector<string> playercmds_turn = {
"move up-right", "move up-left", "move down-right", "move down-left",
"move up", "move right", "move down", "move left",
"stay in place (left + right)", "cancel move", "leave the game",
"drop Dead Orb (up + down)", "center the map on me", "",
""
};
vector<string> pancmds = {
"pan up", "pan right", "pan down", "pan left",
"rotate left", "rotate right", "home",
"world overview", "review your quest", "inventory", "main menu"
};
vector<string> pancmds3 = {
"look up", "look right", "look down", "look left",
"rotate left", "rotate right", "home",
"world overview", "review your quest", "inventory", "main menu",
"scroll forward", "scroll backward"
};
#if HDR
#define SHMUPAXES_BASE 4
#define SHMUPAXES ((SHMUPAXES_BASE) + 4 * (MAXPLAYER))
#define SHMUPAXES_CUR ((SHMUPAXES_BASE) + 4 * playercfg)
#endif
EX const char* axemodes[SHMUPAXES] = {
"do nothing",
"rotate view",
"panning X",
"panning Y",
"player 1 X",
"player 1 Y",
"player 1 go",
"player 1 spin",
"player 2 X",
"player 2 Y",
"player 2 go",
"player 2 spin",
"player 3 X",
"player 3 Y",
"player 3 go",
"player 3 spin",
"player 4 X",
"player 4 Y",
"player 4 go",
"player 4 spin",
"player 5 X",
"player 5 Y",
"player 5 go",
"player 5 spin",
"player 6 X",
"player 6 Y",
"player 6 go",
"player 6 spin",
"player 7 X",
"player 7 Y",
"player 7 go",
"player 7 spin"
};
EX const char* axemodes3[4] = {
"do nothing",
"camera forward",
"camera rotate X",
"camera rotate Y"
};
EX int centerplayer = -1;
char* axeconfigs[24]; int numaxeconfigs;
int* dzconfigs[24];
string listkeys(config& scfg, int id) {
#if CAP_SDL
string lk = "";
for(int i=0; i<SCANCODES; i++)
if(scfg.keyaction[i] == id)
#if CAP_SDL2
lk = lk + " " + SDL_GetScancodeName(SDL_Scancode(i));
#else
lk = lk + " " + SDL_GetKeyName(SDLKey(i));
#endif
#if CAP_SDLJOY
for(int i=0; i<numsticks; i++) for(int k=0; k<SDL_JoystickNumButtons(sticks[i]) && k<MAXBUTTON; k++)
if(scfg.joyaction[i][k] == id) {
lk = lk + " " + cts('A'+i)+"-B"+its(k);
}
for(int i=0; i<numsticks; i++) for(int k=0; k<SDL_JoystickNumHats(sticks[i]) && k<MAXHAT; k++)
for(int d=0; d<4; d++)
if(scfg.hataction[i][k][d] == id) {
lk = lk + " " + cts('A'+i)+"-"+"URDL"[d];
}
#endif
return lk;
#else
return "";
#endif
}
#define SCJOY 16
string dsc(int id) {
string buf = XLAT(" (%1 $$$, %2 kills, %3 deaths)",
its(multi::treasures[id]),
its(multi::kills[id]),
its(multi::deaths[id])
);
if(friendly_fire)
buf += XLAT(" (%1 pkills)", its(multi::pkills[id]));
if(self_hits)
buf += XLAT(" (%1 self)", its(multi::suicides[id]));
return buf;
}
EX void resetScores() {
for(int i=0; i<MAXPLAYER; i++)
multi::treasures[i] = multi::kills[i] = multi::deaths[i] = multi::pkills[i] = multi::suicides[i] = 0;
}
bool configdead;
void handleConfig(int sym, int uni);
EX string player_count_name(int p) {
return
p == 2 ? XLAT("two players") :
p == 3 ? XLAT("three players") :
p == 4 ? XLAT("four players") :
p == 5 ? XLAT("five players") :
p == 6 ? XLAT("six players") :
p == 7 ? XLAT("seven players") :
XLAT("one player");
}
struct key_configurer {
int sc;
vector<string>& shmupcmdtable;
string caption;
int setwhat;
config *which_config;
key_configurer(int sc, vector<string>& sct, const string& caption, config& w) : sc(sc), shmupcmdtable(sct), caption(caption), setwhat(0), which_config(&w) {
}
void operator() () {
dialog::init(caption);
getcstat = ' ';
for(int i=0; i<isize(shmupcmdtable); i++) if(shmupcmdtable[i][0])
dialog::addSelItem(XLAT(shmupcmdtable[i]), listkeys(*which_config, 16*sc+i),
setwhat ? (setwhat>1 && i == (setwhat&15) ? '?' : 0) : 'a'+i);
else dialog::addBreak(100);
if(setwhat == 1)
dialog::addItem(XLAT("press a key to unassign"), 0);
else if(setwhat)
dialog::addItem(XLAT("press a key for '%1'", XLAT(shmupcmdtable[setwhat&15])), 0);
else
dialog::addItem(XLAT("unassign a key"), 'z');
dialog::display();
keyhandler = [this] (int sym, int uni) {
if(!setwhat) dialog::handleNavigation(sym, uni);
if(sym) {
if(setwhat) {
int scan = key_to_scan(sym);
if(scan >= 0 && scan < SCANCODES) which_config->keyaction[scan] = setwhat;
setwhat = 0;
}
else if(uni >= 'a' && uni < 'a' + isize(shmupcmdtable) && shmupcmdtable[uni-'a'][0])
setwhat = 16*sc+uni - 'a';
else if(uni == 'z')
setwhat = 1;
else if(doexiton(sym, uni))
popScreen();
}
};
#if CAP_SDLJOY
joyhandler = [this] (SDL_Event& ev) {
if(ev.type == SDL_JOYBUTTONDOWN && setwhat) {
int joyid = ev.jbutton.which;
int button = ev.jbutton.button;
if(joyid < 8 && button < 32)
which_config->joyaction[joyid][button] = setwhat;
setwhat = 0;
return true;
}
else if(ev.type == SDL_JOYHATMOTION && setwhat) {
int joyid = ev.jhat.which;
int hat = ev.jhat.hat;
int dir = 4;
if(ev.jhat.value == SDL_HAT_UP) dir = 0;
if(ev.jhat.value == SDL_HAT_RIGHT) dir = 1;
if(ev.jhat.value == SDL_HAT_DOWN) dir = 2;
if(ev.jhat.value == SDL_HAT_LEFT) dir = 3;
printf("%d %d %d\n", joyid, hat, dir);
if(joyid < 8 && hat < 4 && dir < 4) {
which_config->hataction[joyid][hat][dir] = setwhat;
setwhat = 0;
return true;
}
}
return false;
};
#endif
}
};
EX reaction_t get_key_configurer(int sc, vector<string>& sct, string caption) {
return key_configurer(sc, sct, caption, scfg_default);
}
EX reaction_t get_key_configurer(int sc, vector<string>& sct, string caption, config &cfg) {
return key_configurer(sc, sct, caption, cfg);
}
EX reaction_t get_key_configurer(int sc, vector<string>& sct) {
return key_configurer(sc, sct, sc == 1 ? XLAT("configure player 1") :
sc == 2 ? XLAT("configure player 2") :
sc == 3 ? XLAT("configure panning") :
sc == 4 ? XLAT("configure player 3") :
sc == 5 ? XLAT("configure player 4") :
sc == 6 ? XLAT("configure player 5") :
sc == 7 ? XLAT("configure player 6") :
sc == 8 ? XLAT("configure player 7") : "",
scfg_default
);
}
#if CAP_SDLJOY
struct joy_configurer {
bool shmupcfg, racecfg;
int playercfg;
config& scfg;
joy_configurer(int playercfg, config& scfg) : playercfg(playercfg), scfg(scfg) {}
void operator() () {
dialog::init();
getcstat = ' ';
numaxeconfigs = 0;
for(int j=0; j<numsticks; j++) {
for(int ax=0; ax<SDL_JoystickNumAxes(sticks[j]) && ax < MAXAXE; ax++) if(numaxeconfigs<24) {
int y = SDL_JoystickGetAxis(sticks[j], ax);
string buf = " ";
if(configdead)
buf += its(y);
else {
while(y > 10000) buf += "+", y -= 10000;
while(y < -10000) buf += "-", y += 10000;
if(y>0) buf += "+";
if(y<0) buf += "-";
}
axeconfigs[numaxeconfigs] = &(scfg.axeaction[j][ax]);
dzconfigs[numaxeconfigs] = &(scfg.deadzoneval[j][ax]);
char aa = *axeconfigs[numaxeconfigs];
string what = configdead ? its(scfg.deadzoneval[j][ax]) :
(GDIM == 3 && (aa%SHMUPAXES < 4)) ? XLAT(axemodes3[aa%SHMUPAXES]) :
XLAT(axemodes[aa%SHMUPAXES]);
dialog::addSelItem(XLAT("Joystick %1, axis %2", cts('A'+j), its(ax)) + buf,
what, 'a'+numaxeconfigs);
numaxeconfigs++;
}
}
dialog::addBoolItem(XLAT("Configure dead zones"), (configdead), 'z');
dialog::display();
keyhandler = [this] (int sym, int uni) {
dialog::handleNavigation(sym, uni);
if(sym) {
char xuni = uni | 96;
if(xuni >= 'a' && xuni < 'a' + numaxeconfigs) {
if(configdead)
dialog::editNumber( (*dzconfigs[xuni - 'a']), 0, 65536, 100, 0, XLAT("Configure dead zones"), "");
else {
int v = (*axeconfigs[xuni - 'a']);
v += (shiftmul>0?1:-1);
v += SHMUPAXES_CUR;
v %= SHMUPAXES_CUR;
(*axeconfigs[xuni - 'a']) = v;
}
}
else if(xuni == 'z')
configdead = !configdead;
else if(doexiton(sym, uni))
popScreen();
}
};
}
};
#endif
EX const char *axmodes[7] = {"OFF", "auto", "light", "heavy", "arrows", "WASD keys", "VI keys"};
struct shmup_configurer {
void operator()() {
#if CAP_SDL
cmode = sm::SHMUPCONFIG | sm::SIDE | sm::DARKEN;
gamescreen();
dialog::init(XLAT("keyboard & joysticks"));
bool haveconfig = shmup::on || players > 1 || multi::alwaysuse;
if(haveconfig)
dialog::addItem(XLAT("configure player 1"), '1');
else
dialog::addBreak(100);
if(players > 1)
dialog::addItem(XLAT("configure player 2"), '2');
else if(players == 1 && !shmup::on)
dialog::addSelItem(XLAT("input"), multi::alwaysuse ? XLAT("config") : XLAT("default"), 'a');
else
dialog::addBreak(100);
if(players > 2)
dialog::addItem(XLAT("configure player 3"), '3');
#if CAP_SDLJOY
else if(!haveconfig)
dialog::addItem(XLAT("old style joystick configuration"), 'b');
#endif
else dialog::addBreak(100);
if(players > 3)
dialog::addItem(XLAT("configure player 4"), '4');
else if(!shmup::on && !multi::alwaysuse) {
dialog::addBoolItem(XLAT("smooth scrolling"), smooth_scrolling, 'c');
}
else if(alwaysuse)
dialog::addInfo(XLAT("note: configured input is designed for"));
else dialog::addBreak(100);
if(players > 4)
dialog::addItem(XLAT("configure player 5"), '5');
else if(!shmup::on && !multi::alwaysuse) {
if(GDIM == 2) {
dialog::addSelItem(XLAT("help for keyboard users"), XLAT(axmodes[vid.axes]), 'h');
dialog::add_action([] {vid.axes += 70 + (shiftmul > 0 ? 1 : -1); vid.axes %= 7; } );
}
else dialog::addBreak(100);
}
else if(alwaysuse)
dialog::addInfo(XLAT("multiplayer and shmup mode; some features"));
else dialog::addBreak(100);
if(players > 5)
dialog::addItem(XLAT("configure player 6"), '6');
else if(alwaysuse)
dialog::addInfo(XLAT("work worse if you use it."));
else dialog::addBreak(100);
if(players > 6)
dialog::addItem(XLAT("configure player 7"), '7');
else dialog::addBreak(100);
if(shmup::on || multi::alwaysuse || players > 1)
dialog::addItem(XLAT("configure panning and general keys"), 'p');
else dialog::addBreak(100);
#if CAP_SDLJOY
if(numsticks > 0) {
if(shmup::on || multi::alwaysuse || players > 1)
dialog::addItem(XLAT("configure joystick axes"), 'j');
else dialog::addBreak(100);
}
#endif
dialog::addBreak(50);
dialog::addHelp();
dialog::addBack();
dialog::display();
keyhandler = [this] (int sym, int uni) { return handleConfig(sym, uni); };
#endif
}
void handleConfig(int sym, int uni) {
auto& cmdlist = shmup::on ? (WDIM == 3 ? playercmds_shmup3 : playercmds_shmup) : playercmds_turn;
dialog::handleNavigation(sym, uni);
if(0) ;
#if CAP_SDL
else if(uni == '1') pushScreen(get_key_configurer(1, cmdlist));
else if(uni == '2') pushScreen(get_key_configurer(2, cmdlist));
else if(uni == 'p') pushScreen(get_key_configurer(3, GDIM == 3 ? pancmds3 : pancmds));
else if(uni == '3') pushScreen(get_key_configurer(4, cmdlist));
else if(uni == '4') pushScreen(get_key_configurer(5, cmdlist));
else if(uni == '5') pushScreen(get_key_configurer(6, cmdlist));
else if(uni == '6') pushScreen(get_key_configurer(7, cmdlist));
else if(uni == '7') pushScreen(get_key_configurer(8, cmdlist));
#if CAP_SDLJOY
else if(uni == 'j') pushScreen(joy_configurer(players, scfg_default));
#endif
else if(uni == 'a') multi::alwaysuse = !multi::alwaysuse;
#if CAP_SDLJOY
else if(uni == 'b') pushScreen(showJoyConfig);
#endif
else if(uni == 'c') smooth_scrolling = !smooth_scrolling;
#endif
else if(doexiton(sym, uni)) popScreen();
}
};
EX void configure() {
pushScreen(shmup_configurer());
}
EX void showConfigureMultiplayer() {
cmode = sm::SIDE | sm::MAYDARK;
gamescreen();
dialog::init("multiplayer");
for(int i=1; i <= MAXPLAYER; i++) {
string s = player_count_name(i);
if(i <= players) s += dsc(i-1);
dialog::addBoolItem(s, i == multi::players, '0' + i);
if(!dual::state) dialog::add_action([i] {
dialog::do_if_confirmed([i] {
stop_game();
players = i;
if(multi::players > 1 && !shmup::on) bow::weapon = bow::wBlade;
start_game();
});
});
}
add_edit(self_hits);
if(multi::players > 1) {
dialog::addItem(XLAT("reset per-player statistics"), 'r');
dialog::add_action([] {
for(int i=0; i<MAXPLAYER; i++)
kills[i] = deaths[i] = treasures[i] = 0;
});
dialog::addSelItem(XLAT("keyboard & joysticks"), "", 'k');
dialog::add_action(multi::configure);
add_edit(split_screen);
if(shmup::on && !racing::on) {
add_edit(pvp_mode);
add_edit(friendly_fire);
if(pvp_mode)
dialog::addInfo(XLAT("PvP grants infinite lives -- achievements disabled"));
else if(friendly_fire)
dialog::addInfo(XLAT("friendly fire off -- achievements disabled"));
else if(split_screen)
dialog::addInfo(XLAT("achievements disabled in split screen"));
else
dialog::addBreak(100);
}
else {
dialog::addInfo(XLAT("PvP available only in shmup"));
dialog::addBreak(400);
}
if(multi::players == 2 && !split_screen)
add_edit(two_focus);
else
dialog::addBreak(100);
}
else dialog::addBreak(600);
dialog::addBack();
dialog::display();
}
#if HDR
#define NUMACT 128
enum pcmds {
pcForward, pcBackward, pcTurnLeft, pcTurnRight,
pcMoveUp, pcMoveRight, pcMoveDown, pcMoveLeft,
pcFire, pcFace, pcFaceFire,
pcDrop, pcCenter, pcOrbPower, pcOrbKey
};
#endif
EX int actionspressed[NUMACT], axespressed[SHMUPAXES], lactionpressed[NUMACT];
void pressaction(int id) {
if(id >= 0 && id < NUMACT)
actionspressed[id]++;
}
EX int key_to_scan(int sym) {
#if CAP_SDL2
return SDL_GetScancodeFromKey(sym);
#else
return sym;
#endif
}
EX bool notremapped(int sym) {
auto& scfg = scfg_default;
int sc = key_to_scan(sym);
if(sc < 0 || sc >= SCANCODES) return true;
int k = scfg.keyaction[sc];
if(k == 0) return true;
k /= 16;
if(k > 3) k--; else if(k==3) k = 0;
return k > multi::players;
}
EX void sconfig_savers(config& scfg, string prefix) {
// unfortunately we cannot use key names here because SDL is not yet initialized
for(int i=0; i<SCANCODES; i++)
addsaver(scfg.keyaction[i], prefix + string("key:")+its(i));
for(int i=0; i<MAXJOY; i++) {
string pre = prefix + "joystick "+cts('A'+i);
for(int j=0; j<MAXBUTTON; j++)
addsaver(scfg.joyaction[i][j], pre+"-B"+its(j));
for(int j=0; j<MAXAXE; j++) {
addsaver(scfg.axeaction[i][j], pre+" axis "+its(j));
addsaver(scfg.deadzoneval[i][j], pre+" deadzone "+its(j));
}
for(int j=0; j<MAXHAT; j++) for(int k=0; k<4; k++) {
addsaver(scfg.hataction[i][j][k], pre+" hat "+its(j)+" "+"URDL"[k]);
}
}
}
EX void clear_config(config& scfg) {
for(int i=0; i<SCANCODES; i++) scfg.keyaction[i] = 0;
}
EX void initConfig() {
auto& scfg = scfg_default;
char* t = scfg.keyaction;
#if CAP_SDL2
t[SDL_SCANCODE_W] = 16 + 4;
t[SDL_SCANCODE_D] = 16 + 5;
t[SDL_SCANCODE_S] = 16 + 6;
t[SDL_SCANCODE_A] = 16 + 7;
t[SDL_SCANCODE_KP_8] = 16 + 4;
t[SDL_SCANCODE_KP_6] = 16 + 5;
t[SDL_SCANCODE_KP_2] = 16 + 6;
t[SDL_SCANCODE_KP_4] = 16 + 7;
t[SDL_SCANCODE_F] = 16 + pcFire;
t[SDL_SCANCODE_G] = 16 + pcFace;
t[SDL_SCANCODE_H] = 16 + pcFaceFire;
t[SDL_SCANCODE_R] = 16 + pcDrop;
t[SDL_SCANCODE_T] = 16 + pcOrbPower;
t[SDL_SCANCODE_Y] = 16 + pcCenter;
t[SDL_SCANCODE_I] = 32 + 4;
t[SDL_SCANCODE_L] = 32 + 5;
t[SDL_SCANCODE_K] = 32 + 6;
t[SDL_SCANCODE_J] = 32 + 7;
t[SDL_SCANCODE_SEMICOLON] = 32 + 8;
t[SDL_SCANCODE_APOSTROPHE] = 32 + 9;
t[SDL_SCANCODE_P] = 32 + 10;
t[SDL_SCANCODE_LEFTBRACKET] = 32 + pcCenter;
t[SDL_SCANCODE_UP] = 48 ;
t[SDL_SCANCODE_RIGHT] = 48 + 1;
t[SDL_SCANCODE_DOWN] = 48 + 2;
t[SDL_SCANCODE_LEFT] = 48 + 3;
t[SDL_SCANCODE_PAGEUP] = 48 + 4;
t[SDL_SCANCODE_PAGEDOWN] = 48 + 5;
t[SDL_SCANCODE_HOME] = 48 + 6;
#else
t[(int)'w'] = 16 + 4;
t[(int)'d'] = 16 + 5;
t[(int)'s'] = 16 + 6;
t[(int)'a'] = 16 + 7;
#if !ISMOBILE
t[SDLK_KP8] = 16 + 4;
t[SDLK_KP6] = 16 + 5;
t[SDLK_KP2] = 16 + 6;
t[SDLK_KP4] = 16 + 7;
#endif
t[(int)'f'] = 16 + pcFire;
t[(int)'g'] = 16 + pcFace;
t[(int)'h'] = 16 + pcFaceFire;
t[(int)'r'] = 16 + pcDrop;
t[(int)'t'] = 16 + pcOrbPower;
t[(int)'y'] = 16 + pcCenter;
t[(int)'i'] = 32 + 4;
t[(int)'l'] = 32 + 5;
t[(int)'k'] = 32 + 6;
t[(int)'j'] = 32 + 7;
t[(int)';'] = 32 + 8;
t[(int)'\''] = 32 + 9;
t[(int)'p'] = 32 + 10;
t[(int)'['] = 32 + pcCenter;
#if !ISMOBILE
t[SDLK_UP] = 48 ;
t[SDLK_RIGHT] = 48 + 1;
t[SDLK_DOWN] = 48 + 2;
t[SDLK_LEFT] = 48 + 3;
t[SDLK_PAGEUP] = 48 + 4;
t[SDLK_PAGEDOWN] = 48 + 5;
t[SDLK_HOME] = 48 + 6;
#endif
#endif
scfg.joyaction[0][0] = 16 + pcFire;
scfg.joyaction[0][1] = 16 + pcOrbPower;
scfg.joyaction[0][2] = 16 + pcDrop;
scfg.joyaction[0][3] = 16 + pcCenter;
scfg.joyaction[0][4] = 16 + pcFace;
scfg.joyaction[0][5] = 16 + pcFaceFire;
scfg.joyaction[1][0] = 32 + pcFire;
scfg.joyaction[1][1] = 32 + pcOrbPower;
scfg.joyaction[1][2] = 32 + pcDrop;
scfg.joyaction[1][3] = 32 + pcCenter;
scfg.joyaction[1][4] = 32 + pcFace;
scfg.joyaction[1][5] = 32 + pcFaceFire;
scfg.axeaction[0][0] = 4;
scfg.axeaction[0][1] = 5;
scfg.axeaction[0][3] = 2;
scfg.axeaction[0][4] = 3;
scfg.axeaction[1][0] = 8;
scfg.axeaction[1][1] = 9;
// ULRD
scfg.hataction[0][0][0] = 16 + 0;
scfg.hataction[0][0][1] = 16 + 3;
scfg.hataction[0][0][2] = 16 + 1;
scfg.hataction[0][0][3] = 16 + 2;
scfg.hataction[0][1][0] = 16 + 4;
scfg.hataction[0][1][1] = 16 + 7;
scfg.hataction[0][1][2] = 16 + 5;
scfg.hataction[0][1][3] = 16 + 6;
scfg.hataction[1][0][0] = 32 + 0;
scfg.hataction[1][0][1] = 32 + 3;
scfg.hataction[1][0][2] = 32 + 1;
scfg.hataction[1][0][3] = 32 + 2;
scfg.hataction[1][1][0] = 32 + 4;
scfg.hataction[1][1][1] = 32 + 7;
scfg.hataction[1][1][2] = 32 + 5;
scfg.hataction[1][1][3] = 32 + 6;
int charidtable[MAXPLAYER] = {0, 1, 4, 6, 2, 3, 0};
for(int i=0; i<MAXPLAYER; i++) {
initcs(multi::scs[i]);
multi::scs[i].charid = charidtable[i];
}
multi::scs[0].uicolor = 0xC00000FF;
multi::scs[1].uicolor = 0x00C000FF;
multi::scs[2].uicolor = 0x0000C0FF;
multi::scs[3].uicolor = 0xC0C000FF;
multi::scs[4].uicolor = 0xC000C0FF;
multi::scs[5].uicolor = 0x00C0C0FF;
multi::scs[6].uicolor = 0xC0C0C0FF;
#if CAP_CONFIG
addsaver(multi::players, "mode-number of players");
param_b(multi::split_screen, "splitscreen", false)
->editable("split screen mode", 's');
param_b(multi::pvp_mode, "pvp_mode", false)
->editable("player vs player", 'v');
param_b(multi::friendly_fire, "friendly_fire", true)
->editable("friendly fire", 'f');
param_b(multi::self_hits, "self_hits", false)
->editable("self hits", 'h');
param_b(multi::two_focus, "two_focus", false)
->editable("auto-adjust dual-focus projections", 'f');
addsaver(alwaysuse, "use configured keys");
for(int i=0; i<7; i++) addsaver(multi::scs[i], "player"+its(i));
sconfig_savers(scfg, "");
#endif
}
EX void get_actions(config& scfg) {
#if !ISMOBILE
const Uint8 *keystate = SDL12_GetKeyState(NULL);
for(int i=0; i<NUMACT; i++)
lactionpressed[i] = actionspressed[i],
actionspressed[i] = 0;
for(int i=0; i<SHMUPAXES; i++) axespressed[i] = 0;
for(int i=0; i<KEYSTATES; i++) if(keystate[i])
pressaction(scfg.keyaction[i]);
#if CAP_SDLJOY
for(int j=0; j<numsticks; j++) {
for(int b=0; b<SDL_JoystickNumButtons(sticks[j]) && b<MAXBUTTON; b++)
if(SDL_JoystickGetButton(sticks[j], b))
pressaction(scfg.joyaction[j][b]);
for(int b=0; b<SDL_JoystickNumHats(sticks[j]) && b<MAXHAT; b++) {
int stat = SDL_JoystickGetHat(sticks[j], b);
if(stat & SDL_HAT_UP) pressaction(scfg.hataction[j][b][0]);
if(stat & SDL_HAT_RIGHT) pressaction(scfg.hataction[j][b][1]);
if(stat & SDL_HAT_DOWN) pressaction(scfg.hataction[j][b][2]);
if(stat & SDL_HAT_LEFT) pressaction(scfg.hataction[j][b][3]);
}
for(int b=0; b<SDL_JoystickNumAxes(sticks[j]) && b<MAXAXE; b++) {
int value = SDL_JoystickGetAxis(sticks[j], b);
int dz = scfg.deadzoneval[j][b];
if(value > dz) value -= dz; else if(value < -dz) value += dz;
else value = 0;
axespressed[scfg.axeaction[j][b] % SHMUPAXES] += value;
}
}
#endif
#endif
}
EX void handleInput(int delta, config &scfg) {
#if CAP_SDL
double d = delta / 500.;
get_actions(scfg);
const Uint8 *keystate = SDL12_GetKeyState(NULL);
if(keystate[SDL12(SDLK_LCTRL, SDL_SCANCODE_LCTRL)] || keystate[SDL12(SDLK_RCTRL, SDL_SCANCODE_RCTRL)]) d /= 5;
double panx =
actionspressed[49] - actionspressed[51] + axespressed[2] / 32000.0;
double pany =
actionspressed[50] - actionspressed[48] + axespressed[3] / 32000.0;
double panspin = actionspressed[52] - actionspressed[53];
double panmove = actionspressed[59] - actionspressed[60];
if(GDIM == 3)
panmove += axespressed[1] / 32000.0;
else
panspin += axespressed[1] / 32000.0;
if(actionspressed[54]) { centerplayer = -1, playermoved = true; centerpc(100); }
if(actionspressed[55] && !lactionpressed[55])
get_o_key().second();
if(actionspressed[56] && !lactionpressed[56])
showMissionScreen();
#if CAP_INV
if(actionspressed[57] && !lactionpressed[57] && inv::on)
pushScreen(inv::show);
#endif
if(actionspressed[58] && !lactionpressed[58])
pushScreen(showGameMenu);
panx *= d;
pany *= d;
panspin *= d;
panmove *= d;
#if CAP_MOUSEGRAB
if(lctrlclick) {
panx += mouseaim_x / 2;
pany += mouseaim_y / 2;
mouseaim_x = mouseaim_y = 0;
}
#endif
if(panx || pany || panspin || (GDIM == 3 && panmove)) {
if(GDIM == 2) {
View = xpush(-panx) * ypush(-pany) * spin(panspin) * View;
playermoved = false;
}
else {
View = cspin(0, 2, -panx) * cspin(1, 2, -pany) * spin(panspin) * cpush(2, panmove) * View;
if(panmove) playermoved = false;
}
}
#endif
}
EX void handleInput(int delta) { handleInput(delta, scfg_default); }
EX int tableid[7] = {1, 2, 4, 5, 6, 7, 8};
EX void leaveGame(int i) {
multi::player[i].at = NULL;
multi::deaths[i]++;
revive_queue.push_back(i);
checklastmove();
}
EX bool playerActive(int p) {
if(multi::players == 1 || shmup::on) return true;
return player[p].at;
}
EX int activePlayers() {
int q = 0;
for(int i=0; i<players; i++) if(playerActive(i)) q++;
return q;
}
EX cell *multiPlayerTarget(int i) {
cellwalker cwti = multi::player[i];
if(!cwti.at) return NULL;
int dir = multi::whereto[i].d;
if(dir == MD_UNDECIDED) return NULL;
if(dir == MD_USE_ORB) return multi::whereto[i].tgt;
if(dir >= 0)
cwti = cwti + dir + wstep;
return cwti.at;
}
EX void checklastmove() {
for(int i: player_indices()) {
multi::cpid = i;
cwt = multi::player[i]; break;
}
if(multi::activePlayers() == 1) {
multi::checkonly = true;
checkmove();
multi::checkonly = false;
}
}
bool needinput = true;
EX void handleMulti(int delta) {
multi::handleInput(delta);
shiftmatrix bcwtV = cwtV;
cellwalker bcwt = cwt;
bool alldecided = !needinput;
if(multi::players == 1) {
multi::cpid = 0;
multi::whereis[0] = cwtV;
multi::player[0] = cwt;
}
for(int i: player_indices()) {
using namespace multi;
// todo refactor
cpid = i;
int b = 16*tableid[cpid];
for(int ik=0; ik<8; ik++) if(actionspressed[b+ik]) playermoved = true;
for(int ik=0; ik<16; ik++) if(actionspressed[b+ik] && !lactionpressed[b+ik])
multi::combo[i] = false;
bool anypressed = false;
int jb = 4*tableid[cpid];
for(int ik=0; ik<4; ik++)
if(axespressed[jb+ik])
anypressed = true, playermoved = true, multi::combo[i] = false;
double mdx =
(actionspressed[b+0] + actionspressed[b+2] - actionspressed[b+1] - actionspressed[b+3]) * .7 +
actionspressed[b+pcMoveRight] - actionspressed[b+pcMoveLeft] + axespressed[jb]/30000.;
double mdy =
(actionspressed[b+3] + actionspressed[b+2] - actionspressed[b+1] - actionspressed[b+0]) * .7 +
actionspressed[b+pcMoveDown] - actionspressed[b+pcMoveUp] + axespressed[jb+1]/30000.;
if((actionspressed[b+pcMoveRight] && actionspressed[b+pcMoveLeft]) ||
(actionspressed[b+pcMoveUp] && actionspressed[b+pcMoveDown]))
multi::mdx[i] = multi::mdy[i] = 0;
multi::mdx[i] = multi::mdx[i] * (1 - delta / 1000.) + mdx * delta / 2000.;
multi::mdy[i] = multi::mdy[i] * (1 - delta / 1000.) + mdy * delta / 2000.;
if(WDIM == 2) {
if(mdx != 0 || mdy != 0) if(!multi::combo[i]) {
cwtV = multi::whereis[i]; cwt = multi::player[i];
flipplayer = multi::flipped[i];
multi::whereto[i] = vectodir(hpxy(multi::mdx[i], multi::mdy[i]));
}
}
if(multi::actionspressed[b+pcFire] ||
(multi::actionspressed[b+pcMoveLeft] && multi::actionspressed[b+pcMoveRight]))
multi::combo[i] = true, multi::whereto[i].d = MD_WAIT;
if(multi::actionspressed[b+pcFace])
multi::whereto[i].d = MD_UNDECIDED;
cwt.at = multi::player[i].at;
if(multi::ccat[i] && !multi::combo[i] && targetRangedOrb(multi::ccat[i], roMultiCheck)) {
multi::whereto[i].d = MD_USE_ORB;
multi::whereto[i].tgt = multi::ccat[i];
}
if(multi::actionspressed[b+pcFaceFire] && activePlayers() > 1) {
addMessage(XLAT("Left the game."));
multi::leaveGame(i);
}
if(actionspressed[b+pcDrop] ||
(multi::actionspressed[b+pcMoveUp] && multi::actionspressed[b+pcMoveDown]))
multi::combo[i] = true, multi::whereto[i].d = MD_DROP;
if(actionspressed[b+pcCenter]) {
centerplayer = cpid; centerpc(100); playermoved = true;
}
if(multi::whereto[i].d == MD_UNDECIDED) alldecided = false;
for(int ik=0; ik<16; ik++) if(actionspressed[b+ik]) anypressed = true;
if(anypressed) alldecided = false, needinput = false;
else multi::mdx[i] = multi::mdy[i] = 0;
}
cwtV = bcwtV;
cwt = bcwt;
if(alldecided) {
flashMessages();
// check for crashes
needinput = true;
for(int i: player_indices()) {
origpos[i] = player[i].at;
origtarget[i] = multiPlayerTarget(i);
}
for(int i: player_indices())
for(int j: player_indices()) if(i != j) {
if(origtarget[i] == origtarget[j]) {
addMessage("Two players cannot move/attack the same location!");
return;
}
/* if(multiPlayerTarget(i) == multi::player[j].at) {
addMessage("Cannot move into the current location of another player!");
return;
}
if(celldistance(multiPlayerTarget(i), multiPlayerTarget(j)) > 8) {
addMessage("Players cannot get that far away!");
return;
} */
}
if(multi::players == 1) {
if(movepcto(multi::whereto[0]))
multi::whereto[0].d = MD_UNDECIDED;
return;
}
multi::cpid = 0;
if(multimove()) {
multi::aftermove = false;
if(shmup::delayed_safety) {
activateSafety(shmup::delayed_safety_land);
shmup::delayed_safety = false;
checklastmove();
}
else {
monstersTurn();
checklastmove();
}
}
}
}
EX void mousemovement(cell *c) {
if(!c) return;
int countplayers = 0;
int countplayers_undecided = 0;
for(int i=0; i<multi::players; i++)
if(multi::playerActive(i) && (playerpos(i) == c || isNeighbor(c, playerpos(i)))) {
countplayers++;
if(multi::whereto[i].d == MD_UNDECIDED) countplayers_undecided++;
}
for(int i=0; i<multi::players; i++)
if(multi::playerActive(i) && (playerpos(i) == c || isNeighbor(c, playerpos(i)))) {
int& cdir = multi::whereto[i].d;
int scdir = cdir;
bool isUndecided = cdir == MD_UNDECIDED;
if(countplayers_undecided > 0 && ! isUndecided) continue;
if(playerpos(i) == c)
multi::whereto[i].d = MD_WAIT;
else {
for(int d=0; d<playerpos(i)->type; d++) {
cdir = d;
if(multi::multiPlayerTarget(i) == c) break;
cdir = scdir;
cwt = multi::player[i];
calcMousedest();
auto& sd = multi::whereto[i].subdir;
sd = mousedest.subdir;
if(sd == 0) sd = 1;
}
}
}
needinput =
((countplayers == 2 && !countplayers_undecided) || countplayers_undecided >= 2);
}
EX }
}