mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
Controls 4 and 5 - Multi-joy and Preset Files (#2958)
* Updates the AxisInverted hooks to use the new CC_bind. Currently assumes only Joy0, so this may need further tweaking or replacement * * Add ability to claim up to 4 joysticks for player use, using the launcher (via registry or fs2_open.ini) or the new menu system * Hack into existing control polling system to get the new primary/secondary binding schema to work. This should be reworked into a reverse-lookup system. * Finally break the mouse loose from overlapping with Joystick binidngs * Fix bug where joy button wouldn't register on search function * Fix bug with mousewheel not always decaying as it should. * Fix rebase conflict with selecting item during search mode * Fix bug where mouse buttons were incorrectly labeled. * Move some enums around * Fix bug with joy axis labels being displayed as buttons. * Fix possible bug where Other conflict axis wouldn't clear * Updated default joystick axis text. A new strings.tbl will need to be supplied for localization support * Deprecate nAxisInverted ADE functions and replace with AxisInverted(int, int, bool) * Merge Axis mappings into Control_config * More utility functions for CC_bind and CCB * Adds another ADE function for inverting the axis action, instead of individual bindings * Hacks to get plr file to compile. Currently slices out many of the new features like secondary axis and the mouse axis, and the additional joystick bindings (joy1, joy2, joy3) * Fix bug with uncontrollable spin (slopily) * Fix some warnings * Add flags for Hats and Balls for future implementation * Fix bug where inverting an axis would trigger an assert * Fix bug where joy wasn't detected as present * Most axis actions should be relative, instead of absolute * Hopefully optimize joy_get_unscaled_reading * Properly break out of double-for search function for search and bind modes in config menu * Refactor control_get_axes_readings to provide more consistent behavior * Use JOY_REL_THROTTLE instead of Action::REL_THROTTLE when indexing into Control_config * Bank axis has a default binding, Throttle does not. * Fix bug where conflicts weren't being detected. Fix bug where default presets weren't being detectd * Elegantly break out of double fors with goto. Fix bug with bind mode not detecting axis * Save new binding scheme to playerfile * Add cid_assign() to standardize cid assignment with shorts * Add write and read handlers for int8_t * Remove saving controls to csg. This will be replaced by presets. * Fix bug with reading pilotfile * Use "Joy-n" instead of "joystick n" to match default identifiers in config menu * Fix bug with CC_TYPE_TRIGGER controls always marked as used, if not bound with a key. Fix bug with Joy-0 btn 1 triggering multiple controls that are not bound to it. * Improve readibility of the joy info output to log * Message when a joystick connects and using legacy system. * Fix bug where system joystick 0 was mapped in pJoystick that should've been nullptr * Fix bug where *not* having a joystick will cause bind mode in the config menu to immediately cancel. * Add reverse lookup functions for actions. mActionToVal[] does string -> int, mValToAction() does int -> string * Add the foundation for writing the presets files. * Adds a bunch of reverse lookup functions to get the stringified version of various enums, etc. * presets.h and presets.cpp are in something of a barebones at this point * Add new subdir for preset files 'player/presets' * add utility for finding the distance of a bitfield/bitmask. Used to translate mouse button masks into mouse button id's * Write PresetFileHandler as standalone, instead of a subclass. Still mostly a copy of JSONFileHandler * Make functions to map and reverse-map the bindings for use with the preset files and ultimately the ControlConfigDefaults.tbl as well * Check presets from file to see if they are duplicate with an existing preset. * Check for uniqueness of presets fron controlconfigdefaults.tbl before adding them, and warn the user if there are duplicates. * Rename the test functions into proper load/save. * save function has some standard checks * load function is called on init * make finding duplicates its own function outside of the preset label rendering function * Initial draft of saving presets in-game * Fix bug where all loaded presets were thought to be duplicate of defaults. * Fix bug where preset cycling would get stuck on the first preset other than default * Fix bug where saving a mouse button saves the wrong textified string. * Prevent preset names from being too long. * Crash still occurs if the name is too long, as if set outside of FSO * Workaround text running off the popups in 640x480. Doesn't look as nice, sadly * CSG's now track preset names instead of containing bindings * remake control_config_reset_defaults() into control_config_use_preset() * provide a function to get the currently selected preset. * Do PLR backward compatibility, hopefully. * PLR now tracks presets instead of saving the bindings. * On Loading pre PLR3 or CSG6 data, create a new preset. * Strip off file extension before saving preset files. Jason.json.json just looks dumb * Add "-joy_info" commandline option. Will output SDL joystick info of all detected joysticks to stdout * Fix non-standard behavior with registry_read_string(). * Returning NULL instead of the default value, or an empty string, will trigger an access violation with SCP_string and other string copy functions. * Modify io::joystick::init() to take an optional param, no_register, to use with -joy_info * Address CI complaints * Address CI issues 2. * Supress preset duplicate warning if in unit tests. * Address scripting unittest failure due to incorrect argument type. (Ints don't technically exist in LUA) * Use Mouse enums instead of Joy enums or magic numbers * More signed/unsigned madness * Address CI issues 3 * Address gcc-5 issues. (No auto in lambdas) * Fix bug with bit_distance() * Address gcc-5 issues 2 * Address clang issues * Address clang-tidy issues * Address clang-tidy issues in undosys. May need to be cherry picked to Controls2. * Address clang-tidy issues * Improve logic of preset name prompt. * Fix bug with selected presets not being saved * No, a cstr is not a std::string. * Can't use i there because its already defined. * Fix bug where CSG's wouldn't save selected preset * Fix bug where presets wouldn't be loaded correctly from player files * Fix bug where loaded preset files would not update the preset cycle position correctly * Some QoL changes for the preset name prompt * Address Appveyor issues. * Add forward compatibility for player file. This is required because currently any "missing" data in the pilotfile will trigger an Error, and crash FSO. * Fix bug where mouse X axis was unusable. Apparently stating that MOUSE_Z_AXIS IS MOUSE_Z_AXIS doesn't get the point across. * Address CI issues. * Address CI issues. * sel default assigned to -1 * static_cast enum to an int * Assimilate -joy_info int -get_flags, replacing the existing method that launchers use to get joystick information. * Add '\n' to PST log messages. * Change logic of checking joystick .ini keys: Should the multi-joy key not be found, try legacy joy key. * Add some more missing '\n' to mprintf messages. * Notify the player when converting to the new multi-joy system. (Using ReleaseWarning for now...) * Warn the player if a preset defined in their .PLR could not be found. * Actually disable control items without a valid tab, instead of relying on the fact that they're disabled by default. * Add CC_preset::is_duplicate_of() method to make checking for preset uniqueness less of a brain teaser. * Doxy the first_override param of control_config_common_read_section * Adds the os::dialogs::Information() method, which displays an information-only dialog in similar format to Warning and Error dialogs. Does not throw Warning nor Error exceptions * load_player now uses os::dialogs::Information() instead of ::ReleaseWarning, thus not halting unit tests. * Breaks retail localization support for Joystick and Mice labels - This is because the new multi-joy axis end up very confusing "Joy-0 Joystick/Mouse X Axis" * Renames Joy_axis_text to Axis_text, intended to be used both by joysticks and the mouse. * Moves mouse axis and button labels into an array to ease future XSTR support * Adds asserts to ensure indices are within the array sizes of label arrays * Adds XSTR indices for Axis labels, mouse buttons, and mouse wheels. * Seperates conflict check comparisons from equality comparisons. * Conflict checks can ignore certain combinations of CC_bind::flag's; i.e. an Axis bound to a different control but is inverted or relative will still cause a conflict with another control that has the same axis * Equality comparisons is exact equality, allowing presets to check if binds have the same binding and flag configurations. * More UI string changes and localization * Tweaks positioning of top button labels in CC menu * Adds the following XSTR's: 1659 "Preset: %s" 1660 "Preset: custom" 1661 "Next Preset" * Fix bug where mouse axis turned into mouse button when inverting control by its control name. * Fix bug with the new conflict checking subroutine not working with non-axis controls * Address CI issues. * Don't trigger modal popups while in unit tests. * Address clang tidy issues * use nullptr * be consistent with conditional style * use booleans to stuff debug states, methods with just Assertions in it will trigger a false positive on clang tidy * use string::empty() instead of checking for "". * Remove dead code of -joy_info. Functionality has been merged with -get_flags * Address clang-tidy [readability-simplify-boolean-expr] --z64 says "I don't like it!" * fix axis check * Implement revised axis action virtvars for scripting. Also protect against a control mismatch in the CSG file due to a script-inverted control * Encapsulate CC_bind properly * Add mouse and joystick cid's to AxisInverted description * Update translate_key() and its user to correctly translate multijoy bindings for training messages. * Prompt user trying to escape from config menu when they have changes. * Remove dead function. * Fix crash during training missions due to key translation that would overflow internal buffer. * Went back with cstring as return value instead of SCP string, because caller would need a significant refactor * Update doxy of the textify functions * Unify textify_scancode() and textify_scancode_universal() to ensure common code path. * Add "Multijoy" entry to json caps. (For Knossos) * Make CI happy. * Remove unused lambda captures. * Instead of overwrite during plr2 => plr3 conversion, try to use the existing preset file. * Fix conditional for checking if file exists within presets::save_preset_file() * Revert Majority of PR#3739 * PLR and CSG versions are incremented past versions set by 3739 * Forward compatibility feature of #3739 (Only save sexp containers into the CSG if they are used) is removed * Remove forward compatibilty of PLR file when going in between pre- and post-Controls5 FSO versions * Add missing startSection() and endSection() for csg_write_controls() * Fix clang-tidy error #1 * Fix clang-tidy error #2 * fix clandy tidy error #1 part 2/2 * Fix clang tidy error 3 Co-authored-by: Goober5000 <[email protected]> Co-authored-by: Tyler Blount <[email protected]> Co-authored-by: wookieejedi <[email protected]> Co-authored-by: BMagnu <[email protected]>
This commit is contained in:
35 files changed
+3296
-1156
No files matched your search
@@ -91,6 +91,7 @@ cf_pathtype Pathtypes[CF_MAX_PATH_TYPES] = {
|
||||
{ CF_TYPE_FREDDOCS, "data" DIR_SEPARATOR_STR "freddocs", ".html", CF_TYPE_DATA },
|
||||
{ CF_TYPE_INTERFACE_MARKUP, "data" DIR_SEPARATOR_STR "interface" DIR_SEPARATOR_STR "markup", ".rml", CF_TYPE_INTERFACE },
|
||||
{ CF_TYPE_INTERFACE_CSS, "data" DIR_SEPARATOR_STR "interface" DIR_SEPARATOR_STR "css", ".rcss", CF_TYPE_INTERFACE },
|
||||
{ CF_TYPE_PLAYER_BINDS, "data" DIR_SEPARATOR_STR "players" DIR_SEPARATOR_STR "presets", ".json", CF_TYPE_PLAYERS },
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
|
||||
+2
-1
@@ -81,9 +81,10 @@ typedef struct {
|
||||
#define CF_TYPE_FREDDOCS 37
|
||||
#define CF_TYPE_INTERFACE_MARKUP 38
|
||||
#define CF_TYPE_INTERFACE_CSS 39
|
||||
#define CF_TYPE_PLAYER_BINDS 40
|
||||
|
||||
#define CF_MAX_PATH_TYPES \
|
||||
40 // Can be as high as you'd like //DTP; yeah but beware alot of things uses CF_MAX_PATH_TYPES
|
||||
41 // Can be as high as you'd like //DTP; yeah but beware alot of things uses CF_MAX_PATH_TYPES
|
||||
|
||||
// TRUE if type is specified and valid
|
||||
#define CF_TYPE_SPECIFIED(path_type) (((path_type)>CF_TYPE_INVALID) && ((path_type)<CF_MAX_PATH_TYPES))
|
||||
|
||||
@@ -1319,6 +1319,7 @@ static json_t* json_get_v1() {
|
||||
json_array_append_new(caps_array, json_string("No D3D"));
|
||||
json_array_append_new(caps_array, json_string("New Sound"));
|
||||
json_array_append_new(caps_array, json_string("SDL"));
|
||||
json_array_append_new(caps_array, json_string("Multijoy"));
|
||||
|
||||
json_object_set_new(root, "caps", caps_array);
|
||||
}
|
||||
@@ -1410,24 +1411,7 @@ static json_t* json_get_v1() {
|
||||
json_object_set_new(root, "openal", openal_obj);
|
||||
}
|
||||
{
|
||||
auto joystick_array = json_array();
|
||||
|
||||
auto joysticks = io::joystick::getJoystickInformations();
|
||||
for (auto& info : joysticks) {
|
||||
auto joystick_obj = json_object();
|
||||
|
||||
json_object_set_new(joystick_obj, "name", json_string(info.name.c_str()));
|
||||
json_object_set_new(joystick_obj, "guid", json_string(info.guid.c_str()));
|
||||
|
||||
json_object_set_new(joystick_obj, "num_axes", json_integer(info.num_axes));
|
||||
json_object_set_new(joystick_obj, "num_balls", json_integer(info.num_balls));
|
||||
json_object_set_new(joystick_obj, "num_buttons", json_integer(info.num_buttons));
|
||||
json_object_set_new(joystick_obj, "num_hats", json_integer(info.num_hats));
|
||||
|
||||
json_object_set_new(joystick_obj, "is_haptic", json_boolean(info.is_haptic));
|
||||
|
||||
json_array_append_new(joystick_array, joystick_obj);
|
||||
}
|
||||
auto joystick_array = io::joystick::getJsonArray();
|
||||
|
||||
json_object_set_new(root, "joysticks", joystick_array);
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large.
Load diff
+385
-102
@@ -15,68 +15,62 @@
|
||||
|
||||
#define CONTROL_CONFIG_XSTR 507
|
||||
|
||||
#define CCF_AXIS_BTN 0x40 //!< axis is a button on positive side. If also inverted, is on negative side instead
|
||||
#define CCF_RELATIVE 0x20 //!< axis is relative
|
||||
#define CCF_INVERTED 0x10 //!< axis is inverted
|
||||
#define CCF_AXIS 0x08 //!< btn is an axis
|
||||
#define CCF_HAT 0x04 //!< btn is a hat
|
||||
#define CCF_BALL 0x02 //!< btn is a ball
|
||||
|
||||
/*!
|
||||
* These are used to index a corresponding joystick axis value from an array.
|
||||
* These are used to index a corresponding axis value from an array.
|
||||
* Currently only used by ::Axis_map_to[] and ::Axis_map_to_defaults[]
|
||||
*/
|
||||
enum Joy_axis_index {
|
||||
enum Joy_axis_index : short {
|
||||
JOY_X_AXIS =0,
|
||||
JOY_Y_AXIS,
|
||||
JOY_Z_AXIS,
|
||||
JOY_RX_AXIS,
|
||||
JOY_RY_AXIS,
|
||||
JOY_RZ_AXIS
|
||||
JOY_RZ_AXIS,
|
||||
|
||||
JOY_NUM_AXES // Number of axes a joystick may have. Must be last enum in Joy_axis_index.
|
||||
};
|
||||
|
||||
// Aliases for mouse axes. Really should unify this...
|
||||
const short MOUSE_X_AXIS = JOY_X_AXIS;
|
||||
const short MOUSE_Y_AXIS = JOY_Y_AXIS;
|
||||
const short MOUSE_Z_AXIS = JOY_Z_AXIS;
|
||||
const short MOUSE_NUM_AXES = 3;
|
||||
const short MOUSE_NUM_BUTTONS = 9; // Keep this up to date with mouse.h until z64555 stops being lazy and does it right
|
||||
|
||||
/*!
|
||||
* Controller index enumeration
|
||||
* @details For use with hardcoded bindings and scripting API to allow human-readable translation
|
||||
* @note These enums are hardcoded so that an int value of 0 would be JOY0
|
||||
*/
|
||||
enum CID : short {
|
||||
CID_NONE = -3, // No device bound
|
||||
CID_KEYBOARD = -2, // button belongs to keyboard
|
||||
CID_MOUSE = -1, // to mouse
|
||||
CID_JOY0 = 0, // to Joy0
|
||||
CID_JOY1 = 1, // to Joy1 (Throttle?)
|
||||
CID_JOY2 = 2, // to Joy2 (Pedals?)
|
||||
CID_JOY3 = 3 // to Joy3 (Head tracker?)
|
||||
// Any additional joystick is an int >3
|
||||
};
|
||||
|
||||
/*!
|
||||
* These are used to index a corresponding (analog) action, namely controlling the orientation angles and throttle.
|
||||
*/
|
||||
enum Joy_axis_action_index {
|
||||
JOY_HEADING_AXIS =0, //
|
||||
JOY_PITCH_AXIS,
|
||||
JOY_BANK_AXIS,
|
||||
JOY_ABS_THROTTLE_AXIS,
|
||||
JOY_REL_THROTTLE_AXIS,
|
||||
|
||||
/*!
|
||||
* This must always be below the last defined item
|
||||
*/
|
||||
NUM_JOY_AXIS_ACTIONS //!< The total number of actions an axis may map to
|
||||
};
|
||||
|
||||
/*!
|
||||
* Joystick action modes
|
||||
* note: should this be in joy.h?
|
||||
*/
|
||||
enum Joy_axis_action_mode {
|
||||
JAAM_ABS, //!< Absolute mode. Axis position = output value
|
||||
JAAM_REL, //!< Relative mode. Axis postion away from its center adds or subtracts an output value register.
|
||||
JAAM_BTN_NEG, //!< Button mode, negative side. Axis position in the negative side will trigger a button action
|
||||
JAAM_BTN_POS, //!< Button mode, positive side. Axis position in the positive side will trigger a button action
|
||||
enum CID : int8_t {
|
||||
CID_NONE = -3, //!< No device bound
|
||||
CID_KEYBOARD = -2, //!< belongs to keyboard
|
||||
CID_MOUSE = -1, //!< to mouse
|
||||
CID_JOY0 = 0, //!< to Joy0
|
||||
CID_JOY1 = 1, //!< to Joy1 (Throttle?)
|
||||
CID_JOY2 = 2, //!< to Joy2 (Pedals?)
|
||||
CID_JOY3 = 3, //!< to Joy3 (Head tracker?)
|
||||
|
||||
CID_JOY_MAX //!< Maximum supported joysticks, must be last in the enum definition
|
||||
};
|
||||
|
||||
/*!
|
||||
* Control Configuration Types. Namely differ in how the control is activated
|
||||
*/
|
||||
enum CC_type {
|
||||
CC_TYPE_TRIGGER =0, //!< A normal, one-shot type control that is activated when a key is or button is pressed
|
||||
CC_TYPE_CONTINUOUS //!< A continous control that is activated as long as the key or button is held down
|
||||
CC_TYPE_TRIGGER = 0, //!< A normal, one-shot type control that is activated when a key is or button is pressed
|
||||
CC_TYPE_CONTINUOUS, //!< A continous control that is activated as long as the key or button is held down
|
||||
CC_TYPE_AXIS_ABS, //!< Absolute Axis mode. Axis position = output value
|
||||
CC_TYPE_AXIS_REL, //!< Relative Axis mode. Axis postion away from its center adds or subtracts an output value register
|
||||
CC_TYPE_AXIS_BTN_NEG, //!< Axis Button mode, negative side. Axis position in the negative side will trigger a button action
|
||||
CC_TYPE_AXIS_BTN_POS //!< Axis Button mode, positive side. Axis position in the positive side will trigger a button action
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -86,7 +80,7 @@ enum CC_type {
|
||||
*
|
||||
* Note: Do not adjust the order or numeric value
|
||||
*/
|
||||
enum IoActionId {
|
||||
enum IoActionId : int {
|
||||
TARGET_NEXT =0, //!< target next
|
||||
TARGET_PREV =1, //!< target previous
|
||||
TARGET_NEXT_CLOSEST_HOSTILE =2, //!< target the next hostile target
|
||||
@@ -288,52 +282,77 @@ enum IoActionId {
|
||||
CUSTOM_CONTROL_4 = 122,
|
||||
CUSTOM_CONTROL_5 = 123,
|
||||
|
||||
//!< @n
|
||||
//!< Analog controls
|
||||
//!<-----------------------------
|
||||
//!< All axis actions must be kept together
|
||||
JOY_HEADING_AXIS = 124,
|
||||
JOY_PITCH_AXIS = 125,
|
||||
JOY_BANK_AXIS = 126,
|
||||
JOY_ABS_THROTTLE_AXIS = 127,
|
||||
JOY_REL_THROTTLE_AXIS = 128,
|
||||
|
||||
/*!
|
||||
* This must always be below the last defined item
|
||||
*/
|
||||
CCFG_MAX //!< The total number of defined control actions (or last define + 1)
|
||||
};
|
||||
|
||||
// Constants for array-based checking (within Control_config) for the axis actions.
|
||||
// z64: Yes, this is hackish. No I don't really like it.
|
||||
const int JOY_AXIS_BEGIN = JOY_HEADING_AXIS;
|
||||
const int JOY_AXIS_END = JOY_REL_THROTTLE_AXIS + 1;
|
||||
const int NUM_JOY_AXIS_ACTIONS = JOY_AXIS_END - JOY_AXIS_BEGIN;
|
||||
|
||||
/*!
|
||||
* @brief Enum for indexing into axis action arrays.
|
||||
* @note Keep this up to date with JOY_HEADING_AXIS and friends. Pay attention to their order and copy to here
|
||||
*/
|
||||
namespace Action {
|
||||
enum {
|
||||
HEADING = 0,
|
||||
PITCH,
|
||||
BANK,
|
||||
ABS_THROTTLE,
|
||||
REL_THROTTLE,
|
||||
|
||||
NUM_VALUES
|
||||
};
|
||||
}
|
||||
|
||||
class CCB;
|
||||
|
||||
/*!
|
||||
* A singular button binding
|
||||
*/
|
||||
class CC_bind {
|
||||
public:
|
||||
CID cid = CID_NONE; //!< Which controller this belongs to
|
||||
short btn = -1; //!< Which button to index; If cid == CID_KEYBOARD, this is a key hash.
|
||||
|
||||
public:
|
||||
CC_bind() = default;
|
||||
CC_bind(CID _cid, short _btn) : cid(_cid), btn(_btn) { validate(); };
|
||||
CC_bind(CID _cid, short _btn, char _flags) : cid(_cid), flags(_flags), btn(_btn) { validate(); };
|
||||
CC_bind(const CC_bind &A) = default;
|
||||
|
||||
CC_bind(CID _cid, short _btn) : cid(_cid), btn(_btn) {};
|
||||
|
||||
CC_bind& operator=(const CC_bind &A);
|
||||
CC_bind& operator=(const CC_bind &A) = default;
|
||||
|
||||
/*!
|
||||
* Checks if this CC_bind is equal to the given CC_bind
|
||||
*
|
||||
* @note EQUAL means EQUAL, this will return FALSE if the flags differ by a single bit
|
||||
*/
|
||||
bool operator==(const CC_bind &B) const;
|
||||
|
||||
/*!
|
||||
* Checks if this CC_bind is equal to either in the pair
|
||||
*/
|
||||
bool operator==(const CCB &pair) const;
|
||||
|
||||
/*!
|
||||
* Checks if this CC_bind is not equal to the given CC_bind
|
||||
*/
|
||||
bool operator!=(const CC_bind &B) const;
|
||||
|
||||
/*!
|
||||
* Checks if this CC_bind is not equal to either in the pair
|
||||
* Checks if this CC_bind is equal to the given CC_bind, disregarding the inversion flag
|
||||
*/
|
||||
bool operator!=(const CCB &pair) const;
|
||||
bool invert_agnostic_equals(const CC_bind &B) const;
|
||||
|
||||
/*!
|
||||
* Clears the binding
|
||||
* Clears the binding and flags. Some flags are retained.
|
||||
*/
|
||||
void clear();
|
||||
|
||||
@@ -342,10 +361,62 @@ public:
|
||||
*/
|
||||
bool empty() const;
|
||||
|
||||
/*!
|
||||
* Returns a copy of ::btn
|
||||
*/
|
||||
short get_btn() const;
|
||||
|
||||
/*!
|
||||
* Returns a copy of ::cid
|
||||
*/
|
||||
CID get_cid() const;
|
||||
|
||||
/*!
|
||||
* Returns a copy of ::flags
|
||||
*/
|
||||
char get_flags() const;
|
||||
|
||||
/**
|
||||
* Takes the given binding.
|
||||
*/
|
||||
void take(CID _cid, short _btn, char _flags = 0);
|
||||
|
||||
/**
|
||||
* Validates the binding, clearing it if necassary
|
||||
*/
|
||||
void validate();
|
||||
|
||||
/*!
|
||||
* Returns a human-readable string of this binding
|
||||
*/
|
||||
SCP_string textify() const;
|
||||
|
||||
/*!
|
||||
* Sets the inversion state of this binding according to the passed bool. True = inverted, false = normal
|
||||
*/
|
||||
void invert(bool);
|
||||
|
||||
/*!
|
||||
* Toggles the inversion state
|
||||
*/
|
||||
void invert_toggle();
|
||||
|
||||
/*!
|
||||
* Is true if inverted, false otherwise
|
||||
*/
|
||||
bool is_inverted() const;
|
||||
|
||||
/*!
|
||||
* Checks if this CC_bind conflicts with the given argument
|
||||
*
|
||||
* @note Similar to operator==, but ignores certain combinations of flags.
|
||||
*/
|
||||
bool conflicts_with(const CC_bind&) const;
|
||||
|
||||
private:
|
||||
CID cid = CID_NONE; //!< Which controller this belongs to
|
||||
char flags = 0; //!< mask to determine various additional attributes of btn
|
||||
short btn = -1; //!< The button, key combo, or axis that's bound.
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -392,17 +463,57 @@ public:
|
||||
/*!
|
||||
* Assigns the contents of the given CCB to this CCB
|
||||
*/
|
||||
CCB& operator=(const CCB&);
|
||||
CCB& operator=(const CCB&) = default;
|
||||
|
||||
/*!
|
||||
* Returns True if this CCB's first isn't empty and the given CCB has a binding equal to it
|
||||
* Checks if the given CCB is exactly equal to this
|
||||
*/
|
||||
bool has_first(const CCB&) const;
|
||||
bool operator==(const CCB&);
|
||||
|
||||
/*!
|
||||
* Returns True if this CCB's second isn't empty and the given CCB has a binding equal to it
|
||||
* Checks if the given CCB differs from this
|
||||
*/
|
||||
bool has_second(const CCB&) const;
|
||||
bool operator!=(const CCB&);
|
||||
|
||||
/*!
|
||||
* Returns True if this CCB's first isn't empty and the given CCB has a binding that can conflict with it
|
||||
*/
|
||||
bool has_first_conflict(const CCB&) const;
|
||||
|
||||
/*!
|
||||
* Returns True if this CCB's second isn't empty and the given CCB has a binding that can conflict with it
|
||||
*/
|
||||
bool has_second_conflict(const CCB&) const;
|
||||
|
||||
/*!
|
||||
* Returns a pointer to first, or second, whichever has a binding equal to the given CC_bind, or nullptr if neither
|
||||
*/
|
||||
CC_bind* find(const CC_bind&);
|
||||
|
||||
/*!
|
||||
* Returns a pointer to first, or second, whichever has a CID equal to the given CID, or nullptr if neither
|
||||
*/
|
||||
CC_bind* find(CID);
|
||||
|
||||
/*!
|
||||
* Returns a pointer to first, or second, whichever has *all* flags in the given mask
|
||||
*/
|
||||
CC_bind* find_flags(const char);
|
||||
|
||||
/*!
|
||||
* Sets the inversion state of both bindings
|
||||
*/
|
||||
void invert(bool);
|
||||
|
||||
/*!
|
||||
* Toggles the inversion state of Primary and copies it to Secondary
|
||||
*/
|
||||
void invert_toggle();
|
||||
|
||||
/*!
|
||||
* Is true if both bindings are inverted, false otherwise
|
||||
*/
|
||||
bool is_inverted() const;
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -412,6 +523,12 @@ class CC_preset {
|
||||
public:
|
||||
SCP_vector<CCB> bindings;
|
||||
SCP_string name;
|
||||
|
||||
public:
|
||||
/*!
|
||||
* Checks if the given preset is a duplicate of this one
|
||||
*/
|
||||
bool is_duplicate_of(CC_preset&);
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -449,6 +566,11 @@ public:
|
||||
* @brief Takes the bindings of the given CCB, but leaves all other members alone
|
||||
*/
|
||||
CCI& operator=(const CCB&);
|
||||
|
||||
/*!
|
||||
* Returns true if this item is analogue or an axis
|
||||
*/
|
||||
bool is_axis();
|
||||
};
|
||||
|
||||
/*!
|
||||
@@ -473,9 +595,23 @@ public:
|
||||
|
||||
/*!
|
||||
* Assigns the hardcoded binding to the given action
|
||||
*
|
||||
* @note This differs from the original hardcode from :V: in the hopes of it being more intuitive to future IoAction additions
|
||||
*
|
||||
* param[in] action_id The IoActionId enum associated with the control
|
||||
* param[in] primary The btn, key combo, or axis for the default primary binding
|
||||
* param[in] secondary The btn, key combo, or axis for the default secondary binding
|
||||
* param[in] tab Category tab this control should show up under on the config menu
|
||||
* param[in] indexXSTR ID for the XSTR localization utility. 0 for no localization, 1 for automatic lookup, all other values are index for item in strings.tbl
|
||||
* param[in] text Label text displayed in the config menu.
|
||||
* param[in] type How the control should be treated. see doxy on CC_type for details
|
||||
* param[in] disabled If true, this control will not be available in the game. Controlsconfigdefaults.tbl may override this. Don't use this to temporarily disable controls in mission.
|
||||
*
|
||||
* @details The CID for the default primary and secondary is determined by the type parameter.
|
||||
* If the type is CC_TYPE_TRIGGER or CC_TYPE_CONTINOUS, the primary is assumed to be a key combo and secondary is assumed to be a joy button.
|
||||
* If the type is CC_TYPE_AXIS_ABS, CC_TYPE_AXIS_REL, CC_TYPE_AXIS_BTN_POS, or CC_TYPE_AXIS_BTN_NEG, the primary is a Joy0 axis and the secondary is a Mouse axis
|
||||
*/
|
||||
CCI_builder& operator()(IoActionId action_id, short key_default, short joy_default, char tab, int indexXSTR, const char *text, CC_type type, bool disabled = false);
|
||||
CCI_builder& operator()(IoActionId action_id, short primary, short secondary, char tab, int indexXSTR, const char *text, CC_type type, bool disabled = false);
|
||||
|
||||
private:
|
||||
CCI_builder(); // Only one builder per Control Config, so a default constructor is useless
|
||||
@@ -485,11 +621,6 @@ private:
|
||||
|
||||
extern int Failed_key_index;
|
||||
|
||||
extern int Axis_map_to[]; // Array to map an axis action to a joy axis. size() = NUM_JOY_AXIS_ACTIONS
|
||||
extern int Axis_map_to_defaults[];
|
||||
extern int Invert_axis[]; // Array to hold inversion bools for a joy axis. size() = JOY_NUM_AXES
|
||||
extern int Invert_axis_defaults[];
|
||||
|
||||
extern int Joy_dead_zone_size;
|
||||
extern int Joy_sensitivity;
|
||||
|
||||
@@ -498,7 +629,14 @@ extern int Control_config_overlay_id;
|
||||
extern SCP_vector<CCI> Control_config; //!< Stores the keyboard configuration
|
||||
extern SCP_vector<CC_preset> Control_config_presets; // tabled control presets; pointers to config_item arrays
|
||||
extern const char **Scan_code_text;
|
||||
extern const char **Joy_button_text;
|
||||
extern const char **Joy_button_text; // String table of button labels. XSTR'd on init.
|
||||
|
||||
// string table constants for labels and stuff.
|
||||
#define NUM_AXIS_TEXT JOY_NUM_AXES
|
||||
#define NUM_MOUSE_TEXT MOUSE_NUM_BUTTONS
|
||||
|
||||
extern char *Axis_text[NUM_AXIS_TEXT]; // String table of axis labels (joystick and mice). XSTR'd on init.
|
||||
extern char *Mouse_button_text[NUM_MOUSE_TEXT]; // String table of mouse button labels. XSTR'd on init.
|
||||
|
||||
/*!
|
||||
* @brief Checks if either binding in the CCB has the given cid
|
||||
@@ -509,6 +647,11 @@ extern const char **Joy_button_text;
|
||||
*/
|
||||
int is_cid_either(CID, const CCB);
|
||||
|
||||
/*!
|
||||
* Allows CID to be assigned by a short. Has Assert to check values
|
||||
*/
|
||||
void cid_assign(CID&, const short);
|
||||
|
||||
/*!
|
||||
* @brief initialize common control config stuff - call at game startup after localization has been initialized
|
||||
*/
|
||||
@@ -540,51 +683,74 @@ void control_config_close();
|
||||
void control_config_cancel_exit();
|
||||
|
||||
/*!
|
||||
* @brief Resets all controls to values within the currently selected preset
|
||||
* @brief Copies all bindings within preset into Control_config
|
||||
*/
|
||||
void control_config_reset_defaults();
|
||||
void control_config_use_preset(CC_preset &preset);
|
||||
|
||||
/*!
|
||||
* Returns the IoActionId of a control bound to the given key
|
||||
* @brief Search for a given preset by name and use it, if it exists.
|
||||
*
|
||||
* @returns TRUE if the preset was found and selected
|
||||
* @returns FALSE if the preset was not found, and the defaults were selected
|
||||
*
|
||||
* @details If the preset does not exist, then defaults are used instead.
|
||||
* @see control_config_use_preset()
|
||||
*/
|
||||
bool control_config_use_preset_by_name(const SCP_string &name);
|
||||
|
||||
/**
|
||||
* @brief Gets the currently used preset
|
||||
*
|
||||
* @returns an iterator to the current preset, or
|
||||
* @returns ::iterator Control_config_presets.end() if current bindings are not in a preset
|
||||
*/
|
||||
SCP_vector<CC_preset>::iterator control_config_get_current_preset();
|
||||
|
||||
/*!
|
||||
* Returns the IoActionId (index within Control_config[]) of a control bound to the given key
|
||||
*
|
||||
* @param[in] key The key combo to look for
|
||||
* @param[in] find_override If true, return the IoActionId of a control that has this key as its default
|
||||
*
|
||||
* @return -1 if the given key is NULL or not bound.
|
||||
*
|
||||
* @param[in] key The key combo to look for
|
||||
* @param[in] find_override If true, return the IoActionId of a control that has this key as its default
|
||||
* @details If find_override is set to true, then this returns the index of the action
|
||||
*/
|
||||
int translate_key_to_index(const char *key, bool find_override=true);
|
||||
|
||||
|
||||
/*!
|
||||
* @brief Given the system default key 'key', return the current key that is bound to that function.
|
||||
* @brief Given the system default key 'key', return the current control input(s) that is bound to that function.
|
||||
*
|
||||
* @param[in] key The default key combo (as string) to a certain control
|
||||
* @param[in] key The default key combo (as cstring) to a certain control
|
||||
*
|
||||
* @returns The key combo (as string) currently bound to the control
|
||||
* @return The key combo (as cstring) currently bound to the control, or
|
||||
* @return nullptr if the given key is not a system default key, or
|
||||
* @return "None" if there is nothing bound to the control
|
||||
*
|
||||
* @details Both the 'key' and the return value are descriptive strings that can be displayed
|
||||
* directly to the user. If 'key' isn't a real key, is not normally bound to anything,
|
||||
* or there is no key currently bound to the function, NULL is returned.
|
||||
* or there is no key currently bound to the function, nullptr is returned.
|
||||
*
|
||||
* @note Not thread safe. Has an internal buffer for the return value which is overwritten on each call.
|
||||
*
|
||||
* @note Uses CC_bind::textify for translations
|
||||
*/
|
||||
char *translate_key(char *key);
|
||||
const char *translate_key(char *key);
|
||||
|
||||
/**
|
||||
* @brief Converts the specified key code to a human readable string
|
||||
* @brief Converts the specified key code to a human readable string, according to the selected locale
|
||||
*
|
||||
* @note The returned value is localized to the current language
|
||||
* @param[in] code The key code to convert
|
||||
* @param[in] use_default_locale If true, return the default locale (English) translation of the key
|
||||
*
|
||||
* @param code The key code to convert
|
||||
* @return The text representation of the code. The returned value is stored in a temporary location, copy it to your
|
||||
* own buffer if you want to continue using it.
|
||||
* @return The text representation of the code.
|
||||
*
|
||||
* @note Not thread safe. Has an internal buffer for the return value which is overwritten on each call.
|
||||
*
|
||||
* @note The return value is translated according to localization settings
|
||||
*/
|
||||
const char *textify_scancode(int code);
|
||||
|
||||
/**
|
||||
* @note Same as textify_scancode but always returns the same value regardless of current language
|
||||
* @param code The key code to convert
|
||||
* @return The name of the key
|
||||
* @see textify_scancode
|
||||
*/
|
||||
const char *textify_scancode_universal(int code);
|
||||
const char *textify_scancode(int code, bool use_default_locale = false);
|
||||
|
||||
/*!
|
||||
* @brief Checks how long a control has been active
|
||||
@@ -600,26 +766,29 @@ float check_control_timef(int id);
|
||||
/**
|
||||
* @brief Wrapper for check_control_used. Allows the game to ignore the key if told to do so by the ignore-key SEXP.
|
||||
*
|
||||
* @brief id The IoActionId of the control to check
|
||||
* @brief key The key combo to check against the control.
|
||||
* @param[in] id The IoActionId of the control to check
|
||||
* @param[in] key The key combo to check against the control. If -1, re-check the last key passed to this function
|
||||
*
|
||||
* @returns 0 If the control wasn't used, or
|
||||
* @returns 1 If the control was used
|
||||
*
|
||||
* @details If the given control is CC_CONTINOUS, param key is effectively ignored and instead keyd_press[] is checked.
|
||||
* This function also checks against joy and mouse presses.
|
||||
*/
|
||||
int check_control(int id, int key = -1);
|
||||
|
||||
/**
|
||||
* @brief Gets the scaled reading for all control axes.
|
||||
*
|
||||
* @param[out] h heading
|
||||
* @param[out] p pitch
|
||||
* @param[out] b bank
|
||||
* @param[out] ta Throttle - Absolute
|
||||
* @param[out] tr Throttle - Relative
|
||||
* @param[in] frametime Frametime used to scale the mouse axes
|
||||
* @param[out] axis_v Output array of all control axes, must have size of JOY_NUM_AXIS_ACTIONS
|
||||
*
|
||||
* @note None of the input pointers may be nullptr
|
||||
* @details
|
||||
* * Clamps output to stay within +/-JOY_AXIS_RANGE.
|
||||
* * Should multiple axes be bound to a control, their values are blended with a simple sum. This is to save a bit of
|
||||
* processing for speed.
|
||||
*/
|
||||
void control_get_axes_readings(int *h, int *p, int *b, int *ta, int *tr);
|
||||
void control_get_axes_readings(int *axis_v, float frame_time);
|
||||
|
||||
/**
|
||||
* @brief Markes the given control (by IoActionId) as used
|
||||
@@ -652,4 +821,118 @@ void control_config_clear_used_status();
|
||||
*/
|
||||
int joy_get_scaled_reading(int raw);
|
||||
|
||||
/**
|
||||
* Lookups the given stringified enum to find its value
|
||||
* @return -1 if not found, or
|
||||
* @return IoActionId if found
|
||||
*/
|
||||
int ActionToVal(const char * str);
|
||||
|
||||
/**
|
||||
* Reverse lookups the IoActionId to get its stringified name
|
||||
* @return Pointer to the stringified name of the action, or
|
||||
* @return nullptr if not found
|
||||
*/
|
||||
const char * ValToAction(IoActionId id);
|
||||
|
||||
/**
|
||||
* Reverse lookups the IoActionId to get its stringified name
|
||||
* @return Pointer to the stringified name of the action, or
|
||||
* @return nullptr if not found, or invalid id
|
||||
*/
|
||||
const char * ValToAction(int id);
|
||||
|
||||
/**
|
||||
* Lookups the given stringified enum to find its value
|
||||
*/
|
||||
CID CIDToVal(const char * str);
|
||||
|
||||
/**
|
||||
* Reverse lookups the CID to get its stringified name
|
||||
* @return Pointer to the stringified name of the CID, or
|
||||
* @return nullptr if not found
|
||||
*/
|
||||
const char * ValToCID(CID id);
|
||||
|
||||
/**
|
||||
* Reverse lookups the CID to get its stringified name
|
||||
* @return Pointer to the stringified name of the CID, or
|
||||
* @return nullptr if not found, or invalid id
|
||||
*/
|
||||
const char * ValToCID(int id);
|
||||
|
||||
/**
|
||||
* Lookups the given stringified enum to find its value
|
||||
*/
|
||||
char CCFToVal(const char * str);
|
||||
|
||||
/**
|
||||
* Constructs a enum string from the CCF_FLAGS
|
||||
* @return Pointer to the stringified name of the CCF, or
|
||||
* @return nullptr if not found, or invalid id
|
||||
*/
|
||||
SCP_string ValToCCF(char id);
|
||||
|
||||
/**
|
||||
* Lookups the given stringified enum to find its value
|
||||
*/
|
||||
short InputToVal(CID cid, const char * str);
|
||||
|
||||
/**
|
||||
* Constructs a enumstring from the input binding, depending on the CID
|
||||
*
|
||||
* @return Pointer to the stringified name of the input, or
|
||||
* @return nullptr if not found, or invalid CC_bind
|
||||
*
|
||||
* @note This requires a CCB due to the way things are encoded
|
||||
*/
|
||||
SCP_string ValToInput(const CC_bind &bind);
|
||||
|
||||
/**
|
||||
* Lookups the given stringified enum to find its btn value
|
||||
*/
|
||||
short MouseToVal(const char * str);
|
||||
|
||||
/**
|
||||
* Constructs a enum string from the mouse input
|
||||
*
|
||||
* @return Pointer to the stringified name of the input, or
|
||||
* @return nullptr if not found, or invalid CC_bind
|
||||
* TODO XSTR
|
||||
*
|
||||
* @note This requires a CCB due to the way things are encoded
|
||||
*/
|
||||
SCP_string ValToMouse(const CC_bind &bind);
|
||||
|
||||
/**
|
||||
* Lookups the given stringified enum to find its btn value
|
||||
*/
|
||||
short KeyboardToVal(const char * str);
|
||||
|
||||
/**
|
||||
* Constructs an enum string from the key binding
|
||||
*
|
||||
* @return Pointer to the stringified name of the input, or
|
||||
* @return nullptr if not found, or invalid CC_bind
|
||||
*
|
||||
* @note This requires a CCB due to the way things are encoded
|
||||
* TODO XSTR
|
||||
*/
|
||||
SCP_string ValToKeyboard(const CC_bind &bind);
|
||||
|
||||
/**
|
||||
* Lookups the given stringified enum to find its value
|
||||
*/
|
||||
short JoyToVal(const char * str);
|
||||
|
||||
/**
|
||||
* Constructs a enum string from the Joystick input
|
||||
*
|
||||
* @return Pointer to the stringified name of the input, or
|
||||
* @return nullptr if not found, or invalid CC_bind
|
||||
*
|
||||
* @note This requires a CCB due to the way things are encoded
|
||||
* TODO XSTR
|
||||
*/
|
||||
SCP_string ValToJoy(const CC_bind &bind);
|
||||
#endif
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,481 @@
|
||||
#include "controlconfig/controlsconfig.h"
|
||||
#include "controlconfig/presets.h"
|
||||
#include "libs/jansson.h"
|
||||
#include "parse/parselo.h"
|
||||
|
||||
#include <jansson.h>
|
||||
|
||||
using namespace io::presets;
|
||||
|
||||
const SCP_vector<std::pair<Section, const char*>> SectionMapping {
|
||||
std::pair<Section, const char*>(Section::Unnamed, nullptr),
|
||||
std::pair<Section, const char*>(Section::Actions, "actions"),
|
||||
std::pair<Section, const char*>(Section::Primary, "primary"),
|
||||
std::pair<Section, const char*>(Section::Secondary, "secondary"),
|
||||
};
|
||||
|
||||
const char * lookupSectionName(Section id) {
|
||||
for (const auto& pair: SectionMapping) {
|
||||
if (pair.first == id) {
|
||||
return pair.second;
|
||||
}
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
Section lookupSectionValue(const char* name) {
|
||||
Assertion(name != nullptr, "Key name must be a valid pointer!");
|
||||
|
||||
for (auto& pair : SectionMapping) {
|
||||
if (!strcmp(pair.second, name)) {
|
||||
return pair.first;
|
||||
}
|
||||
}
|
||||
return Section::Invalid;
|
||||
}
|
||||
|
||||
void load_preset_files() {
|
||||
SCP_vector<SCP_string> filelist;
|
||||
cf_get_file_list(filelist, CF_TYPE_PLAYER_BINDS, NOX("*.json"), CF_SORT_NAME, nullptr,
|
||||
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
|
||||
|
||||
std::unique_ptr<PresetFileHandler> handler = nullptr;
|
||||
|
||||
for (const auto &file : filelist) {
|
||||
CFILE* fp = cfopen((file + ".json").c_str(), "r", CFILE_NORMAL, CF_TYPE_PLAYER_BINDS, false,
|
||||
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
|
||||
|
||||
if (!fp) {
|
||||
mprintf(("PLR => Unable to open '%s' for loading!\n", file.c_str()));
|
||||
// try next
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
handler.reset(new PresetFileHandler(fp, true));
|
||||
} catch (const std::exception& e) {
|
||||
mprintf(("PLR => Failed to open JSON: `%s`\n", e.what()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Check the signature
|
||||
int pst_id = handler->readInt("signature");
|
||||
if (pst_id != PST_FILE_ID) {
|
||||
mprintf(("PST => Invalid header id for `%s`, skipping...\n", file.c_str()));
|
||||
continue;
|
||||
}
|
||||
|
||||
// Version
|
||||
int version = handler->readInt("version");
|
||||
mprintf(("PST => Loading `%s` with version %i\n", file.c_str(), version));
|
||||
|
||||
|
||||
// Start reading in data
|
||||
CC_preset preset;
|
||||
preset.name = file;
|
||||
preset.bindings.resize(Control_config.size());
|
||||
|
||||
size_t size;
|
||||
handler->beginArrayRead(Section::Actions, size);
|
||||
for (size_t i = 0; i < size; ++i, handler->nextArrayElement()) {
|
||||
SCP_string str;
|
||||
int id;
|
||||
|
||||
//handler->beginSectionRead(Section::Unnamed);
|
||||
str = handler->readString("bind");
|
||||
id = ActionToVal(str.c_str());
|
||||
|
||||
if (id < 0) {
|
||||
// Unknown bind, possibly custom. Ignore for now
|
||||
handler->endSectionRead(); // Unnamed
|
||||
continue;
|
||||
}
|
||||
|
||||
auto &item = preset.bindings[id];
|
||||
|
||||
handler->beginSectionRead(Section::Primary);
|
||||
str = handler->readString("cid");
|
||||
auto cid = CIDToVal(str.c_str());
|
||||
|
||||
str = handler->readString("flags");
|
||||
auto flags = CCFToVal(str.c_str());
|
||||
|
||||
str = handler->readString("input");
|
||||
auto btn = InputToVal(cid, str.c_str());
|
||||
item.first.take(cid, btn, flags);
|
||||
handler->endSectionRead(); // Primary
|
||||
|
||||
|
||||
handler->beginSectionRead(Section::Secondary);
|
||||
str = handler->readString("cid");
|
||||
cid = CIDToVal(str.c_str());
|
||||
|
||||
str = handler->readString("flags");
|
||||
flags = CCFToVal(str.c_str());
|
||||
|
||||
str = handler->readString("input");
|
||||
btn = InputToVal(cid, str.c_str());
|
||||
item.second.take(cid, btn, flags);
|
||||
handler->endSectionRead(); // Secondary
|
||||
|
||||
//handler->endSectionRead(); // Unnamed
|
||||
}
|
||||
handler->endArrayRead(); // Actions
|
||||
|
||||
// Done with the file
|
||||
bool unique = true;
|
||||
auto it = Control_config_presets.begin();
|
||||
for (; it != Control_config_presets.end(); ++it) {
|
||||
if (preset.is_duplicate_of(*it)) {
|
||||
unique = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (unique) {
|
||||
Control_config_presets.push_back(preset);
|
||||
} else {
|
||||
Warning(LOCATION, "Preset '%s' is a duplicate of '%s', ignoring", preset.name.c_str(), it->name.c_str());
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
bool preset_file_exists(SCP_string name) {
|
||||
if (name.find(".json") == std::string::npos) {
|
||||
name.append(".json");
|
||||
}
|
||||
|
||||
return cf_exists(name.c_str(), CF_TYPE_PLAYER_BINDS) != 0;
|
||||
}
|
||||
|
||||
bool save_preset_file(CC_preset preset, bool overwrite) {
|
||||
// Must have a name
|
||||
if (preset.name.empty()) {
|
||||
mprintf(("PST => Unable to save preset, missing name!\n"));
|
||||
return false;
|
||||
}
|
||||
|
||||
SCP_string filename = preset.name + ".json";
|
||||
std::unique_ptr<PresetFileHandler> handler = nullptr;
|
||||
|
||||
// Check if there's a file already
|
||||
CFILE* fp = cfopen(filename.c_str(), "r", CFILE_NORMAL, CF_TYPE_PLAYER_BINDS, false,
|
||||
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
|
||||
|
||||
if ((fp != nullptr) && !overwrite) {
|
||||
mprintf(("PST => Unable to save '%s', file already exists!\n", filename.c_str()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Try opening file for write
|
||||
fp = cfopen(filename.c_str(), "w", CFILE_NORMAL, CF_TYPE_PLAYER_BINDS, false,
|
||||
CF_LOCATION_ROOT_USER | CF_LOCATION_ROOT_GAME | CF_LOCATION_TYPE_ROOT);
|
||||
|
||||
if (!fp) {
|
||||
mprintf(("PST => Unable to save '%s', unknown error!\n", filename.c_str()));
|
||||
}
|
||||
|
||||
try {
|
||||
handler.reset(new PresetFileHandler(fp, false));
|
||||
} catch (const std::exception& e) {
|
||||
mprintf(("PST => Failed to open JSON: %s\n", e.what()));
|
||||
return false;
|
||||
}
|
||||
|
||||
// Write header and version
|
||||
handler->writeInt("signature", PST_FILE_ID);
|
||||
handler->writeInt("version", PST_VERSION);
|
||||
|
||||
mprintf(("PST => Saving %s with version %d...\n", filename.c_str(), (int)PST_VERSION));
|
||||
|
||||
handler->beginArrayWrite(Section::Actions);
|
||||
for (int i = 0; static_cast<size_t>(i) < preset.bindings.size(); ++i) {
|
||||
const auto& item = preset.bindings[i];
|
||||
const auto& first = item.first;
|
||||
const auto& second = item.second;
|
||||
|
||||
handler->beginSectionWrite(Section::Unnamed);
|
||||
handler->writeString("bind", ValToAction(i));
|
||||
|
||||
handler->beginSectionWrite(Section::Primary);
|
||||
handler->writeString("cid", ValToCID(first.get_cid()));
|
||||
handler->writeString("flags", ValToCCF(first.get_flags()));
|
||||
handler->writeString("input", ValToInput(first));
|
||||
handler->endSectionWrite(); // Primary
|
||||
|
||||
handler->beginSectionWrite(Section::Secondary);
|
||||
handler->writeString("cid", ValToCID(second.get_cid()));
|
||||
handler->writeString("flags", ValToCCF(second.get_flags()));
|
||||
handler->writeString("input", ValToInput(second));
|
||||
handler->endSectionWrite(); // Secondary
|
||||
|
||||
handler->endSectionWrite(); // Control_config[i]
|
||||
}
|
||||
handler->endArrayWrite(); // Actions
|
||||
|
||||
handler->flush();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
PresetFileHandler::PresetFileHandler(CFILE* cfp, bool reading) : _cfp(cfp) {
|
||||
Assertion(cfp != nullptr, "File pointer must be valid!");
|
||||
|
||||
if (reading) {
|
||||
json_error_t error;
|
||||
_rootObj = json_load_cfile(cfp, 0, &error);
|
||||
|
||||
if (!_rootObj) {
|
||||
SCP_string errorStr;
|
||||
sprintf(errorStr, "Error while reading pilot file! %d: %s", error.line, error.text);
|
||||
throw std::runtime_error(errorStr);
|
||||
}
|
||||
} else {
|
||||
_rootObj = json_object();
|
||||
}
|
||||
_currentEl = _rootObj;
|
||||
_elementStack.push_back(_currentEl);
|
||||
};
|
||||
|
||||
PresetFileHandler::~PresetFileHandler() {
|
||||
json_decref(_rootObj);
|
||||
|
||||
cfclose(_cfp);
|
||||
_cfp = nullptr;
|
||||
};
|
||||
|
||||
bool PresetFileHandler::beginSectionRead(Section s) {
|
||||
const char * sectionName = lookupSectionName(s);
|
||||
ensureExists(sectionName);
|
||||
|
||||
auto section = json_object_get(_currentEl, sectionName);
|
||||
if (json_typeof(section) != JSON_OBJECT) {
|
||||
Error(LOCATION, "Section must be a JSON object!");
|
||||
return false;
|
||||
}
|
||||
|
||||
pushElement(section);
|
||||
|
||||
return true;
|
||||
};
|
||||
|
||||
void PresetFileHandler::beginSectionWrite(Section s) {
|
||||
auto key_name = lookupSectionName(s);
|
||||
|
||||
json_t* obj = json_object();
|
||||
|
||||
if (json_is_array(_currentEl)) {
|
||||
// Currently in an array, section must be unnamed
|
||||
Assertion(key_name == nullptr, "Elements of array may not be named sections!");
|
||||
json_array_append_new(_currentEl, obj);
|
||||
} else {
|
||||
Assertion(key_name != nullptr, "Section outside of arrays must be named!");
|
||||
json_object_set_new(_currentEl, key_name, obj);
|
||||
}
|
||||
pushElement(obj);
|
||||
};
|
||||
|
||||
bool PresetFileHandler::beginArrayRead(Section s, size_t &size) {
|
||||
Assertion(_arrayIndex == INVALID_SIZE, "Array nesting is not supported yet!");
|
||||
|
||||
const char * name = lookupSectionName(s);
|
||||
ensureExists(name);
|
||||
|
||||
auto array = json_object_get(_currentEl, name);
|
||||
if (json_typeof(array) != JSON_ARRAY) {
|
||||
Error(LOCATION, "Expected an array for '%s' but it was a different type!", name);
|
||||
return false;
|
||||
}
|
||||
|
||||
pushElement(array);
|
||||
size = json_array_size(array);
|
||||
|
||||
if (size == 0) {
|
||||
// Nothing to do here, avoid calling nextArraySection since that assumes that there is at least one element
|
||||
return true;
|
||||
} // Else, advance to the first element in the array
|
||||
|
||||
_arrayIndex = 0;
|
||||
nextArrayElement();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PresetFileHandler::beginArrayWrite(Section s) {
|
||||
const char * name = lookupSectionName(s);
|
||||
auto array = json_array();
|
||||
|
||||
if (json_is_array(_currentEl)) {
|
||||
// We are in an array, section must be unnamed
|
||||
Assertion(name == nullptr, "Inside an array there can be no named section!");
|
||||
json_array_append_new(_currentEl, array);
|
||||
} else {
|
||||
Assertion(name != nullptr, "Section outside of arrays must be named!");
|
||||
json_object_set_new(_currentEl, name, array);
|
||||
}
|
||||
pushElement(array);
|
||||
}
|
||||
|
||||
void PresetFileHandler::endSectionRead() {
|
||||
Assertion(json_typeof(_currentEl) == JSON_OBJECT, "Current element for section reading is not an object!");
|
||||
|
||||
popElement();
|
||||
};
|
||||
|
||||
void PresetFileHandler::endSectionWrite() {
|
||||
Assertion(json_is_object(_currentEl), "Section ended while not in a section!");
|
||||
|
||||
popElement();
|
||||
};
|
||||
|
||||
void PresetFileHandler::endArrayRead() {
|
||||
// First, remove the element we are currently in if it exists
|
||||
if (json_typeof(_currentEl) != JSON_ARRAY) {
|
||||
popElement();
|
||||
}
|
||||
|
||||
Assertion(json_typeof(_currentEl) == JSON_ARRAY, "Current element must be an array!");
|
||||
|
||||
// TODO Sections are straightforward, but Arrays may need special consideration re: index.
|
||||
popElement();
|
||||
_arrayIndex = INVALID_SIZE;
|
||||
};
|
||||
|
||||
void PresetFileHandler::endArrayWrite() {
|
||||
Assertion(json_is_array(_currentEl), "Array ended while not in an array!");
|
||||
|
||||
popElement();
|
||||
};
|
||||
|
||||
void PresetFileHandler::ensureExists(const char* name) {
|
||||
if (json_typeof(_currentEl) != JSON_OBJECT) {
|
||||
Error(LOCATION, "JSON reading requires a value with name '%s' but the current element is not an object!", name);
|
||||
}
|
||||
if (json_object_get(_currentEl, name) == nullptr) {
|
||||
Error(LOCATION, "JSON reading requires a value with name '%s' but there is no such value!", name);
|
||||
}
|
||||
}
|
||||
|
||||
void PresetFileHandler::ensureNotExists(const char* name) {
|
||||
// Stuff debug checks into booleans, otherwise clang will trigger a false positive for static method with just the asserts
|
||||
// Make sure we're in an element that can support keys
|
||||
bool supports_keys = json_is_object(_currentEl);
|
||||
|
||||
// Make sure we don't overwrite previous values
|
||||
bool element_is_unique = json_object_get(_currentEl, name) == nullptr;
|
||||
|
||||
Assertion(supports_keys, "Currently not in an element that supports keys!");
|
||||
Assertion(element_is_unique, "Entry with name %s already exists!", name);
|
||||
}
|
||||
|
||||
bool PresetFileHandler::exists(const char* name) {
|
||||
if (json_typeof(_currentEl) != JSON_OBJECT) {
|
||||
return false;
|
||||
}
|
||||
if (json_object_get(_currentEl, name) == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PresetFileHandler::flush() {
|
||||
Assertion(_elementStack.size() == 1, "Not all sections or arrays have been ended!");
|
||||
|
||||
json_dump_cfile(_rootObj, _cfp, JSON_INDENT(4) | JSON_PRESERVE_ORDER);
|
||||
}
|
||||
|
||||
void PresetFileHandler::nextArrayElement() {
|
||||
Assertion(_arrayIndex != INVALID_SIZE, "Array index must be valid for this function!");
|
||||
bool in_section = json_is_object(_currentEl);
|
||||
|
||||
if (in_section) {
|
||||
// We have to pop the previous section first
|
||||
popElement();
|
||||
|
||||
Assertion(json_typeof(_currentEl) == JSON_ARRAY, "The previous element should have been an array!");
|
||||
}
|
||||
|
||||
auto max = json_array_size(_currentEl);
|
||||
Assertion(_arrayIndex <= max, "Invalid array index detected!");
|
||||
|
||||
// Silently ignore if we are one past the last element since this function is used in a for loop where this function
|
||||
// is executed one time after the index is incremented past the last element
|
||||
if (_arrayIndex == max) {
|
||||
// Increment the index so we catch usage errors the next time someone tries to call this function
|
||||
++_arrayIndex;
|
||||
return;
|
||||
}
|
||||
|
||||
// We use the current array index and then increment it to avoid skipping the first element
|
||||
auto el = json_array_get(_currentEl, _arrayIndex);
|
||||
++_arrayIndex;
|
||||
|
||||
pushElement(el);
|
||||
}
|
||||
|
||||
json_t * PresetFileHandler::popElement() {
|
||||
Assertion(_elementStack.size() > 1, "Element stack may not get smaller than one element!");
|
||||
|
||||
_elementStack.pop_back();
|
||||
_currentEl = _elementStack.back();
|
||||
return _currentEl;
|
||||
}
|
||||
|
||||
void PresetFileHandler::pushElement(json_t* el) {
|
||||
Assertion(el != nullptr, "Invalid JSON element pointer passed!");
|
||||
|
||||
_currentEl = el;
|
||||
_elementStack.push_back(_currentEl);
|
||||
}
|
||||
|
||||
SCP_string PresetFileHandler::readString(const char* key) {
|
||||
auto el = json_object_get(_currentEl, key);
|
||||
|
||||
if (el == nullptr || json_typeof(el) != JSON_STRING) {
|
||||
Error(LOCATION, "JSON element %s must be a string but it is not valid!", key);
|
||||
return SCP_string();
|
||||
}
|
||||
auto json_str = json_string_value(el);
|
||||
SCP_string val;
|
||||
val.assign(json_str, json_str + json_string_length(el));
|
||||
|
||||
return val;
|
||||
}
|
||||
|
||||
int PresetFileHandler::readInt(const char* key) {
|
||||
auto el = json_object_get(_currentEl, key);
|
||||
|
||||
if (el == nullptr || json_typeof(el) != JSON_INTEGER) {
|
||||
Error(LOCATION, "JSON element %s must be an integer but it is not valid!", key);
|
||||
return 0;
|
||||
}
|
||||
|
||||
return static_cast<int>(json_integer_value(el));
|
||||
}
|
||||
|
||||
void PresetFileHandler::writeString(const char* key, SCP_string s) {
|
||||
ensureNotExists(key);
|
||||
json_t *jstr = nullptr;
|
||||
|
||||
if (!s.empty()) {
|
||||
// if this string isn't proper UTF-8, try to convert it
|
||||
if (utf8::find_invalid(s.begin(), s.end()) != s.end()) {
|
||||
SCP_string buffer;
|
||||
coerce_to_utf8(buffer, s.c_str());
|
||||
jstr = json_string(buffer.c_str());
|
||||
} else {
|
||||
jstr = json_string(s.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
json_object_set_new(_currentEl, key, jstr);
|
||||
};
|
||||
|
||||
void PresetFileHandler::writeInt(const char* key, int val) {
|
||||
ensureNotExists(key);
|
||||
|
||||
json_object_set_new(_currentEl, key, json_integer(val));
|
||||
}
|
||||
@@ -0,0 +1,222 @@
|
||||
#pragma once
|
||||
|
||||
#include "cfile/cfile.h"
|
||||
#include "controlconfig/controlsconfig.h"
|
||||
#include "globalincs/pstypes.h"
|
||||
#include "globalincs/vmallocator.h"
|
||||
|
||||
#include <jansson.h>
|
||||
|
||||
/** API for saving and loading player custom bindings, known as Presets
|
||||
|
||||
Init. If the player/preset dir doesn't exist. Create it, and print a help.txt with all enums (as strings) to label
|
||||
the button names and action names.
|
||||
|
||||
Example output:
|
||||
{
|
||||
"actions": {
|
||||
"TARGET_NEXT": {
|
||||
"first": {
|
||||
"cid": 0,
|
||||
"flags": 0,
|
||||
"btn": 0
|
||||
},
|
||||
"second": {
|
||||
"cid": 0,
|
||||
"flags": 0,
|
||||
"btn": 0
|
||||
}
|
||||
},
|
||||
"TARGET_PREV" : {
|
||||
"first": {
|
||||
"cid": 0,
|
||||
"flags": 0,
|
||||
"btn": 0
|
||||
},
|
||||
"second": {
|
||||
"cid": 0,
|
||||
"flags": 0,
|
||||
"btn": 0
|
||||
}
|
||||
},
|
||||
},
|
||||
"version": 0
|
||||
}
|
||||
|
||||
|
||||
*/
|
||||
namespace io {
|
||||
namespace presets {
|
||||
|
||||
static const unsigned int PST_VERSION = 0;
|
||||
static const unsigned int PST_FILE_ID = 0x5f545350; // "PST_" in file
|
||||
|
||||
enum class Section {
|
||||
Invalid = -1,
|
||||
Unnamed = 0,
|
||||
|
||||
// arrays
|
||||
Actions,
|
||||
|
||||
// subsections
|
||||
Primary,
|
||||
Secondary
|
||||
};
|
||||
|
||||
class PresetFileHandler {
|
||||
public:
|
||||
PresetFileHandler(CFILE* cfp, bool reading);
|
||||
~PresetFileHandler();
|
||||
|
||||
/**
|
||||
* begins writing to a section using the presets::Section as its name
|
||||
* @param[in] s id of the section
|
||||
*/
|
||||
void beginSectionWrite(presets::Section s);
|
||||
|
||||
/**
|
||||
* begins reading a section, if it exists
|
||||
* @param[in] s id of the section
|
||||
*/
|
||||
bool beginSectionRead(presets::Section s);
|
||||
|
||||
/**
|
||||
* Begins writing to an array using the presets::Section as its name
|
||||
*
|
||||
* @param[in] s id of the array
|
||||
*/
|
||||
void beginArrayWrite(presets::Section s);
|
||||
|
||||
/**
|
||||
* Begins reading from an array
|
||||
* @param[int] s id of the array
|
||||
* @param[out] size the size of the array
|
||||
*/
|
||||
bool beginArrayRead(presets::Section s, size_t &size);
|
||||
|
||||
/**
|
||||
* ends writing to a section
|
||||
*/
|
||||
void endSectionWrite();
|
||||
|
||||
/**
|
||||
* ends reading a section
|
||||
*/
|
||||
void endSectionRead();
|
||||
|
||||
/**
|
||||
* ends writing to an array.
|
||||
*/
|
||||
void endArrayWrite();
|
||||
|
||||
/**
|
||||
* ends reading from an array
|
||||
*/
|
||||
void endArrayRead();
|
||||
|
||||
/**
|
||||
* Writes a string with the given key name
|
||||
*/
|
||||
void writeString(const char* key, SCP_string s);
|
||||
|
||||
/**
|
||||
* Reads a string with the given key name
|
||||
*/
|
||||
SCP_string readString(const char* key);
|
||||
|
||||
/**
|
||||
* Writes an int with the given key name
|
||||
*/
|
||||
void writeInt(const char* key, int val);
|
||||
|
||||
/**
|
||||
* Reads an int with the given key name
|
||||
*/
|
||||
int readInt(const char* key);
|
||||
|
||||
/**
|
||||
* For writing, Flushes the buffers and dumps to file
|
||||
*/
|
||||
void flush();
|
||||
|
||||
/**
|
||||
* Advances the array parser to the next element
|
||||
*/
|
||||
void nextArrayElement();
|
||||
|
||||
private:
|
||||
CFILE* _cfp = nullptr;
|
||||
|
||||
json_t* _rootObj = nullptr;
|
||||
|
||||
json_t* _currentEl = nullptr;
|
||||
|
||||
/**
|
||||
* BufferStack of all json elements being read/written
|
||||
*/
|
||||
SCP_vector<json_t*> _elementStack;
|
||||
|
||||
/**
|
||||
* Stack to keep track of the heirachy/nesting. Back is immediate parent, Front is root
|
||||
*/
|
||||
SCP_vector<void*> _parentStack;
|
||||
|
||||
size_t _arrayIndex = INVALID_SIZE;
|
||||
|
||||
//void* _sectionIterator = nullptr; //!< Pointer to the current section/array we're in
|
||||
|
||||
private:
|
||||
/**
|
||||
* Pushes an element onto the elementStack
|
||||
*/
|
||||
void pushElement(json_t* el);
|
||||
|
||||
/**
|
||||
* Pops an element off the elementStack
|
||||
*/
|
||||
json_t * popElement();
|
||||
|
||||
/**
|
||||
* Used for optionals, checks if an element exists with the given name
|
||||
* @return True if the element exists
|
||||
* @return False otherwise
|
||||
*/
|
||||
bool exists(const char* name);
|
||||
|
||||
/**
|
||||
* Throws an error if an element with the given name does not exist
|
||||
*/
|
||||
void ensureExists(const char* name);
|
||||
|
||||
/**
|
||||
* Throws an error if an element with the given name exists
|
||||
*/
|
||||
void ensureNotExists(const char* name);
|
||||
};
|
||||
|
||||
} // namespace presets
|
||||
} // namespace io
|
||||
|
||||
/**
|
||||
* @brief Saves the given preset to file.
|
||||
* @param[in] preset The preset to save
|
||||
* @param[in] overwrite If true, overwrite existing preset file which have the same name as the given preset
|
||||
*
|
||||
* @returns True if successful, or
|
||||
* @returns False Preset does not have a name, or
|
||||
* @returns False Preset file exists and overwrite == false, or
|
||||
* @returns False Preset file .JSON could not be written
|
||||
*/
|
||||
bool save_preset_file(CC_preset preset, bool overwrite);
|
||||
|
||||
/**
|
||||
* @brief Loads in all preset .json files from 'players/presets'
|
||||
* @details If a preset file is unique, it is loaded into the game and available for use by the player. However, if
|
||||
* a preset is a duplicate of another preset, it is ignored, and the player is warned of it
|
||||
*/
|
||||
void load_preset_files();
|
||||
|
||||
/**
|
||||
* @brief Returns true if a preset file with the given name exists.
|
||||
*/
|
||||
bool preset_file_exists(SCP_string name);
|
||||
+295
-90
@@ -1,3 +1,4 @@
|
||||
#include "controlconfig/controlsconfig.h"
|
||||
#include "io/joy.h"
|
||||
#include "io/joy_ff.h"
|
||||
#include "io/timer.h"
|
||||
@@ -6,6 +7,7 @@
|
||||
#include "osapi/osapi.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <bitset>
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
@@ -19,8 +21,8 @@ namespace {
|
||||
|
||||
typedef std::unique_ptr<Joystick> JoystickPtr;
|
||||
|
||||
SCP_vector<JoystickPtr> joysticks;
|
||||
Joystick *currentJoystick = nullptr;
|
||||
SCP_vector<JoystickPtr> joysticks; //!< All joysticks detected by SDL
|
||||
std::array<Joystick*, CID_JOY_MAX> pJoystick = {nullptr}; //!< The 4 joysticks FSO are using. pJoystick[0] replaces CurrentJoystick
|
||||
|
||||
bool initialized = false;
|
||||
|
||||
@@ -118,14 +120,65 @@ bool joystickMatchesGuid(Joystick* testStick, const SCP_string& guid, int id)
|
||||
return testStick->getDeviceId() == id;
|
||||
}
|
||||
|
||||
bool isCurrentJoystick(Joystick* testStick)
|
||||
{
|
||||
const auto currentGUID = os_config_read_string(nullptr, "CurrentJoystickGUID", "");
|
||||
const auto currentId = static_cast<int>(os_config_read_uint(nullptr, "CurrentJoystick", 0));
|
||||
/**
|
||||
* Returns True if the given stick should be assigned the given cid
|
||||
*
|
||||
* @param[in] testStick The joystick ptr to test
|
||||
* @param[in] cid The cid to test this against
|
||||
*
|
||||
* @details
|
||||
* Should cid be CID_JOY0, it also checks for legacy .ini keys.
|
||||
* Should Joy0ID key be null, legacy keys will be tried
|
||||
*/
|
||||
bool isPlayerJoystick(Joystick* testStick, short cid) {
|
||||
SCP_string GUID;
|
||||
int Id;
|
||||
|
||||
return joystickMatchesGuid(testStick, currentGUID, currentId);
|
||||
SCP_string key_guid;
|
||||
SCP_string key_id;
|
||||
|
||||
// Select key strings according to cid
|
||||
switch (cid) {
|
||||
case CID_JOY0:
|
||||
key_guid = "Joy0GUID";
|
||||
key_id = "Joy0ID";
|
||||
break;
|
||||
case CID_JOY1:
|
||||
key_guid = "Joy1GUID";
|
||||
key_id = "Joy1ID";
|
||||
break;
|
||||
case CID_JOY2:
|
||||
key_guid = "Joy2GUID";
|
||||
key_id = "Joy2ID";
|
||||
break;
|
||||
case CID_JOY3:
|
||||
key_guid = "Joy3GUID";
|
||||
key_id = "Joy3ID";
|
||||
break;
|
||||
case CID_NONE:
|
||||
case CID_KEYBOARD:
|
||||
case CID_MOUSE:
|
||||
return false;
|
||||
default:
|
||||
mprintf(("Unknown CID %i\n", cid));
|
||||
return false;
|
||||
}
|
||||
|
||||
GUID = os_config_read_string(nullptr, key_guid.c_str(), "");
|
||||
Id = static_cast<int>(os_config_read_uint(nullptr, key_id.c_str(), 0));
|
||||
|
||||
if ((cid == CID_JOY0) && GUID.empty()) {
|
||||
// Check legacy keys
|
||||
GUID = os_config_read_string(nullptr, "CurrentJoystickGUID", "");
|
||||
Id = static_cast<int>(os_config_read_uint(nullptr, "CurrentJoystick", 0));
|
||||
}
|
||||
|
||||
return joystickMatchesGuid(testStick, GUID, Id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads/deserializes the given json line and retrieves the joystick that it references
|
||||
*/
|
||||
Joystick* joystick_deserialize(const json_t* value)
|
||||
{
|
||||
const char* guid;
|
||||
@@ -145,6 +198,9 @@ Joystick* joystick_deserialize(const json_t* value)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts/serializes joystick reference data into a json line
|
||||
*/
|
||||
json_t* joystick_serializer(Joystick* joystick)
|
||||
{
|
||||
if (joystick == nullptr) {
|
||||
@@ -154,6 +210,9 @@ json_t* joystick_serializer(Joystick* joystick)
|
||||
return json_pack("{sssi}", "guid", joystick->getGUID().c_str(), "id", joystick->getID());
|
||||
}
|
||||
|
||||
/**
|
||||
* Scripting callback for displaying the given joystick's name
|
||||
*/
|
||||
SCP_string joystick_display(Joystick* stick)
|
||||
{
|
||||
if (stick == nullptr) {
|
||||
@@ -162,6 +221,9 @@ SCP_string joystick_display(Joystick* stick)
|
||||
return stick->getName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scripting callback for enumerating a SCP_vector with all available joysticks
|
||||
*/
|
||||
SCP_vector<Joystick*> joystick_enumerator()
|
||||
{
|
||||
SCP_vector<Joystick*> out;
|
||||
@@ -172,7 +234,23 @@ SCP_vector<Joystick*> joystick_enumerator()
|
||||
return out;
|
||||
}
|
||||
|
||||
auto JoystickOption = options::OptionBuilder<Joystick*>("Input.Joystick", "Joystick", "The current joystick.")
|
||||
/**
|
||||
* Joystick options for the new menu system
|
||||
* These should be displayed as a dropdown box type widget
|
||||
*/
|
||||
auto JoystickOption = options::OptionBuilder<Joystick*>("Input.Joystick", "Joystick", "The current joystick 0.")
|
||||
.category("Input") // Category this option shows up in the scripting heirachy
|
||||
.deserializer(joystick_deserialize) // callback for json to C++
|
||||
.serializer(joystick_serializer) // callback for C++ to json
|
||||
.display(joystick_display) // callback for constructing the display label
|
||||
.enumerator(joystick_enumerator) // callback for enumerating/constructing the values this option may take
|
||||
.level(options::ExpertLevel::Beginner)
|
||||
.default_val(nullptr) // initial/default value for this option
|
||||
.flags({options::OptionFlags::ForceMultiValueSelection})
|
||||
.importance(3)
|
||||
.finish();
|
||||
|
||||
auto JoystickOption1 = options::OptionBuilder<Joystick*>("Input.Joystick1", "Joystick1", "The current joystick 1.")
|
||||
.category("Input")
|
||||
.deserializer(joystick_deserialize)
|
||||
.serializer(joystick_serializer)
|
||||
@@ -180,10 +258,34 @@ auto JoystickOption = options::OptionBuilder<Joystick*>("Input.Joystick", "Joyst
|
||||
.enumerator(joystick_enumerator)
|
||||
.level(options::ExpertLevel::Beginner)
|
||||
.default_val(nullptr)
|
||||
.flags({options::OptionFlags::ForceMultiValueSelection})
|
||||
.flags({ options::OptionFlags::ForceMultiValueSelection })
|
||||
.importance(3)
|
||||
.finish();
|
||||
|
||||
auto JoystickOption2 = options::OptionBuilder<Joystick*>("Input.Joystick2", "Joystick2", "The current joystick 2.")
|
||||
.category("Input")
|
||||
.deserializer(joystick_deserialize)
|
||||
.serializer(joystick_serializer)
|
||||
.display(joystick_display)
|
||||
.enumerator(joystick_enumerator)
|
||||
.level(options::ExpertLevel::Beginner)
|
||||
.default_val(nullptr)
|
||||
.flags({ options::OptionFlags::ForceMultiValueSelection })
|
||||
.importance(3)
|
||||
.finish();
|
||||
|
||||
auto JoystickOption3 = options::OptionBuilder<Joystick*>("Input.Joystick3", "Joystick3", "The current joystick 3.")
|
||||
.category("Input")
|
||||
.deserializer(joystick_deserialize)
|
||||
.serializer(joystick_serializer)
|
||||
.display(joystick_display)
|
||||
.enumerator(joystick_enumerator)
|
||||
.level(options::ExpertLevel::Beginner)
|
||||
.default_val(nullptr)
|
||||
.flags({ options::OptionFlags::ForceMultiValueSelection })
|
||||
.importance(3)
|
||||
.finish();
|
||||
|
||||
HatPosition convertSDLHat(int val)
|
||||
{
|
||||
switch (val)
|
||||
@@ -227,22 +329,30 @@ void enumerateJoysticks(SCP_vector<JoystickPtr>& outVec)
|
||||
}
|
||||
}
|
||||
|
||||
void setCurrentJoystick(Joystick *stick)
|
||||
/**
|
||||
* Assigns the given cid to the given stick
|
||||
*
|
||||
* @param[in] stick The stick that is being assigned
|
||||
* @param[in] cid The cid being assigned, must be a valid short between CID_JOY0 and CID_JOY_MAX
|
||||
*/
|
||||
void setPlayerJoystick(Joystick *stick, short cid)
|
||||
{
|
||||
currentJoystick = stick;
|
||||
Assert((cid >= CID_JOY0) && (cid < CID_JOY_MAX));
|
||||
pJoystick[cid] = stick;
|
||||
|
||||
if (currentJoystick != nullptr)
|
||||
if (pJoystick[cid] != nullptr)
|
||||
{
|
||||
mprintf((" Using '%s' as the primary joystick\n", currentJoystick->getName().c_str()));
|
||||
mprintf((" Using '%s' as Joy-%i\n", pJoystick[cid]->getName().c_str(), cid));
|
||||
mprintf(("\n"));
|
||||
mprintf((" Number of axes: %d\n", pJoystick[cid]->numAxes()));
|
||||
mprintf((" Number of buttons: %d\n", pJoystick[cid]->numButtons()));
|
||||
mprintf((" Number of hats: %d\n", pJoystick[cid]->numHats()));
|
||||
mprintf((" Number of trackballs: %d\n", pJoystick[cid]->numBalls()));
|
||||
mprintf(("\n"));
|
||||
mprintf((" Number of axes: %d\n", currentJoystick->numAxes()));
|
||||
mprintf((" Number of buttons: %d\n", currentJoystick->numButtons()));
|
||||
mprintf((" Number of hats: %d\n", currentJoystick->numHats()));
|
||||
mprintf((" Number of trackballs: %d\n", currentJoystick->numBalls()));
|
||||
}
|
||||
else
|
||||
{
|
||||
mprintf((" Using no joystick.\n"));
|
||||
mprintf((" Joystick %i removed\n", cid));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -347,38 +457,76 @@ bool device_event_handler(const SDL_Event &evt)
|
||||
joysticks.push_back(std::move(added));
|
||||
}
|
||||
|
||||
if (currentJoystick != nullptr)
|
||||
// Is true if all of the pJoysticks are bound
|
||||
bool all_bound = std::all_of(pJoystick.begin(), pJoystick.end(), [](Joystick* pJoy){ return pJoy != nullptr; });
|
||||
|
||||
if (all_bound)
|
||||
{
|
||||
// We already have a valid joystick device, ignore any further events
|
||||
// We already have all valid joystick devices, bail early
|
||||
return true;
|
||||
}
|
||||
} // Else, at least one player stick is unbound
|
||||
|
||||
if (Using_in_game_options) {
|
||||
auto value = JoystickOption->getValue();
|
||||
// Bind the added stick, if we want it
|
||||
if (Using_in_game_options)
|
||||
{
|
||||
// New menu system lets players select joysticks in-game
|
||||
Joystick* value[CID_JOY_MAX];
|
||||
value[0] = JoystickOption->getValue();
|
||||
value[1] = JoystickOption1->getValue();
|
||||
value[2] = JoystickOption2->getValue();
|
||||
value[3] = JoystickOption3->getValue();
|
||||
|
||||
// Since the new stick has been added to the joystick list, the option will return the same pointer here
|
||||
if (value == addedStick) {
|
||||
setCurrentJoystick(addedStick);
|
||||
|
||||
for (short i = CID_JOY0; i < CID_JOY_MAX; ++i)
|
||||
{
|
||||
if (value[i] == addedStick)
|
||||
{
|
||||
mprintf(("Joystick %i connected\n", i));
|
||||
setPlayerJoystick(addedStick, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (isCurrentJoystick(addedStick)) {
|
||||
// found our wanted stick!
|
||||
setCurrentJoystick(addedStick);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Legacy system requires setting joysticks in the launcher
|
||||
for (short i = CID_JOY0; i < CID_JOY_MAX; ++i)
|
||||
{
|
||||
if (isPlayerJoystick(addedStick, i))
|
||||
{
|
||||
mprintf(("Joystick %i connected\n", i));
|
||||
setPlayerJoystick(addedStick, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (evtType == SDL_JOYDEVICEREMOVED)
|
||||
{
|
||||
if (currentJoystick != nullptr && joyDeviceEvent.which == currentJoystick->getID())
|
||||
bool any_bound = std::any_of(pJoystick.begin(), pJoystick.end(), [](Joystick* pJoy){ return pJoy != nullptr; });
|
||||
|
||||
if (!any_bound)
|
||||
{
|
||||
// We just lost our joystick, reset the data
|
||||
setCurrentJoystick(nullptr);
|
||||
// Nothing bound, ignore
|
||||
return true;
|
||||
}
|
||||
|
||||
// Else, Find if any of our joysticks were lost
|
||||
for (short i = CID_JOY0; i < CID_JOY_MAX; ++i)
|
||||
{
|
||||
if (joyDeviceEvent.which == pJoystick[i]->getID())
|
||||
{
|
||||
// We just lost our joystick, reset the data
|
||||
mprintf(("Joystick %i disconnected\n", i));
|
||||
setPlayerJoystick(nullptr, i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
} // namespace
|
||||
|
||||
namespace io
|
||||
{
|
||||
@@ -576,6 +724,7 @@ namespace joystick
|
||||
_name.assign(SDL_JoystickName(_joystick));
|
||||
_guidStr = getJoystickGUID(_joystick);
|
||||
_id = SDL_JoystickInstanceID(_joystick);
|
||||
_isHaptic = SDL_JoystickIsHaptic(_joystick);
|
||||
|
||||
// Initialize values of the axes
|
||||
auto numSticks = SDL_JoystickNumAxes(_joystick);
|
||||
@@ -771,11 +920,30 @@ namespace joystick
|
||||
mprintf((" Joystick ID: %d\n", getID()));
|
||||
mprintf((" Joystick device ID: %d\n", _device_id));
|
||||
}
|
||||
|
||||
json_t* Joystick::getJSON() {
|
||||
json_t* object;
|
||||
|
||||
object = json_pack("{s:s, s:s, s:i, s:i, s:i, s:i, s:i, s:i, s:b}",
|
||||
"name", getName().c_str(), // s:s
|
||||
"guid", getGUID().c_str(), // s:s
|
||||
"id", static_cast<int>( getID() ), // s:i
|
||||
"device", _device_id, // s:i
|
||||
"num_axes", numAxes(), // s:i
|
||||
"num_balls", numBalls(), // s:i
|
||||
"num_buttons", numButtons(), // s:i
|
||||
"num_hats", numHats(), // s:i
|
||||
"is_haptic", _isHaptic // s:b
|
||||
);
|
||||
|
||||
return object;
|
||||
}
|
||||
|
||||
int Joystick::getDeviceId() const {
|
||||
return _device_id;
|
||||
}
|
||||
|
||||
bool init()
|
||||
bool init(bool no_register)
|
||||
{
|
||||
using namespace os::events;
|
||||
|
||||
@@ -808,6 +976,11 @@ namespace joystick
|
||||
// Get the initial list of connected joysticks
|
||||
enumerateJoysticks(joysticks);
|
||||
|
||||
if (no_register) {
|
||||
// bail before registering anything
|
||||
return false;
|
||||
}
|
||||
|
||||
// Register all the event handlers
|
||||
addEventListener(SDL_JOYAXISMOTION, DEFAULT_LISTENER_WEIGHT, axis_event_handler);
|
||||
addEventListener(SDL_JOYBALLMOTION, DEFAULT_LISTENER_WEIGHT, ball_event_handler);
|
||||
@@ -821,25 +994,40 @@ namespace joystick
|
||||
addEventListener(SDL_JOYDEVICEREMOVED, DEFAULT_LISTENER_WEIGHT, device_event_handler);
|
||||
|
||||
// Search for the correct stick
|
||||
if (Using_in_game_options) {
|
||||
// The new options system is in use
|
||||
setCurrentJoystick(JoystickOption->getValue());
|
||||
} else {
|
||||
// Fall back to the legacy config values
|
||||
for (auto& stick : joysticks) {
|
||||
if (isCurrentJoystick(stick.get())) {
|
||||
// Joystick found
|
||||
setCurrentJoystick(stick.get());
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (Using_in_game_options)
|
||||
{
|
||||
// The new options system is in use
|
||||
setPlayerJoystick(JoystickOption->getValue(), 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Fall back to the legacy config values
|
||||
for (short i = CID_JOY0; i < CID_JOY_MAX; ++i)
|
||||
{
|
||||
for (auto& stick : joysticks)
|
||||
{
|
||||
if (isPlayerJoystick(stick.get(), i))
|
||||
{
|
||||
// Joystick found
|
||||
setPlayerJoystick(stick.get(), i);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentJoystick == nullptr) {
|
||||
mprintf((" No joystick is being used.\n"));
|
||||
}
|
||||
}
|
||||
bool used = false;
|
||||
for (const auto pJoy : pJoystick) {
|
||||
if (pJoy != nullptr) {
|
||||
used = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (!used) {
|
||||
mprintf((" No joystick is being used.\n"));
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
initialized = true;
|
||||
|
||||
joy_ff_init();
|
||||
|
||||
@@ -858,9 +1046,28 @@ namespace joystick
|
||||
return joysticks[index].get();
|
||||
}
|
||||
|
||||
Joystick *getCurrentJoystick()
|
||||
Joystick *getPlayerJoystick(short cid)
|
||||
{
|
||||
return currentJoystick;
|
||||
if ((cid >= CID_JOY0) && (cid < CID_JOY_MAX))
|
||||
{
|
||||
return pJoystick[cid];
|
||||
}
|
||||
else
|
||||
{
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
int getPlayerJoystickCount() {
|
||||
int count = 0;
|
||||
|
||||
for (auto pJoy : pJoystick) {
|
||||
if (pJoy != nullptr) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
void shutdown()
|
||||
@@ -868,58 +1075,46 @@ namespace joystick
|
||||
joy_ff_shutdown();
|
||||
|
||||
initialized = false;
|
||||
currentJoystick = nullptr;
|
||||
std::for_each(pJoystick.begin(), pJoystick.end(), [](Joystick*& pJoy){ pJoy = nullptr; });
|
||||
|
||||
// Automatically frees joystick resources
|
||||
joysticks.clear();
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
}
|
||||
|
||||
SCP_vector<JoystickInformation> getJoystickInformations() {
|
||||
SCP_vector<JoystickInformation> joystickInfo;
|
||||
json_t* getJsonArray() {
|
||||
// Do a full init of the SDL_JOYSTICK systems
|
||||
init();
|
||||
|
||||
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK) < 0) {
|
||||
return SCP_vector<JoystickInformation>();
|
||||
}
|
||||
json_t* array = json_array();
|
||||
size_t len = 0;
|
||||
|
||||
auto num_joysticks = SDL_NumJoysticks();
|
||||
for (auto i = 0; i < num_joysticks; ++i) {
|
||||
auto joystick = SDL_JoystickOpen(i);
|
||||
// Get the JSON info of each detected joystick and attach it to array
|
||||
for (auto& joystick : joysticks) {
|
||||
json_t* object = joystick->getJSON();
|
||||
|
||||
JoystickInformation info{};
|
||||
|
||||
auto name = SDL_JoystickName(joystick);
|
||||
if (name != nullptr) {
|
||||
info.name = name;
|
||||
if (object != nullptr) {
|
||||
json_array_append_new(array, object);
|
||||
len++;
|
||||
}
|
||||
info.guid = getJoystickGUID(joystick);
|
||||
|
||||
info.num_axes = static_cast<uint32_t>(SDL_JoystickNumAxes(joystick));
|
||||
info.num_balls = static_cast<uint32_t>(SDL_JoystickNumBalls(joystick));
|
||||
info.num_buttons = static_cast<uint32_t>(SDL_JoystickNumButtons(joystick));
|
||||
info.num_hats = static_cast<uint32_t>(SDL_JoystickNumHats(joystick));
|
||||
|
||||
info.is_haptic = SDL_JoystickIsHaptic(joystick) == SDL_TRUE;
|
||||
|
||||
joystickInfo.push_back(info);
|
||||
|
||||
SDL_JoystickClose(joystick);
|
||||
}
|
||||
|
||||
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
||||
// Do a full de-init of the SDL_JOYSTICK systems
|
||||
shutdown();
|
||||
|
||||
return joystickInfo;
|
||||
return array;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Compatibility for the rest of the engine follows
|
||||
|
||||
int joystick_read_raw_axis(int num_axes, int *axis)
|
||||
int joystick_read_raw_axis(short cid, int num_axes, int *axis)
|
||||
{
|
||||
int i;
|
||||
|
||||
auto current = io::joystick::getCurrentJoystick();
|
||||
auto current = io::joystick::getPlayerJoystick(cid);
|
||||
if (!initialized || current == nullptr)
|
||||
{
|
||||
// fake a return value so that controlconfig doesn't get freaky with no joystick
|
||||
@@ -946,11 +1141,12 @@ int joystick_read_raw_axis(int num_axes, int *axis)
|
||||
return 1;
|
||||
}
|
||||
|
||||
float joy_down_time(int btn)
|
||||
float joy_down_time(const CC_bind &bind)
|
||||
{
|
||||
int btn = bind.get_btn();
|
||||
if (btn < 0) return 0.f;
|
||||
|
||||
auto current = io::joystick::getCurrentJoystick();
|
||||
auto current = io::joystick::getPlayerJoystick(bind.get_cid());
|
||||
|
||||
if (current == nullptr)
|
||||
{
|
||||
@@ -978,11 +1174,12 @@ float joy_down_time(int btn)
|
||||
return current->getButtonDownTime(btn);
|
||||
}
|
||||
|
||||
int joy_down_count(int btn, int reset_count)
|
||||
int joy_down_count(const CC_bind &bind, int reset_count)
|
||||
{
|
||||
int btn = bind.get_btn();
|
||||
if (btn < 0) return 0;
|
||||
|
||||
auto current = io::joystick::getCurrentJoystick();
|
||||
auto current = io::joystick::getPlayerJoystick(bind.get_cid());
|
||||
|
||||
if (current == nullptr)
|
||||
{
|
||||
@@ -1010,7 +1207,15 @@ int joy_down_count(int btn, int reset_count)
|
||||
return current->getButtonDownCount(btn, reset_count != 0);
|
||||
}
|
||||
|
||||
int joy_down(int btn)
|
||||
int joy_down(const CC_bind &bind)
|
||||
{
|
||||
return joy_down_time(btn) > 0.0f;
|
||||
return joy_down_time(bind) > 0.0f;
|
||||
}
|
||||
|
||||
bool joy_present(short cid) {
|
||||
if ((cid < CID_JOY0) || (cid >= CID_JOY_MAX)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return pJoystick[cid] != nullptr;
|
||||
}
|
||||
+48
-32
@@ -10,8 +10,10 @@
|
||||
#ifndef __JOY_H__
|
||||
#define __JOY_H__
|
||||
|
||||
#include "controlconfig/controlsconfig.h"
|
||||
#include "globalincs/pstypes.h"
|
||||
|
||||
#include "libs/jansson.h"
|
||||
#include "SDL_joystick.h"
|
||||
|
||||
// z64: Moved up here for compatibility. Bye bye, organization!
|
||||
@@ -226,7 +228,16 @@ namespace io
|
||||
*/
|
||||
void handleJoyEvent(const SDL_Event &evt);
|
||||
|
||||
/**
|
||||
* @brief Prints joystick info to log
|
||||
*/
|
||||
void printInfo();
|
||||
|
||||
/**
|
||||
* @brief Like printInfo, but returns info as a JSON object
|
||||
*/
|
||||
json_t* getJSON();
|
||||
|
||||
private:
|
||||
Joystick(const Joystick &);
|
||||
Joystick &operator=(const Joystick &);
|
||||
@@ -241,13 +252,13 @@ namespace io
|
||||
void handleButtonEvent(const SDL_JoyButtonEvent& evt);
|
||||
void handleHatEvent(const SDL_JoyHatEvent& evt);
|
||||
|
||||
int _device_id; //!< The SDL device index
|
||||
int _device_id; //!< The OS device index
|
||||
SDL_Joystick *_joystick; //!< The SDL joystick handle
|
||||
|
||||
SCP_string _guidStr; //!< The GUID string
|
||||
SCP_string _name; //!< The joystick name
|
||||
|
||||
SDL_JoystickID _id; //!< The instance ID
|
||||
SCP_string _guidStr; //!< The GUID string
|
||||
SCP_string _name; //!< The joystick name
|
||||
SDL_JoystickID _id; //!< The instance ID
|
||||
bool _isHaptic = false; //!< If this joystick supports haptic feedback
|
||||
|
||||
SCP_vector<Sint16> _axisValues; //!< The current axes values
|
||||
SCP_vector<coord2d> _ballValues; //!< The ball values
|
||||
@@ -277,9 +288,15 @@ namespace io
|
||||
|
||||
/**
|
||||
* @brief Initializes the joystick subsystem
|
||||
*
|
||||
* @param[in] no_register if true, init won't try to register joysticks for use.
|
||||
*
|
||||
* @return @c true when successfully initialized
|
||||
*
|
||||
* @details Registration involves setting up listeners and grabbing the selected joystick from the registry.
|
||||
* Should no_register be true, the only init done is the SDL systems needed to query joystick information
|
||||
*/
|
||||
bool init();
|
||||
bool init(bool no_register = false);
|
||||
|
||||
/**
|
||||
* @brief Gets number of connected joysticks
|
||||
@@ -297,48 +314,47 @@ namespace io
|
||||
Joystick *getJoystick(size_t index);
|
||||
|
||||
/**
|
||||
* @brief Gets the primary joystick
|
||||
* @return The joystick pointer, may be @c nullptr of no primary joystick
|
||||
*
|
||||
* @warning This function may be removed in the future when multi-joystick support is implemented
|
||||
* @brief Gets the Joystick with the given cid
|
||||
* @return The joystick pointer, may be @c nullptr if unassigned or invalid cid
|
||||
*/
|
||||
Joystick *getCurrentJoystick();
|
||||
Joystick *getPlayerJoystick(short cid);
|
||||
|
||||
/**
|
||||
* @brief Gets the number of active player joysticks
|
||||
* @return 0 If no player joysticks are bound or active, or
|
||||
* @return the number of player joysticks
|
||||
*/
|
||||
int getPlayerJoystickCount();
|
||||
|
||||
/**
|
||||
* @brief Frees resources of the joystick subsystem
|
||||
*/
|
||||
void shutdown();
|
||||
|
||||
struct JoystickInformation {
|
||||
SCP_string name;
|
||||
SCP_string guid;
|
||||
|
||||
uint32_t num_axes;
|
||||
uint32_t num_balls;
|
||||
uint32_t num_buttons;
|
||||
uint32_t num_hats;
|
||||
|
||||
bool is_haptic;
|
||||
};
|
||||
|
||||
SCP_vector<JoystickInformation> getJoystickInformations();
|
||||
/**
|
||||
* @brief Called from cmdline -get_info, gets Joy info JSON and shuts down SDL_JOYSTICK subsystem.
|
||||
*/
|
||||
json_t* getJsonArray();
|
||||
}
|
||||
}
|
||||
|
||||
// For now this is a constant for the rest of the engine
|
||||
const int JOY_NUM_AXES = 6;
|
||||
|
||||
const int JOY_AXIS_MIN = 0;
|
||||
const int JOY_AXIS_CENTER = 32768;
|
||||
const int JOY_AXIS_CENTER = 32768; // = JOY_AXIS_MIN + ((JOY_AXIS_MAX - JOY_AXIS_MIN) / 2)
|
||||
const int JOY_AXIS_MAX = 65536;
|
||||
const int JOY_AXIS_RANGE = (JOY_AXIS_MAX - JOY_AXIS_MIN) / 2; // Currently equal to JOY_AXIS_CENTER
|
||||
|
||||
int joystick_read_raw_axis(int num_axes, int *axis);
|
||||
int joystick_read_raw_axis(short cid, int num_axes, int *axis);
|
||||
|
||||
float joy_down_time(int btn);
|
||||
float joy_down_time(const CC_bind &bind);
|
||||
|
||||
int joy_down_count(int btn, int reset_count);
|
||||
int joy_down_count(const CC_bind &bind, int reset_count);
|
||||
|
||||
int joy_down(int btn);
|
||||
int joy_down(const CC_bind &bind);
|
||||
|
||||
/**
|
||||
* Checks if the given joystick is present or not
|
||||
*/
|
||||
bool joy_present(short cid);
|
||||
|
||||
|
||||
#endif /* __JOY_H__ */
|
||||
@@ -110,8 +110,8 @@ int joy_ff_init()
|
||||
} else {
|
||||
ff_enabled = os_config_read_uint(nullptr, "EnableJoystickFF", 1) != 0;
|
||||
}
|
||||
|
||||
if ( !ff_enabled || io::joystick::getCurrentJoystick() == nullptr) {
|
||||
|
||||
if ( !ff_enabled || (io::joystick::getPlayerJoystick(CID_JOY0) == nullptr) ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -140,7 +140,7 @@ int joy_ff_init()
|
||||
}
|
||||
#endif
|
||||
|
||||
haptic = SDL_HapticOpenFromJoystick(io::joystick::getCurrentJoystick()->getDevice());
|
||||
haptic = SDL_HapticOpenFromJoystick(io::joystick::getPlayerJoystick(CID_JOY0)->getDevice());
|
||||
|
||||
if (haptic == NULL) {
|
||||
mprintf((" ERROR: Unable to open haptic joystick: %s\n", SDL_GetError()));
|
||||
|
||||
+2
-2
@@ -543,7 +543,7 @@ void key_mark( uint code, int state, uint latency )
|
||||
}
|
||||
|
||||
if (Script_system.IsActiveAction(CHA_KEYRELEASED)) {
|
||||
Script_system.SetHookVar("Key", 's', textify_scancode_universal(Current_key_down));
|
||||
Script_system.SetHookVar("Key", 's', textify_scancode(Current_key_down, true));
|
||||
Script_system.RunCondition(CHA_KEYRELEASED);
|
||||
Script_system.RemHookVar("Key");
|
||||
}
|
||||
@@ -574,7 +574,7 @@ void key_mark( uint code, int state, uint latency )
|
||||
|
||||
if (Script_system.IsActiveAction(CHA_KEYPRESSED)) {
|
||||
// We use the universal value here to keep the scripting interface consistent regardless of the current language
|
||||
Script_system.SetHookVar("Key", 's', textify_scancode_universal(Current_key_down));
|
||||
Script_system.SetHookVar("Key", 's', textify_scancode(Current_key_down, true));
|
||||
Script_system.RunCondition(CHA_KEYPRESSED);
|
||||
Script_system.RemHookVar("Key");
|
||||
}
|
||||
|
||||
+54
-12
@@ -14,6 +14,7 @@
|
||||
#include <windowsx.h>
|
||||
#endif
|
||||
|
||||
#include "controlconfig/controlsconfig.h"
|
||||
#include "graphics/2d.h"
|
||||
#include "io/mouse.h"
|
||||
#include "options/Option.h"
|
||||
@@ -300,8 +301,17 @@ void mouse_flush()
|
||||
SDL_UnlockMutex( mouse_lock );
|
||||
}
|
||||
|
||||
int mouse_down_count(int n, int reset_count)
|
||||
int mouse_down_count(const CC_bind &bind, int reset_count)
|
||||
{
|
||||
if (bind.get_cid() != CID_MOUSE) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
int n = 1 << bind.get_btn();
|
||||
return mouse_down_count(n, reset_count);
|
||||
}
|
||||
|
||||
int mouse_down_count(int n, int reset_count) {
|
||||
int tmp = 0;
|
||||
if ( !mouse_inited ) return 0;
|
||||
|
||||
@@ -356,8 +366,16 @@ int mouse_down_count(int n, int reset_count)
|
||||
//
|
||||
// parameters: n - button of mouse (see #define's in mouse.h)
|
||||
//
|
||||
int mouse_up_count(int n)
|
||||
int mouse_up_count(const CC_bind &bind)
|
||||
{
|
||||
if (bind.get_cid() != CID_MOUSE) {
|
||||
return 0;
|
||||
}
|
||||
int n = 1 << bind.get_btn();
|
||||
return mouse_up_count(n);
|
||||
}
|
||||
|
||||
int mouse_up_count(int n) {
|
||||
int tmp = 0;
|
||||
if ( !mouse_inited ) return 0;
|
||||
|
||||
@@ -403,8 +421,17 @@ int mouse_up_count(int n)
|
||||
|
||||
// returns 1 if mouse button btn is down, 0 otherwise
|
||||
|
||||
int mouse_down(int btn)
|
||||
int mouse_down(const CC_bind &bind)
|
||||
{
|
||||
if (bind.get_cid() != CID_MOUSE) {
|
||||
return 0;
|
||||
}
|
||||
int btn = 1 << bind.get_btn();
|
||||
|
||||
return mouse_down(btn);
|
||||
}
|
||||
|
||||
int mouse_down(int btn) {
|
||||
int tmp;
|
||||
if ( !mouse_inited ) return 0;
|
||||
|
||||
@@ -574,19 +601,17 @@ void mousewheel_motion(int x, int y, bool reversed) {
|
||||
}
|
||||
|
||||
void mousewheel_decay(int btn) {
|
||||
switch (btn) {
|
||||
case MOUSE_WHEEL_UP:
|
||||
if (btn & MOUSE_WHEEL_UP) {
|
||||
Mouse_wheel_y -= 1;
|
||||
break;
|
||||
case MOUSE_WHEEL_DOWN:
|
||||
}
|
||||
if (btn & MOUSE_WHEEL_DOWN) {
|
||||
Mouse_wheel_y += 1;
|
||||
break;
|
||||
case MOUSE_WHEEL_LEFT:
|
||||
}
|
||||
if (btn & MOUSE_WHEEL_LEFT) {
|
||||
Mouse_wheel_x -= 1;
|
||||
break;
|
||||
case MOUSE_WHEEL_RIGHT:
|
||||
}
|
||||
if (btn & MOUSE_WHEEL_RIGHT) {
|
||||
Mouse_wheel_x += 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (Mouse_wheel_x == 0) {
|
||||
@@ -596,3 +621,20 @@ void mousewheel_decay(int btn) {
|
||||
mouse_flags &= ~(MOUSE_WHEEL_RIGHT | MOUSE_WHEEL_LEFT);
|
||||
}
|
||||
}
|
||||
|
||||
short bit_distance(short x) {
|
||||
short i;
|
||||
const short max_dist = sizeof(short) * 8;
|
||||
|
||||
for (i = 0; i < max_dist; ++i, x >>= 1) {
|
||||
if (x & 0x01) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= max_dist) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
+20
-1
@@ -12,6 +12,7 @@
|
||||
#ifndef _MOUSE_H
|
||||
#define _MOUSE_H
|
||||
|
||||
#include "controlconfig/controlsconfig.h"
|
||||
#include "globalincs/pstypes.h"
|
||||
|
||||
extern int Mouse_sensitivity;
|
||||
@@ -58,11 +59,15 @@ extern void mouse_set_pos(int xpos,int ypos);
|
||||
/**
|
||||
* @brief Returns the number of times button n went from up to down since last call
|
||||
*/
|
||||
int mouse_down_count(const CC_bind &bind, int reset_count = 1);
|
||||
|
||||
int mouse_down_count(int n, int reset_count = 1);
|
||||
|
||||
/**
|
||||
* @brief Returns the number of times button n went from down to up since last call
|
||||
*/
|
||||
int mouse_up_count(const CC_bind &bind);
|
||||
|
||||
int mouse_up_count(int n);
|
||||
|
||||
/**
|
||||
@@ -70,7 +75,15 @@ int mouse_up_count(int n);
|
||||
*/
|
||||
void mouse_flush();
|
||||
|
||||
int mouse_down(int btn); // returns 1 if mouse button btn is down, 0 otherwise
|
||||
/**
|
||||
* Returns 1 if the given mouse button down, 0 otherwise
|
||||
*/
|
||||
int mouse_down(const CC_bind &bind);
|
||||
|
||||
/**
|
||||
* Returns 1 if any of the given mouse buttons are down. 0 otherwise
|
||||
*/
|
||||
int mouse_down(int btn);
|
||||
|
||||
void mouse_reset_deltas();
|
||||
void mouse_get_delta(int *dx = NULL, int *dy = NULL, int *dz = NULL);
|
||||
@@ -84,4 +97,10 @@ void mousewheel_motion(int x, int y, bool reversed);
|
||||
|
||||
extern void mouse_force_pos(int x, int y);
|
||||
|
||||
/**
|
||||
* Get the closest distance of a bit mask to 0
|
||||
* @note use this to translate mouse button masks to button id's
|
||||
*/
|
||||
short bit_distance(short x);
|
||||
|
||||
#endif
|
||||
@@ -59,7 +59,8 @@ bool *Lcl_unexpected_tstring_check = nullptr;
|
||||
// the english version (in the code) to a foreign version (in the table). Thus, if you
|
||||
// add a new string to the code, you must assign it a new index. Use the number below for
|
||||
// that index and increase the number below by one.
|
||||
#define XSTR_SIZE 1647
|
||||
// retail XSTR_SIZE = 1570
|
||||
#define XSTR_SIZE 1662
|
||||
|
||||
|
||||
// struct to allow for strings.tbl-determined x offset
|
||||
|
||||
@@ -1587,7 +1587,7 @@ void barracks_do_frame(float /*frametime*/)
|
||||
}
|
||||
|
||||
// check mouse over help
|
||||
if (mouse_down(MOUSE_LEFT_BUTTON)) {
|
||||
if (mouse_down(CC_bind(CID_MOUSE, MOUSE_LEFT_BUTTON))) {
|
||||
help_overlay_set_state(Barracks_overlay_id, gr_screen.res, 0);
|
||||
}
|
||||
|
||||
|
||||
@@ -636,8 +636,8 @@ char *translate_message_token(char *str)
|
||||
*/
|
||||
SCP_string message_translate_tokens(const char *text)
|
||||
{
|
||||
char temp[40], *ptr;
|
||||
const char *toke1, *toke2;
|
||||
char temp[40];
|
||||
const char *ptr, *toke1, *toke2;
|
||||
int r;
|
||||
SCP_string buf;
|
||||
|
||||
@@ -659,7 +659,7 @@ SCP_string message_translate_tokens(const char *text)
|
||||
strncpy(temp, text, toke2 - text); // isolate token into seperate buffer
|
||||
temp[toke2 - text] = 0; // null terminate string
|
||||
ptr = translate_key(temp); // try and translate key
|
||||
if (ptr) { // was key translated properly?
|
||||
if (ptr != nullptr) { // was key translated properly?
|
||||
if (!stricmp(ptr, NOX("none")) && (Training_bind_warning != Missiontime)) {
|
||||
if ( The_mission.game_type & MISSION_TYPE_TRAINING ) {
|
||||
r = popup(PF_TITLE_BIG | PF_TITLE_RED, 2, XSTR( "&Bind Control", 424), XSTR( "&Abort mission", 425),
|
||||
|
||||
@@ -465,6 +465,44 @@ namespace os
|
||||
#endif
|
||||
}
|
||||
|
||||
void Information(const char* filename, int line, const char* format, ...) {
|
||||
SCP_string msg;
|
||||
va_list args;
|
||||
|
||||
va_start(args, format);
|
||||
vsprintf(msg, format, args);
|
||||
va_end(args);
|
||||
|
||||
// Below is essentially a stripped down copy pasta of WarningImpl
|
||||
filename = clean_filename(filename);
|
||||
|
||||
// output to the debug log before anything else (so that we have a complete record)
|
||||
mprintf(("INFO: \"%s\" at %s:%d\n", msg.c_str(), filename, line));
|
||||
|
||||
// now go for the additional popup window, if we want it ...
|
||||
if (Cmdline_noninteractive || running_unittests) {
|
||||
return;
|
||||
}
|
||||
|
||||
SCP_stringstream boxMsgStream;
|
||||
boxMsgStream << "Information: " << msg << "\n";
|
||||
boxMsgStream << "File: " << filename << "\n";
|
||||
boxMsgStream << "Line: " << line << "\n";
|
||||
|
||||
set_clipboard_text(boxMsgStream.str().c_str());
|
||||
|
||||
boxMsgStream << "\n";
|
||||
|
||||
SCP_string boxMessage = truncateLines(boxMsgStream, Messagebox_lines);
|
||||
boxMessage += "\n[ This info is in the clipboard so you can paste it somewhere now ]\n";
|
||||
|
||||
gr_activate(0);
|
||||
|
||||
SDL_ShowSimpleMessageBox(SDL_MESSAGEBOX_INFORMATION, "Information", boxMessage.c_str(), getDialogParent());
|
||||
|
||||
gr_activate(1);
|
||||
}
|
||||
|
||||
void Message(MessageType type, const char* message, const char* title)
|
||||
{
|
||||
if (running_unittests) {
|
||||
|
||||
+19
-5
@@ -114,15 +114,29 @@ namespace os
|
||||
*/
|
||||
void ReleaseWarning(const char* filename, int line, SCP_FORMAT_STRING const char* format, ...) SCP_FORMAT_STRING_ARGS(3, 4);
|
||||
|
||||
/**
|
||||
* @brief Shows an information dialog
|
||||
* Displays a modal dialog with at most an OK button and some text. Use instead of ReleaseWarning if
|
||||
* debugging is not necassary and you just want to inform the player of something important and can't use
|
||||
* other UI. Use sparingly.
|
||||
*
|
||||
* @param filename The source code filename where this function was called
|
||||
* @param line The source code line number where this function was called
|
||||
* @param format The message to display
|
||||
*/
|
||||
void Information(const char* filename, int line, SCP_FORMAT_STRING const char* format, ...) SCP_FORMAT_STRING_ARGS(3, 4);
|
||||
|
||||
|
||||
void Message(MessageType type, const char* message, const char* title = NULL);
|
||||
}
|
||||
}
|
||||
|
||||
// Make these available in the global namespace for compatibility
|
||||
using os::dialogs::LuaError;
|
||||
using os::dialogs::Error;
|
||||
using os::dialogs::Warning;
|
||||
using os::dialogs::ReleaseWarning;
|
||||
using os::dialogs::WarningEx;
|
||||
using os::dialogs::LuaError; //NOLINT
|
||||
using os::dialogs::Error; //NOLINT
|
||||
using os::dialogs::Warning; //NOLINT
|
||||
using os::dialogs::ReleaseWarning; //NOLINT
|
||||
using os::dialogs::WarningEx; //NOLINT
|
||||
using os::dialogs::Information; //NOLINT
|
||||
|
||||
#endif // _OSAPI_DIALOGS_H
|
||||
@@ -274,11 +274,18 @@ namespace
|
||||
|
||||
}
|
||||
|
||||
// Reads a string from the INI file. If default is passed,
|
||||
// and the string isn't found, returns ptr to default otherwise
|
||||
// returns NULL; Copy the return value somewhere before
|
||||
// calling os_read_string again, because it might reuse the
|
||||
// same buffer.
|
||||
/**
|
||||
* @brief Reads a string from the INI file or registry.
|
||||
*
|
||||
* @param[in] section Section to look in
|
||||
* @param[in] name Key name of the value
|
||||
* @param[in] default_value Value to return, if not found or if OS_reg_inited is false
|
||||
*
|
||||
* @returns The value of the string with the given name in the given section if found
|
||||
*
|
||||
* @note Copy the return value somewhere before calling os_read_string again, because it might reuse the same
|
||||
* buffer
|
||||
*/
|
||||
static char registry_tmp_string_data[1024];
|
||||
const char * registry_read_string(const char *section, const char *name, const char *default_value)
|
||||
{
|
||||
@@ -288,7 +295,7 @@ namespace
|
||||
LONG lResult;
|
||||
|
||||
if (!Os_reg_inited) {
|
||||
return NULL;
|
||||
return default_value;
|
||||
}
|
||||
|
||||
HKEY useHKey = get_registry_keyname(keyname, section, Cmdline_alternate_registry_path);
|
||||
|
||||
@@ -10,6 +10,9 @@ pilot::BinaryFileHandler::~BinaryFileHandler() {
|
||||
_cfp = nullptr;
|
||||
}
|
||||
|
||||
void pilot::BinaryFileHandler::writeByte(const char*, std::int8_t value) {
|
||||
cfwrite_char(value, _cfp);
|
||||
}
|
||||
void pilot::BinaryFileHandler::writeUByte(const char*, std::uint8_t value) {
|
||||
cfwrite_ubyte(value, _cfp);
|
||||
}
|
||||
@@ -95,6 +98,9 @@ void pilot::BinaryFileHandler::endArrayWrite() {
|
||||
void pilot::BinaryFileHandler::flush() {
|
||||
cflush(_cfp);
|
||||
}
|
||||
std::int8_t pilot::BinaryFileHandler::readByte(const char*) {
|
||||
return cfread_char(_cfp);
|
||||
}
|
||||
std::uint8_t pilot::BinaryFileHandler::readUByte(const char*) {
|
||||
return cfread_ubyte(_cfp);
|
||||
}
|
||||
|
||||
@@ -30,6 +30,8 @@ class BinaryFileHandler: public FileHandler {
|
||||
|
||||
~BinaryFileHandler() override;
|
||||
|
||||
void writeByte(const char* name, std::int8_t value) override;
|
||||
|
||||
void writeUByte(const char* name, std::uint8_t value) override;
|
||||
|
||||
void writeShort(const char* name, std::int16_t value) override;
|
||||
@@ -59,6 +61,8 @@ class BinaryFileHandler: public FileHandler {
|
||||
|
||||
void flush() override;
|
||||
|
||||
std::int8_t readByte(const char* name) override;
|
||||
|
||||
std::uint8_t readUByte(const char* name) override;
|
||||
|
||||
std::int16_t readShort(const char* name) override;
|
||||
|
||||
@@ -44,6 +44,8 @@ class FileHandler {
|
||||
public:
|
||||
virtual ~FileHandler() {}
|
||||
|
||||
virtual void writeByte(const char* name, std::int8_t value) = 0;
|
||||
|
||||
virtual void writeUByte(const char* name, std::uint8_t value) = 0;
|
||||
|
||||
virtual void writeShort(const char* name, std::int16_t value) = 0;
|
||||
@@ -70,6 +72,8 @@ class FileHandler {
|
||||
|
||||
virtual void flush() = 0;
|
||||
|
||||
virtual std::int8_t readByte(const char* name) = 0;
|
||||
|
||||
virtual std::uint8_t readUByte(const char* name) = 0;
|
||||
|
||||
virtual std::int16_t readShort(const char* name) = 0;
|
||||
|
||||
@@ -87,6 +87,9 @@ void pilot::JSONFileHandler::popElement() {
|
||||
_elementStack.pop_back();
|
||||
_currentEl = _elementStack.back();
|
||||
}
|
||||
void pilot::JSONFileHandler::writeByte(const char* name, std::int8_t value) {
|
||||
writeInteger(name, value);
|
||||
}
|
||||
void pilot::JSONFileHandler::writeUByte(const char* name, std::uint8_t value) {
|
||||
writeInteger(name, value);
|
||||
}
|
||||
@@ -209,6 +212,9 @@ void pilot::JSONFileHandler::ensureExists(const char* name) {
|
||||
Error(LOCATION, "JSON reading requires a value with name '%s' but there is no such value!", name);
|
||||
}
|
||||
}
|
||||
std::int8_t pilot::JSONFileHandler::readByte(const char* name) {
|
||||
return (std::int8_t)readInteger(name);
|
||||
}
|
||||
std::uint8_t pilot::JSONFileHandler::readUByte(const char* name) {
|
||||
return (std::uint8_t)readInteger(name);
|
||||
}
|
||||
|
||||
@@ -38,6 +38,8 @@ class JSONFileHandler: public FileHandler {
|
||||
|
||||
~JSONFileHandler() override;
|
||||
|
||||
void writeByte(const char* name, std::int8_t value) override;
|
||||
|
||||
void writeUByte(const char* name, std::uint8_t value) override;
|
||||
|
||||
void writeShort(const char* name, std::int16_t value) override;
|
||||
@@ -66,6 +68,7 @@ class JSONFileHandler: public FileHandler {
|
||||
|
||||
void flush() override;
|
||||
|
||||
std::int8_t readByte(const char* name) override;
|
||||
|
||||
std::uint8_t readUByte(const char* name) override;
|
||||
|
||||
|
||||
+54
-34
@@ -1,4 +1,6 @@
|
||||
|
||||
#include "controlconfig/controlsconfig.h"
|
||||
#include "controlconfig/presets.h"
|
||||
#include "cutscene/cutscenes.h"
|
||||
#include "freespace.h"
|
||||
#include "gamesnd/eventmusic.h"
|
||||
@@ -1209,37 +1211,67 @@ void pilotfile::csg_write_settings()
|
||||
|
||||
void pilotfile::csg_read_controls()
|
||||
{
|
||||
int idx, list_size;
|
||||
short id1, id2, id3 __UNUSED;
|
||||
if (version < 7) {
|
||||
// Pre CSG-7 compatibility
|
||||
int idx, list_size;
|
||||
short id1, id2, id3 __UNUSED;
|
||||
|
||||
list_size = (int)cfread_ushort(cfp);
|
||||
list_size = (int)cfread_ushort(cfp);
|
||||
|
||||
for (idx = 0; idx < list_size; idx++) {
|
||||
id1 = cfread_short(cfp);
|
||||
id2 = cfread_short(cfp);
|
||||
id3 = cfread_short(cfp); // unused, at the moment
|
||||
for (idx = 0; idx < list_size; idx++) {
|
||||
id1 = cfread_short(cfp);
|
||||
id2 = cfread_short(cfp);
|
||||
id3 = cfread_short(cfp); // unused, at the moment
|
||||
|
||||
if (idx < CCFG_MAX) {
|
||||
Control_config[idx].take(CC_bind(CID_KEYBOARD, id1), 0);
|
||||
Control_config[idx].take(CC_bind(CID_JOY0, id2), 1);
|
||||
if (idx < CCFG_MAX) {
|
||||
Control_config[idx].take(CC_bind(CID_KEYBOARD, id1), 0);
|
||||
Control_config[idx].take(CC_bind(CID_JOY0, id2), 1);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that these bindings are in a preset.
|
||||
auto it = control_config_get_current_preset();
|
||||
if (it == Control_config_presets.end()) {
|
||||
// Not a preset, create one and its file
|
||||
CC_preset preset;
|
||||
preset.name = filename;
|
||||
|
||||
// strip off extension
|
||||
auto n = preset.name.find_last_of('.');
|
||||
preset.name.resize(n);
|
||||
|
||||
std::copy(Control_config.begin(), Control_config.end(), std::back_inserter(preset.bindings));
|
||||
Control_config_presets.push_back(preset);
|
||||
save_preset_file(preset, true);
|
||||
}
|
||||
return;
|
||||
|
||||
} else {
|
||||
// >= CSG-7
|
||||
char buf[MAX_FILENAME_LEN];
|
||||
memset(buf, '\0', sizeof(buf));
|
||||
cfread_string(buf, sizeof(buf), cfp);
|
||||
|
||||
auto it = std::find_if(Control_config_presets.begin(), Control_config_presets.end(),
|
||||
[buf](const CC_preset& preset) { return preset.name == buf; });
|
||||
|
||||
if (it == Control_config_presets.end()) {
|
||||
// Couldn't find the preset, use defaults
|
||||
it = Control_config_presets.begin();
|
||||
}
|
||||
|
||||
control_config_use_preset(*it);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void pilotfile::csg_write_controls()
|
||||
{
|
||||
int idx;
|
||||
auto it = control_config_get_current_preset();
|
||||
|
||||
startSection(Section::Controls);
|
||||
|
||||
cfwrite_ushort(CCFG_MAX, cfp);
|
||||
|
||||
for (idx = 0; idx < CCFG_MAX; idx++) {
|
||||
cfwrite_short(Control_config[idx].get_btn(CID_KEYBOARD), cfp);
|
||||
cfwrite_short(Control_config[idx].get_btn(CID_JOY0), cfp);
|
||||
// placeholder? for future mouse_id?
|
||||
cfwrite_short(-1, cfp);
|
||||
}
|
||||
cfwrite_string(it->name.c_str(), cfp);
|
||||
|
||||
endSection();
|
||||
}
|
||||
@@ -1374,11 +1406,6 @@ void pilotfile::csg_read_container(sexp_container& container)
|
||||
}
|
||||
}
|
||||
|
||||
bool pilotfile::csg_has_persistent_containers()
|
||||
{
|
||||
return !Campaign.persistent_containers.empty() || !Campaign.red_alert_containers.empty();
|
||||
}
|
||||
|
||||
void pilotfile::csg_write_containers()
|
||||
{
|
||||
startSection(Section::Containers);
|
||||
@@ -1728,10 +1755,7 @@ bool pilotfile::save_savefile()
|
||||
|
||||
// header and version
|
||||
cfwrite_int(CSG_FILE_ID, cfp);
|
||||
static_assert(CSG_VERSION == PRE_CONTAINERS_CSG_VERSION + 1,
|
||||
"CSG_VERSION shouldn't be bumped beyond containers without removing compatibility fix");
|
||||
const ubyte file_version = csg_has_persistent_containers() ? CSG_VERSION : PRE_CONTAINERS_CSG_VERSION;
|
||||
cfwrite_ubyte(file_version, cfp);
|
||||
cfwrite_ubyte(CSG_VERSION, cfp);
|
||||
|
||||
mprintf(("CSG => Saving '%s' with version %d...\n", filename.c_str(), (int)CSG_VERSION));
|
||||
|
||||
@@ -1764,12 +1788,8 @@ bool pilotfile::save_savefile()
|
||||
csg_write_cutscenes();
|
||||
mprintf(("CSG => Saving: Last Missions...\n"));
|
||||
csg_write_lastmissions();
|
||||
if (csg_has_persistent_containers()) {
|
||||
mprintf(("CSG => Saving: Containers...\n"));
|
||||
csg_write_containers();
|
||||
} else {
|
||||
mprintf(("CSG => No containers to save.\n"));
|
||||
}
|
||||
mprintf(("CSG => Saving: Containers...\n"));
|
||||
csg_write_containers();
|
||||
|
||||
// Done!
|
||||
mprintf(("CSG => Saving complete!\n"));
|
||||
|
||||
@@ -23,18 +23,18 @@ static const unsigned int CSG_FILE_ID = 0x5f475343; // "CSG_" in file
|
||||
// 1 - Adding support for the player is multi flag
|
||||
// 2 - Add language in use when pilot was created
|
||||
// (due to intel entries using translated text as the primary key)
|
||||
static const ubyte PRE_CONTAINERS_PLR_VERSION = 2;
|
||||
// 3 - Add SEXP containers
|
||||
static const ubyte PLR_VERSION = 3;
|
||||
// 4 Controls are removed, and instead a preset name is saved/loaded
|
||||
static const ubyte PLR_VERSION = 4;
|
||||
// 0 - initial version
|
||||
// 1 - re-add recent missions
|
||||
// 2 - separate single/multi squad name & pic
|
||||
// 3 - remove separate detail settings for campaigns
|
||||
// 4 - add CPV rollback for Red Alert missions
|
||||
// 5 - save rank to flags for quick access
|
||||
static const ubyte PRE_CONTAINERS_CSG_VERSION = 5;
|
||||
// 6 - add SEXP containers
|
||||
static const ubyte CSG_VERSION = 6;
|
||||
// 7 - Controls are removed, and instead a preset name is saved/loaded.
|
||||
static const ubyte CSG_VERSION = 7;
|
||||
|
||||
typedef struct index_list_t {
|
||||
SCP_string name;
|
||||
@@ -155,7 +155,6 @@ class pilotfile {
|
||||
void plr_write_stats_multi();
|
||||
void plr_write_multiplayer();
|
||||
void plr_write_variables();
|
||||
bool plr_has_persistent_containers() const;
|
||||
void plr_write_containers();
|
||||
void plr_write_hud();
|
||||
void plr_write_controls();
|
||||
@@ -195,7 +194,6 @@ class pilotfile {
|
||||
void csg_write_controls();
|
||||
void csg_write_cutscenes();
|
||||
void csg_write_lastmissions();
|
||||
static bool csg_has_persistent_containers();
|
||||
void csg_write_containers();
|
||||
void csg_write_container(const sexp_container& container);
|
||||
|
||||
|
||||
+77
-61
@@ -1,4 +1,6 @@
|
||||
|
||||
#include "controlconfig/controlsconfig.h"
|
||||
#include "controlconfig/presets.h"
|
||||
#include "freespace.h"
|
||||
#include "gamesnd/eventmusic.h"
|
||||
#include "hud/hudconfig.h"
|
||||
@@ -346,11 +348,6 @@ void pilotfile::plr_read_containers()
|
||||
handler->endArrayRead();
|
||||
}
|
||||
|
||||
bool pilotfile::plr_has_persistent_containers() const
|
||||
{
|
||||
return !p->containers.empty();
|
||||
}
|
||||
|
||||
void pilotfile::plr_write_containers()
|
||||
{
|
||||
handler->startSectionWrite(Section::Containers);
|
||||
@@ -651,65 +648,91 @@ void pilotfile::plr_write_stats_multi()
|
||||
|
||||
void pilotfile::plr_read_controls()
|
||||
{
|
||||
short id1, id2;
|
||||
int axi, inv;
|
||||
if (version < 4) {
|
||||
// PLR < 4
|
||||
short id1, id2;
|
||||
int axi, inv;
|
||||
|
||||
auto list_size = handler->startArrayRead("controls", true);
|
||||
for (size_t idx = 0; idx < list_size; idx++, handler->nextArraySection()) {
|
||||
id1 = handler->readShort("key");
|
||||
id2 = handler->readShort("joystick");
|
||||
handler->readShort("mouse"); // unused, at the moment
|
||||
// Set up preset name, we'll populate the rest of the preset's data later
|
||||
CC_preset preset;
|
||||
preset.name = filename;
|
||||
|
||||
if (idx < Control_config.size()) {
|
||||
Control_config[idx].take(CC_bind(CID_KEYBOARD, id1), 0);
|
||||
Control_config[idx].take(CC_bind(CID_JOY0, id2), 1);
|
||||
// strip off extension
|
||||
auto n = preset.name.find_last_of('.');
|
||||
preset.name.resize(n);
|
||||
|
||||
// Load in the bindings to Control_config
|
||||
// ...First the digital controls
|
||||
auto list_size = handler->startArrayRead("controls", true);
|
||||
for (size_t idx = 0; idx < list_size; idx++, handler->nextArraySection()) {
|
||||
id1 = handler->readShort("key");
|
||||
id2 = handler->readShort("joystick");
|
||||
handler->readShort("mouse");
|
||||
|
||||
if (idx < Control_config.size()) {
|
||||
// Force the bindings into Control_config
|
||||
Control_config[idx].take(CC_bind(CID_KEYBOARD, id1), 0);
|
||||
Control_config[idx].take(CC_bind(CID_JOY0, id2), 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
handler->endArrayRead();
|
||||
handler->endArrayRead();
|
||||
|
||||
auto list_axis = handler->startArrayRead("axes");
|
||||
for (size_t idx = 0; idx < list_axis; idx++, handler->nextArraySection()) {
|
||||
axi = handler->readInt("axis_map");
|
||||
inv = handler->readInt("invert_axis");
|
||||
// ...Then the analog controls
|
||||
auto list_axis = handler->startArrayRead("axes");
|
||||
for (size_t idx = 0; idx < list_axis; idx++, handler->nextArraySection()) {
|
||||
axi = handler->readInt("axis_map");
|
||||
inv = handler->readInt("invert_axis");
|
||||
|
||||
if (idx < NUM_JOY_AXIS_ACTIONS) {
|
||||
Axis_map_to[idx] = axi;
|
||||
Invert_axis[idx] = inv;
|
||||
if (idx < NUM_JOY_AXIS_ACTIONS) {
|
||||
Control_config[idx + JOY_AXIS_BEGIN].take(CC_bind(CID_JOY0, static_cast<short>(axi), CCF_AXIS), 0);
|
||||
Control_config[idx + JOY_AXIS_BEGIN].invert(inv != 0);
|
||||
}
|
||||
}
|
||||
handler->endArrayRead();
|
||||
|
||||
// Check that these bindings are in a preset. If it is not, create a new preset file
|
||||
auto it = control_config_get_current_preset();
|
||||
if (it == Control_config_presets.end()) {
|
||||
std::copy(Control_config.begin(), Control_config.end(), std::back_inserter(preset.bindings));
|
||||
Control_config_presets.push_back(preset);
|
||||
|
||||
// Try to save the preset file
|
||||
if (save_preset_file(preset, false)) {
|
||||
Information(LOCATION, "Successfully converted playerfile to v4. Please rebind your mouse controls, if any.");
|
||||
} else {
|
||||
Warning(LOCATION, "Could not save controls preset file (%s) when converting playerfile to v3.", preset.name.c_str());
|
||||
}
|
||||
}
|
||||
return;
|
||||
|
||||
} else {
|
||||
// read PLR >= 4
|
||||
SCP_string buf = handler->readString("preset");
|
||||
|
||||
auto it = std::find_if(Control_config_presets.begin(), Control_config_presets.end(),
|
||||
[buf](const CC_preset& preset) { return preset.name == buf; });
|
||||
|
||||
if (it == Control_config_presets.end()) {
|
||||
// Couldn't find the preset, use defaults
|
||||
ReleaseWarning(LOCATION, "Could not find preset %s, using defaults\n", buf.c_str());
|
||||
it = Control_config_presets.begin();
|
||||
}
|
||||
|
||||
control_config_use_preset(*it);
|
||||
return;
|
||||
}
|
||||
handler->endArrayRead();
|
||||
}
|
||||
|
||||
void pilotfile::plr_write_controls()
|
||||
{
|
||||
handler->startSectionWrite(Section::Controls);
|
||||
|
||||
// As of PLR v4, control bindings are saved outside of the PLR file into PST files.
|
||||
// We now only save the currently selected preset in the PLR file itself.
|
||||
auto it = control_config_get_current_preset();
|
||||
handler->writeString("preset", it->name.c_str());
|
||||
|
||||
// For some unknown reason, the old code used a short for the array length here...
|
||||
handler->startArrayWrite("controls", Control_config.size(), true);
|
||||
for (auto &item : Control_config) {
|
||||
handler->startSectionWrite(Section::Unnamed);
|
||||
|
||||
handler->writeShort("key", item.get_btn(CID_KEYBOARD));
|
||||
handler->writeShort("joystick", item.get_btn(CID_JOY0));
|
||||
// placeholder? for future mouse_id?
|
||||
handler->writeShort("mouse", -1);
|
||||
|
||||
handler->endSectionWrite();
|
||||
}
|
||||
handler->endArrayWrite();
|
||||
|
||||
handler->startArrayWrite("axes", NUM_JOY_AXIS_ACTIONS);
|
||||
for (size_t idx = 0; idx < NUM_JOY_AXIS_ACTIONS; idx++) {
|
||||
handler->startSectionWrite(Section::Unnamed);
|
||||
|
||||
handler->writeInt("axis_map", Axis_map_to[idx]);
|
||||
handler->writeInt("invert_axis", Invert_axis[idx]);
|
||||
|
||||
handler->endSectionWrite();
|
||||
}
|
||||
handler->endArrayWrite();
|
||||
|
||||
handler->endSectionWrite();
|
||||
handler->endSectionWrite(); // Section::controls
|
||||
}
|
||||
|
||||
void pilotfile::plr_read_settings()
|
||||
@@ -1081,10 +1104,7 @@ bool pilotfile::save_player(player *_p)
|
||||
|
||||
// header and version
|
||||
handler->writeInt("signature", PLR_FILE_ID);
|
||||
static_assert(PLR_VERSION == PRE_CONTAINERS_PLR_VERSION + 1,
|
||||
"PLR_VERSION shouldn't be bumped beyond containers without removing compatibility fix");
|
||||
const ubyte file_version = plr_has_persistent_containers() ? PLR_VERSION : PRE_CONTAINERS_PLR_VERSION;
|
||||
handler->writeUByte("version", file_version);
|
||||
handler->writeUByte("version", PLR_VERSION);
|
||||
|
||||
mprintf(("PLR => Saving '%s' with version %d...\n", filename.c_str(), (int)PLR_VERSION));
|
||||
|
||||
@@ -1105,12 +1125,8 @@ bool pilotfile::save_player(player *_p)
|
||||
plr_write_hud();
|
||||
mprintf(("PLR => Saving: Variables...\n"));
|
||||
plr_write_variables();
|
||||
if (plr_has_persistent_containers()) {
|
||||
mprintf(("PLR => Saving: Containers...\n"));
|
||||
plr_write_containers();
|
||||
} else {
|
||||
mprintf(("PLR => No containers to save.\n"));
|
||||
}
|
||||
mprintf(("PLR => Saving: Containers...\n"));
|
||||
plr_write_containers();
|
||||
mprintf(("PLR => Saving: Multiplayer...\n"));
|
||||
plr_write_multiplayer();
|
||||
mprintf(("PLR => Saving: Controls...\n"));
|
||||
|
||||
@@ -88,7 +88,7 @@ void init_new_pilot(player *p, int reset)
|
||||
if (reset) {
|
||||
hud_set_default_hud_config(p); // use a default hud config
|
||||
|
||||
control_config_reset_defaults(); // get a default keyboard config
|
||||
control_config_use_preset(Control_config_presets[0]); // get a default keyboard config
|
||||
player_set_pilot_defaults(p); // set up any player struct defaults
|
||||
|
||||
// set the default detail level based on tabling rather than the above method
|
||||
|
||||
@@ -79,13 +79,6 @@ float Minimum_player_warpout_time = 3.0f;
|
||||
DCF_BOOL( show_killer_weapon, Show_killer_weapon )
|
||||
#endif
|
||||
|
||||
/**
|
||||
* @brief Reads and combines the axes from the player's joystick and mouse.
|
||||
* @param[out] axis Pointer to axes array
|
||||
* @param[in] frame_time The frame time when this was called
|
||||
*/
|
||||
void playercontrol_read_stick(int *axis, float frame_time);
|
||||
|
||||
/**
|
||||
* @brief Slew angles chase towards a value like they're on a spring.
|
||||
*
|
||||
@@ -151,7 +144,7 @@ void view_modify(angles *ma, angles *da, float max_p, float max_h)
|
||||
float u = 0;
|
||||
|
||||
// Analog inputs
|
||||
int axis[NUM_JOY_AXIS_ACTIONS];
|
||||
int axis[Action::NUM_VALUES];
|
||||
float h = 0.0f;
|
||||
float p = 0.0f;
|
||||
|
||||
@@ -223,11 +216,11 @@ void view_modify(angles *ma, angles *da, float max_p, float max_h)
|
||||
t = (check_control_timef(YAW_RIGHT) - check_control_timef(YAW_LEFT));
|
||||
u = (check_control_timef(PITCH_FORWARD) - check_control_timef(PITCH_BACK));
|
||||
|
||||
playercontrol_read_stick(axis, flRealframetime);
|
||||
control_get_axes_readings(axis, flRealframetime);
|
||||
|
||||
// Does the same thing as t and u but for the joystick input
|
||||
h = f2fl(axis[JOY_HEADING_AXIS]);
|
||||
p = -f2fl(axis[JOY_PITCH_AXIS]);
|
||||
h = f2fl(axis[Action::HEADING]);
|
||||
p = -f2fl(axis[Action::PITCH]);
|
||||
}
|
||||
|
||||
// Combine Analog and Digital slew commands
|
||||
@@ -543,74 +536,10 @@ void player_control_reset_ci( control_info *ci )
|
||||
ci->forward_cruise_percent = oldspeed;
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Reads the joystick Pitch, Bank, and Heading axes.
|
||||
*
|
||||
* @param[out] axis The axis array to store the scaled joystick position. (Must have a size of NUM_JOY_AXIS_ACTIONS)
|
||||
* @param[in] frame_time The frame time at which this function is called.
|
||||
*
|
||||
* @details This is its own function because we only want to read it at a certain rate, since it takes time.
|
||||
*/
|
||||
void playercontrol_read_stick(int *axis, float frame_time)
|
||||
{
|
||||
int i;
|
||||
|
||||
// Read the stick
|
||||
control_get_axes_readings(&axis[0], &axis[1], &axis[2], &axis[3], &axis[4]);
|
||||
|
||||
if (Use_mouse_to_fly) {
|
||||
int dx, dy, dz;
|
||||
float factor;
|
||||
|
||||
factor = (float) Mouse_sensitivity + 1.77f;
|
||||
factor = factor * factor / frame_time / 0.6f;
|
||||
|
||||
mouse_get_delta(&dx, &dy, &dz);
|
||||
int x_axis, y_axis, z_axis;
|
||||
x_axis = y_axis = z_axis = -1;
|
||||
|
||||
for (i = 0; i < NUM_JOY_AXIS_ACTIONS; i++) {
|
||||
switch(Axis_map_to[i])
|
||||
{
|
||||
case JOY_X_AXIS:
|
||||
x_axis = i;
|
||||
break;
|
||||
case JOY_Y_AXIS:
|
||||
y_axis = i;
|
||||
break;
|
||||
case JOY_Z_AXIS:
|
||||
z_axis = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (x_axis >= 0) {
|
||||
if (Invert_axis[x_axis]) {
|
||||
dx = -dx;
|
||||
}
|
||||
axis[x_axis] += (int) ((float) dx * factor);
|
||||
}
|
||||
|
||||
if (y_axis >= 0) {
|
||||
if (Invert_axis[y_axis]) {
|
||||
dy = -dy;
|
||||
}
|
||||
axis[y_axis] += (int) ((float) dy * factor);
|
||||
}
|
||||
|
||||
if (z_axis >= 0) {
|
||||
if (Invert_axis[z_axis]) {
|
||||
dz = -dz;
|
||||
}
|
||||
axis[z_axis] += (int) ((float) dz * factor);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void read_keyboard_controls( control_info * ci, float frame_time, physics_info *pi )
|
||||
{
|
||||
float kh=0.0f, scaled, newspeed, delta, oldspeed;
|
||||
int axis[NUM_JOY_AXIS_ACTIONS];
|
||||
int axis[Action::NUM_VALUES];
|
||||
static int afterburner_last = 0;
|
||||
static float analog_throttle_last = 9e9f;
|
||||
static int override_analog_throttle = 0;
|
||||
@@ -805,41 +734,36 @@ void read_keyboard_controls( control_info * ci, float frame_time, physics_info *
|
||||
// code to read joystick axis for pitch/heading. Code to read joystick buttons
|
||||
// for bank.
|
||||
if ( !(Game_mode & GM_DEAD) ) {
|
||||
playercontrol_read_stick(axis, frame_time);
|
||||
control_get_axes_readings(axis, frame_time);
|
||||
} else {
|
||||
axis[0] = axis[1] = axis[2] = axis[3] = axis[4] = 0;
|
||||
}
|
||||
|
||||
if (Viewer_mode & VM_CAMERA_LOCKED) {
|
||||
// Player has control of the ship
|
||||
if (Axis_map_to[JOY_HEADING_AXIS] >= 0) {
|
||||
// check the heading on the x axis
|
||||
if ( check_control(BANK_WHEN_PRESSED) ) {
|
||||
delta = f2fl( axis[JOY_HEADING_AXIS] );
|
||||
if ( (delta > 0.05f) || (delta < -0.05f) ) {
|
||||
ci->bank -= delta;
|
||||
}
|
||||
} else {
|
||||
ci->heading += f2fl( axis[JOY_HEADING_AXIS] );
|
||||
// Set heading
|
||||
if ( check_control(BANK_WHEN_PRESSED) ) {
|
||||
delta = f2fl( axis[Action::HEADING] );
|
||||
if ( (delta > 0.05f) || (delta < -0.05f) ) {
|
||||
ci->bank -= delta;
|
||||
}
|
||||
} else {
|
||||
ci->heading += f2fl( axis[Action::HEADING] );
|
||||
}
|
||||
// check the pitch on the y axis
|
||||
if (Axis_map_to[JOY_PITCH_AXIS] >= 0) {
|
||||
ci->pitch -= f2fl( axis[JOY_PITCH_AXIS] );
|
||||
}
|
||||
// Set pitch
|
||||
ci->pitch -= f2fl( axis[Action::PITCH] );
|
||||
|
||||
} else {
|
||||
// Player has control of the camera
|
||||
ci->pitch = 0.0f;
|
||||
ci->heading = 0.0f;
|
||||
}
|
||||
|
||||
if (Axis_map_to[JOY_BANK_AXIS] >= 0) {
|
||||
ci->bank -= f2fl( axis[JOY_BANK_AXIS] ) * 1.5f;
|
||||
}
|
||||
// Set bank
|
||||
ci->bank -= f2fl( axis[Action::BANK] ) * 1.5f;
|
||||
|
||||
// axis 2 is for throttle
|
||||
if (Axis_map_to[JOY_ABS_THROTTLE_AXIS] >= 0) {
|
||||
scaled = (float) axis[JOY_ABS_THROTTLE_AXIS] * 1.2f / (float) F1_0 - 0.1f; // convert to -0.1 - 1.1 range
|
||||
if (!Control_config[JOY_ABS_THROTTLE_AXIS].empty()) {
|
||||
scaled = (float) axis[Action::ABS_THROTTLE] * 1.2f / (float) F1_0 - 0.1f; // convert to -0.1 - 1.1 range
|
||||
oldspeed = ci->forward_cruise_percent;
|
||||
|
||||
newspeed = (1.0f - scaled) * 100.0f;
|
||||
@@ -852,8 +776,8 @@ void read_keyboard_controls( control_info * ci, float frame_time, physics_info *
|
||||
}
|
||||
}
|
||||
|
||||
if (Axis_map_to[JOY_REL_THROTTLE_AXIS] >= 0)
|
||||
ci->forward_cruise_percent += f2fl(axis[JOY_REL_THROTTLE_AXIS]) * 100.0f * frame_time;
|
||||
if (!Control_config[JOY_REL_THROTTLE_AXIS].empty())
|
||||
ci->forward_cruise_percent += f2fl(axis[Action::REL_THROTTLE]) * 100.0f * frame_time;
|
||||
|
||||
CLAMP(ci->forward_cruise_percent, 0, 100);
|
||||
|
||||
|
||||
@@ -529,7 +529,7 @@ int popup_init(popup_info *pi, int flags)
|
||||
|
||||
// if this is an input popup, create and center the popup
|
||||
if(flags & PF_INPUT){
|
||||
Popup_input.create(&Popup_window, Popup_text_coords[gr_screen.res][0], pbg->coords[1] + Popup_input_y_offset[gr_screen.res], Popup_text_coords[gr_screen.res][2], pi->max_input_text_len, "", UI_INPUTBOX_FLAG_INVIS | UI_INPUTBOX_FLAG_ESC_CLR | UI_INPUTBOX_FLAG_ESC_FOC | UI_INPUTBOX_FLAG_KEYTHRU | UI_INPUTBOX_FLAG_TEXT_CEN);
|
||||
Popup_input.create(&Popup_window, Popup_text_coords[gr_screen.res][0], pbg->coords[1] + Popup_input_y_offset[gr_screen.res], Popup_text_coords[gr_screen.res][2], pi->max_input_text_len, pi->input_text, UI_INPUTBOX_FLAG_INVIS | UI_INPUTBOX_FLAG_ESC_CLR | UI_INPUTBOX_FLAG_ESC_FOC | UI_INPUTBOX_FLAG_KEYTHRU | UI_INPUTBOX_FLAG_TEXT_CEN);
|
||||
Popup_input.set_focus();
|
||||
}
|
||||
|
||||
@@ -1102,7 +1102,7 @@ int popup_till_condition(int (*condition)(), ...)
|
||||
}
|
||||
|
||||
// popup to return the value from an input box
|
||||
char *popup_input(int flags, const char *caption, int max_output_len)
|
||||
char *popup_input(int flags, const char *caption, int max_output_len, const char *default_input)
|
||||
{
|
||||
if ( Popup_is_active ) {
|
||||
Int3(); // should never happen
|
||||
@@ -1129,8 +1129,9 @@ char *popup_input(int flags, const char *caption, int max_output_len)
|
||||
Popup_info.max_input_text_len = max_output_len;
|
||||
}
|
||||
|
||||
// zero the popup input text
|
||||
// zero the popup input text and set default, if provided
|
||||
memset(Popup_info.input_text, 0, POPUP_INPUT_MAX_CHARS);
|
||||
strcpy_s(Popup_info.input_text, default_input);
|
||||
|
||||
gamesnd_play_iface(InterfaceSounds::POPUP_APPEAR); // play sound when popup appears
|
||||
|
||||
|
||||
+8
-2
@@ -94,8 +94,14 @@ int popup(int flags, int nchoices, ... );
|
||||
// rval = popup_till_condition( condition_function, "Cancel", "Checking to see if %s is an idiot.", pl->callsign);
|
||||
int popup_till_condition( int(*condition)() , ...);
|
||||
|
||||
// popup to return the value from an input box
|
||||
char *popup_input(int flags, const char *caption, int max_output_len = -1);
|
||||
/*!
|
||||
* Popup that prompts the user for input
|
||||
* @param[in] flags Apperance and behavior flags. See the PF_* defines in popup.h
|
||||
* @param[in] caption Caption text of the prompt
|
||||
* @param[in] max_output_len (Optional) Maximum string length of the input text
|
||||
* @param[in] default_input (Optional) Default input text
|
||||
*/
|
||||
char *popup_input(int flags, const char *caption, int max_output_len = -1, const char *default_input = "");
|
||||
|
||||
int popup_active();
|
||||
|
||||
|
||||
@@ -80,52 +80,117 @@ ADE_FUNC(isMouseButtonDown,
|
||||
return ade_set_args(L, "b", rtn);
|
||||
}
|
||||
|
||||
static int AxisInverted_sub(Joy_axis_index joy_axis, lua_State* L)
|
||||
static int AxisActionInverted_sub(int AxisAction, lua_State* L)
|
||||
{
|
||||
Assertion(joy_axis == JOY_X_AXIS || joy_axis == JOY_Y_AXIS || joy_axis == JOY_Z_AXIS, "Only the X, Y, and Z axes are supported for now.");
|
||||
// z64: If so desired, it may be possible to have two inversion states, one set by the player in the config menu,
|
||||
// and one set by scripting. Essentially, the CCI will have its own inversion flag in addition to the inversion
|
||||
// flags to both bindings. Thus, its possible for the player to invert one binding while keeping the other
|
||||
// binding normal instead of having the script try to set each bind's inversion state individually.
|
||||
|
||||
bool b;
|
||||
if (!ade_get_args(L, "*|b", &b))
|
||||
return ade_set_error(L, "b", false);
|
||||
|
||||
int axis = -1;
|
||||
for (int i = 0; i < NUM_JOY_AXIS_ACTIONS; ++i)
|
||||
{
|
||||
if (Axis_map_to[i] == joy_axis)
|
||||
{
|
||||
axis = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((AxisAction < JOY_HEADING_AXIS) || (AxisAction > JOY_REL_THROTTLE_AXIS))
|
||||
return ade_set_error(L, "b", false); // invalid IoActionId
|
||||
|
||||
if (axis < 0)
|
||||
{
|
||||
LuaError(L, "Control axis %d not found!", static_cast<int>(joy_axis));
|
||||
return ADE_RETURN_NIL;
|
||||
}
|
||||
auto& item = Control_config[AxisAction];
|
||||
|
||||
if (ADE_SETTING_VAR)
|
||||
Invert_axis[axis] = b ? 1 : 0;
|
||||
item.invert(b);
|
||||
|
||||
if (Invert_axis[axis] != 0)
|
||||
if (item.is_inverted())
|
||||
return ADE_RETURN_TRUE;
|
||||
else
|
||||
return ADE_RETURN_FALSE;
|
||||
}
|
||||
|
||||
ADE_VIRTVAR(XAxisInverted, l_Mouse, "boolean inverted", "Gets or sets whether the X-axis is inverted", "boolean", "True/false")
|
||||
ADE_VIRTVAR_DEPRECATED(XAxisInverted, l_Mouse, "boolean inverted", "Gets or sets whether the X-axis action is inverted", "boolean", "True/false", gameversion::version(21, 6), "Deprecated in favor of HeadingAxisInverted")
|
||||
{
|
||||
return AxisInverted_sub(JOY_X_AXIS, L);
|
||||
return AxisActionInverted_sub(JOY_HEADING_AXIS, L);
|
||||
}
|
||||
|
||||
ADE_VIRTVAR(YAxisInverted, l_Mouse, "boolean inverted", "Gets or sets whether the Y-axis is inverted", "boolean", "True/false")
|
||||
ADE_VIRTVAR_DEPRECATED(YAxisInverted, l_Mouse, "boolean inverted", "Gets or sets whether the Y-axis action is inverted", "boolean", "True/false", gameversion::version(21, 6), "Deprecated in favor of PitchAxisInverted")
|
||||
{
|
||||
return AxisInverted_sub(JOY_Y_AXIS, L);
|
||||
return AxisActionInverted_sub(JOY_PITCH_AXIS, L);
|
||||
}
|
||||
|
||||
ADE_VIRTVAR(ZAxisInverted, l_Mouse, "boolean inverted", "Gets or sets whether the Z-axis is inverted", "boolean", "True/false")
|
||||
ADE_VIRTVAR_DEPRECATED(ZAxisInverted, l_Mouse, "boolean inverted", "Gets or sets whether the Z-axis action is inverted", "boolean", "True/false", gameversion::version(21, 6), "Deprecated in favor of BankAxisInverted")
|
||||
{
|
||||
return AxisInverted_sub(JOY_Z_AXIS, L);
|
||||
return AxisActionInverted_sub(JOY_BANK_AXIS, L);
|
||||
}
|
||||
|
||||
ADE_VIRTVAR(HeadingAxisInverted, l_Mouse, "boolean inverted", "Gets or sets whether the heading axis action is inverted", "boolean", "True/false")
|
||||
{
|
||||
return AxisActionInverted_sub(JOY_HEADING_AXIS, L);
|
||||
}
|
||||
|
||||
ADE_VIRTVAR(PitchAxisInverted, l_Mouse, "boolean inverted", "Gets or sets whether the pitch axis action is inverted", "boolean", "True/false")
|
||||
{
|
||||
return AxisActionInverted_sub(JOY_PITCH_AXIS, L);
|
||||
}
|
||||
|
||||
ADE_VIRTVAR(BankAxisInverted, l_Mouse, "boolean inverted", "Gets or sets whether the bank axis action is inverted", "boolean", "True/false")
|
||||
{
|
||||
return AxisActionInverted_sub(JOY_BANK_AXIS, L);
|
||||
}
|
||||
|
||||
ADE_VIRTVAR(AbsoluteThrottleAxisInverted, l_Mouse, "boolean inverted", "Gets or sets whether the absolute throttle axis action is inverted", "boolean", "True/false")
|
||||
{
|
||||
return AxisActionInverted_sub(JOY_ABS_THROTTLE_AXIS, L);
|
||||
}
|
||||
|
||||
ADE_VIRTVAR(RelativeThrottleAxisInverted, l_Mouse, "boolean inverted", "Gets or sets whether the relative throttle axis action is inverted", "boolean", "True/false")
|
||||
{
|
||||
return AxisActionInverted_sub(JOY_REL_THROTTLE_AXIS, L);
|
||||
}
|
||||
|
||||
ADE_FUNC(AxisInverted, l_Mouse, "number cid, number axis, boolean inverted", "Gets or sets the given Joystick or Mouse axis inversion state. Mouse cid = -1, Joystick cid = [0, 3]", "boolean", "True/false")
|
||||
{
|
||||
int joy = CID_NONE;
|
||||
int axis = -1;
|
||||
bool inverted = false;
|
||||
int n = ade_get_args(L, "ii|b", &joy, &axis, &inverted);
|
||||
|
||||
if (n == 0)
|
||||
return ade_set_error(L, "b", false); // no arguments passed
|
||||
|
||||
if ((joy < CID_JOY0) || (CID_JOY_MAX <= joy) || (joy != CID_MOUSE))
|
||||
return ade_set_error(L, "b", false); // Invalid cid
|
||||
|
||||
if ((axis < 0) || (axis > JOY_NUM_AXES))
|
||||
return ade_set_error(L, "b", false); // invalid axis
|
||||
|
||||
// Find the binding that has the given joy.
|
||||
// This is a bit obtuse since inversion is on the config side instead of on the joystick side
|
||||
CC_bind *A = nullptr;
|
||||
for (int i = JOY_HEADING_AXIS; i <= JOY_REL_THROTTLE_AXIS; ++i)
|
||||
{
|
||||
CC_bind B(static_cast<CID>(joy), static_cast<short>(axis), CCF_AXIS);
|
||||
A = Control_config[i].find(B);
|
||||
if (A != nullptr)
|
||||
break;
|
||||
|
||||
// the binding we're looking for could be inverted
|
||||
CC_bind C(static_cast<CID>(joy), static_cast<short>(axis), CCF_AXIS | CCF_INVERTED);
|
||||
A = Control_config[i].find(C);
|
||||
if (A != nullptr)
|
||||
break;
|
||||
}
|
||||
|
||||
// TODO Should this be an error or silent false?
|
||||
if (A == nullptr)
|
||||
return ade_set_error(L, "b", false); // Axis not bound
|
||||
|
||||
if (n > 2)
|
||||
{
|
||||
A->invert(inverted);
|
||||
}
|
||||
|
||||
if (A->is_inverted())
|
||||
return ADE_RETURN_TRUE;
|
||||
else
|
||||
return ADE_RETURN_FALSE;
|
||||
}
|
||||
|
||||
ADE_FUNC(setCursorImage, l_Mouse, "string filename", "Sets mouse cursor image, and allows you to lock/unlock the image. (A locked cursor may only be changed with the unlock parameter)", "boolean", "true if successful, false otherwise")
|
||||
|
||||
@@ -145,6 +145,8 @@ add_file_folder("ControlConfig"
|
||||
controlconfig/controlsconfig.cpp
|
||||
controlconfig/controlsconfig.h
|
||||
controlconfig/controlsconfigcommon.cpp
|
||||
controlconfig/presets.cpp
|
||||
controlconfig/presets.h
|
||||
)
|
||||
|
||||
# Cutscene files
|
||||
|
||||
@@ -4412,14 +4412,14 @@ int game_poll()
|
||||
|
||||
int k = key_inkey();
|
||||
|
||||
// Move the mouse cursor with the joystick.
|
||||
// Move the mouse cursor with the joystick. Currently uses Joystick0
|
||||
if (os_foreground() && !io::mouse::CursorManager::get()->isCursorShown() && (Use_joy_mouse)) {
|
||||
// Move the mouse cursor with the joystick
|
||||
int mx, my;
|
||||
|
||||
int raw_axis[2];
|
||||
|
||||
joystick_read_raw_axis(2, raw_axis);
|
||||
joystick_read_raw_axis(CID_JOY0, 2, raw_axis);
|
||||
|
||||
int jx = joy_get_scaled_reading(raw_axis[0]);
|
||||
int jy = joy_get_scaled_reading(raw_axis[1]);
|
||||
@@ -4432,7 +4432,7 @@ int game_poll()
|
||||
mouse_set_pos( mx+dx, my+dy );
|
||||
}
|
||||
|
||||
int j = joy_down(0);
|
||||
int j = joy_down(CC_bind(CID_JOY0, 0));
|
||||
int m = mouse_down(MOUSE_LEFT_BUTTON);
|
||||
|
||||
if ( j != Joymouse_button_status ) {
|
||||
|
||||
Reference in new issue
Block a user