forked from KolibriOS/kolibrios
a27452493c
git-svn-id: svn://kolibrios.org@9169 a494cfbc-eb01-0410-851d-a64ba20cac60
654 lines
16 KiB
C
654 lines
16 KiB
C
/*
|
|
* OpenTyrian: A modern cross-platform port of Tyrian
|
|
* Copyright (C) 2007-2009 The OpenTyrian Development Team
|
|
*
|
|
* This program is free software; you can redistribute it and/or
|
|
* modify it under the terms of the GNU General Public License
|
|
* as published by the Free Software Foundation; either version 2
|
|
* of the License, or (at your option) any later version.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU General Public License
|
|
* along with this program; if not, write to the Free Software
|
|
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
*/
|
|
#include "joystick.h"
|
|
|
|
#include "config.h"
|
|
#include "config_file.h"
|
|
#include "file.h"
|
|
#include "keyboard.h"
|
|
#include "nortsong.h"
|
|
#include "opentyr.h"
|
|
#include "params.h"
|
|
#include "varz.h"
|
|
#include "video.h"
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <string.h>
|
|
|
|
int joystick_axis_threshold( int j, int value );
|
|
int check_assigned( SDL_Joystick *joystick_handle, const Joystick_assignment assignment[2] );
|
|
|
|
const char *assignment_to_code( const Joystick_assignment *assignment );
|
|
void code_to_assignment( Joystick_assignment *assignment, const char *buffer );
|
|
|
|
int joystick_repeat_delay = 300; // milliseconds, repeat delay for buttons
|
|
bool joydown = false; // any joystick buttons down, updated by poll_joysticks()
|
|
bool ignore_joystick = false;
|
|
|
|
int joysticks = 0;
|
|
Joystick *joystick = NULL;
|
|
|
|
static const int joystick_analog_max = 32767;
|
|
|
|
// eliminates axis movement below the threshold
|
|
int joystick_axis_threshold( int j, int value )
|
|
{
|
|
assert(j < joysticks);
|
|
|
|
bool negative = value < 0;
|
|
if (negative)
|
|
value = -value;
|
|
|
|
if (value <= joystick[j].threshold * 1000)
|
|
return 0;
|
|
|
|
value -= joystick[j].threshold * 1000;
|
|
|
|
return negative ? -value : value;
|
|
}
|
|
|
|
// converts joystick axis to sane Tyrian-usable value (based on sensitivity)
|
|
int joystick_axis_reduce( int j, int value )
|
|
{
|
|
assert(j < joysticks);
|
|
|
|
value = joystick_axis_threshold(j, value);
|
|
|
|
if (value == 0)
|
|
return 0;
|
|
|
|
return value / (3000 - 200 * joystick[j].sensitivity);
|
|
}
|
|
|
|
// converts analog joystick axes to an angle
|
|
// returns false if axes are centered (there is no angle)
|
|
bool joystick_analog_angle( int j, float *angle )
|
|
{
|
|
assert(j < joysticks);
|
|
|
|
float x = joystick_axis_threshold(j, joystick[j].x), y = joystick_axis_threshold(j, joystick[j].y);
|
|
|
|
if (x != 0)
|
|
{
|
|
*angle += atanf(-y / x);
|
|
*angle += (x < 0) ? -M_PI_2 : M_PI_2;
|
|
return true;
|
|
}
|
|
else if (y != 0)
|
|
{
|
|
*angle += y < 0 ? M_PI : 0;
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* gives back value 0..joystick_analog_max indicating that one of the assigned
|
|
* buttons has been pressed or that one of the assigned axes/hats has been moved
|
|
* in the assigned direction
|
|
*/
|
|
int check_assigned( SDL_Joystick *joystick_handle, const Joystick_assignment assignment[2] )
|
|
{
|
|
int result = 0;
|
|
|
|
for (int i = 0; i < 2; i++)
|
|
{
|
|
int temp = 0;
|
|
|
|
switch (assignment[i].type)
|
|
{
|
|
case NONE:
|
|
continue;
|
|
|
|
case AXIS:
|
|
temp = SDL_JoystickGetAxis(joystick_handle, assignment[i].num);
|
|
|
|
if (assignment[i].negative_axis)
|
|
temp = -temp;
|
|
break;
|
|
|
|
case BUTTON:
|
|
temp = SDL_JoystickGetButton(joystick_handle, assignment[i].num) == 1 ? joystick_analog_max : 0;
|
|
break;
|
|
|
|
case HAT:
|
|
temp = SDL_JoystickGetHat(joystick_handle, assignment[i].num);
|
|
|
|
if (assignment[i].x_axis)
|
|
temp &= SDL_HAT_LEFT | SDL_HAT_RIGHT;
|
|
else
|
|
temp &= SDL_HAT_UP | SDL_HAT_DOWN;
|
|
|
|
if (assignment[i].negative_axis)
|
|
temp &= SDL_HAT_LEFT | SDL_HAT_UP;
|
|
else
|
|
temp &= SDL_HAT_RIGHT | SDL_HAT_DOWN;
|
|
|
|
temp = temp ? joystick_analog_max : 0;
|
|
break;
|
|
}
|
|
|
|
if (temp > result)
|
|
result = temp;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
// updates joystick state
|
|
void poll_joystick( int j )
|
|
{
|
|
assert(j < joysticks);
|
|
|
|
if (joystick[j].handle == NULL)
|
|
return;
|
|
|
|
SDL_JoystickUpdate();
|
|
|
|
// indicates that a direction/action was pressed since last poll
|
|
joystick[j].input_pressed = false;
|
|
|
|
// indicates that an direction/action has been held long enough to fake a repeat press
|
|
bool repeat = joystick[j].joystick_delay < SDL_GetTicks();
|
|
|
|
// update direction state
|
|
for (uint d = 0; d < COUNTOF(joystick[j].direction); d++)
|
|
{
|
|
bool old = joystick[j].direction[d];
|
|
|
|
joystick[j].analog_direction[d] = check_assigned(joystick[j].handle, joystick[j].assignment[d]);
|
|
joystick[j].direction[d] = joystick[j].analog_direction[d] > (joystick_analog_max / 2);
|
|
joydown |= joystick[j].direction[d];
|
|
|
|
joystick[j].direction_pressed[d] = joystick[j].direction[d] && (!old || repeat);
|
|
joystick[j].input_pressed |= joystick[j].direction_pressed[d];
|
|
}
|
|
|
|
joystick[j].x = -joystick[j].analog_direction[3] + joystick[j].analog_direction[1];
|
|
joystick[j].y = -joystick[j].analog_direction[0] + joystick[j].analog_direction[2];
|
|
|
|
// update action state
|
|
for (uint d = 0; d < COUNTOF(joystick[j].action); d++)
|
|
{
|
|
bool old = joystick[j].action[d];
|
|
|
|
joystick[j].action[d] = check_assigned(joystick[j].handle, joystick[j].assignment[d + COUNTOF(joystick[j].direction)]) > (joystick_analog_max / 2);
|
|
joydown |= joystick[j].action[d];
|
|
|
|
joystick[j].action_pressed[d] = joystick[j].action[d] && (!old || repeat);
|
|
joystick[j].input_pressed |= joystick[j].action_pressed[d];
|
|
}
|
|
|
|
joystick[j].confirm = joystick[j].action[0] || joystick[j].action[4];
|
|
joystick[j].cancel = joystick[j].action[1] || joystick[j].action[5];
|
|
|
|
// if new input, reset press-repeat delay
|
|
if (joystick[j].input_pressed)
|
|
joystick[j].joystick_delay = SDL_GetTicks() + joystick_repeat_delay;
|
|
}
|
|
|
|
// updates all joystick states
|
|
void poll_joysticks( void )
|
|
{
|
|
joydown = false;
|
|
|
|
for (int j = 0; j < joysticks; j++)
|
|
poll_joystick(j);
|
|
}
|
|
|
|
// sends SDL KEYDOWN and KEYUP events for a key
|
|
void push_key( SDLKey key )
|
|
{
|
|
SDL_Event e;
|
|
|
|
memset(&e.key.keysym, 0, sizeof(e.key.keysym));
|
|
|
|
e.key.keysym.sym = key;
|
|
e.key.keysym.unicode = key;
|
|
|
|
e.key.state = SDL_RELEASED;
|
|
|
|
e.type = SDL_KEYDOWN;
|
|
SDL_PushEvent(&e);
|
|
|
|
e.type = SDL_KEYUP;
|
|
SDL_PushEvent(&e);
|
|
}
|
|
|
|
// helps us be lazy by pretending joysticks are a keyboard (useful for menus)
|
|
void push_joysticks_as_keyboard( void )
|
|
{
|
|
const SDLKey confirm = SDLK_RETURN, cancel = SDLK_ESCAPE;
|
|
const SDLKey direction[4] = { SDLK_UP, SDLK_RIGHT, SDLK_DOWN, SDLK_LEFT };
|
|
|
|
poll_joysticks();
|
|
|
|
for (int j = 0; j < joysticks; j++)
|
|
{
|
|
if (!joystick[j].input_pressed)
|
|
continue;
|
|
|
|
if (joystick[j].confirm)
|
|
push_key(confirm);
|
|
if (joystick[j].cancel)
|
|
push_key(cancel);
|
|
|
|
for (uint d = 0; d < COUNTOF(joystick[j].direction_pressed); d++)
|
|
{
|
|
if (joystick[j].direction_pressed[d])
|
|
push_key(direction[d]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// initializes SDL joystick system and loads assignments for joysticks found
|
|
void init_joysticks( void )
|
|
{
|
|
if (ignore_joystick)
|
|
return;
|
|
|
|
if (SDL_InitSubSystem(SDL_INIT_JOYSTICK))
|
|
{
|
|
fprintf(stderr, "warning: failed to initialize joystick system: %s\n", SDL_GetError());
|
|
ignore_joystick = true;
|
|
return;
|
|
}
|
|
|
|
SDL_JoystickEventState(SDL_IGNORE);
|
|
|
|
joysticks = SDL_NumJoysticks();
|
|
joystick = malloc(joysticks * sizeof(*joystick));
|
|
|
|
for (int j = 0; j < joysticks; j++)
|
|
{
|
|
memset(&joystick[j], 0, sizeof(*joystick));
|
|
|
|
joystick[j].handle = SDL_JoystickOpen(j);
|
|
if (joystick[j].handle != NULL)
|
|
{
|
|
printf("joystick detected: %s ", SDL_JoystickName(j));
|
|
printf("(%d axes, %d buttons, %d hats)\n",
|
|
SDL_JoystickNumAxes(joystick[j].handle),
|
|
SDL_JoystickNumButtons(joystick[j].handle),
|
|
SDL_JoystickNumHats(joystick[j].handle));
|
|
|
|
if (!load_joystick_assignments(&opentyrian_config, j))
|
|
reset_joystick_assignments(j);
|
|
}
|
|
}
|
|
|
|
if (joysticks == 0)
|
|
printf("no joysticks detected\n");
|
|
}
|
|
|
|
// deinitializes SDL joystick system and saves joystick assignments
|
|
void deinit_joysticks( void )
|
|
{
|
|
if (ignore_joystick)
|
|
return;
|
|
|
|
for (int j = 0; j < joysticks; j++)
|
|
{
|
|
if (joystick[j].handle != NULL)
|
|
{
|
|
save_joystick_assignments(&opentyrian_config, j);
|
|
SDL_JoystickClose(joystick[j].handle);
|
|
}
|
|
}
|
|
|
|
free(joystick);
|
|
|
|
SDL_QuitSubSystem(SDL_INIT_JOYSTICK);
|
|
}
|
|
|
|
void reset_joystick_assignments( int j )
|
|
{
|
|
assert(j < joysticks);
|
|
|
|
// defaults: first 2 axes, first hat, first 6 buttons
|
|
for (uint a = 0; a < COUNTOF(joystick[j].assignment); a++)
|
|
{
|
|
// clear assignments
|
|
for (uint i = 0; i < COUNTOF(joystick[j].assignment[a]); i++)
|
|
joystick[j].assignment[a][i].type = NONE;
|
|
|
|
if (a < 4)
|
|
{
|
|
if (SDL_JoystickNumAxes(joystick[j].handle) >= 2)
|
|
{
|
|
joystick[j].assignment[a][0].type = AXIS;
|
|
joystick[j].assignment[a][0].num = (a + 1) % 2;
|
|
joystick[j].assignment[a][0].negative_axis = (a == 0 || a == 3);
|
|
}
|
|
|
|
if (SDL_JoystickNumHats(joystick[j].handle) >= 1)
|
|
{
|
|
joystick[j].assignment[a][1].type = HAT;
|
|
joystick[j].assignment[a][1].num = 0;
|
|
joystick[j].assignment[a][1].x_axis = (a == 1 || a == 3);
|
|
joystick[j].assignment[a][1].negative_axis = (a == 0 || a == 3);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (a - 4 < (unsigned)SDL_JoystickNumButtons(joystick[j].handle))
|
|
{
|
|
joystick[j].assignment[a][0].type = BUTTON;
|
|
joystick[j].assignment[a][0].num = a - 4;
|
|
}
|
|
}
|
|
}
|
|
|
|
joystick[j].analog = false;
|
|
joystick[j].sensitivity = 5;
|
|
joystick[j].threshold = 5;
|
|
}
|
|
|
|
static const char* const assignment_names[] =
|
|
{
|
|
"up",
|
|
"right",
|
|
"down",
|
|
"left",
|
|
"fire",
|
|
"change fire",
|
|
"left sidekick",
|
|
"right sidekick",
|
|
"menu",
|
|
"pause",
|
|
};
|
|
|
|
bool load_joystick_assignments( Config *config, int j )
|
|
{
|
|
ConfigSection *section = config_find_section(config, "joystick", SDL_JoystickName(j));
|
|
if (section == NULL)
|
|
return false;
|
|
|
|
if (!config_get_bool_option(section, "analog", &joystick[j].analog))
|
|
joystick[j].analog = false;
|
|
|
|
joystick[j].sensitivity = config_get_or_set_int_option(section, "sensitivity", 5);
|
|
|
|
joystick[j].threshold = config_get_or_set_int_option(section, "threshold", 5);
|
|
|
|
for (size_t a = 0; a < COUNTOF(assignment_names); ++a)
|
|
{
|
|
for (unsigned int i = 0; i < COUNTOF(joystick[j].assignment[a]); ++i)
|
|
joystick[j].assignment[a][i].type = NONE;
|
|
|
|
ConfigOption *option = config_get_option(section, assignment_names[a]);
|
|
if (option == NULL)
|
|
continue;
|
|
|
|
foreach_option_i_value(i, value, option)
|
|
{
|
|
if (i >= COUNTOF(joystick[j].assignment[a]))
|
|
break;
|
|
|
|
code_to_assignment(&joystick[j].assignment[a][i], value);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool save_joystick_assignments( Config *config, int j )
|
|
{
|
|
ConfigSection *section = config_find_or_add_section(config, "joystick", SDL_JoystickName(j));
|
|
if (section == NULL)
|
|
exit(EXIT_FAILURE); // out of memory
|
|
|
|
config_set_bool_option(section, "analog", joystick[j].analog, NO_YES);
|
|
|
|
config_set_int_option(section, "sensitivity", joystick[j].sensitivity);
|
|
|
|
config_set_int_option(section, "threshold", joystick[j].threshold);
|
|
|
|
for (size_t a = 0; a < COUNTOF(assignment_names); ++a)
|
|
{
|
|
ConfigOption *option = config_set_option(section, assignment_names[a], NULL);
|
|
if (option == NULL)
|
|
exit(EXIT_FAILURE); // out of memory
|
|
|
|
option = config_set_value(option, NULL);
|
|
if (option == NULL)
|
|
exit(EXIT_FAILURE); // out of memory
|
|
|
|
for (size_t i = 0; i < COUNTOF(joystick[j].assignment[a]); ++i)
|
|
{
|
|
if (joystick[j].assignment[a][i].type == NONE)
|
|
continue;
|
|
|
|
option = config_add_value(option, assignment_to_code(&joystick[j].assignment[a][i]));
|
|
if (option == NULL)
|
|
exit(EXIT_FAILURE); // out of memory
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// fills buffer with comma separated list of assigned joystick functions
|
|
void joystick_assignments_to_string( char *buffer, size_t buffer_len, const Joystick_assignment *assignments )
|
|
{
|
|
strncpy(buffer, "", buffer_len);
|
|
|
|
bool comma = false;
|
|
for (uint i = 0; i < COUNTOF(*joystick->assignment); ++i)
|
|
{
|
|
if (assignments[i].type == NONE)
|
|
continue;
|
|
|
|
size_t len = snprintf(buffer, buffer_len, "%s%s",
|
|
comma ? ", " : "",
|
|
assignment_to_code(&assignments[i]));
|
|
buffer += len;
|
|
buffer_len -= len;
|
|
|
|
comma = true;
|
|
}
|
|
}
|
|
|
|
// reverse of assignment_to_code()
|
|
void code_to_assignment( Joystick_assignment *assignment, const char *buffer )
|
|
{
|
|
memset(assignment, 0, sizeof(*assignment));
|
|
|
|
char axis = 0, direction = 0;
|
|
|
|
if (sscanf(buffer, " AX %d%c", &assignment->num, &direction) == 2)
|
|
assignment->type = AXIS;
|
|
else if (sscanf(buffer, " BTN %d", &assignment->num) == 1)
|
|
assignment->type = BUTTON;
|
|
else if (sscanf(buffer, " H %d%c%c", &assignment->num, &axis, &direction) == 3)
|
|
assignment->type = HAT;
|
|
|
|
if (assignment->num == 0)
|
|
assignment->type = NONE;
|
|
else
|
|
--assignment->num;
|
|
|
|
assignment->x_axis = (toupper(axis) == 'X');
|
|
assignment->negative_axis = (toupper(direction) == '-');
|
|
}
|
|
|
|
/* gives the short (6 or less characters) identifier for a joystick assignment
|
|
*
|
|
* two of these per direction/action is all that can fit on the joystick config screen,
|
|
* assuming two digits for the axis/button/hat number
|
|
*/
|
|
const char *assignment_to_code( const Joystick_assignment *assignment )
|
|
{
|
|
static char name[7];
|
|
|
|
switch (assignment->type)
|
|
{
|
|
case NONE:
|
|
strcpy(name, "");
|
|
break;
|
|
|
|
case AXIS:
|
|
snprintf(name, sizeof(name), "AX %d%c",
|
|
assignment->num + 1,
|
|
assignment->negative_axis ? '-' : '+');
|
|
break;
|
|
|
|
case BUTTON:
|
|
snprintf(name, sizeof(name), "BTN %d",
|
|
assignment->num + 1);
|
|
break;
|
|
|
|
case HAT:
|
|
snprintf(name, sizeof(name), "H %d%c%c",
|
|
assignment->num + 1,
|
|
assignment->x_axis ? 'X' : 'Y',
|
|
assignment->negative_axis ? '-' : '+');
|
|
break;
|
|
}
|
|
|
|
return name;
|
|
}
|
|
|
|
// captures joystick input for configuring assignments
|
|
// returns false if non-joystick input was detected
|
|
// TODO: input from joystick other than the one being configured probably should not be ignored
|
|
bool detect_joystick_assignment( int j, Joystick_assignment *assignment )
|
|
{
|
|
// get initial joystick state to compare against to see if anything was pressed
|
|
|
|
const int axes = SDL_JoystickNumAxes(joystick[j].handle);
|
|
Sint16 *axis = malloc(axes * sizeof(*axis));
|
|
for (int i = 0; i < axes; i++)
|
|
axis[i] = SDL_JoystickGetAxis(joystick[j].handle, i);
|
|
|
|
const int buttons = SDL_JoystickNumButtons(joystick[j].handle);
|
|
Uint8 *button = malloc(buttons * sizeof(*button));
|
|
for (int i = 0; i < buttons; i++)
|
|
button[i] = SDL_JoystickGetButton(joystick[j].handle, i);
|
|
|
|
const int hats = SDL_JoystickNumHats(joystick[j].handle);
|
|
Uint8 *hat = malloc(hats * sizeof(*hat));
|
|
for (int i = 0; i < hats; i++)
|
|
hat[i] = SDL_JoystickGetHat(joystick[j].handle, i);
|
|
|
|
bool detected = false;
|
|
|
|
do
|
|
{
|
|
setjasondelay(1);
|
|
|
|
SDL_JoystickUpdate();
|
|
|
|
for (int i = 0; i < axes; ++i)
|
|
{
|
|
Sint16 temp = SDL_JoystickGetAxis(joystick[j].handle, i);
|
|
|
|
if (abs(temp - axis[i]) > joystick_analog_max * 2 / 3)
|
|
{
|
|
assignment->type = AXIS;
|
|
assignment->num = i;
|
|
assignment->negative_axis = temp < axis[i];
|
|
detected = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < buttons; ++i)
|
|
{
|
|
Uint8 new_button = SDL_JoystickGetButton(joystick[j].handle, i),
|
|
changed = button[i] ^ new_button;
|
|
|
|
if (!changed)
|
|
continue;
|
|
|
|
if (new_button == 0) // button was released
|
|
{
|
|
button[i] = new_button;
|
|
}
|
|
else // button was pressed
|
|
{
|
|
assignment->type = BUTTON;
|
|
assignment->num = i;
|
|
detected = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
for (int i = 0; i < hats; ++i)
|
|
{
|
|
Uint8 new_hat = SDL_JoystickGetHat(joystick[j].handle, i),
|
|
changed = hat[i] ^ new_hat;
|
|
|
|
if (!changed)
|
|
continue;
|
|
|
|
if ((new_hat & changed) == SDL_HAT_CENTERED) // hat was centered
|
|
{
|
|
hat[i] = new_hat;
|
|
}
|
|
else
|
|
{
|
|
assignment->type = HAT;
|
|
assignment->num = i;
|
|
assignment->x_axis = changed & (SDL_HAT_LEFT | SDL_HAT_RIGHT);
|
|
assignment->negative_axis = changed & (SDL_HAT_LEFT | SDL_HAT_UP);
|
|
detected = true;
|
|
}
|
|
}
|
|
|
|
service_SDL_events(true);
|
|
JE_showVGA();
|
|
|
|
wait_delay();
|
|
}
|
|
while (!detected && !newkey && !newmouse);
|
|
|
|
free(axis);
|
|
free(button);
|
|
free(hat);
|
|
|
|
return detected;
|
|
}
|
|
|
|
// compares relevant parts of joystick assignments for equality
|
|
bool joystick_assignment_cmp( const Joystick_assignment *a, const Joystick_assignment *b )
|
|
{
|
|
if (a->type == b->type)
|
|
{
|
|
switch (a->type)
|
|
{
|
|
case NONE:
|
|
return true;
|
|
case AXIS:
|
|
return (a->num == b->num) &&
|
|
(a->negative_axis == b->negative_axis);
|
|
case BUTTON:
|
|
return (a->num == b->num);
|
|
case HAT:
|
|
return (a->num == b->num) &&
|
|
(a->x_axis == b->x_axis) &&
|
|
(a->negative_axis == b->negative_axis);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|