turbocat a27452493c OpenTyrian: Sources uploaded
git-svn-id: svn://kolibrios.org@9169 a494cfbc-eb01-0410-851d-a64ba20cac60
2021-08-31 18:22:39 +00:00

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;
}