/***************************************************************************
input.c
Handle input from the user.
****************************************************************************
Copyright Aaron Giles
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
* Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
* Neither the name 'MAME' nor the names of its contributors may be
used to endorse or promote products derived from this software
without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY AARON GILES ''AS IS'' AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL AARON GILES BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING
IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
****************************************************************************
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 "emu.h"
#include "emuopts.h"
//**************************************************************************
// CONSTANTS
//**************************************************************************
// invalid memory value for axis polling
const INT32 INVALID_AXIS_VALUE = 0x7fffffff;
// additional expanded input codes for sequences
const input_code input_seq::end_code(DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_END);
const input_code input_seq::default_code(DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_DEFAULT);
const input_code input_seq::not_code(DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_NOT);
const input_code input_seq::or_code(DEVICE_CLASS_INTERNAL, 0, ITEM_CLASS_INVALID, ITEM_MODIFIER_NONE, ITEM_ID_SEQ_OR);
// constant sequences
const input_seq input_seq::empty_seq;
//**************************************************************************
// TYPE DEFINITIONS
//**************************************************************************
// ======================> input_device_item
// a single item on an input device
class input_device_item
{
protected:
// construction/destruction
input_device_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate, input_item_class itemclass);
virtual ~input_device_item() { }
public:
// getters
input_device &device() const { return m_device; }
input_manager &manager() const { return m_device.manager(); }
running_machine &machine() const { return m_device.machine(); }
const char *name() const { return m_name; }
void *internal() const { return m_internal; }
input_item_id itemid() const { return m_itemid; }
input_item_class itemclass() const { return m_itemclass; }
const char *token() const { return m_token; }
INT32 current() const { return m_current; }
INT32 memory() const { return m_memory; }
// helpers
INT32 update_value() { return m_current = (*m_getstate)(m_device.internal(), m_internal); }
void set_memory(INT32 value) { m_memory = value; }
// readers
virtual INT32 read_as_switch(input_item_modifier modifier) = 0;
virtual INT32 read_as_relative(input_item_modifier modifier) = 0;
virtual INT32 read_as_absolute(input_item_modifier modifier) = 0;
protected:
// internal state
input_device & m_device; // reference to our owning device
astring m_name; // string name of item
void * m_internal; // internal callback pointer
input_item_id m_itemid; // originally specified item id
input_item_class m_itemclass; // class of the item
item_get_state_func m_getstate; // get state callback
astring m_token; // tokenized name for non-standard items
// live state
INT32 m_current; // current raw value
INT32 m_memory; // "memory" value, to remember where we started during polling
};
// ======================> input_device_switch_item
// derived input item representing a switch input
class input_device_switch_item : public input_device_item
{
public:
// construction/destruction
input_device_switch_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate);
// readers
virtual INT32 read_as_switch(input_item_modifier modifier);
virtual INT32 read_as_relative(input_item_modifier modifier);
virtual INT32 read_as_absolute(input_item_modifier modifier);
// steadykey helper
bool steadykey_changed();
void steadykey_update_to_current() { m_steadykey = m_current; }
private:
// internal state
INT32 m_steadykey; // the live steadykey state
INT32 m_oldkey; // old live state
};
// ======================> input_device_switch_item
// derived input item representing a relative axis input
class input_device_relative_item : public input_device_item
{
public:
// construction/destruction
input_device_relative_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate);
// readers
virtual INT32 read_as_switch(input_item_modifier modifier);
virtual INT32 read_as_relative(input_item_modifier modifier);
virtual INT32 read_as_absolute(input_item_modifier modifier);
};
// ======================> input_device_switch_item
// derived input item representing an absolute axis input
class input_device_absolute_item : public input_device_item
{
public:
// construction/destruction
input_device_absolute_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate);
// readers
virtual INT32 read_as_switch(input_item_modifier modifier);
virtual INT32 read_as_relative(input_item_modifier modifier);
virtual INT32 read_as_absolute(input_item_modifier modifier);
};
// ======================> code_string_table
// simple class to match codes to strings
struct code_string_table
{
UINT32 operator[](const char *string) const
{
for (const code_string_table *current = this; current->m_code != ~0; current++)
if (strcmp(current->m_string, string) == 0)
return current->m_code;
return ~0;
}
const char *operator[](UINT32 code) const
{
for (const code_string_table *current = this; current->m_code != ~0; current++)
if (current->m_code == code)
return current->m_string;
return NULL;
}
UINT32 m_code;
const char * m_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" },
// Hats
{ ITEM_ID_HAT1UP, "HAT1UP" },
{ ITEM_ID_HAT1DOWN, "HAT1DOWN" },
{ ITEM_ID_HAT1LEFT, "HAT1LEFT" },
{ ITEM_ID_HAT1RIGHT, "HAT1RIGHT" },
{ ITEM_ID_HAT2UP, "HAT2UP" },
{ ITEM_ID_HAT2DOWN, "HAT2DOWN" },
{ ITEM_ID_HAT2LEFT, "HAT2LEFT" },
{ ITEM_ID_HAT2RIGHT, "HAT2RIGHT" },
{ ITEM_ID_HAT3UP, "HAT3UP" },
{ ITEM_ID_HAT3DOWN, "HAT3DOWN" },
{ ITEM_ID_HAT3LEFT, "HAT3LEFT" },
{ ITEM_ID_HAT3RIGHT, "HAT3RIGHT" },
{ ITEM_ID_HAT4UP, "HAT4UP" },
{ ITEM_ID_HAT4DOWN, "HAT4DOWN" },
{ ITEM_ID_HAT4LEFT, "HAT4LEFT" },
{ ITEM_ID_HAT4RIGHT, "HAT4RIGHT" },
// Additional IDs
{ ITEM_ID_ADD_SWITCH1, "ADDSW1" },
{ ITEM_ID_ADD_SWITCH2, "ADDSW2" },
{ ITEM_ID_ADD_SWITCH3, "ADDSW3" },
{ ITEM_ID_ADD_SWITCH4, "ADDSW4" },
{ ITEM_ID_ADD_SWITCH5, "ADDSW5" },
{ ITEM_ID_ADD_SWITCH6, "ADDSW6" },
{ ITEM_ID_ADD_SWITCH7, "ADDSW7" },
{ ITEM_ID_ADD_SWITCH8, "ADDSW8" },
{ ITEM_ID_ADD_SWITCH9, "ADDSW9" },
{ ITEM_ID_ADD_SWITCH10, "ADDSW10" },
{ ITEM_ID_ADD_SWITCH11, "ADDSW11" },
{ ITEM_ID_ADD_SWITCH12, "ADDSW12" },
{ ITEM_ID_ADD_SWITCH13, "ADDSW13" },
{ ITEM_ID_ADD_SWITCH14, "ADDSW14" },
{ ITEM_ID_ADD_SWITCH15, "ADDSW15" },
{ ITEM_ID_ADD_SWITCH16, "ADDSW16" },
{ ITEM_ID_ADD_ABSOLUTE1, "ADDAXIS1" },
{ ITEM_ID_ADD_ABSOLUTE2, "ADDAXIS2" },
{ ITEM_ID_ADD_ABSOLUTE3, "ADDAXIS3" },
{ ITEM_ID_ADD_ABSOLUTE4, "ADDAXIS4" },
{ ITEM_ID_ADD_ABSOLUTE5, "ADDAXIS5" },
{ ITEM_ID_ADD_ABSOLUTE6, "ADDAXIS6" },
{ ITEM_ID_ADD_ABSOLUTE7, "ADDAXIS7" },
{ ITEM_ID_ADD_ABSOLUTE8, "ADDAXIS8" },
{ ITEM_ID_ADD_ABSOLUTE9, "ADDAXIS9" },
{ ITEM_ID_ADD_ABSOLUTE10,"ADDAXIS10" },
{ ITEM_ID_ADD_ABSOLUTE11,"ADDAXIS11" },
{ ITEM_ID_ADD_ABSOLUTE12,"ADDAXIS12" },
{ ITEM_ID_ADD_ABSOLUTE13,"ADDAXIS13" },
{ ITEM_ID_ADD_ABSOLUTE14,"ADDAXIS14" },
{ ITEM_ID_ADD_ABSOLUTE15,"ADDAXIS15" },
{ ITEM_ID_ADD_ABSOLUTE16,"ADDAXIS16" },
{ ITEM_ID_ADD_RELATIVE1, "ADDREL1" },
{ ITEM_ID_ADD_RELATIVE2, "ADDREL2" },
{ ITEM_ID_ADD_RELATIVE3, "ADDREL3" },
{ ITEM_ID_ADD_RELATIVE4, "ADDREL4" },
{ ITEM_ID_ADD_RELATIVE5, "ADDREL5" },
{ ITEM_ID_ADD_RELATIVE6, "ADDREL6" },
{ ITEM_ID_ADD_RELATIVE7, "ADDREL7" },
{ ITEM_ID_ADD_RELATIVE8, "ADDREL8" },
{ ITEM_ID_ADD_RELATIVE9, "ADDREL9" },
{ ITEM_ID_ADD_RELATIVE10,"ADDREL10" },
{ ITEM_ID_ADD_RELATIVE11,"ADDREL11" },
{ ITEM_ID_ADD_RELATIVE12,"ADDREL12" },
{ ITEM_ID_ADD_RELATIVE13,"ADDREL13" },
{ ITEM_ID_ADD_RELATIVE14,"ADDREL14" },
{ ITEM_ID_ADD_RELATIVE15,"ADDREL15" },
{ ITEM_ID_ADD_RELATIVE16,"ADDREL16" },
{ ~0, NULL }
};
//**************************************************************************
// GLOBAL VARIABLES
//**************************************************************************
// 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";
//**************************************************************************
// JOYSTICK MAP
//**************************************************************************
//-------------------------------------------------
// joystick_map - constructor
//-------------------------------------------------
joystick_map::joystick_map()
: m_lastmap(JOYSTICK_MAP_NEUTRAL)
{
// parse the standard 8-way map as default
parse(joystick_map_8way);
}
//-------------------------------------------------
// parse - parse a string into a joystick map
//-------------------------------------------------
bool joystick_map::parse(const char *mapstring)
{
// save a copy of the original string
m_origstring = mapstring;
// iterate over rows
for (int rownum = 0; rownum < 9; rownum++)
{
// if we're done, copy from another row
if (*mapstring == 0 || *mapstring == '.')
{
bool symmetric = (rownum >= 5 && *mapstring == 0);
const UINT8 *srcrow = &m_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 (int 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);
m_map[rownum][colnum] = val;
}
}
// otherwise, parse this column
else
{
for (int colnum = 0; colnum < 9; colnum++)
{
// if we're at the end of row, copy previous to the middle, then apply left/right symmetry
if (colnum > 0 && (*mapstring == 0 || *mapstring == '.'))
{
bool symmetric = (colnum >= 5);
UINT8 val = m_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);
m_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;
m_map[rownum][colnum] = charmap[ptr - validchars];
}
}
}
// if we ended with a period, advance to the next row
if (*mapstring == '.')
mapstring++;
}
return true;
}
//-------------------------------------------------
// to_string - output the map as a string for
// friendly display
//-------------------------------------------------
const char *joystick_map::to_string(astring &string) const
{
string.printf("%s\n", m_origstring.cstr());
for (int rownum = 0; rownum < 9; rownum++)
{
string.catprintf(" ");
for (int colnum = 0; colnum < 9; colnum++)
switch (m_map[rownum][colnum])
{
case JOYSTICK_MAP_UP | JOYSTICK_MAP_LEFT: string.catprintf("7"); break;
case JOYSTICK_MAP_UP: string.catprintf("8"); break;
case JOYSTICK_MAP_UP | JOYSTICK_MAP_RIGHT: string.catprintf("9"); break;
case JOYSTICK_MAP_LEFT: string.catprintf("4"); break;
case JOYSTICK_MAP_NEUTRAL: string.catprintf("5"); break;
case JOYSTICK_MAP_RIGHT: string.catprintf("6"); break;
case JOYSTICK_MAP_DOWN | JOYSTICK_MAP_LEFT: string.catprintf("1"); break;
case JOYSTICK_MAP_DOWN: string.catprintf("2"); break;
case JOYSTICK_MAP_DOWN | JOYSTICK_MAP_RIGHT:string.catprintf("3"); break;
case JOYSTICK_MAP_STICKY: string.catprintf("s"); break;
default: string.catprintf("?"); break;
}
string.catprintf("\n");
}
return string;
}
//-------------------------------------------------
// update - update the state of the joystick
// map based on the given X/Y axis values
//-------------------------------------------------
UINT8 joystick_map::update(INT32 xaxisval, INT32 yaxisval)
{
// now map the X and Y axes to a 9x9 grid using the raw values
xaxisval = ((xaxisval - INPUT_ABSOLUTE_MIN) * 9) / (INPUT_ABSOLUTE_MAX - INPUT_ABSOLUTE_MIN + 1);
yaxisval = ((yaxisval - INPUT_ABSOLUTE_MIN) * 9) / (INPUT_ABSOLUTE_MAX - INPUT_ABSOLUTE_MIN + 1);
UINT8 mapval = m_map[yaxisval][xaxisval];
// handle stickiness
if (mapval == JOYSTICK_MAP_STICKY)
mapval = m_lastmap;
else
m_lastmap = mapval;
// return based on whether the appropriate bit is set
return mapval;
}
//**************************************************************************
// INPUT CODE
//**************************************************************************
//-------------------------------------------------
// input_code - construct an input code from a
// device/item pair
//-------------------------------------------------
input_code::input_code(input_device &device, input_item_id itemid)
{
assert(itemid < ITEM_ID_ABSOLUTE_MAXIMUM);
input_device_item *item = device.item(itemid);
assert(item != NULL);
m_internal = ((device.devclass() & 0xf) << 28) | ((device.devindex() & 0xff) << 20) | ((item->itemclass() & 0xf) << 16) | (ITEM_MODIFIER_NONE << 12) | (item->itemid() & 0xfff);
}
//**************************************************************************
// INPUT SEQ
//**************************************************************************
//-------------------------------------------------
// operator+= - append a code to the end of an
// input sequence
//-------------------------------------------------
input_seq &input_seq::operator+=(input_code code)
{
// if not enough room, return FALSE
int curlength = length();
if (curlength < ARRAY_LENGTH(m_code) - 1)
{
m_code[curlength++] = code;
m_code[curlength] = end_code;
}
return *this;
}
//-------------------------------------------------
// operator|= - append a code to a sequence; if
// the sequence is non-empty, insert an OR
// before the new code
//-------------------------------------------------
input_seq &input_seq::operator|=(input_code code)
{
// overwrite end/default with the new code
if (m_code[0] == end_code || m_code[0] == default_code)
m_code[0] = code;
// otherwise, append an OR token and then the new code
else
{
*this += or_code;
*this += code;
}
return *this;
}
//-------------------------------------------------
// length - return the length of the sequence
//-------------------------------------------------
int input_seq::length() const
{
// find the end token; error if none found
for (int seqnum = 0; seqnum < ARRAY_LENGTH(m_code); seqnum++)
if (m_code[seqnum] == end_code)
return seqnum;
return ARRAY_LENGTH(m_code);
}
//-------------------------------------------------
// is_valid - return true if a given sequence is
// valid
//-------------------------------------------------
bool input_seq::is_valid() const
{
// "default" can only be of length 1
if (m_code[0] == default_code)
return (length() == 1);
// scan the sequence for valid codes
input_item_class lastclass = ITEM_CLASS_INVALID;
input_code lastcode = INPUT_CODE_INVALID;
int positive_code_count = 0;
for (int seqnum = 0; seqnum < ARRAY_LENGTH(m_code); seqnum++)
{
// invalid codes are never permitted
input_code code = m_code[seqnum];
if (code == INPUT_CODE_INVALID)
return false;
// if we hit an OR or the end, validate the previous chunk
if (code == or_code || code == end_code)
{
// must be at least one positive code
if (positive_code_count == 0)
return false;
// last code must not have been an internal code
if (lastcode.internal())
return false;
// if this is the end, we're ok
if (code == end_code)
return true;
// reset the state for the next chunk
positive_code_count = 0;
lastclass = ITEM_CLASS_INVALID;
}
// if we hit a NOT, make sure we don't have a double
else if (code == not_code)
{
if (lastcode == not_code)
return false;
}
// anything else
else
{
// count positive codes
if (lastcode != not_code)
positive_code_count++;
// non-switch items can't have a NOT
input_item_class itemclass = code.item_class();
if (itemclass != ITEM_CLASS_SWITCH && lastcode == not_code)
return FALSE;
// absolute/relative items must all be the same class
if ((lastclass == ITEM_CLASS_ABSOLUTE && itemclass != ITEM_CLASS_ABSOLUTE) ||
(lastclass == ITEM_CLASS_RELATIVE && itemclass != ITEM_CLASS_RELATIVE))
return false;
}
// remember the last code
lastcode = code;
}
// if we got here, we were missing an END token; fail
return false;
}
//-------------------------------------------------
// set - directly set up to the first 7 codes
//-------------------------------------------------
void input_seq::set(input_code code0, input_code code1, input_code code2, input_code code3, input_code code4, input_code code5, input_code code6)
{
m_code[0] = code0;
m_code[1] = code1;
m_code[2] = code2;
m_code[3] = code3;
m_code[4] = code4;
m_code[5] = code5;
m_code[6] = code6;
for (int codenum = 7; codenum < ARRAY_LENGTH(m_code); codenum++)
m_code[codenum] = end_code;
}
//-------------------------------------------------
// backspace - "backspace" over the last entry in
// a sequence
//-------------------------------------------------
void input_seq::backspace()
{
// if we have at least one entry, remove it
int curlength = length();
if (curlength > 0)
m_code[curlength - 1] = end_code;
}
//-------------------------------------------------
// replace - replace all instances of oldcode
// with newcode in a sequence
//-------------------------------------------------
void input_seq::replace(input_code oldcode, input_code newcode)
{
for (int codenum = 0; codenum < ARRAY_LENGTH(m_code); codenum++)
if (m_code[codenum] == oldcode)
m_code[codenum] = newcode;
}
//**************************************************************************
// INPUT DEVICE
//**************************************************************************
//-------------------------------------------------
// input_device - constructor
//-------------------------------------------------
input_device::input_device(input_class &_class, int devindex, const char *name, void *internal)
: m_class(_class),
m_name(name),
m_devindex(devindex),
m_maxitem(input_item_id(0)),
m_internal(internal),
m_joystick_deadzone((INT32)(_class.manager().machine().options().joystick_deadzone() * INPUT_ABSOLUTE_MAX)),
m_joystick_saturation((INT32)(_class.manager().machine().options().joystick_saturation() * INPUT_ABSOLUTE_MAX)),
m_steadykey_enabled(_class.manager().machine().options().steadykey()),
m_lightgun_reload_button(_class.manager().machine().options().offscreen_reload())
{
// reset the items
memset(m_item, 0, sizeof(m_item));
// additional work for joysticks
if (devclass() == DEVICE_CLASS_JOYSTICK)
{
// get the default joystick map
const char *mapstring = machine().options().joystick_map();
if (mapstring[0] == 0 || strcmp(mapstring, "auto") == 0)
mapstring = joystick_map_8way;
// parse it
astring tempstr;
if (!m_joymap.parse(mapstring))
{
mame_printf_error("Invalid joystick map: %s\n", mapstring);
m_joymap.parse(joystick_map_8way);
}
else if (mapstring != joystick_map_8way)
mame_printf_verbose("Input: Default joystick map = %s", m_joymap.to_string(tempstr));
}
}
//-------------------------------------------------
// add_item - add a new item to an input device
//-------------------------------------------------
input_item_id input_device::add_item(const char *name, input_item_id itemid, item_get_state_func getstate, void *internal)
{
assert_always(machine().phase() == MACHINE_PHASE_INIT, "Can only call input_device::add_item 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
input_item_id originalid = itemid;
if (itemid >= ITEM_ID_OTHER_SWITCH && itemid <= ITEM_ID_OTHER_AXIS_RELATIVE)
for (itemid = (input_item_id)(ITEM_ID_MAXIMUM + 1); itemid <= ITEM_ID_ABSOLUTE_MAXIMUM; itemid++)
if (m_item[itemid] == NULL)
break;
assert(itemid <= ITEM_ID_ABSOLUTE_MAXIMUM);
// make sure we don't have any overlap
assert(m_item[itemid] == NULL);
// determine the class and create the appropriate item class
input_device_item *item = NULL;
switch (m_class.standard_item_class(originalid))
{
case ITEM_CLASS_SWITCH:
item = auto_alloc(machine(), input_device_switch_item(*this, name, internal, itemid, getstate));
break;
case ITEM_CLASS_RELATIVE:
item = auto_alloc(machine(), input_device_relative_item(*this, name, internal, itemid, getstate));
break;
case ITEM_CLASS_ABSOLUTE:
item = auto_alloc(machine(), input_device_absolute_item(*this, name, internal, itemid, getstate));
break;
default:
assert(false);
}
// assign the new slot and update the maximum
m_item[itemid] = item;
m_maxitem = MAX(m_maxitem, itemid);
return itemid;
}
//-------------------------------------------------
// apply_deadzone_and_saturation - apply global
// deadzone and saturation parameters to an
// absolute value
//-------------------------------------------------
INT32 input_device::apply_deadzone_and_saturation(INT32 result) const
{
// ignore for non-joysticks
if (devclass() != DEVICE_CLASS_JOYSTICK)
return result;
// properties are symmetric
bool negative = false;
if (result < 0)
{
negative = true;
result = -result;
}
// if in the deadzone, return 0
if (result < m_joystick_deadzone)
result = 0;
// if saturated, return the max
else if (result > m_joystick_saturation)
result = INPUT_ABSOLUTE_MAX;
// otherwise, scale
else
result = (INT64)(result - m_joystick_deadzone) * (INT64)INPUT_ABSOLUTE_MAX / (INT64)(m_joystick_saturation - m_joystick_deadzone);
// re-apply sign and return
return negative ? -result : result;
}
//-------------------------------------------------
// apply_steadykey - apply steadykey option if
// enabled
//-------------------------------------------------
void input_device::apply_steadykey() const
{
// ignore if not enabled
if (!m_steadykey_enabled)
return;
// update the state of all the keys and see if any changed state
bool anything_changed = false;
for (input_item_id itemid = ITEM_ID_FIRST_VALID; itemid <= m_maxitem; itemid++)
{
input_device_item *item = m_item[itemid];
if (item != NULL && item->itemclass() == ITEM_CLASS_SWITCH)
if (downcast<input_device_switch_item *>(item)->steadykey_changed())
anything_changed = true;
}
// if the keyboard state is stable, flush the current state
if (!anything_changed)
for (input_item_id itemid = ITEM_ID_FIRST_VALID; itemid <= m_maxitem; itemid++)
{
input_device_item *item = m_item[itemid];
if (item != NULL && item->itemclass() == ITEM_CLASS_SWITCH)
downcast<input_device_switch_item *>(item)->steadykey_update_to_current();
}
}
//**************************************************************************
// INPUT CLASS
//**************************************************************************
//-------------------------------------------------
// input_class - constructor
//-------------------------------------------------
input_class::input_class(input_manager &manager, input_device_class devclass, bool enabled, bool multi)
: m_manager(manager),
m_devclass(devclass),
m_maxindex(0),
m_enabled(enabled),
m_multi(multi)
{
memset(m_device, 0, sizeof(m_device));
// request a per-frame callback for the keyboard class
if (devclass == DEVICE_CLASS_KEYBOARD)
machine().add_notifier(MACHINE_NOTIFY_FRAME, machine_notify_delegate(FUNC(input_class::frame_callback), this));
}
//-------------------------------------------------
// add_device - add a new input device
//-------------------------------------------------
input_device *input_class::add_device(const char *name, void *internal)
{
// find the next empty index
int devindex;
for (devindex = 0; devindex < DEVICE_INDEX_MAXIMUM; devindex++)
if (m_device[devindex] == NULL)
break;
// call through
return add_device(devindex, name, internal);
}
input_device *input_class::add_device(int devindex, const char *name, void *internal)
{
assert_always(machine().phase() == MACHINE_PHASE_INIT, "Can only call input_class::add_device at init time!");
assert(name != NULL);
assert(devindex >= 0 && devindex < DEVICE_INDEX_MAXIMUM);
assert(m_device[devindex] == NULL);
// allocate a new device
input_device *device = m_device[devindex] = auto_alloc(machine(), input_device(*this, devindex, name, internal));
// update the maximum index found
m_maxindex = MAX(m_maxindex, devindex);
mame_printf_verbose("Input: Adding %s #%d: %s\n", (*devclass_string_table)[m_devclass], devindex, name);
return device;
}
//-------------------------------------------------
// standard_item_class - return the class of a
// standard item
//-------------------------------------------------
input_item_class input_class::standard_item_class(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 && itemid < ITEM_ID_ADD_ABSOLUTE1))
return ITEM_CLASS_SWITCH;
// standard mouse axes are relative
else if (m_devclass == DEVICE_CLASS_MOUSE || itemid == ITEM_ID_OTHER_AXIS_RELATIVE || (itemid >= ITEM_ID_ADD_RELATIVE1 && itemid <= ITEM_ID_ADD_RELATIVE16))
return ITEM_CLASS_RELATIVE;
// all other standard axes are absolute
else
return ITEM_CLASS_ABSOLUTE;
}
//-------------------------------------------------
// frame_callback - per-frame callback for various
// bookkeeping
//-------------------------------------------------
void input_class::frame_callback()
{
// iterate over all devices in our class
for (int devnum = 0; devnum <= m_maxindex; devnum++)
if (m_device[devnum] != NULL)
m_device[devnum]->apply_steadykey();
}
//**************************************************************************
// INPUT MANAGER
//**************************************************************************
//-------------------------------------------------
// input_manager - constructor
//-------------------------------------------------
input_manager::input_manager(running_machine &machine)
: m_machine(machine),
m_keyboard_class(*this, DEVICE_CLASS_KEYBOARD, true, machine.options().multi_keyboard()),
m_mouse_class(*this, DEVICE_CLASS_MOUSE, machine.options().mouse(), machine.options().multi_mouse()),
m_joystick_class(*this, DEVICE_CLASS_JOYSTICK, machine.options().joystick(), true),
m_lightgun_class(*this, DEVICE_CLASS_LIGHTGUN, machine.options().lightgun(), true),
m_poll_seq_last_ticks(0),
m_poll_seq_class(ITEM_CLASS_SWITCH)
{
// reset code memory
reset_memory();
// create pointers for the classes
memset(m_class, 0, sizeof(m_class));
m_class[DEVICE_CLASS_KEYBOARD] = &m_keyboard_class;
m_class[DEVICE_CLASS_MOUSE] = &m_mouse_class;
m_class[DEVICE_CLASS_JOYSTICK] = &m_joystick_class;
m_class[DEVICE_CLASS_LIGHTGUN] = &m_lightgun_class;
}
//-------------------------------------------------
// code_value - return the value of a given
// input code
//-------------------------------------------------
INT32 input_manager::code_value(input_code code)
{
g_profiler.start(PROFILER_INPUT);
INT32 result = 0;
// dummy loop to allow clean early exits
do
{
// return 0 for any invalid devices
input_device *device = device_from_code(code);
if (device == NULL)
break;
// also return 0 if the device class is disabled
input_class &devclass = *m_class[code.device_class()];
if (!devclass.enabled())
break;
// if this is not a multi device, only return data for item 0 and iterate over all
int startindex = code.device_index();
int stopindex = startindex;
if (!devclass.multi())
{
if (startindex != 0)
break;
stopindex = devclass.maxindex();
}
// iterate over all device indices
input_item_class targetclass = code.item_class();
for (int curindex = startindex; curindex <= stopindex; curindex++)
{
// lookup the item for the appropriate index
code.set_device_index(curindex);
input_device_item *item = item_from_code(code);
if (item == NULL)
continue;
// process items according to their native type
switch (targetclass)
{
case ITEM_CLASS_ABSOLUTE:
if (result == 0)
result = item->read_as_absolute(code.item_modifier());
break;
case ITEM_CLASS_RELATIVE:
result += item->read_as_relative(code.item_modifier());
break;
case ITEM_CLASS_SWITCH:
result |= item->read_as_switch(code.item_modifier());
break;
default:
break;
}
}
} while (0);
// stop the profiler before exiting
g_profiler.stop();
return result;
}
//-------------------------------------------------
// code_pressed_once - return non-zero if a given
// input code has transitioned from off to on
// since the last call
//-------------------------------------------------
bool input_manager::code_pressed_once(input_code code)
{
// look for the code in the memory
bool curvalue = code_pressed(code);
int empty = -1;
for (int memnum = 0; memnum < ARRAY_LENGTH(m_switch_memory); memnum++)
{
// were we previous pressed on the last time through here?
if (m_switch_memory[memnum] == code)
{
// if no longer pressed, clear entry
if (curvalue == false)
m_switch_memory[memnum] = INPUT_CODE_INVALID;
// always return false
return false;
}
// remember the first empty entry
if (empty == -1 && m_switch_memory[memnum] == INPUT_CODE_INVALID)
empty = memnum;
}
// if we get here, we were not previously pressed; if still not pressed, return 0
if (curvalue == false)
return false;
// otherwise, add ourself to the memory and return 1
assert(empty != -1);
if (empty != -1)
m_switch_memory[empty] = code;
return true;
}
//-------------------------------------------------
// reset_polling - reset memories in preparation
// for polling
//-------------------------------------------------
void input_manager::reset_polling()
{
// reset switch memory
reset_memory();
// iterate over device classes and devices
for (input_device_class devclass = DEVICE_CLASS_FIRST_VALID; devclass <= DEVICE_CLASS_LAST_VALID; devclass++)
for (int devnum = 0; devnum <= m_class[devclass]->maxindex(); devnum++)
{
// fetch the device; ignore if NULL
input_device *device = m_class[devclass]->device(devnum);
if (device == NULL)
continue;
// iterate over items within each device
for (input_item_id itemid = ITEM_ID_FIRST_VALID; itemid <= device->maxitem(); itemid++)
{
// for any non-switch items, set memory equal to the current value
input_device_item *item = device->item(itemid);
if (item != NULL && item->itemclass() != ITEM_CLASS_SWITCH)
item->set_memory(code_value(input_code(*device, itemid)));
}
}
}
//-------------------------------------------------
// poll_switches - poll for any input
//-------------------------------------------------
input_code input_manager::poll_switches()
{
// iterate over device classes and devices
for (input_device_class devclass = DEVICE_CLASS_FIRST_VALID; devclass <= DEVICE_CLASS_LAST_VALID; devclass++)
for (int devnum = 0; devnum <= m_class[devclass]->maxindex(); devnum++)
{
// fetch the device; ignore if NULL
input_device *device = m_class[devclass]->device(devnum);
if (device == NULL)
continue;
// iterate over items within each device
for (input_item_id itemid = ITEM_ID_FIRST_VALID; itemid <= device->maxitem(); itemid++)
{
input_device_item *item = device->item(itemid);
if (item != NULL)
{
input_code code(*device, itemid);
// if the item is natively a switch, poll it
if (item->itemclass() == ITEM_CLASS_SWITCH)
{
if (code_pressed_once(code))
return code;
else
continue;
}
// skip if there is not enough axis movement
if (!code_check_axis(*item, code))
continue;
// otherwise, poll axes digitally
code.set_item_class(ITEM_CLASS_SWITCH);
// if this is a joystick X axis, check with left/right modifiers
if (devclass == DEVICE_CLASS_JOYSTICK && code.item_id() == ITEM_ID_XAXIS)
{
code.set_item_modifier(ITEM_MODIFIER_LEFT);
if (code_pressed_once(code))
return code;
code.set_item_modifier(ITEM_MODIFIER_RIGHT);
if (code_pressed_once(code))
return code;
}
// if this is a joystick Y axis, check with up/down modifiers
else if (devclass == DEVICE_CLASS_JOYSTICK && code.item_id() == ITEM_ID_YAXIS)
{
code.set_item_modifier(ITEM_MODIFIER_UP);
if (code_pressed_once(code))
return code;
code.set_item_modifier(ITEM_MODIFIER_DOWN);
if (code_pressed_once(code))
return code;
}
// any other axis, check with pos/neg modifiers
else
{
code.set_item_modifier(ITEM_MODIFIER_POS);
if (code_pressed_once(code))
return code;
code.set_item_modifier(ITEM_MODIFIER_NEG);
if (code_pressed_once(code))
return code;
}
}
}
}
// if nothing, return an invalid code
return INPUT_CODE_INVALID;
}
//-------------------------------------------------
// poll_keyboard_switches - poll for any
// keyboard-specific input
//-------------------------------------------------
input_code input_manager::poll_keyboard_switches()
{
// iterate over devices within each class
for (int devnum = 0; devnum < m_keyboard_class.maxindex(); devnum++)
{
// fetch the device; ignore if NULL
input_device *device = m_keyboard_class.device(devnum);
if (device == NULL)
continue;
// iterate over items within each device
for (input_item_id itemid = ITEM_ID_FIRST_VALID; itemid <= device->maxitem(); itemid++)
{
input_device_item *item = device->item(itemid);
if (item != NULL && item->itemclass() == ITEM_CLASS_SWITCH)
{
input_code code(*device, itemid);
if (code_pressed_once(code))
return code;
}
}
}
// if nothing, return an invalid code
return INPUT_CODE_INVALID;
}
//-------------------------------------------------
// code_check_axis - see if axis has moved far
// enough to trigger a read when polling
//-------------------------------------------------
bool input_manager::code_check_axis(input_device_item &item, input_code code)
{
// if we've already reported this one, don't bother
if (item.memory() == INVALID_AXIS_VALUE)
return false;
// ignore min/max for lightguns
// so the selection will not be affected by a gun going out of range
INT32 curval = code_value(code);
if (code.device_class() == DEVICE_CLASS_LIGHTGUN &&
(code.item_id() == ITEM_ID_XAXIS || code.item_id() == ITEM_ID_YAXIS) &&
(curval == INPUT_ABSOLUTE_MAX || curval == INPUT_ABSOLUTE_MIN))
return false;
// compute the diff against memory
INT32 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.set_memory(INVALID_AXIS_VALUE);
return true;
}
// for relative axes, look for ~20 pixels movement
if (item.itemclass() == ITEM_CLASS_RELATIVE && diff > 20 * INPUT_RELATIVE_PER_PIXEL)
{
item.set_memory(INVALID_AXIS_VALUE);
return true;
}
return false;
}
//-------------------------------------------------
// poll_axes - poll for any input
//-------------------------------------------------
input_code input_manager::poll_axes()
{
// iterate over device classes and devices
for (input_device_class devclass = DEVICE_CLASS_FIRST_VALID; devclass <= DEVICE_CLASS_LAST_VALID; devclass++)
for (int devnum = 0; devnum <= m_class[devclass]->maxindex(); devnum++)
{
// fetch the device; ignore if NULL
input_device *device = m_class[devclass]->device(devnum);
if (device == NULL)
continue;
// iterate over items within each device
for (input_item_id itemid = ITEM_ID_FIRST_VALID; itemid <= device->maxitem(); itemid++)
{
input_device_item *item = device->item(itemid);
if (item != NULL && item->itemclass() != ITEM_CLASS_SWITCH)
{
input_code code(*device, itemid);
if (code_check_axis(*item, code))
return code;
}
}
}
// if nothing, return an invalid code
return INPUT_CODE_INVALID;
}
//-------------------------------------------------
// device_from_code - given an input_code return
// a pointer to the associated device
//-------------------------------------------------
input_device *input_manager::device_from_code(input_code code) const
{
// if the class is valid, return the appropriate device pointer
input_device_class devclass = code.device_class();
if (devclass >= DEVICE_CLASS_FIRST_VALID && devclass <= DEVICE_CLASS_LAST_VALID)
return m_class[devclass]->device(code.device_index());
// otherwise, return NULL
return NULL;
}
//-------------------------------------------------
// item_from_code - given an input_code return
// a pointer to the appropriate input_device_item
//-------------------------------------------------
input_device_item *input_manager::item_from_code(input_code code) const
{
// first get the device; if none, then we don't have an item
input_device *device = device_from_code(code);
if (device == NULL)
return NULL;
// then return the device's item
return device->item(code.item_id());
}
//-------------------------------------------------
// reset_memory - reset the array of memory for
// pressed switches
//-------------------------------------------------
void input_manager::reset_memory()
{
// reset all entries in switch memory to invalid
for (int memnum = 0; memnum < ARRAY_LENGTH(m_switch_memory); memnum++)
m_switch_memory[memnum] = INPUT_CODE_INVALID;
}
//-------------------------------------------------
// code_from_itemid - translates an input_item_id
// to an input_code
//-------------------------------------------------
input_code input_manager::code_from_itemid(input_item_id itemid) const
{
// iterate over device classes and devices
for (input_device_class devclass = DEVICE_CLASS_FIRST_VALID; devclass <= DEVICE_CLASS_LAST_VALID; devclass++)
for (int devnum = 0; devnum <= m_class[devclass]->maxindex(); devnum++)
{
input_device *device = m_class[devclass]->device(devnum);
if (device != NULL && device->item(itemid) != NULL)
return input_code(*device, itemid);
}
return INPUT_CODE_INVALID;
}
//-------------------------------------------------
// code_name - convert an input code into a
// friendly name
//-------------------------------------------------
const char *input_manager::code_name(astring &string, input_code code) const
{
string.reset();
// if nothing there, return an empty string
input_device_item *item = item_from_code(code);
if (item == NULL)
return string;
// determine the devclass part
const char *devclass = (*devclass_string_table)[code.device_class()];
// determine the devindex part
astring devindex;
devindex.printf("%d", code.device_index() + 1);
// if we're unifying all devices, don't display a number
if (!m_class[code.device_class()]->multi())
devindex.reset();
// keyboard 0 doesn't show a class or index if it is the only one
input_device_class device_class = item->device().devclass();
if (device_class == DEVICE_CLASS_KEYBOARD && m_keyboard_class.maxindex() == 0)
{
devclass = "";
devindex.reset();
}
// devcode part comes from the item name
const char *devcode = item->name();
// determine the modifier part
const char *modifier = (*modifier_string_table)[code.item_modifier()];
// devcode is redundant with joystick switch left/right/up/down
if (device_class == DEVICE_CLASS_JOYSTICK && code.item_class() == ITEM_CLASS_SWITCH)
if (code.item_modifier() >= ITEM_MODIFIER_LEFT && code.item_modifier() <= ITEM_MODIFIER_DOWN)
devcode = "";
// concatenate the strings
string.cpy(devclass);
if (devindex)
string.cat(" ").cat(devindex);
if (devcode[0] != 0)
string.cat(" ").cat(devcode);
if (modifier != NULL)
string.cat(" ").cat(modifier);
// delete any leading spaces
return string.trimspace();
}
//-------------------------------------------------
// code_to_token - create a token for a given code
//-------------------------------------------------
const char *input_manager::code_to_token(astring &string, input_code code) const
{
// determine the devclass part
const char *devclass = (*devclass_token_table)[code.device_class()];
// determine the devindex part; keyboard 0 doesn't show an index
astring devindex;
devindex.printf("%d", code.device_index() + 1);
if (code.device_class() == DEVICE_CLASS_KEYBOARD && code.device_index() == 0)
devindex.reset();
// determine the itemid part; look up in the table if we don't have a token
input_device_item *item = item_from_code(code);
const char *devcode = (item != NULL) ? item->token() : "UNKNOWN";
// determine the modifier part
const char *modifier = (*modifier_token_table)[code.item_modifier()];
// determine the itemclass part; if we match the native class, we don't include this
const char *itemclass = "";
if (item == NULL || item->itemclass() != code.item_class())
itemclass = (*itemclass_token_table)[code.item_class()];
// concatenate the strings
string.cpy(devclass);
if (devindex)
string.cat("_").cat(devindex);
if (devcode[0] != 0)
string.cat("_").cat(devcode);
if (modifier != NULL)
string.cat("_").cat(modifier);
if (itemclass[0] != 0)
string.cat("_").cat(itemclass);
return string;
}
//-------------------------------------------------
// code_from_token - extract an input code from a
// token
//-------------------------------------------------
input_code input_manager::code_from_token(const char *_token)
{
// copy the token and break it into pieces
astring token[6];
int numtokens;
for (numtokens = 0; numtokens < ARRAY_LENGTH(token); )
{
// make a token up to the next underscore
char *score = (char *)strchr(_token, '_');
token[numtokens++].cpy(_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
int curtok = 0;
input_device_class devclass = input_device_class((*devclass_token_table)[token[curtok++].cstr()]);
if (devclass == ~0)
return INPUT_CODE_INVALID;
// second token might be index; look for number
int devindex = 0;
if (numtokens > 2 && sscanf(token[curtok], "%d", &devindex) == 1)
{
curtok++;
devindex--;
}
if (curtok >= numtokens)
return INPUT_CODE_INVALID;
// next token is the item ID
input_item_id itemid = input_item_id((*itemid_token_table)[token[curtok].cstr()]);
bool standard = (itemid != ~0);
// if we're a standard code, default the itemclass based on it
input_item_class itemclass = ITEM_CLASS_INVALID;
if (standard)
itemclass = m_class[devclass]->standard_item_class(itemid);
// otherwise, keep parsing
else
{
// if this is an invalid device, we have nothing to look up
input_device *device = m_class[devclass]->device(devindex);
if (device == NULL)
return INPUT_CODE_INVALID;
// if not a standard code, look it up in the device specific codes
for (itemid = ITEM_ID_FIRST_VALID; itemid <= device->maxitem(); itemid++)
{
input_device_item *item = device->item(itemid);
if (item != NULL && token[curtok].cmp(item->token()) == 0)
{
// take the itemclass from the item
itemclass = item->itemclass();
break;
}
}
// bail on fail
if (itemid > device->maxitem())
return INPUT_CODE_INVALID;
}
curtok++;
// if we have another token, it is probably a modifier
input_item_modifier modifier = ITEM_MODIFIER_NONE;
if (curtok < numtokens)
{
modifier = input_item_modifier((*modifier_token_table)[token[curtok].cstr()]);
if (modifier != ~0)
curtok++;
else
modifier = ITEM_MODIFIER_NONE;
}
// if we have another token, it is the item class
if (curtok < numtokens)
{
UINT32 temp = (*itemclass_token_table)[token[curtok].cstr()];
if (temp != ~0)
{
curtok++;
itemclass = input_item_class(temp);
}
}
// we should have consumed all tokens
if (curtok != numtokens)
return INPUT_CODE_INVALID;
// assemble the final code
return input_code(devclass, devindex, itemclass, modifier, itemid);
}
//-------------------------------------------------
// seq_pressed - return true if the given sequence
// of switch inputs is "pressed"
//-------------------------------------------------
bool input_manager::seq_pressed(const input_seq &seq)
{
// iterate over all of the codes
bool result = false;
bool invert = false;
bool first = true;
for (int codenum = 0; ; codenum++)
{
// handle NOT
input_code code = seq[codenum];
if (code == input_seq::not_code)
invert = true;
// handle OR and END
else if (code == input_seq::or_code || code == input_seq::end_code)
{
// if we have a positive result from the previous set, we're done
if (result || code == input_seq::end_code)
break;
// otherwise, reset our state
result = false;
invert = false;
first = true;
}
// handle everything else as a series of ANDs
else
{
// if this is the first in the sequence, result is set equal
if (first)
result = code_pressed(code) ^ invert;
// further values are ANDed
else if (result)
result &= code_pressed(code) ^ invert;
// no longer first, and clear the invert flag
first = invert = false;
}
}
// return the result if we queried at least one switch
return result;
}
//-------------------------------------------------
// seq_axis_value - return the value of an axis
// defined in an input sequence
//-------------------------------------------------
INT32 input_manager::seq_axis_value(const input_seq &seq, input_item_class &itemclass)
{
// start with no valid classes
input_item_class itemclasszero = ITEM_CLASS_INVALID;
itemclass = ITEM_CLASS_INVALID;
// iterate over all of the codes
INT32 result = 0;
bool invert = false;
bool enable = true;
for (int codenum = 0; ; codenum++)
{
// handle NOT
input_code code = seq[codenum];
if (code == input_seq::not_code)
invert = true;
// handle OR and END
else if (code == input_seq::or_code || code == input_seq::end_code)
{
// if we have a positive result from the previous set, we're done
if (itemclass != ITEM_CLASS_INVALID || code == input_seq::end_code)
break;
// otherwise, reset our state
result = 0;
invert = false;
enable = true;
}
// handle everything else only if we're still enabled
else if (enable)
{
// switch codes serve as enables
if (code.item_class() == ITEM_CLASS_SWITCH)
{
// AND against previous digital codes
if (enable)
enable &= code_pressed(code) ^ invert;
}
// non-switch codes are analog values
else
{
INT32 value = code_value(code);
// if we got a 0 value, don't do anything except remember the first type
if (value == 0)
{
if (itemclasszero == ITEM_CLASS_INVALID)
itemclasszero = code.item_class();
}
// non-zero absolute values stick
else if (code.item_class() == ITEM_CLASS_ABSOLUTE)
{
itemclass = ITEM_CLASS_ABSOLUTE;
result = value;
}
// non-zero relative values accumulate
else if (code.item_class() == ITEM_CLASS_RELATIVE)
{
itemclass = ITEM_CLASS_RELATIVE;
result += value;
}
}
// clear the invert flag
invert = false;
}
}
// if the caller wants to know the type, provide it
if (result == 0)
itemclass = itemclasszero;
return result;
}
//-------------------------------------------------
// seq_poll_start - begin polling for a new
// sequence of the given itemclass
//-------------------------------------------------
void input_manager::seq_poll_start(input_item_class itemclass, const input_seq *startseq)
{
assert(itemclass == ITEM_CLASS_SWITCH || itemclass == ITEM_CLASS_ABSOLUTE || itemclass == ITEM_CLASS_RELATIVE);
// reset the recording count and the clock
m_poll_seq_last_ticks = 0;
m_poll_seq_class = itemclass;
m_poll_seq.reset();
// grab the starting sequence to append to, and append an OR
if (startseq != NULL)
{
m_poll_seq = *startseq;
if (m_poll_seq.length() > 0)
m_poll_seq += input_seq::or_code;
}
// flush out any goobers
reset_polling();
input_code dummycode = KEYCODE_ENTER;
while (dummycode != INPUT_CODE_INVALID)
dummycode = (m_poll_seq_class == ITEM_CLASS_SWITCH) ? poll_switches() : poll_axes();
}
//-------------------------------------------------
// input_seq_poll - continue polling
//-------------------------------------------------
bool input_manager::seq_poll()
{
int curlen = m_poll_seq.length();
input_code lastcode = m_poll_seq[curlen - 1];
// switch case: see if we have a new code to process
input_code newcode;
if (m_poll_seq_class == ITEM_CLASS_SWITCH)
{
newcode = poll_switches();
if (newcode != INPUT_CODE_INVALID)
{
// if code is duplicate, toggle the NOT state on the code
if (curlen > 0 && newcode == lastcode)
{
// back up over the existing code
m_poll_seq.backspace();
// if there was a NOT preceding it, delete it as well, otherwise append a fresh one
if (m_poll_seq[curlen - 2] == input_seq::not_code)
m_poll_seq.backspace();
else
m_poll_seq += input_seq::not_code;
}
}
}
// absolute/relative case: see if we have an analog change of sufficient amount
else
{
bool has_or = false;
if (lastcode == input_seq::or_code)
{
lastcode = m_poll_seq[curlen - 2];
has_or = true;
}
newcode = poll_axes();
// if the last code doesn't match absolute/relative of this code, ignore the new one
if ((lastcode.item_class() == ITEM_CLASS_ABSOLUTE && newcode.item_class() != ITEM_CLASS_ABSOLUTE) ||
(lastcode.item_class() == ITEM_CLASS_RELATIVE && newcode.item_class() != ITEM_CLASS_RELATIVE))
newcode = INPUT_CODE_INVALID;
// if the new code is valid, check for half-axis toggles on absolute controls
if (newcode != INPUT_CODE_INVALID && curlen > 0 && newcode.item_class() == ITEM_CLASS_ABSOLUTE)
{
input_code last_nomodifier = lastcode;
last_nomodifier.set_item_modifier(ITEM_MODIFIER_NONE);
if (newcode == last_nomodifier)
{
// increment the modifier, wrapping back to none
switch (lastcode.item_modifier())
{
case ITEM_MODIFIER_NONE: newcode.set_item_modifier(ITEM_MODIFIER_POS); break;
case ITEM_MODIFIER_POS: newcode.set_item_modifier(ITEM_MODIFIER_NEG); break;
default:
case ITEM_MODIFIER_NEG: newcode.set_item_modifier(ITEM_MODIFIER_NONE); break;
}
// back up over the previous code so we can re-append
if (has_or)
m_poll_seq.backspace();
m_poll_seq.backspace();
}
}
}
// if we got a new code to append it, append it and reset the timer
if (newcode != INPUT_CODE_INVALID)
{
m_poll_seq += newcode;
m_poll_seq_last_ticks = osd_ticks();
}
// if we're recorded at least one item and 2/3 of a second has passed, we're done
if (m_poll_seq_last_ticks != 0 && osd_ticks() > m_poll_seq_last_ticks + osd_ticks_per_second() * 2 / 3)
{
// if the final result is invalid, reset to nothing
if (!m_poll_seq.is_valid())
m_poll_seq.reset();
// return true to indicate that we are finished
return true;
}
// return false to indicate we are still polling
return false;
}
//-------------------------------------------------
// seq_name - generate the friendly name of a
// sequence
//-------------------------------------------------
const char *input_manager::seq_name(astring &string, const input_seq &seq) const
{
// make a copy of our sequence, removing any invalid bits
input_code clean_codes[sizeof(seq) / sizeof(input_code)];
int clean_index = 0;
astring codestr;
for (int codenum = 0; seq[codenum] != input_seq::end_code; codenum++)
{
// if this is a code item which is not valid, don't copy it and remove any preceding ORs/NOTs
input_code code = seq[codenum];
if (!code.internal() && *(code_name(codestr, code)) == 0)
{
while (clean_index > 0 && clean_codes[clean_index - 1].internal())
clean_index--;
}
else if (clean_index > 0 || !code.internal())
clean_codes[clean_index++] = code;
}
// special case: empty
if (clean_index == 0)
return string.cpy((seq.length() == 0) ? "None" : "n/a");
// start with an empty buffer
string.reset();
// loop until we hit the end
for (int codenum = 0; codenum < clean_index; codenum++)
{
// append a space if not the first code
if (codenum != 0)
string.cat(" ");
// handle OR/NOT codes here
input_code code = clean_codes[codenum];
if (code == input_seq::or_code)
string.cat("or");
else if (code == input_seq::not_code)
string.cat("not");
// otherwise, assume it is an input code and ask the input system to generate it
else
string.cat(code_name(codestr, code));
}
return string;
}
//-------------------------------------------------
// seq_to_tokens - generate the tokenized form of
// a sequence
//-------------------------------------------------
const char *input_manager::seq_to_tokens(astring &string, const input_seq &seq) const
{
// start with an empty buffer
string.reset();
// loop until we hit the end
astring codestr;
for (int codenum = 0; seq[codenum] != input_seq::end_code; codenum++)
{
// append a space if not the first code
if (codenum != 0)
string.cat(" ");
// handle OR/NOT codes here
input_code code = seq[codenum];
if (code == input_seq::or_code)
string.cat("OR");
else if (code == input_seq::not_code)
string.cat("NOT");
else if (code == input_seq::default_code)
string.cat("DEFAULT");
// otherwise, assume it is an input code and ask the input system to generate it
else
string.cat(code_to_token(codestr, code));
}
return string;
}
//-------------------------------------------------
// seq_from_tokens - generate the tokenized form
// of a sequence
//-------------------------------------------------
void input_manager::seq_from_tokens(input_seq &seq, const char *string)
{
// start with a blank sequence
seq.reset();
// loop until we're done
astring strcopy = string;
char *str = const_cast<char *>(strcopy.cstr());
while (1)
{
// trim any leading spaces
while (*str != 0 && isspace((UINT8)*str))
str++;
// bail if we're done
if (*str == 0)
return;
// find the end of the token and make it upper-case along the way
char *strtemp;
for (strtemp = str; *strtemp != 0 && !isspace((UINT8)*strtemp); strtemp++)
*strtemp = toupper((UINT8)*strtemp);
char origspace = *strtemp;
*strtemp = 0;
// look for common stuff
input_code code;
if (strcmp(str, "OR") == 0)
code = input_seq::or_code;
else if (strcmp(str, "NOT") == 0)
code = input_seq::not_code;
else if (strcmp(str, "DEFAULT") == 0)
code = input_seq::default_code;
else
code = code_from_token(str);
// translate and add to the sequence
seq += code;
// advance
if (origspace == 0)
return;
str = strtemp + 1;
}
}
//-------------------------------------------------
// set_global_joystick_map - set the joystick map
// for all devices
//-------------------------------------------------
bool input_manager::set_global_joystick_map(const char *mapstring)
{
// parse the map
joystick_map map;
if (!map.parse(mapstring))
return false;
astring tempstr;
mame_printf_verbose("Input: Changing default joystick map = %s", map.to_string(tempstr));
// iterate over joysticks and set the map
for (int joynum = 0; joynum <= m_joystick_class.maxindex(); joynum++)
{
input_device *device = m_joystick_class.device(joynum);
if (device != NULL)
device->set_joystick_map(map);
}
return true;
}
//**************************************************************************
// INPUT DEVICE ITEM
//**************************************************************************
//-------------------------------------------------
// input_device_item - constructor
//-------------------------------------------------
input_device_item::input_device_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate, input_item_class itemclass)
: m_device(device),
m_name(name),
m_internal(internal),
m_itemid(itemid),
m_itemclass(itemclass),
m_getstate(getstate),
m_current(0),
m_memory(0)
{
// use a standard token name for know item IDs
if (itemid <= ITEM_ID_MAXIMUM && (*itemid_token_table)[itemid] != NULL)
m_token.cpy((*itemid_token_table)[itemid]);
// otherwise, create a tokenized name
else
m_token.cpy(name).makeupper().delchr(' ').delchr('_');
}
//**************************************************************************
// INPUT DEVICE SWITCH ITEM
//**************************************************************************
//-------------------------------------------------
// input_device_switch_item - constructor
//-------------------------------------------------
input_device_switch_item::input_device_switch_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate)
: input_device_item(device, name, internal, itemid, getstate, ITEM_CLASS_SWITCH),
m_steadykey(0),
m_oldkey(0)
{
}
//-------------------------------------------------
// read_as_switch - return the raw switch value,
// modified as necessary
//-------------------------------------------------
INT32 input_device_switch_item::read_as_switch(input_item_modifier modifier)
{
// if we're doing a lightgun reload hack, button 1 and 2 operate differently
input_device_class devclass = m_device.devclass();
if (devclass == DEVICE_CLASS_LIGHTGUN && m_device.lightgun_reload_button())
{
// button 1 is pressed if either button 1 or 2 are active
if (m_itemid == ITEM_ID_BUTTON1)
{
input_device_item *button2_item = m_device.item(ITEM_ID_BUTTON2);
if (button2_item != NULL)
return button2_item->update_value() | update_value();
}
// button 2 is never officially pressed
if (m_itemid == ITEM_ID_BUTTON2)
return 0;
}
// steadykey for keyboards
if (devclass == DEVICE_CLASS_KEYBOARD && m_device.steadykey_enabled())
return m_steadykey;
// everything else is just the current value as-is
return update_value();
}
//-------------------------------------------------
// read_as_relative - return the switch input as
// a relative axis value
//-------------------------------------------------
INT32 input_device_switch_item::read_as_relative(input_item_modifier modifier)
{
// no translation to relative
return 0;
}
//-------------------------------------------------
// read_as_absolute - return the switch input as
// an absolute axis value
//-------------------------------------------------
INT32 input_device_switch_item::read_as_absolute(input_item_modifier modifier)
{
// no translation to absolute
return 0;
}
//-------------------------------------------------
// steadykey_changed - update for steadykey
// behavior, returning true if the current state
// has changed since the last call
//-------------------------------------------------
bool input_device_switch_item::steadykey_changed()
{
INT32 old = m_oldkey;
m_oldkey = update_value();
if (((m_current ^ old) & 1) == 0)
return false;
// if the keypress was missed, turn it on for one frame
if (((m_current | m_steadykey) & 1) == 0)
m_steadykey = 1;
return true;
}
//**************************************************************************
// INPUT DEVICE RELATIVE ITEM
//**************************************************************************
//-------------------------------------------------
// input_device_relative_item - constructor
//-------------------------------------------------
input_device_relative_item::input_device_relative_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate)
: input_device_item(device, name, internal, itemid, getstate, ITEM_CLASS_RELATIVE)
{
}
//-------------------------------------------------
// read_as_switch - return the relative value as
// a switch result based on the modifier
//-------------------------------------------------
INT32 input_device_relative_item::read_as_switch(input_item_modifier modifier)
{
// process according to modifiers
if (modifier == ITEM_MODIFIER_POS || modifier == ITEM_MODIFIER_RIGHT || modifier == ITEM_MODIFIER_DOWN)
return (update_value() > 0);
else if (modifier == ITEM_MODIFIER_NEG || modifier == ITEM_MODIFIER_LEFT || modifier == ITEM_MODIFIER_UP)
return (update_value() < 0);
// all other cases just return 0
return 0;
}
//-------------------------------------------------
// read_as_relative - return the relative input
// as a relative axis value
//-------------------------------------------------
INT32 input_device_relative_item::read_as_relative(input_item_modifier modifier)
{
// just return directly
return update_value();
}
//-------------------------------------------------
// read_as_absolute - return the relative input
// as an absolute axis value
//-------------------------------------------------
INT32 input_device_relative_item::read_as_absolute(input_item_modifier modifier)
{
// no translation to absolute
return 0;
}
//**************************************************************************
// INPUT DEVICE ABSOLUTE ITEM
//**************************************************************************
//-------------------------------------------------
// input_device_absolute_item - constructor
//-------------------------------------------------
input_device_absolute_item::input_device_absolute_item(input_device &device, const char *name, void *internal, input_item_id itemid, item_get_state_func getstate)
: input_device_item(device, name, internal, itemid, getstate, ITEM_CLASS_ABSOLUTE)
{
}
//-------------------------------------------------
// read_as_switch - return the absolute value as
// a switch result based on the modifier
//-------------------------------------------------
INT32 input_device_absolute_item::read_as_switch(input_item_modifier modifier)
{
// start with the current value
INT32 result = m_device.apply_deadzone_and_saturation(update_value());
assert(result >= INPUT_ABSOLUTE_MIN && result <= INPUT_ABSOLUTE_MAX);
// left/right/up/down: if this is a joystick, fetch the paired X/Y axis values and convert
if (m_device.devclass() == DEVICE_CLASS_JOYSTICK && modifier >= ITEM_MODIFIER_LEFT && modifier <= ITEM_MODIFIER_DOWN)
{
input_device_item *xaxis_item = m_device.item(ITEM_ID_XAXIS);
input_device_item *yaxis_item = m_device.item(ITEM_ID_YAXIS);
if (xaxis_item != NULL && yaxis_item != NULL)
{
// determine which item we didn't update, and update it
assert(this == xaxis_item || this == yaxis_item);
if (this == xaxis_item)
yaxis_item->update_value();
else
xaxis_item->update_value();
// now map the X and Y axes to a 9x9 grid using the raw values
return (m_device.joymap().update(xaxis_item->current(), yaxis_item->current()) >> (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 (result > 0);
else if (modifier == ITEM_MODIFIER_NEG || modifier == ITEM_MODIFIER_LEFT || modifier == ITEM_MODIFIER_UP)
return (result < 0);
// all other cases just return 0
return 0;
}
//-------------------------------------------------
// read_as_relative - return the absolute input
// as a relative axis value
//-------------------------------------------------
INT32 input_device_absolute_item::read_as_relative(input_item_modifier modifier)
{
// no translation to relative
return 0;
}
//-------------------------------------------------
// read_as_absolute - return the absolute input
// as an absolute axis value, with appropriate
// tweaks
//-------------------------------------------------
INT32 input_device_absolute_item::read_as_absolute(input_item_modifier modifier)
{
// start with the current value
INT32 result = m_device.apply_deadzone_and_saturation(update_value());
assert(result >= INPUT_ABSOLUTE_MIN && result <= INPUT_ABSOLUTE_MAX);
// if we're doing a lightgun reload hack, override the value
if (m_device.devclass() == DEVICE_CLASS_LIGHTGUN && m_device.lightgun_reload_button())
{
// if it is pressed, return (min,max)
input_device_item *button2_item = m_device.item(ITEM_ID_BUTTON2);
if (button2_item != NULL && button2_item->update_value())
result = (m_itemid == ITEM_ID_XAXIS) ? INPUT_ABSOLUTE_MIN : INPUT_ABSOLUTE_MAX;
}
// positive/negative: scale to full axis
if (modifier == ITEM_MODIFIER_POS)
result = MAX(result, 0) * 2 + INPUT_ABSOLUTE_MIN;
if (modifier == ITEM_MODIFIER_NEG)
result = MAX(-result, 0) * 2 + INPUT_ABSOLUTE_MIN;
return result;
}