diff --git a/bin/amd64/game_actions_967460.vdf b/bin/amd64/game_actions_967460.vdf new file mode 100644 index 000000000..d6e339b14 --- /dev/null +++ b/bin/amd64/game_actions_967460.vdf @@ -0,0 +1,167 @@ +"In Game Actions" +{ + "actions" + { + "InGameControls" + { + "title" "#Set_Ingame" + "StickPadGyro" + { + "move" + { + "title" "#Action_Move" + "input_mode" "joystick_move" + } + "camera" + { + "title" "#Action_Camera" + "input_mode" "absolute_mouse" + } + "pie_cursor" + { + "title" "#Action_PieCursor" + "input_mode" "joystick_move" + } + } + "Button" + { + "primary" "#Action_Primary" + "secondary" "#Action_Secondary" + "reload" "#Action_Reload" + "use" "#Action_Use" + "jump" "#Action_Jump" + "walk" "#Action_Walk" + "crouch" "#Action_Crouch" + "special" "#Action_Special" + "drop" "#Action_Drop" + "affinity" "#Action_Affinity" + "dash" "#Action_Dash" + + "next_weapon" "#Action_NextWeapon" + "previous_weapon" "#Action_PreviousWeapon" + "primary_weapon" "#Action_PrimaryWeapon" + "secondary_weapon" "#Action_SecondaryWeapon" + "last_weapon" "#Action_LastWeapon" + "claw" "#Action_Claw" + "pistol" "#Action_Pistol" + "sword" "#Action_Sword" + "shotgun" "#Action_Shotgun" + "smg" "#Action_SMG" + "flamer" "#Action_Flamer" + "plasma" "#Action_Plasma" + "zapper" "#Action_Zapper" + "rifle" "#Action_Rifle" + "corroder" "#Action_Corroder" + "grenade" "#Action_Grenade" + "mine" "#Action_Mine" + "rocket" "#Action_Rocket" + "minigun" "#Action_Minigun" + "jetsaw" "#Action_Jetsaw" + "melee" "#Action_Melee" + "wheel_select" "#Action_WheelSelect" + "change_loadout" "#Action_ChangeLoadout" + + "scoreboard" "#Action_Scoreboard" + "suicide" "#Action_Suicide" + "menu" "#Action_Menu" + + "recenter_camera" "#Action_RecenterCamera" + + } + } + "MenuControls" + { + "title" "#Set_Menu" + "StickPadGyro" + { + "menu_cursor" + { + "title" "#Menu_Cursor" + "input_mode" "absolute_mouse" + } + } + "AnalogTrigger" + { + } + "Button" + { + "menu_select" "#Menu_Select" + "menu_cancel" "#Menu_Cancel" + "menu_scroll_up" "#Menu_ScrollUp" + "menu_scroll_down" "#Menu_ScrollDown" + } + } + "EditingControls" + { + "title" "#Set_Editing" + "StickPadGyro" + { + } + "AnalogTrigger" + { + } + "Button" + { + } + } + } + "localization" + { + "english" + { + "Set_Ingame" "In-Game Controls" + + "Action_Move" "Move" + "Action_Camera" "Camera" + "Action_PieCursor" "Pie Cursor" + "Action_Primary" "Primary Fire" + "Action_Secondary" "Secondary Fire" + "Action_Reload" "Reload" + "Action_Use" "Use/Pick Up" + "Action_Jump" "Jump" + "Action_Walk" "Walk" + "Action_Crouch" "Crouch" + "Action_Special" "Special" + "Action_Drop" "Drop Weapon" + "Action_Affinity" "Drop Affinity" + "Action_Dash" "Dash" + "Action_NextWeapon" "Next Weapon" + "Action_PreviousWeapon" "Previous Weapon" + "Action_PrimaryWeapon" "Primary Weapon" + "Action_SecondaryWeapon" "Secondary Weapon" + "Action_LastWeapon" "Last Weapon" + "Action_Claw" "Claw Attack" + "Action_Pistol" "Sidearm Pistol" + "Action_Sword" "Energy Sword" + "Action_Shotgun" "Super Shotgun" + "Action_SMG" "Submachine Gun" + "Action_Flamer" "Flame Thrower" + "Action_Plasma" "Plasma Inductor" + "Action_Zapper" "Electro Zapper" + "Action_Rifle" "Laser Rifle" + "Action_Corroder" "Corrosion Cannon" + "Action_Grenade" "Frag Grenade" + "Action_Mine" "Shock Mine" + "Action_Rocket" "Rocket Launcher" + "Action_Minigun" "Heavy Minigun" + "Action_Jetsaw" "Jet Chainsaw" + "Action_Melee" "Eclipse Rod" + "Action_WheelSelect" "Wheel Select" + "Action_ChangeLoadout" "Change Loadout" + "Action_Scoreboard" "Scoreboard" + "Action_Suicide" "Suicide" + "Action_Menu" "Menu" + + "Action_RecenterCamera" "Recenter Camera" + + "Set_Menu" "Menu Controls" + "Menu_Cursor" "Move Cursor" + "Menu_Select" "Select" + "Menu_Cancel" "Cancel/Return" + "Menu_ScrollUp" "Scroll Up" + "Menu_ScrollDown" "Scroll Down" + + "Set_Editing" "Editing Mode Controls" + } + } +} \ No newline at end of file diff --git a/config/keymap.cfg b/config/keymap.cfg index 05ff94180..9a89e89b8 100644 --- a/config/keymap.cfg +++ b/config/keymap.cfg @@ -16,6 +16,45 @@ keymap -13 MOUSE13 keymap -14 MOUSE14 keymap -15 MOUSE15 keymap -16 MOUSE16 +// Steam Input API controller actions +keymap -20 SIAPI_PRIMARY +keymap -21 SIAPI_SECONDARY +keymap -22 SIAPI_RELOAD +keymap -23 SIAPI_USE +keymap -24 SIAPI_JUMP +keymap -25 SIAPI_WALK +keymap -26 SIAPI_CROUCH +keymap -27 SIAPI_SPECIAL +keymap -28 SIAPI_DROP +keymap -29 SIAPI_AFFINITY +keymap -30 SIAPI_DASH +keymap -31 SIAPI_NEXT_WEAPON +keymap -32 SIAPI_PREVIOUS_WEAPON +keymap -33 SIAPI_PRIMARY_WEAPON +keymap -34 SIAPI_SECONDARY_WEAPON +keymap -35 SIAPI_LAST_WEAPON +keymap -36 SIAPI_WHEEL_SELECT +keymap -37 SIAPI_CHANGE_LOADOUT +keymap -38 SIAPI_SCOREBOARD +keymap -39 SIAPI_SUICIDE +keymap -40 SIAPI_MENU +keymap -41 SIAPI_CLAW +keymap -42 SIAPI_PISTOL +keymap -43 SIAPI_SWORD +keymap -44 SIAPI_SHOTGUN +keymap -45 SIAPI_SMG +keymap -46 SIAPI_FLAMER +keymap -47 SIAPI_PLASMA +keymap -48 SIAPI_ZAPPER +keymap -49 SIAPI_RIFLE +keymap -50 SIAPI_CORRODER +keymap -51 SIAPI_GRENADE +keymap -52 SIAPI_MINE +keymap -53 SIAPI_ROCKET +keymap -54 SIAPI_MINIGUN +keymap -55 SIAPI_JETSAW +keymap -56 SIAPI_MELEE + keymap 8 BACKSPACE keymap 9 TAB keymap 13 RETURN diff --git a/config/setup.cfg b/config/setup.cfg index db8c94664..178ef2bc2 100644 --- a/config/setup.cfg +++ b/config/setup.cfg @@ -131,6 +131,49 @@ bind K [ suicide ] bind TAB [ showscores ] +// Steam Input controller binds; these actions _should not be rebound_ - do it +// through the Steam Input configuration interface +bind SIAPI_PRIMARY [ primary ] +specbind SIAPI_PRIMARY [ spectate 0 ] +bind SIAPI_SECONDARY [ secondary ] +specbind SIAPI_SECONDARY [ spectate 0 ] +bind SIAPI_WHEEL_SELECT [ game_hud_piemenu_open_weapsel_key ] +bind SIAPI_NEXT_WEAPON [ universaldelta 1 ] +bind SIAPI_PREVIOUS_WEAPON [ universaldelta -1 ] +bind SIAPI_PRIMARY_WEAPON [ weapon (weapload 0) 1 ] +bind SIAPI_SECONDARY_WEAPON [ weapon (weapload 1) 1 ] +bind SIAPI_LAST_WEAPON [ weapon (weapprev) 1 ] +bind SIAPI_MENU [ uitoggle ] +bind SIAPI_JUMP [ jump ] +specbind SIAPI_JUMP [ specmodeswitch ] +bind SIAPI_SPECIAL [ special ] +bind SIAPI_CROUCH [ crouch ] +bind SIAPI_USE [ use ] +specbind SIAPI_USE [ spectate 0 ] +bind SIAPI_RELOAD [ reload ] +specbind SIAPI_RELOAD [ specmodeswitch ] +bind SIAPI_DROP [ drop ] +bind SIAPI_AFFINITY [ affinity ] +bind SIAPI_SCOREBOARD [ showscores ] +bind SIAPI_CHANGE_LOADOUT [ gameui_player_show_loadout ] +bind SIAPI_SUICIDE [ suicide ] +bind SIAPI_CLAW [ weapon 0 1] +bind SIAPI_PISTOL [ weapon 1 1] +bind SIAPI_SWORD [ weapon 2 1] +bind SIAPI_SHOTGUN [ weapon 3 1] +bind SIAPI_SMG [ weapon 4 1] +bind SIAPI_FLAMER [ weapon 5 1] +bind SIAPI_PLASMA [ weapon 6 1] +bind SIAPI_ZAPPER [ weapon 7 1] +bind SIAPI_RIFLE [ weapon 8 1] +bind SIAPI_CORRODER [ weapon 9 1] +bind SIAPI_GRENADE [ weapon 10 1] +bind SIAPI_MINE [ weapon 11 1] +bind SIAPI_ROCKET [ weapon 12 1] +bind SIAPI_MINIGUN [ weapon 13 1] +bind SIAPI_JETSAW [ weapon 14 1] +bind SIAPI_MELEE [ weapon 15 1] + saytextcolour = 0; setpersist saytextcolour 1; setcomplete saytextcolour 1 getsaycolour = [ sc = $saytextcolour diff --git a/config/ui/game/hud/pie.cfg b/config/ui/game/hud/pie.cfg index 76cf0f124..dcc1afd64 100644 --- a/config/ui/game/hud/pie.cfg +++ b/config/ui/game/hud/pie.cfg @@ -222,6 +222,7 @@ ui_game_hud_piemenu = [ ] newui "hud_piemenu" $SURFACE_FOREGROUND [ + uiwinstyle $WINSTYLE_PIE uiallowinput 1 ui_game_hud_piemenu diff --git a/config/ui/game/settings.cfg b/config/ui/game/settings.cfg index 7cbe4ddc4..a86c15ed4 100644 --- a/config/ui/game/settings.cfg +++ b/config/ui/game/settings.cfg @@ -429,6 +429,35 @@ ui_gameui_settings_controls_dims = 0.72 ] ] + uivlist 0.025 [ + ui_gameui_group [ + uistyle clampx + + uigrid 2 0.05 0 [ + uistyle clampx + + uicolourtext "Type" 0xaaaaaa + ui_gameui_switch textkeyimagepreference [ + p_options = [ + "Automatic" + "Always Keyboard/Mouse" + "Always Controller" + "Both" + ] + p_tip = "Which device icons to show in text" + p_id = #(gameui_get_id switch) + ] + ] + ] [ + p_label = "Input Icons" + ] + ui_gameui_button [ + p_label = "Open Controller Configurator" + p_on_click = [ showsiapibindpanel ] + p_id = #(gameui_get_id button) + ] + ] + ui_gameui_vscrollarea [ uiborderedimageclamped $skintex 0x44010101 0 $ui_texborder $ui_screenborder 0.56 0.5 [ uivlist 0 [ diff --git a/src/Makefile b/src/Makefile index d5b2e9626..756a3c5ed 100644 --- a/src/Makefile +++ b/src/Makefile @@ -344,7 +344,8 @@ CLIENT_OBJS = \ game/scoreboard.o \ game/server.o \ game/waypoint.o \ - game/weapons.o + game/weapons.o \ + game/controller.o # Build tests ifneq (,$(findstring -D_DEBUG,$(CXXFLAGS))) diff --git a/src/engine/cdpi.cpp b/src/engine/cdpi.cpp index 8e0a3558a..f362d3459 100644 --- a/src/engine/cdpi.cpp +++ b/src/engine/cdpi.cpp @@ -40,6 +40,7 @@ namespace cdpi ISteamUserStats *stats = NULL; ISteamClient *client = NULL, *sclient = NULL; ISteamGameServer *serv = NULL; + ISteamInput *input = NULL; HSteamPipe umpipe = 0, smpipe = 0; HSteamUser uupipe = 0, supipe = 0; HAuthTicket authticket = k_HAuthTicketInvalid; @@ -184,6 +185,9 @@ namespace cdpi if(!friends) { conoutf(colourred, "Failed to get Steam friends interface."); cleanup(SWCLIENT); return true; } stats = (ISteamUserStats *)SteamAPI_ISteamClient_GetISteamUserStats(client, uupipe, umpipe, STEAMUSERSTATS_INTERFACE_VERSION); if(!stats) { conoutf(colourred, "Failed to get Steam stats interface."); cleanup(SWCLIENT); return true; } + input = (ISteamInput *)SteamAPI_ISteamClient_GetISteamInput(client, uupipe, umpipe, STEAMINPUT_INTERFACE_VERSION); + if (!input) { conoutf(colourred, "Failed to get Steam Input interface."); cleanup(SWCLIENT); return true; } + input->Init(false); const char *name = SteamAPI_ISteamFriends_GetPersonaName(friends); if(name && *name) diff --git a/src/engine/engine.h b/src/engine/engine.h index c0c2d7da1..b777d7729 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -3,6 +3,9 @@ #include "version.h" #include "cube.h" +#if defined(USE_STEAM) +#include "steam_api_flat.h" +#endif #define LOG_FILE "log.txt" @@ -49,6 +52,9 @@ namespace cdpi namespace steam { extern char *steamusername, *steamuserid, *steamserverid; + #if defined(USE_STEAM) + extern ISteamInput *input; + #endif extern bool clientready(); extern bool clientauthticket(char *token, uint *tokenlen, ENetAddress *addr = NULL); diff --git a/src/engine/main.cpp b/src/engine/main.cpp index eff8bdc71..4d412c908 100644 --- a/src/engine/main.cpp +++ b/src/engine/main.cpp @@ -1,6 +1,7 @@ // main.cpp: initialisation & main loop #include "engine.h" +#include "controller.h" #include #ifdef SDL_VIDEO_DRIVER_X11 @@ -737,7 +738,10 @@ void checkinput() case SDL_KEYDOWN: case SDL_KEYUP: if(keyrepeatmask || !event.key.repeat) + { + controller::lastinputwassiapi = false; processkey(event.key.keysym.sym, event.key.state==SDL_PRESSED); + } break; case SDL_WINDOWEVENT: @@ -784,7 +788,7 @@ void checkinput() { int dx = event.motion.xrel, dy = event.motion.yrel; checkmousemotion(dx, dy); - shouldwarp = game::mousemove(dx, dy, event.motion.x, event.motion.y, screenw, screenh); // whether game controls engine cursor + shouldwarp = game::mousemove(dx, dy, event.motion.x, event.motion.y, screenw, screenh, false); // whether game controls engine cursor mousemoved = true; } else if(shouldgrab) inputgrab(grabinput = true); @@ -807,10 +811,12 @@ void checkinput() int button = event.button.button; if(button >= 6) button += 4; // skip mousewheel X (-4,-5) & Y (-8, 9) else if(button >= 4) button += 2; // skip mousewheel X (-4,-5) + controller::lastinputwassiapi = false; processkey(-button, event.button.state==SDL_PRESSED); break; } case SDL_MOUSEWHEEL: + controller::lastinputwassiapi = false; if(event.wheel.y > 0) { processkey(-4, true); processkey(-4, false); } else if(event.wheel.y < 0) { processkey(-5, true); processkey(-5, false); } else if(event.wheel.x > 0) { processkey(-8, true); processkey(-8, false); } @@ -824,6 +830,7 @@ void checkinput() warping = false; if(grabinput && shouldwarp) resetcursor(true, false); } + controller::update_from_controller(); } void swapbuffers(bool overlay) diff --git a/src/engine/rendertext.cpp b/src/engine/rendertext.cpp index 7ea63cb65..71ce275ea 100644 --- a/src/engine/rendertext.cpp +++ b/src/engine/rendertext.cpp @@ -1,4 +1,13 @@ +#include "rendertext.h" #include "engine.h" +#include "controller.h" + +enum textkeyimagetype { + tkip_automatic, // Figure out which glyphs to show based on last input + tkip_kbm, // Always show keyboard/mouse glyphs + tkip_controller, // Always show controller glyphs + tkip_both, // Always show *both* keyboard/mouse and controller glyphs +}; VARF(IDF_PERSIST, textsupersample, 0, 1, 2, initwarning("Text Supersampling", INIT_LOAD, CHANGE_SHADERS)); @@ -14,6 +23,7 @@ FVAR(IDF_PERSIST, textspacescale, 0, 0.5f, 10); FVAR(IDF_PERSIST, textimagescale, 0, 0.8f, FVAR_MAX); VAR(IDF_PERSIST, textkeyimages, 0, 1, 1); +VAR(IDF_PERSIST, textkeyimagepreference, tkip_automatic, tkip_automatic, tkip_both); FVAR(IDF_PERSIST, textkeyimagescale, 0, 0.8f, FVAR_MAX); SVAR(IDF_PERSIST, textkeyprefix, "textures/keys/"); VAR(IDF_PERSIST, textkeyseps, 0, 1, 1); @@ -34,6 +44,21 @@ font *curfont = NULL; int curfontpass = 0; bool wantfontpass = false; +bool shouldkeepkey(const char *str) +{ + bool is_siapi_textkey = controller::is_siapi_textkey(str); + switch (textkeyimagepreference) { + case tkip_automatic: + return controller::lastinputwassiapi ? is_siapi_textkey : !is_siapi_textkey; + case tkip_kbm: + return !is_siapi_textkey; + case tkip_controller: + return is_siapi_textkey; + case tkip_both: + return true; + }; +} + void fontscale(float *scale) { if(!fontdef) return; @@ -742,37 +767,53 @@ void text_boundsf(const char *str, float &width, float &height, float xpad, floa #undef TEXTCHAR } -struct textkey +textkey *findtextkey_common(const char *str, vector textkeycache, const char *filename) { - char *name, *file; - Texture *tex; - textkey() : name(NULL), file(NULL), tex(NULL) {} - textkey(char *n, char *f, Texture *t) : name(newstring(n)), file(newstring(f)), tex(t) {} - ~textkey() + loopv(textkeycache) if(!strcmp(textkeycache[i]->name, str)) return textkeycache[i]; + + static string key; + + // The controller code has a separate way of determining the filename, so + // take the passed filename if given + if (!filename) { - DELETEA(name); - DELETEA(file); + copystring(key, textkeyprefix); + int q = strlen(key); + concatstring(key, str); + for(int r = strlen(key); q < r; q++) key[q] = tolower(key[q]); + } else { + copystring(key, filename); } -}; -vector textkeys; -textkey *findtextkey(const char *str) -{ - loopv(textkeys) if(!strcmp(textkeys[i]->name, str)) return textkeys[i]; - static string key; - copystring(key, textkeyprefix); - int q = strlen(key); - concatstring(key, str); - for(int r = strlen(key); q < r; q++) key[q] = tolower(key[q]); textkey *t = new textkey; t->name = newstring(str); t->file = newstring(key); t->tex = textureload(t->file, 3, true, false); if(t->tex == notexture) t->tex = NULL; - textkeys.add(t); + textkeycache.add(t); return t; } +vector textkeys; +vector _findtextkeys_container; + +vector findtextkeys(const char *str) +{ + // SIAPI actions have special handling because there are several fundamental + // differences between SIAPI textkeys and KB/M textkeys + if(controller::is_siapi_textkey(str)) return controller::get_siapi_textkeys(str); + + textkey *tk = findtextkey_common(str, textkeys, NULL); + + // Should probably just arrange to have this vector be 1 long at + // initialization, but I don't know how to do this... + if(!_findtextkeys_container.capacity()) _findtextkeys_container.add(tk); + else _findtextkeys_container[0] = tk; + + return _findtextkeys_container; + +} + struct tklookup { char *name; @@ -819,21 +860,35 @@ float key_widthf(const char *str) vector list; explodelist(keyn, list); float width = 0, scale = curfont->scale*curtextscale*textkeyimagescale; + int skippedkeys = 0; loopv(list) { - if(i && textkeyseps) width += text_widthf(" or "); + if(!shouldkeepkey(list[i])) + { + skippedkeys++; + continue; + } + if(i && i > skippedkeys && textkeyseps) width += text_widthf(" or "); + bool foundtextkey = false; if(textkeyimages) { - textkey *t = findtextkey(list[i]); - if(t && t->tex) + vector tks = findtextkeys(list[i]); + loopvj(tks) { - width += (t->tex->w*scale)/float(t->tex->h); - continue; + textkey *t = tks[j]; + if(t && t->tex) + { + width += (t->tex->w*scale)/float(t->tex->h); + foundtextkey = true; + } + if(j && textkeyseps) width += text_widthf(" or "); } - // fallback if not found } - defformatkey(keystr, list[i]); - width += text_widthf(keystr); + if(!foundtextkey) + { + defformatkey(keystr, list[i]); + width += text_widthf(keystr); + } } list.deletearrays(); return width; @@ -847,9 +902,15 @@ static float draw_key(Texture *&tex, const char *str, float sx, float sy, bvec4 vector list; explodelist(keyn, list); float width = 0; + int skippedkeys = 0; loopv(list) { - if(i && textkeyseps) + if(!shouldkeepkey(list[i])) + { + skippedkeys++; + continue; + } + if(i && i > skippedkeys && textkeyseps) { if(!curfontpass) { @@ -863,44 +924,65 @@ static float draw_key(Texture *&tex, const char *str, float sx, float sy, bvec4 } width += text_widthf(" or "); } + bool foundtextkey = false; if(textkeyimages) { - textkey *t = findtextkey(list[i]); - if(t && t->tex) + vector tks = findtextkeys(list[i]); + loopvj(tks) { - float sh = curfont->scale*curtextscale, h = sh*textkeyimagescale, w = (t->tex->w*h)/float(t->tex->h); - if(curfontpass) + textkey *t = tks[j]; + if(j && textkeyseps) + { + if(!curfontpass) + { + if(tex != oldtex) + { + xtraverts += gle::end(); + tex = oldtex; + settexture(tex); + } + draw_text(" or ", sx + width, sy, color.r, color.g, color.b, color.a, 0, -1, -1, 1); + } + width += text_widthf(" or "); + } + if(t && t->tex) { - if(tex != t->tex) + float sh = curfont->scale*curtextscale, h = sh*textkeyimagescale, w = (t->tex->w*h)/float(t->tex->h); + if(curfontpass) { - xtraverts += gle::end(); - tex = t->tex; - settexture(tex); + if(tex != t->tex) + { + xtraverts += gle::end(); + tex = t->tex; + settexture(tex); + } + float oh = h-sh, oy = sy-oh*0.5f; + textvert(sx + width, oy ); gle::attribf(0, 0); + textvert(sx + width + w, oy ); gle::attribf(1, 0); + textvert(sx + width + w, oy + h); gle::attribf(1, 1); + textvert(sx + width, oy + h); gle::attribf(0, 1); } - float oh = h-sh, oy = sy-oh*0.5f; - textvert(sx + width, oy ); gle::attribf(0, 0); - textvert(sx + width + w, oy ); gle::attribf(1, 0); - textvert(sx + width + w, oy + h); gle::attribf(1, 1); - textvert(sx + width, oy + h); gle::attribf(0, 1); + else wantfontpass = true; + width += w; + foundtextkey = true; } - else wantfontpass = true; - width += w; - continue; } - // fallback if not found } - defformatkey(keystr, list[i]); - if(!curfontpass) + if(!foundtextkey) { - if(tex != oldtex) + defformatkey(keystr, list[i]); + if(!curfontpass) { - xtraverts += gle::end(); - tex = oldtex; - settexture(tex); + if(tex != oldtex) + { + xtraverts += gle::end(); + tex = oldtex; + settexture(tex); + } + draw_text(keystr, sx + width, sy, color.r, color.g, color.b, color.a, 0, -1, -1, 1); } - draw_text(keystr, sx + width, sy, color.r, color.g, color.b, color.a, 0, -1, -1, 1); + width += text_widthf(keystr); } - width += text_widthf(keystr); } list.deletearrays(); return width; diff --git a/src/engine/rendertext.h b/src/engine/rendertext.h new file mode 100644 index 000000000..b3661fed4 --- /dev/null +++ b/src/engine/rendertext.h @@ -0,0 +1,20 @@ +#ifndef CPP_RENDERTEXT_HEADER +#define CPP_RENDERTEXT_HEADER + +#include "engine.h" + +struct textkey +{ + char *name, *file; + Texture *tex; + textkey() : name(NULL), file(NULL), tex(NULL) {} + textkey(char *n, char *f, Texture *t) : name(newstring(n)), file(newstring(f)), tex(t) {} + ~textkey() + { + DELETEA(name); + DELETEA(file); + } +}; + +textkey *findtextkey_common(const char *str, vector textkeycache, const char *filename = NULL); +#endif diff --git a/src/engine/ui.cpp b/src/engine/ui.cpp index 9279d32df..51b43a2ac 100644 --- a/src/engine/ui.cpp +++ b/src/engine/ui.cpp @@ -955,7 +955,7 @@ namespace UI } }; - #define WINSTYLE_ENUM(en, um) en(um, Normal, NORMAL) en(um, Tool Tip, TOOLTIP) en(um, Popup, POPUP) en(um, Crosshair, CROSSHAIR) en(um, Cursor, CURSOR) en(um, Max, MAX) + #define WINSTYLE_ENUM(en, um) en(um, Normal, NORMAL) en(um, Pie, PIE) en(um, Gameplay, GAMEPLAY) en(um, Tool Tip, TOOLTIP) en(um, Popup, POPUP) en(um, Crosshair, CROSSHAIR) en(um, Cursor, CURSOR) en(um, Max, MAX) ENUM_DLN(WINSTYLE); struct Window : Object @@ -2057,6 +2057,26 @@ namespace UI return false; } + bool menuisgameplay() + { + if(surfaceinput == type) loopwindows(w, + { + if(!w->visible || !checkexclusive(w) || w->winstyle >= WINSTYLE_CROSSHAIR) continue; + if(!(w->state&STATE_HIDDEN) && (w->winstyle == WINSTYLE_PIE || w->winstyle == WINSTYLE_GAMEPLAY)) return true; + }); + return false; + } + + bool menuispie() + { + if(surfaceinput == type) loopwindows(w, + { + if(!w->visible || !checkexclusive(w) || w->winstyle >= WINSTYLE_CROSSHAIR) continue; + if(!(w->state&STATE_HIDDEN) && w->winstyle == WINSTYLE_PIE) return true; + }); + return false; + } + const char *topname() { loopwindowsrev(w, @@ -2078,7 +2098,13 @@ namespace UI uiscale = 1; switch(w->winstyle) { - case WINSTYLE_NORMAL: default: break; + // Pie and gameplay window style are means for the + // controller code to know if a UI element is supposed to be + // used in-game (and so should continue to use in-game + // controls) or is a menu (and so should switch to menu + // controls); it shouldn't actually affect handling or + // rendering. + case WINSTYLE_PIE: case WINSTYLE_GAMEPLAY: case WINSTYLE_NORMAL: default: break; case WINSTYLE_TOOLTIP: { w->setpos(getuicursorx() - w->w * getuicursorx(false), getuicursory() >= 0.5f ? getuicursory() - w->h - uitipoffset : getuicursory() + hud::cursorsize + uitipoffset); @@ -7295,6 +7321,20 @@ namespace UI return ret; } + bool menuisgameplay(int stype) + { + bool ret = false; + SWSURFACE(stype, if(surface->menuisgameplay()) ret = true); + return ret; + } + + bool menuispie(int stype) + { + bool ret = false; + SWSURFACE(stype, if(surface->menuispie()) ret = true); + return ret; + } + ICOMMANDV(0, uihasmenu, hasmenu()); ICOMMAND(0, uigetmenu, "ib", (int *pass, int *stype), intret(hasmenu(*pass != 0, *stype >= 0 ? *stype : -1))); diff --git a/src/game/controller.cpp b/src/game/controller.cpp new file mode 100644 index 000000000..b7837c277 --- /dev/null +++ b/src/game/controller.cpp @@ -0,0 +1,634 @@ +//#include "controller.h" +#include "game.h" +#include "tools.h" +#include "rendertext.h" +#include "engine.h" + +#if defined(USE_STEAM) +#include "steam_api_flat.h" +#endif + +#include + +#define DEF_ACTION_SET(x) InputActionSetHandle_t x##_handle = 0 +#define DEF_ANALOG_ACTION(x) InputAnalogActionHandle_t x##_handle = 0 + +#define SET_ACTION_SET(x) x##_handle = cdpi::steam::input->GetActionSetHandle(#x) +#define SET_ANALOG_ACTION(x) x##_handle =cdpi::steam::input->GetAnalogActionHandle(#x) +#define SET_DIGITAL_ACTION(x) x.handle = cdpi::steam::input->GetDigitalActionHandle(#x) + +namespace controller +{ +// Track if last input was keyboard or mouse buttons. +// Set to true when using any SIAPI actions +// Set to false when using any other type of action +// Note that mouse motion does _not_ reset the state of this variable. +bool lastinputwassiapi = false; + +// If we are not using the SIAPI move action, then do not overwrite strafing +// data set by the regular keyboard bindings +bool lastmovementwaskeyboard = true; + +// keymap codes to nice names - keep this in sync with keymaps.cfg +enum siapi_keycodes { + SIAPI_PRIMARY = -20, + SIAPI_SECONDARY = -21, + SIAPI_RELOAD = -22, + SIAPI_USE = -23, + SIAPI_JUMP = -24, + SIAPI_WALK = -25, + SIAPI_CROUCH = -26, + SIAPI_SPECIAL = -27, + SIAPI_DROP = -28, + SIAPI_AFFINITY = -29, + SIAPI_DASH = -30, + SIAPI_NEXT_WEAPON = -31, + SIAPI_PREVIOUS_WEAPON = -32, + SIAPI_PRIMARY_WEAPON = -33, + SIAPI_SECONDARY_WEAPON = -34, + SIAPI_LAST_WEAPON = -35, + SIAPI_WHEEL_SELECT = -36, + SIAPI_CHANGE_LOADOUT = -37, + SIAPI_SCOREBOARD = -38, + SIAPI_SUICIDE = -39, + SIAPI_MENU = -40, + SIAPI_CLAW = -41, + SIAPI_PISTOL = -42, + SIAPI_SWORD = -43, + SIAPI_SHOTGUN = -44, + SIAPI_SMG = -45, + SIAPI_FLAMER = -46, + SIAPI_PLASMA = -47, + SIAPI_ZAPPER = -48, + SIAPI_RIFLE = -49, + SIAPI_CORRODER = -50, + SIAPI_GRENADE = -51, + SIAPI_MINE = -52, + SIAPI_ROCKET = -53, + SIAPI_MINIGUN = -54, + SIAPI_JETSAW = -55, + SIAPI_MELEE = -56, + // The menu code does not use the keymap system and doesn't allow you to + // rebind those controls. Because of this, it is safe to 'pretend' to be + // the other keys directly with no consequences. + SIAPI_MENU_SELECT = -1, // left mouse + SIAPI_MENU_CANCEL = -2, // right mouse + SIAPI_MENU_SCROLL_UP = -4, // scroll wheel -Y + SIAPI_MENU_SCROLL_DOWN = -5, // scroll wheel +Y +}; + +// For returning SIAPI glyphs as a list +vector textkeyvec; + +// This current controller implementation depends on Steam Input and is not +// available outside of Steam +#if defined(USE_STEAM) + +InputHandle_t *controllers = new InputHandle_t[STEAM_INPUT_MAX_COUNT]; +InputHandle_t lastusedcontroller = 0; + +bool get_digital_action_state(int controlleridx, int siapi_digital_handle) +{ + InputDigitalActionData_t data = cdpi::steam::input->GetDigitalActionData(controllers[controlleridx], siapi_digital_handle); + if(data.bState) + { + lastinputwassiapi = true; + lastusedcontroller = controllers[controlleridx]; + } + return data.bState; +} + +class digital_action_state +{ + bool input_last_frame[STEAM_INPUT_MAX_COUNT]; + bool input_this_frame[STEAM_INPUT_MAX_COUNT]; + +public: + InputDigitalActionHandle_t handle = -1; + int keymap_id = -1; + // Should be more than one origin eventually! + EInputActionOrigin origin = k_EInputActionOrigin_None; + + void update(int controlleridx) + { + this->input_last_frame[controlleridx] = this->input_this_frame[controlleridx]; + this->input_this_frame[controlleridx] = get_digital_action_state(controlleridx, this->handle); + } + + bool pressed(int controlleridx) + { + return this->input_this_frame[controlleridx]; + } + + bool released(int controlleridx) + { + return !this->input_this_frame[controlleridx]; + } + + bool just_pressed(int controlleridx) + { + return this->input_this_frame[controlleridx] && !this->input_last_frame[controlleridx]; + } + + bool just_released(int controlleridx) + { + return !this->input_this_frame[controlleridx] && this->input_last_frame[controlleridx]; + } + + void ingame_process(int controlleridx) + { + this->update(controlleridx); + + if(this->just_pressed(controlleridx)) processkey(this->keymap_id, true); + else if(this->just_released(controlleridx)) processkey(this->keymap_id, false); + } + + void menu_process(int controlleridx) + { + this->update(controlleridx); + + if(this->just_pressed(controlleridx)) UI::keypress(this->keymap_id, true); + else if(this->just_released(controlleridx)) UI::keypress(this->keymap_id, false); + } +}; + +DEF_ACTION_SET(InGameControls); + +DEF_ANALOG_ACTION(move); +DEF_ANALOG_ACTION(camera); +DEF_ANALOG_ACTION(pie_cursor); + +class digital_action_state primary; +class digital_action_state secondary; +class digital_action_state reload; +class digital_action_state use; +class digital_action_state jump; +class digital_action_state walk; +class digital_action_state crouch; +class digital_action_state special; +class digital_action_state drop; +class digital_action_state affinity; +class digital_action_state dash; +class digital_action_state next_weapon; +class digital_action_state previous_weapon; +class digital_action_state primary_weapon; +class digital_action_state secondary_weapon; +class digital_action_state last_weapon; +class digital_action_state claw; +class digital_action_state pistol; +class digital_action_state sword; +class digital_action_state shotgun; +class digital_action_state smg; +class digital_action_state flamer; +class digital_action_state plasma; +class digital_action_state zapper; +class digital_action_state rifle; +class digital_action_state corroder; +class digital_action_state grenade; +class digital_action_state mine; +class digital_action_state rocket; +class digital_action_state minigun; +class digital_action_state jetsaw; +class digital_action_state melee; +class digital_action_state wheel_select; +class digital_action_state change_loadout; +class digital_action_state scoreboard; +class digital_action_state suicide; +class digital_action_state menu; + +class digital_action_state recenter_camera; + +DEF_ACTION_SET(MenuControls); +DEF_ANALOG_ACTION(menu_cursor); +class digital_action_state menu_select; +class digital_action_state menu_cancel; +class digital_action_state menu_scroll_up; +class digital_action_state menu_scroll_down; + +DEF_ACTION_SET(EditingControls); + +void init_siapi_handles() +{ + SET_ACTION_SET(InGameControls); + + SET_ANALOG_ACTION(move); + SET_ANALOG_ACTION(camera); + SET_ANALOG_ACTION(pie_cursor); + + SET_DIGITAL_ACTION(primary); + primary.keymap_id = SIAPI_PRIMARY; + + SET_DIGITAL_ACTION(secondary); + secondary.keymap_id = SIAPI_SECONDARY; + + SET_DIGITAL_ACTION(reload); + reload.keymap_id = SIAPI_RELOAD; + + SET_DIGITAL_ACTION(use); + use.keymap_id = SIAPI_USE; + + SET_DIGITAL_ACTION(jump); + jump.keymap_id = SIAPI_JUMP; + + SET_DIGITAL_ACTION(walk); + walk.keymap_id = SIAPI_WALK; + + SET_DIGITAL_ACTION(crouch); + crouch.keymap_id = SIAPI_CROUCH; + + SET_DIGITAL_ACTION(special); + special.keymap_id = SIAPI_SPECIAL; + + SET_DIGITAL_ACTION(drop); + drop.keymap_id = SIAPI_DROP; + + SET_DIGITAL_ACTION(affinity); + affinity.keymap_id = SIAPI_AFFINITY; + + SET_DIGITAL_ACTION(dash); + dash.keymap_id = SIAPI_DASH; + + SET_DIGITAL_ACTION(next_weapon); + next_weapon.keymap_id = SIAPI_NEXT_WEAPON; + + SET_DIGITAL_ACTION(previous_weapon); + previous_weapon.keymap_id = SIAPI_PREVIOUS_WEAPON; + + SET_DIGITAL_ACTION(primary_weapon); + primary_weapon.keymap_id = SIAPI_PRIMARY_WEAPON; + + SET_DIGITAL_ACTION(secondary_weapon); + secondary_weapon.keymap_id = SIAPI_SECONDARY_WEAPON; + + SET_DIGITAL_ACTION(last_weapon); + last_weapon.keymap_id = SIAPI_LAST_WEAPON; + + SET_DIGITAL_ACTION(claw); + claw.keymap_id = SIAPI_CLAW; + + SET_DIGITAL_ACTION(pistol); + pistol.keymap_id = SIAPI_PISTOL; + + SET_DIGITAL_ACTION(sword); + sword.keymap_id = SIAPI_SWORD; + + SET_DIGITAL_ACTION(shotgun); + shotgun.keymap_id = SIAPI_SHOTGUN; + + SET_DIGITAL_ACTION(smg); + smg.keymap_id = SIAPI_SMG; + + SET_DIGITAL_ACTION(flamer); + flamer.keymap_id = SIAPI_FLAMER; + + SET_DIGITAL_ACTION(plasma); + plasma.keymap_id = SIAPI_PLASMA; + + SET_DIGITAL_ACTION(zapper); + zapper.keymap_id = SIAPI_ZAPPER; + + SET_DIGITAL_ACTION(rifle); + rifle.keymap_id = SIAPI_RIFLE; + + SET_DIGITAL_ACTION(corroder); + corroder.keymap_id = SIAPI_CORRODER; + + SET_DIGITAL_ACTION(grenade); + grenade.keymap_id = SIAPI_GRENADE; + + SET_DIGITAL_ACTION(mine); + mine.keymap_id = SIAPI_MINE; + + SET_DIGITAL_ACTION(rocket); + rocket.keymap_id = SIAPI_ROCKET; + + SET_DIGITAL_ACTION(minigun); + minigun.keymap_id = SIAPI_MINIGUN; + + SET_DIGITAL_ACTION(jetsaw); + jetsaw.keymap_id = SIAPI_JETSAW; + + SET_DIGITAL_ACTION(melee); + melee.keymap_id = SIAPI_MELEE; + + SET_DIGITAL_ACTION(wheel_select); + wheel_select.keymap_id = SIAPI_WHEEL_SELECT; + + SET_DIGITAL_ACTION(change_loadout); + change_loadout.keymap_id = SIAPI_CHANGE_LOADOUT; + + SET_DIGITAL_ACTION(scoreboard); + scoreboard.keymap_id = SIAPI_SCOREBOARD; + + SET_DIGITAL_ACTION(suicide); + suicide.keymap_id = SIAPI_SUICIDE; + + SET_DIGITAL_ACTION(menu); + menu.keymap_id = SIAPI_MENU; + + SET_DIGITAL_ACTION(recenter_camera); + + SET_ACTION_SET(MenuControls); + SET_ANALOG_ACTION(menu_cursor); + + SET_DIGITAL_ACTION(menu_select); + menu_select.keymap_id = SIAPI_MENU_SELECT; + + SET_DIGITAL_ACTION(menu_cancel); + menu_cancel.keymap_id = SIAPI_MENU_CANCEL; + + SET_DIGITAL_ACTION(menu_scroll_up); + menu_scroll_up.keymap_id = SIAPI_MENU_SCROLL_UP; + + SET_DIGITAL_ACTION(menu_scroll_down); + menu_scroll_down.keymap_id = SIAPI_MENU_SCROLL_DOWN; + + SET_ACTION_SET(EditingControls); + + // Also initialize textkey glyph return buffer to its maximum size + textkeyvec.growbuf(STEAM_INPUT_MAX_ORIGINS); +} + +void update_ingame_actions(int controlleridx); +void update_menu_actions(int controlleridx); + +void update_from_controller() +{ + // Steamworks ( https://partner.steamgames.com/doc/api/ISteamInput#RunFrame + // ) says that + + // > Synchronize API state with the latest Steam Controller inputs + // > available. This is performed automatically by SteamAPI_RunCallbacks, + // > but for the absolute lowest possible latency, you can call this + // > directly before reading controller state. + + // which appears to be necessary here, otherwise we seem to drop some + // gamepad inputs + cdpi::steam::input->RunFrame(); + + int connected_count = cdpi::steam::input->GetConnectedControllers(controllers); + + if(connected_count == 0) return; + + // Initialize handles if needed + if(!(InGameControls_handle && MenuControls_handle && EditingControls_handle)) controller::init_siapi_handles(); + + for (int i = 0; i < connected_count; i++) + { + if(editmode) + { + // TODO: We currently don't have SIAPI actions for + // editing mode, but we do provide an action set for + // convenience + cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, EditingControls_handle); + continue; + } + + if(UI::hasinput() && !UI::menuisgameplay()) + { + cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, MenuControls_handle); + update_menu_actions(i); + continue; + } + + cdpi::steam::input->ActivateActionSet(STEAM_INPUT_HANDLE_ALL_CONTROLLERS, InGameControls_handle); + update_ingame_actions(i); + } +} + +void update_ingame_actions(int controlleridx) +{ + InputAnalogActionData_t move_data = cdpi::steam::input->GetAnalogActionData( + controllers[controlleridx], + move_handle + ); + + if(lastusedcontroller == controllers[controlleridx]) + { + //game::player1->move = move_data.y; + + if(move_data.y < -0.5f) + game::player1->move = -1; + else if(move_data.y > 0.5f) + game::player1->move = 1; + else if(!lastmovementwaskeyboard) + game::player1->move = 0; + + //game::player1->strafe = -move_data.x; + + if(move_data.x < -0.5f) + game::player1->strafe = 1; + else if(move_data.x > 0.5f) + game::player1->strafe = -1; + else if(!lastmovementwaskeyboard) + game::player1->strafe = 0; + } + + if(move_data.x != 0 || move_data.y != 0) + { + lastinputwassiapi = true; + lastusedcontroller = controllers[controlleridx]; + lastmovementwaskeyboard = false; + } + + // We have to read the camera delta every frame even if we don't intend + // on doing anything with it, otherwise it will 'build up', which is not + // what we want in the cases where we are going to deliberately ignore + // it. + InputAnalogActionData_t camera_delta = cdpi::steam::input->GetAnalogActionData( + controllers[controlleridx], + camera_handle + ); + + recenter_camera.update(controlleridx); + if(recenter_camera.pressed(controlleridx)) + { + game::resetplayerpitch(); + } else { + game::mousemove(camera_delta.x, camera_delta.y, 0, 0, screenw, screenh, true); + if(camera_delta.x != 0 || camera_delta.y != 0) + { + lastinputwassiapi = true; + lastusedcontroller = controllers[controlleridx]; + } + if(UI::menuispie()) + { + // For pie menus (like the weapon select wheel), we have + // a special joystick-like mode that's also available to + // use. It will override the standard camera action if + // in use (good for not needing to disable a gyro, for + // example). The expectation is that the controller + // config will modeshift the stick/pad when a button + // bound to a pie menu is hit. + InputAnalogActionData_t pie_pos = cdpi::steam::input->GetAnalogActionData( + controllers[controlleridx], + pie_cursor_handle + ); + + if(pie_pos.x != 0.0f || pie_pos.y != 0.0f) + { + resetcursor(true, true); + game::mousemove( + // screenh is not a typo + pie_pos.x * (screenh / 4), + -pie_pos.y * (screenh / 4), + 0, 0, + screenw, screenh, + true + ); + } + } + } + + primary.ingame_process(controlleridx); + secondary.ingame_process(controlleridx); + reload.ingame_process(controlleridx); + use.ingame_process(controlleridx); + jump.ingame_process(controlleridx); + walk.ingame_process(controlleridx); + crouch.ingame_process(controlleridx); + special.ingame_process(controlleridx); + drop.ingame_process(controlleridx); + affinity.ingame_process(controlleridx); + dash.ingame_process(controlleridx); + next_weapon.ingame_process(controlleridx); + previous_weapon.ingame_process(controlleridx); + primary_weapon.ingame_process(controlleridx); + secondary_weapon.ingame_process(controlleridx); + last_weapon.ingame_process(controlleridx); + claw.ingame_process(controlleridx); + pistol.ingame_process(controlleridx); + sword.ingame_process(controlleridx); + shotgun.ingame_process(controlleridx); + smg.ingame_process(controlleridx); + flamer.ingame_process(controlleridx); + plasma.ingame_process(controlleridx); + zapper.ingame_process(controlleridx); + rifle.ingame_process(controlleridx); + corroder.ingame_process(controlleridx); + grenade.ingame_process(controlleridx); + mine.ingame_process(controlleridx); + rocket.ingame_process(controlleridx); + minigun.ingame_process(controlleridx); + jetsaw.ingame_process(controlleridx); + melee.ingame_process(controlleridx); + wheel_select.ingame_process(controlleridx); + change_loadout.ingame_process(controlleridx); + scoreboard.ingame_process(controlleridx); + suicide.ingame_process(controlleridx); + menu.ingame_process(controlleridx); +} + +void update_menu_actions(int controlleridx) +{ + InputAnalogActionData_t cursor_data = cdpi::steam::input->GetAnalogActionData( + controllers[controlleridx], + menu_cursor_handle + ); + + game::mousemove(cursor_data.x, cursor_data.y, 0, 0, screenw, screenh, true); + + if(cursor_data.x != 0 || cursor_data.y != 0) lastinputwassiapi = true; + + menu_select.menu_process(controlleridx); + menu_cancel.menu_process(controlleridx); + menu_scroll_up.menu_process(controlleridx); + menu_scroll_down.menu_process(controlleridx); +} + +bool is_siapi_textkey(const char *str) +{ + return !strncmp(str, "SIAPI_", 6); +} + +digital_action_state *get_das_for_keymap_name(const char *str) +{ + // This function is awful, redo to be smarter + if(!strcmp(str, "SIAPI_PRIMARY")) return &primary; + if(!strcmp(str, "SIAPI_SECONDARY")) return &secondary; + if(!strcmp(str, "SIAPI_RELOAD")) return &reload; + if(!strcmp(str, "SIAPI_USE")) return &use; + if(!strcmp(str, "SIAPI_JUMP")) return &jump; + if(!strcmp(str, "SIAPI_WALK")) return &walk; + if(!strcmp(str, "SIAPI_CROUCH")) return &crouch; + if(!strcmp(str, "SIAPI_SPECIAL")) return &special; + if(!strcmp(str, "SIAPI_DROP")) return &drop; + if(!strcmp(str, "SIAPI_AFFINITY")) return &affinity; + if(!strcmp(str, "SIAPI_DASH")) return ‐ + if(!strcmp(str, "SIAPI_NEXT_WEAPON")) return &next_weapon; + if(!strcmp(str, "SIAPI_PREVIOUS_WEAPON")) return &previous_weapon; + if(!strcmp(str, "SIAPI_PRIMARY_WEAPON")) return &primary_weapon; + if(!strcmp(str, "SIAPI_SECONDARY_WEAPON")) return &secondary_weapon; + if(!strcmp(str, "SIAPI_WHEEL_SELECT")) return &wheel_select; + if(!strcmp(str, "SIAPI_CHANGE_LOADOUT")) return &change_loadout; + if(!strcmp(str, "SIAPI_SCOREBOARD")) return &scoreboard; + if(!strcmp(str, "SIAPI_SUICIDE")) return &suicide; + if(!strcmp(str, "SIAPI_MENU")) return &menu; + return NULL; // Should never happen +} + +EInputActionOrigin *origins = NULL; + +vector textkeys; + +vector get_siapi_textkeys(const char *str) +{ + digital_action_state *das = get_das_for_keymap_name(str); + + if(!origins) origins = new EInputActionOrigin[STEAM_INPUT_MAX_ORIGINS]; + + // We have to clear the buffer ourselves between calls + memset(origins, 0, STEAM_INPUT_MAX_ORIGINS * sizeof(EInputActionOrigin)); + cdpi::steam::input->GetDigitalActionOrigins( + lastusedcontroller, + hud::hasinput(true) ? MenuControls_handle : InGameControls_handle, + das->handle, + origins + ); + + for (int i = 0; i < STEAM_INPUT_MAX_ORIGINS; i++) + { + if(origins[i] == k_EInputActionOrigin_None + || origins[i] > k_EInputActionOrigin_MaximumPossibleValue) + { + // textkeyvec is always STEAM_INPUT_MAX_ORIGINS items long, + // but we adjust the reported length so that the draw code + // doesn't try to draw origins we don't actually have + textkeyvec.setsize(i); + break; + } + const char *siapi_origin_glyph = cdpi::steam::input->GetGlyphPNGForActionOrigin( + origins[i], + k_ESteamInputGlyphSize_Medium, + ESteamInputGlyphStyle_Dark + ); + + char origin_enum_string[13]; + snprintf(origin_enum_string, 13, "origin_%d", origins[0]); + + textkeyvec[i] = findtextkey_common(origin_enum_string, textkeys, siapi_origin_glyph); + } + + return textkeyvec; +} + +ICOMMAND(0, showsiapibindpanel, "", (), { cdpi::steam::input->ShowBindingPanel(lastusedcontroller); }); +#else /* defined(USE_STEAM) */ +void update_from_controller() +{ + return; +} + +bool is_siapi_textkey(const char *str) +{ + return false; +} + +vector get_siapi_textkeys(const char *str) +{ + return textkeyvec; +} + +ICOMMAND(0, showsiapibindpanel, "", (), { return; }); +#endif /* defined(USE_STEAM) */ +} diff --git a/src/game/controller.h b/src/game/controller.h new file mode 100644 index 000000000..49c7be110 --- /dev/null +++ b/src/game/controller.h @@ -0,0 +1,14 @@ +#ifndef CPP_CONTROLLER_HEADER +#define CPP_CONTROLLER_HEADER + +#include "rendertext.h" + +namespace controller +{ + extern bool lastinputwassiapi; + extern bool lastmovementwaskeyboard; + extern void update_from_controller(); + extern bool is_siapi_textkey(const char *str); + extern vector get_siapi_textkeys(const char *str); +} +#endif diff --git a/src/game/game.cpp b/src/game/game.cpp index 9e59bd977..cf15a5db1 100644 --- a/src/game/game.cpp +++ b/src/game/game.cpp @@ -6,7 +6,7 @@ namespace game int nextmode = G_EDITING, nextmuts = 0, gamestate = G_S_WAITING, gamemode = G_EDITING, mutators = 0, maptime = 0, mapstart = 0, timeremaining = 0, timeelapsed = 0, timelast = 0, timewait = 0, timesync = 0, lastcamera = 0, lasttvcam = 0, lasttvchg = 0, lastzoom = 0, lastcamcn = -1; - bool zooming = false, inputmouse = false, inputview = false, inputmode = false, wantsloadoutmenu = false, hasspotlights = false; + bool zooming = false, inputmouse = false, inputview = false, inputmode = false, wantsloadoutmenu = false, hasspotlights = false, lastinputwaskeyboard = true; float swayfade = 0, swayspeed = 0, swaydist = 0, bobfade = 0, bobdist = 0; vec swaydir(0, 0, 0), swaypush(0, 0, 0); @@ -3057,15 +3057,46 @@ namespace game else curfov = float(fov()); } - VAR(0, mouseoverride, 0, 0, 3); - bool mousemove(int dx, int dy, int x, int y, int w, int h) + float zoomsens() { + if (focus == player1 && inzoom() && zoomsensitivity > 0) + return (1.f-((zoomlevel+1)/float(zoomlevels+2)))*zoomsensitivity; + else + return 1.f; + } + + VAR(0, mouseoverride, 0, 0, 3); + // FIXME: We take x and y parameters but don't use them. A relic of an + // earlier implementation? Delete if possible. + bool mousemove(float dx, float dy, int x, int y, int w, int h, bool fromcontroller) + { + // When input comes from a controller, we deliberately *do not* respect + // the mouse sensitivity settings. The Steamworks page 'getting started' + // page ( + // https://partner.steamgames.com/doc/features/steam_controller/getting_started_for_devs + // ) explicitly states that: + + // > You should either rely on the configurator to provide sensitivity + // > (ie you don't filter incoming Steam Input data), or you should use + // > a dedicated sensitivity option for Steam Input that's distinct from + // > the system mouse. + + // We opt to take Option A - make gamepad aim sensitivity entirely the + // responsibility of Steam Input. This reduces the amount of additional + // support code needed in-engine and also has the added benefit that the + // 'Dots per 360' setting for flick stick and RWS gyro configuration is + // always a fixed value in every configuration. (This value is 3600, for + // the record). We choose to make the universal base sensitivity the + // value exactly the same as the default sensitivity for mouse, so that + // Steam Input 100% sensitivity matches the game's default setting. + #define mousesens(a,b,c) ((float(a)/float(b))*c) if(mouseoverride&2 || (!mouseoverride && hud::hasinput(true))) { - float mousemovex = mousesens(dx, w, mousesensitivity); - float mousemovey = mousesens(dy, h, mousesensitivity); + float scale = fromcontroller ? 1.0f : mousesensitivity; + float mousemovex = mousesens(dx, w, scale); + float mousemovey = mousesens(dy, h, scale); UI::mousetrack(mousemovex, mousemovey); @@ -3083,9 +3114,16 @@ namespace game physent *d = (!gs_playing(gamestate) || player1->state >= CS_SPECTATOR) && (focus == player1 || followaim()) ? camera1 : (allowmove(player1) ? player1 : NULL); if(d) { - float scale = (focus == player1 && inzoom() && zoomsensitivity > 0 ? (1.f-((zoomlevel+1)/float(zoomlevels+2)))*zoomsensitivity : 1.f)*sensitivity; - d->yaw += mousesens(dx, sensitivityscale, yawsensitivity*scale); - d->pitch -= mousesens(dy, sensitivityscale, pitchsensitivity*scale*(mouseinvert ? -1.f : 1.f)); + if (fromcontroller) + { + float scale = zoomsens()*10.f; + d->yaw += mousesens(dx, 100.f, scale); + d->pitch -= mousesens(dy, 100.f, scale); + } else { + float scale = zoomsens()*sensitivity; + d->yaw += mousesens(dx, sensitivityscale, yawsensitivity*scale); + d->pitch -= mousesens(dy, sensitivityscale, pitchsensitivity*scale*(mouseinvert ? -1.f : 1.f)); + } fixrange(d->yaw, d->pitch); } return true; @@ -3093,6 +3131,15 @@ namespace game return false; } + void resetplayerpitch() + { + if(!gs_waiting(gamestate) && (mouseoverride&1 || (!mouseoverride && !tvmode()))) + { + if((!gs_playing(gamestate) || player1->state >= CS_SPECTATOR && (focus == player1 || followaim()))) return; + if(allowmove(player1)) player1->pitch = 0.0f; + } + } + void getyawpitch(const vec &from, const vec &pos, float &yaw, float &pitch) { float dist = from.dist(pos); diff --git a/src/game/physics.cpp b/src/game/physics.cpp index 628f25871..7aa7077bc 100644 --- a/src/game/physics.cpp +++ b/src/game/physics.cpp @@ -1,4 +1,5 @@ #include "game.h" +#include "controller.h" namespace physics { FVAR(IDF_MAP, stairheight, 0, 4.1f, 1000); @@ -71,6 +72,7 @@ namespace physics #define imov(name,v,u,d,s,os) \ void do##name(bool down) \ { \ + controller::lastmovementwaskeyboard = true; \ game::player1->s = down; \ int dir = game::player1->s ? d : (game::player1->os ? -(d) : 0); \ game::player1->v = dir; \ diff --git a/src/shared/iengine.h b/src/shared/iengine.h index 8a03109da..0c43a2031 100644 --- a/src/shared/iengine.h +++ b/src/shared/iengine.h @@ -554,6 +554,8 @@ namespace UI extern bool uitest(const char *name, int stype = SURFACE_FOREGROUND, int param = -1); extern int hasinput(bool cursor = false, int stype = -1); extern bool hasmenu(bool pass = true, int stype = -1); + extern bool menuisgameplay(int stype = -1); + extern bool menuispie(int stype = -1); extern bool keypress(int code, bool isdown); extern bool textinput(const char *str, int len); diff --git a/src/shared/igame.h b/src/shared/igame.h index 813adc738..97ce979bd 100644 --- a/src/shared/igame.h +++ b/src/shared/igame.h @@ -182,7 +182,8 @@ namespace game extern void fxtrack(vec &pos, physent *owner, int mode, int tag = 0); extern void particletrack(particle *p, uint type, int &ts, bool step); extern void dynlighttrack(physent *owner, vec &o, vec &hud); - extern bool mousemove(int dx, int dy, int x, int y, int w, int h); + extern bool mousemove(float dx, float dy, int x, int y, int w, int h, bool fromcontroller); + extern void resetplayerpitch(void); extern void recomputecamera(); extern void adjustorientation(vec &pos); extern int gettimeremain();