Skip to content

Custom Encounter creatures takes too long to attack

I'm working on a custom encounter system and I could use some tips. I've noticed that when using the default encounter system, the enemies start to attack the PC right away, and when I do spawn those, it take some time (one turn maybe?) for them to attack.

At first I just dropped a
AssignCommand(creature, DetermineCombatRound(pc));

And at first it worked. However, it seems that even if my pc is invisible the creatures will jump on him instantly.

Then I tried
    if(GetObjectSeen(pc, creature)) {
        AssignCommand(creature, DetermineCombatRound(pc));
    }

But then it seems that the pc is not seen. Can I somehow update the perception table from my just spawn creatures prior calling this? Or is there a better way to achieve this?

Comments

  • fot1fot1 Member Posts: 74
    edited September 2020
    Closest I could get is the following:
    if(!InvisibleTrue(pc)) {
            AssignCommand(creature, DetermineCombatRound(pc));
    }
    

    Since
    InvisibleTrue
    
    also acount for stealth. But of course is not ideal
  • TerrorbleTerrorble Member Posts: 179
    Does using this instead of assigning DetermineCombatRound() work?
    AssignCommand(creature,ActionAttack(pc));
    


    I think assigning DetermineCombatRound() isn't any different than when they just spawn. (i.e. the default scripts do their thing and call DetermineCombatRound() based on things like if they see an enemy, or an enemy vanished, or they are attacked/spell cast at, etc)
  • ProlericProleric Member Posts: 1,316
    That's not quite right, either, because you may not want spellcasters to launch physical attacks.

    Try
    AssignCommand(creature, DetermineCombatRound() );
    
    i.e. omitting the PC. This lets the creature select its own target. If that doesn't work in your particular script, a 1 second delay may help, to give time for OnPerception to fire.
  • fot1fot1 Member Posts: 74
    edited September 2020
    Thanks for the suggestions!
    ActionCommand indeed make wizards to run to attack the PC, which is terrible decision making with their d4 life :-)

    I was really hopeful that DetermineCombatRound without parameters would work, but unfortunately it doesn't. I also tried to add some delay, but even after 1.5 secs there is no response.

    I've improved my code a little bit in the meantime, making it to target the PC only if the spawn is nearby and the PC is not invisible, which is not ideal yet, but for the time being, it will have to do:
    if(GetDistanceBetween(creature, pc) > 25.0) return;
    if(InvisibleTrue(pc)) return;
    
    AssignCommand(creature, DetermineCombatRound(pc));
    

    But essentially I would love a function `UpdatePerceptionTable` or something on those lines, which I could call immediately before the DetermineCombatRound without parameters.
  • ProlericProleric Member Posts: 1,316
    It's hard to comment much further without seeing your scripts, to understand what you're doing differently from the built-in encounters. It does seem like perception isn't happening quickly enough, or maybe there's something on heartbeat which ought to be on spawn?

    I have found that changing faction to hostile, immediately followed by DetermineCombatRound() with no target, causes members of a neutral faction to attack immediately, but I don't use the encounter system much.
  • fot1fot1 Member Posts: 74
    edited October 2020
    I've tested with some encounter systems from the vault but I noticed every encounter system seems to have the same problem. The default encounters actually make the creatures to react faster.

    Here is almost the full code. The idea is quite simple actually, it uses a custom trigger `on_enter`, which calls `encounterSpawn`. And when creatures die, in the default7 script, I call `decrementLivingCreatures`.

    The code here does not contains the code in which select which creatures will spawn (enc_inc_npcs, through the function call selectNPCs) and the code on where those will spawn (enc_inc_location, through the function call selectLocation), since those portions of the code are the biggest and are actually in other file, it shouldn't make much difference, since those return only a string with the resrefs, and a location to the creatures be spawn in the code presented here.

    Ah, the SL functions are String List functions, that is a list of strings serialized in only one string. It provides methods to access the nth element (SL_at), the length of the list, and etc. Also please consider that the `inc_log` functions are either `PrintString` or `timestampedPrintStringWithLongAndHardToRememberName`.

    #include "inc_list"
    #include "inc_log"
    #include "enc_inc_npcs"
    #include "enc_inc_location"
    
    #include "NW_I0_GENERIC"
    
    
    // This should be set to overwrite the default encounter cooldown
    const string ENC_COOLDOWN = "ENC_COOLDOWN";
    // Set a global default cooldown for encounters
    const int DEFAULT_COOLDOWN = 300;
    
    const string ENC_ACTIVE = "ENC_ACTIVE";
    const string ENC_ONDEATH = "ENC_ONDEATH";
    const string ENC_UUID = "ENC_UUID";
    const string ENC_ON_COOLDOWN = "ENC_ON_COOLDOWN";
    const string ENC_LIVING_CREATURES = "ENC_LIVING_CREATURES";
    
    
    int encounterCanSpawn(object encounter) {
        int active = GetLocalInt(encounter, ENC_ACTIVE);
        int onCooldown = GetLocalInt(encounter, ENC_ON_COOLDOWN);
        debug("[ENCSPAWN] active: " + IntToString(active) + ", cooldown: " + IntToString(onCooldown));
    
        return !active && !onCooldown;
    }
    
    void disableCooldown(object encounter) {
        debug("[ENCSPAWN] Setting cooldown to false");
        SetLocalInt(encounter, ENC_ON_COOLDOWN, FALSE);
    }
    
    void encounterFinished(object encounter) {
        int cooldown = GetLocalInt(encounter, ENC_COOLDOWN);
        cooldown = cooldown == 0 ? DEFAULT_COOLDOWN : cooldown;
        debug("[ENCSPAWN] Encounter finished. Cooldown set to "+ IntToString(cooldown));
    
        SetLocalInt(encounter, ENC_ON_COOLDOWN, TRUE);
        SetLocalInt(encounter, ENC_ACTIVE, FALSE);
    
        AssignCommand(encounter, DelayCommand(IntToFloat(cooldown), disableCooldown(encounter)));
    }
    
    void setCreatureEnvents(object creature, object encounter) {
        SetLocalString(creature, ENC_UUID, GetObjectUUID(encounter));
        SetLocalString(creature, ENC_ONDEATH, "enc_ondeath");
    }
    
    void incrementLivingCreatures(object encounter) {
        int livingCreatures = GetLocalInt(encounter, ENC_LIVING_CREATURES);
        livingCreatures += 1;
        SetLocalInt(encounter, ENC_LIVING_CREATURES, livingCreatures);
    
        debug("Living creatures: " + IntToString(livingCreatures));
    }
    
    void decrementLivingCreatures(object encounter) {
        int livingCreatures = GetLocalInt(encounter, ENC_LIVING_CREATURES);
        livingCreatures -= 1;
        SetLocalInt(encounter, ENC_LIVING_CREATURES, livingCreatures);
    
        debug("Living creatures: " + IntToString(livingCreatures));
        if(livingCreatures <= 0) encounterFinished(encounter);
    }
    
    
    void encounterOnDeath(object creature) {
        string encounterUUID = GetLocalString(creature, ENC_UUID);
        object encounter = GetObjectByUUID(encounterUUID);
        decrementLivingCreatures(encounter);
    }
    
    void creatureReact(object creature, object pc) {
        if(GetDistanceBetween(creature, pc) > 25.0) return;
        if(InvisibleTrue(pc)) return;
    
        AssignCommand(creature, DetermineCombatRound(pc));
    }
    
    
    void encounterSpawn(object encounter, object pc) {
        SetLocalInt(encounter, ENC_ACTIVE, TRUE);
    
        string npcsResrefs = selectNPCs(encounter);
        location loc = selectLocation(encounter, pc);
    
        int i;
        for(i = 0; i < SL_size(npcsResrefs); i++) {
            string resref = SL_at(npcsResrefs, i);
            object creature = CreateObject(OBJECT_TYPE_CREATURE, resref, loc);
            setCreatureEnvents(creature, encounter);
            creatureReact(creature, pc);
    
            incrementLivingCreatures(encounter);
        }
    }
    
  • ProlericProleric Member Posts: 1,316
    edited October 2020
    Looking at that, the key question is how fast hostile creatures react after CreateObject().

    Looking at similar code in my own module, spawning visuals take around 2 seconds, after which attack occurs in 3-4 seconds. This concurs with the Lexicon comments about the OnPerception event:
    When a creature spawns it appears they only get 1 event fired about 4 seconds after spawning (no AI except OnSpawn is run before this time, no heartbeats or anything)

    Personally, I can live with that delay, as it gives the player a moment to react.

    If you're finding that the official encounter system reacts faster, perhaps it's an engine feature that can't be reproduced with scripting?

    Probably best to ask at the Vault forum as others know a lot more than I do.
Sign In or Register to comment.