diff --git a/ChangeLog b/ChangeLog index 1896fad6f0..dbf386cbad 100644 --- a/ChangeLog +++ b/ChangeLog @@ -4,6 +4,7 @@ BZFlag 2.4.27 ------------- +* Improved joystick support - Joshua Bodine * Cleaned up and updated the Xcode project file - Joshua Bodine * Fixed yet another SDL2 window management regression on macOS - Joshua Bodine * Remove non-SDL2 platform code and add SDL2 controller rumble support diff --git a/MSVC/build/bzflag.vcxproj b/MSVC/build/bzflag.vcxproj index d430a3cc2b..a26d646af6 100644 --- a/MSVC/build/bzflag.vcxproj +++ b/MSVC/build/bzflag.vcxproj @@ -335,6 +335,8 @@ copy "$(BZ_DEPS)\licenses\*" "..\..\bin_$(Configuration)_$(Platform)\licenses\"< + + @@ -939,12 +941,14 @@ copy "$(BZ_DEPS)\licenses\*" "..\..\bin_$(Configuration)_$(Platform)\licenses\"< + + diff --git a/MSVC/build/bzflag.vcxproj.filters b/MSVC/build/bzflag.vcxproj.filters index d9baef2184..a06801f43c 100644 --- a/MSVC/build/bzflag.vcxproj.filters +++ b/MSVC/build/bzflag.vcxproj.filters @@ -265,6 +265,12 @@ Header Files + + Source Files + + + Source Files + @@ -531,6 +537,12 @@ Header Files + + Header Files + + + Header Files + diff --git a/Xcode/BZFlag.xcodeproj/project.pbxproj b/Xcode/BZFlag.xcodeproj/project.pbxproj index 6cb7689536..9077b62707 100644 --- a/Xcode/BZFlag.xcodeproj/project.pbxproj +++ b/Xcode/BZFlag.xcodeproj/project.pbxproj @@ -410,6 +410,8 @@ 0394E966167B2D00007F4035 /* wwzones.dylib in CopyFiles */ = {isa = PBXBuildFile; fileRef = 0394E93D167B2A9E007F4035 /* wwzones.dylib */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; }; 0397B73B1BC1D639001C04D2 /* CustomZoneSample.bzw in Resources */ = {isa = PBXBuildFile; fileRef = C353B1E81AE2489900C5AED5 /* CustomZoneSample.bzw */; }; 0397B73C1BC1E72B001C04D2 /* flagStay.bzw in Resources */ = {isa = PBXBuildFile; fileRef = C353B1E91AE2490D00C5AED5 /* flagStay.bzw */; }; + 03AD0A172481A06E00207E3E /* JoystickTestMenu.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 03AD0A162481A06E00207E3E /* JoystickTestMenu.cxx */; }; + 03AD0A1A2481B2D600207E3E /* HUDuiJSTestLabel.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 03AD0A192481B2D600207E3E /* HUDuiJSTestLabel.cxx */; }; 03C5E82B1670A98C005A26C4 /* FontManager.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 035542FD166C846F008806E9 /* FontManager.cxx */; }; 03C5E82C1670A98C005A26C4 /* ImageFont.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 035542FE166C846F008806E9 /* ImageFont.cxx */; }; 03C5E82D1670A98C005A26C4 /* TextureFont.cxx in Sources */ = {isa = PBXBuildFile; fileRef = 03554302166C846F008806E9 /* TextureFont.cxx */; }; @@ -1775,6 +1777,10 @@ 0394E93D167B2A9E007F4035 /* wwzones.dylib */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; path = wwzones.dylib; sourceTree = BUILT_PRODUCTS_DIR; }; 039708A11DD7991D00C9215C /* SDLMain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLMain.h; sourceTree = ""; }; 039708A21DD7991D00C9215C /* SDLMain.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLMain.m; sourceTree = ""; }; + 03AD0A152481A06E00207E3E /* JoystickTestMenu.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = JoystickTestMenu.h; sourceTree = ""; }; + 03AD0A162481A06E00207E3E /* JoystickTestMenu.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = JoystickTestMenu.cxx; sourceTree = ""; }; + 03AD0A182481B2D500207E3E /* HUDuiJSTestLabel.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = HUDuiJSTestLabel.h; sourceTree = ""; }; + 03AD0A192481B2D600207E3E /* HUDuiJSTestLabel.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = HUDuiJSTestLabel.cxx; sourceTree = ""; }; 03C338C71AFB5AFC00E8F655 /* SDL2Display.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SDL2Display.cxx; sourceTree = ""; }; 03C338C81AFB5AFC00E8F655 /* SDL2Display.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDL2Display.h; sourceTree = ""; }; 03C338C91AFB5AFC00E8F655 /* SDL2Visual.cxx */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SDL2Visual.cxx; sourceTree = ""; }; @@ -2529,6 +2535,8 @@ 0355435A166C846F008806E9 /* HUDuiControl.h */, 0355435B166C846F008806E9 /* HUDuiDefaultKey.cxx */, 0355435C166C846F008806E9 /* HUDuiDefaultKey.h */, + 03AD0A192481B2D600207E3E /* HUDuiJSTestLabel.cxx */, + 03AD0A182481B2D500207E3E /* HUDuiJSTestLabel.h */, 0355435D166C846F008806E9 /* HUDuiLabel.cxx */, 0355435E166C846F008806E9 /* HUDuiLabel.h */, 0355435F166C846F008806E9 /* HUDuiList.cxx */, @@ -2541,6 +2549,8 @@ 03554366166C846F008806E9 /* InputMenu.h */, 03554367166C846F008806E9 /* JoinMenu.cxx */, 03554368166C846F008806E9 /* JoinMenu.h */, + 03AD0A162481A06E00207E3E /* JoystickTestMenu.cxx */, + 03AD0A152481A06E00207E3E /* JoystickTestMenu.h */, 03554369166C846F008806E9 /* KeyboardMapMenu.cxx */, 0355436A166C846F008806E9 /* KeyboardMapMenu.h */, 0355436B166C846F008806E9 /* LocalCommand.cxx */, @@ -5571,6 +5581,7 @@ 03C8EE6F167AC26A00BB07A5 /* callbacks.cxx in Sources */, 03C8EE71167AC26A00BB07A5 /* clientCommands.cxx in Sources */, 03C8EE72167AC26A00BB07A5 /* clientConfig.cxx in Sources */, + 03AD0A1A2481B2D600207E3E /* HUDuiJSTestLabel.cxx in Sources */, 03C8EE75167AC26A00BB07A5 /* CommandsImplementation.cxx in Sources */, 03C8EE76167AC26A00BB07A5 /* ComposeDefaultKey.cxx in Sources */, 03C8EE78167AC26A00BB07A5 /* ControlPanel.cxx in Sources */, @@ -5647,6 +5658,7 @@ 03C8EF01167AC26A00BB07A5 /* TrackMarks.cxx in Sources */, 03C8EF03167AC26A00BB07A5 /* Weapon.cxx in Sources */, 03C8EF05167AC26A00BB07A5 /* WeatherRenderer.cxx in Sources */, + 03AD0A172481A06E00207E3E /* JoystickTestMenu.cxx in Sources */, 03C8EF07167AC26A00BB07A5 /* World.cxx in Sources */, 03C8EF09167AC26A00BB07A5 /* WorldBuilder.cxx in Sources */, 03C8EF0B167AC26A00BB07A5 /* WorldPlayer.cxx in Sources */, diff --git a/include/BzfJoystick.h b/include/BzfJoystick.h index ab29e540cd..91c3ae869a 100644 --- a/include/BzfJoystick.h +++ b/include/BzfJoystick.h @@ -29,7 +29,7 @@ class BzfJoystick virtual void initJoystick(const char* joystickName); virtual bool joystick() const; - virtual void getJoy(int& x, int& y); + virtual void getJoy(float& x, float& y); virtual int getNumHats(); virtual void getJoyHat(int hat, float &hatX, float &hatY); virtual unsigned long getJoyButtons(); diff --git a/src/bzflag/HUDuiJSTestLabel.cxx b/src/bzflag/HUDuiJSTestLabel.cxx new file mode 100644 index 0000000000..b64a5dfb0e --- /dev/null +++ b/src/bzflag/HUDuiJSTestLabel.cxx @@ -0,0 +1,107 @@ +/* bzflag + * Copyright (c) 1993-2020 Tim Riker + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the license found in the file + * named COPYING that should have accompanied this file. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +// interface headers +#include "HUDuiJSTestLabel.h" + +// system headers +#include + +// common implementation headers +#include "TextureManager.h" +#include "OpenGLTexture.h" +#include "playing.h" + +// +// HUDuiJSTestLabel +// + +HUDuiJSTestLabel::HUDuiJSTestLabel() : HUDuiLabel(), width(0), height(0) +{ + OpenGLGStateBuilder builder(gstate); + builder.setBlending(); + builder.enableTexture(false); + gstate = builder.getState(); +} + +void HUDuiJSTestLabel::setSize(float newWidth, float newHeight) +{ + width = newWidth; + height = newHeight; +} + +void HUDuiJSTestLabel::doRender() +{ + gstate.setState(); + + // scale elements from a relative screen height of 800 pixels +#define BZ_SCALE_JS_TEST_ELEMS(x) std::ceil(float(x) * height / 800.0f) + + // appearance constants + const float backgroundColor[] = { 0.0f, 0.0f, 0.0f, 0.75f }; + + const auto realCursorThickness = BZ_SCALE_JS_TEST_ELEMS(2); + const auto realCursorLength = BZ_SCALE_JS_TEST_ELEMS(40.0f); + const float realCursorColor[] = { 1.0f, 1.0f, 1.0f, 1.0f }; + + const auto modifiedCursorLength = BZ_SCALE_JS_TEST_ELEMS(20.0f); + const float modifiedCursorColor[] = { 0.5f, 0.5f, 0.5f, 1.0f }; + + // draw the background + const auto rangeLimit = float(BZDB.evalInt("jsRangeMax")) / 100.0f; + glColor4fv(backgroundColor); + glBegin(GL_TRIANGLE_FAN); + glVertex2f(getX() + (1.0f - rangeLimit) / 2.0f * width, getY() + (1.0f + rangeLimit) / 2.0f * height); + glVertex2f(getX() + (1.0f - rangeLimit) / 2.0f * width, getY() + (1.0f - rangeLimit) / 2.0f * height); + glVertex2f(getX() + (1.0f + rangeLimit) / 2.0f * width, getY() + (1.0f - rangeLimit) / 2.0f * height); + glVertex2f(getX() + (1.0f + rangeLimit) / 2.0f * width, getY() + (1.0f + rangeLimit) / 2.0f * height); + glEnd(); + + // draw the real cursor + float jsx, jsy; + mainWindow->getJoyPosition(jsx, jsy); + jsx *= BZDB.evalInt("jsInvertAxes") % 2 == 1 ? -1.0f : 1.0f; // invert axes as required + jsy *= BZDB.evalInt("jsInvertAxes") > 1 ? -1.0f : 1.0f; + auto jsxTransformed = ((1.0f + jsx) / 2.0f) * width; + auto jsyTransformed = ((1.0f - jsy) / 2.0f) * height; + glPushAttrib(GL_LINE_BIT); + glLineWidth(realCursorThickness); + glColor4fv(realCursorColor); + glBegin(GL_LINES); + glVertex2f(getX() + jsxTransformed - realCursorLength / 2.0f, getY() + jsyTransformed); + glVertex2f(getX() + jsxTransformed + realCursorLength / 2.0f, getY() + jsyTransformed); + glVertex2f(getX() + jsxTransformed, getY() + jsyTransformed - realCursorLength / 2.0f); + glVertex2f(getX() + jsxTransformed, getY() + jsyTransformed + realCursorLength / 2.0f); + glEnd(); + glPopAttrib(); + + // draw the modified cursor + mainWindow->getJoyPosition(jsx, jsy); + applyJSModifiers(jsx, jsy); + jsxTransformed = ((1.0f + jsx) / 2.0f) * width; + jsyTransformed = ((1.0f - jsy) / 2.0f) * height; + glColor4fv(modifiedCursorColor); + glBegin(GL_TRIANGLE_FAN); + glVertex2f(getX() + jsxTransformed, getY() + jsyTransformed - modifiedCursorLength / 2.0f); + glVertex2f(getX() + jsxTransformed + modifiedCursorLength / 2.0f, getY() + jsyTransformed); + glVertex2f(getX() + jsxTransformed, getY() + jsyTransformed + modifiedCursorLength / 2.0f); + glVertex2f(getX() + jsxTransformed - modifiedCursorLength / 2.0f, getY() + jsyTransformed); + glEnd(); +} + +// Local Variables: *** +// mode: C++ *** +// tab-width: 4 *** +// c-basic-offset: 4 *** +// indent-tabs-mode: nil *** +// End: *** +// ex: shiftwidth=4 tabstop=4 diff --git a/src/bzflag/HUDuiJSTestLabel.h b/src/bzflag/HUDuiJSTestLabel.h new file mode 100644 index 0000000000..45fbb8ed22 --- /dev/null +++ b/src/bzflag/HUDuiJSTestLabel.h @@ -0,0 +1,48 @@ +/* bzflag + * Copyright (c) 1993-2020 Tim Riker + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the license found in the file + * named COPYING that should have accompanied this file. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +/* + * HUDuiJSTestLabel: + * User interface classes and functions for the joystick range test + */ + +#ifndef __HUDUIJSTESTLABEL_H__ +#define __HUDUIJSTESTLABEL_H__ + +#include "HUDuiLabel.h" +#include "OpenGLGState.h" + +class HUDuiJSTestLabel : public HUDuiLabel +{ +public: + HUDuiJSTestLabel(); + ~HUDuiJSTestLabel() { }; + + void setSize(float newWidth, float newHeight); + +protected: + void doRender(); + +private: + float width, height; + OpenGLGState gstate; +}; + +#endif // __HUDUIJSTESTLABEL_H__ + +// Local Variables: *** +// mode: C++ *** +// tab-width: 4 *** +// c-basic-offset: 4 *** +// indent-tabs-mode: nil *** +// End: *** +// ex: shiftwidth=4 tabstop=4 diff --git a/src/bzflag/InputMenu.cxx b/src/bzflag/InputMenu.cxx index 35d236def7..1e3afaa7ab 100644 --- a/src/bzflag/InputMenu.cxx +++ b/src/bzflag/InputMenu.cxx @@ -24,7 +24,7 @@ #include "playing.h" #include "HUDui.h" -InputMenu::InputMenu() : keyboardMapMenu(NULL) +InputMenu::InputMenu() : keyboardMapMenu(nullptr), joystickTestMenu(nullptr) { std::string currentJoystickDevice = BZDB.get("joystickname"); // cache font face ID @@ -43,12 +43,13 @@ InputMenu::InputMenu() : keyboardMapMenu(NULL) listHUD.push_back(keyMapping); HUDuiList* option; + std::vector* options; activeInput = new HUDuiList; activeInput->setFontFace(fontFace); activeInput->setLabel("Active input device:"); activeInput->setCallback(callback, "A"); - std::vector* options = &activeInput->getList(); + options = &activeInput->getList(); options->push_back("Auto"); options->push_back(LocalPlayer::getInputMethodName(LocalPlayer::Keyboard)); options->push_back(LocalPlayer::getInputMethodName(LocalPlayer::Mouse)); @@ -57,10 +58,9 @@ InputMenu::InputMenu() : keyboardMapMenu(NULL) listHUD.push_back(activeInput); option = new HUDuiList; - options = &option->getList(); // set joystick Device option->setFontFace(fontFace); - option->setLabel("Joystick device:"); + option->setLabel("Joystick Device:"); option->setCallback(callback, "J"); options = &option->getList(); options->push_back(std::string("Off")); @@ -72,7 +72,7 @@ InputMenu::InputMenu() : keyboardMapMenu(NULL) joystickDevices.erase(joystickDevices.begin(), joystickDevices.end()); for (i = 0; i < (int)options->size(); i++) { - if ((*options)[i].compare(currentJoystickDevice) == 0) + if ((*options)[i].compare(0, 1, currentJoystickDevice, 0, 1) == 0) { option->setIndex(i); break; @@ -81,18 +81,6 @@ InputMenu::InputMenu() : keyboardMapMenu(NULL) option->update(); listHUD.push_back(option); - option = new HUDuiList; - // force feedback - option->setFontFace(fontFace); - option->setLabel("Rumble:"); - option->setCallback(callback, "F"); - options = &option->getList(); - options->push_back(std::string("Off")); - options->push_back(std::string("On")); - option->setIndex(BZDB.isTrue("rumble") ? 1 : 0); - option->update(); - listHUD.push_back(option); - option = new HUDuiList; // axis settings jsx = option; @@ -107,18 +95,93 @@ InputMenu::InputMenu() : keyboardMapMenu(NULL) option->setCallback(callback, "Y"); listHUD.push_back(option); fillJSOptions(); + + option = new HUDuiList; + // joystick range settings + option->setFontFace(fontFace); + option->setLabel("Joystick Range Limit:"); + option->setCallback(callback, "T"); + options = &option->getList(); + char percentText[5]; + for(i = 25; i <= 100; ++i) + { + snprintf(percentText, 5, "%i%%", i); + options->push_back(percentText); + } + for (i = 0; i < (int)options->size(); i++) + { + std::string currentOption = (*options)[i]; + if (BZDB.get("jsRangeMax") + "%" == currentOption) + option->setIndex(i); + } + option->update(); + listHUD.push_back(option); + option = new HUDuiList; + option->setFontFace(fontFace); + option->setLabel("Joystick Dead Zone:"); + option->setCallback(callback, "B"); + options = &option->getList(); + for(i = 0; i <= 20; ++i) + { + snprintf(percentText, 5, "%i%%", i); + options->push_back(percentText); + } + for (i = 0; i < (int)options->size(); i++) + { + std::string currentOption = (*options)[i]; + if (BZDB.get("jsRangeMin") + "%" == currentOption) + option->setIndex(i); + } + option->update(); + listHUD.push_back(option); option = new HUDuiList; option->setFontFace(fontFace); - option->setLabel("Invert Joystick Axes:"); - option->setCallback(callback, "I"); + option->setLabel("Stretch Joystick Range Corners:"); + option->setCallback(callback, "S"); options = &option->getList(); options->push_back(std::string("No")); - options->push_back(std::string("X")); - options->push_back(std::string("Y")); - options->push_back(std::string("X and Y")); - option->setIndex(BZDB.evalInt("jsInvertAxes")); + options->push_back(std::string("Yes")); + option->setIndex(BZDB.isTrue("jsStretchCorners") ? 1 : 0); + option->update(); + listHUD.push_back(option); + option = new HUDuiList; + option->setFontFace(fontFace); + option->setLabel("Joystick Ramp Type:"); + option->setCallback(callback, "R"); + options = &option->getList(); + options->push_back(std::string("Linear")); + options->push_back(std::string("Exponential (Squared)")); + options->push_back(std::string("Exponential (Cubed)")); + if(BZDB.get("jsRampType") == "squared") + option->setIndex(1); + else if(BZDB.get("jsRampType") == "cubed") + option->setIndex(2); + else + option->setIndex(0); + option->update(); + listHUD.push_back(option); + + option = new HUDuiList; + // force feedback + option->setFontFace(fontFace); + option->setLabel("Force Feedback:"); + option->setCallback(callback, "F"); + options = &option->getList(); + options->push_back(std::string("None")); + options->push_back(std::string("Rumble")); + options->push_back(std::string("Directional")); + for (i = 0; i < (int)options->size(); i++) + { + std::string currentOption = (*options)[i]; + if (BZDB.get("forceFeedback") == currentOption) + option->setIndex(i); + } option->update(); listHUD.push_back(option); + joystickTest = new HUDuiLabel; + joystickTest->setFontFace(fontFace); + joystickTest->setLabel("Test Joystick Range"); + listHUD.push_back(joystickTest); option = new HUDuiList; // confine mouse @@ -165,6 +228,7 @@ InputMenu::InputMenu() : keyboardMapMenu(NULL) InputMenu::~InputMenu() { delete keyboardMapMenu; + delete joystickTestMenu; } void InputMenu::fillJSOptions() @@ -173,38 +237,63 @@ void InputMenu::fillJSOptions() std::vector* yoptions = &jsy->getList(); std::vector joystickAxes; getMainWindow()->getJoyDeviceAxes(joystickAxes); - if (joystickAxes.empty()) + const auto xAxisInverted = BZDB.evalInt("jsInvertAxes") % 2 == 1; + const auto yAxisInverted = BZDB.evalInt("jsInvertAxes") > 1; + + xoptions->clear(); + yoptions->clear(); + + if(joystickAxes.empty()) joystickAxes.push_back("N/A"); int i; for (i = 0; i < (int)joystickAxes.size(); i++) { xoptions->push_back(joystickAxes[i]); yoptions->push_back(joystickAxes[i]); + if(joystickAxes[i] != "N/A") + { + xoptions->push_back(joystickAxes[i] + " (Inverted)"); + yoptions->push_back(joystickAxes[i] + " (Inverted)"); + } } bool found = false; for (i = 0; i < (int)xoptions->size(); i++) { + bool currentOptionInverted = false; std::string currentOption = (*xoptions)[i]; - if (BZDB.get("jsXAxis") == currentOption) + + // could also be inverted + if(currentOption.length() >= 12 + && currentOption.substr(currentOption.length() - 10, std::string::npos) == "(Inverted)") + { + currentOption = currentOption.substr(0, currentOption.length() - 11); + currentOptionInverted = true; + } + + if (BZDB.get("jsXAxis") == currentOption && currentOptionInverted == xAxisInverted) { jsx->setIndex(i); found = true; } } if (!found) - { - // If there are at least two axes, use the first one for X - if (xoptions->size() > 2) - jsx->setIndex(1); - else - jsx->setIndex(0); - } + jsx->setIndex(0); jsx->update(); found = false; for (i = 0; i < (int)yoptions->size(); i++) { + bool currentOptionInverted = false; std::string currentOption = (*yoptions)[i]; - if (BZDB.get("jsYAxis") == currentOption) + + // could also be inverted + if(currentOption.length() >= 12 + && currentOption.substr(currentOption.length() - 10, std::string::npos) == "(Inverted)") + { + currentOption = currentOption.substr(0, currentOption.length() - 11); + currentOptionInverted = true; + } + + if (BZDB.get("jsYAxis") == currentOption && currentOptionInverted == yAxisInverted) { jsy->setIndex(i); found = true; @@ -212,7 +301,6 @@ void InputMenu::fillJSOptions() } if (!found) { - // If there are at least two axes, use the second one for Y if (yoptions->size() > 2) jsy->setIndex(2); else @@ -229,6 +317,11 @@ void InputMenu::execute() if (!keyboardMapMenu) keyboardMapMenu = new KeyboardMapMenu; HUDDialogStack::get()->push(keyboardMapMenu); } + else if (_focus == joystickTest) + { + if (!joystickTestMenu) joystickTestMenu = new JoystickTestMenu; + HUDDialogStack::get()->push(joystickTestMenu); + } } void InputMenu::callback(HUDuiControl* w, const void* data) @@ -251,15 +344,73 @@ void InputMenu::callback(HUDuiControl* w, const void* data) /* Joystick x-axis */ case 'X': - BZDB.set("jsXAxis", selectedOption); - getMainWindow()->setJoyXAxis(selectedOption); + { + auto selectedAxis = selectedOption; + auto xAxisInverted = false; + const auto oldInvertAxes = BZDB.evalInt("jsInvertAxes"); + + if(selectedOption.length() >= 12 + && selectedOption.substr(selectedOption.length() - 10, std::string::npos) == "(Inverted)") + { + selectedAxis = selectedAxis.substr(0, selectedAxis.length() - 11); + xAxisInverted = true; + } + + BZDB.set("jsXAxis", selectedAxis); + getMainWindow()->setJoyXAxis(selectedAxis); + + if(xAxisInverted) // X axis inversion needs to be enabled + { + if(oldInvertAxes % 2 != 1) // it wasn't already enabled + { + BZDB.setInt("jsInvertAxes", oldInvertAxes == 2 ? 3 : 1); // preserve the Y enabled value + } + } + else // X axis inversion needs to be disabled + { + if(oldInvertAxes % 2 == 1) // it was enabled previously + { + BZDB.setInt("jsInvertAxes", oldInvertAxes == 3 ? 2 : 0); // preserve the Y enabled value + } + } + break; + } /* Joystick y-axis */ case 'Y': - BZDB.set("jsYAxis", selectedOption); - getMainWindow()->setJoyYAxis(selectedOption); + { + auto selectedAxis = selectedOption; + auto yAxisInverted = false; + const auto oldInvertAxes = BZDB.evalInt("jsInvertAxes"); + + if(selectedOption.length() >= 12 + && selectedOption.substr(selectedOption.length() - 10, std::string::npos) == "(Inverted)") + { + selectedAxis = selectedAxis.substr(0, selectedAxis.length() - 11); + yAxisInverted = true; + } + + BZDB.set("jsYAxis", selectedAxis); + getMainWindow()->setJoyYAxis(selectedAxis); + + if(yAxisInverted) // Y axis inversion needs to be enabled + { + if(oldInvertAxes < 2) // it wasn't already enabled + { + BZDB.setInt("jsInvertAxes", oldInvertAxes == 1 ? 3 : 2); // preserve the X enabled value + } + } + else // Y axis inversion needs to be disabled + { + if(oldInvertAxes > 1) // it was enabled previously + { + BZDB.setInt("jsInvertAxes", oldInvertAxes == 3 ? 1 : 0); // preserve the X enabled value + } + } + break; + } /* Joystick axes inversion */ case 'I': @@ -318,9 +469,34 @@ void InputMenu::callback(HUDuiControl* w, const void* data) } break; - /* Force feedback */ + /* Force Feedback */ case 'F': - BZDB.setBool("rumble", listHUD->getIndex() == 1); + BZDB.set("forceFeedback", selectedOption); + break; + + /* Joystick Range Limit */ + case 'T': + BZDB.set("jsRangeMax", selectedOption.substr(0, selectedOption.length() - 1)); + break; + + /* Joystick Dead Zone */ + case 'B': + BZDB.set("jsRangeMin", selectedOption.substr(0, selectedOption.length() - 1)); + break; + + /* Stretch Joystick Range Corners */ + case 'S': + BZDB.setBool("jsStretchCorners", selectedOption == "Yes" ? true : false); + break; + + /* Joystick Ramp Type */ + case 'R': + if(selectedOption == "Exponential (Squared)") + BZDB.set("jsRampType", "squared"); + else if(selectedOption == "Exponential (Cubed)") + BZDB.set("jsRampType", "cubed"); + else + BZDB.set("jsRampType", "linear"); break; } @@ -355,8 +531,8 @@ void InputMenu::resize(int _width, int _height) { listHUD[i]->setFontSize(fontSize); listHUD[i]->setPosition(x, y); - // Add extra space after Change Key Mapping, Active input device, Invert Joystick Axes, and Mouse Box Size - if (i == 1 || i == 2 || i == 7 || i == 9) + // Add extra space after Change Key Mapping, Active input device, Test Joystick Range, and Mouse Box Size + if (i == 1 || i == 2 || i == 11 || i == 13) y -= 1.75f * h; else y -= 1.0f * h; @@ -375,7 +551,7 @@ void InputMenu::resize(int _width, int _height) SceneRenderer* renderer = getSceneRenderer(); if (renderer != nullptr) - ((HUDuiList*)listHUD[9])->setIndex(renderer->getMaxMotionFactor() + 11); + ((HUDuiList*)listHUD[13])->setIndex(renderer->getMaxMotionFactor() + 11); } diff --git a/src/bzflag/InputMenu.h b/src/bzflag/InputMenu.h index 37e9eb9fd9..1c3f61991a 100644 --- a/src/bzflag/InputMenu.h +++ b/src/bzflag/InputMenu.h @@ -21,6 +21,7 @@ /* local interface headers */ #include "MenuDefaultKey.h" #include "KeyboardMapMenu.h" +#include "JoystickTestMenu.h" #include "HUDuiControl.h" #include "HUDuiList.h" #include "HUDuiDefaultKey.h" @@ -45,11 +46,13 @@ class InputMenu : public HUDDialog void fillJSOptions(); private: - HUDuiControl* keyMapping; - HUDuiList* activeInput; - HUDuiList* jsx; - HUDuiList* jsy; - KeyboardMapMenu* keyboardMapMenu; + HUDuiControl* keyMapping; + HUDuiList* activeInput; + HUDuiList* jsx; + HUDuiList* jsy; + HUDuiControl* joystickTest; + KeyboardMapMenu* keyboardMapMenu; + JoystickTestMenu* joystickTestMenu; }; diff --git a/src/bzflag/JoystickTestMenu.cxx b/src/bzflag/JoystickTestMenu.cxx new file mode 100644 index 0000000000..d194e8c7a2 --- /dev/null +++ b/src/bzflag/JoystickTestMenu.cxx @@ -0,0 +1,87 @@ +/* bzflag + * Copyright (c) 1993-2020 Tim Riker + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the license found in the file + * named COPYING that should have accompanied this file. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +/* interface header */ +#include "JoystickTestMenu.h" + +/* common implementation headers */ +#include "StateDatabase.h" +#include "FontManager.h" + +/* local implementation headers */ +#include "MainMenu.h" +#include "HUDDialogStack.h" +#include "LocalPlayer.h" +#include "playing.h" +#include "HUDui.h" + +JoystickTestMenu::JoystickTestMenu() +{ + std::string currentJoystickDevice = BZDB.get("joystickname"); + // cache font face ID + int fontFace = MainMenu::getFontFace(); + // add controls + std::vector& listHUD = getControls(); + + titleLabel = new HUDuiLabel; + titleLabel->setFontFace(fontFace); + titleLabel->setString("Test Joystick Range"); + listHUD.push_back(titleLabel); + + // make a fake menu entry to hide offscreen, since something needs to have focus + fakeLabel = new HUDuiLabel; + fakeLabel->setFontFace(fontFace); + fakeLabel->setString("If you can see this, my worst nightmares have come true."); + listHUD.push_back(fakeLabel); + + fakeLabel->setPrev(fakeLabel); + fakeLabel->setNext(fakeLabel); + setFocus(fakeLabel); + + jsTestLabel = new HUDuiJSTestLabel; + listHUD.push_back(jsTestLabel); +} + +void JoystickTestMenu::resize(int _width, int _height) +{ + HUDDialog::resize(_width, _height); + + // use a big font for title + const auto titleFontSize = float(_height) / 15.0f; + const auto fontSize = float(_height) / 45.0f; + FontManager &fm = FontManager::instance(); + + // reposition title + titleLabel->setFontSize(titleFontSize); + const auto titleWidth = fm.getStrLength(MainMenu::getFontFace(), titleFontSize, titleLabel->getString()); + const auto titleHeight = fm.getStrHeight(MainMenu::getFontFace(), titleFontSize, " "); + titleLabel->setPosition(0.5f * ((float)_width - titleWidth), (float)_height - titleHeight); + + // reposition and resize joystick test area + const auto testAreaSize = float(_height) - titleHeight - 0.6f * titleHeight; + jsTestLabel->setPosition(0.5f * (float(_width) - testAreaSize), + fm.getStrHeight(MainMenu::getFontFace(), fontSize, " ")); + jsTestLabel->setSize(testAreaSize, testAreaSize); + + // reposition fake label off-screen + fakeLabel->setFontSize(fontSize); + fakeLabel->setPosition(0, _height * 2.0f); +} + + +// Local Variables: *** +// mode: C++ *** +// tab-width: 4 *** +// c-basic-offset: 4 *** +// indent-tabs-mode: nil *** +// End: *** +// ex: shiftwidth=4 tabstop=4 diff --git a/src/bzflag/JoystickTestMenu.h b/src/bzflag/JoystickTestMenu.h new file mode 100644 index 0000000000..63215a92c7 --- /dev/null +++ b/src/bzflag/JoystickTestMenu.h @@ -0,0 +1,59 @@ +/* bzflag + * Copyright (c) 1993-2020 Tim Riker + * + * This package is free software; you can redistribute it and/or + * modify it under the terms of the license found in the file + * named COPYING that should have accompanied this file. + * + * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. + */ + +#ifndef __JOYSTICKTESTMENU_H__ +#define __JOYSTICKTESTMENU_H__ + +#include "common.h" + +/* common interface headers */ +#include "HUDDialog.h" + +/* local interface headers */ +#include "MenuDefaultKey.h" +#include "HUDuiControl.h" +#include "HUDuiList.h" +#include "HUDuiDefaultKey.h" +#include "HUDuiJSTestLabel.h" + + +/** this class provides a visual test of the joystick range + */ +class JoystickTestMenu : public HUDDialog +{ +public: + JoystickTestMenu(); + ~JoystickTestMenu() { }; + + HUDuiDefaultKey* getDefaultKey() + { + return MenuDefaultKey::getInstance(); + } + void execute() { }; + void resize(int width, int height); + +private: + HUDuiLabel* titleLabel; + HUDuiLabel* fakeLabel; + HUDuiJSTestLabel* jsTestLabel; +}; + + +#endif /* __JOYSTICKTESTMENU_H__ */ + +// Local Variables: *** +// mode: C++ *** +// tab-width: 4 *** +// c-basic-offset: 4 *** +// indent-tabs-mode: nil *** +// End: *** +// ex: shiftwidth=4 tabstop=4 diff --git a/src/bzflag/MainWindow.cxx b/src/bzflag/MainWindow.cxx index e1ef84a4a2..35e84df78d 100644 --- a/src/bzflag/MainWindow.cxx +++ b/src/bzflag/MainWindow.cxx @@ -307,16 +307,9 @@ bool MainWindow::haveJoystick() const return joystick->joystick(); } -void MainWindow::getJoyPosition(int& mx, int& my) const -{ - // joystick axes inversion values - // 0: no inversion - // 1: invert X - // 2: invert Y - // 3: invert both - joystick->getJoy(mx, my); - mx = ((width >> 1) * mx * (BZDB.evalInt("jsInvertAxes") % 2 == 1 ? -1 : 1)) / (900); - my = ((height >> 1) * my * (BZDB.evalInt("jsInvertAxes") > 1 ? -1 : 1)) / (900); +void MainWindow::getJoyPosition(float& jsx, float& jsy) const +{ + joystick->getJoy(jsx, jsy); } int MainWindow::getNumHats() const diff --git a/src/bzflag/MainWindow.h b/src/bzflag/MainWindow.h index 40f0926d64..b91231d3d3 100644 --- a/src/bzflag/MainWindow.h +++ b/src/bzflag/MainWindow.h @@ -97,7 +97,7 @@ class MainWindow // events instead because it means no round trip to the server // for these values that we need every frame. void getMousePosition(int& mx, int& my) const; - void getJoyPosition(int& mx, int& my) const; + void getJoyPosition(float& jsx, float& jsy) const; int getNumHats() const; void getJoyHat(int hat, float &hatX, float &hatY) const; unsigned long getJoyButtonSet() const; diff --git a/src/bzflag/Makefile.am b/src/bzflag/Makefile.am index 6e14cacf40..da72a99f72 100644 --- a/src/bzflag/Makefile.am +++ b/src/bzflag/Makefile.am @@ -65,6 +65,8 @@ bzflag_SOURCES = \ HUDuiControl.h \ HUDuiDefaultKey.cxx \ HUDuiDefaultKey.h \ + HUDuiJSTestLabel.cxx \ + HUDuiJSTestLabel.h \ HUDuiLabel.cxx \ HUDuiLabel.h \ HUDuiList.cxx \ @@ -73,6 +75,8 @@ bzflag_SOURCES = \ HUDuiTextureLabel.h \ HUDuiTypeIn.cxx \ HUDuiTypeIn.h \ + JoystickTestMenu.cxx \ + JoystickTestMenu.h \ JoinMenu.cxx \ JoinMenu.h \ InputMenu.cxx \ diff --git a/src/bzflag/defaultBZDB.cxx b/src/bzflag/defaultBZDB.cxx index ef490f9050..5f7f35e55d 100644 --- a/src/bzflag/defaultBZDB.cxx +++ b/src/bzflag/defaultBZDB.cxx @@ -45,6 +45,11 @@ DefaultDBItem defaultDBItems[] = { "radarsize", "8", true, StateDatabase::ReadWrite, NULL }, { "mouseboxsize", "5", true, StateDatabase::ReadWrite, NULL }, { "mouseClamp", "0", true, StateDatabase::ReadWrite, NULL }, + { "jsInvertAxes", "0", true, StateDatabase::ReadWrite, NULL }, + { "jsRangeMax", "100", true, StateDatabase::ReadWrite, NULL }, + { "jsRangeMin", "5", true, StateDatabase::ReadWrite, NULL }, + { "jsStretchCorners", "1", true, StateDatabase::ReadWrite, NULL }, + { "jsRampType", "linear", true, StateDatabase::ReadWrite, NULL }, { "cpanelfontsize", "0", true, StateDatabase::ReadWrite, NULL }, { "controlPanelTimestamp", "0", true, StateDatabase::ReadWrite, NULL }, { "scorefontsize", "0", true, StateDatabase::ReadWrite, NULL }, diff --git a/src/bzflag/playing.cxx b/src/bzflag/playing.cxx index 18bd15dcc4..73896dfbb3 100644 --- a/src/bzflag/playing.cxx +++ b/src/bzflag/playing.cxx @@ -903,6 +903,67 @@ static void doKey(const BzfKeyEvent& key, bool pressed) doKeyPlaying(key, pressed, haveBinding); } + +void applyJSModifiers(float& jsx, float& jsy) +{ + // enforce range setting limits (min 0% to 20%, max 25% to 100%) so the maximum can't be less than the minimum + const auto jsRangeMax = std::max(std::min(float(BZDB.evalInt("jsRangeMax")) / 100.0f, 1.0f), 0.25f); + const auto jsRangeMin = std::max(std::min(float(BZDB.evalInt("jsRangeMin")) / 100.0f, 0.2f), 0.0f); + + // joystick axes inversion values + // 0: no inversion + // 1: invert X + // 2: invert Y + // 3: invert both + jsx *= BZDB.evalInt("jsInvertAxes") % 2 == 1 ? -1.0f : 1.0f; + jsy *= BZDB.evalInt("jsInvertAxes") > 1 ? -1.0f : 1.0f; + + // scaled radial dead zone and cap + const auto jsMagnitude = std::sqrt(jsx * jsx + jsy * jsy); + const auto jsRangeMultiplier = jsRangeMax * (jsMagnitude - jsRangeMin) / (jsRangeMax - jsRangeMin) / jsMagnitude; + + // exponential ramp + const auto jsRampType = BZDB.get("jsRampType"); + auto jsRampFactor = jsRangeMultiplier; + if(jsRampType == "squared") + jsRampFactor = std::pow(jsRangeMultiplier, 2.0f); + else if(jsRampType == "cubed") + jsRampFactor = std::pow(jsRangeMultiplier, 3.0f); + + // apply both factors + if(jsMagnitude < jsRangeMin || isnan(jsRangeMultiplier)) + jsx = jsy = 0.0f; + else + { + jsx *= jsRampFactor; + jsy *= jsRampFactor; + } + + // stretch corners + if(BZDB.isTrue("jsStretchCorners")) + { + const auto stretchFactor = (1.0f - float(std::abs(std::abs(atan(jsy / jsx) / M_PI) - 0.25f)) * 4.0f) * jsMagnitude; + const auto stretchValue = std::sqrt(2.0f); + + if(! isnan(stretchFactor)) + { + jsx *= (1.0f - stretchFactor) + stretchValue * stretchFactor; // mix based on stretchFactor from 0.0 to 1.0 + jsy *= (1.0f - stretchFactor) + stretchValue * stretchFactor; + } + } + + // proportionally scale the coordinates back to the range limit + const auto jsxAbs = std::abs(jsx); + const auto jsyAbs = std::abs(jsy); + if(jsxAbs > jsRangeMax || jsyAbs > jsRangeMax) + { + const auto jsRangeExcess = (jsxAbs > jsyAbs ? jsxAbs : jsyAbs) / jsRangeMax; + jsx /= jsRangeExcess; + jsy /= jsRangeExcess; + } +} + + static void doMotion() { float rotation = 0.0f, speed = 1.0f; @@ -926,12 +987,13 @@ static void doMotion() // determine if joystick motion should be used instead of mouse motion // when the player bumps the mouse, LocalPlayer::getInputMethod return Mouse; // make it return Joystick when the user bumps the joystick + auto jsx = 0.0f, jsy = 0.0f; if (mainWindow->haveJoystick()) { if (myTank->getInputMethod() == LocalPlayer::Joystick) { // if we're using the joystick right now, replace mouse coords with joystick coords - mainWindow->getJoyPosition(mx, my); + mainWindow->getJoyPosition(jsx, jsy); } else { @@ -939,11 +1001,10 @@ static void doMotion() // see if it's moved and autoswitch if (BZDB.isTrue("allowInputChange")) { - int jx = 0, jy = 0; - mainWindow->getJoyPosition(jx, jy); + mainWindow->getJoyPosition(jsx, jsy); + // if we aren't using the joystick, but it's moving, start using it - if ((jx < -noMotionSize * 2) || (jx > noMotionSize * 2) - || (jy < -noMotionSize * 2) || (jy > noMotionSize * 2)) + if(std::sqrt(jsx * jsx + jsy * jsy) > std::min(std::max(float(BZDB.evalInt("jsRangeMin")) / 100.0f, 0.0f), 0.2f)) myTank->setInputMethod(LocalPlayer::Joystick); // joystick motion } // allowInputChange } // getInputMethod == Joystick @@ -977,9 +1038,48 @@ static void doMotion() speed *= 0.5f; } } - else // both mouse and joystick + else if (myTank->getInputMethod() == LocalPlayer::Joystick) { + applyJSModifiers(jsx, jsy); + // calculate desired rotation + if (keyboardRotation && !devDriving) + { + rotation = float(keyboardRotation); + rotation *= BZDB.eval("displayFOV") / 60.0f; + if (BZDB.isTrue("slowKeyboard")) + rotation *= 0.5f; + } + else + { + rotation = -jsx; + + if (rotation > 1.0f) + rotation = 1.0f; + if (rotation < -1.0f) + rotation = -1.0f; + } + + // calculate desired speed + if (keyboardSpeed && !devDriving) + { + speed = float(keyboardSpeed); + if (speed < 0.0f) + speed *= 0.5f; + if (BZDB.isTrue("slowKeyboard")) + speed *= 0.5f; + } + else + { + speed = -jsy; + if (speed > 1.0f) + speed = 1.0f; + if (speed < -0.5f) + speed = -0.5f; + } + } + else // mouse + { // calculate desired rotation if (keyboardRotation && !devDriving) { diff --git a/src/bzflag/playing.h b/src/bzflag/playing.h index e3a8ab669e..aba6c38d64 100644 --- a/src/bzflag/playing.h +++ b/src/bzflag/playing.h @@ -102,6 +102,8 @@ void killGlobalAres(); extern void joinGame(); +extern void applyJSModifiers(float& jsx, float& jsy); + extern HUDRenderer *hud; extern char messageMessage[PlayerIdPLen + MessageLen]; extern ServerLink* serverLink; diff --git a/src/platform/BzfJoystick.cxx b/src/platform/BzfJoystick.cxx index 9713369666..cc8038e7e7 100644 --- a/src/platform/BzfJoystick.cxx +++ b/src/platform/BzfJoystick.cxx @@ -59,9 +59,9 @@ bool BzfJoystick::joystick() const return false; } -void BzfJoystick::getJoy(int& x, int& y) +void BzfJoystick::getJoy(float& x, float& y) { - x = y = 0; + x = y = 0.0f; } void BzfJoystick::getJoyDevices(std::vector diff --git a/src/platform/SDLJoystick.cxx b/src/platform/SDLJoystick.cxx index 253c1090a8..3e100502ae 100644 --- a/src/platform/SDLJoystick.cxx +++ b/src/platform/SDLJoystick.cxx @@ -109,24 +109,16 @@ bool SDLJoystick::joystick() const return joystickID != nullptr; } -void SDLJoystick::getJoy(int& x, int& y) +void SDLJoystick::getJoy(float& x, float& y) { - x = y = 0; + x = y = 0.0f; if (!joystickID) return; SDL_JoystickUpdate(); - x = SDL_JoystickGetAxis(joystickID, xAxis); - y = SDL_JoystickGetAxis(joystickID, yAxis); - - x = x * 1000 / 32768; - y = y * 1000 / 32768; - - // ballistic - x = (x * abs(x)) / 1000; - y = (y * abs(y)) / 1000; - + x = float(SDL_JoystickGetAxis(joystickID, xAxis)) / 32768.0f; + y = float(SDL_JoystickGetAxis(joystickID, yAxis)) / 32768.0f; } unsigned long SDLJoystick::getJoyButtons() diff --git a/src/platform/SDLJoystick.h b/src/platform/SDLJoystick.h index afc7f9ccee..dd84dfc8c6 100644 --- a/src/platform/SDLJoystick.h +++ b/src/platform/SDLJoystick.h @@ -28,7 +28,7 @@ class SDLJoystick : public BzfJoystick void initJoystick(const char* joystickName); bool joystick() const; - void getJoy(int& x, int& y); + void getJoy(float& x, float& y); unsigned long getJoyButtons(); int getNumHats(); void getJoyHat(int hat, float &hatX, float &hatY);