/** * @file input.c * * @brief Handle all the keybindings and input. */ #include "lephisto.h" #include "log.h" #include "player.h" #include "pause.h" #include "toolkit.h" #include "menu.h" #include "board.h" #include "map.h" #include "escort.h" #include "land.h" #include "lstd.h" #include "gui.h" #include "input.h" #define KEY_PRESS ( 1.) /**< Key is pressed. */ #define KEY_RELEASE (-1.) /**< Key is released. */ /* Keybind structure. */ /** * @brief Lephisto Keybinding. */ typedef struct Keybind_ { char* name; /**< Keybinding name, taken from keybindNames[] */ KeybindType type; /**< type, defined in player.h. */ SDLKey key; /**< Key/axis/button event number. */ SDLMod mod; /**< Key modifiers (where applicable). */ } Keybind; static Keybind** input_keybinds; /**< Contains the players keybindings. */ /* Name of each keybinding. */ const char* keybindNames[] = { /* Movement. */ "accel", "left", "right", "reverse", "afterburn", /* Targetting. */ "target", "target_prev", "target_nearest", "target_hostile", /* Fighting. */ "primary", "face", "board", /* Secondary weapons. */ "secondary", "secondary_next", "secondary_prev", /* Escorts. */ "e_targetNext", "e_targetPrev", "e_attack", "e_hold", "e_return", "e_clear", /* Space Navigation. */ "autonav", "target_planet", "land", "thyperspace","starmap", "jump", /* Communication. */ "hail", /* Misc. */ "mapzoomin", "mapzoomout", "screenshot", "pause", "speed", "menu", "info", /* Must terminate in "end". */ "end" /**< Names of possible keybindings. */ }; /* Keybinding descriptions. Should match in position of the names. */ const char* keybindDescription[] = { /* Movement. */ "Makes your ship accelerate forward.", "Makes your ship turn left.", "Makes your ship turn right.", "Makes your ship turn around and face the direction you're moving from. Good for braking.", "Makes your ship afterburn if you have an afterburner installed.", /* Targetting. */ "Cycles through ship targets.", "Cycles backwards through ship targets.", "Targets the nearest non-disabled ship.", "Targets the nearest hostile ship.", /* Fighting. */ "Fires your primary weapons.", "Faces your target (ship target if you have one, otherwise planet target).", "Attempts to board your target ship.", /* Secondary weapons. */ "Fires your secondary weapon.", "Cycles through secondary weapons.", "Cycles backwards through secondary weapons.", /* Escorts. */ "Cycles through your esorts.", "Cycles backwards through your escorts.", "Tells your escorts to attack your target.", "Tells your escorts to hold their posistion.", "Tells your escorts to return to your ships hanger.", "Clears your escorts commands.", /* Space navigation. */ "Initializes the autonavigation system.", "Cycles through planet targets.", "Attempt to land on your targetted planet or targets nearest landable planet. \ Requests for landing if you don't have permission yet.", "Cycles through hyperspace targets.", "Opens the Star Map.", "Attempt to jump to your hyperspace target.", /* Communication. */ "Attempts to initialize communication with your taretted ship.", /* Misc. */ "Zooms in on your radar.", "Zooms out on your radar.", "Takes a screenshot.", "Toggles 2x speed modifier.", "Pauses the game.", "Opens the small ingame menu.", "Opens the information menu." }; /**< Descriptions of the keybindings. Should be in the same position as the matching keybinding name. */ /* Accel hacks. */ static unsigned int input_accelLast = 0; /**< Used to see if double tap. */ unsigned int input_afterburnSensitivity = 200; /**< ms between taps to afterburn. */ extern double player_left; /**< player.c */ extern double player_right; /**< player.c */ #if SDL_VERSION_ATLEAST(1,3,0) # define INPUT_NUMKEYS SDL_NUM_SCANCODES /**< Number of keys available. */ #else /* SDL_VERSION_ATLEASE(1,3,0) */ # define INPUT_NUMKEYS SDLK_LAST /**< Number of keys available. */ #endif /* SDL_VERSION_ATLEAST(1,3,0) */ static const char* keyconv[INPUT_NUMKEYS]; /**< Key conversion table. */ static void input_keyConvGen(void); static void input_keyConvDestroy(void); /** * @brief Set the default input keys. */ void input_setDefault(void) { /* Movement. */ input_setKeybind("accel", KEYBIND_KEYBOARD, SDLK_w, KMOD_ALL); input_setKeybind("afterburn", KEYBIND_NULL, SDLK_UNKNOWN, KMOD_NONE); input_setKeybind("left", KEYBIND_KEYBOARD, SDLK_a, KMOD_ALL); input_setKeybind("right", KEYBIND_KEYBOARD, SDLK_d, KMOD_ALL); input_setKeybind("reverse", KEYBIND_KEYBOARD, SDLK_s, KMOD_ALL); /* Targetting. */ input_setKeybind("target", KEYBIND_KEYBOARD, SDLK_TAB, KMOD_NONE); input_setKeybind("target_prev", KEYBIND_KEYBOARD, SDLK_TAB, KMOD_RCTRL); input_setKeybind("target_nearest", KEYBIND_KEYBOARD, SDLK_t, KMOD_NONE); input_setKeybind("target_hostile", KEYBIND_KEYBOARD, SDLK_r, KMOD_NONE); /* Combat. */ input_setKeybind("primary", KEYBIND_KEYBOARD, SDLK_SPACE, KMOD_ALL); input_setKeybind("face", KEYBIND_KEYBOARD, SDLK_f, KMOD_NONE); input_setKeybind("board", KEYBIND_KEYBOARD, SDLK_b, KMOD_NONE); /* Escorts. */ input_setKeybind("e_targetNext", KEYBIND_KEYBOARD, SDLK_g, KMOD_NONE); /* Tmp keybind. */ input_setKeybind("e_targetPrev", KEYBIND_KEYBOARD, SDLK_g, KMOD_LCTRL); /* Tmp keybind. */ input_setKeybind("e_attack", KEYBIND_KEYBOARD, SDLK_1, KMOD_NONE); input_setKeybind("e_hold", KEYBIND_KEYBOARD, SDLK_2, KMOD_NONE); input_setKeybind("e_return", KEYBIND_KEYBOARD, SDLK_3, KMOD_NONE); input_setKeybind("e_clear", KEYBIND_KEYBOARD, SDLK_4, KMOD_NONE); /* Secondary weapon. */ input_setKeybind("secondary", KEYBIND_KEYBOARD, SDLK_LALT, KMOD_ALL); input_setKeybind("secondary_next", KEYBIND_KEYBOARD, SDLK_e, KMOD_NONE); input_setKeybind("secondary_prev", KEYBIND_KEYBOARD, SDLK_e, KMOD_LCTRL); /* Space */ input_setKeybind("autonav", KEYBIND_KEYBOARD, SDLK_j, KMOD_LCTRL); input_setKeybind("target_planet", KEYBIND_KEYBOARD, SDLK_p, KMOD_NONE); input_setKeybind("land", KEYBIND_KEYBOARD, SDLK_l, KMOD_NONE); input_setKeybind("thyperspace", KEYBIND_KEYBOARD, SDLK_h, KMOD_NONE); input_setKeybind("starmap", KEYBIND_KEYBOARD, SDLK_m, KMOD_NONE); input_setKeybind("jump", KEYBIND_KEYBOARD, SDLK_j, KMOD_NONE); /* Communication. */ input_setKeybind("hail", KEYBIND_KEYBOARD, SDLK_y, KMOD_NONE); /* Misc. */ input_setKeybind("mapzoomin", KEYBIND_KEYBOARD, SDLK_KP_PLUS, KMOD_ALL); input_setKeybind("mapzoomout", KEYBIND_KEYBOARD, SDLK_KP_MINUS, KMOD_ALL); input_setKeybind("screenshot", KEYBIND_KEYBOARD, SDLK_KP_MULTIPLY, KMOD_ALL); input_setKeybind("pause", KEYBIND_KEYBOARD, SDLK_F1, KMOD_NONE); input_setKeybind("speed", KEYBIND_KEYBOARD, SDLK_BACKQUOTE, KMOD_ALL); input_setKeybind("menu", KEYBIND_KEYBOARD, SDLK_ESCAPE, KMOD_ALL); input_setKeybind("info", KEYBIND_KEYBOARD, SDLK_i, KMOD_NONE); } /** * @brief Initialize the input subsystem (does not set keys). */ void input_init(void) { Keybind* tmp; int i; #if SDL_VERSION_ATLEAST(1,3,0) /* Window. */ SDL_EventState(SDL_WINDOW_EVENT, SDL_DISABLE); sDL_EventState(SDL_SYSWMEVENT, SDL_DISABLE); /* Keyboard. */ SDL_EventState(SDL_KEYDOWN, SDL_ENABLE); SDL_EventState(SDL_KEYUP, SDL_ENABLE); SDL_EventState(SDL_TEXTINPUT, SDL_DISABLE); /* Mice. */ SDL_EventState(SDL_MOUSEMOTION, SDL_ENABLE); SDL_EventState(SDL_MOUSEBUTTONDOWN, SDL_ENABLE); SDL_EventState(SDL_MOUSEBUTTONUP, SDL_ENABLE); SDL_EventState(SDL_MOUSEWHEEL, SDL_ENABLE); /* Joystick, enabled in joystick.c if needed. */ SDL_EventState(SDL_JOYAXISMOTION, SDL_DISABLE); SDL_EventState(SDL_JOYHATMOTION, SDL_DISABLE); SDL_EventState(SDL_JOYBUTTONDOWN, SDL_DISABLE); SDL_EventState(SDL_JOYBUTTONUP, SDL_DISABLE); /* Quit. */ SDL_EventState(SDL_QUIT, SDL_ENABLE); /* Proximity. */ SDL_EventState(SDL_PROXIMITYIN, SDL_DISABLE); SDL_EventState(SDL_PROXIMITYOUT, SDL_DISABLE); #endif /* Get the number of keybindings. */ for(i = 0; strcmp(keybindNames[i], "end"); i++); input_keybinds = malloc(i*sizeof(Keybind*)); /* Create sane null keybinding for each. */ for(i = 0; strcmp(keybindNames[i], "end"); i++) { tmp = malloc(sizeof(Keybind)); tmp->name = (char*)keybindNames[i]; tmp->type = KEYBIND_NULL; tmp->key = SDLK_UNKNOWN; tmp->mod = KMOD_NONE; input_keybinds[i] = tmp; } /* Generate key translation table. */ input_keyConvGen(); } /** * @brief Exit the input subsystem. */ void input_exit(void) { int i; for(i = 0; strcmp(keybindNames[i], "end"); i++) free(input_keybinds[i]); free(input_keybinds); input_keyConvDestroy(); } /** * @brief Create the key conversion table. */ static void input_keyConvGen(void) { SDLKey k; for(k = 0; k < INPUT_NUMKEYS; k++) keyconv[k] = strdup(SDL_GetKeyName(k)); } /** * @brief Destroy the key conversion table. */ static void input_keyConvDestroy(void) { int i; for(i = 0; i < INPUT_NUMKEYS; i++) free(keyconv[i]); } /** * @brief Get the key id from its name. * @param name Name of the key to get id from. * @return ID of the key. */ SDLKey input_keyConv(char* name) { SDLKey k, m; size_t l; char buf; l = strlen(name); buf = tolower(name[0]); /* Compare for single character. */ if(l == 1) { m = MIN(256, INPUT_NUMKEYS); for(k = 0; k < m; k++) { /* Only valid for char range. */ /* Must not be NULL. */ if(keyconv[k] == NULL) continue; /* Check if it's also a single char. */ if((buf == tolower(keyconv[k][0])) && (keyconv[k][1] == '\0')) return k; } } else { /* Compare for strings. */ for(k = 0; k < INPUT_NUMKEYS; k++) { /* Must not be NULL. */ if(keyconv[k] == NULL) continue; /* Compare strings. */ if(strcmp(name, keyconv[k])==0) return k; } } WARN("Keyname '%s' doesn't match any key.", name); return SDLK_UNKNOWN; } /** * @brief Bind a key of type to action keybind. * @param keybind The name of the keybind defined above. * @param type The type of the keybind. * @param key The key to bind to. * @param mod Modifiers to check for. */ void input_setKeybind(char* keybind, KeybindType type, int key, SDLMod mod) { int i; for(i = 0; strcmp(keybindNames[i], "end"); i++) if(strcmp(keybind, input_keybinds[i]->name)==0) { input_keybinds[i]->type = type; input_keybinds[i]->key = key; /* Non-keyboards get mod KMOD_ALL to always match. */ input_keybinds[i]->mod = (type == KEYBIND_KEYBOARD) ? mod : KMOD_ALL; return; } WARN("Unable to set keybind '%s', That command does not exist.", keybind); } /** * @brief Get the value of a keybind. * @param keybind Name of the keybinding to get. * @param[out] type Store the type of the keybinding. * @param[out] mod Store the modifiers used with the keybinding. * @return The key assosciated with the keybinding. */ SDLKey input_getKeybind(const char* keybind, KeybindType* type, SDLMod* mod) { int i; for(i = 0; strcmp(keybindNames[i], "end"); i++) if(strcmp(keybind, input_keybinds[i]->name)==0) { if(type != NULL) (*type) = input_keybinds[i]->type; if(mod != NULL) (*mod) = input_keybinds[i]->mod; return input_keybinds[i]->key; } WARN("Unable to get keybinding '%s', that command doesn't exist", keybind); return (SDLKey)-1; } /** * @brief Get the description of the keybinding */ const char* input_getKeybindDescription(char* keybind) { int i; for(i = 0; strcmp(keybindNames[i], "end"); i++) if(strcmp(keybind, input_keybinds[i]->name)==0) return keybindDescription[i]; WARN("Unable to key keybinding description '%s', that cmmand doesn't exist", keybind); return NULL; } #define KEY(s) (strcmp(input_keybinds[keynum]->name, s)==0) /**< Shortcut for ease. */ #define INGAME() (!toolkit && !paused) /**< Make sure player is in game. */ #define NOHYP() \ (player && !pilot_isFlag(player, PILOT_HYP_PREP) && \ !pilot_isFlag(player, PILOT_HYP_BEGIN) && \ !pilot_isFlag(player, PILOT_HYPERSPACE)) /**< Make sure player isn't jumping. */ #define NODEAD() (player) /**< Player isn't dead. */ #define NOLAND() (!landed) /**< Player isn't landed. */ /** * @brief Run the input command. * @param keynum The index of the keybind. * @param value The value of the keypress (defined above). * @param kabs The absolute value. */ static void input_key(int keynum, double value, double kabs) { unsigned int t; /* Accelerating. */ if(KEY("accel")) { if(kabs) { player_abortAutonav(NULL); player_accel(value); } else { /* Prevent it from getting stuck. */ if(value == KEY_PRESS) { player_abortAutonav(NULL); player_accel(1.); } else if(value == KEY_RELEASE) player_accelOver(); } /* Double tap accel = afterburn! */ t = SDL_GetTicks(); if((value == KEY_PRESS) && INGAME() && NOHYP() && (t-input_accelLast <= input_afterburnSensitivity)) player_afterburn(); else if((value == KEY_RELEASE) && player_isFlag(PLAYER_AFTERBURNER)) player_afterburnOver(); if(value == KEY_PRESS) input_accelLast = t; } /* Afterburning. */ else if(KEY("afterburn") && INGAME() && NOHYP()) { if(value == KEY_PRESS) player_afterburn(); else if((value == KEY_RELEASE) && player_isFlag(PLAYER_AFTERBURNER)) player_afterburnOver(); } /* Turning left. */ else if(KEY("left")) { if(kabs) { player_abortAutonav(NULL); player_setFlag(PLAYER_TURN_LEFT); player_left = value; } else { /* Set flags for facing correction. */ if(value == KEY_PRESS) { player_abortAutonav(NULL); player_setFlag(PLAYER_TURN_LEFT); player_left = 1.; } else if(value == KEY_RELEASE) { player_rmFlag(PLAYER_TURN_LEFT); player_left = 0.; } } } /* Turning right. */ else if(KEY("right")) { if(kabs) { player_abortAutonav(NULL); player_setFlag(PLAYER_TURN_RIGHT); player_right = value; } else { /* Set flags for facing correction. */ if(value == KEY_PRESS) { player_abortAutonav(NULL); player_setFlag(PLAYER_TURN_RIGHT); player_right = 1.; } else if(value == KEY_RELEASE) { player_rmFlag(PLAYER_TURN_RIGHT); player_right = 0.; } } } /* Turn around to face vel. */ else if(KEY("reverse")) { if(value == KEY_PRESS) { player_abortAutonav(NULL); player_setFlag(PLAYER_REVERSE); } else if((value == KEY_RELEASE) && player_isFlag(PLAYER_REVERSE)) player_rmFlag(PLAYER_REVERSE); } /* Combat. */ /* Shooting primary weapon. */ else if(KEY("primary")) { if(value == KEY_PRESS) { player_abortAutonav(NULL); player_setFlag(PLAYER_PRIMARY); } else if(value == KEY_RELEASE) { player_rmFlag(PLAYER_PRIMARY); } } /* Targetting. */ else if(INGAME() && NODEAD() && KEY("target")) { if(value == KEY_PRESS) player_targetNext(); } else if(INGAME() && NODEAD() && KEY("target_prev")) { if(value == KEY_PRESS) player_targetPrev(); } else if(INGAME() && NODEAD() && KEY("target_nearest")) { if(value == KEY_PRESS) player_targetNearest(); } else if(INGAME() && NODEAD() && KEY("target_hostile")) { if(value == KEY_PRESS) player_targetHostile(); } /* Face the target. */ else if(KEY("face")) { if(value == KEY_PRESS) { player_abortAutonav(NULL); player_setFlag(PLAYER_FACE); } else if((value == KEY_RELEASE) && player_isFlag(PLAYER_FACE)) player_rmFlag(PLAYER_FACE); } /* Board those ships. */ else if(KEY("board") && INGAME() && NOHYP()) { if(value == KEY_PRESS) { player_abortAutonav(NULL); player_board(); } } /* Escorts. */ else if(INGAME() && NODEAD() && KEY("e_targetNext")) { if(value == KEY_PRESS) player_targetEscort(0); } else if(INGAME() && NODEAD() && KEY("e_targetPrev")) { if(value == KEY_PRESS) player_targetEscort(1); } else if(INGAME() && NODEAD() && KEY("e_attack")) { if(value == KEY_PRESS) escorts_attack(player); } else if(INGAME() && NODEAD() && KEY("e_hold")) { if(value == KEY_PRESS) escorts_hold(player); } else if(INGAME() && NODEAD() && KEY("e_return")) { if(value == KEY_PRESS) escorts_return(player); } else if(INGAME() && NODEAD() && KEY("e_clear")) { if(value == KEY_PRESS) escorts_clear(player); } /* Secondary weapons. */ /* Shooting secondary weapon. */ else if(KEY("secondary") && NOHYP()) { if(value == KEY_PRESS) { player_abortAutonav(NULL); player_setFlag(PLAYER_SECONDARY); } else if(value == KEY_RELEASE) player_rmFlag(PLAYER_SECONDARY); } /* Selecting secondary weapon. */ else if(KEY("secondary_next") && INGAME()) { if(value == KEY_PRESS) player_secondaryNext(); } else if(KEY("secondary_prev") && INGAME()) { if(value == KEY_PRESS) player_secondaryPrev(); } /* Space. */ else if(KEY("autonav") && INGAME() && NOHYP()) { if(value == KEY_PRESS) player_startAutonav(); } /* Target planet (cycles just like target). */ else if(KEY("target_planet") && INGAME() && NOHYP()) { if(value == KEY_PRESS) player_targetPlanet(); } /* Target nearest planet or attempt to land. */ else if(KEY("land") && INGAME() && NOHYP()) { if(value == KEY_PRESS) { player_abortAutonav(NULL); player_land(); } } else if(KEY("thyperspace") && NOHYP() && NOLAND() && NODEAD()) { if(value == KEY_PRESS) { player_abortAutonav(NULL); player_targetHyperspace(); } } else if(KEY("starmap") && NOHYP() && NODEAD()) { if(value == KEY_PRESS) map_open(); } else if(KEY("jump") && INGAME()) { if(value == KEY_PRESS) { player_abortAutonav(NULL); player_jump(); } } /* Communication. */ else if(KEY("hail") && INGAME() && NOHYP()) { if(value == KEY_PRESS) { player_hail(); } } /* Zoom in. */ else if(KEY("mapzoomin") && INGAME()) { if(value == KEY_PRESS) gui_setRadarRel(1); } /* Zoom out. */ else if(KEY("mapzoomout") && INGAME()) { if(value == KEY_PRESS) gui_setRadarRel(-1); } /* Take a screenshot. */ else if(KEY("screenshot") && INGAME()) { if(value == KEY_PRESS) player_screenshot(); } /* Pause the game. */ else if(KEY("pause") && NOHYP()) { if(value == KEY_PRESS) { if(!toolkit) { if(paused) unpause_game(); } else pause_game(); } } /* Toggle speed mode. */ else if(KEY("speed")) { if(value == KEY_PRESS) { if(dt_mod == 1.) pause_setSpeed(2.); else pause_setSpeed(1.); } } /* Opens a menu. */ else if(KEY("menu")) { if(value == KEY_PRESS) menu_small(); } /* Show pilot information. */ else if(KEY("info") && NOHYP()) { if(value == KEY_PRESS) menu_info(); } } #undef KEY /* --Events-- */ static void input_joyaxis(const SDLKey axis, const int value); static void input_joyevent(const int event, const SDLKey button); static void input_keyevent(const int event, const SDLKey key, const SDLMod mod); /* Joystick. */ /** * @brief Filters a joystick axis event. * @param axis Axis generated by the event. * @param value Value of the axis. */ static void input_joyaxis(const SDLKey axis, const int value) { int i, k; for(i = 0; strcmp(keybindNames[i], "end"); i++) { if(input_keybinds[i]->key == axis) { /* Positive axis keybinding. */ if((input_keybinds[i]->type == KEYBIND_JAXISPOS) && (value >= 0)) { k = (value > 0) ? KEY_PRESS : KEY_RELEASE; input_key(i, k, fabs(((double)value)/32767.)); } /* Negative axis keybinding. */ if((input_keybinds[i]->type == KEYBIND_JAXISNEG) && (value <= 0)) { k = (value < 0) ? KEY_PRESS : KEY_RELEASE; input_key(i, k, fabs(((double)value)/32767.)); } } } } /** * @brief Filters a joystick button event. * @param event Event type (down/up). * @param button Button generating the event. */ static void input_joyevent(const int event, const SDLKey button) { int i; for(i = 0; strcmp(keybindNames[i], "end"); i++) if((input_keybinds[i]->type == KEYBIND_JBUTTON) && (input_keybinds[i]->key == button)) input_key(i, event, 0.); } /* Keyboard. */ /** * @brief Filters a keyboard event. * @param event Event type(down/up). * @param key Key generating the event. * @param mod Modifiers active when event was generated. */ static void input_keyevent(const int event, SDLKey key, const SDLMod mod) { int i; SDLMod mod_filtered; /* We want to ignore "global" modifiers. */ mod_filtered = mod & ~(KMOD_CAPS | KMOD_NUM | KMOD_MODE); for(i = 0; strcmp(keybindNames[i], "end"); i++) { if((input_keybinds[i]->type == KEYBIND_KEYBOARD) && (input_keybinds[i]->key == key)) { if((input_keybinds[i]->mod == mod_filtered) || (input_keybinds[i]->mod == KMOD_ALL) || (event == KEY_RELEASE)) /**< Release always gets through. */ input_key(i, event, 0.); /* No break so all keys get pressed if needed. */ } } } /** * @brief Handle global input. * * Basically seperates the event types. * @param event Incoming SDL_Event. */ void input_handle(SDL_Event* event) { /* Pause the game if it is unfocused. */ if(event->type == SDL_ACTIVEEVENT) { if(event->active.state != SDL_APPMOUSEFOCUS) { /* We don't need mouse focus. */ if((event->active.gain == 0) && !paused) pause_game(); else if((event->active.gain == 1) && paused) unpause_game(); return; } } if(toolkit) /* Toolkit is handled seperately. */ if(toolkit_input(event)) return; /* We don't process it if toolkit grabs it. */ switch(event->type) { case SDL_JOYAXISMOTION: input_joyaxis(event->jaxis.axis, event->jaxis.value); break; case SDL_JOYBUTTONDOWN: input_joyevent(KEY_PRESS, event->jbutton.button); break; case SDL_JOYBUTTONUP: input_joyevent(KEY_RELEASE, event->jbutton.button); break; case SDL_KEYDOWN: input_keyevent(KEY_PRESS, event->key.keysym.sym, event->key.keysym.mod); break; case SDL_KEYUP: input_keyevent(KEY_RELEASE, event->key.keysym.sym, event->key.keysym.mod); break; } }