How to make enemies avoid traps?

I'm getting closer to an acceptable script, but it still has problems. The first half of the video looks fine, but the second half they start doing weird things. The most notable of them is just the last enemy, notice that I throw him into the air, and when he lines up right with the trap below, he is instantly tossed by the script. I guess I'll have to make the script from acting only if the character is in WALK and RUN animations.
I suggest checking if the enemy is in the walk animation. I didn't understand the rangex/rangez formula but I made some changes according to what I understand about the behaviour you want to apply.

C:
void main()
{
    void self = getlocalvar("self");
    void trap = findtarget(self);
    void isfire = getentityproperty(trap, "defaultname");
 
    if(isfire == "potfire" || isfire == "potfire2" || isfire == "potfire3")
    {
        void anim = getentityproperty(self, "animationID"); //GET THE CURRENT ANIMATION
        int z1 = getentityproperty(self, "z"); //ENEMY
        int z2 = getentityproperty(trap, "z"); //NPC TRAP
        int rangeZBelow = (z1 - z2);
        int rangeZAbove = (z2 - z1);
        
        //CHECK IF THE ENEMY IS IN THE WALK ANIMATION
        if(anim == openborconstant("ANI_WALK"))
        {
            //FIRE TRAP IS AT THE TOP OF THE Z BOUNDARY AND ENEMY IS BELOW (LESS THAN 45 PIXELS)
            if(z1 > z2 && rangeZBelow < 45){ //CHECK RANGE
                changeentityproperty(self, "animation", openborconstant("ANI_JUMP")); //CHANGE ANIMATION
                tossentity(self, 2, 0, 2);
            }
            //FIRE TRAP IS AT THE TOP OF THE Z BOUNDARY AND ENEMY IS BELOW (BETWEEN 45 AND 60 PIXELS)
            else if(z1 > z2 && rangeZBelow > 45 && rangeZBelow < 60){ //CHECK RANGE
                changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID"));
            }
            //FIRE TRAP IS AT THE BOTTOM Z BOUNDARY AND ENEMY IS ABOVE (LESS THAN 45 PIXELS)
            else if(z2 > z1 && rangeZAbove < 45){ //CHECK RANGE
                changeentityproperty(self, "animation", openborconstant("ANI_JUMP")); //CHANGE ANIMATION
                tossentity(self, 2, 0, -2);
            }
            //FIRE TRAP IS AT THE BOTTOM Z BOUNDARY AND ENEMY IS ABOVE (BETWEEN 45 AND 60 PIXELS)
            else if(z2 > z1 && rangeZAbove > 45 && rangeZBelow < 60){ //CHECK RANGE
                changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID"));
            }
            else
            {
                //ANY OTHER SITUATION BUT THE NEAREST TARGET IS THE FIRE TRAP
                changeentityproperty(self, "aimove", openborconstant("AIMOVE1_NORMAL"));   
            }
        }
    }
    else
    {
        //ANY OTHER SITUATION
        changeentityproperty(self, "aimove", openborconstant("AIMOVE1_CHASE"));   
    }
}
 
@Kratus or @DCurrent

how does the engine work when it comes to enemy entites capacity to avoid the players, (AIMOVE1_AVOID , avoid z avoid x)

and in the case of "helpers" NPC's, Do enemy entities have the capacity of avoiding them like they do Player entiites?

if not, would there be a way to give a trap or helper a "player entity" type as if it where anything beyond player 4?
Entity : spike trap
type : player
"@script - "this is player #25 "


i was thinking a @Bloodbane's factions type trick could be applied and make NPCs or traps a "Player faction "
 
@Toranks
reguarding current solutions,
it seems that since the scripts are using a range detection of sorts to activate certain animations, i would use an animspecial that branches into other animspecials that have the capacity for random reactions.

example, one reaction is to stop, look up and step back, (3 to 5 walk frames) the step can be controlled with a jupframe script that moves the character back and up or down z coordinates

reaction 2 jump away startled , same thing z movement variation torwads the camera along the z axis or away from the camera

reaction 3, using walkframes and scripted jump-frame makes the character repeat frames that walk up or down the z axis until the detection is over, normal walk idle sycles are restored...

reaction 4, character does a half a second, pause, but range script is ignored, walks into trap anyway like an idiot (random chance very low)
 
Ok, I think I already have acceptable behavior (at least better than default), but there is one thing I would like to fix. And it's that quick flip they make looking from left to right many times in a matter of a second when they pass between one trap and another or between a trap and the player. Would there be a way to avoid that, and always have them look at the player? Or put some delay to prevent them from flipping so fast?


 
I got it! I have some clarifications to make, but I think I achieved the perfect behavior.

- There is no difference between treating traps as fake npc or fake player. Enemies act the same with hostile NPCs as they do with players. ( @oldyz this detail and the code below may interests you)

- Therefore, traps need not be anything other than real traps. So they are left with "type trap". So, the enemies now only watch to the players, ignoring the traps, and avoiding the unpleasant flipping effect, and the fact that they seem to be constantly attracted and repelled by the fire.

- Findtarget doesn't work with traps, even if they are hostile ( @O Ilusionista you could add it to the wiki, for clarification. That findtarget looks only for enemies and npc, not traps and probably neither obstacles.)

- This method is compatible with any type of enemy that can jump, as it is more common than enemies that can dodge. Although it can obviously be adapted, using tossentity without vertical speed, only horizontal. Just add thinkscript data/scripts/avoidtraps.c and nothing else. You don't need to add ranges, or special animations. Which is exactly what I was looking for.

- I use different scripts for every default behavior. For example if the enemy is "aimove chase" or "subtype chase", i use AIMOVE1_CHASE then "thinkscript data/scripts/chase.c", but with normal enemies I use AIMOVE1_NORMAL and "thinkscript data/scripts/normal.c", the same code with only this difference. Same with wanderers, etc. Same thing with other parts of the code, to dodge instead of jump for example.


The content of the avoidtraps.c script (with chase as the default behavior):

C#:
void main()
{
    void self = getlocalvar("self");

    int  iMax = openborvariant("count_entities");    //Entity count.
    int  iEntity;                //Loop counter
    void vEntity;                //Target entity

    for(iEntity=0; iEntity<iMax; iEntity++)
    {
        vEntity = getentity(iEntity);    //entity from current loop.
        void isfire = getentityproperty(vEntity, "defaultname");

        if(isfire == "potfire" || isfire == "potfire2" || isfire == "potfire3") //IS THE ENTITY A FIRE?
        {
            void anim = getentityproperty(self, "animationID"); //GET THE CURRENT ANIMATION
            int z1 = getentityproperty(self, "z"); //ENEMY
            int z2 = getentityproperty(vEntity, "z"); //NPC TRAP
            int x1 = getentityproperty(self, "x"); //ENEMY
            int x2 = getentityproperty(vEntity, "x"); //NPC TRAP
            int rangeZBelow = (z1 - z2);
            int rangeZAbove = (z2 - z1);
            int rangeX = (x1 - x2);

            setlocalvar("thereisfire", 1); //THERE IS ANY FIRE
 
            //CHECK IF THE ENEMY IS IN THE WALK OR RUN ANIMATION
            if(anim == openborconstant("ANI_WALK") || anim == openborconstant("ANI_RUN"))
            {
                //FIRE TRAP IS AT THE TOP OF THE Z BOUNDARY AND ENEMY IS BELOW (LESS THAN 35 PIXELS ON Z, LESS THAN 100 ON X)
                if(z1 > z2 && rangeZBelow < 35 && (-100 < rangeX < 100)){ //CHECK RANGE
                    changeentityproperty(self, "animation", openborconstant("ANI_JUMP")); //JUMP BELOW
                    tossentity(self, 2, 0, 2);
                }
                //FIRE TRAP IS AT THE TOP OF THE Z BOUNDARY AND ENEMY IS BELOW (BETWEEN 35 AND 60 PIXELS ON Z, LESS THAN 120 ON X)
                else if(z1 > z2 && (35 < rangeZBelow < 60) && (-120 < rangeX < 120)){ //CHECK RANGE
                    changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID")); //AVOID BEHAVIOR
                }
                //FIRE TRAP IS AT THE BOTTOM Z BOUNDARY AND ENEMY IS ABOVE (LESS THAN 35 PIXELS ON Z, LESS THAN 100 ON X)
                else if(z2 > z1 && rangeZAbove < 35 && (-100 < rangeX < 100)){ //CHECK RANGE
                    changeentityproperty(self, "animation", openborconstant("ANI_JUMP")); //JUMP ABOVE
                    tossentity(self, 2, 0, -2);
                }
                //FIRE TRAP IS AT THE BOTTOM Z BOUNDARY AND ENEMY IS ABOVE (BETWEEN 35 AND 60 PIXELS ON Z, LESS THAN 120 ON X)
                else if(z2 > z1 && (35 < rangeZAbove < 60) && (-120 < rangeX < 120)){ //CHECK RANGE
                    changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID")); //AVOID BEHAVIOR
                }
                //FIRE TRAP IS FURTHER THAN 60 PIXELS ON Z, OR 120 ON X
                else if(rangeZBelow > 60 || rangeZAbove > 60 || rangeX > 120 || rangeX < -120) //CHECK RANGE
                {
                    changeentityproperty(self, "aimove", openborconstant("AIMOVE1_NORMAL")); //NORMAL BEHAVIOR
                }
            }
        }
    }
    if (getlocalvar("thereisfire") != 1){
        changeentityproperty(self, "aimove", openborconstant("AIMOVE1_CHASE")); //DEFAULT BEHAVIOR IF THERE IS NO FIRES ACTIVE
    }
    else
    {
        setlocalvar("thereisfire", 0); //RESET FIRE COUNT
    }
}

Now, a question... Is this method too demanding, or is it fine as is? There can be up to 20 enemies on screen, and 3 or 4 fires simultaneously.

If anyone knows a more efficient method of doing the same thing, or I have made a mistake in any of the X/Z dimensions, I would appreciate corrections. I have tried to leave everything explained in a similar way to what Kratus did

EDIT:
How enemies react when the fire dissapear, It looks fast and fluid too.
 
Last edited:
int iMax = openborvariant("ent_max"); //Entity count.
Hey buddy, I saw a point which could be optimized on your code:
Avoid using "ent_max", because this is a permanent and big number (5000, if I am not mistaken), so your code is making the entity to count 5000 times to execute this code.
Instead, use "count_entities", as it counts how many entities are active. So if there are just 10 entities active, the engine will count only 10 times and there is it.

Findtarget doesn't work with traps, even if they are hostile ( @O Ilusionista you could add it to the wiki, for clarification. That findtarget looks only for enemies and npc, not traps and probably neither obstacles.)
Also, I would need to dive into the source code but I think "trap" is not a valid value for "hostile", so the engine is just ignoring it maybe
 
Hey buddy, I saw a point which could be optimized on your code:
Avoid using "ent_max", because this is a permanent and big number (5000, if I am not mistaken), so your code is making the entity to count 5000 times to execute this code.
Instead, use "count_entities", as it counts how many entities are active. So if there are just 10 entities active, the engine will count only 10 times and there is it.
I have changed just as you say and they behave exactly the same. If I didn't see any slowdown before, I imagine that 4990 less CPU cycles will be even better xD
I edit the code on the post too.
 
I have changed just as you say and they behave exactly the same. If I didn't see any slowdown before,
Because it have the same result, but using different methods.
And the engine is pretty much optmized, so for you to feel some degree of slowdown, you need to have something really heavy (like an optimized updated script or really big images using).

But I've learned something with @DCurrent : always thinks on the bigger picture. You may not having issues with few entities now, but what happens when you decide to use something like 10, 20? So its better to always optimize your code.

And it's not something related to you only: some people had added stuff on the engine source code which never cared about this, and what they added kida bloats the source code a lot for little reward (boomerang is on example). This is why the 4.0 is way more optimized now.
 
Last edited:
I got it! I have some clarifications to make, but I think I achieved the perfect behavior.

- There is no difference between treating traps as fake npc or fake player. Enemies act the same with hostile NPCs as they do with players. ( @oldyz this detail and the code below may interests you)

- Therefore, traps need not be anything other than real traps. So they are left with "type trap". So, the enemies now only watch to the players, ignoring the traps, and avoiding the unpleasant flipping effect, and the fact that they seem to be constantly attracted and repelled by the fire.

- Findtarget doesn't work with traps, even if they are hostile ( @O Ilusionista you could add it to the wiki, for clarification. That findtarget looks only for enemies and npc, not traps and probably neither obstacles.)

- This method is compatible with any type of enemy that can jump, as it is more common than enemies that can dodge. Although it can obviously be adapted, using tossentity without vertical speed, only horizontal. Just add thinkscript data/scripts/avoidtraps.c and nothing else. You don't need to add ranges, or special animations. Which is exactly what I was looking for.

- I use different scripts for every default behavior. For example if the enemy is "aimove chase" or "subtype chase", i use AIMOVE1_CHASE then "thinkscript data/scripts/chase.c", but with normal enemies I use AIMOVE1_NORMAL and "thinkscript data/scripts/normal.c", the same code with only this difference. Same with wanderers, etc. Same thing with other parts of the code, to dodge instead of jump for example.


The content of the avoidtraps.c script (with chase as the default behavior):

C#:
void main()
{
    void self = getlocalvar("self");

    int  iMax = openborvariant("count_entities");    //Entity count.
    int  iEntity;                //Loop counter
    void vEntity;                //Target entity

    for(iEntity=0; iEntity<iMax; iEntity++)
    {
        vEntity = getentity(iEntity);    //entity from current loop.
        void isfire = getentityproperty(vEntity, "defaultname");

        if(isfire == "potfire" || isfire == "potfire2" || isfire == "potfire3") //IS THE ENTITY A FIRE?
        {
            void anim = getentityproperty(self, "animationID"); //GET THE CURRENT ANIMATION
            int z1 = getentityproperty(self, "z"); //ENEMY
            int z2 = getentityproperty(vEntity, "z"); //NPC TRAP
            int x1 = getentityproperty(self, "x"); //ENEMY
            int x2 = getentityproperty(vEntity, "x"); //NPC TRAP
            int rangeZBelow = (z1 - z2);
            int rangeZAbove = (z2 - z1);
            int rangeX = (x1 - x2);

            setlocalvar("thereisfire", 1); //THERE IS ANY FIRE
 
            //CHECK IF THE ENEMY IS IN THE WALK OR RUN ANIMATION
            if(anim == openborconstant("ANI_WALK") || anim == openborconstant("ANI_RUN"))
            {
                //FIRE TRAP IS AT THE TOP OF THE Z BOUNDARY AND ENEMY IS BELOW (LESS THAN 35 PIXELS ON Z, LESS THAN 100 ON X)
                if(z1 > z2 && rangeZBelow < 35 && (-100 < rangeX < 100)){ //CHECK RANGE
                    changeentityproperty(self, "animation", openborconstant("ANI_JUMP")); //JUMP BELOW
                    tossentity(self, 2, 0, 2);
                }
                //FIRE TRAP IS AT THE TOP OF THE Z BOUNDARY AND ENEMY IS BELOW (BETWEEN 35 AND 60 PIXELS ON Z, LESS THAN 120 ON X)
                else if(z1 > z2 && 35 < rangeZBelow < 60 && (-120 < rangeX < 150)){ //CHECK RANGE
                    changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID")); //AVOID BEHAVIOR
                }
                //FIRE TRAP IS AT THE BOTTOM Z BOUNDARY AND ENEMY IS ABOVE (LESS THAN 35 PIXELS ON Z, LESS THAN 100 ON X)
                else if(z2 > z1 && rangeZAbove < 35 && (-100 < rangeX < 100)){ //CHECK RANGE
                    changeentityproperty(self, "animation", openborconstant("ANI_JUMP")); //JUMP ABOVE
                    tossentity(self, 2, 0, -2);
                }
                //FIRE TRAP IS AT THE BOTTOM Z BOUNDARY AND ENEMY IS ABOVE (BETWEEN 35 AND 60 PIXELS ON Z, LESS THAN 120 ON X)
                else if(z2 > z1 && (35 < rangeZAbove < 60) && (-120 < rangeX < 120)){ //CHECK RANGE
                    changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID")); //AVOID BEHAVIOR
                }
                //FIRE TRAP IS FURTHER THAN 60 PIXELS ON Z, OR 120 ON X
                else if(rangeZBelow > 60 || rangeZAbove > 60 || rangeX > 120 || rangeX < -120) //CHECK RANGE
                {
                    changeentityproperty(self, "aimove", openborconstant("AIMOVE1_NORMAL")); //NORMAL BEHAVIOR
                }
            }
        }
    }
    if (getlocalvar("thereisfire") != 1){
        changeentityproperty(self, "aimove", openborconstant("AIMOVE1_CHASE")); //DEFAULT BEHAVIOR IF THERE IS NO FIRES ACTIVE
    }
    else
    {
        setlocalvar("thereisfire", 0); //RESET FIRE COUNT
    }
}

Now, a question... Is this method too demanding, or is it fine as is? There can be up to 20 enemies on screen, and 3 or 4 fires simultaneously.

If anyone knows a more efficient method of doing the same thing, or I have made a mistake in any of the X/Z dimensions, I would appreciate corrections. I have tried to leave everything explained in a similar way to what Kratus did

EDIT:
How enemies react when the fire dissapear, It looks fast and fluid too.
Entity enumeration is a good idea, great job :) According to what I understand, enemies are avoiding players and not traps, right? But the "avoid" behaviour in general works fine for this kind of situation, and when fire is gone the "chase" behaviour will be activated.

So, if players are at the top of the Z boundary, enemies will avoid moving to the bottom, but if players are at the bottom, enemies will avoid to the top and , however, before touching the fire trap they will jump. IMO is perfect this way, I don't see any more issues at the moment.

About the flipping this is the default engine behaviour looking for the nearest hostile target, it happens with my CPU Partners in the SORX too. @DCurrent maybe a simple update adding some delay during the target's detection can solve the problem, or maybe some kind of "target lock" feature could work too.

Now, a question... Is this method too demanding, or is it fine as is?
It's fine as it is, and I agree with @O Ilusionista about the "count_entities" replacement. Other than that this code is small and light, I don't see any problem.

Also, I would need to dive into the source code but I think "trap" is not a valid value for "hostile", so the engine is just ignoring it maybe
Yes buddy, you´re right. I looked at the engine source and "type_trap" is not present in the hostile list in both new/legacy versions.

1702928379143.png
 
Back
Top Bottom