Click to reveal..
DEPRECATED

It calls a function when a combo gets completed. It works with collections of scancodes as references to keys. The data is stored in a linked tree manner where key nodes hold their next possible keys. This way the combo detector fuction only searchs into the smallest scope of possible combos.

There are only three public functions:
- comboRun makes the system work.
- comboCreate creates a new combo.
- combosRemove removes all created combos.

Code:
#define KEY_A     30
#define KEY_S     31
#define KEY_D     32

void myEvent ()
{
	printf ( "COMBO!" );
}

void playerCombosCreate ()
{
	var _keys[8] = { KEY_A, KEY_A, KEY_A, KEY_S, KEY_S, KEY_A, KEY_S, KEY_D };
	comboCreate ( _keys, 8, myEvent );
	comboCreate ( vector(KEY_A, KEY_S, NULL), 2, myEvent );
	comboCreate ( KEY_A, KEY_S, KEY_D, myEvent );
	comboCreate ( KEY_A, KEY_D, KEY_A, KEY_D, KEY_A, myEvent );
}

action actPlayer ()
{
	combosRemove ();
	playerCombosCreate ();
	player = me;
	while ( player )
	{
		var *_scancodes = comboRun ( NULL );
		wait(1);
	}
	combosRemove ();
}



So it needs to:
- create some combos with any of the versions of comboCreate
- make the system run by calling comboRun inside a loop
- remember to remove stored data when finished
You can deactivate a combo by calling comboCreate with its scancode sequence and a NULL event and reactivate it with a valid event. You can also empty the combos memory at any time, (fixed)but not inside a combo event! If your needs, wait a frame as in engine events. You will need to ensure COMBO_LENGTH_MAX is almost twice the longer scancode sequence of your project.

comboRun returns a pointer to an array containing a secuence of scancodes that has been detected as into a combo at that run, so it can be evaluated.

Code:
str_cpy ( str, "Combo on air: " );
var *_scodes = comboRun ();
for ( ; *_scodes!=-1; _scodes++ )
{
	str_cat ( str, str_for_key ( NULL, *_scodes ) );
	str_cat ( str, " " );
}
draw_text ( str, 10, 600, COLOR_WHITE );



The module internaly incorporates a 'key released' virtual scancode, so it states a bunch of ways of calling the same event as combinations of the scancodes secuence and this virtual scancode. (fixed)So the return of comboRun may contain some zeros interleaved. The resulting data tree of creating DAD, ADD and AA key strokes combos, drawn with a recursive function:



'rls' means that 'key release' virtual scancode I spoke about.

Code:
TEXT *txtChain = { string = ( "-", "" ); }
STRING *str = "";
FONT *fnt = "Courier#16";
var drawComboChildren ( COMBO *_cmb, var _posX, var _posY )
{
	var _posYbak = _posY;
	if ( _cmb == comboRoot )
	{
		draw_text ( "ROOT", _posX, _posY, COLOR_WHITE );
		_posX += str_width("ROOT",fnt) + 24;
	}
	COMBO *_cmbChild = _cmb->child;
	for ( ; _cmbChild!=NULL; _cmbChild=_cmbChild->next )
	{
		if ( _posYbak == _posY )
			draw_text ( (txtChain->pstring)[0], _posX-16, _posY, COLOR_WHITE );
		else
			draw_text ( (txtChain->pstring)[1], _posX-16, _posY, COLOR_WHITE );
		if ( _cmbChild->key == 280 )
			str_cpy ( str, "mouse left" );
		else if ( _cmbChild->key == 281 )
			str_cpy ( str, "mouse right" );
		else if ( _cmbChild->key == 0 )
			str_cpy ( str, "rls" );
		else
			str_for_key ( str, _cmbChild->key );
		if ( _cmbChild->event )
		{
			char *_name;
			engine_getscriptinfo ( _cmbChild->event, &_name );
			str_cat ( str, " -> " );
			str_cat ( str, _name );
		}
		draw_text ( str, _posX, _posY, COLOR_WHITE );
		var _posMax = drawComboChildren ( _cmbChild, _posX+str_width(str,fnt)+24, _posY );
		_posY = maxv ( _posY+16,_posMax );
	}
	return _posY;
}


Each node of the tree is a COMBO data struct

Code:
typedef struct COMBO {
	var key;
	struct COMBO *child;
	void event ( var *_result, void *_ptr );
	struct COMBO *next;
} COMBO;


that holds a linked list of the next possible key strokes. I set three combos but there are only two branches borning from the root since ADD and AA share their first key.

comboRun has two code parts. The first looks for changes on key strokes and if any, executes the second part just once. comboRun has a static pointer to a COMBO struct that serves as combo state. It begins been the root but may climb the data tree step by step when any of its borning branches coincide with the actual key stroke change. If not, it goes back to the root and starts again.

It is possible to make the event be executed when last key is released by adding NULL as last member in the scancodes sequence.

Code:
comboCreate ( KEY_A, KEY_S, NULL, evnAS );



It is possible to force a key be released before pressing next one in order to complete the combo by adding NULL between scancodes.

Code:
comboCreate ( KEY_A, NULL, KEY_S, evnAS ); // Releasing A is needed for AS combo completion



Source
[spoiler]
combos.h
Code:
#ifndef _COMBOS_H_
#define _COMBOS_H_

#define COMBO_TIMEOUT        4
#define COMBO_LENGTH_MAX     16

typedef struct COMBO {
	var key;
	struct COMBO *child;
	void event ( var *_result, void *_ptr );
	struct COMBO *next;
} COMBO;

COMBO *comboRoot = { key = NULL; child = NULL; event = NULL; next = NULL; }

var comboRun ( void *_ptr );
void comboCreate ( var *_keys, int _count, void *_event );
void comboCreate ( var _key1, var _key2, var _key3, void *_event );
void comboCreate ( var _key1, var _key2, var _key3, var _key4, void *_event );
void comboCreate ( var _key1, var _key2, var _key3, var _key4, var _key5, void *_event );
void comboCreate ( var _key1, var _key2, var _key3, var _key4, var _key5, var _key6, void *_event );
void comboCreate ( var _key1, var _key2, var _key3, var _key4, var _key5, var _key6, var _key7, void *_event );
void combosRemove ();

#include "combos.c"
#endif



combos.c
Code:
void comboRemove ( COMBO *_cmb ) {
	COMBO *_cmbChild = _cmb->child;
	while ( _cmbChild )
	{
		COMBO *_cmbNext = _cmbChild->next;
		comboRemove ( _cmbChild );
		_cmbChild = _cmbNext;
	}
	sys_free ( _cmb );
}

void combosRemove () {
	COMBO *_cmbChild = comboRoot->child;
	while ( _cmbChild )
	{
		COMBO *_cmbNext = _cmbChild->next;
		comboRemove ( _cmbChild );
		_cmbChild = _cmbNext;
	}
	comboRoot->child = NULL;
}

COMBO *comboChildFind ( COMBO *_cmb, var _key ) {
	COMBO *_cmbChild = _cmb->child;
	for ( ; _cmbChild!=NULL; _cmbChild=_cmbChild->next ) {
		if ( _cmbChild->key < _key )
			return NULL;
		if ( _cmbChild->key == _key )
			return _cmbChild;
	}
	return NULL;
}

COMBO *comboChildAdd ( COMBO *_cmb, var _key, void *_event ) {
	COMBO *_cmbNew = sys_malloc ( sizeof(COMBO) );
	_cmbNew->key = _key;
	_cmbNew->child = NULL;
	_cmbNew->event = _event;
	_cmbNew->next = NULL;
	COMBO *_cmbChild = _cmb->child;
	if ( !_cmbChild ) {                                     // first child
		_cmb->child = _cmbNew;
	} else if ( _cmbChild->key < _key ) {                   // lowest key on childhood
		_cmbNew->next = _cmbChild;
		_cmb->child = _cmbNew;
	} else {                                                // insert sorted by key, lower first
		for ( ; _cmbChild->next!=NULL; _cmbChild=_cmbChild->next ) {
			COMBO *_cmbNext = _cmbChild->next;
			if ( _cmbNext->key > _key )
				continue;
			_cmbChild->next = _cmbNew;
			_cmbNew->next = _cmbNext;
			break;
		}
		if ( _cmbChild->next == NULL )
			_cmbChild->next = _cmbNew;
	}
	return _cmbNew;
}

void comboSet ( var *_keys, int _count, void *_event ) {
	int _i = 0;
	for ( ; _i<_count-1; _i+=1 )                            // check viability
		if ( _keys[_i] == _keys[_i+1] )
			return;
	COMBO *_cmb = comboRoot;                                // actual combo branch, root init
	int _i = 0;
	for ( ; _i<_count; _i+=1 ) {
		COMBO *_cmbT = comboChildFind ( _cmb, *_keys );      // search into the actual combo branch
		if ( _cmbT )                                         // succesful, so the child becomes the parent 
			_cmb = _cmbT;
		else                                                 // failure, new one is needed
			_cmb = comboChildAdd ( _cmb, *_keys, NULL );
		_keys ++;                                            // advance a position in the array
	}
	_cmb->event = _event;
}


void comboVariants ( var *_keys, var *_keys2, int _index, int _count, void *_event ) {
	memcpy ( _keys2+_index, _keys, (_count-_index)*sizeof(var) );
	comboSet ( _keys2, _count, _event );
	int _i = _index;
	for ( ; _i<_count-1; _i+=1 ) {
		int _offset = _i - _index;
		memcpy ( _keys2+_i, _keys+_offset, sizeof(var) );
		_keys2[_i+1] = NULL;
		memcpy ( _keys2+_i+2, _keys+1+_offset, (_count-(_i+1))*sizeof(var) );
		comboVariants ( _keys+1+_offset, _keys2, _index+2+_offset, _count+1, _event );
	}
}

void comboCreate (  var *_keys, int _count, void *_event ) {
	var _keys2[COMBO_LENGTH_MAX];
	comboVariants ( _keys, _keys2, 0, _count, _event );
}

void comboCreate (  var _key1, var _key2, var _key3, void *_event ) {
	var _keys2[COMBO_LENGTH_MAX];
	comboVariants ( &_key1, _keys2, 0, 3, _event );
}

void comboCreate (  var _key1, var _key2, var _key3, var _key4, void *_event ) {
	var _keys2[COMBO_LENGTH_MAX];
	comboVariants ( &_key1, _keys2, 0, 4, _event );
}

void comboCreate (  var _key1, var _key2, var _key3, var _key4, var _key5, void *_event ) {
	var _keys2[COMBO_LENGTH_MAX];
	comboVariants ( &_key1, _keys2, 0, 5, _event );
}

void comboCreate (  var _key1, var _key2, var _key3, var _key4, var _key5, var _key6, void *_event ) {
	var _keys2[COMBO_LENGTH_MAX];
	comboVariants ( &_key1, _keys2, 0, 6, _event );
}

void comboCreate (  var _key1, var _key2, var _key3, var _key4, var _key5, var _key6, var _key7, void *_event ) {
	var _keys2[COMBO_LENGTH_MAX];
	comboVariants ( &_key1, _keys2, 0, 7, _event );
}

var *comboRun ( void *_ptr ) {
	static var _result[COMBO_LENGTH_MAX] = { -1 };
	static int _index = 0;
	static var _keyLast = 0;
	static var _t = total_ticks;
	static COMBO *_cmb = comboRoot;                         // actual combo branch, root init
	var _key = key_lastpressed;
	if ( !key_pressed(_key) )                               // force a 'not pressed key' event
		_key = 0;
	if ( ( _t < total_ticks ) && ( _cmb != comboRoot ) ) {  // restart climbing if last change was a while ago
		_cmb = comboRoot;   
		_result[0] = -1;
		_index = 0;
	}
	if ( _key == _keyLast )                                 // nothing to do when changes are scarce
		return _result;
	_keyLast = _key;
	_cmb = comboChildFind ( _cmb, _key );                   // search into the actual combo branch
	if ( _cmb ) {                                           // succesful, so the child becomes the parent 
		if ( _key != 0 ) {                                   // update active combo only when a true key has been pressed
			_t = total_ticks + COMBO_TIMEOUT;
			_result[_index] = _key;
			_result[_index+1] = -1;
			_index += 1;
		}            
		if ( _cmb->event )                                   // at the end, we have come this far for this
			_cmb->event ( _result, _ptr );
	} else {                                                // failure, check if failed key starts a new combo
		if ( _cmb != comboRoot )
			_cmb = comboChildFind ( comboRoot, _key );        
		if ( _cmb ) {                                        // succesful, so the child becomes the parent 
			_t = total_ticks + COMBO_TIMEOUT;
			_result[0] = _key;
			_result[1] = -1;
			_index = 1;
			if ( _cmb->event )               
				_cmb->event ( _result, _ptr );
		} else {                                             // failure, back to the root
			_cmb = comboRoot;
			_t = total_ticks;
			_result[0] = -1;
			_index = 0;
		}
	}
	if ( !comboRoot->child ) {                              // reset local stuff when comboRoot is empty
		_cmb = comboRoot;
		_t = total_ticks;
		_index = 0;
		_result[0] = -1;
	}
	return _result;
}



Last edited by txesmi; 11/03/17 02:36.