Solved Pressing Buttons Simultaneously with Key Scripts

Question that is answered or resolved.

maxman

Well-known member
I have trouble with key delays and one of the buttons is pressed first before pressing the other to perform a special move. It's kinda like anim special which you hold attack first and then press jump to perform that move when you have anim block, but I'm trying not to have press delays. For example, in scripts, you have attack and attack2 keys. You press attack button first and then after that, you press attack2 button to perform a special move. It can be either way. I'm trying not to press one and hold and then press the other to perform a special.

This is my keyscript and I'm having trouble with SimulPress() function. It's based on the charging script like Mega Man charging shot, but I changed it to make my attempted simultaneous press. (How can I press keys at the same time without having key delays?)
Code:
#define ov openborvariant
#define time ov("elapsed_time")

void main(){
    SimulPress();
}

void SimulPress(){
    void self = getlocalvar("self");
    void ani = getentityproperty(self, "animationid");
    int index = getentityproperty(self, "playerindex");

    // Presses
    int attack = playerkeys(index, 1, "attack");
    int attack2 = playerkeys(index, 1, "attack2");

    // Releases
    int attackR = playerkeys(index, 2, "attack");
    int attack2R = playerkeys(index, 2, "attack2");

    float A = getentityvar(self, "A");
    float A2 = getentityvar(self, "A2");

    float AR = getentityvar(self, "AR");
    float A2R = getentityvar(self, "A2R");

    if(attack){
        if(A == NULL()){
            A = time;
            setentityvar(self, "A", A);
        }
    }

    if(A <= time - 10){
        A == NULL();
        setentityvar(self, "A", NULL());
    }

    if(attack2){
        if(A2 == NULL()){
            A2 = time;
            setentityvar(self, "A2", A2);
        }
    }

    if(A2 <= time - 10){
        A2 == NULL();
        setentityvar(self, "A2", NULL());
    }

    if(A != NULL() && A2 != NULL() && ani != openborconstant("ANI_SPAWN")){
        performattack(self, openborconstant("ANI_FREESPECIAL4"));
    }
    
}
 
Solution
There's no such thing as a simultaneous press, and that's not just OpenBOR. This is the case no matter what platform or software you are using. To truly get rid of input delays, you have to somehow check it both ways. Check one key press and the other key's hold, then do it in the opposite direction.

The problem is this really starts to get messy from a coding perspective, which is why no professional developer gets key values piecemeal like you're doing here. You need to start using bitwise logic. This will simplify (and optimize) your key scripts to an unbelievable degree. I outlined how to do it step by step in my last few coding videos. I suggest you check them out.

The end result will look something like this. Notice how there's...
There's no such thing as a simultaneous press, and that's not just OpenBOR. This is the case no matter what platform or software you are using. To truly get rid of input delays, you have to somehow check it both ways. Check one key press and the other key's hold, then do it in the opposite direction.

The problem is this really starts to get messy from a coding perspective, which is why no professional developer gets key values piecemeal like you're doing here. You need to start using bitwise logic. This will simplify (and optimize) your key scripts to an unbelievable degree. I outlined how to do it step by step in my last few coding videos. I suggest you check them out.

The end result will look something like this. Notice how there's a single variable for press, hold, and release, and you only need to get them once.

C:
int key_press = getplayerproperty(player_index, "newkeys");
int key_hold = getplayerproperty(player_index, "keys");
// int key_release = getplayerproperty(player_index, "releasekeys"); // Not used in this example.

if(key_press & openborconstant("FLAG_ATTACK") && key_hold & openborconstant("FLAG_ATTACK2")
|| key_press & openborconstant("FLAG_ATTACK2") && key_hold & openborconstant("FLAG_ATTACK"))
{
     /*
     *  Do Attack + Attack 2 action.
     */
}

if(key_press & openborconstant("FLAG_ATTACK") && key_hold & openborconstant("FLAG_SPECIAL")
|| key_press & openborconstant("FLAG_SPECIAL") && key_hold & openborconstant("FLAG_ATTACK"))
{
     /*
     *  Do Attack + Special action.
     */
}

HTH,
DC
 
Solution
Thank you very much, DC! This works wonders. I forgot about FLAG_ATTACK which I don't see in the manual. That must be for 6 specific actions right?

These include FLAG_ATTACK, FLAG_JUMP, FLAG_SPECIAL, FLAG_ATTACK2, FLAG_ATTACK3, and FLAG_ATTACK4.
 
Thank you very much, DC! This works wonders. I forgot about FLAG_ATTACK which I don't see in the manual. That must be for 6 specific actions right?

These include FLAG_ATTACK, FLAG_JUMP, FLAG_SPECIAL, FLAG_ATTACK2, FLAG_ATTACK3, and FLAG_ATTACK4.

Correct. There's also all four move keys, Start, Screenshot, and the Escape key.

DC
 
There's no such thing as a simultaneous press, and that's not just OpenBOR. This is the case no matter what platform or software you are using. To truly get rid of input delays, you have to somehow check it both ways. Check one key press and the other key's hold, then do it in the opposite direction.

The problem is this really starts to get messy from a coding perspective, which is why no professional developer gets key values piecemeal like you're doing here. You need to start using bitwise logic. This will simplify (and optimize) your key scripts to an unbelievable degree. I outlined how to do it step by step in my last few coding videos. I suggest you check them out.

The end result will look something like this. Notice how there's a single variable for press, hold, and release, and you only need to get them once.

C:
int key_press = getplayerproperty(player_index, "newkeys");
int key_hold = getplayerproperty(player_index, "keys");
// int key_release = getplayerproperty(player_index, "releasekeys"); // Not used in this example.

if(key_press & openborconstant("FLAG_ATTACK") && key_hold & openborconstant("FLAG_ATTACK2")
|| key_press & openborconstant("FLAG_ATTACK2") && key_hold & openborconstant("FLAG_ATTACK"))
{
     /*
     *  Do Attack + Attack 2 action.
     */
}

if(key_press & openborconstant("FLAG_ATTACK") && key_hold & openborconstant("FLAG_SPECIAL")
|| key_press & openborconstant("FLAG_SPECIAL") && key_hold & openborconstant("FLAG_ATTACK"))
{
     /*
     *  Do Attack + Special action.
     */
}

HTH,
DC
is there a way to trigger 2 different directions and a button pressed/held?
Example, Down, Forward + A2
I've tried many different solutions based on your code, but nothing worked.
I am running out of buttons and directions to use for keyscript.c
Thank you
 
is there a way to trigger 2 different directions and a button pressed/held?
Example, Down, Forward + A2
I've tried many different solutions based on your code, but nothing worked.
I am running out of buttons and directions to use for keyscript.c
Thank you

@DCurrent provided an example of a script which triggers 2 directions combined for activating years ago. It's interesting to see the result. However, I added a key, like attack, for the two directional buttons combined, just for my taste. I got it from him, but I modified a couple of these scripts based on his example for my own.

zangief.c:
C:
#import    "data/scripts/actual/zangief_actual.c"

void main(){
    actual_main();
}

zangief_actual.c:
C:
void actual_main(){
    playerControl();
    Diags();
}

void Diags()
{
    void target;        // Target entity for action.
    int player_index;   // Player index triggering event.
      
    // Key status.
    int key_hold;
    int key_forward;
    int iAttack;
  
    // Get the player index and target entity.
    player_index    = getlocalvar("player");
    target          = getplayerproperty(player_index, "entity");       
  
    // Get key hold status.
    key_hold    = getplayerproperty(player_index, "keys");   
  
    // Holding the down key?
    if(key_hold & openborconstant("FLAG_MOVEDOWN"))
    {
        // Check to see if current hold key has a forward match.
        key_forward = dc_check_key_forward(key_hold, target);
        iAttack    = playerkeys(player_index, 1, "attack");
  
        if(key_forward && iAttack)
        {
            performattack(target, openborconstant("ani_freespecial9"));
        }
    }
}
      
// Caskey, Damon V.
// 2018-08-13
//
// Return true if key status includes a "forward"
// direction key in relation to target entity facing.
int dc_check_key_forward(int key_status, void target)
{
    int result;
  
    // Default false.
    result = 0;
  
    // Which direction are we facing?
    int direction = getentityproperty(target, "direction");
  
    // Facing right?
    if(direction == openborconstant("DIRECTION_RIGHT"))
    {
        // Does the current key status contain a match
        // to the right direction key? If so result is true.
        if(key_status & openborconstant("FLAG_MOVERIGHT"))
        {
            result = 1;
        }       
    }
    else
    {
        // Same as above, but for left.
        if(key_status & openborconstant("FLAG_MOVELEFT"))
        {
            result = 1;
        }
    }
  
    return result;
}

You can either use keyscript or entity script for this. However, I use script like this.

Code:
script data/scripts/zangief.c
Code:
anim freespecial9 #Crouch LK
    delay    4
    offset    337 291
    bbox 312 219 62 72
    frame data/CHARS/Zangief/CVS2_Zangief_257.png
    sound data/sounds/common/kick.wav
    attack4 349 256 70 39 5 0 0 0 0 0
    hitfx data/sounds/common/beat4.wav
    frame data/CHARS/Zangief/CVS2_Zangief_255.png
    attack4 349 260 89 39 5 0 0 0 0 0
    frame data/CHARS/Zangief/CVS2_Zangief_256.png
    attack4 0
    frame data/CHARS/Zangief/CVS2_Zangief_255.png
    frame data/CHARS/Zangief/CVS2_Zangief_257.png
    @cmd    keyint2 "ANI_DUCK" 2 "D" 0
    @cmd    keyint2 "ANI_FOLLOW6" 0 "D" 1
    frame data/CHARS/Zangief/CVS2_Zangief_257.png

Here's another one:
C:
void actual_main()
{
    PlayerControl();
    Diagonals();
}


// Diagonal basics

void Diagonals()
{
    void target;        // Target entity for action.
    int player_index;   // Player index triggering event.
      
    // Key status.
    int key_hold;
    int key_forward;
    int iAttack;
    int iAttack3;

    int fightStarted = getindexedvar("fightStarted");
  
    // Get the player index and target entity.
    //player_index    = getlocalvar("player");
    target = getlocalvar("self");
    player_index = getentityproperty(target, "playerindex");
    //target          = getplayerproperty(player_index, "entity");       
  
    // Get key hold status.
    key_hold    = getplayerproperty(player_index, "keys");   
  
  if(fightStarted){
    // Holding the down key?
    if(key_hold & openborconstant("FLAG_MOVEDOWN"))
    {
        // Check to see if current hold key has a forward match.
        key_forward = dc_check_key_forward(key_hold, target);
        iAttack    = playerkeys(player_index, 1, "attack");
        iAttack3    = playerkeys(player_index, 1, "attack3");
  
        if(key_forward && iAttack3)
        {
            performattack(target, openborconstant("ani_attack25"));
        }
    }
  }
}

Code:
script data/chars/joe/joe.c

anim    attack25 #Sliding Spectre
    delay    7
    offset    71 114
    @cmd setindexedvar "fightStarted" 1
    frame    data/chars/joe/slide01.png
    frame    data/chars/joe/slide02.png
    frame    data/chars/joe/slide03.png
    @cmd    velo001 1.4 0 0
    frame    data/chars/joe/slide04.png
    frame    data/chars/joe/slide05.png
    frame    data/chars/joe/slide06.png
    @cmd    velo001 0 0 0
    frame    data/chars/joe/slide07.png
    frame    data/chars/joe/slide08.png
    frame    data/chars/joe/slide09.png
    @cmd    keyint2 "ANI_DUCK" 2 "D" 0
    @cmd    keyint2 "ANI_FOLLOW6" 0 "D" 1
    frame    data/chars/joe/slide10.png

This is when/where you press/hold both down and forward together and then press an attack button. Mine works fine but I don't know about yours. Maybe from @kimono's Way of the Martial Arts WIP thread?

P.S.: I was right. Just found it.
 
press/hold both down and forward together
Does this mean hold/press diagonal angle D/F or can it be press Down then press Forward + Attack?

I didn't have the press/hold down check method before, that's why it didn't work.
After reading the code, now I understand it better that by having the check method, it gives the system more time before the forward direction is pressed.
DC is genius....
I am at work atm, so I can not try out your code yet.

Btw, I am trying to create a universal assistant skill system keyscript for the assistant chars and summon npc's, so that the players can use these assistant skills to add to their combos with the same 6 sets of key combinations. Which means the players have choices to create their own combos on top of the combos I came up with...
Summon NPC's will have 3 assist skills each and assistant chars will also have 3 assist skills each.
I am almost done with Dante.. I will add Vergil next, so they can use assistant skills with each other.
I still need to learn how to create stages but not enough time to do it all... but one step at a time and it will get there eventually...
My game will have a lot of skills and combos to play with and it will be fun.

Thank you so much for your help.
 
Last edited:
Does this mean hold/press diagonal angle D/F or can it be press Down then press Forward + Attack?
It's either way, but not in a way you quickly press down forward attack for Hado(u)ken. Diagonally, you can press or hold forward first and then down (or press/hold both at the same time), then press attack too.

Any time.
 
It's either way, but not in a way you quickly press down forward attack for Hado(u)ken. Diagonally, you can press or hold forward first and then down (or press/hold both at the same time), then press attack too.

Any time.
Yeah, this is like pressing diagonal D/F.
I just tried the code to make it like U, D or D, U. It won't work.
Based on your information provided, I am just going to use the diagonal D/F and U/F combinations like this

Code:
if((KeyPress & DOWN || KeyHold & DOWN) && (KeyPress & RIGHT || KeyHold & RIGHT) && (KeyPress & Attack_2 || KeyHold & Attack_2)) {
I wanted to have U, D and D, U, but it looks like it is not easy to make it work for keyscript.

Thanks again for your help
 
I wanted to have U, D and D, U, but it looks like it is not easy to make it work for keyscript.
I believe you want a QCF move, right? I will post my SORX script used by the qcf rage move once it can be useful for everyone.
I cleaned some SORX variables but didn't test yet, let me know if it crashes the engine.

These codes must be placed in a keyscript.c event and called in every playable character.
Code:
void main()
{
    qcfMove();
}

void qcfMove()
{//Perform QCF moves
    void self = getlocalvar("self");
    void vAniID = getentityproperty(self,"animationID");
    int iPIndex = getlocalvar("player");
    int maxMp = getentityproperty(self,"maxMp");
    int mp = getentityproperty(self,"mp");
    int dir = getentityproperty(self,"direction");
    int costMp = maxMp;
    float time = openborvariant("elapsed_time");

    //CALL THE QCF KEY REGISTRATION
    pressQcf();

    //CHECK IF THE MP IS ENOUGH
    if(mp >= costMp){
        
        //DOWN/FRONT/ENERGY
        int checkKey1 = (playerkeys(iPIndex, 0, "attack2") && getglobalvar("qcfLeft"+iPIndex) > time && dir == 0); //LEFT
        int checkKey2 = (playerkeys(iPIndex, 0, "attack2") && getglobalvar("qcfRight"+iPIndex) > time && dir == 1); //RIGHT
        
        //CHECK KEYS
        if(checkKey1 || checkKey2){

            //CHECK ALL THE ALLOWED ANIMATIONS
            if( vAniID == openborconstant("ANI_IDLE")   ||
                vAniID == openborconstant("ANI_WALK")   ||
                vAniID == openborconstant("ANI_RUN")    ){

                mpCost(costMp); //SUBTRACT MP
                changeentityproperty(self, "velocity", 0, 0, 0); //RESET VELOCITY
                performattack(self, openborconstant("ANI_FREESPECIAL"), 0); //PERFORM THE ATTACK
            }
        }
    }
}

void pressQcf()
{//Global registration when the player presses "QCF" command
 //This script will register the "DOWN > FRONT" command as a single input to make some key script easier to develop
    void self   = getlocalvar("self");
    int iPIndex = getlocalvar("player");
    int dir     = getentityproperty(self,"direction");
    float time  = openborvariant("elapsed_time");
    float delay = 30; //FREELY ADJUST THE DELAY
    
    //STEP 1 - REGISTER "DOWN" KEY PRESSED
    if(playerkeys(iPIndex, 1, "movedown")){
        setglobalvar("qcfDown"+iPIndex, time+delay);
    }

    //STEP 2 - REGISTER "LEFT" KEY PRESSED
    if(playerkeys(iPIndex, 1, "moveleft") && dir == 0){
        if(getglobalvar("qcfDown"+iPIndex) > time){
            setglobalvar("qcfLeft"+iPIndex, time+delay);
            setglobalvar("qcfDown"+iPIndex, NULL());
        }
    }

    //STEP 2 - REGISTER "RIGHT" KEY PRESSED
    if(playerkeys(iPIndex, 1, "moveright") && dir == 1){
        if(getglobalvar("qcfDown"+iPIndex) > time){
            setglobalvar("qcfRight"+iPIndex, time+delay);
            setglobalvar("qcfDown"+iPIndex, NULL());
        }
    }
}

void mpCost(int cost)
{//Spend some mp
    void self = getlocalvar("self");
    int mp = getentityproperty(self,"mp");

    changeentityproperty(self, "mp", mp-cost);
}

I also suggest clearing some variables in the level.c file to avoid issues since the "elapsed_time" will reset too.

Code:
void main()
{
    //RESET KEY REGISTRATIONS
    setglobalvar("qcfDown0", NULL());
    setglobalvar("qcfDown1", NULL());
    setglobalvar("qcfDown2", NULL());
    setglobalvar("qcfDown3", NULL());
    setglobalvar("qcfLeft0", NULL());
    setglobalvar("qcfLeft1", NULL());
    setglobalvar("qcfLeft2", NULL());
    setglobalvar("qcfLeft3", NULL());
    setglobalvar("qcfRight0", NULL());
    setglobalvar("qcfRight1", NULL());
    setglobalvar("qcfRight2", NULL());
    setglobalvar("qcfRight3", NULL());
}
 
Why not apply the same for the up key like this?

C:
void Diags()
{
    void target;        // Target entity for action.
    int player_index;   // Player index triggering event.
     
    // Key status.
    int key_hold;
    int key_forward;
    int iAttack;
 
    // Get the player index and target entity.
    player_index    = getlocalvar("player");
    target          = getplayerproperty(player_index, "entity");      
    int iAttack = playerkeys(player_index, 1, "attack");;
   
    // Get key hold status.
    key_hold    = getplayerproperty(player_index, "keys");  
 
    // Holding the down key?
    if(key_hold & openborconstant("FLAG_MOVEDOWN"))
    {
        // Check to see if current hold key has a forward match.
        key_forward = dc_check_key_forward(key_hold, target);
 
        if(key_forward && iAttack)
        {
            performattack(target, openborconstant("ani_freespecial9"));
        }
    }
   
    // Holding the up key?
    if(key_hold & openborconstant("FLAG_MOVEUP"))
    {
        // Check to see if current hold key has a forward match.
        key_forward = dc_check_key_forward(key_hold, target);
 
        if(key_forward && iAttack)
        {
            performattack(target, openborconstant("ani_freespecial23"));
        }
    }
   
}
     
// Caskey, Damon V.
// 2018-08-13
//
// Return true if key status includes a "forward"
// direction key in relation to target entity facing.
int dc_check_key_forward(int key_status, void target)
{
    int result;
 
    // Default false.
    result = 0;
 
    // Which direction are we facing?
    int direction = getentityproperty(target, "direction");
 
    // Facing right?
    if(direction == openborconstant("DIRECTION_RIGHT"))
    {
        // Does the current key status contain a match
        // to the right direction key? If so result is true.
        if(key_status & openborconstant("FLAG_MOVERIGHT"))
        {
            result = 1;
        }      
    }
    else
    {
        // Same as above, but for left.
        if(key_status & openborconstant("FLAG_MOVELEFT"))
        {
            result = 1;
        }
    }
 
    return result;
}

You can use script instead of keyscript in the character header for this.

P.S.: Ahh. Didn't notice Kratus posted here.
 
I believe you want a QCF move, right? I will post my SORX script used by the qcf rage move once it can be useful for everyone.
I cleaned some SORX variables but didn't test yet, let me know if it crashes the engine.

These codes must be placed in a keyscript.c event and called in every playable character.
Code:
void main()
{
    qcfMove();
}

void qcfMove()
{//Perform QCF moves
    void self = getlocalvar("self");
    void vAniID = getentityproperty(self,"animationID");
    int iPIndex = getlocalvar("player");
    int maxMp = getentityproperty(self,"maxMp");
    int mp = getentityproperty(self,"mp");
    int dir = getentityproperty(self,"direction");
    int costMp = maxMp;
    float time = openborvariant("elapsed_time");

    //CALL THE QCF KEY REGISTRATION
    pressQcf();

    //CHECK IF THE MP IS ENOUGH
    if(mp >= costMp){
      
        //DOWN/FRONT/ENERGY
        int checkKey1 = (playerkeys(iPIndex, 0, "attack2") && getglobalvar("qcfLeft"+iPIndex) > time && dir == 0); //LEFT
        int checkKey2 = (playerkeys(iPIndex, 0, "attack2") && getglobalvar("qcfRight"+iPIndex) > time && dir == 1); //RIGHT
      
        //CHECK KEYS
        if(checkKey1 || checkKey2){

            //CHECK ALL THE ALLOWED ANIMATIONS
            if( vAniID == openborconstant("ANI_IDLE")   ||
                vAniID == openborconstant("ANI_WALK")   ||
                vAniID == openborconstant("ANI_RUN")    ){

                mpCost(costMp); //SUBTRACT MP
                changeentityproperty(self, "velocity", 0, 0, 0); //RESET VELOCITY
                performattack(self, openborconstant("ANI_FREESPECIAL"), 0); //PERFORM THE ATTACK
            }
        }
    }
}

void pressQcf()
{//Global registration when the player presses "QCF" command
 //This script will register the "DOWN > FRONT" command as a single input to make some key script easier to develop
    void self   = getlocalvar("self");
    int iPIndex = getlocalvar("player");
    int dir     = getentityproperty(self,"direction");
    float time  = openborvariant("elapsed_time");
    float delay = 30; //FREELY ADJUST THE DELAY
  
    //STEP 1 - REGISTER "DOWN" KEY PRESSED
    if(playerkeys(iPIndex, 1, "movedown")){
        setglobalvar("qcfDown"+iPIndex, time+delay);
    }

    //STEP 2 - REGISTER "LEFT" KEY PRESSED
    if(playerkeys(iPIndex, 1, "moveleft") && dir == 0){
        if(getglobalvar("qcfDown"+iPIndex) > time){
            setglobalvar("qcfLeft"+iPIndex, time+delay);
            setglobalvar("qcfDown"+iPIndex, NULL());
        }
    }

    //STEP 2 - REGISTER "RIGHT" KEY PRESSED
    if(playerkeys(iPIndex, 1, "moveright") && dir == 1){
        if(getglobalvar("qcfDown"+iPIndex) > time){
            setglobalvar("qcfRight"+iPIndex, time+delay);
            setglobalvar("qcfDown"+iPIndex, NULL());
        }
    }
}

void mpCost(int cost)
{//Spend some mp
    void self = getlocalvar("self");
    int mp = getentityproperty(self,"mp");

    changeentityproperty(self, "mp", mp-cost);
}

I also suggest clearing some variables in the level.c file to avoid issues since the "elapsed_time" will reset too.

Code:
void main()
{
    //RESET KEY REGISTRATIONS
    setglobalvar("qcfDown0", NULL());
    setglobalvar("qcfDown1", NULL());
    setglobalvar("qcfDown2", NULL());
    setglobalvar("qcfDown3", NULL());
    setglobalvar("qcfLeft0", NULL());
    setglobalvar("qcfLeft1", NULL());
    setglobalvar("qcfLeft2", NULL());
    setglobalvar("qcfLeft3", NULL());
    setglobalvar("qcfRight0", NULL());
    setglobalvar("qcfRight1", NULL());
    setglobalvar("qcfRight2", NULL());
    setglobalvar("qcfRight3", NULL());
}
Thank you so much Kratus. To be honest, I thought of doing similar before, but saving the direction commands to the setentityvar variables instead. Can you please help me understand the benefits for using global variables over the setentityvar variables in this case?
All I want is to set 3 commands for the summon npc's and 3 commands for the assistant entities in the keyscript.c, and it will be used for all the main characters.
I was curious, so I went ahead and changed all global variables to entityvar variables since "self" can represent the player 1-4, and it works.
It is amazing how you guys can come with codes to work in many situations.
 
Can you please help me understand the benefits for using global variables over the setentityvar variables in this case?
They are very similar, in my case I used globalvar+player index because I have other scripts in some global events where self will not fit, plus playerkeys use index numbers too.
But if entityvar works fine for your game, there's no problem using it.
 
Back
Top Bottom