User Tag List

Results 1 to 3 of 3

Thread: Input recording and playback

  1. #1
    Is this the end?
    ZC Developer
    Saffith's Avatar
    Join Date
    Jan 2001
    Age
    41
    Posts
    3,389
    Mentioned
    178 Post(s)
    Tagged
    6 Thread(s)
    vBActivity - Stats
    Points
    6,433
    Level
    24
    vBActivity - Bars
    Lv. Percent
    70.07%

    Input recording and playback

    Some stuff for recording and playing back input. Useful for cutscenes, and maybe for testing some things.

    First, a few functions:
    Spoiler: show
    Code:
    const int CRKEY_UP    = 00000000000001b;
    const int CRKEY_DOWN  = 00000000000010b;
    const int CRKEY_LEFT  = 00000000000100b;
    const int CRKEY_RIGHT = 00000000001000b;
    const int CRKEY_A     = 00000000010000b;
    const int CRKEY_B     = 00000000100000b;
    const int CRKEY_L     = 00000001000000b;
    const int CRKEY_R     = 00000010000000b;
    const int CRKEY_START = 00000100000000b;
    const int CRKEY_MAP   = 00001000000000b;
    const int CRKEY_EX1   = 00010000000000b;
    const int CRKEY_EX2   = 00100000000000b;
    const int CRKEY_EX3   = 01000000000000b;
    const int CRKEY_EX4   = 10000000000000b;
    
    void ReplayInput(int inputArr, int pressArr)
    {
        int inputPtr=-2; // Start at -2 so they'll increment to 0
        int pressPtr=-2;
        int currInput;
        int currPress;
        int inputTimer=0;
        int pressTimer=0;
        bool inputDone=false;
        bool pressDone=false;
        
        while(true)
        {
            // Advance through the Input* array.
            if(!inputDone)
            {
                if(inputTimer>0)
                    inputTimer--;
                else
                {
                    // Timer expired; move to the next input.
                    inputPtr+=2;
                    if(inputPtr<SizeOfArray(inputArr))
                    {
                        currInput=inputArr[inputPtr];
                        inputTimer=inputArr[inputPtr+1]-1;
                        
                        // If the timer was 0, consider this the end.
                        if(inputTimer<0)
                        {
                            inputDone=true;
                            currInput=0;
                        }
                    }
                    else
                    {
                        // Reached the end
                        inputDone=true;
                        currInput=0;
                    }
                }
            }
            
            // Advance through the Press* array.
            if(!pressDone)
            {
                if(pressTimer>0)
                    pressTimer--;
                else
                {
                    // Timer expired; move to the next input.
                    pressPtr+=2;
                    if(pressPtr<SizeOfArray(pressArr))
                    {
                        currPress=pressArr[pressPtr];
                        pressTimer=pressArr[pressPtr+1]-1;
                        
                        // If the timer was 0, consider this the end.
                        if(pressTimer<0)
                        {
                            pressDone=true;
                            currPress=0;
                        }
                    }
                    else
                    {
                        // Reached the end.
                        pressDone=true;
                        currPress=0;
                    }
                }
            }
            
            // Check if finished here so there's not an extra
            // frame of no input at the end.
            if(inputDone && pressDone)
                return;
            
            SetInput(currInput, currPress);
            Waitframe();
        }
    }
    
    int GetInput()
    {
        int keys=0;
        
        if(Link->InputUp)
            keys|=CRKEY_UP;
        if(Link->InputDown)
            keys|=CRKEY_DOWN;
        if(Link->InputLeft)
            keys|=CRKEY_LEFT;
        if(Link->InputRight)
            keys|=CRKEY_RIGHT;
        if(Link->InputA)
            keys|=CRKEY_A;
        if(Link->InputB)
            keys|=CRKEY_B;
        if(Link->InputL)
            keys|=CRKEY_L;
        if(Link->InputR)
            keys|=CRKEY_R;
        if(Link->InputStart)
            keys|=CRKEY_START;
        if(Link->InputMap)
            keys|=CRKEY_MAP;
        if(Link->InputEx1)
            keys|=CRKEY_EX1;
        if(Link->InputEx2)
            keys|=CRKEY_EX2;
        if(Link->InputEx3)
            keys|=CRKEY_EX3;
        if(Link->InputEx4)
            keys|=CRKEY_EX4;
        
        return keys;
    }
    
    int GetPressed()
    {
        int keys=0;
        
        if(Link->PressUp)
            keys|=CRKEY_UP;
        if(Link->PressDown)
            keys|=CRKEY_DOWN;
        if(Link->PressLeft)
            keys|=CRKEY_LEFT;
        if(Link->PressRight)
            keys|=CRKEY_RIGHT;
        if(Link->PressA)
            keys|=CRKEY_A;
        if(Link->PressB)
            keys|=CRKEY_B;
        if(Link->PressL)
            keys|=CRKEY_L;
        if(Link->PressR)
            keys|=CRKEY_R;
        if(Link->PressStart)
            keys|=CRKEY_START;
        if(Link->PressMap)
            keys|=CRKEY_MAP;
        if(Link->PressEx1)
            keys|=CRKEY_EX1;
        if(Link->PressEx2)
            keys|=CRKEY_EX2;
        if(Link->PressEx3)
            keys|=CRKEY_EX3;
        if(Link->PressEx4)
            keys|=CRKEY_EX4;
        
        return keys;
    }
    
    void SetInput(int input, int press)
    {
        Link->InputUp=input&CRKEY_UP;
        Link->PressUp=press&CRKEY_UP;
        
        Link->InputDown=input&CRKEY_DOWN;
        Link->PressDown=press&CRKEY_DOWN;
        
        Link->InputLeft=input&CRKEY_LEFT;
        Link->PressLeft=press&CRKEY_LEFT;
        
        Link->InputRight=input&CRKEY_RIGHT;
        Link->PressRight=press&CRKEY_RIGHT;
        
        Link->InputA=input&CRKEY_A;
        Link->PressA=press&CRKEY_A;
        
        Link->InputB=input&CRKEY_B;
        Link->PressB=press&CRKEY_B;
        
        Link->InputL=input&CRKEY_L;
        Link->PressL=press&CRKEY_L;
        
        Link->InputR=input&CRKEY_R;
        Link->PressR=press&CRKEY_R;
        
        Link->InputStart=input&CRKEY_START;
        Link->PressStart=press&CRKEY_START;
        
        Link->InputMap=input&CRKEY_MAP;
        Link->PressMap=press&CRKEY_MAP;
        
        Link->InputEx1=input&CRKEY_EX1;
        Link->PressEx1=press&CRKEY_EX1;
        
        Link->InputEx2=input&CRKEY_EX2;
        Link->PressEx2=press&CRKEY_EX2;
        
        Link->InputEx3=input&CRKEY_EX3;
        Link->PressEx3=press&CRKEY_EX3;
        
        Link->InputEx4=input&CRKEY_EX4;
        Link->PressEx4=press&CRKEY_EX4;
    }


    GetInput() and GetPressed() return numbers representing the state of Link->Input* and Link->Press* for all standard keys (directions, start, map, A/B/L/R/Ex*). SetInput() takes those same numbers as arguments and sets each Input and Press variable accordingly.
    ReplayInput() takes two arrays. The first is a series of Input*, and the second is a series of Press*. The arrays consist of pairs of input states and durations. For instance:
    int inputArr[] = { CRKEY_DOWN, 30, CRKEY_DOWN | CRKEY_A, 10, CRKEY_UP, 15 };
    That represents down being held for 30 frames, down+A for 10 frames, and up for 15 frames. The Press* array works the same way, even though keys aren't normally newly pressed for consecutive frames.

    Hit the character limit. Continued below.

  2. #2
    Is this the end?
    ZC Developer
    Saffith's Avatar
    Join Date
    Jan 2001
    Age
    41
    Posts
    3,389
    Mentioned
    178 Post(s)
    Tagged
    6 Thread(s)
    vBActivity - Stats
    Points
    6,433
    Level
    24
    vBActivity - Bars
    Lv. Percent
    70.07%
    Rather than writing out such arrays manually, you can use this script to record input:

    Spoiler: show
    Code:
    const int IR_LAYER = 7; // Layer to draw on - should be 6 or 7
    const int IR_FGCOLOR = 1; // Text color
    const int IR_BGCOLOR = 0; // Text background color
    const int IR_POINTERTILE = 20500;
    const int IR_POINTERCSET = 7;
    
    // No need to change these
    const int IRIDX_STATE = 0;
    const int IRIDX_ARRAYDONECOUNTER = 1;
    const int IRIDX_SCRIPTDONECOUNTER = 2;
    const int IRIDX_RECORD = 3;
    const int IRIDX_STOP = 4;
    const int IRIDX_REPLAY = 5;
    const int IRIDX_WRITEARRAYS = 6;
    const int IRIDX_WRITESCRIPT = 7;
    const int IRIDX_DONE = 8;
    
    const int IRST_INACTIVE = 0;
    const int IRST_RECORDING = 1;
    
    ffc script InputRecorder
    {
        void run(bool noDelay)
        {
            // You shouldn't need to make these bigger, but you can.
            int inputArr[2048];
            int pressArr[2048];
            
            int lengths[2];
            
            int recordText[]="Record";
            int stopText[]="Stop";
            int replayText[]="Replay";
            int arrayText[]="Write arrays to allegro.log";
            int scriptText[]="Write script to allegro.log";
            int doneText[]="Done!";
            
            bool ignoreClick=false; // There's no Link->PressMouseB, so...
            
            // Store the pointers for Wait()
            this->Misc[IRIDX_RECORD]=recordText;
            this->Misc[IRIDX_STOP]=stopText;
            this->Misc[IRIDX_REPLAY]=replayText;
            this->Misc[IRIDX_WRITEARRAYS]=arrayText;
            this->Misc[IRIDX_WRITESCRIPT]=scriptText;
            this->Misc[IRIDX_DONE]=doneText;
            
            Game->ClickToFreezeEnabled=false;
            
            while(true)
            {
                // This is the inactive loop; other states
                // are in separate functions.
                if(Link->InputMouseB)
                {
                    if(!ignoreClick)
                    {
                        ignoreClick=true;
                        
                        // Determine which button was clicked based on the mouse's
                        // Y position. The X position is ignored.
                        if(Link->InputMouseY>=-48 && Link->InputMouseY<=-37)
                        {
                            // Record
                            this->Misc[IRIDX_STATE]=IRST_RECORDING;
                            RecordInput(this, inputArr, pressArr, lengths, noDelay==0);
                            this->Misc[IRIDX_STATE]=IRST_INACTIVE;
                        }
                        else if(Link->InputMouseY>=-36 && Link->InputMouseY<=-25)
                        {
                            // Replay
                            ReplayInput(inputArr, pressArr);
                        }
                        else if(Link->InputMouseY>=-24 && Link->InputMouseY<=-13)
                        {
                            // Write arrays
                            if(this->Misc[IRIDX_ARRAYDONECOUNTER]<=0)
                            {
                                WriteArrays(inputArr, pressArr, lengths[0], lengths[1], 0);
                                this->Misc[IRIDX_ARRAYDONECOUNTER]=120;
                            }
                        }
                        else if(Link->InputMouseY>=-12 && Link->InputMouseY<=-1)
                        {
                            // Write script
                            if(this->Misc[IRIDX_SCRIPTDONECOUNTER]<=0)
                            {
                                WriteScript(inputArr, pressArr, lengths[0], lengths[1]);
                                this->Misc[IRIDX_SCRIPTDONECOUNTER]=120;
                            }
                        }
                    }
                }
                else if(ignoreClick)
                        ignoreClick=false;
                
                Wait(this);
            }
        }
        
        void RecordInput(ffc this, int inputArr, int pressArr, int lengths, bool waitForInput)
        {
            bool ignoreClick=true; // The button will be down initially
            
            if(waitForInput)
            {
                // Don't start recording until a key is pressed.
                while(GetInput()==0)
                {
                    if(Link->InputMouseB)
                    {
                        if(!ignoreClick)
                        {
                            // Clicked stop already? Return without changing anything.
                            return;
                        }
                    }
                    else if(ignoreClick)
                        ignoreClick=false;
                    
                    Wait(this);
                }
            }
            
            // Clear whatever was in the arrays before
            for(int i=SizeOfArray(inputArr)-1; i>=0; i--)
                inputArr[i]=0;
            for(int i=SizeOfArray(pressArr)-1; i>=0; i--)
                pressArr[i]=0;
            
            // Initial values are a little weird. That's to make the first time
            // through the loop set things correctly.
            int currInput=GetInput();
            int currPress=GetPressed();
            int prevInput=currInput;
            int prevPress=currPress;
            int inputCounter=0;
            int pressCounter=0;
            int inputPtr=0;
            int pressPtr=0;
            
            while(true)
            {
                currInput=GetInput();
                currPress=GetPressed();
                
                if(currInput==prevInput)
                    inputCounter++;
                else
                {
                    // Input has changed; record the previous state and duration
                    inputArr[inputPtr]=prevInput;
                    inputArr[inputPtr+1]=inputCounter;
                    inputPtr+=2;
                    
                    prevInput=currInput;
                    inputCounter=1;
                }
                
                if(currPress==prevPress)
                    pressCounter++;
                else
                {
                    // Input has changed; record the previous state and duration
                    pressArr[pressPtr]=prevPress;
                    pressArr[pressPtr+1]=pressCounter;
                    pressPtr+=2;
                    
                    prevPress=currPress;
                    pressCounter=1;
                }
                
                Wait(this);
                
                // Check if the stop button was clicked...
                if(Link->InputMouseB)
                {
                    if(!ignoreClick)
                    {
                        ignoreClick=true;
                        
                        if(Link->InputMouseY>=-48 && Link->InputMouseY<=-37)
                        {
                            // Record any keys held when the button was clicked
                            if(currInput!=0)
                            {
                                inputArr[inputPtr]=currInput;
                                inputArr[inputPtr+1]=inputCounter;
                                inputPtr+=2;
                            }
                            
                            if(currPress!=0)
                            {
                                pressArr[pressPtr]=currPress;
                                pressArr[pressPtr+1]=pressCounter;
                                pressPtr+=2;
                            }
                            
                            lengths[0]=inputPtr;
                            lengths[1]=pressPtr;
                            return;
                        }
                    }
                }
                else if(ignoreClick)
                    ignoreClick=false;
            }
        }
        
        void WriteArrays(int inputArr, int pressArr, int inputLength, int pressLength, int indentation)
        {
            int inputText[]="int inputArr[] = { ";
            int pressText[]="int pressArr[] = { ";
            int commaFmt[]="%d, ";
            int endFmt[]="%d };\n";
            int space[]=" ";
            int i;
            
            TraceNL();
            
            for(i=0; i<indentation; i++)
                TraceS(space);
            
            TraceS(inputText);
            for(i=0; i<inputLength; i++)
            {
                if(i<inputLength-1)
                    printf(commaFmt, inputArr[i]);
                else
                    printf(endFmt, inputArr[i]);
            }
            
            for(i=0; i<indentation; i++)
                TraceS(space);
            
            TraceS(pressText);
            for(i=0; i<pressLength; i++)
            {
                if(i<pressLength-1)
                    printf(commaFmt, pressArr[i]);
                else
                    printf(endFmt, pressArr[i]);
            }
            
            TraceNL();
        }
        
        void WriteScript(int inputArr, int pressArr, int inputLength, int pressLength)
        {
            int scriptNameFmt[]="ReplayInput_%d_%X";
            int scriptPart1[]="ffc script ";
            int scriptPart2[]="\n{\n    void run()\n    {";
            int scriptPart3[]="        ReplayInput(inputArr, pressArr);\n    }\n}\n";
            
            TraceNL();
            TraceS(scriptPart1);
            printf(scriptNameFmt, Game->GetCurMap(), Game->GetCurScreen());
            printf(scriptPart2);
            WriteArrays(inputArr, pressArr, inputLength, pressLength, 8);
            printf(scriptPart3);
            TraceNL();
        }
        
        // Draws the strings and pointer, then calls Waitframe()
        void Wait(ffc this)
        {
            if(this->Misc[IRIDX_STATE]==IRST_INACTIVE)
            {
                Screen->DrawString(IR_LAYER, 8, -46, FONT_Z1, IR_FGCOLOR, IR_BGCOLOR, TF_NORMAL, this->Misc[IRIDX_RECORD], OP_OPAQUE);
                Screen->DrawString(IR_LAYER, 8, -34, FONT_Z1, IR_FGCOLOR, IR_BGCOLOR, TF_NORMAL, this->Misc[IRIDX_REPLAY], OP_OPAQUE);
                
                if(this->Misc[IRIDX_ARRAYDONECOUNTER]<=0)
                    Screen->DrawString(IR_LAYER, 8, -22, FONT_Z1, IR_FGCOLOR, IR_BGCOLOR, TF_NORMAL, this->Misc[IRIDX_WRITEARRAYS], OP_OPAQUE);
                else
                {
                    Screen->DrawString(IR_LAYER, 8, -22, FONT_Z1, IR_FGCOLOR, IR_BGCOLOR, TF_NORMAL, this->Misc[IRIDX_DONE], OP_OPAQUE);
                    this->Misc[IRIDX_ARRAYDONECOUNTER]--;
                }
                
                if(this->Misc[IRIDX_SCRIPTDONECOUNTER]<=0)
                    Screen->DrawString(IR_LAYER, 8, -10, FONT_Z1, IR_FGCOLOR, IR_BGCOLOR, TF_NORMAL, this->Misc[IRIDX_WRITESCRIPT], OP_OPAQUE);
                else
                {
                    Screen->DrawString(IR_LAYER, 8, -10, FONT_Z1, IR_FGCOLOR, IR_BGCOLOR, TF_NORMAL, this->Misc[IRIDX_DONE], OP_OPAQUE);
                    this->Misc[IRIDX_SCRIPTDONECOUNTER]--;
                }
                
            }
            else // Recording
            {
                Screen->DrawString(IR_LAYER, 8, -46, FONT_Z1, IR_FGCOLOR, IR_BGCOLOR, TF_NORMAL, this->Misc[IRIDX_STOP], OP_OPAQUE);
            }
            
            Screen->FastTile(IR_LAYER, Link->InputMouseX, Link->InputMouseY, IR_POINTERTILE, IR_POINTERCSET, OP_OPAQUE);
            Waitframe();
        }
    }


    D0 controls whether it waits for input before it starts recording (0=yes, 1=no).
    This will draw some text over the passive subscreen; set IR_LAYER correctly or it won't appear. Click on the text to do whatever it says; it should be self-explanatory.
    With input recorded, it can write the resulting arrays to allegro.log to copy and paste into scripts. It can also output a complete FFC script that will do nothing but play back the input. The script's name will include the current map and screen, so you should do the recording on the same screen where you want to use it.

    Note that the way ZC handles Link's position can lead to the same input producing slightly different results sometimes. This is more likely to be significant if diagonal movement is disabled. Be sure to test more than once.

  3. #3
    The Time-Loop Continues ZC Developer
    Gleeok's Avatar
    Join Date
    Apr 2007
    Posts
    4,826
    Mentioned
    259 Post(s)
    Tagged
    10 Thread(s)
    vBActivity - Stats
    Points
    12,959
    Level
    33
    vBActivity - Bars
    Lv. Percent
    26.21%
    Interesting. Now we just need a script binding for "void Game->SeedRand(int value);".
    This post contains the official Gleeok seal of approval. Look for these and other posts in an area near you.

Thread Information

Users Browsing this Thread

There are currently 1 users browsing this thread. (0 members and 1 guests)

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  
About us
Armageddon Games is a game development group founded in 1997. We are extremely passionate about our work and our inspirations are mostly drawn from games of the 8-bit and 16-bit era.
Social