- Free view: hold a mapped input (default: left grip) and that thumbstick drives the engine's mouse look, with the stick's normal bindings suspended while held. Also available for the right stick, unbound by default. - Cyberspace actions: eleven thrust/climb/dive/bank/roll actions with their own bindings, active only in cyberspace. The normal movement actions are inactive there, and capture exclusivity only compares actions that can be active together, so one input can be bound in both sets. - Cyberspace pitch and bank scale with thumbstick deflection instead of running at full strength. - Remove the toggle_freelook action; bind the automap view to the right stick click by default. - Engine patches: 24 exposes whether the player is in cyberspace, 25 lets the controller code scale cyberspace pitch and bank.
This commit is contained in:
@@ -94,9 +94,10 @@ static const char *kRawInputNames[QS_RAW_COUNT] = {
|
||||
"right_thumbstick_right", "trigger", "squeeze", "none",
|
||||
};
|
||||
|
||||
// Remappable game actions: movement, click and jump, the engine's own
|
||||
// keyboard hotkeys (Prefs.c's HotKeyLookup[]), and fire. The hotkey actions
|
||||
// default to QS_RAW_NONE (unbound). menu_toggle is not an action; it is fixed
|
||||
// Remappable game actions: movement, click and jump, free view, a separate set
|
||||
// of cyberspace movement actions, the engine's own keyboard hotkeys (Prefs.c's
|
||||
// HotKeyLookup[]), and fire. The hotkey actions default to QS_RAW_NONE
|
||||
// (unbound). menu_toggle is not an action; it is fixed
|
||||
// (see xr_input_sync_and_draw()).
|
||||
//
|
||||
// HotKeyLookup[] entries not exposed:
|
||||
@@ -121,7 +122,6 @@ typedef enum {
|
||||
QS_ACTION_STAND,
|
||||
QS_ACTION_CROUCH,
|
||||
QS_ACTION_PRONE,
|
||||
QS_ACTION_TOGGLE_FREELOOK,
|
||||
QS_ACTION_PAUSE,
|
||||
QS_ACTION_FULL_VIEW,
|
||||
QS_ACTION_NORMAL_VIEW,
|
||||
@@ -154,6 +154,19 @@ typedef enum {
|
||||
QS_ACTION_MFD_RIGHT_4,
|
||||
QS_ACTION_MFD_RIGHT_5,
|
||||
QS_ACTION_FIRE_WEAPON,
|
||||
QS_ACTION_FREE_LEFT_THUMBSTICK_VIEW,
|
||||
QS_ACTION_FREE_RIGHT_THUMBSTICK_VIEW,
|
||||
QS_ACTION_CYBER_THRUST,
|
||||
QS_ACTION_CYBER_CLIMB,
|
||||
QS_ACTION_CYBER_DIVE,
|
||||
QS_ACTION_CYBER_BANK_LEFT,
|
||||
QS_ACTION_CYBER_BANK_RIGHT,
|
||||
QS_ACTION_CYBER_ROLL_LEFT,
|
||||
QS_ACTION_CYBER_ROLL_RIGHT,
|
||||
QS_ACTION_CYBER_CLIMB_LEFT,
|
||||
QS_ACTION_CYBER_CLIMB_RIGHT,
|
||||
QS_ACTION_CYBER_DIVE_LEFT,
|
||||
QS_ACTION_CYBER_DIVE_RIGHT,
|
||||
QS_ACTION_COUNT
|
||||
} QsControlAction;
|
||||
|
||||
@@ -170,7 +183,6 @@ static const char *kActionNames[QS_ACTION_COUNT] = {
|
||||
"stand",
|
||||
"crouch",
|
||||
"prone",
|
||||
"toggle_freelook",
|
||||
"pause",
|
||||
"full_view",
|
||||
"normal_view",
|
||||
@@ -203,14 +215,28 @@ static const char *kActionNames[QS_ACTION_COUNT] = {
|
||||
"mfd_right_4",
|
||||
"mfd_right_5",
|
||||
"fire_weapon",
|
||||
"free_left_thumbstick_view",
|
||||
"free_right_thumbstick_view",
|
||||
"cyber_thrust",
|
||||
"cyber_climb",
|
||||
"cyber_dive",
|
||||
"cyber_bank_left",
|
||||
"cyber_bank_right",
|
||||
"cyber_roll_left",
|
||||
"cyber_roll_right",
|
||||
"cyber_climb_left",
|
||||
"cyber_climb_right",
|
||||
"cyber_dive_left",
|
||||
"cyber_dive_right",
|
||||
};
|
||||
|
||||
// Current bindings; xr_input.c owns them (qs_config.c applies parsed config via
|
||||
// xr_input_set_action_binding() and reads them back for Save). Defaults: left
|
||||
// stick moves (up/down) and strafes (left/right), its click jumps; right stick
|
||||
// turns (left/right) and stands/goes prone (up/down), its click toggles free
|
||||
// look; right A/B click/double-click; right trigger fires. Everything else is
|
||||
// QS_RAW_NONE.
|
||||
// turns (left/right) and stands/goes prone (up/down), its click opens the
|
||||
// automap; right A/B click/double-click; right trigger fires; left grip holds
|
||||
// free view on the left stick. In cyberspace: left stick climbs/dives and banks,
|
||||
// right stick thrusts (up) and rolls. Everything else is QS_RAW_NONE.
|
||||
static QsRawInput s_binding[QS_ACTION_COUNT] = {
|
||||
QS_RAW_LEFT_THUMBSTICK_UP, // move_forward
|
||||
QS_RAW_LEFT_THUMBSTICK_DOWN, // move_back
|
||||
@@ -224,11 +250,10 @@ static QsRawInput s_binding[QS_ACTION_COUNT] = {
|
||||
QS_RAW_RIGHT_THUMBSTICK_UP, // stand
|
||||
QS_RAW_NONE, // crouch
|
||||
QS_RAW_RIGHT_THUMBSTICK_DOWN, // prone
|
||||
QS_RAW_RIGHT_THUMBSTICK_CLICK, // toggle_freelook
|
||||
QS_RAW_NONE, // pause
|
||||
QS_RAW_NONE, // full_view
|
||||
QS_RAW_NONE, // normal_view
|
||||
QS_RAW_NONE, // map_view
|
||||
QS_RAW_RIGHT_THUMBSTICK_CLICK, // map_view
|
||||
QS_RAW_NONE, // save_game
|
||||
QS_RAW_NONE, // load_game
|
||||
QS_RAW_NONE, // select_grenade
|
||||
@@ -257,6 +282,19 @@ static QsRawInput s_binding[QS_ACTION_COUNT] = {
|
||||
QS_RAW_NONE, // mfd_right_4
|
||||
QS_RAW_NONE, // mfd_right_5
|
||||
QS_RAW_RIGHT_TRIGGER, // fire_weapon
|
||||
QS_RAW_LEFT_SQUEEZE, // free_left_thumbstick_view
|
||||
QS_RAW_NONE, // free_right_thumbstick_view
|
||||
QS_RAW_RIGHT_THUMBSTICK_UP, // cyber_thrust
|
||||
QS_RAW_LEFT_THUMBSTICK_UP, // cyber_climb
|
||||
QS_RAW_LEFT_THUMBSTICK_DOWN, // cyber_dive
|
||||
QS_RAW_LEFT_THUMBSTICK_LEFT, // cyber_bank_left
|
||||
QS_RAW_LEFT_THUMBSTICK_RIGHT, // cyber_bank_right
|
||||
QS_RAW_RIGHT_THUMBSTICK_LEFT, // cyber_roll_left
|
||||
QS_RAW_RIGHT_THUMBSTICK_RIGHT, // cyber_roll_right
|
||||
QS_RAW_NONE, // cyber_climb_left
|
||||
QS_RAW_NONE, // cyber_climb_right
|
||||
QS_RAW_NONE, // cyber_dive_left
|
||||
QS_RAW_NONE, // cyber_dive_right
|
||||
};
|
||||
|
||||
// One entry per key-injecting action. A plain, unmodified, printable key
|
||||
@@ -282,11 +320,10 @@ static const QsKeyAction kKeyActions[] = {
|
||||
{QS_ACTION_TURN_LEFT, SDL_SCANCODE_LEFT, SDLK_LEFT, KMOD_NONE, false, 0},
|
||||
{QS_ACTION_TURN_RIGHT, SDL_SCANCODE_RIGHT, SDLK_RIGHT, KMOD_NONE, false, 0},
|
||||
{QS_ACTION_JUMP, SDL_SCANCODE_SPACE, SDLK_SPACE, KMOD_NONE, true, ' '},
|
||||
// "stand"/"crouch"/"prone"/"toggle_freelook"/"pause" - DOWN('t'/'g'/'b'/'f'/'p').
|
||||
// "stand"/"crouch"/"prone"/"pause" - DOWN('t'/'g'/'b'/'p').
|
||||
{QS_ACTION_STAND, SDL_SCANCODE_T, SDLK_t, KMOD_NONE, true, 't'},
|
||||
{QS_ACTION_CROUCH, SDL_SCANCODE_G, SDLK_g, KMOD_NONE, true, 'g'},
|
||||
{QS_ACTION_PRONE, SDL_SCANCODE_B, SDLK_b, KMOD_NONE, true, 'b'},
|
||||
{QS_ACTION_TOGGLE_FREELOOK, SDL_SCANCODE_F, SDLK_f, KMOD_NONE, true, 'f'},
|
||||
{QS_ACTION_PAUSE, SDL_SCANCODE_P, SDLK_p, KMOD_NONE, true, 'p'},
|
||||
// "full_view"/"normal_view"/"map_view" - CTRL('f'/'d'/'a') - jump
|
||||
// straight to the fullscreen/game/automap loop mode.
|
||||
@@ -335,6 +372,19 @@ static const QsKeyAction kKeyActions[] = {
|
||||
// view3d_key_handler()): Enter fires the readied weapon at the cursor and
|
||||
// re-arms on release. Clicking in the 3D view (mouse_click) also fires.
|
||||
{QS_ACTION_FIRE_WEAPON, SDL_SCANCODE_RETURN, SDLK_RETURN, KMOD_NONE, false, 0},
|
||||
// Cyberspace movement (Prefs.c's MoveCyberKeybinds defaults): S thrust, W climb, X dive,
|
||||
// A/D bank, E/Q roll, and the keypad 7/9/1/3 diagonal combinations.
|
||||
{QS_ACTION_CYBER_THRUST, SDL_SCANCODE_S, SDLK_s, KMOD_NONE, true, 's'},
|
||||
{QS_ACTION_CYBER_CLIMB, SDL_SCANCODE_W, SDLK_w, KMOD_NONE, true, 'w'},
|
||||
{QS_ACTION_CYBER_DIVE, SDL_SCANCODE_X, SDLK_x, KMOD_NONE, true, 'x'},
|
||||
{QS_ACTION_CYBER_BANK_LEFT, SDL_SCANCODE_A, SDLK_a, KMOD_NONE, true, 'a'},
|
||||
{QS_ACTION_CYBER_BANK_RIGHT, SDL_SCANCODE_D, SDLK_d, KMOD_NONE, true, 'd'},
|
||||
{QS_ACTION_CYBER_ROLL_LEFT, SDL_SCANCODE_E, SDLK_e, KMOD_NONE, true, 'e'},
|
||||
{QS_ACTION_CYBER_ROLL_RIGHT, SDL_SCANCODE_Q, SDLK_q, KMOD_NONE, true, 'q'},
|
||||
{QS_ACTION_CYBER_CLIMB_LEFT, SDL_SCANCODE_KP_7, SDLK_KP_7, KMOD_NONE, false, 0},
|
||||
{QS_ACTION_CYBER_CLIMB_RIGHT, SDL_SCANCODE_KP_9, SDLK_KP_9, KMOD_NONE, false, 0},
|
||||
{QS_ACTION_CYBER_DIVE_LEFT, SDL_SCANCODE_KP_1, SDLK_KP_1, KMOD_NONE, false, 0},
|
||||
{QS_ACTION_CYBER_DIVE_RIGHT, SDL_SCANCODE_KP_3, SDLK_KP_3, KMOD_NONE, false, 0},
|
||||
};
|
||||
#define QS_NUM_KEY_ACTIONS (sizeof(kKeyActions) / sizeof(kKeyActions[0]))
|
||||
|
||||
@@ -393,6 +443,8 @@ typedef struct {
|
||||
bool up, down, left, right;
|
||||
} XrStickDigital;
|
||||
static XrStickDigital g_stick_digital[2];
|
||||
// Raw analog stick/trackpad position per hand, for the free-view actions.
|
||||
static float g_stick_xy[2][2];
|
||||
|
||||
// Rebind capture state (see xr_input_capture_begin()/qs_config.c's own
|
||||
// comment on the cross-thread request that calls it) - g_capture_action is
|
||||
@@ -723,6 +775,8 @@ static void xr_input_update_raw_states(void) {
|
||||
xrGetActionStateVector2f(g_session, &info, &state);
|
||||
xr_input_digitize_stick(&g_stick_digital[hand], state.isActive, state.currentState.x,
|
||||
state.currentState.y);
|
||||
g_stick_xy[hand][0] = state.isActive ? state.currentState.x : 0.0f;
|
||||
g_stick_xy[hand][1] = state.isActive ? state.currentState.y : 0.0f;
|
||||
}
|
||||
g_raw_down[QS_RAW_LEFT_THUMBSTICK_UP] = g_stick_digital[LEFT].up;
|
||||
g_raw_down[QS_RAW_LEFT_THUMBSTICK_DOWN] = g_stick_digital[LEFT].down;
|
||||
@@ -768,11 +822,10 @@ void xr_input_reset_player_controls(void) {
|
||||
QS_RAW_RIGHT_THUMBSTICK_UP, // stand
|
||||
QS_RAW_NONE, // crouch
|
||||
QS_RAW_RIGHT_THUMBSTICK_DOWN, // prone
|
||||
QS_RAW_RIGHT_THUMBSTICK_CLICK, // toggle_freelook
|
||||
QS_RAW_NONE, // pause
|
||||
QS_RAW_NONE, // full_view
|
||||
QS_RAW_NONE, // normal_view
|
||||
QS_RAW_NONE, // map_view
|
||||
QS_RAW_RIGHT_THUMBSTICK_CLICK, // map_view
|
||||
QS_RAW_NONE, // save_game
|
||||
QS_RAW_NONE, // load_game
|
||||
QS_RAW_NONE, // select_grenade
|
||||
@@ -801,6 +854,19 @@ void xr_input_reset_player_controls(void) {
|
||||
QS_RAW_NONE, // mfd_right_4
|
||||
QS_RAW_NONE, // mfd_right_5
|
||||
QS_RAW_RIGHT_TRIGGER, // fire_weapon
|
||||
QS_RAW_LEFT_SQUEEZE, // free_left_thumbstick_view
|
||||
QS_RAW_NONE, // free_right_thumbstick_view
|
||||
QS_RAW_RIGHT_THUMBSTICK_UP, // cyber_thrust
|
||||
QS_RAW_LEFT_THUMBSTICK_UP, // cyber_climb
|
||||
QS_RAW_LEFT_THUMBSTICK_DOWN, // cyber_dive
|
||||
QS_RAW_LEFT_THUMBSTICK_LEFT, // cyber_bank_left
|
||||
QS_RAW_LEFT_THUMBSTICK_RIGHT, // cyber_bank_right
|
||||
QS_RAW_RIGHT_THUMBSTICK_LEFT, // cyber_roll_left
|
||||
QS_RAW_RIGHT_THUMBSTICK_RIGHT, // cyber_roll_right
|
||||
QS_RAW_NONE, // cyber_climb_left
|
||||
QS_RAW_NONE, // cyber_climb_right
|
||||
QS_RAW_NONE, // cyber_dive_left
|
||||
QS_RAW_NONE, // cyber_dive_right
|
||||
};
|
||||
memcpy(s_binding, kDefaults, sizeof(s_binding));
|
||||
g_capture_active = false;
|
||||
@@ -831,6 +897,39 @@ const char *xr_input_get_action_binding_name(int action_index) {
|
||||
return xr_input_raw_input_name((QsRawInput)xr_input_get_action_binding(action_index));
|
||||
}
|
||||
|
||||
// Where an action applies. NORMAL actions (walking, jumping, posture) are
|
||||
// inactive in cyberspace and CYBER actions only work there, so each set has its
|
||||
// own bindings and one input can be bound in both. Everything else (clicks,
|
||||
// fire, free view, hotkeys) applies everywhere.
|
||||
typedef enum { QS_CONTEXT_ALWAYS, QS_CONTEXT_NORMAL, QS_CONTEXT_CYBER } QsActionContext;
|
||||
|
||||
static QsActionContext xr_input_action_context(QsControlAction action) {
|
||||
if (action >= QS_ACTION_CYBER_THRUST)
|
||||
return QS_CONTEXT_CYBER;
|
||||
switch (action) {
|
||||
case QS_ACTION_MOVE_FORWARD:
|
||||
case QS_ACTION_MOVE_BACK:
|
||||
case QS_ACTION_STRAFE_LEFT:
|
||||
case QS_ACTION_STRAFE_RIGHT:
|
||||
case QS_ACTION_TURN_LEFT:
|
||||
case QS_ACTION_TURN_RIGHT:
|
||||
case QS_ACTION_JUMP:
|
||||
case QS_ACTION_STAND:
|
||||
case QS_ACTION_CROUCH:
|
||||
case QS_ACTION_PRONE:
|
||||
return QS_CONTEXT_NORMAL;
|
||||
default:
|
||||
return QS_CONTEXT_ALWAYS;
|
||||
}
|
||||
}
|
||||
|
||||
// True if two actions can be active at the same time, so one input shared by
|
||||
// both would fire both.
|
||||
static bool xr_input_contexts_overlap(QsControlAction a, QsControlAction b) {
|
||||
QsActionContext ca = xr_input_action_context(a), cb = xr_input_action_context(b);
|
||||
return ca == QS_CONTEXT_ALWAYS || cb == QS_CONTEXT_ALWAYS || ca == cb;
|
||||
}
|
||||
|
||||
// True if an action holding `binding` would still fire from a press of
|
||||
// `matched`: the same concrete input, or a symmetric "trigger"/"squeeze"
|
||||
// binding when `matched` is that hand's trigger/squeeze.
|
||||
@@ -849,9 +948,10 @@ static bool xr_input_binding_conflicts(QsRawInput binding, QsRawInput matched) {
|
||||
// xr_input_update_raw_states() and before the key-action dispatch loop, so
|
||||
// a same-frame press isn't also consumed as this action's normal effect
|
||||
// (see xr_input_action_suppressed_by_capture() for the other half of that
|
||||
// guarantee). On a match, enforces exclusivity (at most one action may
|
||||
// respond to a given raw input - see xr_input_binding_conflicts() above)
|
||||
// by clearing any other action that would otherwise still fire from it,
|
||||
// guarantee). On a match, enforces exclusivity (at most one action that can
|
||||
// be active together with it - see xr_input_contexts_overlap() - may respond
|
||||
// to a given raw input, see xr_input_binding_conflicts()) by clearing any
|
||||
// other such action that would otherwise still fire from it,
|
||||
// then reports both results in one call.
|
||||
static void xr_input_process_capture(void) {
|
||||
if (!g_capture_active)
|
||||
@@ -867,7 +967,9 @@ static void xr_input_process_capture(void) {
|
||||
QsRawInput matched = (QsRawInput)i;
|
||||
int displacedAction = -1;
|
||||
for (int a = 0; a < QS_ACTION_COUNT; a++) {
|
||||
if (a != (int)g_capture_action && xr_input_binding_conflicts(s_binding[a], matched)) {
|
||||
if (a != (int)g_capture_action &&
|
||||
xr_input_contexts_overlap((QsControlAction)a, g_capture_action) &&
|
||||
xr_input_binding_conflicts(s_binding[a], matched)) {
|
||||
s_binding[a] = QS_RAW_NONE;
|
||||
displacedAction = a;
|
||||
break; // exclusivity guarantees at most one prior holder
|
||||
@@ -1671,6 +1773,162 @@ static bool xr_input_push_key(const QsKeyAction *keyAction, bool down) {
|
||||
return SDL_PushEvent(&event) == 1;
|
||||
}
|
||||
|
||||
// Free view: while a free_{left,right}_thumbstick_view action's button is
|
||||
// held (and the menu is closed), that hand's stick looks around instead of
|
||||
// driving its normal bindings. It feeds the engine's own mouse look
|
||||
// (mouselook.c): mlook_enabled is forced on for as long as a free view is
|
||||
// held, and the stick adds to MouseChaosX/Y, the extra look velocity that
|
||||
// get_mouselook_vel() (sdl_events.c) consumes once per physics tick.
|
||||
// get_mouselook_vel() returns zero while SDL considers the cursor shown, so
|
||||
// the cursor is kept hidden for the duration.
|
||||
extern int mlook_enabled;
|
||||
extern int qs_in_cyberspace(void); // engine patch 24 (mouselook.c)
|
||||
extern unsigned char game_paused;
|
||||
extern int MouseChaosX, MouseChaosY;
|
||||
|
||||
#define QS_FREE_VIEW_DEADZONE 0.15f
|
||||
#define QS_FREE_VIEW_YAW_PER_SEC 500.0f
|
||||
#define QS_FREE_VIEW_PITCH_PER_SEC 250.0f
|
||||
|
||||
static bool g_free_view_forced = false; // true while we have mlook_enabled forced on
|
||||
static int g_free_view_saved_mlook = 0; // mlook_enabled before that
|
||||
static float g_free_view_acc[2]; // fractional look counts not yet emitted
|
||||
static int g_free_view_saved_cursor = 0; // SDL_ShowCursor() state before it
|
||||
static Uint32 g_free_view_last_ticks = 0;
|
||||
static Uint32 g_free_view_log_ticks = 0;
|
||||
|
||||
static void xr_input_apply_free_view(const bool active[2]) {
|
||||
Uint32 now = SDL_GetTicks();
|
||||
float dt = g_free_view_last_ticks ? (float)(now - g_free_view_last_ticks) / 1000.0f : 0.0f;
|
||||
g_free_view_last_ticks = now;
|
||||
if (dt > 0.1f)
|
||||
dt = 0.1f;
|
||||
|
||||
if (!active[LEFT] && !active[RIGHT]) {
|
||||
if (g_free_view_forced) {
|
||||
mlook_enabled = g_free_view_saved_mlook;
|
||||
SDL_ShowCursor(g_free_view_saved_cursor ? SDL_ENABLE : SDL_DISABLE);
|
||||
g_free_view_forced = false;
|
||||
g_free_view_acc[0] = g_free_view_acc[1] = 0.0f;
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (!g_free_view_forced) {
|
||||
g_free_view_saved_mlook = mlook_enabled;
|
||||
g_free_view_saved_cursor = SDL_ShowCursor(SDL_QUERY);
|
||||
g_free_view_forced = true;
|
||||
}
|
||||
mlook_enabled = 1;
|
||||
SDL_ShowCursor(SDL_DISABLE);
|
||||
if (game_paused)
|
||||
return;
|
||||
|
||||
float x = 0.0f, y = 0.0f;
|
||||
for (int hand = 0; hand < 2; hand++) {
|
||||
if (active[hand]) {
|
||||
x += g_stick_xy[hand][0];
|
||||
y += g_stick_xy[hand][1];
|
||||
}
|
||||
}
|
||||
float mag = sqrtf(x * x + y * y);
|
||||
if (mag > QS_FREE_VIEW_DEADZONE) {
|
||||
float scaled = (mag - QS_FREE_VIEW_DEADZONE) / (1.0f - QS_FREE_VIEW_DEADZONE);
|
||||
if (scaled > 1.0f)
|
||||
scaled = 1.0f;
|
||||
// Quadratic response: fine control near the centre, full rate at the edge.
|
||||
float factor = scaled * scaled / mag;
|
||||
x *= factor;
|
||||
y *= factor;
|
||||
} else {
|
||||
x = y = 0.0f;
|
||||
}
|
||||
// Stick right = look right; stick up = look up (mouse motion up is negative y).
|
||||
g_free_view_acc[0] += x * QS_FREE_VIEW_YAW_PER_SEC * dt;
|
||||
g_free_view_acc[1] += -y * QS_FREE_VIEW_PITCH_PER_SEC * dt;
|
||||
int dx = (int)g_free_view_acc[0];
|
||||
int dy = (int)g_free_view_acc[1];
|
||||
g_free_view_acc[0] -= (float)dx;
|
||||
g_free_view_acc[1] -= (float)dy;
|
||||
// Bounded so a held stick can't pile up unconsumed velocity when the
|
||||
// engine isn't running physics (e.g. outside gameplay).
|
||||
if (MouseChaosX > -4096 && MouseChaosX < 4096)
|
||||
MouseChaosX += dx;
|
||||
if (MouseChaosY > -4096 && MouseChaosY < 4096)
|
||||
MouseChaosY += dy;
|
||||
|
||||
if (now - g_free_view_log_ticks > 500) {
|
||||
g_free_view_log_ticks = now;
|
||||
LOGI("XR: free view mlook=%d cursorShown=%d paused=%d stick=(%.2f,%.2f) chaos=(%d,%d)",
|
||||
mlook_enabled, SDL_ShowCursor(SDL_QUERY), (int)game_paused, x, y, MouseChaosX,
|
||||
MouseChaosY);
|
||||
}
|
||||
}
|
||||
|
||||
// Cyberspace pitch and bank are full-strength on/off keys in the engine. For an
|
||||
// action bound to a thumbstick direction, engine patch 25 (movekeys.c) scales
|
||||
// that strength by these globals, set every frame from how far the stick is
|
||||
// pushed: MIN just past the digital threshold up to MAX at full deflection. A
|
||||
// button-bound action keeps full strength.
|
||||
extern float qs_cyber_pitch_scale;
|
||||
extern float qs_cyber_bank_scale;
|
||||
|
||||
#define QS_CYBER_ROTATION_MIN_SCALE 0.2f
|
||||
#define QS_CYBER_ROTATION_MAX_SCALE 0.6f
|
||||
|
||||
// Deflection (0..1) of the stick direction `raw` names, or -1 if `raw` is not a
|
||||
// thumbstick direction. The 8 direction inputs are ordered up/down/left/right
|
||||
// per hand.
|
||||
static float xr_input_stick_direction_deflection(QsRawInput raw) {
|
||||
if (!xr_input_raw_is_thumbstick_direction(raw))
|
||||
return -1.0f;
|
||||
const float *xy = g_stick_xy[xr_input_raw_input_hand(raw)];
|
||||
float d;
|
||||
switch ((raw - QS_RAW_LEFT_THUMBSTICK_UP) % 4) {
|
||||
case 0: d = xy[1]; break;
|
||||
case 1: d = -xy[1]; break;
|
||||
case 2: d = -xy[0]; break;
|
||||
default: d = xy[0]; break;
|
||||
}
|
||||
return d < 0.0f ? 0.0f : (d > 1.0f ? 1.0f : d);
|
||||
}
|
||||
|
||||
static float xr_input_cyber_rotation_scale(QsRawInput raw) {
|
||||
float d = xr_input_stick_direction_deflection(raw);
|
||||
if (d < 0.0f)
|
||||
return 1.0f;
|
||||
float t = (d - 0.5f) / 0.5f;
|
||||
t = t < 0.0f ? 0.0f : (t > 1.0f ? 1.0f : t);
|
||||
return QS_CYBER_ROTATION_MIN_SCALE + (QS_CYBER_ROTATION_MAX_SCALE - QS_CYBER_ROTATION_MIN_SCALE) * t;
|
||||
}
|
||||
|
||||
// Sets the two globals from the cyberspace pitch/bank actions currently held.
|
||||
static void xr_input_update_cyber_scales(const bool wantDown[]) {
|
||||
float pitchScale = 1.0f, bankScale = 1.0f;
|
||||
bool havePitch = false, haveBank = false;
|
||||
for (size_t i = 0; i < QS_NUM_KEY_ACTIONS; i++) {
|
||||
QsControlAction a = kKeyActions[i].action;
|
||||
bool pitch = a == QS_ACTION_CYBER_CLIMB || a == QS_ACTION_CYBER_DIVE ||
|
||||
a == QS_ACTION_CYBER_CLIMB_LEFT || a == QS_ACTION_CYBER_CLIMB_RIGHT ||
|
||||
a == QS_ACTION_CYBER_DIVE_LEFT || a == QS_ACTION_CYBER_DIVE_RIGHT;
|
||||
bool bank = a == QS_ACTION_CYBER_BANK_LEFT || a == QS_ACTION_CYBER_BANK_RIGHT ||
|
||||
a == QS_ACTION_CYBER_CLIMB_LEFT || a == QS_ACTION_CYBER_CLIMB_RIGHT ||
|
||||
a == QS_ACTION_CYBER_DIVE_LEFT || a == QS_ACTION_CYBER_DIVE_RIGHT;
|
||||
if (!wantDown[i] || (!pitch && !bank))
|
||||
continue;
|
||||
float scale = xr_input_cyber_rotation_scale(s_binding[a]);
|
||||
if (pitch) {
|
||||
pitchScale = havePitch ? fmaxf(pitchScale, scale) : scale;
|
||||
havePitch = true;
|
||||
}
|
||||
if (bank) {
|
||||
bankScale = haveBank ? fmaxf(bankScale, scale) : scale;
|
||||
haveBank = true;
|
||||
}
|
||||
}
|
||||
qs_cyber_pitch_scale = pitchScale;
|
||||
qs_cyber_bank_scale = bankScale;
|
||||
}
|
||||
|
||||
void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
||||
if (g_action_set == XR_NULL_HANDLE)
|
||||
return;
|
||||
@@ -1718,13 +1976,32 @@ void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
||||
// ray-dispatch loop below.
|
||||
xr_input_update_raw_states();
|
||||
xr_input_process_capture();
|
||||
// A free-view action's button held: that hand's stick looks around and
|
||||
// its own direction bindings are suspended (see xr_input_apply_free_view()).
|
||||
bool freeView[2];
|
||||
for (int hand = 0; hand < 2; hand++) {
|
||||
QsControlAction viewAction =
|
||||
hand == LEFT ? QS_ACTION_FREE_LEFT_THUMBSTICK_VIEW : QS_ACTION_FREE_RIGHT_THUMBSTICK_VIEW;
|
||||
QsRawInput viewRaw = s_binding[viewAction];
|
||||
freeView[hand] = viewRaw < QS_RAW_CONCRETE_COUNT && !menuVisible &&
|
||||
!xr_input_action_suppressed_by_capture(viewAction) && g_raw_down[viewRaw];
|
||||
}
|
||||
bool inCyber = qs_in_cyberspace() != 0;
|
||||
bool wantDown[QS_NUM_KEY_ACTIONS];
|
||||
for (size_t i = 0; i < QS_NUM_KEY_ACTIONS; i++) {
|
||||
const QsKeyAction *keyAction = &kKeyActions[i];
|
||||
QsRawInput raw = s_binding[keyAction->action];
|
||||
wantDown[i] = raw != QS_RAW_NONE && !xr_input_action_suppressed_by_capture(keyAction->action) &&
|
||||
!(xr_input_raw_is_thumbstick_direction(raw) && menuVisible) && g_raw_down[raw];
|
||||
QsActionContext context = xr_input_action_context(keyAction->action);
|
||||
bool contextActive = context == QS_CONTEXT_ALWAYS ||
|
||||
(context == QS_CONTEXT_CYBER) == inCyber;
|
||||
wantDown[i] = raw != QS_RAW_NONE && contextActive &&
|
||||
!xr_input_action_suppressed_by_capture(keyAction->action) &&
|
||||
!(xr_input_raw_is_thumbstick_direction(raw) &&
|
||||
(menuVisible || freeView[xr_input_raw_input_hand(raw)])) &&
|
||||
g_raw_down[raw];
|
||||
}
|
||||
xr_input_update_cyber_scales(wantDown);
|
||||
xr_input_apply_free_view(freeView);
|
||||
for (size_t i = 0; i < QS_NUM_KEY_ACTIONS; i++) {
|
||||
const QsKeyAction *keyAction = &kKeyActions[i];
|
||||
bool down = wantDown[i];
|
||||
@@ -1781,6 +2058,10 @@ void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
||||
} kWatchedKeys[] = {
|
||||
{SDLK_UP, 0x7E}, {SDLK_DOWN, 0x7D}, {SDLK_LEFT, 0x7B}, {SDLK_RIGHT, 0x7C}, {SDLK_SPACE, 0x31},
|
||||
{SDLK_RETURN, 0x24},
|
||||
// Cyberspace movement keys (letters and keypad, movekeys.h's CODE_*).
|
||||
{SDLK_w, 0x0D}, {SDLK_a, 0x00}, {SDLK_s, 0x01}, {SDLK_d, 0x02}, {SDLK_q, 0x0C},
|
||||
{SDLK_e, 0x0E}, {SDLK_x, 0x07}, {SDLK_KP_7, 0x59}, {SDLK_KP_9, 0x5C}, {SDLK_KP_1, 0x53},
|
||||
{SDLK_KP_3, 0x55},
|
||||
};
|
||||
static Uint32 s_stuck_since[sizeof(kWatchedKeys) / sizeof(kWatchedKeys[0])];
|
||||
for (size_t w = 0; w < sizeof(kWatchedKeys) / sizeof(kWatchedKeys[0]); w++) {
|
||||
@@ -2096,7 +2377,9 @@ void xr_input_sync_and_draw(XrSpace baseSpace, XrTime time, bool draw) {
|
||||
py = 0;
|
||||
else if (py >= gameHeight)
|
||||
py = gameHeight - 1;
|
||||
xr_mouse_move(px, py);
|
||||
// Mouse look keeps the engine's cursor centred; don't fight it.
|
||||
if (!freeView[LEFT] && !freeView[RIGHT])
|
||||
xr_mouse_move(px, py);
|
||||
}
|
||||
if (selectDownEdge && gameHit) {
|
||||
xr_mouse_click(true);
|
||||
@@ -2144,6 +2427,11 @@ bool xr_input_get_beam_layer(int hand, XrCompositionLayerQuad *out_quad) {
|
||||
}
|
||||
|
||||
void xr_input_shutdown(void) {
|
||||
if (g_free_view_forced) {
|
||||
mlook_enabled = g_free_view_saved_mlook;
|
||||
SDL_ShowCursor(g_free_view_saved_cursor ? SDL_ENABLE : SDL_DISABLE);
|
||||
g_free_view_forced = false;
|
||||
}
|
||||
// Clearing g_key_action_prev below without releasing what it says is
|
||||
// held would leave the engine's own held-key state stuck on (nothing
|
||||
// else ever sends that key's release).
|
||||
|
||||
@@ -103,7 +103,7 @@ public class MenuOverlay extends OverlayPanel {
|
||||
private static final String[] ACTION_KEYS = {
|
||||
"move_forward", "move_back", "strafe_left", "strafe_right",
|
||||
"turn_left", "turn_right", "jump", "mouse_click", "mouse_double_click",
|
||||
"stand", "crouch", "prone", "toggle_freelook", "pause",
|
||||
"stand", "crouch", "prone", "pause",
|
||||
"full_view", "normal_view", "map_view", "save_game", "load_game",
|
||||
"select_grenade", "arm_grenade", "select_drug", "use_drug",
|
||||
"toggle_online_help", "show_help", "toggle_music", "quit",
|
||||
@@ -112,7 +112,14 @@ public class MenuOverlay extends OverlayPanel {
|
||||
"clear_fullscreen",
|
||||
"mfd_left_1", "mfd_left_2", "mfd_left_3", "mfd_left_4", "mfd_left_5",
|
||||
"mfd_right_1", "mfd_right_2", "mfd_right_3", "mfd_right_4", "mfd_right_5",
|
||||
"fire_weapon",
|
||||
"fire_weapon", "free_left_thumbstick_view", "free_right_thumbstick_view",
|
||||
"cyber_thrust", "cyber_climb", "cyber_dive", "cyber_bank_left", "cyber_bank_right",
|
||||
"cyber_roll_left", "cyber_roll_right", "cyber_climb_left", "cyber_climb_right",
|
||||
"cyber_dive_left", "cyber_dive_right",
|
||||
};
|
||||
// Shown first in the Controller Options list, in this order.
|
||||
private static final String[] FIRST_ROW_ACTIONS = {
|
||||
"fire_weapon", "free_left_thumbstick_view", "free_right_thumbstick_view",
|
||||
};
|
||||
private static final String CONTROLLER_PROFILES_DIR = QuestShockActivity.GAME_DIR + "/controller_profiles";
|
||||
// Shown (with its own explanatory note - see Strings.java's
|
||||
@@ -599,12 +606,21 @@ public class MenuOverlay extends OverlayPanel {
|
||||
controlRows = new LinearLayout[ACTION_KEYS.length];
|
||||
controlActionLabels = new TextView[ACTION_KEYS.length];
|
||||
controlBindingButtons = new Button[ACTION_KEYS.length];
|
||||
// Rows are created in display order (Fire Weapon first, then the
|
||||
// rest in ACTION_KEYS order); every array below stays indexed by
|
||||
// Rows are created in display order (FIRST_ROW_ACTIONS first, then
|
||||
// the rest in ACTION_KEYS order); every array below stays indexed by
|
||||
// the real action index, which must keep matching xr_input.c.
|
||||
int fireIndex = Arrays.asList(ACTION_KEYS).indexOf("fire_weapon");
|
||||
int[] displayOrder = new int[ACTION_KEYS.length];
|
||||
int filled = 0;
|
||||
for (String key : FIRST_ROW_ACTIONS) {
|
||||
displayOrder[filled++] = Arrays.asList(ACTION_KEYS).indexOf(key);
|
||||
}
|
||||
for (int k = 0; k < ACTION_KEYS.length; k++) {
|
||||
if (!Arrays.asList(FIRST_ROW_ACTIONS).contains(ACTION_KEYS[k])) {
|
||||
displayOrder[filled++] = k;
|
||||
}
|
||||
}
|
||||
for (int d = 0; d < ACTION_KEYS.length; d++) {
|
||||
final int i = d == 0 ? fireIndex : (d - 1 < fireIndex ? d - 1 : d);
|
||||
final int i = displayOrder[d];
|
||||
final int actionIndex = i;
|
||||
LinearLayout row = new LinearLayout(activity);
|
||||
row.setOrientation(LinearLayout.HORIZONTAL);
|
||||
|
||||
@@ -79,8 +79,6 @@ final class Strings {
|
||||
put("action_stand", "Stand", "Debout", "Stehen");
|
||||
put("action_crouch", "Crouch", "Accroupi", "Ducken");
|
||||
put("action_prone", "Prone", "Couché", "Liegen");
|
||||
put("action_toggle_freelook", "Toggle Free Look", "Basculer la vue libre",
|
||||
"Freies Umsehen umschalten");
|
||||
put("action_pause", "Pause Game", "Mettre en pause", "Spiel pausieren");
|
||||
put("action_full_view", "Fullscreen View", "Vue plein écran", "Vollbildansicht");
|
||||
put("action_normal_view", "Normal View", "Vue normale", "Normale Ansicht");
|
||||
@@ -121,6 +119,34 @@ final class Strings {
|
||||
put("action_mfd_right_4", "Right MFD: Target", "MFD droit : cible", "Rechtes MFD: Ziel");
|
||||
put("action_mfd_right_5", "Right MFD: Info", "MFD droit : info", "Rechtes MFD: Info");
|
||||
put("action_fire_weapon", "Fire Weapon", "Tirer", "Waffe abfeuern");
|
||||
put("action_free_left_thumbstick_view", "Free View (Left Stick)",
|
||||
"Vue libre (stick gauche)", "Freie Ansicht (linker Stick)");
|
||||
put("action_free_right_thumbstick_view", "Free View (Right Stick)",
|
||||
"Vue libre (stick droit)", "Freie Ansicht (rechter Stick)");
|
||||
|
||||
// Cyberspace actions (active only in cyberspace, see xr_input.c).
|
||||
put("action_cyber_thrust", "Cyberspace: Thrust", "Cyberespace : poussée",
|
||||
"Cyberspace: Schub");
|
||||
put("action_cyber_climb", "Cyberspace: Climb", "Cyberespace : monter",
|
||||
"Cyberspace: Steigen");
|
||||
put("action_cyber_dive", "Cyberspace: Dive", "Cyberespace : plonger",
|
||||
"Cyberspace: Sinken");
|
||||
put("action_cyber_bank_left", "Cyberspace: Bank Left", "Cyberespace : virage à gauche",
|
||||
"Cyberspace: Links kippen");
|
||||
put("action_cyber_bank_right", "Cyberspace: Bank Right", "Cyberespace : virage à droite",
|
||||
"Cyberspace: Rechts kippen");
|
||||
put("action_cyber_roll_left", "Cyberspace: Roll Left", "Cyberespace : roulis gauche",
|
||||
"Cyberspace: Links rollen");
|
||||
put("action_cyber_roll_right", "Cyberspace: Roll Right", "Cyberespace : roulis droit",
|
||||
"Cyberspace: Rechts rollen");
|
||||
put("action_cyber_climb_left", "Cyberspace: Climb Left", "Cyberespace : monter à gauche",
|
||||
"Cyberspace: Links steigen");
|
||||
put("action_cyber_climb_right", "Cyberspace: Climb Right", "Cyberespace : monter à droite",
|
||||
"Cyberspace: Rechts steigen");
|
||||
put("action_cyber_dive_left", "Cyberspace: Dive Left", "Cyberespace : plonger à gauche",
|
||||
"Cyberspace: Links sinken");
|
||||
put("action_cyber_dive_right", "Cyberspace: Dive Right", "Cyberespace : plonger à droite",
|
||||
"Cyberspace: Rechts sinken");
|
||||
put("capture_prompt", "Press a button…", "Appuyez sur un bouton…", "Taste drücken…");
|
||||
put("reset_to_defaults", "Reset to Defaults", "Réinitialiser", "Auf Standard zurücksetzen");
|
||||
put("generic_profile_note",
|
||||
|
||||
Reference in New Issue
Block a user