/*************************************************************************** inputseq.c Input sequence abstractions. Copyright Nicola Salmoria and the MAME Team. Visit http://mamedev.org for licensing and usage restrictions. ***************************************************************************/ #include "inputseq.h" #include "restrack.h" #include /*************************************************************************** CONSTANTS ***************************************************************************/ /* max time between key presses */ #define RECORD_TIME (osd_ticks_per_second() * 2 / 3) /*************************************************************************** GLOBAL VARIABLES ***************************************************************************/ /* information about the current sequence being recorded */ static input_seq record_seq; static osd_ticks_t record_last; static input_item_class record_class; /*************************************************************************** FUNCTION PROTOTYPES ***************************************************************************/ /* internal functions */ static int input_seq_is_valid(const input_seq *seq); /*************************************************************************** INLINE FUNCTIONS ***************************************************************************/ /*------------------------------------------------- input_seq_length - return the length of the sequence -------------------------------------------------*/ INLINE int input_seq_length(const input_seq *seq) { int seqnum; /* find the end token; error if none found */ for (seqnum = 0; seqnum < ARRAY_LENGTH(seq->code); seqnum++) if (seq->code[seqnum] == SEQCODE_END) return seqnum; return ARRAY_LENGTH(seq->code); } /*------------------------------------------------- input_seq_append - append a code to the end of an input sequence -------------------------------------------------*/ INLINE int input_seq_append(input_seq *seq, input_code code) { int length = input_seq_length(seq); /* if not enough room, return FALSE */ if (length >= ARRAY_LENGTH(seq->code) - 1) return FALSE; /* otherwise, append the code and add a new end */ seq->code[length++] = code; seq->code[length] = SEQCODE_END; return TRUE; } /*------------------------------------------------- input_seq_get_last - return the last code in a sequence -------------------------------------------------*/ INLINE input_code input_seq_get_last(const input_seq *seq) { int length = input_seq_length(seq); return (length == 0) ? SEQCODE_END : seq->code[length - 1]; } /*------------------------------------------------- input_seq_get_last_but_one - return the last_but_one code in a sequence -------------------------------------------------*/ INLINE input_code input_seq_get_last_but_one(const input_seq *seq) { int length = input_seq_length(seq); return (length < 2) ? SEQCODE_END : seq->code[length - 2]; } /*------------------------------------------------- input_seq_backspace - "backspace" over the last entry in a sequence -------------------------------------------------*/ INLINE void input_seq_backspace(input_seq *seq) { int length = input_seq_length(seq); /* if we have at least one entry, remove it */ if (length > 0) seq->code[length - 1] = SEQCODE_END; } /*************************************************************************** STATE QUERIES ***************************************************************************/ /*------------------------------------------------- input_seq_pressed - return true if the given sequence of switch inputs is "pressed" -------------------------------------------------*/ int input_seq_pressed(running_machine *machine, const input_seq *seq) { int result = FALSE; int invert = FALSE; int first = TRUE; int codenum; /* iterate over all of the codes */ for (codenum = 0; codenum < ARRAY_LENGTH(seq->code); codenum++) { input_code code = seq->code[codenum]; /* handle NOT */ if (code == SEQCODE_NOT) invert = TRUE; /* handle OR and END */ else if (code == SEQCODE_OR || code == SEQCODE_END) { /* if we have a positive result from the previous set, we're done */ if (result || code == SEQCODE_END) 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 = input_code_pressed(machine, code) ^ invert; /* further values are ANDed */ else if (result) result &= input_code_pressed(machine, 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; } /*------------------------------------------------- input_seq_axis_value - return the value of an axis defined in an input sequence -------------------------------------------------*/ INT32 input_seq_axis_value(running_machine *machine, const input_seq *seq, input_item_class *itemclass_ptr) { input_item_class itemclasszero = ITEM_CLASS_ABSOLUTE; input_item_class itemclass = ITEM_CLASS_INVALID; int result = 0; int invert = FALSE; int enable = TRUE; int codenum; /* iterate over all of the codes */ for (codenum = 0; codenum < ARRAY_LENGTH(seq->code); codenum++) { input_code code = seq->code[codenum]; /* handle NOT */ if (code == SEQCODE_NOT) invert = TRUE; /* handle OR and END */ else if (code == SEQCODE_OR || code == SEQCODE_END) { /* if we have a positive result from the previous set, we're done */ if (itemclass != ITEM_CLASS_INVALID || code == SEQCODE_END) 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 (INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_SWITCH) { /* AND against previous digital codes */ if (enable) enable &= input_code_pressed(machine, code) ^ invert; } /* non-switch codes are analog values */ else { INT32 value = input_code_value(machine, 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 = INPUT_CODE_ITEMCLASS(code); } /* non-zero absolute values stick */ else if (INPUT_CODE_ITEMCLASS(code) == ITEM_CLASS_ABSOLUTE) { itemclass = ITEM_CLASS_ABSOLUTE; result = value; } /* non-zero relative values accumulate */ else if (INPUT_CODE_ITEMCLASS(code) == 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 (itemclass_ptr != NULL) *itemclass_ptr = (result == 0) ? itemclasszero : itemclass; return result; } /*************************************************************************** SEQUENCE POLLING ***************************************************************************/ /*------------------------------------------------- input_seq_poll_start - begin polling for a new sequence of the given itemclass -------------------------------------------------*/ void input_seq_poll_start(running_machine *machine, input_item_class itemclass, const input_seq *startseq) { input_code dummycode; assert(itemclass == ITEM_CLASS_SWITCH || itemclass == ITEM_CLASS_ABSOLUTE || itemclass == ITEM_CLASS_RELATIVE); /* reset the recording count and the clock */ record_last = 0; record_class = itemclass; /* grab the starting sequence to append to */ if (startseq != NULL) record_seq = *startseq; else input_seq_set_0(&record_seq); /* append an OR if this is not a NULL sequence */ if (input_seq_length(&record_seq) > 0) input_seq_append(&record_seq, SEQCODE_OR); /* flush out any goobers */ dummycode = (record_class == ITEM_CLASS_SWITCH) ? input_code_poll_switches(machine, TRUE) : input_code_poll_axes(machine, TRUE); while (dummycode != INPUT_CODE_INVALID) dummycode = (record_class == ITEM_CLASS_SWITCH) ? input_code_poll_switches(machine, FALSE) : input_code_poll_axes(machine, FALSE); } /*------------------------------------------------- input_seq_poll - continue polling -------------------------------------------------*/ int input_seq_poll(running_machine *machine, input_seq *finalseq) { input_code lastcode = input_seq_get_last(&record_seq); int has_or = FALSE; input_code newcode; /* switch case: see if we have a new code to process */ if (record_class == ITEM_CLASS_SWITCH) { newcode = input_code_poll_switches(machine, FALSE); if (newcode != INPUT_CODE_INVALID) { /* if code is duplicate, toggle the NOT state on the code */ if (input_seq_length(&record_seq) > 0 && newcode == lastcode) { /* back up over the code */ input_seq_backspace(&record_seq); /* if there was a NOT preceding it, just delete it, otherwise append one */ if (input_seq_get_last(&record_seq) == SEQCODE_NOT) input_seq_backspace(&record_seq); else input_seq_append(&record_seq, SEQCODE_NOT); } } } /* absolute/relative case: see if we have an analog change of sufficient amount */ else { if (lastcode == SEQCODE_OR) { lastcode = input_seq_get_last_but_one(&record_seq); has_or = TRUE; } newcode = input_code_poll_axes(machine, FALSE); /* if the last code doesn't match absolute/relative of this code, ignore the new one */ if ((INPUT_CODE_ITEMCLASS(lastcode) == ITEM_CLASS_ABSOLUTE && INPUT_CODE_ITEMCLASS(newcode) != ITEM_CLASS_ABSOLUTE) || (INPUT_CODE_ITEMCLASS(lastcode) == ITEM_CLASS_RELATIVE && INPUT_CODE_ITEMCLASS(newcode) != ITEM_CLASS_RELATIVE)) newcode = INPUT_CODE_INVALID; if (newcode != INPUT_CODE_INVALID) { /* if code is duplicate and an absolute control, toggle to half axis */ if (input_seq_length(&record_seq) > 0 && INPUT_CODE_ITEMCLASS(newcode) == ITEM_CLASS_ABSOLUTE) { if (newcode == INPUT_CODE_SET_MODIFIER(lastcode, ITEM_MODIFIER_NONE)) { /* increment the modifier, wrapping back to none */ input_item_modifier oldmod = INPUT_CODE_MODIFIER(lastcode); input_item_modifier newmod = (oldmod < ITEM_MODIFIER_NEG) ? (input_item_modifier)(oldmod + 1) : ITEM_MODIFIER_NONE; newcode = INPUT_CODE_SET_MODIFIER(newcode, newmod); /* back up over the previous code so we can re-append */ if (has_or) input_seq_backspace(&record_seq); input_seq_backspace(&record_seq); } } } } /* if we got a new code to append it, append it and reset the timer */ if (newcode != INPUT_CODE_INVALID) { input_seq_append(&record_seq, newcode); record_last = osd_ticks(); } /* if we're recorded at least one item and the RECORD_TIME has passed, we're done */ if (record_last != 0 && (osd_ticks() > record_last + RECORD_TIME)) { /* if the final result is invalid, reset to nothing */ if (!input_seq_is_valid(&record_seq)) input_seq_set_0(&record_seq); /* return TRUE to indicate that we are finished */ *finalseq = record_seq; return TRUE; } /* return FALSE to indicate we are still polling */ return FALSE; } /*************************************************************************** STRINGS AND TOKENIZATION ***************************************************************************/ /*------------------------------------------------- input_seq_name - generate the friendly name of a sequence -------------------------------------------------*/ astring *input_seq_name(running_machine *machine, astring *string, const input_seq *seq) { astring *codestr = astring_alloc(); int codenum, copycodenum; input_seq seqcopy; /* walk the sequence first, removing any pieces that are invalid */ for (codenum = copycodenum = 0; codenum < ARRAY_LENGTH(seq->code) && seq->code[codenum] != SEQCODE_END; codenum++) { input_code code = seq->code[codenum]; /* if this is a code item which is not valid, don't copy it and remove any preceding ORs/NOTs */ if (!INPUT_CODE_IS_INTERNAL(code) && astring_len(input_code_name(machine, codestr, code)) == 0) { while (copycodenum > 0 && INPUT_CODE_IS_INTERNAL(seqcopy.code[copycodenum - 1])) copycodenum--; } else if (copycodenum > 0 || !INPUT_CODE_IS_INTERNAL(code)) seqcopy.code[copycodenum++] = code; } seqcopy.code[copycodenum] = SEQCODE_END; /* special case: empty */ if (copycodenum == 0) { astring_free(codestr); return astring_cpyc(string, (seq->code[0] == SEQCODE_END) ? "None" : "n/a"); } /* start with an empty buffer */ astring_reset(string); /* loop until we hit the end */ for (codenum = 0; codenum < ARRAY_LENGTH(seqcopy.code) && seqcopy.code[codenum] != SEQCODE_END; codenum++) { input_code code = seqcopy.code[codenum]; /* append a space if not the first code */ if (codenum != 0) astring_catc(string, " "); /* handle OR/NOT codes here */ if (code == SEQCODE_OR) astring_catc(string, "or"); else if (code == SEQCODE_NOT) astring_catc(string, "not"); /* otherwise, assume it is an input code and ask the input system to generate it */ else astring_cat(string, input_code_name(machine, codestr, code)); } astring_free(codestr); return string; } /*------------------------------------------------- input_seq_to_tokens - generate the tokenized form of a sequence -------------------------------------------------*/ astring *input_seq_to_tokens(running_machine *machine, astring *string, const input_seq *seq) { astring *codestr = astring_alloc(); int codenum; /* start with an empty buffer */ astring_reset(string); /* loop until we hit the end */ for (codenum = 0; codenum < ARRAY_LENGTH(seq->code) && seq->code[codenum] != SEQCODE_END; codenum++) { input_code code = seq->code[codenum]; /* append a space if not the first code */ if (codenum != 0) astring_catc(string, " "); /* handle OR/NOT codes here */ if (code == SEQCODE_OR) astring_catc(string, "OR"); else if (code == SEQCODE_NOT) astring_catc(string, "NOT"); else if (code == SEQCODE_DEFAULT) astring_catc(string, "DEFAULT"); /* otherwise, assume it is an input code and ask the input system to generate it */ else astring_cat(string, input_code_to_token(machine, codestr, code)); } astring_free(codestr); return string; } /*------------------------------------------------- input_seq_from_tokens - generate the tokenized form of a sequence -------------------------------------------------*/ int input_seq_from_tokens(running_machine *machine, const char *string, input_seq *seq) { char *strcopy = alloc_array_or_die(char, strlen(string) + 1); char *str = strcopy; int result = FALSE; /* start with a blank sequence */ input_seq_set_0(seq); /* loop until we're done */ strcpy(strcopy, string); while (1) { input_code code; char origspace; char *strtemp; /* trim any leading spaces */ while (*str != 0 && isspace((UINT8)*str)) str++; /* bail if we're done */ if (*str == 0) { result = TRUE; break; } /* find the end of the token and make it upper-case along the way */ for (strtemp = str; *strtemp != 0 && !isspace((UINT8)*strtemp); strtemp++) *strtemp = toupper((UINT8)*strtemp); origspace = *strtemp; *strtemp = 0; /* look for common stuff */ if (strcmp(str, "OR") == 0) code = SEQCODE_OR; else if (strcmp(str, "NOT") == 0) code = SEQCODE_NOT; else if (strcmp(str, "DEFAULT") == 0) code = SEQCODE_DEFAULT; else code = input_code_from_token(machine, str); /* translate and add to the sequence */ input_seq_append(seq, code); /* advance */ if (origspace == 0) { result = TRUE; break; } str = strtemp + 1; } free(strcopy); return result; } /*************************************************************************** INTERNAL FUNCTIONS ***************************************************************************/ /*------------------------------------------------- input_seq_is_valid - return TRUE if a given sequence is valid -------------------------------------------------*/ static int input_seq_is_valid(const input_seq *seq) { input_item_class lastclass = ITEM_CLASS_INVALID; input_code lastcode = INPUT_CODE_INVALID; int positive_code_count = 0; int seqnum; /* scan the sequence for valid codes */ for (seqnum = 0; seqnum < ARRAY_LENGTH(seq->code); seqnum++) { input_code code = seq->code[seqnum]; /* invalid codes are invalid */ if (code == INPUT_CODE_INVALID) return FALSE; /* if we hit an OR or the end, validate the previous chunk */ if (code == SEQCODE_OR || code == SEQCODE_END) { /* must be at least one positive code */ if (positive_code_count == 0) return FALSE; /* last code must not have been an internal code */ if (INPUT_CODE_IS_INTERNAL(lastcode)) return FALSE; /* if this is the end, we're ok */ if (code == SEQCODE_END) 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 == SEQCODE_NOT) { if (lastcode == SEQCODE_NOT) return FALSE; } /* anything else */ else { input_item_class itemclass = INPUT_CODE_ITEMCLASS(code); /* count positive codes */ if (lastcode != SEQCODE_NOT) positive_code_count++; /* non-switch items can't have a NOT */ if (itemclass != ITEM_CLASS_SWITCH && lastcode == SEQCODE_NOT) 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; }