2 registered members (Quad, degenerate_762),
642
guests, and 4
spiders. |
Key:
Admin,
Global Mod,
Mod
|
|
|
Re: [SUB] Key stroke combos as event triggers
[Re: txesmi]
#468920
10/27/17 14:33
10/27/17 14:33
|
Joined: Jun 2007
Posts: 1,337 Hiporope and its pain
txesmi
OP
Serious User
|
OP
Serious User
Joined: Jun 2007
Posts: 1,337
Hiporope and its pain
|
DEPRECATEDIt 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.
#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.
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.
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
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.
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.
comboCreate ( KEY_A, NULL, KEY_S, evnAS ); // Releasing A is needed for AS combo completion
Source[spoiler] combos.h
#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
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.
|
|
|
|