• All, Gmail is currently rejecting messages from my host. I have a ticket in process, but it may take some time to resolve. Until further notice, do NOT use Gmail for your accounts. You will be unable to receive confirmations and two factor messages to login.

How to make enemies avoid traps?

Toranks

Active member
Whether fire, spikes, etc. I imagine that someone has already thought of a way, but I can't think about one with the options I have in the manual right now.
 
Whether fire, spikes, etc. I imagine that someone has already thought of a way, but I can't think about one with the options I have in the manual right now.

There's no native trap avoidance, so you have to do it yourself.

One really cheap hack would be to put a small hole under it. The AI would avoid it (or try to jump over) and presumably the trap is going to hit players before they could fall in.

Other than that, I haven't worked anything out in my head that wouldn’t get pretty complex with way pointing.

Tagging @O Ilusionista, and @Kratus. They have really rich stages and have probably made fixes you can use.

DC
 
Whether fire, spikes, etc. I imagine that someone has already thought of a way, but I can't think about one with the options I have in the manual right now.
I have some scripts used for enemies avoidance but not directly related to traps. At first sight I think that you can use the findtarget function inside the thinkscript for enemies and then changing their aimove to avoid below a defined range compared with the trap's position. Above a this limit the aimove returns to normal.
And don't forget to make enemies hostile to the traps, otherwise the findtarget will not work. You can use the own animation's range properties for the findtarget function, I suggest to points to the walk animation in the animnum.
The thinkscript works like the update but less intensive, a good place to put such features.

1702614472130.png

Here's some aimove examples:
C:
changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID"));
changeentityproperty(self, "aimove", openborconstant("AIMOVE1_CHASE"));
changeentityproperty(self, "aimove", openborconstant("AIMOVE1_NORMAL"));

Here's the findtarget usage example:
C:
void target = findtarget(self, openborconstant("ANI_WALK"));

Then it will check if the target is in range according to the defined animation:
C:
anim walk
    range    0 485
    rangea    -999 999
    rangez    -999 999
    loop    1
    delay    16
    offset    62 125
    bbox    50 55 23 74
    @cmd clearL
    frame    data/chars/heroes/axel/idle00.png
    frame    data/chars/heroes/axel/idle01.png
    frame    data/chars/heroes/axel/idle02.png
    frame    data/chars/heroes/axel/idle03.png
    frame    data/chars/heroes/axel/idle00.png
    frame    data/chars/heroes/axel/idle01.png
    frame    data/chars/heroes/axel/idle02.png
    frame    data/chars/heroes/axel/idle03.png
 
Whether fire, spikes, etc. I imagine that someone has already thought of a way, but I can't think about one with the options I have in the manual right now.

Detecting traps might be straightforward but there are couple issues if enemies do detect a trap:
1. What would they do after they found a trap? go back? alter their path? what happens if another trap is found while doing that?
2. What is their priority level if player is nearby a trap? attack despite the trap? stay still?

I was going to suggest using ondoattackscript to cancel damage from traps while changing animation to a dodge trap animation but this might only work against certain traps. BTW the former means enemies don't try to avoid traps at all but instead react to traps.
 
I was going to suggest using ondoattackscript to cancel damage from traps while changing animation to a dodge trap animation but this might only work against certain traps.
This is what I used - ondoattackscript on a certain enemy, against a certain attack type (attack17).

This is the code @DCurrent helped me back in the day:
C-like:
void main()
{
    void self;      // Entity running event
    void other;     // Entity attacking or receiving vs. self.
    int which;      // Attacker or defender event?
    int hit_by_id;  // Entity ID of entity attack hit.
    int attack_id;  // Entity ID of entity that performed attack.
    int type;


    type    = getlocalvar("attacktype");// Get attack type
    self    = getlocalvar("self");
    other    = getlocalvar("other");
    which    = getlocalvar("which");
    
    // Is this a defending event?
    if(which == 0)//openborconstant("EXCHANGE_RECIPIANT")
    {
        // IDs are not the same? This is how we prevent repeat
        // hits from the same attack.
        hit_by_id   = getentityproperty(self, "hitbyid");
        attack_id   = getlocalvar("attackid");
    
        // Different attack ID?
        if(hit_by_id != attack_id)
        {
            // Is this the attack type we want? If so,
            // set lasthit 0 (engine ignores attack hit).
            if(type == openborconstant("ATK_NORMAL17"))
            {
            changeopenborvariant("lasthitc", 0);
            }           
        }               
    }
  
        // Set hit id on self to attack ID for the next cycle.
    changeentityproperty(self, "hitbyid", hit_by_id);
}

@bWWd Just keep in mind that were some changes on how the flags works after some builds (I can't remember which one), so the code will work on the opposite way (it won't trigger and when it should and vice versa). At least until version 6315, this was working fine.
 
Its weird, since we where talking about Factions a few days back,

i was precisely thinking about an entitity subtype propertty that is the complete opposite to "Follow", and and another interaction property completely opposite to "Hostile"

could they make it in the next Engine ?

example, say a boss character gets furious, suddenly this furius model changes the faction of the boss to "beast" factions, his underlings that where Grunt faction, are programmed to be "Afraid" of the any "beast faction characters" & now, and they avoid the boss like the plague.

I wonder, just like @Bloodbane did by creating factions before the new engine, shouldn't there be a way to take the Engine's regular function (in this case "Hostile") and use script to completely revert it for opposite results?

this could be interesting, and templates to "revert" normal functions hopefully could be added to the manual

the solution would then be that traps are a faction of sorts, and some enemies are made to be "negative-hostile to them"
 
Detecting traps might be straightforward but there are couple issues if enemies do detect a trap:
1. What would they do after they found a trap? go back? alter their path? what happens if another trap is found while doing that?
2. What is their priority level if player is nearby a trap? attack despite the trap? stay still?

I was going to suggest using ondoattackscript to cancel damage from traps while changing animation to a dodge trap animation but this might only work against certain traps. BTW the former means enemies don't try to avoid traps at all but instead react to traps.

1 > Preferably alter the path, but I don't mind if they constantly back out. Traps have a limited time.
2 > I think the priority would be to avoid the trap

The solutions all of you propose require incorporating this behavior into all the enemies in the game, which are a large number. I think I'll wait for the version with factions to be released, which will make things a lot easier, to deal with this little problem.
 
The solutions all of you propose require incorporating this behavior into all the enemies in the game
@Toranks

All the solutions based on script events (ondoattack/thinkscript) require just one line in every enemy in order to call the same script, no need to copy/paste the entire code for all enemies. Here's an example of how to call the thinkscript, the same goes for the ondoattack.

And for the enemies that you don't want the script running, just don't declare it at the header.

1702688076856.png

EDIT: @DCurrent @O Ilusionista I didn't use the hitbyid before, it's interesting. What this property does exactly? And what's the difference compared with the attackid localvar?
 
think I'll wait for the version with factions to be released, which will make things a lot easier, to deal with this little problem.
that in an of itself helps , but does not solve the problem

i mentioned factions , becasue its almost like an additional function the engine does not have , that Bloodbane managed to implement
maybe not as feature rich as the next engine's, but functional.

would hope it to be a similar situation, so we get the equivalent of 6391 "factions" with complimentary things like "fearful to" and "avoid" with script alone...

anyway i would calls such 6391 scripts "Antifollow" and "Antihostile" and they would likely work using onthink or onmove, these scripts would probably require for your entity to be declared "Hostile to trap" so it can work its Anti-magick
(now that opens up another can of worms - a "friendly to" functions that has NPCs actually helping you by picking up items and dropping them for you, healing you, Getting you out of Dizzy or faint animation)

i digress...
if such functions can only be added to a new iteration of the engine, crossing fingers they can be considered
 
In the end I came up with a seemingly brilliant idea in my head, inspired by the method @Kratus suggests, and I ran into an insurmountable problem.
findtarget does not detect traps, even if the enemy is hostile to traps!
I tried changing the traps to npc and the fire started moving gracefully, as if they were fire elementals, and the script finally worked.
Any alternative way to resolve this? Or can I convert the fire entities into immobile npc without alteration to the other properties?
 
started moving gracefully
You can easily turns any npc into static entities using the "nomove" command, same as I'm doing with many decorations.
You can see an example below.

C:
name            Box_Pieces
type            npc
subject_to_wall    0
subject_to_minz    0
subject_to_maxz    0
health            1
lifespan        0.5
nomove            1 1
noquake            1 0
death            1
stealth            2
shadow            0
bounce             1
antigravity        -10
onmoveascript    data/scripts/updateentity/pieces.c

anim idle
    bouncefactor 3
    loop    0
    delay    2
    offset    100 162
    frame    data/chars/obstacles/box13.png

1702767046065.png

findtarget does not detect traps, even if the enemy is hostile to traps
I will check it, honestly I never tried to make any entity hostile to type "trap".
 
Another example of static npc acting as a trap is the SOR1 squeezer, plus having the benefit of all the attack/hostility behaviour in case it's necessary.


C:
name                St6_Squeezer
type                npc
subtype                notgrab
hostile                player npc
candamage            player enemy npc obstacle
nolife                1
nomove                1 1
nodrop                2
shadow                0
stealth                2
offscreenkill        6000
 
The guy

C++:
name          dan
type          enemy
aimove       chase
candamage     player npc
hostile       player npc
[...]
thinkscript data/scripts/traps/chase.c
[...]
anim    walk
    range    0 300
    rangea  -200 200
    rangez    -200 200

The script
C#:
void main()
{
    void self = getlocalvar("self");
    void trap = findtarget(self, openborconstant("ANI_WALK"));
    void isfire = getentityproperty(trap, "model");
 
    if(isfire == "potfire" || isfire == "potfire2" || isfire == "potfire3")
    {
        changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID"));
    }
    else
    {
        changeentityproperty(self, "aimove", openborconstant("AIMOVE1_CHASE"));      
    }
}

The fire
C#:
name     potfire
type     npc
lifespan    120
nolife   1
nomove    1 1
alpha     1
hostile                player enemy npc
candamage            player enemy npc


The result:


This erratic behavior is not entirely satisfactory, nor does it completely prevent them from burning. It requires a better review, although at the moment it's better than before, since they usually walk into the fire like fools as soon as I stand next to (or between) the fire.
 
The guy

C++:
name          dan
type          enemy
aimove       chase
candamage     player npc
hostile       player npc
[...]
thinkscript data/scripts/traps/chase.c
[...]
anim    walk
    range    0 300
    rangea  -200 200
    rangez    -200 200

The script
C#:
void main()
{
    void self = getlocalvar("self");
    void trap = findtarget(self, openborconstant("ANI_WALK"));
    void isfire = getentityproperty(trap, "model");
 
    if(isfire == "potfire" || isfire == "potfire2" || isfire == "potfire3")
    {
        changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID"));
    }
    else
    {
        changeentityproperty(self, "aimove", openborconstant("AIMOVE1_CHASE"));     
    }
}

The fire
C#:
name     potfire
type     npc
lifespan    120
nolife   1
nomove    1 1
alpha     1
hostile                player enemy npc
candamage            player enemy npc


The result:


This erratic behavior is not entirely satisfactory, nor does it completely prevent them from burning. It requires a better review, although at the moment it's better than before, since they usually walk into the fire like fools as soon as I stand next to (or between) the fire.
Really looks weird like if they are not avoiding properly, try replacing "model" by "defaultname", this is the format I'm currently using.
Using the thinkscript you can make many granular adjustments because you are free to code anything related to enemies reaction. As an example, you can check Z position related to the fire and then perform a dodge move.

C:
void main()
{
    void self = getlocalvar("self");
    void trap = findtarget(self, openborconstant("ANI_WALK"));
    void isfire = getentityproperty(trap, "defaultname");
 
    if(isfire == "potfire" || isfire == "potfire2" || isfire == "potfire3")
    {
        changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID"));
    }
    else
    {
        changeentityproperty(self, "aimove", openborconstant("AIMOVE1_CHASE"));      
    }
}
 
@Kratus what is the advantage of having a trap as an npc?
It depends on the usage. As an example, the squeezer I mentioned in the SOR1 route stage 6 needs a more complex behaviour against players, attacking when they approach, but the traps in the SOR3 route stage 8 (the forest) are simple type trap entities that react when touched.

@Toranks You can also try other avoid options like avoidx/avoidz.

1702769971707.png

And here I coded a quick example of how checking range and applying a evasive move manually.
C:
void main()
{
    void self = getlocalvar("self");
    void trap = findtarget(self, openborconstant("ANI_WALK"));
    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 limit = 50; //DISTANCE LIMIT BETWEEN BOTH
        int range = (z1 - z2 < limit);
      
        if(range && anim != openborconstant("ANI_DODGE")){ //CHECK RANGE AND PREVENT TO REPEATING THE DODGE ANIMATION
            changeentityproperty(self, "velocity", NULL(), 2, NULL()); //APPLY Z VELOCITY
            changeentityproperty(self, "animation", openborconstant("ANI_DODGE")); //CHANGE ANIMATION
        }
    }
    else
    {
        changeentityproperty(self, "aimove", openborconstant("AIMOVE1_CHASE"));   
    }
}
 
Last edited:
@Toranks Another idea is creating platforms or walls to prevent enemies from getting damage from traps but allowing players to pass through by changing the subject_to_wall/platform specifically on these levels. Is similar to DC's idea but in this case players can't fall in the holes accidentally.
Don't forget to set the wall/platform bigger than the fire's attack box to maintain enemies in a safe place.
 
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.

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 x1 = getentityproperty(self, "x"); //ENEMY
        int x2 = getentityproperty(trap, "x"); //NPC TRAP
        int range = (z1 - z2);
        int rangez = (-60 < (z1 - z2) < 60);
        int rangex = (-120 < (x1 - x2) < 120);
      
        if(range > 0 && range < 45 && anim != openborconstant("ANI_JUMP")){ //CHECK RANGE AND PREVENT TO REPEATING THE DODGE ANIMATION
            changeentityproperty(self, "animation", openborconstant("ANI_JUMP")); //CHANGE ANIMATION
            tossentity(self, 2, 0, 2);
        }
        if(range < 0 && range > -45 && anim != openborconstant("ANI_JUMP")){ //CHECK RANGE AND PREVENT TO REPEATING THE DODGE ANIMATION
            changeentityproperty(self, "animation", openborconstant("ANI_JUMP")); //CHANGE ANIMATION
            tossentity(self, 2, 0, -2);
        }
        else if (rangez && rangex) {
            changeentityproperty(self, "aimove", openborconstant("AIMOVE1_AVOID")); 
        }
        else
        {
            changeentityproperty(self, "aimove", openborconstant("AIMOVE1_NORMAL"));   
        }
    }
    else
    {
        changeentityproperty(self, "aimove", openborconstant("AIMOVE1_CHASE"));   
    }
}

The idea is:
- Jump or dodge if you are very very close.
- Avoid if you are close
- Acts normally if it detects fire, no matter the distance, to avoid rushing like crazy towards the PC if he is near a fire
- Chase in any other case


 
Back
Top Bottom