Browse Source

ADDED: Automation Events System, exposed to users

Added new API to record and play events
Added examples illustrating functionality
Ray 1 year ago
parent
commit
99dac5451c

+ 289 - 0
examples/core/core_automation_events.c

@@ -0,0 +1,289 @@
+/*******************************************************************************************
+*
+*   raylib [core] example - automation events
+*
+*   Example originally created with raylib 5.0, last time updated with raylib 5.0
+*
+*   Example based on 2d_camera_platformer example by arvyy (@arvyy)
+*
+*   Example licensed under an unmodified zlib/libpng license, which is an OSI-certified,
+*   BSD-like license that allows static linking with closed source software
+*
+*   Copyright (c) 2023 Ramon Santamaria (@raysan5)
+*
+********************************************************************************************/
+
+#include "raylib.h"
+#include "raymath.h"
+
+#define GRAVITY 400
+#define PLAYER_JUMP_SPD 350.0f
+#define PLAYER_HOR_SPD 200.0f
+
+#define MAX_ENVIRONMENT_ELEMENTS    5
+
+typedef struct Player {
+    Vector2 position;
+    float speed;
+    bool canJump;
+} Player;
+
+typedef struct EnvElement {
+    Rectangle rect;
+    int blocking;
+    Color color;
+} EnvElement;
+
+
+//------------------------------------------------------------------------------------
+// Program main entry point
+//------------------------------------------------------------------------------------
+int main(void)
+{
+    // Initialization
+    //--------------------------------------------------------------------------------------
+    const int screenWidth = 800;
+    const int screenHeight = 450;
+
+    InitWindow(screenWidth, screenHeight, "raylib [core] example - automation events");
+
+    // Define player
+    Player player = { 0 };
+    player.position = (Vector2){ 400, 280 };
+    player.speed = 0;
+    player.canJump = false;
+    
+    // Define environment elements (platforms)
+    EnvElement envElements[MAX_ENVIRONMENT_ELEMENTS] = {
+        {{ 0, 0, 1000, 400 }, 0, LIGHTGRAY },
+        {{ 0, 400, 1000, 200 }, 1, GRAY },
+        {{ 300, 200, 400, 10 }, 1, GRAY },
+        {{ 250, 300, 100, 10 }, 1, GRAY },
+        {{ 650, 300, 100, 10 }, 1, GRAY }
+    };
+
+    // Define camera
+    Camera2D camera = { 0 };
+    camera.target = player.position;
+    camera.offset = (Vector2){ screenWidth/2.0f, screenHeight/2.0f };
+    camera.rotation = 0.0f;
+    camera.zoom = 1.0f;
+    
+    // Automation events
+    AutomationEventList aelist = LoadAutomationEventList(0);  // Initialize list of automation events to record new events
+    SetAutomationEventList(&aelist);
+    bool eventRecording = false;
+    bool eventPlaying = false;
+    
+    int frameCounter = 0;
+    int playFrameCounter = 0;
+    int currentFrame = 0;
+
+    SetTargetFPS(60);
+    //--------------------------------------------------------------------------------------
+
+    // Main game loop
+    while (!WindowShouldClose())
+    {
+        // Update
+        //----------------------------------------------------------------------------------
+        float deltaTime = GetFrameTime();
+        
+        // Dropped files logic
+        //----------------------------------------------------------------------------------
+        if (IsFileDropped())
+        {
+            FilePathList droppedFiles = LoadDroppedFiles();
+
+            // Supports loading .rgs style files (text or binary) and .png style palette images
+            if (IsFileExtension(droppedFiles.paths[0], ".txt;.rae"))
+            {
+                UnloadAutomationEventList(&aelist);
+                aelist = LoadAutomationEventList(droppedFiles.paths[0]);
+                
+                eventRecording = false;
+                
+                // Reset scene state to play
+                eventPlaying = true;
+                playFrameCounter = 0;
+                
+                player.position = (Vector2){ 400, 280 };
+                player.speed = 0;
+                player.canJump = false;
+
+                camera.target = player.position;
+                camera.offset = (Vector2){ screenWidth/2.0f, screenHeight/2.0f };
+                camera.rotation = 0.0f;
+                camera.zoom = 1.0f;
+            }
+
+            UnloadDroppedFiles(droppedFiles);   // Unload filepaths from memory
+        }
+        //----------------------------------------------------------------------------------
+
+        // Update player
+        //----------------------------------------------------------------------------------
+        if (IsKeyDown(KEY_LEFT)) player.position.x -= PLAYER_HOR_SPD*deltaTime;
+        if (IsKeyDown(KEY_RIGHT)) player.position.x += PLAYER_HOR_SPD*deltaTime;
+        if (IsKeyDown(KEY_SPACE) && player.canJump)
+        {
+            player.speed = -PLAYER_JUMP_SPD;
+            player.canJump = false;
+        }
+
+        int hitObstacle = 0;
+        for (int i = 0; i < MAX_ENVIRONMENT_ELEMENTS; i++)
+        {
+            EnvElement *element = &envElements[i];
+            Vector2 *p = &(player.position);
+            if (element->blocking &&
+                element->rect.x <= p->x &&
+                element->rect.x + element->rect.width >= p->x &&
+                element->rect.y >= p->y &&
+                element->rect.y <= p->y + player.speed*deltaTime)
+            {
+                hitObstacle = 1;
+                player.speed = 0.0f;
+                p->y = element->rect.y;
+            }
+        }
+
+        if (!hitObstacle)
+        {
+            player.position.y += player.speed*deltaTime;
+            player.speed += GRAVITY*deltaTime;
+            player.canJump = false;
+        }
+        else player.canJump = true;
+
+        camera.zoom += ((float)GetMouseWheelMove()*0.05f);
+
+        if (camera.zoom > 3.0f) camera.zoom = 3.0f;
+        else if (camera.zoom < 0.25f) camera.zoom = 0.25f;
+
+        if (IsKeyPressed(KEY_R))
+        {
+            camera.zoom = 1.0f;
+            player.position = (Vector2){ 400, 280 };
+        }
+        //----------------------------------------------------------------------------------
+
+        // Update camera
+        //----------------------------------------------------------------------------------
+        camera.target = player.position;
+        camera.offset = (Vector2){ screenWidth/2.0f, screenHeight/2.0f };
+        float minX = 1000, minY = 1000, maxX = -1000, maxY = -1000;
+
+        for (int i = 0; i < MAX_ENVIRONMENT_ELEMENTS; i++)
+        {
+            EnvElement *element = &envElements[i];
+            minX = fminf(element->rect.x, minX);
+            maxX = fmaxf(element->rect.x + element->rect.width, maxX);
+            minY = fminf(element->rect.y, minY);
+            maxY = fmaxf(element->rect.y + element->rect.height, maxY);
+        }
+
+        Vector2 max = GetWorldToScreen2D((Vector2){ maxX, maxY }, camera);
+        Vector2 min = GetWorldToScreen2D((Vector2){ minX, minY }, camera);
+
+        if (max.x < screenWidth) camera.offset.x = screenWidth - (max.x - screenWidth/2);
+        if (max.y < screenHeight) camera.offset.y = screenHeight - (max.y - screenHeight/2);
+        if (min.x > 0) camera.offset.x = screenWidth/2 - min.x;
+        if (min.y > 0) camera.offset.y = screenHeight/2 - min.y;
+        //----------------------------------------------------------------------------------
+        
+        // Toggle events recording
+        if (IsKeyPressed(KEY_ONE))
+        {
+            if (!eventPlaying)
+            {
+                if (eventRecording)
+                {
+                    StopAutomationEventRecording();
+                    eventRecording = false;
+                    
+                    ExportAutomationEventList(aelist, "automation.rae");
+                }
+                else 
+                {
+                    StartAutomationEventRecording();
+                    eventRecording = true;
+                }
+            }
+        }
+        
+        if (eventPlaying)
+        {
+            if (playFrameCounter == aelist.events[currentFrame].frame)
+            {
+                PlayAutomationEvent(aelist.events[currentFrame]);
+                currentFrame++;
+
+                if (currentFrame == aelist.count)
+                {
+                    eventPlaying = false;
+                    currentFrame = 0;
+                    playFrameCounter = 0;
+                }
+            }
+
+            playFrameCounter++;
+        }
+        
+        if (eventRecording || eventPlaying) frameCounter++;
+        else frameCounter = 0;
+        //----------------------------------------------------------------------------------
+
+        // Draw
+        //----------------------------------------------------------------------------------
+        BeginDrawing();
+
+            ClearBackground(LIGHTGRAY);
+
+            BeginMode2D(camera);
+
+                // Draw environment elements
+                for (int i = 0; i < MAX_ENVIRONMENT_ELEMENTS; i++)
+                {
+                    DrawRectangleRec(envElements[i].rect, envElements[i].color);
+                }
+
+                // Draw player rectangle
+                DrawRectangleRec((Rectangle){ player.position.x - 20, player.position.y - 40, 40, 40 }, RED);
+
+            EndMode2D();
+            
+            // Draw automation events recording indicator
+            if (eventRecording)
+            {
+                if (((frameCounter/15)%2) == 1)
+                {
+                    DrawCircle(GetScreenWidth() - 200, 20, 10, MAROON);
+                    DrawText(TextFormat("RECORDING EVENTS... [%i]", aelist.count), GetScreenWidth() - 180, 15, 10, RED);
+                }
+            }
+            else if (eventPlaying)
+            {
+                if (((frameCounter/15)%2) == 1)
+                {
+                    DrawTriangle((Vector2){ GetScreenWidth() - 200, 10 }, (Vector2){ GetScreenWidth() - 200, 30 }, (Vector2){ GetScreenWidth() - 200 + 20, 20 }, DARKGREEN);
+                    DrawText(TextFormat("PLAYING EVENTS... [%i]", currentFrame), GetScreenWidth() - 170, 15, 10, LIME);
+                }
+            }
+
+            DrawText("Controls:", 20, 20, 10, BLACK);
+            DrawText("- Right/Left to move", 30, 40, 10, DARKGRAY);
+            DrawText("- Space to jump", 30, 60, 10, DARKGRAY);
+            DrawText("- Mouse Wheel to Zoom in-out, R to reset zoom", 30, 80, 10, DARKGRAY);
+
+        EndDrawing();
+        //----------------------------------------------------------------------------------
+    }
+
+    // De-Initialization
+    //--------------------------------------------------------------------------------------
+    CloseWindow();        // Close window and OpenGL context
+    //--------------------------------------------------------------------------------------
+
+    return 0;
+}

+ 390 - 0
projects/VS2022/examples/core_automation_events.vcxproj

@@ -0,0 +1,390 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <ItemGroup Label="ProjectConfigurations">
+    <ProjectConfiguration Include="Debug.DLL|Win32">
+      <Configuration>Debug.DLL</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug.DLL|x64">
+      <Configuration>Debug.DLL</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|Win32">
+      <Configuration>Debug</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Debug|x64">
+      <Configuration>Debug</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release.DLL|Win32">
+      <Configuration>Release.DLL</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release.DLL|x64">
+      <Configuration>Release.DLL</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|Win32">
+      <Configuration>Release</Configuration>
+      <Platform>Win32</Platform>
+    </ProjectConfiguration>
+    <ProjectConfiguration Include="Release|x64">
+      <Configuration>Release</Configuration>
+      <Platform>x64</Platform>
+    </ProjectConfiguration>
+  </ItemGroup>
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}</ProjectGuid>
+    <Keyword>Win32Proj</Keyword>
+    <RootNamespace>core_automation_events</RootNamespace>
+    <WindowsTargetPlatformVersion>10.0</WindowsTargetPlatformVersion>
+    <ProjectName>core_automation_events</ProjectName>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug.DLL|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug.DLL|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>true</UseDebugLibraries>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release.DLL|Win32'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release.DLL|x64'" Label="Configuration">
+    <ConfigurationType>Application</ConfigurationType>
+    <UseDebugLibraries>false</UseDebugLibraries>
+    <PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
+    <WholeProgramOptimization>true</WholeProgramOptimization>
+    <CharacterSet>Unicode</CharacterSet>
+  </PropertyGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
+  <ImportGroup Label="ExtensionSettings">
+  </ImportGroup>
+  <ImportGroup Label="Shared">
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug.DLL|Win32'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Debug.DLL|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release.DLL|Win32'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <ImportGroup Condition="'$(Configuration)|$(Platform)'=='Release.DLL|x64'" Label="PropertySheets">
+    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
+  </ImportGroup>
+  <PropertyGroup Label="UserMacros" />
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <LinkIncremental>true</LinkIncremental>
+    <OutDir>$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>$(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <LinkIncremental>true</LinkIncremental>
+    <OutDir>$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>$(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug.DLL|Win32'">
+    <LinkIncremental>true</LinkIncremental>
+    <OutDir>$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>$(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug.DLL|x64'">
+    <LinkIncremental>true</LinkIncremental>
+    <OutDir>$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>$(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>$(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>$(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release.DLL|Win32'">
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>$(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release.DLL|x64'">
+    <LinkIncremental>false</LinkIncremental>
+    <OutDir>$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)\</OutDir>
+    <IntDir>$(SolutionDir)\build\$(ProjectName)\obj\$(Platform)\$(Configuration)\</IntDir>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug.DLL|Win32'">
+    <LocalDebuggerWorkingDirectory>$(SolutionDir)..\..\examples\core</LocalDebuggerWorkingDirectory>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug.DLL|x64'">
+    <LocalDebuggerWorkingDirectory>$(SolutionDir)..\..\examples\core</LocalDebuggerWorkingDirectory>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <LocalDebuggerWorkingDirectory>$(SolutionDir)..\..\examples\core</LocalDebuggerWorkingDirectory>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <LocalDebuggerWorkingDirectory>$(SolutionDir)..\..\examples\core</LocalDebuggerWorkingDirectory>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release.DLL|Win32'">
+    <LocalDebuggerWorkingDirectory>$(SolutionDir)..\..\examples\core</LocalDebuggerWorkingDirectory>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <LocalDebuggerWorkingDirectory>$(SolutionDir)..\..\examples\core</LocalDebuggerWorkingDirectory>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <LocalDebuggerWorkingDirectory>$(SolutionDir)..\..\examples\core</LocalDebuggerWorkingDirectory>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+  </PropertyGroup>
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release.DLL|x64'">
+    <LocalDebuggerWorkingDirectory>$(SolutionDir)..\..\examples\core</LocalDebuggerWorkingDirectory>
+    <DebuggerFlavor>WindowsLocalDebugger</DebuggerFlavor>
+  </PropertyGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
+    <ClCompile>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <CompileAs>CompileAsC</CompileAs>
+      <AdditionalIncludeDirectories>$(SolutionDir)..\..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalLibraryDirectories>$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
+      <AdditionalDependencies>raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">
+    <ClCompile>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <CompileAs>CompileAsC</CompileAs>
+      <AdditionalIncludeDirectories>$(SolutionDir)..\..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <AdditionalOptions>/FS %(AdditionalOptions)</AdditionalOptions>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalLibraryDirectories>$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
+      <AdditionalDependencies>raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug.DLL|Win32'">
+    <ClCompile>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <CompileAs>CompileAsC</CompileAs>
+      <AdditionalIncludeDirectories>$(SolutionDir)..\..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalLibraryDirectories>$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
+      <AdditionalDependencies>raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+    <PostBuildEvent>
+      <Command>xcopy /y /d  "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)"</Command>
+      <Message>Copy Debug DLL to output directory</Message>
+    </PostBuildEvent>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug.DLL|x64'">
+    <ClCompile>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <WarningLevel>Level3</WarningLevel>
+      <Optimization>Disabled</Optimization>
+      <PreprocessorDefinitions>WIN32;_DEBUG;_CONSOLE;PLATFORM_DESKTOP;%(PreprocessorDefinitions)</PreprocessorDefinitions>
+      <CompileAs>CompileAsC</CompileAs>
+      <AdditionalIncludeDirectories>$(SolutionDir)..\..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalLibraryDirectories>$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
+      <AdditionalDependencies>raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+    </Link>
+    <PostBuildEvent>
+      <Command>xcopy /y /d  "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)"</Command>
+      <Message>Copy Debug DLL to output directory</Message>
+    </PostBuildEvent>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>$(SolutionDir)..\..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <CompileAs>CompileAsC</CompileAs>
+      <RemoveUnreferencedCodeData>true</RemoveUnreferencedCodeData>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalDependencies>raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>$(SolutionDir)..\..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <CompileAs>CompileAsC</CompileAs>
+      <RemoveUnreferencedCodeData>true</RemoveUnreferencedCodeData>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalDependencies>raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
+    </Link>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release.DLL|Win32'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>$(SolutionDir)..\..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <CompileAs>CompileAsC</CompileAs>
+      <RemoveUnreferencedCodeData>true</RemoveUnreferencedCodeData>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalDependencies>raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
+    </Link>
+    <PostBuildEvent>
+      <Command>xcopy /y /d  "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)"</Command>
+    </PostBuildEvent>
+    <PostBuildEvent>
+      <Message>Copy Release DLL to output directory</Message>
+    </PostBuildEvent>
+  </ItemDefinitionGroup>
+  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release.DLL|x64'">
+    <ClCompile>
+      <WarningLevel>Level3</WarningLevel>
+      <PrecompiledHeader>
+      </PrecompiledHeader>
+      <Optimization>MaxSpeed</Optimization>
+      <FunctionLevelLinking>true</FunctionLevelLinking>
+      <IntrinsicFunctions>true</IntrinsicFunctions>
+      <PreprocessorDefinitions>WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions);PLATFORM_DESKTOP</PreprocessorDefinitions>
+      <AdditionalIncludeDirectories>$(SolutionDir)..\..\src;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
+      <CompileAs>CompileAsC</CompileAs>
+      <RemoveUnreferencedCodeData>true</RemoveUnreferencedCodeData>
+    </ClCompile>
+    <Link>
+      <SubSystem>Console</SubSystem>
+      <EnableCOMDATFolding>true</EnableCOMDATFolding>
+      <OptimizeReferences>true</OptimizeReferences>
+      <GenerateDebugInformation>true</GenerateDebugInformation>
+      <AdditionalDependencies>raylib.lib;opengl32.lib;kernel32.lib;user32.lib;gdi32.lib;winmm.lib;winspool.lib;comdlg32.lib;advapi32.lib;shell32.lib;ole32.lib;oleaut32.lib;uuid.lib;odbc32.lib;odbccp32.lib;%(AdditionalDependencies)</AdditionalDependencies>
+      <AdditionalLibraryDirectories>$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\</AdditionalLibraryDirectories>
+    </Link>
+    <PostBuildEvent>
+      <Command>xcopy /y /d  "$(SolutionDir)\build\raylib\bin\$(Platform)\$(Configuration)\raylib.dll" "$(SolutionDir)\build\$(ProjectName)\bin\$(Platform)\$(Configuration)"</Command>
+    </PostBuildEvent>
+    <PostBuildEvent>
+      <Message>Copy Release DLL to output directory</Message>
+    </PostBuildEvent>
+  </ItemDefinitionGroup>
+  <ItemGroup>
+    <ClCompile Include="..\..\..\examples\core\core_automation_events.c" />
+  </ItemGroup>
+  <ItemGroup>
+    <ResourceCompile Include="..\..\..\src\raylib.rc" />
+  </ItemGroup>
+  <ItemGroup>
+    <ProjectReference Include="..\raylib\raylib.vcxproj">
+      <Project>{e89d61ac-55de-4482-afd4-df7242ebc859}</Project>
+    </ProjectReference>
+  </ItemGroup>
+  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
+  <ImportGroup Label="ExtensionTargets">
+  </ImportGroup>
+</Project>

+ 19 - 0
projects/VS2022/raylib.sln

@@ -273,6 +273,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "audio_sound_multi", "exampl
 EndProject
 Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_2d_camera_split_screen", "examples\core_2d_camera_split_screen.vcxproj", "{CC62F7DB-D089-4677-8575-CAB7A7815C43}"
 EndProject
+Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "core_automation_events", "examples\core_automation_events.vcxproj", "{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}"
+EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug.DLL|x64 = Debug.DLL|x64
@@ -2297,6 +2299,22 @@ Global
 		{CC62F7DB-D089-4677-8575-CAB7A7815C43}.Release|x64.Build.0 = Release|x64
 		{CC62F7DB-D089-4677-8575-CAB7A7815C43}.Release|x86.ActiveCfg = Release|Win32
 		{CC62F7DB-D089-4677-8575-CAB7A7815C43}.Release|x86.Build.0 = Release|Win32
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Debug.DLL|x64.ActiveCfg = Debug.DLL|x64
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Debug.DLL|x64.Build.0 = Debug.DLL|x64
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Debug.DLL|x86.ActiveCfg = Debug.DLL|Win32
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Debug.DLL|x86.Build.0 = Debug.DLL|Win32
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Debug|x64.ActiveCfg = Debug|x64
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Debug|x64.Build.0 = Debug|x64
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Debug|x86.ActiveCfg = Debug|Win32
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Debug|x86.Build.0 = Debug|Win32
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Release.DLL|x64.ActiveCfg = Release.DLL|x64
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Release.DLL|x64.Build.0 = Release.DLL|x64
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Release.DLL|x86.ActiveCfg = Release.DLL|Win32
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Release.DLL|x86.Build.0 = Release.DLL|Win32
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Release|x64.ActiveCfg = Release|x64
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Release|x64.Build.0 = Release|x64
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Release|x86.ActiveCfg = Release|Win32
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26}.Release|x86.Build.0 = Release|Win32
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
@@ -2435,6 +2453,7 @@ Global
 		{3755E9F4-CB48-4EC3-B561-3B85964EBDEF} = {5317807F-61D4-4E0F-B6DC-2D9F12621ED9}
 		{F81C5819-85B4-4D2E-B6DC-104A7634461B} = {CC132A4D-D081-4C26-BFB9-AB11984054F8}
 		{CC62F7DB-D089-4677-8575-CAB7A7815C43} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035}
+		{7AF97D44-707E-48DC-81CB-C9D8D7C9ED26} = {6C82BAAE-BDDF-457D-8FA8-7E2490B07035}
 	EndGlobalSection
 	GlobalSection(ExtensibilityGlobals) = postSolution
 		SolutionGuid = {E926C768-6307-4423-A1EC-57E95B1FAB29}

+ 2 - 1
src/config.h

@@ -63,7 +63,7 @@
 // Support CompressData() and DecompressData() functions
 #define SUPPORT_COMPRESSION_API         1
 // Support automatic generated events, loading and recording of those events when required
-//#define SUPPORT_EVENTS_AUTOMATION       1
+#define SUPPORT_AUTOMATION_EVENTS       1
 // Support custom frame control, only for advance users
 // By default EndDrawing() does this job: draws everything + SwapScreenBuffer() + manage frame timing + PollInputEvents()
 // Enabling this flag allows manual control of the frame processes, use at your own risk
@@ -85,6 +85,7 @@
 
 #define MAX_DECOMPRESSION_SIZE         64       // Max size allocated for decompression in MB
 
+#define MAX_AUTOMATION_EVENTS       16384       // Maximum number of automation events to record
 
 //------------------------------------------------------------------------------------
 // Module: rlgl - Configuration values

+ 23 - 0
src/raylib.h

@@ -506,6 +506,20 @@ typedef struct FilePathList {
     char **paths;                   // Filepaths entries
 } FilePathList;
 
+// Automation event (opaque struct)
+typedef struct AutomationEvent {
+    unsigned int frame;                 // Event frame
+    unsigned int type;                  // Event type (AutomationEventType)
+    int params[4];                      // Event parameters (if required)
+} AutomationEvent;
+
+// Automation event list
+typedef struct AutomationEventList {
+    unsigned int capacity;          // Events max entries (MAX_AUTOMATION_EVENTS)
+    unsigned int count;             // Events entries count
+    AutomationEvent *events;        // Events entries
+} AutomationEventList;
+
 //----------------------------------------------------------------------------------
 // Enumerators Definition
 //----------------------------------------------------------------------------------
@@ -1114,6 +1128,15 @@ RLAPI unsigned char *DecompressData(const unsigned char *compData, int compDataS
 RLAPI char *EncodeDataBase64(const unsigned char *data, int dataSize, int *outputSize);               // Encode data to Base64 string, memory must be MemFree()
 RLAPI unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize);                    // Decode Base64 string data, memory must be MemFree()
 
+// Automation events functionality
+RLAPI AutomationEventList LoadAutomationEventList(const char *fileName);                // Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS
+RLAPI void UnloadAutomationEventList(AutomationEventList *list);                        // Unload automation events list from file
+RLAPI bool ExportAutomationEventList(AutomationEventList list, const char *fileName);   // Export automation events list as text file
+RLAPI void SetAutomationEventList(AutomationEventList *list);                           // Set automation event list to record to
+RLAPI void StartAutomationEventRecording(void);                                         // Start recording automation events (AutomationEventList must be set)
+RLAPI void StopAutomationEventRecording(void);                                          // Stop recording automation events
+RLAPI void PlayAutomationEvent(AutomationEvent event);                                  // Play a recorded automation event
+
 //------------------------------------------------------------------------------------
 // Input Handling Functions (Module: core)
 //------------------------------------------------------------------------------------

+ 442 - 347
src/rcore.c

@@ -3,17 +3,17 @@
 *   rcore - Window/display management, Graphic device/context management and input management
 *
 *   PLATFORMS SUPPORTED:
-*       - PLATFORM_DESKTOP: 
+*       - PLATFORM_DESKTOP:
 *           > Windows (Win32, Win64)
 *           > Linux (X11/Wayland desktop mode)
 *           > macOS/OSX (x64, arm64)
 *           > FreeBSD, OpenBSD, NetBSD, DragonFly (X11 desktop)
-*       - PLATFORM_WEB:     
+*       - PLATFORM_WEB:
 *           > HTML5 (WebAssembly)
-*       - PLATFORM_DRM:     
+*       - PLATFORM_DRM:
 *           > Raspberry Pi 0-5
 *           > Linux native mode (KMS driver)
-*       - PLATFORM_ANDROID: 
+*       - PLATFORM_ANDROID:
 *           > Android (ARM, ARM64)
 *
 *   CONFIGURATION:
@@ -48,8 +48,8 @@
 *           provided by stb_image and stb_image_write libraries, so, those libraries must be enabled on textures module
 *           for linkage
 *
-*       #define SUPPORT_EVENTS_AUTOMATION
-*           Support automatic generated events, loading and recording of those events when required
+*       #define SUPPORT_AUTOMATION_EVENTS
+*           Support automatic events recording and playing, useful for automated testing systems or AI based game playing
 *
 *   DEPENDENCIES:
 *       raymath  - 3D math functionality (Vector2, Vector3, Matrix, Quaternion)
@@ -183,9 +183,8 @@ bool gifRecording = false;           // GIF recording state
 MsfGifState gifState = { 0 };        // MSGIF context state
 #endif
 
-#if defined(SUPPORT_EVENTS_AUTOMATION)
-#define MAX_CODE_AUTOMATION_EVENTS      16384
-
+#if defined(SUPPORT_AUTOMATION_EVENTS)
+// Automation events type
 typedef enum AutomationEventType {
     EVENT_NONE = 0,
     // Input events
@@ -212,12 +211,12 @@ typedef enum AutomationEventType {
     WINDOW_MINIMIZE,                // no params
     WINDOW_RESIZE,                  // param[0]: width, param[1]: height
     // Custom events
-    ACTION_TAKE_SCREENSHOT,
-    ACTION_SETTARGETFPS
+    ACTION_TAKE_SCREENSHOT,         // no params
+    ACTION_SETTARGETFPS             // param[0]: fps
 } AutomationEventType;
 
-// Event type
-// Used to enable events flags
+// Event type to config events flags
+// TODO: Not used at the moment
 typedef enum {
     EVENT_INPUT_KEYBOARD    = 0,
     EVENT_INPUT_MOUSE       = 1,
@@ -228,6 +227,7 @@ typedef enum {
     EVENT_CUSTOM            = 32
 } EventType;
 
+// Event type name strings, required for export
 static const char *autoEventTypeName[] = {
     "EVENT_NONE",
     "INPUT_KEY_UP",
@@ -255,19 +255,19 @@ static const char *autoEventTypeName[] = {
     "ACTION_SETTARGETFPS"
 };
 
+/*
 // Automation event (24 bytes)
-typedef struct AutomationEvent {
+// NOTE: Opaque struct, internal to raylib
+struct AutomationEvent {
     unsigned int frame;                 // Event frame
     unsigned int type;                  // Event type (AutomationEventType)
     int params[4];                      // 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
+static AutomationEventList *currentEventList = NULL;    // Current automation events list, set by user, keep internal pointer
+static bool automationEventRecording = false;           // Recording automation events flag
+//static short automationEventEnabled = 0b0000001111111111; // TODO: Automation events enabled for recording/playing
 #endif
 //-----------------------------------------------------------------------------------
 
@@ -291,11 +291,8 @@ static void SetupViewport(int width, int height);           // Set viewport for
 static void ScanDirectoryFiles(const char *basePath, FilePathList *list, const char *filter);   // Scan all files and directories in a base path
 static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *list, const char *filter);  // Scan all files and directories recursively from a base path
 
-#if defined(SUPPORT_EVENTS_AUTOMATION)
-static void LoadAutomationEvents(const char *fileName);     // Load automation events from file
-static void ExportAutomationEvents(const char *fileName);   // Export recorded automation events into a file
-static void RecordAutomationEvent(unsigned int frame);      // Record frame events (to internal events array)
-static void PlayAutomationEvent(unsigned int frame);        // Play frame events (from internal events array)
+#if defined(SUPPORT_AUTOMATION_EVENTS)
+static void RecordAutomationEvent(void); // Record frame events (to internal events array)
 #endif
 
 #if defined(_WIN32)
@@ -304,14 +301,14 @@ void __stdcall Sleep(unsigned long msTimeout);              // Required for: Wai
 #endif
 
 #if !defined(SUPPORT_MODULE_RTEXT)
-const char *TextFormat(const char *text, ...);       // Formatting of text with variables to 'embed'
+const char *TextFormat(const char *text, ...);              // Formatting of text with variables to 'embed'
 #endif // !SUPPORT_MODULE_RTEXT
 
 // Include platform-specific submodules
 #if defined(PLATFORM_DESKTOP)
     #include "platforms/rcore_desktop.c"
 #elif defined(PLATFORM_DESKTOP_SDL)
-    #include "platforms/rcore_desktop_sdl.c" 
+    #include "platforms/rcore_desktop_sdl.c"
 #elif defined(PLATFORM_WEB)
     #include "platforms/rcore_web.c"
 #elif defined(PLATFORM_DRM)
@@ -434,12 +431,12 @@ void InitWindow(int width, int height, const char *title)
     CORE.Input.Mouse.scale = (Vector2){ 1.0f, 1.0f };
     CORE.Input.Mouse.cursor = MOUSE_CURSOR_ARROW;
     CORE.Input.Gamepad.lastButtonPressed = GAMEPAD_BUTTON_UNKNOWN;
-    
+
     // Initialize platform
-    //--------------------------------------------------------------   
+    //--------------------------------------------------------------
     InitPlatform();
     //--------------------------------------------------------------
-    
+
     // Initialize rlgl default data (buffers and shaders)
     // NOTE: CORE.Window.currentFbo.width and CORE.Window.currentFbo.height not used, just stored as globals in rlgl
     rlglInit(CORE.Window.currentFbo.width, CORE.Window.currentFbo.height);
@@ -485,9 +482,6 @@ void InitWindow(int width, int height, const char *title)
 #endif
 
     CORE.Time.frameCounter = 0;
-#if defined(SUPPORT_EVENTS_AUTOMATION)
-    events = (AutomationEvent *)RL_CALLOC(MAX_CODE_AUTOMATION_EVENTS, sizeof(AutomationEvent));
-#endif
 
     // Initialize random seed
     SetRandomSeed((unsigned int)time(NULL));
@@ -512,14 +506,10 @@ void CloseWindow(void)
     rlglClose();                // De-init rlgl
 
     // De-initialize platform
-    //--------------------------------------------------------------   
+    //--------------------------------------------------------------
     ClosePlatform();
     //--------------------------------------------------------------
 
-#if defined(SUPPORT_EVENTS_AUTOMATION)
-    RL_FREE(events);
-#endif
-
     CORE.Window.ready = false;
     TRACELOG(LOG_INFO, "Window closed successfully");
 }
@@ -684,34 +674,6 @@ void EndDrawing(void)
     }
 #endif
 
-#if defined(SUPPORT_EVENTS_AUTOMATION)
-    // Draw record/play indicator
-    if (eventsRecording)
-    {
-        gifFrameCounter++;
-
-        if (((gifFrameCounter/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)
-    {
-        gifFrameCounter++;
-
-        if (((gifFrameCounter/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
-    }
-#endif
-
 #if !defined(SUPPORT_CUSTOM_FRAME_CONTROL)
     SwapScreenBuffer();                  // Copy back buffer to front buffer (screen)
 
@@ -775,16 +737,9 @@ void EndDrawing(void)
     }
 #endif  // SUPPORT_SCREEN_CAPTURE
 
-#if defined(SUPPORT_EVENTS_AUTOMATION)
-    // Events recording and playing logic
-    if (eventsRecording) RecordAutomationEvent(CORE.Time.frameCounter);
-    else if (eventsPlaying)
-    {
-        // TODO: When should we play? After/before/replace PollInputEvents()?
-        if (CORE.Time.frameCounter >= eventCount) eventsPlaying = false;
-        PlayAutomationEvent(CORE.Time.frameCounter);
-    }
-#endif  // SUPPORT_EVENTS_AUTOMATION
+#if defined(SUPPORT_AUTOMATION_EVENTS)
+    if (automationEventRecording) RecordAutomationEvent();    // Event recording
+#endif
 
     CORE.Time.frameCounter++;
 }
@@ -1470,7 +1425,7 @@ float GetFrameTime(void)
 //----------------------------------------------------------------------------------
 
 // NOTE: Functions with a platform-specific implementation on rcore_<platform>.c
-//void SwapScreenBuffer(void);  
+//void SwapScreenBuffer(void);
 //void PollInputEvents(void);
 
 // Wait for some time (stop program execution)
@@ -1481,7 +1436,7 @@ float GetFrameTime(void)
 void WaitTime(double seconds)
 {
     if (seconds < 0) return;
-    
+
 #if defined(SUPPORT_BUSY_WAIT_LOOP) || defined(SUPPORT_PARTIALBUSY_WAIT_LOOP)
     double destinationTime = GetTime() + seconds;
 #endif
@@ -2180,6 +2135,237 @@ unsigned char *DecodeDataBase64(const unsigned char *data, int *outputSize)
     return decodedData;
 }
 
+//----------------------------------------------------------------------------------
+// Module Functions Definition: Automation Events Recording and Playing
+//----------------------------------------------------------------------------------
+
+// Load automation events list from file, NULL for empty list, capacity = MAX_AUTOMATION_EVENTS
+AutomationEventList LoadAutomationEventList(const char *fileName)
+{
+    AutomationEventList list = { 0 };
+
+    // Allocate and empty automation event list, ready to record new events
+    list.events = (AutomationEvent *)RL_CALLOC(MAX_AUTOMATION_EVENTS, sizeof(AutomationEvent));
+    list.capacity = MAX_AUTOMATION_EVENTS;
+
+#if defined(SUPPORT_AUTOMATION_EVENTS)
+    if (fileName == NULL) TRACELOG(LOG_INFO, "AUTOMATION: New empty events list loaded successfully");
+    else
+    {
+        // Load automation events file (binary)
+        /*
+        //int dataSize = 0;
+        //unsigned char *data = LoadFileData(fileName, &dataSize);
+        
+        FILE *raeFile = fopen(fileName, "rb");
+        unsigned char fileId[4] = { 0 };
+
+        fread(fileId, 1, 4, raeFile);
+
+        if ((fileId[0] == 'r') && (fileId[1] == 'A') && (fileId[2] == 'E') && (fileId[1] == ' '))
+        {
+            fread(&eventCount, sizeof(int), 1, raeFile);
+            TRACELOG(LOG_WARNING, "Events loaded: %i\n", eventCount);
+            fread(events, sizeof(AutomationEvent), eventCount, raeFile);
+        }
+
+        fclose(raeFile);
+        */
+
+        // Load events file (text)
+        //unsigned char *buffer = LoadFileText(fileName);
+        FILE *raeFile = fopen(fileName, "rt");
+
+        if (raeFile != NULL)
+        {
+            unsigned int counter = 0;
+            char buffer[256] = { 0 };
+            char eventDesc[64] = { 0 };
+
+            fgets(buffer, 256, raeFile);
+
+            while (!feof(raeFile))
+            {
+                switch (buffer[0])
+                {
+                    case 'c': sscanf(buffer, "c %i", &list.count); break;
+                    case 'e':
+                    {
+                        sscanf(buffer, "e %d %d %d %d %d %d %[^\n]s", &list.events[counter].frame, &list.events[counter].type,
+                               &list.events[counter].params[0], &list.events[counter].params[1], &list.events[counter].params[2], &list.events[counter].params[3], eventDesc);
+
+                        counter++;
+                    } break;
+                    default: break;
+                }
+
+                fgets(buffer, 256, raeFile);
+            }
+
+            if (counter != list.count) 
+            {
+                TRACELOG(LOG_WARNING, "AUTOMATION: Events read from file [%i] do not mach event count specified [%i]", counter, list.count);
+                list.count = counter;
+            }
+
+            fclose(raeFile);
+
+            TRACELOG(LOG_INFO, "AUTOMATION: Events file loaded successfully");
+        }
+
+        TRACELOG(LOG_INFO, "AUTOMATION: Events loaded from file: %i", list.count);
+    }
+#endif
+    return list;
+}
+
+// Unload automation events list from file
+void UnloadAutomationEventList(AutomationEventList *list)
+{
+#if defined(SUPPORT_AUTOMATION_EVENTS)
+    RL_FREE(list->events);
+    list->events = NULL;
+    list->count = 0;
+    list->capacity = 0;
+#endif
+}
+
+// Export automation events list as text file
+bool ExportAutomationEventList(AutomationEventList list, const char *fileName)
+{
+    bool success = false;
+    
+#if defined(SUPPORT_AUTOMATION_EVENTS)
+    // Export events as binary file
+    // TODO: Save to memory buffer and SaveFileData()
+    /*
+    unsigned char fileId[4] = "rAE ";
+    FILE *raeFile = fopen(fileName, "wb");
+    fwrite(fileId, sizeof(unsigned char), 4, raeFile);
+    fwrite(&eventCount, sizeof(int), 1, raeFile);
+    fwrite(events, sizeof(AutomationEvent), eventCount, raeFile);
+    fclose(raeFile);
+    */
+
+    // Export events as text
+    // TODO: Save to memory buffer and SaveFileText()
+    char *txtData = (char *)RL_CALLOC(256*list.count + 2048, sizeof(char)); // 256 characters per line plus some header
+
+    int byteCount = 0;
+    byteCount += sprintf(txtData + byteCount, "#\n");
+    byteCount += sprintf(txtData + byteCount, "# Automation events exporter v1.0 - raylib automation events list\n");
+    byteCount += sprintf(txtData + byteCount, "#\n");
+    byteCount += sprintf(txtData + byteCount, "#    c <events_count>\n");
+    byteCount += sprintf(txtData + byteCount, "#    e <frame> <event_type> <param0> <param1> <param2> <param3> // <event_type_name>\n");
+    byteCount += sprintf(txtData + byteCount, "#\n");
+    byteCount += sprintf(txtData + byteCount, "# more info and bugs-report:  github.com/raysan5/raylib\n");
+    byteCount += sprintf(txtData + byteCount, "# feedback and support:       ray[at]raylib.com\n");
+    byteCount += sprintf(txtData + byteCount, "#\n");
+    byteCount += sprintf(txtData + byteCount, "# Copyright (c) 2023-2024 Ramon Santamaria (@raysan5)\n");
+    byteCount += sprintf(txtData + byteCount, "#\n\n");
+
+    // Add events data
+    byteCount += sprintf(txtData + byteCount, "c %i\n", list.count);
+    for (int i = 0; i < list.count; i++)
+    {
+        byteCount += sprintf(txtData + byteCount, "e %i %i %i %i %i %i // Event: %s\n", list.events[i].frame, list.events[i].type,
+            list.events[i].params[0], list.events[i].params[1], list.events[i].params[2], list.events[i].params[3], autoEventTypeName[list.events[i].type]);
+    }
+
+    // NOTE: Text data size exported is determined by '\0' (NULL) character
+    success = SaveFileText(fileName, txtData);
+
+    RL_FREE(txtData);
+#endif
+
+    return success;
+}
+
+// Setup automation event list to record to
+void SetAutomationEventList(AutomationEventList *list)
+{
+#if defined(SUPPORT_AUTOMATION_EVENTS)
+    currentEventList = list;
+#endif
+}
+
+// Start recording automation events (AutomationEventList must be set)
+void StartAutomationEventRecording(void)
+{
+#if defined(SUPPORT_AUTOMATION_EVENTS)
+    automationEventRecording = true;
+#endif
+}
+
+// Stop recording automation events
+void StopAutomationEventRecording(void)
+{
+#if defined(SUPPORT_AUTOMATION_EVENTS)
+    automationEventRecording = false;
+#endif
+}
+
+// Play a recorded automation event
+void PlayAutomationEvent(AutomationEvent event)
+{
+#if defined(SUPPORT_AUTOMATION_EVENTS)
+    // WARNING: When should event be played? After/before/replace PollInputEvents()? -> Up to the user!
+
+    if (!automationEventRecording)      // TODO: Allow recording events while playing?
+    {
+        switch (event.type)
+        {
+            // Input event
+            case INPUT_KEY_UP: CORE.Input.Keyboard.currentKeyState[event.params[0]] = false; break;             // param[0]: key
+            case INPUT_KEY_DOWN: CORE.Input.Keyboard.currentKeyState[event.params[0]] = true; break;            // param[0]: key
+            case INPUT_MOUSE_BUTTON_UP: CORE.Input.Mouse.currentButtonState[event.params[0]] = false; break;    // param[0]: key
+            case INPUT_MOUSE_BUTTON_DOWN: CORE.Input.Mouse.currentButtonState[event.params[0]] = true; break;   // param[0]: key
+            case INPUT_MOUSE_POSITION:      // param[0]: x, param[1]: y
+            {
+                CORE.Input.Mouse.currentPosition.x = (float)event.params[0];
+                CORE.Input.Mouse.currentPosition.y = (float)event.params[1];
+            } break;
+            case INPUT_MOUSE_WHEEL_MOTION:  // param[0]: x delta, param[1]: y delta
+            {
+                CORE.Input.Mouse.currentWheelMove.x = (float)event.params[0]; break;
+                CORE.Input.Mouse.currentWheelMove.y = (float)event.params[1]; break;
+            } break;
+            case INPUT_TOUCH_UP: CORE.Input.Touch.currentTouchState[event.params[0]] = false; break;            // param[0]: id
+            case INPUT_TOUCH_DOWN: CORE.Input.Touch.currentTouchState[event.params[0]] = true; break;           // param[0]: id
+            case INPUT_TOUCH_POSITION:      // param[0]: id, param[1]: x, param[2]: y
+            {
+                CORE.Input.Touch.position[event.params[0]].x = (float)event.params[1];
+                CORE.Input.Touch.position[event.params[0]].y = (float)event.params[2];
+            } break;
+            case INPUT_GAMEPAD_CONNECT: CORE.Input.Gamepad.ready[event.params[0]] = true; break;                // param[0]: gamepad
+            case INPUT_GAMEPAD_DISCONNECT: CORE.Input.Gamepad.ready[event.params[0]] = false; break;            // param[0]: gamepad
+            case INPUT_GAMEPAD_BUTTON_UP: CORE.Input.Gamepad.currentButtonState[event.params[0]][event.params[1]] = false; break;    // param[0]: gamepad, param[1]: button
+            case INPUT_GAMEPAD_BUTTON_DOWN: CORE.Input.Gamepad.currentButtonState[event.params[0]][event.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[event.params[0]][event.params[1]] = ((float)event.params[2]/32768.0f);
+            } break;
+            case INPUT_GESTURE: GESTURES.current = event.params[0]; break;     // param[0]: gesture (enum Gesture) -> rgestures.h: GESTURES.current
+
+            // Window event
+            case WINDOW_CLOSE: CORE.Window.shouldClose = true; break;
+            case WINDOW_MAXIMIZE: MaximizeWindow(); break;
+            case WINDOW_MINIMIZE: MinimizeWindow(); break;
+            case WINDOW_RESIZE: SetWindowSize(event.params[0], event.params[1]); break;
+
+            // Custom event
+            case ACTION_TAKE_SCREENSHOT:
+            {
+                TakeScreenshot(TextFormat("screenshot%03i.png", screenshotCounter));
+                screenshotCounter++;
+            } break;
+            case ACTION_SETTARGETFPS: SetTargetFPS(event.params[0]); break;
+            default: break;
+        }
+    }
+#endif
+}
+
 //----------------------------------------------------------------------------------
 // Module Functions Definition: Input Handling: Keyboard
 //----------------------------------------------------------------------------------
@@ -2793,233 +2979,180 @@ static void ScanDirectoryFilesRecursively(const char *basePath, FilePathList *fi
     else TRACELOG(LOG_WARNING, "FILEIO: Directory cannot be opened (%s)", basePath);
 }
 
-#if defined(SUPPORT_EVENTS_AUTOMATION)
-// NOTE: Loading happens over AutomationEvent *events
-// TODO: This system should probably be redesigned
-static void LoadAutomationEvents(const char *fileName)
-{
-    // Load events file (binary)
-    /*
-    FILE *repFile = fopen(fileName, "rb");
-    unsigned char fileId[4] = { 0 };
-
-    fread(fileId, 1, 4, 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 file (text)
-    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)
+#if defined(SUPPORT_AUTOMATION_EVENTS)
+// Automation event recording
+// NOTE: Recording is by default done at EndDrawing(), after PollInputEvents()
+static void RecordAutomationEvent(void)
 {
-    unsigned char fileId[4] = "rEP ";
-
-    // Save as binary
-    /*
-    FILE *repFile = fopen(fileName, "wb");
-    fwrite(fileId, sizeof(unsigned char), 4, 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);
-    }
-}
+    // Checking events in current frame and save them into currentEventList
+    // TODO: How important is the current frame? Could it be modified?
+    
+    if (currentEventList->count == currentEventList->capacity) return;    // Security check
 
-// EndDrawing() -> After PollInputEvents()
-// Check event in current frame and save into the events[i] array
-static void RecordAutomationEvent(unsigned int frame)
-{
+    // Keyboard input events recording
+    //-------------------------------------------------------------------------------------
     for (int key = 0; key < MAX_KEYBOARD_KEYS; key++)
     {
-        // INPUT_KEY_UP (only saved once)
+        // Event type: 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++;
+            currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter;
+            currentEventList->events[currentEventList->count].type = INPUT_KEY_UP;
+            currentEventList->events[currentEventList->count].params[0] = key;
+            currentEventList->events[currentEventList->count].params[1] = 0;
+            currentEventList->events[currentEventList->count].params[2] = 0;
+
+            TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_KEY_UP | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]);
+            currentEventList->count++;
         }
+        
+        if (currentEventList->count == currentEventList->capacity) return;    // Security check
 
-        // INPUT_KEY_DOWN
+        // Event type: 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++;
+            currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter;
+            currentEventList->events[currentEventList->count].type = INPUT_KEY_DOWN;
+            currentEventList->events[currentEventList->count].params[0] = key;
+            currentEventList->events[currentEventList->count].params[1] = 0;
+            currentEventList->events[currentEventList->count].params[2] = 0;
+
+            TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_KEY_DOWN | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]);
+            currentEventList->count++;
         }
+        
+        if (currentEventList->count == currentEventList->capacity) return;    // Security check
     }
+    //-------------------------------------------------------------------------------------
 
+    // Mouse input currentEventList->events recording
+    //-------------------------------------------------------------------------------------
     for (int button = 0; button < MAX_MOUSE_BUTTONS; button++)
     {
-        // INPUT_MOUSE_BUTTON_UP
+        // Event type: 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++;
+            currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter;
+            currentEventList->events[currentEventList->count].type = INPUT_MOUSE_BUTTON_UP;
+            currentEventList->events[currentEventList->count].params[0] = button;
+            currentEventList->events[currentEventList->count].params[1] = 0;
+            currentEventList->events[currentEventList->count].params[2] = 0;
+
+            TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_MOUSE_BUTTON_UP | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]);
+            currentEventList->count++;
         }
+        
+        if (currentEventList->count == currentEventList->capacity) return;    // Security check
 
-        // INPUT_MOUSE_BUTTON_DOWN
+        // Event type: 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++;
+            currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter;
+            currentEventList->events[currentEventList->count].type = INPUT_MOUSE_BUTTON_DOWN;
+            currentEventList->events[currentEventList->count].params[0] = button;
+            currentEventList->events[currentEventList->count].params[1] = 0;
+            currentEventList->events[currentEventList->count].params[2] = 0;
+
+            TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_MOUSE_BUTTON_DOWN | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]);
+            currentEventList->count++;
         }
+        
+        if (currentEventList->count == currentEventList->capacity) return;    // Security check
     }
 
-    // INPUT_MOUSE_POSITION (only saved if changed)
+    // Event type: 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;
+        currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter;
+        currentEventList->events[currentEventList->count].type = INPUT_MOUSE_POSITION;
+        currentEventList->events[currentEventList->count].params[0] = (int)CORE.Input.Mouse.currentPosition.x;
+        currentEventList->events[currentEventList->count].params[1] = (int)CORE.Input.Mouse.currentPosition.y;
+        currentEventList->events[currentEventList->count].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++;
+        TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_MOUSE_POSITION | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]);
+        currentEventList->count++;
+        
+        if (currentEventList->count == currentEventList->capacity) return;    // Security check
     }
 
-    // INPUT_MOUSE_WHEEL_MOTION
+    // Event type: INPUT_MOUSE_WHEEL_MOTION
     if (((int)CORE.Input.Mouse.currentWheelMove.x != (int)CORE.Input.Mouse.previousWheelMove.x) ||
         ((int)CORE.Input.Mouse.currentWheelMove.y != (int)CORE.Input.Mouse.previousWheelMove.y))
     {
-        events[eventCount].frame = frame;
-        events[eventCount].type = INPUT_MOUSE_WHEEL_MOTION;
-        events[eventCount].params[0] = (int)CORE.Input.Mouse.currentWheelMove.x;
-        events[eventCount].params[1] = (int)CORE.Input.Mouse.currentWheelMove.y;;
-        events[eventCount].params[2] = 0;
+        currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter;
+        currentEventList->events[currentEventList->count].type = INPUT_MOUSE_WHEEL_MOTION;
+        currentEventList->events[currentEventList->count].params[0] = (int)CORE.Input.Mouse.currentWheelMove.x;
+        currentEventList->events[currentEventList->count].params[1] = (int)CORE.Input.Mouse.currentWheelMove.y;;
+        currentEventList->events[currentEventList->count].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++;
+        TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_MOUSE_WHEEL_MOTION | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]);
+        currentEventList->count++;
+        
+        if (currentEventList->count == currentEventList->capacity) return;    // Security check
     }
+    //-------------------------------------------------------------------------------------
 
+    // Touch input currentEventList->events recording
+    //-------------------------------------------------------------------------------------
     for (int id = 0; id < MAX_TOUCH_POINTS; id++)
     {
-        // INPUT_TOUCH_UP
+        // Event type: 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++;
+            currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter;
+            currentEventList->events[currentEventList->count].type = INPUT_TOUCH_UP;
+            currentEventList->events[currentEventList->count].params[0] = id;
+            currentEventList->events[currentEventList->count].params[1] = 0;
+            currentEventList->events[currentEventList->count].params[2] = 0;
+
+            TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_TOUCH_UP | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]);
+            currentEventList->count++;
         }
+        
+        if (currentEventList->count == currentEventList->capacity) return;    // Security check
 
-        // INPUT_TOUCH_DOWN
+        // Event type: 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++;
+            currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter;
+            currentEventList->events[currentEventList->count].type = INPUT_TOUCH_DOWN;
+            currentEventList->events[currentEventList->count].params[0] = id;
+            currentEventList->events[currentEventList->count].params[1] = 0;
+            currentEventList->events[currentEventList->count].params[2] = 0;
+
+            TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_TOUCH_DOWN | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]);
+            currentEventList->count++;
         }
+        
+        if (currentEventList->count == currentEventList->capacity) return;    // Security check
 
-        // INPUT_TOUCH_POSITION
+        // Event type: 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++;
+            currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter;
+            currentEventList->events[currentEventList->count].type = INPUT_TOUCH_POSITION;
+            currentEventList->events[currentEventList->count].params[0] = id;
+            currentEventList->events[currentEventList->count].params[1] = (int)CORE.Input.Touch.currentPosition[id].x;
+            currentEventList->events[currentEventList->count].params[2] = (int)CORE.Input.Touch.currentPosition[id].y;
+
+            TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_TOUCH_POSITION | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]);
+            currentEventList->count++;
         }
         */
+        
+        if (currentEventList->count == currentEventList->capacity) return;    // Security check
     }
+    //-------------------------------------------------------------------------------------
 
+    // Gamepad input currentEventList->events recording
+    //-------------------------------------------------------------------------------------
     for (int gamepad = 0; gamepad < MAX_GAMEPADS; gamepad++)
     {
-        // INPUT_GAMEPAD_CONNECT
+        // Event type: INPUT_GAMEPAD_CONNECT
         /*
         if ((CORE.Input.Gamepad.currentState[gamepad] != CORE.Input.Gamepad.previousState[gamepad]) &&
             (CORE.Input.Gamepad.currentState[gamepad])) // Check if changed to ready
@@ -3028,7 +3161,7 @@ static void RecordAutomationEvent(unsigned int frame)
         }
         */
 
-        // INPUT_GAMEPAD_DISCONNECT
+        // Event type: INPUT_GAMEPAD_DISCONNECT
         /*
         if ((CORE.Input.Gamepad.currentState[gamepad] != CORE.Input.Gamepad.previousState[gamepad]) &&
             (!CORE.Input.Gamepad.currentState[gamepad])) // Check if changed to not-ready
@@ -3039,122 +3172,84 @@ static void RecordAutomationEvent(unsigned int frame)
 
         for (int button = 0; button < MAX_GAMEPAD_BUTTONS; button++)
         {
-            // INPUT_GAMEPAD_BUTTON_UP
+            // Event type: 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++;
+                currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter;
+                currentEventList->events[currentEventList->count].type = INPUT_GAMEPAD_BUTTON_UP;
+                currentEventList->events[currentEventList->count].params[0] = gamepad;
+                currentEventList->events[currentEventList->count].params[1] = button;
+                currentEventList->events[currentEventList->count].params[2] = 0;
+
+                TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_GAMEPAD_BUTTON_UP | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]);
+                currentEventList->count++;
             }
+            
+            if (currentEventList->count == currentEventList->capacity) return;    // Security check
 
-            // INPUT_GAMEPAD_BUTTON_DOWN
+            // Event type: 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++;
+                currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter;
+                currentEventList->events[currentEventList->count].type = INPUT_GAMEPAD_BUTTON_DOWN;
+                currentEventList->events[currentEventList->count].params[0] = gamepad;
+                currentEventList->events[currentEventList->count].params[1] = button;
+                currentEventList->events[currentEventList->count].params[2] = 0;
+
+                TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_GAMEPAD_BUTTON_DOWN | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]);
+                currentEventList->count++;
             }
+            
+            if (currentEventList->count == currentEventList->capacity) return;    // Security check
         }
 
         for (int axis = 0; axis < MAX_GAMEPAD_AXIS; axis++)
         {
-            // INPUT_GAMEPAD_AXIS_MOTION
+            // Event type: 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++;
+                currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter;
+                currentEventList->events[currentEventList->count].type = INPUT_GAMEPAD_AXIS_MOTION;
+                currentEventList->events[currentEventList->count].params[0] = gamepad;
+                currentEventList->events[currentEventList->count].params[1] = axis;
+                currentEventList->events[currentEventList->count].params[2] = (int)(CORE.Input.Gamepad.axisState[gamepad][axis]*32768.0f);
+
+                TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_GAMEPAD_AXIS_MOTION | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]);
+                currentEventList->count++;
             }
+            
+            if (currentEventList->count == currentEventList->capacity) return;    // Security check
         }
     }
+    //-------------------------------------------------------------------------------------
 
-    // INPUT_GESTURE
+    // Gestures input currentEventList->events recording
+    //-------------------------------------------------------------------------------------
     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:  // param[0]: x delta, param[1]: y delta
-                {
-                    CORE.Input.Mouse.currentWheelMove.x = (float)events[i].params[0]; break;
-                    CORE.Input.Mouse.currentWheelMove.y = (float)events[i].params[1]; break;
-                } break;
-                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) -> rgestures.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;
-            }
-        }
-    }
+        // Event type: INPUT_GESTURE
+        currentEventList->events[currentEventList->count].frame = CORE.Time.frameCounter;
+        currentEventList->events[currentEventList->count].type = INPUT_GESTURE;
+        currentEventList->events[currentEventList->count].params[0] = GESTURES.current;
+        currentEventList->events[currentEventList->count].params[1] = 0;
+        currentEventList->events[currentEventList->count].params[2] = 0;
+
+        TRACELOG(LOG_INFO, "AUTOMATION: Frame: %i | Event type: INPUT_GESTURE | Event parameters: %i, %i, %i", currentEventList->events[currentEventList->count].frame, currentEventList->events[currentEventList->count].params[0], currentEventList->events[currentEventList->count].params[1], currentEventList->events[currentEventList->count].params[2]);
+        currentEventList->count++;
+        
+        if (currentEventList->count == currentEventList->capacity) return;    // Security check
+    }
+    //-------------------------------------------------------------------------------------
+
+    // Window events recording
+    //-------------------------------------------------------------------------------------
+    // TODO.
+    //-------------------------------------------------------------------------------------
+
+    // Custom actions events recording
+    //-------------------------------------------------------------------------------------
+    // TODO.
+    //-------------------------------------------------------------------------------------
 }
 #endif
 

+ 4 - 0
src/rcore.h

@@ -89,6 +89,10 @@
     #define MAX_DECOMPRESSION_SIZE        64        // Maximum size allocated for decompression in MB
 #endif
 
+#ifndef MAX_AUTOMATION_EVENTS
+    #define MAX_AUTOMATION_EVENTS      16384        // Maximum number of automation events to record
+#endif
+
 // Flags operation macros
 #define FLAG_SET(n, f) ((n) |= (f))
 #define FLAG_CLEAR(n, f) ((n) &= ~(f))