Solved Return projectile to launcher

Question that is answered or resolved.

kimono

Well-known member
Good evening, Openbor fans;
I'd like to return a projectile to its launcher.
The projectile will return in the opposite direction and may injure the enemy.

The change of direction and the candamage changes simply by attacking it.
This is how the knife entity is made up:
Code:
name        knife
health        1
type        none
shootnum     10
candamage     player obstacle
projectilehit 0
remove         0
shadow        0

alternatepal data/chars/soldier/alt1.gif
alternatepal data/chars/soldier/alt2.gif
alternatepal data/chars/soldier/alt3.gif

##ANIMATIONS#############################################################################

anim idle
    loop    1
    delay    5
    offset    4 4
    bbox 0 0 16 5
    attack1 0 0 16 5 2 1 0 0 0 0
    frame    data/chars/weapons/knife.png
Have you ever coded something like this?
I think @Kratus did something like this in SORX.
 

Attachments

  • My Mod - 0003.png
    My Mod - 0003.png
    25 KB · Views: 4
Solution
I made the mistake of setting the ondoattackscript to Kenshiro, which I've since corrected.
The two main issues now are the knife getting stuck in the attack box and the fact that its launcher is invincible against the projectile.
Thank you for your study, and I remain at your disposal if there's anything I can do for you ;)
I was able to make it work properly, but a few changes were required in some scripts. You can download the update here but I will post the scripts to explain how it works.

First, I added some custom types in the library to manage the projectiles better.

Code:
#define PLAYER_SHOT 16384
#define ENEMY_SHOT 32768
#define REFLECTED_SHOT 65536

Then, made some changes in the projectile_return.c (don't...
Good evening, Openbor fans;
I'd like to return a projectile to its launcher.
The projectile will return in the opposite direction and may injure the enemy.

The change of direction and the candamage changes simply by attacking it.
This is how the knife entity is made up:
Code:
name        knife
health        1
type        none
shootnum     10
candamage     player obstacle
projectilehit 0
remove         0
shadow        0

alternatepal data/chars/soldier/alt1.gif
alternatepal data/chars/soldier/alt2.gif
alternatepal data/chars/soldier/alt3.gif

##ANIMATIONS#############################################################################

anim idle
    loop    1
    delay    5
    offset    4 4
    bbox 0 0 16 5
    attack1 0 0 16 5 2 1 0 0 0 0
    frame    data/chars/weapons/knife.png
Have you ever coded something like this?
I think @Kratus did something like this in SORX.
Will the knife flip direction when blocked by the player or when taking a hit?
 
@kimono,

Something like I had going here?
  • Flies a set distance, then reverses and returns.
  • Reverses instantly if it hits an enemy.
  • Can hit on the return - does NOT reverse if already returning.
  • The owner is locked into waiting position until catching the return or hit by an enemy.

DC
 
The boomerang weapon is an interesting idea; however, the scenario here would be that a knife is thrown, potentially damaging the player.
The player attacks the knife, which is thrown back in the opposite direction of its thrower and can injure them.
The knife originates from an enemy entity.
 
Yes, this is correct: the knife flips direction when being attacked and candamage enemy 🙂
You can use the ondoattack event for the knife, like this.

Code:
void main()
{
    void self = getlocalvar("self");
    int currentDir = getentityproperty(self, "direction");
    int newDir;

    newDir = currentDir == 1 ? 0 : 1; //DEFINE DIRECTION

    if(getlocalvar("which")){ //WORKS FOR DEFENDER
        changeopenborvariant("lasthitc", 0); //DISABLE THE COLLISION
        changeentityproperty(self, "direction", newDir); //CHANGE DIRECTION
    }
}

 
Thanks Kratus! Here's where I am now:
Code:
ondoattackscript       data/scripts/projectile_return.c
Code:
name        knife
health        10
type        none
candamage     player obstacle
projectile       1
projectilehit 0
remove           0
falldie 0

alternatepal data/chars/soldier/alt1.gif
alternatepal data/chars/soldier/alt2.gif
alternatepal data/chars/soldier/alt3.gif

##ANIMATIONS#############################################################################

anim idle
    loop    1
    delay    5
    offset    4 4
    bbox 0 0 16 5
    attack1 0 0 16 5 2 1 0 0 0 0
    frame    data/chars/weapons/knife.png

And what this means in game:

I would like any enemy projectile to be reflected by any attack with the
Code:
candamage enemy obstacle
attribute
simply because "Hokuto Shinken muteki da" ("the Hokuto school is invincible").

The demo in question:
 
type_pshot

Because it kind of isn't. PShot was one of those buggy bolts ons from the bad old days that never should have existed. Using TYPE to control deep logic beyond setting up some defaults was never a good idea, and the original BOR didn't really do that. Early OpenBOR (pre SX) started all that mess and it's a long road cleaning it up.

Pshot Had its own hacky logic that caused a lot of problems elsewhere in the engine, plus a lot of inflexible hardcoding. Nothing you can do with it can't be done better another way.

DC
 
@DCurrent thanks for the explanation!

Actually, I probably didn't explain that well at all. What I really mean is having a Type for every kind of behavior you might need is the failure point, which is what early OpenBOR tried to do. Sooner or later, you'll need another type to handle some edge case - then another, then another, and on it goes. That's how we ended up with Pshot, Pshotno, Knife, star, etc. when they all just do variations of the same thing.

I have an example right here at work. We have a database of every single space on Campus. Every room or hallway is, at minimum, a "space", but in some cases, desks or open lab tables can be spaces too. Anything that is a space has a barcode sticker that we can quickly use to reference in the database, so no problem there.

However, one of the fields is "RoomUsage", as in what the space does. Bathroom, lab, hallway, etc., and this is a reference to a lookup table of use types. Here's the problem - spaces can of course have multiple uses and multiple variations of those uses. So the logical thing to do is have a child table with a one to many relationship. That way you could make a space have as many use combinations as you needed out of maybe 100 or so use types, and no problem.

1761662326174.png

That's not what happened. Instead, years ago, someone decided it was a good idea to have a single field in the room table...

1761662717187.png

...and just make a brand new use type every single time there's a new use case variation or combination of use cases. Lab? That's a usage. Lab that happens to have a certain table in it? Another usage. Lab with a bathroom? Another usage, and on it goes. That table now has 74,000+ entries in it - all them just little variations of the same dang thing.

1761663181455.png

Absolutely insane... It's completely useless for any data reporting or logic, and it's going to take years to clean up. That's the exact same thing as trying to have types for all the separate use cases in the engine.

DC
 
I changed the script as follows:
Code:
void main()
{
    void self = getlocalvar("self");
    int currentDir = getentityproperty(self, "direction");
    int newDir;

    newDir = currentDir == 1 ? 0 : 1; //DEFINE DIRECTION

    if(getlocalvar("which")){ //WORKS FOR DEFENDER
        changeopenborvariant("lasthitc", 0); //DISABLE THE COLLISION
        changeentityproperty(self, "direction", newDir); //CHANGE DIRECTION
        changeentityproperty(self, "candamage", "type_enemy", "type_obstacle"); //CHANGE CANDAMAGE ENEMY OBSTACLE
    }
}
The projectile changes direction well and damages enemies.
There are two minor issues, however:
- If you attack from the end of the attack box, the projectile is deflected, but if you make contact right in the middle, the knife gets trapped inside, bouncing back and forth.
- The initial throw is immune to the projectile.
How do I resolve these two specific issues?
 
Actually, I probably didn't explain that well at all. What I really mean is having a Type for every kind of behavior you might need is the failure point, which is what early OpenBOR tried to do. Sooner or later, you'll need another type to handle some edge case - then another, then another, and on it goes. That's how we ended up with Pshot, Pshotno, Knife, star, etc. when they all just do variations of the same thing.

I have an example right here at work. We have a database of every single space on Campus. Every room or hallway is, at minimum, a "space", but in some cases, desks or open lab tables can be spaces too. Anything that is a space has a barcode sticker that we can quickly use to reference in the database, so no problem there.

However, one of the fields is "RoomUsage", as in what the space does. Bathroom, lab, hallway, etc., and this is a reference to a lookup table of use types. Here's the problem - spaces can of course have multiple uses and multiple variations of those uses. So the logical thing to do is have a child table with a one to many relationship. That way you could make a space have as many use combinations as you needed out of maybe 100 or so use types, and no problem.

View attachment 12014

That's not what happened. Instead, years ago, someone decided it was a good idea to have a single field in the room table...

View attachment 12015

...and just make a brand new use type every single time there's a new use case variation or combination of use cases. Lab? That's a usage. Lab that happens to have a certain table in it? Another usage. Lab with a bathroom? Another usage, and on it goes. That table now has 74,000+ entries in it - all them just little variations of the same dang thing.

View attachment 12016

Absolutely insane... It's completely useless for any data reporting or logic, and it's going to take years to clean up. That's the exact same thing as trying to have types for all the separate use cases in the engine.

DC
What do you think about wiping out everything that existed before for Openbor 4.0 and starting again with a healthy base of essential functions that do the job?
There would actually be no backward compatibility with older games and it would require rewriting some aspects of the code.
 
What do you think about wiping out everything that existed before for Openbor 4.0 and starting again with a healthy base of essential functions that do the job?
There would actually be no backward compatibility with older games and it would require rewriting some aspects of the code.

Already started that project - and dropped it. For all the old cruft in it, OpenBOR is still beautifully stable and insanely powerful. There are maybe 10-15 genuine bugs I know of, and for an engine of this scope and size, that's almost unheard of. Reinventing the wheel is pointless. Instead, I set a policy that starting with 4.0, backward compatibility is no longer a priority. If there's a better, cleaner way to handle some functionality, that's what we do, and the old version gets depreciated.

DC
 
- The initial throw is immune to the projectile.
Depends on how you throw the knife. If you use a native method, it gets populated with a parent (or owner, I can't remember) and it is set to not hurt it (or, without it, if you make an entity which can hurt other players or other enemies, the called would get hit instantly by his own projectile).

but by script (like using shoot(), spawn01(), shooter() etc ), unless you set the parent manually, it will hit any target you set in candamage, including the one who throws it.
I've apdated all my functions to include a parent manually.
 
parent (or owner, I can't remember) and it is set to not hurt it (or, without it, if you make an entity which can hurt other players or other enemies, the called would get hit instantly by his own projectile).

It's Owner. Parent only has one function in native logic - Unsummon a child on death or with Unsummon command.

Owner is assigned to natively launched projectiles and does the following:
  • Giving credit to the owner when projectile hits something.
  • Hard codes projectile to never hit its owner.
  • In < 4.0, projectiles are also hard coded to never hit each other. The collision routine exits if both attacker and target have their owner property populated. This was removed in 4.0 to give creators more design freedom.
DC
 
@DCurrent sorry for the double post, but when we use "projectile", the script doesn't assign an owner, right?


or
@cmd projectile 0 "nameoftheprojectile" 20 1 30 0 0 0

It does. Under the hood, the script projectile() function just fires native knife or bomb spawn functions.

C:
//entity * projectile([0/1], char *name, float x, float z, float a, int direction, int pytype, int type, int map);
HRESULT openbor_projectile(ScriptVariant **varlist , ScriptVariant **pretvar, int paramCount)
{
    DOUBLE temp = 0;
    LONG ltemp = 0;
    entity *ent;
    char *name = NULL;
    float x = 0, z = 0, a = 0;
    int direction = DIRECTION_LEFT;
    int type = 0;
    int projectile_prime = 0;
    int map = 0;

    int relative;

    if(paramCount >= 1 && varlist[0]->vt == VT_INTEGER && varlist[0]->lVal)
    {
        relative = 1;
        paramCount--;
        varlist++;
    }
    else
    {
        relative = 0;
    }

    if(paramCount >= 1 && varlist[0]->vt == VT_STR)
    {
        name = StrCache_Get(varlist[0]->strVal);
    }

    if(paramCount >= 2 && SUCCEEDED(ScriptVariant_DecimalValue(varlist[1], &temp)))
    {
        x = (float)temp;
    }
    else if(relative)
    {
        x = 0;
    }
    else
    {
        x = self->position.x;
    }
    if(paramCount >= 3 && SUCCEEDED(ScriptVariant_DecimalValue(varlist[2], &temp)))
    {
        z = (float)temp;
    }
    else if(relative)
    {
        z = 0;
    }
    else
    {
        z = self->position.z;
    }
    if(paramCount >= 4 && SUCCEEDED(ScriptVariant_DecimalValue(varlist[3], &temp)))
    {
        a = (float)temp;
    }
    else if(relative)
    {
        a  = self->animation->projectile.position.y;
    }
    else
    {
        a = self->position.y + self->animation->projectile.position.y;
    }
    if(paramCount >= 5 && SUCCEEDED(ScriptVariant_IntegerValue(varlist[4], &ltemp)))
    {
        direction = (LONG)ltemp;
    }
    else if(relative)
    {
        direction  = DIRECTION_RIGHT;
    }
    else
    {
        direction = self->direction;
    }
    if(paramCount >= 6 && SUCCEEDED(ScriptVariant_IntegerValue(varlist[5], &ltemp)))
    {

        // Backwards compatibility for modules made before bitwise update
        // of projectile_prime and expect base vs. floor and moving
        // behavior to both be tied to a single 0 or 1 value.
        if((LONG)ltemp)
        {
            projectile_prime = PROJECTILE_PRIME_BASE_FLOOR;
            projectile_prime += PROJECTILE_PRIME_LAUNCH_STATIONARY;
        }
        else
        {
            projectile_prime = PROJECTILE_PRIME_BASE_Y;
            projectile_prime += PROJECTILE_PRIME_LAUNCH_MOVING;
        }
    }
    if(paramCount >= 7 && SUCCEEDED(ScriptVariant_IntegerValue(varlist[6], &ltemp)))
    {
        type = (LONG)ltemp;
    }
    if(paramCount >= 8 && SUCCEEDED(ScriptVariant_IntegerValue(varlist[7], &ltemp)))
    {
        map = (LONG)ltemp;
    }

    if(relative)
    {
        if(self->direction == DIRECTION_RIGHT)
        {
            x += self->position.x;
        }
        else
        {
            x = self->position.x - x;
            direction = !direction;
        }
        z += self->position.z;
        a += self->position.y;
    }

    switch(type)
    {
    default:
    case 0:
        ent = knife_spawn(name, -1, x, z, a, direction, projectile_prime, map);
        break;
    case 1:
        ent = bomb_spawn(name, -1, x, z, a, direction, map);
        break;
    }

    ScriptVariant_ChangeType(*pretvar, VT_PTR);
    (*pretvar)->ptrVal = (VOID *) ent;

    return S_OK;
}

DC
 
Back
Top Bottom