Răsfoiți Sursa

NEW CRAZY FEATURE: EVENTS AUTOMATION!

https://youtu.be/3dZenkpmRzM
Ray 4 ani în urmă
părinte
comite
de7b8ad551
2 a modificat fișierele cu 528 adăugiri și 3 ștergeri
  1. 2 0
      src/config.h
  2. 526 3
      src/core.c

+ 2 - 0
src/config.h

@@ -55,6 +55,8 @@
 #define SUPPORT_COMPRESSION_API     1
 // Support saving binary data automatically to a generated storage.data file. This file is managed internally.
 #define SUPPORT_DATA_STORAGE        1
+// Support automatic generated events, loading and recording of those events when required
+#define SUPPORT_EVENTS_AUTOMATION     1
 
 // core: Configuration values
 //------------------------------------------------------------------------------------

+ 526 - 3
src/core.c

@@ -82,6 +82,8 @@
 *   #define SUPPORT_DATA_STORAGE
 *       Support saving binary data automatically to a generated storage.data file. This file is managed internally
 *
+*   #define SUPPORT_EVENTS_AUTOMATION
+*       Support automatic generated events, loading and recording of those events when required
 *
 *   DEPENDENCIES:
 *       rglfw    - Manage graphic device, OpenGL context and inputs on PLATFORM_DESKTOP (Windows, Linux, OSX. FreeBSD, OpenBSD, NetBSD, DragonFly)
@@ -476,6 +478,7 @@ typedef struct CoreData {
 #if defined(PLATFORM_ANDROID) || defined(PLATFORM_RPI) || defined(PLATFORM_DRM) || defined(PLATFORM_UWP)
         unsigned long long base;            // Base time measure for hi-res timer
 #endif
+        unsigned int frameCounter;          // Frame counter
     } Time;
 } CoreData;
 
@@ -496,6 +499,93 @@ static int gifFramesCounter = 0;            // GIF frames counter
 static bool gifRecording = false;           // GIF recording state
 static MsfGifState gifState = { 0 };        // MSGIF context state
 #endif
+
+#if defined(SUPPORT_EVENTS_AUTOMATION)
+#define MAX_CODE_AUTOMATION_EVENTS      16384
+
+typedef enum AutomationEventType {
+    EVENT_NONE = 0,
+    // Input events
+    INPUT_KEY_UP,                   // param[0]: key
+    INPUT_KEY_DOWN,                 // param[0]: key
+    INPUT_KEY_PRESSED,              // param[0]: key
+    INPUT_KEY_RELEASED,             // param[0]: key
+    INPUT_MOUSE_BUTTON_UP,          // param[0]: button
+    INPUT_MOUSE_BUTTON_DOWN,        // param[0]: button
+    INPUT_MOUSE_POSITION,           // param[0]: x, param[1]: y
+    INPUT_MOUSE_WHEEL_MOTION,       // param[0]: delta
+    INPUT_GAMEPAD_CONNECT,          // param[0]: gamepad
+    INPUT_GAMEPAD_DISCONNECT,       // param[0]: gamepad
+    INPUT_GAMEPAD_BUTTON_UP,        // param[0]: button
+    INPUT_GAMEPAD_BUTTON_DOWN,      // param[0]: button
+    INPUT_GAMEPAD_AXIS_MOTION,      // param[0]: axis, param[1]: delta
+    INPUT_TOUCH_UP,                 // param[0]: id
+    INPUT_TOUCH_DOWN,               // param[0]: id
+    INPUT_TOUCH_POSITION,           // param[0]: x, param[1]: y
+    INPUT_GESTURE,                  // param[0]: gesture
+    // Window events
+    WINDOW_CLOSE,                   // no params
+    WINDOW_MAXIMIZE,                // no params
+    WINDOW_MINIMIZE,                // no params
+    WINDOW_RESIZE,                  // param[0]: width, param[1]: height
+    // Custom events
+    ACTION_TAKE_SCREENSHOT,
+    ACTION_SETTARGETFPS
+} AutomationEventType;
+
+// Event type
+// Used to enable events flags
+typedef enum {
+    EVENT_INPUT_KEYBOARD    = 0,
+    EVENT_INPUT_MOUSE       = 1,
+    EVENT_INPUT_GAMEPAD     = 2,
+    EVENT_INPUT_TOUCH       = 4,
+    EVENT_INPUT_GESTURE     = 8,
+    EVENT_WINDOW            = 16,
+    EVENT_CUSTOM            = 32
+} EventType;
+
+static const char *autoEventTypeName[] = {
+    "EVENT_NONE",
+    "INPUT_KEY_UP",             
+    "INPUT_KEY_DOWN",           
+    "INPUT_KEY_PRESSED",        
+    "INPUT_KEY_RELEASED",       
+    "INPUT_MOUSE_BUTTON_UP",    
+    "INPUT_MOUSE_BUTTON_DOWN",  
+    "INPUT_MOUSE_POSITION",     
+    "INPUT_MOUSE_WHEEL_MOTION", 
+    "INPUT_GAMEPAD_CONNECT",    
+    "INPUT_GAMEPAD_DISCONNECT", 
+    "INPUT_GAMEPAD_BUTTON_UP",  
+    "INPUT_GAMEPAD_BUTTON_DOWN",
+    "INPUT_GAMEPAD_AXIS_MOTION",
+    "INPUT_TOUCH_UP",           
+    "INPUT_TOUCH_DOWN",         
+    "INPUT_TOUCH_POSITION",     
+    "INPUT_GESTURE",            
+    "WINDOW_CLOSE",             
+    "WINDOW_MAXIMIZE",          
+    "WINDOW_MINIMIZE",          
+    "WINDOW_RESIZE",            
+    "ACTION_TAKE_SCREENSHOT",
+    "ACTION_SETTARGETFPS"
+};
+
+// Automation Event (20 bytes)
+typedef struct AutomationEvent {
+    unsigned int frame;                 // Event frame
+    unsigned int type;                  // Event type (AutoEventType)
+    int params[3];                      // Event parameters (if required)
+} AutomationEvent;
+
+static AutomationEvent *events = NULL;        // Events array
+static unsigned int eventCount = 0;     // Events count
+static bool eventsPlaying = false;      // Play events
+static bool eventsRecording = false;    // Record events
+
+//static short eventsEnabled = 0b0000001111111111;    // Events enabled for checking
+#endif
 //-----------------------------------------------------------------------------------
 
 //----------------------------------------------------------------------------------
@@ -574,6 +664,13 @@ static int FindNearestConnectorMode(const drmModeConnector *connector, uint widt
 
 #endif  // PLATFORM_RPI || PLATFORM_DRM
 
+#if defined(SUPPORT_EVENTS_AUTOMATION)
+static void LoadAutomationEvents(const char *fileName);
+static void ExportAutomationEvents(const char *fileName);
+static void RecordAutomationEvent(unsigned int frame);
+static void PlayAutomationEvent(unsigned int frame);
+#endif
+
 #if defined(_WIN32)
     // NOTE: We include Sleep() function signature here to avoid windows.h inclusion (kernel32 lib)
     void __stdcall Sleep(unsigned long msTimeout);      // Required for Wait()
@@ -799,7 +896,13 @@ void InitWindow(int width, int height, const char *title)
 
     CORE.Input.Mouse.currentPosition.x = (float)CORE.Window.screen.width/2.0f;
     CORE.Input.Mouse.currentPosition.y = (float)CORE.Window.screen.height/2.0f;
-#endif        // PLATFORM_ANDROID
+    
+#if defined(SUPPORT_EVENTS_AUTOMATION)
+    events = (AutomationEvent *)malloc(MAX_CODE_AUTOMATION_EVENTS*sizeof(AutomationEvent));
+    CORE.Time.frameCounter = 0;
+#endif
+    
+#endif        // PLATFORM_DESKTOP || PLATFORM_WEB || PLATFORM_RPI || PLATFORM_DRM || PLATFORM_UWP
 }
 
 // Close window and unload OpenGL context
@@ -925,6 +1028,10 @@ void CloseWindow(void)
     if (CORE.Input.Gamepad.threadId) pthread_join(CORE.Input.Gamepad.threadId, NULL);
 #endif
 
+#if defined(SUPPORT_EVENTS_AUTOMATION)
+    free(events);
+#endif
+
     CORE.Window.ready = false;
     TRACELOG(LOG_INFO, "Window closed successfully");
 }
@@ -1876,8 +1983,35 @@ void EndDrawing(void)
 
         if (((gifFramesCounter/15)%2) == 1)
         {
-            DrawCircle(30, CORE.Window.screen.height - 20, 10, RED);
-            DrawText("RECORDING", 50, CORE.Window.screen.height - 25, 10, MAROON);
+            DrawCircle(30, CORE.Window.screen.height - 20, 10, MAROON);
+            DrawText("GIF RECORDING", 50, CORE.Window.screen.height - 25, 10, RED);
+        }
+
+        rlDrawRenderBatchActive();  // Update and draw internal render batch
+    }
+#endif
+
+#if defined(SUPPORT_EVENTS_AUTOMATION)
+    if (eventsRecording)
+    {
+        gifFramesCounter++;
+
+        if (((gifFramesCounter/15)%2) == 1)
+        {
+            DrawCircle(30, CORE.Window.screen.height - 20, 10, MAROON);
+            DrawText("EVENTS RECORDING", 50, CORE.Window.screen.height - 25, 10, RED);
+        }
+
+        rlDrawRenderBatchActive();  // Update and draw internal render batch
+    }
+    else if (eventsPlaying)
+    {
+        gifFramesCounter++;
+
+        if (((gifFramesCounter/15)%2) == 1)
+        {
+            DrawCircle(30, CORE.Window.screen.height - 20, 10, LIME);
+            DrawText("EVENTS PLAYING", 50, CORE.Window.screen.height - 25, 10, GREEN);
         }
 
         rlDrawRenderBatchActive();  // Update and draw internal render batch
@@ -1906,6 +2040,19 @@ void EndDrawing(void)
     }
 
     PollInputEvents();              // Poll user events
+    
+#if defined(SUPPORT_EVENTS_AUTOMATION)
+    if (eventsRecording) RecordAutomationEvent(CORE.Time.frameCounter);
+
+    // TODO: When should we play? After/before/replace PollInputEvents()?
+    if (eventsPlaying) 
+    {
+        if (CORE.Time.frameCounter >= eventCount) eventsPlaying = false;
+        PlayAutomationEvent(CORE.Time.frameCounter);
+    }
+#endif
+
+    CORE.Time.frameCounter++;
 }
 
 // Initialize 2D mode with custom camera (2D)
@@ -5039,6 +5186,22 @@ static void KeyCallback(GLFWwindow *window, int key, int scancode, int action, i
         }
     }
 #endif  // SUPPORT_SCREEN_CAPTURE
+#if defined(SUPPORT_EVENTS_AUTOMATION)
+    else if (key == GLFW_KEY_F11 && action == GLFW_PRESS)
+    {
+        eventsRecording = !eventsRecording;
+        
+        // On finish recording, we export events into a file
+        if (!eventsRecording) ExportAutomationEvents("eventsrec.rep");
+    }
+    else if (key == GLFW_KEY_F9 && action == GLFW_PRESS)
+    {
+        LoadAutomationEvents("eventsrec.rep");
+        eventsPlaying = true;
+        
+        TRACELOG(LOG_WARNING, "eventsPlaying enabled!");
+    }
+#endif
     else
     {
         // WARNING: GLFW could return GLFW_REPEAT, we need to consider it as 1
@@ -6627,3 +6790,363 @@ static int FindNearestConnectorMode(const drmModeConnector *connector, uint widt
     return nearestIndex;
 }
 #endif
+
+#if defined(SUPPORT_EVENTS_AUTOMATION)
+// NOTE: Loading happens over AutomationEvent *events
+static void LoadAutomationEvents(const char *fileName)
+{
+    //unsigned char fileId[4] = { 0 };
+    
+    // Load binary
+    /*
+    FILE *repFile = fopen(fileName, "rb");
+    fread(fileId, 4, 1, repFile);
+    
+    if ((fileId[0] == 'r') && (fileId[1] == 'E') && (fileId[2] == 'P') && (fileId[1] == ' '))
+    {
+        fread(&eventCount, sizeof(int), 1, repFile);
+        TraceLog(LOG_WARNING, "Events loaded: %i\n", eventCount);
+        fread(events, sizeof(AutomationEvent), eventCount, repFile);
+    }
+    
+    fclose(repFile);
+    */
+    
+    // Load events (text file)
+    FILE *repFile = fopen(fileName, "rt");
+
+    if (repFile != NULL)
+    {
+        unsigned int count = 0;
+        char buffer[256] = { 0 };
+
+        fgets(buffer, 256, repFile);
+
+        while (!feof(repFile))
+        {
+            if (buffer[0] == 'c') sscanf(buffer, "c %i", &eventCount);
+            else if (buffer[0] == 'e')
+            {
+                sscanf(buffer, "e %d %d %d %d %d", &events[count].frame, &events[count].type, 
+                       &events[count].params[0], &events[count].params[1], &events[count].params[2]);
+                       
+                count++;
+            }
+
+            fgets(buffer, 256, repFile);
+        }
+        
+        if (count != eventCount) TRACELOG(LOG_WARNING, "Events count provided is different than count");
+
+        fclose(repFile);
+    }
+    
+    TRACELOG(LOG_WARNING, "Events loaded: %i", eventCount);
+}
+
+// Export recorded events into a file
+static void ExportAutomationEvents(const char *fileName)
+{
+    // TODO: eventCount is required -> header? -> rAEL
+    unsigned char fileId[4] = "rEP ";
+
+    // Save as binary
+    /*
+    FILE *repFile = fopen(fileName, "wb");
+    fwrite(fileId, 4, 1, repFile);
+    fwrite(&eventCount, sizeof(int), 1, repFile);
+    fwrite(events, sizeof(AutomationEvent), eventCount, repFile);
+    fclose(repFile);
+    */
+    
+    // Export events as text
+    FILE *repFile = fopen(fileName, "wt");
+    
+    if (repFile != NULL)
+    {
+        fprintf(repFile, "# Automation events list\n");
+        fprintf(repFile, "#    c <events_count>\n");
+        fprintf(repFile, "#    e <frame> <event_type> <param0> <param1> <param2> // <event_type_name>\n");
+        
+        fprintf(repFile, "c %i\n", eventCount);
+        for (int i = 0; i < eventCount; i++)
+        {
+            fprintf(repFile, "e %i %i %i %i %i // %s\n", events[i].frame, events[i].type, 
+                    events[i].params[0], events[i].params[1], events[i].params[2], autoEventTypeName[events[i].type]);
+        }
+        
+        fclose(repFile);
+    }
+}
+
+// EndDrawing() -> After PollInputEvents()
+// Check event in current frame and save into the events[i] array
+static void RecordAutomationEvent(unsigned int frame)
+{
+    for (int key = 0; key < 512; key++)
+    {
+        // INPUT_KEY_UP (only saved once)
+        if (CORE.Input.Keyboard.previousKeyState[key] && !CORE.Input.Keyboard.currentKeyState[key])
+        {
+            events[eventCount].frame = frame;
+            events[eventCount].type = INPUT_KEY_UP;
+            events[eventCount].params[0] = key;
+            events[eventCount].params[1] = 0;
+            events[eventCount].params[2] = 0;
+            
+            TRACELOG(LOG_INFO, "[%i] INPUT_KEY_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
+            eventCount++;
+        }
+        
+        // INPUT_KEY_DOWN
+        if (CORE.Input.Keyboard.currentKeyState[key])
+        {
+            events[eventCount].frame = frame;
+            events[eventCount].type = INPUT_KEY_DOWN;
+            events[eventCount].params[0] = key;
+            events[eventCount].params[1] = 0;
+            events[eventCount].params[2] = 0;
+
+            TRACELOG(LOG_INFO, "[%i] INPUT_KEY_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
+            eventCount++;
+        }
+    }
+
+    for (int button = 0; button < MAX_MOUSE_BUTTONS; button++)
+    {
+        // INPUT_MOUSE_BUTTON_UP
+        if (CORE.Input.Mouse.previousButtonState[button] && !CORE.Input.Mouse.currentButtonState[button])
+        {
+            events[eventCount].frame = frame;
+            events[eventCount].type = INPUT_MOUSE_BUTTON_UP;
+            events[eventCount].params[0] = button;
+            events[eventCount].params[1] = 0;
+            events[eventCount].params[2] = 0;
+            
+            TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_BUTTON_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
+            eventCount++;
+        }
+        
+        // INPUT_MOUSE_BUTTON_DOWN
+        if (CORE.Input.Mouse.currentButtonState[button])
+        {
+            events[eventCount].frame = frame;
+            events[eventCount].type = INPUT_MOUSE_BUTTON_DOWN;
+            events[eventCount].params[0] = button;
+            events[eventCount].params[1] = 0;
+            events[eventCount].params[2] = 0;
+
+            TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_BUTTON_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
+            eventCount++;
+        }
+    }
+    
+    // INPUT_MOUSE_POSITION (only saved if changed)
+    if (((int)CORE.Input.Mouse.currentPosition.x != (int)CORE.Input.Mouse.previousPosition.x) ||
+        ((int)CORE.Input.Mouse.currentPosition.y != (int)CORE.Input.Mouse.previousPosition.y))
+    {
+        events[eventCount].frame = frame;
+        events[eventCount].type = INPUT_MOUSE_POSITION;
+        events[eventCount].params[0] = (int)CORE.Input.Mouse.currentPosition.x;
+        events[eventCount].params[1] = (int)CORE.Input.Mouse.currentPosition.y;
+        events[eventCount].params[2] = 0;
+
+        TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_POSITION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
+        eventCount++;
+    }
+    
+    // INPUT_MOUSE_WHEEL_MOTION
+    if ((int)CORE.Input.Mouse.currentWheelMove != (int)CORE.Input.Mouse.previousWheelMove)
+    {
+        events[eventCount].frame = frame;
+        events[eventCount].type = INPUT_MOUSE_WHEEL_MOTION;
+        events[eventCount].params[0] = (int)CORE.Input.Mouse.currentWheelMove;
+        events[eventCount].params[1] = 0;
+        events[eventCount].params[2] = 0;
+
+        TRACELOG(LOG_INFO, "[%i] INPUT_MOUSE_WHEEL_MOTION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
+        eventCount++;
+    }
+    
+    for (int id = 0; id < MAX_TOUCH_POINTS; id++)
+    {
+        // INPUT_TOUCH_UP
+        if (CORE.Input.Touch.previousTouchState[id] && !CORE.Input.Touch.currentTouchState[id])
+        {
+            events[eventCount].frame = frame;
+            events[eventCount].type = INPUT_TOUCH_UP;
+            events[eventCount].params[0] = id;
+            events[eventCount].params[1] = 0;
+            events[eventCount].params[2] = 0;
+
+            TRACELOG(LOG_INFO, "[%i] INPUT_TOUCH_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
+            eventCount++;
+        }
+        
+        // INPUT_TOUCH_DOWN
+        if (CORE.Input.Touch.currentTouchState[id])
+        {
+            events[eventCount].frame = frame;
+            events[eventCount].type = INPUT_TOUCH_DOWN;
+            events[eventCount].params[0] = id;
+            events[eventCount].params[1] = 0;
+            events[eventCount].params[2] = 0;
+            
+            TRACELOG(LOG_INFO, "[%i] INPUT_TOUCH_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
+            eventCount++;
+        }
+        
+        // INPUT_TOUCH_POSITION
+        // TODO: It requires the id!
+        /*
+        if (((int)CORE.Input.Touch.currentPosition[id].x != (int)CORE.Input.Touch.previousPosition[id].x) ||
+            ((int)CORE.Input.Touch.currentPosition[id].y != (int)CORE.Input.Touch.previousPosition[id].y))
+        {
+            events[eventCount].frame = frame;
+            events[eventCount].type = INPUT_TOUCH_POSITION;
+            events[eventCount].params[0] = id;
+            events[eventCount].params[1] = (int)CORE.Input.Touch.currentPosition[id].x;
+            events[eventCount].params[2] = (int)CORE.Input.Touch.currentPosition[id].y;
+
+            TRACELOG(LOG_INFO, "[%i] INPUT_TOUCH_POSITION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
+            eventCount++;
+        }
+        */
+    }
+    
+    for (int gamepad = 0; gamepad < MAX_GAMEPADS; gamepad++)
+    {
+        // INPUT_GAMEPAD_CONNECT
+        /*
+        if ((CORE.Input.Gamepad.currentState[gamepad] != CORE.Input.Gamepad.previousState[gamepad]) &&
+            (CORE.Input.Gamepad.currentState[gamepad] == true)) // Check if changed to ready
+        {
+            // TODO: Save gamepad connect event
+        }
+        */
+        
+        // INPUT_GAMEPAD_DISCONNECT
+        /*
+        if ((CORE.Input.Gamepad.currentState[gamepad] != CORE.Input.Gamepad.previousState[gamepad]) &&
+            (CORE.Input.Gamepad.currentState[gamepad] == false)) // Check if changed to not-ready
+        {
+            // TODO: Save gamepad disconnect event
+        }
+        */
+        
+        for (int button = 0; button < MAX_GAMEPAD_BUTTONS; button++)
+        {
+            // INPUT_GAMEPAD_BUTTON_UP
+            if (CORE.Input.Gamepad.previousButtonState[gamepad][button] && !CORE.Input.Gamepad.currentButtonState[gamepad][button])
+            {
+                events[eventCount].frame = frame;
+                events[eventCount].type = INPUT_GAMEPAD_BUTTON_UP;
+                events[eventCount].params[0] = gamepad;
+                events[eventCount].params[1] = button;
+                events[eventCount].params[2] = 0;
+                
+                TRACELOG(LOG_INFO, "[%i] INPUT_GAMEPAD_BUTTON_UP: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
+                eventCount++;
+            }
+            
+            // INPUT_GAMEPAD_BUTTON_DOWN
+            if (CORE.Input.Gamepad.currentButtonState[gamepad][button])
+            {
+                events[eventCount].frame = frame;
+                events[eventCount].type = INPUT_GAMEPAD_BUTTON_DOWN;
+                events[eventCount].params[0] = gamepad;
+                events[eventCount].params[1] = button;
+                events[eventCount].params[2] = 0;
+
+                TRACELOG(LOG_INFO, "[%i] INPUT_GAMEPAD_BUTTON_DOWN: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
+                eventCount++;
+            }
+        }
+        
+        for (int axis = 0; axis < MAX_GAMEPAD_AXIS; axis++)
+        {
+            // INPUT_GAMEPAD_AXIS_MOTION
+            if (CORE.Input.Gamepad.axisState[gamepad][axis] > 0.1f)
+            {
+                events[eventCount].frame = frame;
+                events[eventCount].type = INPUT_GAMEPAD_AXIS_MOTION;
+                events[eventCount].params[0] = gamepad;
+                events[eventCount].params[1] = axis;
+                events[eventCount].params[2] = (int)(CORE.Input.Gamepad.axisState[gamepad][axis]*32768.0f);
+
+                TRACELOG(LOG_INFO, "[%i] INPUT_GAMEPAD_AXIS_MOTION: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
+                eventCount++;
+            }
+        }
+    }
+    
+    // INPUT_GESTURE
+    if (GESTURES.current != GESTURE_NONE)
+    {
+        events[eventCount].frame = frame;
+        events[eventCount].type = INPUT_GESTURE;
+        events[eventCount].params[0] = GESTURES.current;
+        events[eventCount].params[1] = 0;
+        events[eventCount].params[2] = 0;
+
+        TRACELOG(LOG_INFO, "[%i] INPUT_GESTURE: %i, %i, %i", events[eventCount].frame, events[eventCount].params[0], events[eventCount].params[1], events[eventCount].params[2]);
+        eventCount++;
+    }
+}
+
+// Play automation event
+static void PlayAutomationEvent(unsigned int frame)
+{
+    for (unsigned int i = 0; i < eventCount; i++)
+    {
+        if (events[i].frame == frame)
+        {
+            switch (events[i].type)
+            {
+                // Input events
+                case INPUT_KEY_UP: CORE.Input.Keyboard.currentKeyState[events[i].params[0]] = false; break;             // param[0]: key
+                case INPUT_KEY_DOWN: CORE.Input.Keyboard.currentKeyState[events[i].params[0]] = true; break;            // param[0]: key
+                case INPUT_MOUSE_BUTTON_UP: CORE.Input.Mouse.currentButtonState[events[i].params[0]] = false; break;    // param[0]: key
+                case INPUT_MOUSE_BUTTON_DOWN: CORE.Input.Mouse.currentButtonState[events[i].params[0]] = true; break;   // param[0]: key
+                case INPUT_MOUSE_POSITION:      // param[0]: x, param[1]: y
+                {
+                    CORE.Input.Mouse.currentPosition.x = (float)events[i].params[0];
+                    CORE.Input.Mouse.currentPosition.y = (float)events[i].params[1];
+                } break;
+                case INPUT_MOUSE_WHEEL_MOTION: CORE.Input.Mouse.currentWheelMove = (float)events[i].params[0]; break;   // param[0]: delta
+                case INPUT_TOUCH_UP: CORE.Input.Touch.currentTouchState[events[i].params[0]] = false; break;            // param[0]: id
+                case INPUT_TOUCH_DOWN: CORE.Input.Touch.currentTouchState[events[i].params[0]] = true; break;           // param[0]: id
+                case INPUT_TOUCH_POSITION:      // param[0]: id, param[1]: x, param[2]: y
+                {
+                    CORE.Input.Touch.position[events[i].params[0]].x = (float)events[i].params[1];
+                    CORE.Input.Touch.position[events[i].params[0]].y = (float)events[i].params[2];
+                } break;
+                case INPUT_GAMEPAD_CONNECT: CORE.Input.Gamepad.ready[events[i].params[0]] = true; break;                // param[0]: gamepad
+                case INPUT_GAMEPAD_DISCONNECT: CORE.Input.Gamepad.ready[events[i].params[0]] = false; break;            // param[0]: gamepad
+                case INPUT_GAMEPAD_BUTTON_UP: CORE.Input.Gamepad.currentButtonState[events[i].params[0]][events[i].params[1]] = false; break;    // param[0]: gamepad, param[1]: button
+                case INPUT_GAMEPAD_BUTTON_DOWN: CORE.Input.Gamepad.currentButtonState[events[i].params[0]][events[i].params[1]] = true; break;   // param[0]: gamepad, param[1]: button
+                case INPUT_GAMEPAD_AXIS_MOTION: // param[0]: gamepad, param[1]: axis, param[2]: delta
+                {
+                    CORE.Input.Gamepad.axisState[events[i].params[0]][events[i].params[1]] = ((float)events[i].params[2]/32768.0f);
+                } break;      
+                case INPUT_GESTURE: GESTURES.current = events[i].params[0]; break;     // param[0]: gesture (enum Gesture) -> gestures.h: GESTURES.current
+                
+                // Window events
+                case WINDOW_CLOSE: CORE.Window.shouldClose = true; break;
+                case WINDOW_MAXIMIZE: MaximizeWindow(); break;
+                case WINDOW_MINIMIZE: MinimizeWindow(); break;
+                case WINDOW_RESIZE: SetWindowSize(events[i].params[0], events[i].params[1]); break;
+                
+                // Custom events
+                case ACTION_TAKE_SCREENSHOT: 
+                {
+                    TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter)); 
+                    screenshotCounter++;
+                } break;
+                case ACTION_SETTARGETFPS: SetTargetFPS(events[i].params[0]); break;
+                default: break;
+            }
+        }
+    }
+}
+#endif