diff options
Diffstat (limited to 'src/uqm/gameinp.c')
-rw-r--r-- | src/uqm/gameinp.c | 496 |
1 files changed, 496 insertions, 0 deletions
diff --git a/src/uqm/gameinp.c b/src/uqm/gameinp.c new file mode 100644 index 0000000..66e667f --- /dev/null +++ b/src/uqm/gameinp.c @@ -0,0 +1,496 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include "controls.h" +#include "battlecontrols.h" +#include "init.h" +#include "intel.h" + // For computer_intelligence +#ifdef NETPLAY +# include "supermelee/netplay/netmelee.h" +#endif +#include "settings.h" +#include "sounds.h" +#include "tactrans.h" +#include "uqmdebug.h" +#include "libs/async.h" +#include "libs/inplib.h" +#include "libs/timelib.h" +#include "libs/threadlib.h" + + +#define ACCELERATION_INCREMENT (ONE_SECOND / 12) +#define MENU_REPEAT_DELAY (ONE_SECOND / 2) + + +typedef struct +{ + BOOLEAN (*InputFunc) (void *pInputState); +} INPUT_STATE_DESC; + +/* These static variables are the values that are set by the controllers. */ + +typedef struct +{ + DWORD key [NUM_TEMPLATES][NUM_KEYS]; + DWORD menu [NUM_MENU_KEYS]; +} MENU_ANNOTATIONS; + + +CONTROL_TEMPLATE PlayerControls[NUM_PLAYERS]; +CONTROLLER_INPUT_STATE CurrentInputState, PulsedInputState; +static CONTROLLER_INPUT_STATE CachedInputState, OldInputState; +static MENU_ANNOTATIONS RepeatDelays, Times; +static DWORD GestaltRepeatDelay, GestaltTime; +static BOOLEAN OldGestalt, CachedGestalt; +static DWORD _max_accel, _min_accel, _step_accel; +static BOOLEAN _gestalt_keys; + +static MENU_SOUND_FLAGS sound_0, sound_1; + +volatile CONTROLLER_INPUT_STATE ImmediateInputState; + +volatile BOOLEAN ExitRequested; +volatile BOOLEAN GamePaused; + +static InputFrameCallback *inputCallback; + +static void +_clear_menu_state (void) +{ + int i, j; + for (i = 0; i < NUM_TEMPLATES; i++) + { + for (j = 0; j < NUM_KEYS; j++) + { + PulsedInputState.key[i][j] = 0; + CachedInputState.key[i][j] = 0; + } + } + for (i = 0; i < NUM_MENU_KEYS; i++) + { + PulsedInputState.menu[i] = 0; + CachedInputState.menu[i] = 0; + } + CachedGestalt = FALSE; +} + +void +ResetKeyRepeat (void) +{ + DWORD initTime = GetTimeCounter (); + int i, j; + for (i = 0; i < NUM_TEMPLATES; i++) + { + for (j = 0; j < NUM_KEYS; j++) + { + RepeatDelays.key[i][j] = _max_accel; + Times.key[i][j] = initTime; + } + } + for (i = 0; i < NUM_MENU_KEYS; i++) + { + RepeatDelays.menu[i] = _max_accel; + Times.menu[i] = initTime; + } + GestaltRepeatDelay = _max_accel; + GestaltTime = initTime; +} + +static void +_check_for_pulse (int *current, int *cached, int *old, DWORD *accel, + DWORD *newtime, DWORD *oldtime) +{ + if (*cached && *old) + { + if (*newtime - *oldtime < *accel) + { + *current = 0; + } + else + { + *current = *cached; + if (*accel > _min_accel) + *accel -= _step_accel; + if (*accel < _min_accel) + *accel = _min_accel; + *oldtime = *newtime; + } + } + else + { + *current = *cached; + *oldtime = *newtime; + *accel = _max_accel; + } +} + +/* BUG: If a key from a currently unused control template is held, + * this will affect the gestalt repeat rate. This isn't a problem + * *yet*, but it will be once the user gets to define control + * templates on his own --McM */ +static void +_check_gestalt (DWORD NewTime) +{ + BOOLEAN CurrentGestalt; + int i, j; + OldGestalt = CachedGestalt; + + CachedGestalt = 0; + CurrentGestalt = 0; + for (i = 0; i < NUM_TEMPLATES; i++) + { + for (j = 0; j < NUM_KEYS; j++) + { + CachedGestalt |= ImmediateInputState.key[i][j]; + CurrentGestalt |= PulsedInputState.key[i][j]; + } + } + for (i = 0; i < NUM_MENU_KEYS; i++) + { + CachedGestalt |= ImmediateInputState.menu[i]; + CurrentGestalt |= PulsedInputState.menu[i]; + } + + if (OldGestalt && CachedGestalt) + { + if (NewTime - GestaltTime < GestaltRepeatDelay) + { + for (i = 0; i < NUM_TEMPLATES; i++) + { + for (j = 0; j < NUM_KEYS; j++) + { + PulsedInputState.key[i][j] = 0; + } + } + for (i = 0; i < NUM_MENU_KEYS; i++) + { + PulsedInputState.menu[i] = 0; + } + } + else + { + for (i = 0; i < NUM_TEMPLATES; i++) + { + for (j = 0; j < NUM_KEYS; j++) + { + PulsedInputState.key[i][j] = CachedInputState.key[i][j]; + } + } + for (i = 0; i < NUM_MENU_KEYS; i++) + { + PulsedInputState.menu[i] = CachedInputState.menu[i]; + } + if (GestaltRepeatDelay > _min_accel) + GestaltRepeatDelay -= _step_accel; + if (GestaltRepeatDelay < _min_accel) + GestaltRepeatDelay = _min_accel; + GestaltTime = NewTime; + } + } + else + { + for (i = 0; i < NUM_TEMPLATES; i++) + { + for (j = 0; j < NUM_KEYS; j++) + { + PulsedInputState.key[i][j] = CachedInputState.key[i][j]; + } + } + for (i = 0; i < NUM_MENU_KEYS; i++) + { + PulsedInputState.menu[i] = CachedInputState.menu[i]; + } + GestaltTime = NewTime; + GestaltRepeatDelay = _max_accel; + } +} + +void +UpdateInputState (void) +{ + DWORD NewTime; + /* First, if the game is, in fact, paused, we stall until + * unpaused. Every thread with control over game logic calls + * UpdateInputState routinely, so we handle pause and exit + * state updates here. */ + + // Automatically pause and enter low-activity state while inactive, + // for example, window minimized. + if (!GameActive) + SleepGame (); + + if (GamePaused) + PauseGame (); + + if (ExitRequested) + ConfirmExit (); + + CurrentInputState = ImmediateInputState; + OldInputState = CachedInputState; + CachedInputState = ImmediateInputState; + BeginInputFrame (); + NewTime = GetTimeCounter (); + if (_gestalt_keys) + { + _check_gestalt (NewTime); + } + else + { + int i, j; + for (i = 0; i < NUM_TEMPLATES; i++) + { + for (j = 0; j < NUM_KEYS; j++) + { + _check_for_pulse (&PulsedInputState.key[i][j], + &CachedInputState.key[i][j], &OldInputState.key[i][j], + &RepeatDelays.key[i][j], &NewTime, &Times.key[i][j]); + } + } + for (i = 0; i < NUM_MENU_KEYS; i++) + { + _check_for_pulse (&PulsedInputState.menu[i], + &CachedInputState.menu[i], &OldInputState.menu[i], + &RepeatDelays.menu[i], &NewTime, &Times.menu[i]); + } + } + + if (CurrentInputState.menu[KEY_PAUSE]) + GamePaused = TRUE; + + if (CurrentInputState.menu[KEY_EXIT]) + ExitRequested = TRUE; + +#if defined(DEBUG) || defined(USE_DEBUG_KEY) + if (PulsedInputState.menu[KEY_DEBUG]) + debugKeyPressedSynchronous (); +#endif +} + +InputFrameCallback * +SetInputCallback (InputFrameCallback *callback) +{ + InputFrameCallback *old = inputCallback; + + // Replacing an existing callback with another is not a problem, + // but currently this should never happen, which is why the assert. + assert (!old || !callback); + inputCallback = callback; + + return old; +} + +void +SetMenuRepeatDelay (DWORD min, DWORD max, DWORD step, BOOLEAN gestalt) +{ + _min_accel = min; + _max_accel = max; + _step_accel = step; + _gestalt_keys = gestalt; + //_clear_menu_state (); + ResetKeyRepeat (); +} + +void +SetDefaultMenuRepeatDelay (void) +{ + _min_accel = ACCELERATION_INCREMENT; + _max_accel = MENU_REPEAT_DELAY; + _step_accel = ACCELERATION_INCREMENT; + _gestalt_keys = FALSE; + //_clear_menu_state (); + ResetKeyRepeat (); +} + +void +FlushInput (void) +{ + TFB_ResetControls (); + _clear_menu_state (); +} + +static MENU_SOUND_FLAGS +MenuKeysToSoundFlags (const CONTROLLER_INPUT_STATE *state) +{ + MENU_SOUND_FLAGS soundFlags; + + soundFlags = MENU_SOUND_NONE; + if (state->menu[KEY_MENU_UP]) + soundFlags |= MENU_SOUND_UP; + if (state->menu[KEY_MENU_DOWN]) + soundFlags |= MENU_SOUND_DOWN; + if (state->menu[KEY_MENU_LEFT]) + soundFlags |= MENU_SOUND_LEFT; + if (state->menu[KEY_MENU_RIGHT]) + soundFlags |= MENU_SOUND_RIGHT; + if (state->menu[KEY_MENU_SELECT]) + soundFlags |= MENU_SOUND_SELECT; + if (state->menu[KEY_MENU_CANCEL]) + soundFlags |= MENU_SOUND_CANCEL; + if (state->menu[KEY_MENU_SPECIAL]) + soundFlags |= MENU_SOUND_SPECIAL; + if (state->menu[KEY_MENU_PAGE_UP]) + soundFlags |= MENU_SOUND_PAGEUP; + if (state->menu[KEY_MENU_PAGE_DOWN]) + soundFlags |= MENU_SOUND_PAGEDOWN; + if (state->menu[KEY_MENU_DELETE]) + soundFlags |= MENU_SOUND_DELETE; + if (state->menu[KEY_MENU_BACKSPACE]) + soundFlags |= MENU_SOUND_DELETE; + + return soundFlags; +} + +void +DoInput (void *pInputState, BOOLEAN resetInput) +{ + if (resetInput) + FlushInput (); + + do + { + MENU_SOUND_FLAGS soundFlags; + Async_process (); + TaskSwitch (); + + UpdateInputState (); + +#if DEMO_MODE || CREATE_JOURNAL + if (ArrowInput != DemoInput) +#endif + { +#if CREATE_JOURNAL + JournalInput (InputState); +#endif /* CREATE_JOURNAL */ + } + + soundFlags = MenuKeysToSoundFlags (&PulsedInputState); + + if (MenuSounds && (soundFlags & (sound_0 | sound_1))) + { + SOUND S; + + S = MenuSounds; + if (soundFlags & sound_1) + S = SetAbsSoundIndex (S, MENU_SOUND_SUCCESS); + + PlaySoundEffect (S, 0, NotPositional (), NULL, 0); + } + + if (inputCallback) + inputCallback (); + + } while (((INPUT_STATE_DESC*)pInputState)->InputFunc (pInputState)); + + if (resetInput) + FlushInput (); +} + +void +SetMenuSounds (MENU_SOUND_FLAGS s0, MENU_SOUND_FLAGS s1) +{ + sound_0 = s0; + sound_1 = s1; +} + +void +GetMenuSounds (MENU_SOUND_FLAGS *s0, MENU_SOUND_FLAGS *s1) +{ + *s0 = sound_0; + *s1 = sound_1; +} + +static BATTLE_INPUT_STATE +ControlInputToBattleInput (const int *keyState) +{ + BATTLE_INPUT_STATE InputState = 0; + + if (keyState[KEY_UP]) + InputState |= BATTLE_THRUST; + if (keyState[KEY_LEFT]) + InputState |= BATTLE_LEFT; + if (keyState[KEY_RIGHT]) + InputState |= BATTLE_RIGHT; + if (keyState[KEY_WEAPON]) + InputState |= BATTLE_WEAPON; + if (keyState[KEY_SPECIAL]) + InputState |= BATTLE_SPECIAL; + if (keyState[KEY_ESCAPE]) + InputState |= BATTLE_ESCAPE; + if (keyState[KEY_DOWN]) + InputState |= BATTLE_DOWN; + + return InputState; +} + +BATTLE_INPUT_STATE +CurrentInputToBattleInput (COUNT player) +{ + return ControlInputToBattleInput( + CurrentInputState.key[PlayerControls[player]]); +} + +BATTLE_INPUT_STATE +PulsedInputToBattleInput (COUNT player) +{ + return ControlInputToBattleInput( + PulsedInputState.key[PlayerControls[player]]); +} + +BOOLEAN +AnyButtonPress (BOOLEAN CheckSpecial) +{ + int i, j; + (void) CheckSpecial; // Ignored + UpdateInputState (); + for (i = 0; i < NUM_TEMPLATES; i++) + { + for (j = 0; j < NUM_KEYS; j++) + { + if (CurrentInputState.key[i][j]) + return TRUE; + } + } + for (i = 0; i < NUM_MENU_KEYS; i++) + { + if (CurrentInputState.menu[i]) + return TRUE; + } + return FALSE; +} + +BOOLEAN +ConfirmExit (void) +{ + DWORD old_max_accel, old_min_accel, old_step_accel; + BOOLEAN old_gestalt_keys, result; + + old_max_accel = _max_accel; + old_min_accel = _min_accel; + old_step_accel = _step_accel; + old_gestalt_keys = _gestalt_keys; + + SetDefaultMenuRepeatDelay (); + + result = DoConfirmExit (); + + SetMenuRepeatDelay (old_min_accel, old_max_accel, old_step_accel, + old_gestalt_keys); + return result; +} + |