summaryrefslogtreecommitdiff
path: root/src/uqm/gameinp.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/uqm/gameinp.c')
-rw-r--r--src/uqm/gameinp.c496
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;
+}
+