/*************************************************************************** input.c Handle input from the user. Copyright Nicola Salmoria and the MAME Team. Visit http://mamedev.org for licensing and usage restrictions. **************************************************************************** To do: * auto-selecting joystick configs * per-joystick configs? * test half-axis selections * add input test menu * get rid of osd_customize_inputport_list ***************************************************************************/ #include "input.h" #include "mame.h" #include "emuopts.h" #include "profiler.h" #include "deprecat.h" /*************************************************************************** CONSTANTS ***************************************************************************/ /* joystick mapping codes */ #define JOYSTICK_MAP_NEUTRAL 0x00 #define JOYSTICK_MAP_LEFT 0x01 #define JOYSTICK_MAP_RIGHT 0x02 #define JOYSTICK_MAP_UP 0x04 #define JOYSTICK_MAP_DOWN 0x08 #define JOYSTICK_MAP_STICKY 0x0f /* the largest number of tracked pressed switches for memory */ #define MAX_PRESSED_SWITCHES 64 /* invalid memory value for axis polling */ #define INVALID_AXIS_VALUE 0x7fffffff /*************************************************************************** TYPE DEFINITIONS ***************************************************************************/ /* single device item */ typedef struct _input_device_item input_device_item; struct _input_device_item { input_device_class devclass; /* device class of parent item */ int devindex; /* device index of parent item */ astring * name; /* string name of item */ astring * token; /* tokenized name for non-standard items */ void * internal; /* internal callback pointer */ input_item_class itemclass; /* class of the item */ input_item_id itemid; /* originally specified item id */ item_get_state_cb getstate; /* get state callback */ INT32 current; /* current raw value */ INT32 memory; /* "memory" value, to remember where we started during polling */ INT32 oncelatch; /* latched "once" value, cleared after each read */ /* keyboard information */ INT32 steadykey; /* the live steadykey state */ INT32 oldkey; /* old live state */ }; /* a 9x9 joystick map */ typedef struct _joystick_map joystick_map; struct _joystick_map { UINT8 map[9][9]; /* 9x9 grid */ }; /* a single input device */ struct _input_device { astring * name; /* string name of device */ input_device_class devclass; /* class of this device */ int devindex; /* device index of this device */ input_device_item * item[ITEM_ID_ABSOLUTE_MAXIMUM]; /* array of pointers to items */ input_item_id maxitem; /* maximum item index */ void * internal; /* internal callback pointer */ /* joystick information */ joystick_map joymap; /* joystick map for this device */ UINT8 lastmap; /* last joystick map value for this device */ }; /* a list of devices as an array */ typedef struct _input_device_list input_device_list; struct _input_device_list { input_device * list; /* the array */ int count; /* elements in the array */ UINT8 enabled; /* is this class enabled? */ UINT8 multi; /* are multiple instances of this class allowed? */ }; /* code <-> string matching */ typedef struct _code_string_table code_string_table; struct _code_string_table { UINT32 code; const char * string; }; /*************************************************************************** TOKEN/STRING TABLES ***************************************************************************/ /* token strings for device classes */ static const code_string_table devclass_token_table[] = { { DEVICE_CLASS_KEYBOARD, "KEYCODE" }, { DEVICE_CLASS_MOUSE, "MOUSECODE" }, { DEVICE_CLASS_LIGHTGUN, "GUNCODE" }, { DEVICE_CLASS_JOYSTICK, "JOYCODE" }, { ~0, "UNKCODE" } }; /* friendly strings for device classes */ static const code_string_table devclass_string_table[] = { { DEVICE_CLASS_KEYBOARD, "Kbd" }, { DEVICE_CLASS_MOUSE, "Mouse" }, { DEVICE_CLASS_LIGHTGUN, "Gun" }, { DEVICE_CLASS_JOYSTICK, "Joy" }, { ~0, "Unk" } }; /* token strings for item modifiers */ static const code_string_table modifier_token_table[] = { { ITEM_MODIFIER_POS, "POS" }, { ITEM_MODIFIER_NEG, "NEG" }, { ITEM_MODIFIER_LEFT, "LEFT" }, { ITEM_MODIFIER_RIGHT, "RIGHT" }, { ITEM_MODIFIER_UP, "UP" }, { ITEM_MODIFIER_DOWN, "DOWN" }, { ~0, "" } }; /* friendly strings for item modifiers */ static const code_string_table modifier_string_table[] = { { ITEM_MODIFIER_POS, "+" }, { ITEM_MODIFIER_NEG, "-" }, { ITEM_MODIFIER_LEFT, "Left" }, { ITEM_MODIFIER_RIGHT, "Right" }, { ITEM_MODIFIER_UP, "Up" }, { ITEM_MODIFIER_DOWN, "Down" }, { ~0, "" } }; /* token strings for item classes */ static const code_string_table itemclass_token_table[] = { { ITEM_CLASS_SWITCH, "SWITCH" }, { ITEM_CLASS_ABSOLUTE, "ABSOLUTE" }, { ITEM_CLASS_RELATIVE, "RELATIVE" }, { ~0, "" } }; /* token strings for standard item ids */ static const code_string_table itemid_token_table[] = { /* standard keyboard codes */ { ITEM_ID_A, "A" }, { ITEM_ID_B, "B" }, { ITEM_ID_C, "C" }, { ITEM_ID_D, "D" }, { ITEM_ID_E, "E" }, { ITEM_ID_F, "F" }, { ITEM_ID_G, "G" }, { ITEM_ID_H, "H" }, { ITEM_ID_I, "I" }, { ITEM_ID_J, "J" }, { ITEM_ID_K, "K" }, { ITEM_ID_L, "L" }, { ITEM_ID_M, "M" }, { ITEM_ID_N, "N" }, { ITEM_ID_O, "O" }, { ITEM_ID_P, "P" }, { ITEM_ID_Q, "Q" }, { ITEM_ID_R, "R" }, { ITEM_ID_S, "S" }, { ITEM_ID_T, "T" }, { ITEM_ID_U, "U" }, { ITEM_ID_V, "V" }, { ITEM_ID_W, "W" }, { ITEM_ID_X, "X" }, { ITEM_ID_Y, "Y" }, { ITEM_ID_Z, "Z" }, { ITEM_ID_0, "0" }, { ITEM_ID_1, "1" }, { ITEM_ID_2, "2" }, { ITEM_ID_3, "3" }, { ITEM_ID_4, "4" }, { ITEM_ID_5, "5" }, { ITEM_ID_6, "6" }, { ITEM_ID_7, "7" }, { ITEM_ID_8, "8" }, { ITEM_ID_9, "9" }, { ITEM_ID_F1, "F1" }, { ITEM_ID_F2, "F2" }, { ITEM_ID_F3, "F3" }, { ITEM_ID_F4, "F4" }, { ITEM_ID_F5, "F5" }, { ITEM_ID_F6, "F6" }, { ITEM_ID_F7, "F7" }, { ITEM_ID_F8, "F8" }, { ITEM_ID_F9, "F9" }, { ITEM_ID_F10, "F10" }, { ITEM_ID_F11, "F11" }, { ITEM_ID_F12, "F12" }, { ITEM_ID_F13, "F13" }, { ITEM_ID_F14, "F14" }, { ITEM_ID_F15, "F15" }, { ITEM_ID_ESC, "ESC" }, { ITEM_ID_TILDE, "TILDE" }, { ITEM_ID_MINUS, "MINUS" }, { ITEM_ID_EQUALS, "EQUALS" }, { ITEM_ID_BACKSPACE, "BACKSPACE" }, { ITEM_ID_TAB, "TAB" }, { ITEM_ID_OPENBRACE, "OPENBRACE" }, { ITEM_ID_CLOSEBRACE, "CLOSEBRACE" }, { ITEM_ID_ENTER, "ENTER" }, { ITEM_ID_COLON, "COLON" }, { ITEM_ID_QUOTE, "QUOTE" }, { ITEM_ID_BACKSLASH, "BACKSLASH" }, { ITEM_ID_BACKSLASH2, "BACKSLASH2" }, { ITEM_ID_COMMA, "COMMA" }, { ITEM_ID_STOP, "STOP" }, { ITEM_ID_SLASH, "SLASH" }, { ITEM_ID_SPACE, "SPACE" }, { ITEM_ID_INSERT, "INSERT" }, { ITEM_ID_DEL, "DEL" }, { ITEM_ID_HOME, "HOME" }, { ITEM_ID_END, "END" }, { ITEM_ID_PGUP, "PGUP" }, { ITEM_ID_PGDN, "PGDN" }, { ITEM_ID_LEFT, "LEFT" }, { ITEM_ID_RIGHT, "RIGHT" }, { ITEM_ID_UP, "UP" }, { ITEM_ID_DOWN, "DOWN" }, { ITEM_ID_0_PAD, "0PAD" }, { ITEM_ID_1_PAD, "1PAD" }, { ITEM_ID_2_PAD, "2PAD" }, { ITEM_ID_3_PAD, "3PAD" }, { ITEM_ID_4_PAD, "4PAD" }, { ITEM_ID_5_PAD, "5PAD" }, { ITEM_ID_6_PAD, "6PAD" }, { ITEM_ID_7_PAD, "7PAD" }, { ITEM_ID_8_PAD, "8PAD" }, { ITEM_ID_9_PAD, "9PAD" }, { ITEM_ID_SLASH_PAD, "SLASHPAD" }, { ITEM_ID_ASTERISK, "ASTERISK" }, { ITEM_ID_MINUS_PAD, "MINUSPAD" }, { ITEM_ID_PLUS_PAD, "PLUSPAD" }, { ITEM_ID_DEL_PAD, "DELPAD" }, { ITEM_ID_ENTER_PAD, "ENTERPAD" }, { ITEM_ID_PRTSCR, "PRTSCR" }, { ITEM_ID_PAUSE, "PAUSE" }, { ITEM_ID_LSHIFT, "LSHIFT" }, { ITEM_ID_RSHIFT, "RSHIFT" }, { ITEM_ID_LCONTROL, "LCONTROL" }, { ITEM_ID_RCONTROL, "RCONTROL" }, { ITEM_ID_LALT, "LALT" }, { ITEM_ID_RALT, "RALT" }, { ITEM_ID_SCRLOCK, "SCRLOCK" }, { ITEM_ID_NUMLOCK, "NUMLOCK" }, { ITEM_ID_CAPSLOCK, "CAPSLOCK" }, { ITEM_ID_LWIN, "LWIN" }, { ITEM_ID_RWIN, "RWIN" }, { ITEM_ID_MENU, "MENU" }, { ITEM_ID_CANCEL, "CANCEL" }, /* standard mouse/joystick/gun codes */ { ITEM_ID_XAXIS, "XAXIS" }, { ITEM_ID_YAXIS, "YAXIS" }, { ITEM_ID_ZAXIS, "ZAXIS" }, { ITEM_ID_RXAXIS, "RXAXIS" }, { ITEM_ID_RYAXIS, "RYAXIS" }, { ITEM_ID_RZAXIS, "RZAXIS" }, { ITEM_ID_SLIDER1, "SLIDER1" }, { ITEM_ID_SLIDER2, "SLIDER2" }, { ITEM_ID_BUTTON1, "BUTTON1" }, { ITEM_ID_BUTTON2, "BUTTON2" }, { ITEM_ID_BUTTON3, "BUTTON3" }, { ITEM_ID_BUTTON4, "BUTTON4" }, { ITEM_ID_BUTTON5, "BUTTON5" }, { ITEM_ID_BUTTON6, "BUTTON6" }, { ITEM_ID_BUTTON7, "BUTTON7" }, { ITEM_ID_BUTTON8, "BUTTON8" }, { ITEM_ID_BUTTON9, "BUTTON9" }, { ITEM_ID_BUTTON10, "BUTTON10" }, { ITEM_ID_BUTTON11, "BUTTON11" }, { ITEM_ID_BUTTON12, "BUTTON12" }, { ITEM_ID_BUTTON13, "BUTTON13" }, { ITEM_ID_BUTTON14, "BUTTON14" }, { ITEM_ID_BUTTON15, "BUTTON15" }, { ITEM_ID_BUTTON16, "BUTTON16" }, { ITEM_ID_START, "START" }, { ITEM_ID_SELECT, "SELECT" }, { ~0, NULL } }; /*************************************************************************** GLOBAL VARIABLES ***************************************************************************/ /* array of devices for each class */ static input_device_list device_list[DEVICE_CLASS_MAXIMUM]; static input_code code_pressed_memory[MAX_PRESSED_SWITCHES]; /* device configuration */ static UINT8 steadykey_enabled; static UINT8 lightgun_reload_button; static const char * joystick_map_default; static INT32 joystick_deadzone; static INT32 joystick_saturation; /* standard joystick mappings */ const char joystick_map_8way[] = "7778...4445"; const char joystick_map_4way_sticky[] = "s8.4s8.44s8.4445"; const char joystick_map_4way_diagonal[] = "4444s8888..444458888.444555888.ss5.222555666.222256666.2222s6666.2222s6666"; /*************************************************************************** FUNCTION PROTOTYPES ***************************************************************************/ static void input_frame(running_machine *machine); static input_device_item *input_code_item(input_code code); static INT32 convert_absolute_value(input_code code, input_device_item *item); static INT32 convert_relative_value(input_code code, input_device_item *item); static INT32 convert_switch_value(input_code code, input_device_item *item); static INT32 apply_deadzone_and_saturation(input_code code, INT32 result); static int joystick_map_parse(const char *mapstring, joystick_map *map); static void joystick_map_print(const char *header, const char *origstring, const joystick_map *map); /*************************************************************************** INLINE FUNCTIONS ***************************************************************************/ /*------------------------------------------------- input_code_device - given an input_code return a pointer to the associated device -------------------------------------------------*/ INLINE input_device *input_code_device(input_code code) { /* if the class is valid... */ input_device_class devclass = INPUT_CODE_DEVCLASS(code); if (devclass > DEVICE_CLASS_INVALID && devclass < DEVICE_CLASS_MAXIMUM) { /* ...and the index is valid for that class, return a pointer to the device */ int devindex = INPUT_CODE_DEVINDEX(code); if (devindex < device_list[devclass].count) return &device_list[devclass].list[devindex]; } /* otherwise, return NULL */ return NULL; } /*------------------------------------------------- device_item_to_code - convert a device/item pair to a standard code -------------------------------------------------*/ INLINE input_code device_item_to_code(input_device *device, input_item_id itemid) { int devindex = device->devindex; assert(devindex < device_list[device->devclass].count); assert(itemid < ITEM_ID_ABSOLUTE_MAXIMUM); assert(device->item[itemid] != NULL); return INPUT_CODE(device->devclass, devindex, device->item[itemid]->itemclass, ITEM_MODIFIER_NONE, itemid); } /*------------------------------------------------- input_item_standard_class - return the class of a standard item -------------------------------------------------*/ INLINE input_item_class input_item_standard_class(input_device_class devclass, input_item_id itemid) { /* most everything standard is a switch, apart from the axes */ if (itemid == ITEM_ID_OTHER_SWITCH || itemid < ITEM_ID_XAXIS || itemid > ITEM_ID_SLIDER2) return ITEM_CLASS_SWITCH; /* standard mouse axes are relative */ else if (devclass == DEVICE_CLASS_MOUSE || itemid == ITEM_ID_OTHER_AXIS_RELATIVE) return ITEM_CLASS_RELATIVE; /* all other standard axes are absolute */ else return ITEM_CLASS_ABSOLUTE; } /*------------------------------------------------- input_item_update_value - update the value of an input item -------------------------------------------------*/ INLINE void input_item_update_value(input_device_item *item) { item->current = (*item->getstate)(device_list[item->devclass].list[item->devindex].internal, item->internal); } /*------------------------------------------------- code_pressed_memory_reset - reset the array of memory for pressed switches -------------------------------------------------*/ INLINE void code_pressed_memory_reset(void) { int memnum; for (memnum = 0; memnum < MAX_PRESSED_SWITCHES; memnum++) code_pressed_memory[memnum] = INPUT_CODE_INVALID; } /*------------------------------------------------- string_to_code - convert a string to a code via table lookup -------------------------------------------------*/ INLINE UINT32 string_to_code(const code_string_table *table, const char *string) { /* find a matching string */ for ( ; table->code != ~0; table++) if (strcmp(string, table->string) == 0) return table->code; /* on failure, return ~0 */ return ~0; } /*------------------------------------------------- code_to_string - convert a code to a string via table lookup -------------------------------------------------*/ INLINE const char *code_to_string(const code_string_table *table, UINT32 code) { /* find a matching code, or */ for ( ; table->code != ~0; table++) if (table->code == code) return table->string; /* return the default string */ return table->string; } /*************************************************************************** INITIALIZATION ***************************************************************************/ /*------------------------------------------------- input_init - initialize the input lists -------------------------------------------------*/ void input_init(running_machine *machine) { joystick_map map; /* reset the device lists */ memset(device_list, 0, sizeof(device_list)); /* reset code memory */ code_pressed_memory_reset(); /* request a per-frame callback for bookkeeping */ add_frame_callback(machine, input_frame); /* read input enable options */ device_list[DEVICE_CLASS_KEYBOARD].enabled = TRUE; device_list[DEVICE_CLASS_MOUSE].enabled = options_get_bool(mame_options(), OPTION_MOUSE); device_list[DEVICE_CLASS_LIGHTGUN].enabled = options_get_bool(mame_options(), OPTION_LIGHTGUN); device_list[DEVICE_CLASS_JOYSTICK].enabled = options_get_bool(mame_options(), OPTION_JOYSTICK); /* read input device multi options */ device_list[DEVICE_CLASS_KEYBOARD].multi = options_get_bool(mame_options(), OPTION_MULTIKEYBOARD); device_list[DEVICE_CLASS_MOUSE].multi = options_get_bool(mame_options(), OPTION_MULTIMOUSE); device_list[DEVICE_CLASS_LIGHTGUN].multi = TRUE; device_list[DEVICE_CLASS_JOYSTICK].multi = TRUE; /* read other input options */ steadykey_enabled = options_get_bool(mame_options(), OPTION_STEADYKEY); lightgun_reload_button = options_get_bool(mame_options(), OPTION_OFFSCREEN_RELOAD); joystick_deadzone = (INT32)(options_get_float(mame_options(), OPTION_JOYSTICK_DEADZONE) * INPUT_ABSOLUTE_MAX); joystick_saturation = (INT32)(options_get_float(mame_options(), OPTION_JOYSTICK_SATURATION) * INPUT_ABSOLUTE_MAX); /* get the default joystick map */ joystick_map_default = options_get_string(mame_options(), OPTION_JOYSTICK_MAP); if (joystick_map_default[0] == 0 || strcmp(joystick_map_default, "auto") == 0) joystick_map_default = joystick_map_8way; if (!joystick_map_parse(joystick_map_default, &map)) mame_printf_error("Invalid joystick map: %s\n", joystick_map_default); else if (joystick_map_default != joystick_map_8way) joystick_map_print("Input: Default joystick map", joystick_map_default, &map); } /*------------------------------------------------- input_device_class_enable - enable or disable a device class -------------------------------------------------*/ void input_device_class_enable(input_device_class devclass, UINT8 enable) { assert(devclass > DEVICE_CLASS_INVALID && devclass < DEVICE_CLASS_MAXIMUM); device_list[devclass].enabled = enable; } /*------------------------------------------------- input_enable_device_class - is a device class enabled? -------------------------------------------------*/ UINT8 input_device_class_enabled(input_device_class devclass) { assert(devclass > DEVICE_CLASS_INVALID && devclass < DEVICE_CLASS_MAXIMUM); return device_list[devclass].enabled; } /*------------------------------------------------- input_device_set_joystick_map - set the joystick map for a device -------------------------------------------------*/ int input_device_set_joystick_map(int devindex, const char *mapstring) { int startindex = devindex; int stopindex = devindex; joystick_map map; int joynum; /* parse the map */ if (!joystick_map_parse(mapstring, &map)) return FALSE; /* devindex -1 means set the same for all */ if (devindex == -1) { startindex = 0; stopindex = device_list[DEVICE_CLASS_JOYSTICK].count - 1; joystick_map_print("Input: Changing default joystick map", mapstring, &map); } /* ignore if out of range */ else if (devindex >= device_list[DEVICE_CLASS_JOYSTICK].count) return TRUE; /* iterate over joysticks and set the map */ for (joynum = startindex; joynum <= stopindex; joynum++) device_list[DEVICE_CLASS_JOYSTICK].list[joynum].joymap = map; return TRUE; } /*------------------------------------------------- input_frame - per-frame callback for various bookkeeping -------------------------------------------------*/ static void input_frame(running_machine *machine) { /* if steadykey is enabled, do processing here */ if (steadykey_enabled) { int devnum; /* iterate over keyboards */ for (devnum = 0; devnum < device_list[DEVICE_CLASS_KEYBOARD].count; devnum++) { input_device *device = &device_list[DEVICE_CLASS_KEYBOARD].list[devnum]; input_item_id itemid; int changed = FALSE; /* update the state of all the keys and see if any changed state */ for (itemid = ITEM_ID_INVALID + 1; itemid <= device->maxitem; itemid++) { input_device_item *item = device->item[itemid]; if (item != NULL && item->itemclass == ITEM_CLASS_SWITCH) { input_item_update_value(item); if ((item->current ^ item->oldkey) & 1) { changed = TRUE; /* if the keypress was missed, turn it on for one frame */ if (((item->current | item->steadykey) & 1) == 0) item->steadykey = 1; } } } /* if the keyboard state is stable, copy it over */ for (itemid = ITEM_ID_INVALID + 1; itemid <= device->maxitem; itemid++) { input_device_item *item = device->item[itemid]; if (item != NULL && item->itemclass == ITEM_CLASS_SWITCH) { if (!changed) item->steadykey = item->current; item->oldkey = item->current; } } } } } /*************************************************************************** OSD CONFIGURATION AND ACCESS ***************************************************************************/ /*------------------------------------------------- input_device_add - add a new input device -------------------------------------------------*/ input_device *input_device_add(input_device_class devclass, const char *name, void *internal) { input_device_list *devlist = &device_list[devclass]; input_device *device; assert_always(mame_get_phase(Machine) == MAME_PHASE_INIT, "Can only call input_device_add at init time!"); assert(name != NULL); assert(devclass != DEVICE_CLASS_INVALID && devclass < DEVICE_CLASS_MAXIMUM); /* allocate a new device */ devlist->list = auto_realloc(devlist->list, (devlist->count + 1) * sizeof(devlist->list[0])); device = &devlist->list[devlist->count++]; memset(device, 0, sizeof(*device)); /* fill in the data */ device->name = astring_cpyc(auto_astring_alloc(), name); device->devclass = devclass; device->devindex = devlist->count - 1; device->internal = internal; /* default to 8-way map for joysticks */ if (devclass == DEVICE_CLASS_JOYSTICK) { joystick_map_parse(joystick_map_default, &device->joymap); device->lastmap = JOYSTICK_MAP_NEUTRAL; } mame_printf_verbose("Input: Adding %s #%d: %s\n", code_to_string(devclass_string_table, devclass), devlist->count, astring_c(device->name)); return device; } /*------------------------------------------------- input_device_item_add - add a new item to an input device -------------------------------------------------*/ void input_device_item_add(input_device *device, const char *name, void *internal, input_item_id itemid, item_get_state_cb getstate) { input_device_item *item; assert_always(mame_get_phase(Machine) == MAME_PHASE_INIT, "Can only call input_device_item_add at init time!"); assert(name != NULL); assert(itemid > ITEM_ID_INVALID && itemid < ITEM_ID_MAXIMUM); assert(getstate != NULL); /* if we have a generic ID, pick a new internal one */ if (itemid >= ITEM_ID_OTHER_SWITCH && itemid <= ITEM_ID_OTHER_AXIS_RELATIVE) for (itemid = ITEM_ID_MAXIMUM + 1; itemid <= ITEM_ID_ABSOLUTE_MAXIMUM; itemid++) if (device->item[itemid] == NULL) break; assert(itemid <= ITEM_ID_ABSOLUTE_MAXIMUM); /* make sure we don't have any overlap */ assert(device->item[itemid] == NULL); /* allocate a new item and copy data into it */ item = auto_malloc(sizeof(*item)); memset(item, 0, sizeof(*item)); device->item[itemid] = item; device->maxitem = MAX(device->maxitem, itemid); /* copy in the data passed in from the item list */ item->devclass = device->devclass; item->devindex = device->devindex; item->name = astring_cpyc(auto_astring_alloc(), name); item->token = NULL; item->internal = internal; item->itemclass = input_item_standard_class(device->devclass, itemid); item->itemid = itemid; item->getstate = getstate; /* if we're custom, create a tokenized name */ if (itemid > ITEM_ID_MAXIMUM) { /* copy the item name, removing spaces/underscores and making all caps */ item->token = astring_toupper(astring_cpyc(auto_astring_alloc(), name)); astring_delchr(item->token, ' '); astring_delchr(item->token, '_'); } /* otherwise, make sure we have a valid standard token */ else { assert(code_to_string(itemid_token_table, itemid) != NULL); } } /*************************************************************************** STATE QUERIES ***************************************************************************/ /*------------------------------------------------- input_code_value - return the value of a given input code -------------------------------------------------*/ INT32 input_code_value(input_code code) { input_device_class devclass = INPUT_CODE_DEVCLASS(code); int startindex = INPUT_CODE_DEVINDEX(code); int stopindex = startindex; INT32 result = 0; int curindex; profiler_mark(PROFILER_INPUT); /* return 0 for any disabled or invalid device classes */ if (devclass <= DEVICE_CLASS_INVALID || devclass >= DEVICE_CLASS_MAXIMUM || !device_list[devclass].enabled) goto exit; if (startindex >= device_list[devclass].count) goto exit; /* if this is not a multi device, only return data for item 0 */ if (!device_list[devclass].multi) { if (startindex != 0) goto exit; /* otherwise, iterate over all */ startindex = 0; stopindex = device_list[devclass].count - 1; } /* iterate over all indices */ for (curindex = startindex; curindex <= stopindex; curindex++) { /* lookup the item for the appropriate index */ input_device_item *item = input_code_item(INPUT_CODE_SET_DEVINDEX(code, curindex)); if (item == NULL) continue; /* update the value of this item */ input_item_update_value(item); /* process items according to their native type */ switch (item->itemclass) { case ITEM_CLASS_ABSOLUTE: result = convert_absolute_value(code, item); break; case ITEM_CLASS_RELATIVE: result += convert_relative_value(code, item); break; case ITEM_CLASS_SWITCH: result |= convert_switch_value(code, item); break; default: break; } } exit: profiler_mark(PROFILER_END); return result; } /*------------------------------------------------- input_code_pressed - return non-zero if a given input code has been pressed -------------------------------------------------*/ int input_code_pressed(input_code code) { return (input_code_value(code) != 0); } /*------------------------------------------------- input_code_pressed_once - return non-zero if a given input code has transitioned from off to on since the last call -------------------------------------------------*/ int input_code_pressed_once(input_code code) { int curvalue = input_code_pressed(code); int memnum, empty = -1; /* look for the code in the memory */ for (memnum = 0; memnum < MAX_PRESSED_SWITCHES; memnum++) { /* were we previous pressed on the last time through here? */ if (code_pressed_memory[memnum] == code) { /* if no longer pressed, clear entry */ if (!curvalue) code_pressed_memory[memnum] = INPUT_CODE_INVALID; /* always return 0 */ return 0; } /* remember the first empty entry */ if (empty == -1 && code_pressed_memory[memnum] == INPUT_CODE_INVALID) empty = memnum; } /* if we get here, we were not previously pressed; if still not pressed, return 0 */ if (!curvalue) return 0; /* otherwise, add ourself to the memory and return 1 */ assert(empty != -1); if (empty != -1) code_pressed_memory[empty] = code; return 1; } /*------------------------------------------------- input_code_poll_switches - poll for any input -------------------------------------------------*/ input_code input_code_poll_switches(int reset) { input_device_class devclass; /* if resetting memory, do it now */ if (reset) code_pressed_memory_reset(); /* iterate over device classes and devices */ for (devclass = DEVICE_CLASS_INVALID + 1; devclass < DEVICE_CLASS_MAXIMUM; devclass++) { input_device_list *devlist = &device_list[devclass]; int devnum; /* iterate over devices within each class */ for (devnum = 0; devnum < devlist->count; devnum++) { input_device *device = &devlist->list[devnum]; input_item_id itemid; /* iterate over items within each device */ for (itemid = ITEM_ID_INVALID + 1; itemid <= device->maxitem; itemid++) { input_device_item *item = device->item[itemid]; if (item != NULL) { input_code code = device_item_to_code(device, itemid); /* if the item is natively a switch, poll it */ if (item->itemclass == ITEM_CLASS_SWITCH) { if (input_code_pressed_once(code)) return code; } /* otherwise, poll axes digitally */ else { code = INPUT_CODE_SET_ITEMCLASS(code, ITEM_CLASS_SWITCH); /* if this is a joystick X axis, check with left/right modifiers */ if (devclass == DEVICE_CLASS_JOYSTICK && INPUT_CODE_ITEMID(code) == ITEM_ID_XAXIS) { code = INPUT_CODE_SET_MODIFIER(code, ITEM_MODIFIER_LEFT); if (input_code_pressed_once(code)) return code; code = INPUT_CODE_SET_MODIFIER(code, ITEM_MODIFIER_RIGHT); if (input_code_pressed_once(code)) return code; } /* if this is a joystick Y axis, check with up/down modifiers */ else if (devclass == DEVICE_CLASS_JOYSTICK && INPUT_CODE_ITEMID(code) == ITEM_ID_YAXIS) { code = INPUT_CODE_SET_MODIFIER(code, ITEM_MODIFIER_UP); if (input_code_pressed_once(code)) return code; code = INPUT_CODE_SET_MODIFIER(code, ITEM_MODIFIER_DOWN); if (input_code_pressed_once(code)) return code; } /* any other axis, check with pos/neg modifiers */ else { code = INPUT_CODE_SET_MODIFIER(code, ITEM_MODIFIER_POS); if (input_code_pressed_once(code)) return code; code = INPUT_CODE_SET_MODIFIER(code, ITEM_MODIFIER_NEG); if (input_code_pressed_once(code)) return code; } } } } } } /* if nothing, return an invalid code */ return INPUT_CODE_INVALID; } /*------------------------------------------------- input_code_poll_keyboard_switches - poll for any keyboard-specific input -------------------------------------------------*/ input_code input_code_poll_keyboard_switches(int reset) { input_device_list *devlist = &device_list[DEVICE_CLASS_KEYBOARD]; int devnum; /* if resetting memory, do it now */ if (reset) code_pressed_memory_reset(); /* iterate over devices within each class */ for (devnum = 0; devnum < devlist->count; devnum++) { input_device *device = &devlist->list[devnum]; input_item_id itemid; /* iterate over items within each device */ for (itemid = ITEM_ID_INVALID + 1; itemid <= device->maxitem; itemid++) { input_device_item *item = device->item[itemid]; if (item != NULL && item->itemclass == ITEM_CLASS_SWITCH) { input_code code = device_item_to_code(device, itemid); if (input_code_pressed_once(code)) return code; } } } /* if nothing, return an invalid code */ return INPUT_CODE_INVALID; } /*------------------------------------------------- input_code_poll_axes - poll for any input -------------------------------------------------*/ input_code input_code_poll_axes(int reset) { input_device_class devclass; /* iterate over device classes and devices */ for (devclass = DEVICE_CLASS_INVALID + 1; devclass < DEVICE_CLASS_MAXIMUM; devclass++) { input_device_list *devlist = &device_list[devclass]; int devnum; /* iterate over devices within each class */ for (devnum = 0; devnum < devlist->count; devnum++) { input_device *device = &devlist->list[devnum]; input_item_id itemid; /* iterate over items within each device */ for (itemid = ITEM_ID_INVALID + 1; itemid <= device->maxitem; itemid++) { input_device_item *item = device->item[itemid]; if (item != NULL) { input_code code = device_item_to_code(device, itemid); INT32 curval, diff; /* skip any switches or already-reported axes */ if (item->itemclass == ITEM_CLASS_SWITCH) continue; /* poll the current value and reset the memory */ curval = input_code_value(code); if (reset) item->memory = curval; /* if we've already reported this one, don't bother */ if (item->memory == INVALID_AXIS_VALUE) continue; /* compute the diff against memory */ diff = curval - item->memory; if (diff < 0) diff = -diff; /* for absolute axes, look for 25% of maximum */ if (item->itemclass == ITEM_CLASS_ABSOLUTE && diff > (INPUT_ABSOLUTE_MAX - INPUT_ABSOLUTE_MIN) / 4) { item->memory = INVALID_AXIS_VALUE; return code; } /* for relative axes, look for ~20 pixels movement */ if (item->itemclass == ITEM_CLASS_RELATIVE && diff > 20 * INPUT_RELATIVE_PER_PIXEL) { item->memory = INVALID_AXIS_VALUE; return code; } } } } } /* if nothing, return an invalid code */ return INPUT_CODE_INVALID; } /*************************************************************************** STRINGS AND TOKENIZATION ***************************************************************************/ /*------------------------------------------------- input_code_name - convert an input code into a friendly name -------------------------------------------------*/ astring *input_code_name(astring *string, input_code code) { input_device_item *item = input_code_item(code); const char *devclass; const char *devcode; const char *modifier; char devindex[10]; /* if nothing there, return an empty string */ if (item == NULL) return astring_reset(string); /* determine the devclass part */ devclass = code_to_string(devclass_string_table, INPUT_CODE_DEVCLASS(code)); /* determine the devindex part */ sprintf(devindex, "%d", INPUT_CODE_DEVINDEX(code) + 1); /* if we're unifying all devices, don't display a number */ if (!device_list[INPUT_CODE_DEVCLASS(code)].multi) devindex[0] = 0; /* keyboard 0 doesn't show a class or index if it is the only one */ if (item->devclass == DEVICE_CLASS_KEYBOARD && device_list[DEVICE_CLASS_KEYBOARD].count == 1) { devclass = ""; devindex[0] = 0; } /* devcode part comes from the item name */ devcode = astring_c(item->name); /* determine the modifier part */ modifier = code_to_string(modifier_string_table, INPUT_CODE_MODIFIER(code)); /* devcode is redundant with joystick switch left/right/up/down */ if (item->devclass == DEVICE_CLASS_JOYSTICK && INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_SWITCH) if (INPUT_CODE_MODIFIER(code) >= ITEM_MODIFIER_LEFT && INPUT_CODE_MODIFIER(code) <= ITEM_MODIFIER_DOWN) devcode = ""; /* concatenate the strings */ astring_cpyc(string, devclass); if (devindex[0] != 0) astring_catc(astring_catc(string, " "), devindex); if (devcode[0] != 0) astring_catc(astring_catc(string, " "), devcode); if (modifier[0] != 0) astring_catc(astring_catc(string, " "), modifier); /* delete any leading spaces */ return astring_trimspace(string); } /*------------------------------------------------- input_code_to_token - create a token for a given code -------------------------------------------------*/ astring *input_code_to_token(astring *string, input_code code) { input_device_item *item = input_code_item(code); const char *devclass; const char *devcode; const char *itemclass; const char *modifier; char devindex[10]; /* determine the devclass part */ devclass = code_to_string(devclass_token_table, INPUT_CODE_DEVCLASS(code)); /* determine the devindex part; keyboard 0 doesn't show an index */ sprintf(devindex, "%d", INPUT_CODE_DEVINDEX(code) + 1); if (INPUT_CODE_DEVCLASS(code) == DEVICE_CLASS_KEYBOARD && INPUT_CODE_DEVINDEX(code) == 0) devindex[0] = 0; /* determine the itemid part; look up in the table if we don't have a token */ if (item != NULL && item->token != NULL) devcode = astring_c(item->token); else devcode = code_to_string(itemid_token_table, INPUT_CODE_ITEMID(code)); assert(devcode != NULL); /* determine the modifier part */ modifier = code_to_string(modifier_token_table, INPUT_CODE_MODIFIER(code)); /* determine the itemclass part; if we match the native class, we don't include this */ if (item == NULL || item->itemclass == INPUT_CODE_ITEMCLASS(code)) itemclass = ""; else itemclass = code_to_string(itemclass_token_table, INPUT_CODE_ITEMCLASS(code)); /* concatenate the strings */ astring_cpyc(string, devclass); if (devindex[0] != 0) astring_catc(astring_catc(string, "_"), devindex); if (devcode[0] != 0) astring_catc(astring_catc(string, "_"), devcode); if (modifier[0] != 0) astring_catc(astring_catc(string, "_"), modifier); if (itemclass[0] != 0) astring_catc(astring_catc(string, "_"), itemclass); return string; } /*------------------------------------------------- input_code_from_token - extract an input code from a token -------------------------------------------------*/ input_code input_code_from_token(const char *_token) { UINT32 devclass, itemid, devindex, modifier, standard; UINT32 itemclass = ITEM_CLASS_INVALID; input_code code = INPUT_CODE_INVALID; astring *token[6] = { NULL }; int numtokens, curtok; /* copy the token and break it into pieces */ for (numtokens = 0; numtokens < ARRAY_LENGTH(token); ) { /* make a token up to the next underscore */ char *score = strchr(_token, '_'); token[numtokens++] = astring_dupch(_token, (score == NULL) ? strlen(_token) : (score - _token)); /* if we hit the end, we're done, else advance our pointer */ if (score == NULL) break; _token = score + 1; } /* first token should be the devclass */ curtok = 0; devclass = string_to_code(devclass_token_table, astring_c(token[curtok++])); if (devclass == ~0) goto exit; /* second token might be index; look for number */ devindex = 0; if (numtokens > 2 && sscanf(astring_c(token[curtok]), "%d", &devindex) == 1) { curtok++; devindex--; } if (curtok >= numtokens) goto exit; /* next token is the item ID */ itemid = string_to_code(itemid_token_table, astring_c(token[curtok])); standard = (itemid != ~0); /* if we're a standard code, default the itemclass based on it */ if (standard) itemclass = input_item_standard_class(devclass, itemid); /* otherwise, keep parsing */ else { input_device *device; /* if this is an invalid device, we have nothing to look up */ if (devindex >= device_list[devclass].count) goto exit; device = &device_list[devclass].list[devindex]; /* if not a standard code, look it up in the device specific codes */ for (itemid = ITEM_ID_INVALID + 1; itemid <= device->maxitem; itemid++) { input_device_item *item = device->item[itemid]; if (item != NULL && item->token != NULL && astring_cmp(token[curtok], item->token) == 0) { /* take the itemclass from the item */ itemclass = item->itemclass; break; } } /* bail on fail */ if (itemid > device->maxitem) goto exit; } curtok++; /* if we have another token, it is probably a modifier */ modifier = ITEM_MODIFIER_NONE; if (curtok < numtokens) { modifier = string_to_code(modifier_token_table, astring_c(token[curtok])); if (modifier != ~0) curtok++; else modifier = ITEM_MODIFIER_NONE; } /* if we have another token, it is the item class */ if (curtok < numtokens) { UINT32 temp = string_to_code(itemclass_token_table, astring_c(token[curtok])); if (temp != ~0) { curtok++; itemclass = temp; } } /* we should have consumed all tokens */ if (curtok != numtokens) goto exit; /* assemble the final code */ code = INPUT_CODE(devclass, devindex, itemclass, modifier, itemid); exit: for (curtok = 0; curtok < ARRAY_LENGTH(token); curtok++) if (token[curtok] != NULL) astring_free(token[curtok]); return code; } /*************************************************************************** INTERNAL FUNCTIONS ***************************************************************************/ /*------------------------------------------------- input_code_item - get a pointer to the item from the code -------------------------------------------------*/ static input_device_item *input_code_item(input_code code) { input_device *device = input_code_device(code); input_item_id itemid; /* if no device, we fail */ if (device == NULL) return NULL; /* if the devcode is in range, return a pointer to the item */ itemid = INPUT_CODE_ITEMID(code); if (itemid < ARRAY_LENGTH(device->item)) return device->item[itemid]; /* otherwise fail with NULL */ return NULL; } /*------------------------------------------------- convert_absolute_value - convert an absolute value into the class specified by code -------------------------------------------------*/ static INT32 convert_absolute_value(input_code code, input_device_item *item) { /* make sure values are valid */ assert(item->current >= INPUT_ABSOLUTE_MIN && item->current <= INPUT_ABSOLUTE_MAX); /* relative value conversion not supported */ if (INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_RELATIVE) return 0; /* if we want the absolute value, process it according to the modifier field */ else if (INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_ABSOLUTE) { input_item_modifier modifier = INPUT_CODE_MODIFIER(code); INT32 result = apply_deadzone_and_saturation(code, item->current); /* if we're doing a lightgun reload hack, override the value */ if (lightgun_reload_button && item->devclass == DEVICE_CLASS_LIGHTGUN) { input_device_item *button2_item = device_list[item->devclass].list[item->devindex].item[ITEM_ID_BUTTON2]; if (button2_item != NULL) { /* if it is pressed, return (min,max) */ input_item_update_value(button2_item); if (button2_item->current) result = (INPUT_CODE_ITEMID(code) == ITEM_ID_XAXIS) ? INPUT_ABSOLUTE_MIN : INPUT_ABSOLUTE_MAX; } } /* standard axis: apply deadzone and saturation */ if (modifier == ITEM_MODIFIER_NONE) return result; /* positive/negative: scale to full axis */ else if (modifier == ITEM_MODIFIER_POS) return MAX(result, 0) * 2 + INPUT_ABSOLUTE_MIN; else if (modifier == ITEM_MODIFIER_NEG) return MAX(-result, 0) * 2 + INPUT_ABSOLUTE_MIN; } /* if we want a switch value, process it according to the modifier field */ else if (INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_SWITCH) { input_item_modifier modifier = INPUT_CODE_MODIFIER(code); /* left/right/up/down: if this is a joystick, fetch the paired X/Y axis values and convert */ if (modifier >= ITEM_MODIFIER_LEFT && modifier <= ITEM_MODIFIER_DOWN && item->devclass == DEVICE_CLASS_JOYSTICK) { input_device *device = &device_list[item->devclass].list[item->devindex]; input_device_item *xaxis_item = device->item[ITEM_ID_XAXIS]; input_device_item *yaxis_item = device->item[ITEM_ID_YAXIS]; if (xaxis_item != NULL && yaxis_item != NULL) { INT32 xaxisval, yaxisval; UINT8 mapval; /* determine which item we didn't update, and update it */ assert(item == xaxis_item || item == yaxis_item); input_item_update_value((item == xaxis_item) ? yaxis_item : xaxis_item); /* now map the X and Y axes to a 9x9 grid */ xaxisval = ((xaxis_item->current - INPUT_ABSOLUTE_MIN) * 9) / (INPUT_ABSOLUTE_MAX - INPUT_ABSOLUTE_MIN + 1); yaxisval = ((yaxis_item->current - INPUT_ABSOLUTE_MIN) * 9) / (INPUT_ABSOLUTE_MAX - INPUT_ABSOLUTE_MIN + 1); mapval = device->joymap.map[yaxisval][xaxisval]; /* handle stickiness */ if (mapval == JOYSTICK_MAP_STICKY) mapval = device->lastmap; else device->lastmap = mapval; /* return based on whether the appropriate bit is set */ return (mapval >> (modifier - ITEM_MODIFIER_LEFT)) & 1; } } /* positive/negative: TRUE if past the deadzone in either direction */ if (modifier == ITEM_MODIFIER_POS || modifier == ITEM_MODIFIER_RIGHT || modifier == ITEM_MODIFIER_DOWN) return (apply_deadzone_and_saturation(code, item->current) > 0); else if (modifier == ITEM_MODIFIER_NEG || modifier == ITEM_MODIFIER_LEFT || modifier == ITEM_MODIFIER_UP) return (apply_deadzone_and_saturation(code, item->current) < 0); } return 0; } /*------------------------------------------------- convert_relative_value - convert a relative value into the class specified by code -------------------------------------------------*/ static INT32 convert_relative_value(input_code code, input_device_item *item) { /* if we want relative values, take it as-is */ if (INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_RELATIVE) return item->current; /* absolute value conversion not supported */ else if (INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_ABSOLUTE) return 0; /* if we want a switch value, process it according to the modifier field */ else if (INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_SWITCH) { input_item_modifier modifier = INPUT_CODE_MODIFIER(code); /* positive/negative: TRUE if non-zero in either direction */ if (modifier == ITEM_MODIFIER_POS || modifier == ITEM_MODIFIER_RIGHT || modifier == ITEM_MODIFIER_DOWN) return (item->current > 0); else if (modifier == ITEM_MODIFIER_NEG || modifier == ITEM_MODIFIER_LEFT || modifier == ITEM_MODIFIER_UP) return (item->current < 0); } return 0; } /*------------------------------------------------- convert_switch_value - convert a switch value into the class specified by code -------------------------------------------------*/ static INT32 convert_switch_value(input_code code, input_device_item *item) { /* only a switch is supported */ if (INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_SWITCH) { /* if we're doing a lightgun reload hack, button 1 and 2 operate differently */ if (lightgun_reload_button && item->devclass == DEVICE_CLASS_LIGHTGUN) { /* button 1 is pressed if either button 1 or 2 are active */ if (INPUT_CODE_ITEMID(code) == ITEM_ID_BUTTON1) { input_device_item *button2_item = device_list[item->devclass].list[item->devindex].item[ITEM_ID_BUTTON2]; if (button2_item != NULL) { input_item_update_value(button2_item); return item->current | button2_item->current; } } /* button 2 is never officially pressed */ if (INPUT_CODE_ITEMID(code) == ITEM_ID_BUTTON2) return 0; } /* steadykey for keyboards */ if (steadykey_enabled && item->devclass == DEVICE_CLASS_KEYBOARD) return item->steadykey; /* everything else is just the current value as-is */ return item->current; } return 0; } /*------------------------------------------------- apply_deadzone_and_saturation - apply global deadzone and saturation parameters to an absolute value -------------------------------------------------*/ static INT32 apply_deadzone_and_saturation(input_code code, INT32 result) { int negative = FALSE; /* ignore if not a joystick */ if (INPUT_CODE_DEVCLASS(code) != DEVICE_CLASS_JOYSTICK) return result; /* properties are symmetric */ if (result < 0) { negative = TRUE; result = -result; } /* if in the deadzone, return 0 */ if (result < joystick_deadzone) result = 0; /* if saturated, return the max */ else if (result > joystick_saturation) result = INPUT_ABSOLUTE_MAX; /* otherwise, scale */ else result = (INT64)(result - joystick_deadzone) * (INT64)INPUT_ABSOLUTE_MAX / (INT64)(joystick_saturation - joystick_deadzone); /* apply sign and return */ return negative ? -result : result; } /*------------------------------------------------- joystick_map_parse - parse a string into a joystick map -------------------------------------------------*/ static int joystick_map_parse(const char *mapstring, joystick_map *map) { int rownum, colnum; /* iterate over rows */ for (rownum = 0; rownum < 9; rownum++) { /* if we're done, copy from another row */ if (*mapstring == 0 || *mapstring == '.') { int symmetric = (rownum >= 5 && *mapstring == 0); const UINT8 *srcrow = &map->map[symmetric ? (8 - rownum) : (rownum - 1)][0]; /* if this is row 0, we don't have a source row -- invalid */ if (rownum == 0) return FALSE; /* copy from the srcrow, applying up/down symmetry if in the bottom half */ for (colnum = 0; colnum < 9; colnum++) { UINT8 val = srcrow[colnum]; if (symmetric) val = (val & (JOYSTICK_MAP_LEFT | JOYSTICK_MAP_RIGHT)) | ((val & JOYSTICK_MAP_UP) << 1) | ((val & JOYSTICK_MAP_DOWN) >> 1); map->map[rownum][colnum] = val; } } /* otherwise, parse this column */ else { for (colnum = 0; colnum < 9; colnum++) { /* if we're at the end of row, copy previous to the middle, then apply left/right symmetry */ if (*mapstring == 0 || *mapstring == '.') { int symmetric = (colnum >= 5); UINT8 val = map->map[rownum][symmetric ? (8 - colnum) : (colnum - 1)]; if (symmetric) val = (val & (JOYSTICK_MAP_UP | JOYSTICK_MAP_DOWN)) | ((val & JOYSTICK_MAP_LEFT) << 1) | ((val & JOYSTICK_MAP_RIGHT) >> 1); map->map[rownum][colnum] = val; } /* otherwise, convert the character to its value */ else { static const UINT8 charmap[] = { JOYSTICK_MAP_UP | JOYSTICK_MAP_LEFT, JOYSTICK_MAP_UP, JOYSTICK_MAP_UP | JOYSTICK_MAP_RIGHT, JOYSTICK_MAP_LEFT, JOYSTICK_MAP_NEUTRAL, JOYSTICK_MAP_RIGHT, JOYSTICK_MAP_DOWN | JOYSTICK_MAP_LEFT, JOYSTICK_MAP_DOWN, JOYSTICK_MAP_DOWN | JOYSTICK_MAP_RIGHT, JOYSTICK_MAP_STICKY }; static const char validchars[] = "789456123s"; const char *ptr = strchr(validchars, *mapstring++); /* invalid characters exit immediately */ if (ptr == NULL) return FALSE; map->map[rownum][colnum] = charmap[ptr - validchars]; } } } /* if we ended with a period, advance to the next row */ if (*mapstring == '.') mapstring++; } return TRUE; } /*------------------------------------------------- joystick_map_print - print a joystick map via the verbose output -------------------------------------------------*/ static void joystick_map_print(const char *header, const char *origstring, const joystick_map *map) { int rownum, colnum; mame_printf_verbose("%s = %s\n", header, origstring); for (rownum = 0; rownum < 9; rownum++) { mame_printf_verbose(" "); for (colnum = 0; colnum < 9; colnum++) switch (map->map[rownum][colnum]) { case JOYSTICK_MAP_UP | JOYSTICK_MAP_LEFT: mame_printf_verbose("7"); break; case JOYSTICK_MAP_UP: mame_printf_verbose("8"); break; case JOYSTICK_MAP_UP | JOYSTICK_MAP_RIGHT: mame_printf_verbose("9"); break; case JOYSTICK_MAP_LEFT: mame_printf_verbose("4"); break; case JOYSTICK_MAP_NEUTRAL: mame_printf_verbose("5"); break; case JOYSTICK_MAP_RIGHT: mame_printf_verbose("6"); break; case JOYSTICK_MAP_DOWN | JOYSTICK_MAP_LEFT: mame_printf_verbose("1"); break; case JOYSTICK_MAP_DOWN: mame_printf_verbose("2"); break; case JOYSTICK_MAP_DOWN | JOYSTICK_MAP_RIGHT:mame_printf_verbose("3"); break; case JOYSTICK_MAP_STICKY: mame_printf_verbose("s"); break; default: mame_printf_verbose("?"); break; } mame_printf_verbose("\n"); } }