⚠️ Réaliser une attaque sournoise

Contenu

⚠️ Réaliser une attaque sournoise#

Snippet qui cherche à savoir si le personnage est en mesure de réussir une attaque sournoise et d’infliger des dégâts.
Dans le même temps, il cherche à gérer les sneak attacks.

IF
    ActionListEmpty()
    Global("BDAI_DISABLE_ATTACK", "LOCALS", 0)      //# Attacking is authorized
    !StateCheck(Myself, STATE_SLEEPING | STATE_HELPLESS | STATE_REALLY_DEAD)  //# ACTION actions are usable
    CheckStat(Myself, 0, CASTERHOLD)

    //# Dégâts bonus
    CheckStatGT(Myself, 1, BACKSTABDAMAGEMULTIPLIER)

    //# Arme compatible et utilisable par les voleurs
    !IsWeaponRanged(Myself)

    //# Contrainte d'invisibilité
    OR(4)
        StateCheck(Myself, STATE_INVISIBLE)
        CheckStat(Myself, 1, ASSASSINATE) //# BitCheck BIT0 and BIT1 instead
        CheckStat(Myself, 2, ASSASSINATE)
        False() //# Check 3E sneak attack

    //# Contrainte de position
    OR(4)
        Allegiance(Myself, NOTGOOD)
        CheckStat(Myself, 1, ASSASSINATE) //# BitCheck BIT0 and BIT2 instead
        CheckStat(Myself, 4, ASSASSINATE)
        False()  //# placement dans le dos nécessaire

    //# Recherche de cible…
THEN
IF
    //# …
    See([ANYONE]) //# TODO

    CheckStat(LastSeenBy(Myself), 0, IMMUNITY_TO_BACKSTAB)
    !Kit(LastSeenBy(Myself), BARBARIAN)
    CheckStatLT(LastSeenBy(Myself), 1, STONESKINS)
    CheckStatLT(LastSeenBy(Myself), 1, STONESKINSGOLEM)
    WeaponEffectiveVs(LastSeenBy(Myself), MAINHAND)
    WeaponCanDamage(LastSeenBy(Myself), MAINHAND)
THEN

La caractéristique IMMUNITY_TO_BACKSTAB protège contre les attaques sournoises et les sneak attacks.
Les personnages de kit BARBARIAN sont également immunisés.
On enlève aussi les personnages protégés de peaux de pierre ainsi que ceux qui ne peuvent être touchés par l’arme équipée.

Il s’agit des contraintes qui empêchent l’attaque de réussir.
D’autres contraintes moins fortes peuvent être ajoutées comme !StateCheck(LastSeenBy(Myself), STATE_MIRRORIMAGE).

IF
    Detect(Myself)
    ActionListEmpty()
    Global("BDAI_DISABLE_ATTACK", "LOCALS", 0)      //# Attacking is authorized
    !StateCheck(Myself, STATE_SLEEPING | STATE_HELPLESS | STATE_REALLY_DEAD)  //# ACTION actions are usable
    CheckStat(Myself, 0, CASTERHOLD)
    !CheckStat(Myself, 2, ENCUMBERANCE)             //# Can move

    //# Dégâts bonus
    CheckStatGT(Myself, 1, BACKSTABDAMAGEMULTIPLIER)

    //# Arme compatible et utilisable par les voleurs
    !IsWeaponRanged(Myself)

    //# Contrainte d'invisibilité
    OR(4)
        StateCheck(Myself, STATE_INVISIBLE)
        CheckStat(Myself, 1, ASSASSINATE) //# BitCheck BIT0 and BIT1 instead
        CheckStat(Myself, 2, ASSASSINATE)
        False() //# Check 3E sneak attack

    //# Absence de contrainte de position
    OR(3)
        Allegiance(Myself, NOTGOOD)
        CheckStat(Myself, 1, ASSASSINATE) //# BitCheck BIT0 and BIT2 instead
        CheckStat(Myself, 4, ASSASSINATE)

    //# Ciblage
    OR(7)
        !See(NearestEnemyOf(Myself))
        !CheckStat(LastSeenBy(Myself), 0, IMMUNITY_TO_BACKSTAB)
        Kit(LastSeenBy(Myself), BARBARIAN)
        !CheckStatLT(LastSeenBy(Myself), 1, STONESKINS)
        !CheckStatLT(LastSeenBy(Myself), 1, STONESKINSGOLEM)
        !WeaponEffectiveVs(LastSeenBy(Myself), MAINHAND)
        !WeaponCanDamage(LastSeenBy(Myself), MAINHAND)
    //# …

    Detect(Myself)
    False()
THEN
    RESPONSE #0
END

IF
    !Range(LastSeenBy(Myself), 0)
THEN
    RESPONSE #1
        AttackOneRound(LastSeenBy(Myself))
END

Dans ces conditions avantageuses, le personnage peut réussir une attaque sournoise ou une sneak attack sans avoir à se repositionner car l’angle d’attaque n’a aucun impact.

IF
    OR(2)
        ActionListEmpty()
        Trigger(32)
    Detect(Myself)
    Global("BDAI_DISABLE_ATTACK", "LOCALS", 0)
    !StateCheck(Myself, STATE_SLEEPING | STATE_HELPLESS | STATE_REALLY_DEAD)  //# ACTION actions are usable
    CheckStat(Myself, 0, CASTERHOLD)
    !CheckStat(Myself, 2, ENCUMBERANCE)

    //# Dégâts bonus
    CheckStatGT(Myself, 1, BACKSTABDAMAGEMULTIPLIER)

    //# Arme compatible et utilisable par les voleurs
    !IsWeaponRanged(Myself)

    //# Contrainte d'invisibilité
    OR(4)
        StateCheck(Myself, STATE_INVISIBLE)
        CheckStat(Myself, 1, ASSASSINATE) //# BitCheck BIT0 and BIT1 instead
        CheckStat(Myself, 2, ASSASSINATE)
        False() //# Check 3E sneak attack

    //# Ciblage
    OR(7)
        !See(NearestEnemyOf(Myself))
        !CheckStat(LastSeenBy(Myself), 0, IMMUNITY_TO_BACKSTAB)
        Kit(LastSeenBy(Myself), BARBARIAN)
        !CheckStatLT(LastSeenBy(Myself), 1, STONESKINS)
        !CheckStatLT(LastSeenBy(Myself), 1, STONESKINSGOLEM)
        !WeaponEffectiveVs(LastSeenBy(Myself), MAINHAND)
        !WeaponCanDamage(LastSeenBy(Myself), MAINHAND)
    //# …

    Detect(Myself)
    False()
THEN
    RESPONSE #0
END

//# Gestion du cas où le positionnement n'est pas requis
IF
    ActionListEmpty()
    !Range(LastSeenBy(Myself), 0)
    OR(3)
        Allegiance(Myself, NOTGOOD)
        CheckStat(Myself, 1, ASSASSINATE) //# BitCheck BIT0 and BIT2 instead
        CheckStat(Myself, 4, ASSASSINATE)
THEN
    RESPONSE #1
        AttackOneRound(LastSeenBy(Myself))
END

//# Gestion du cas où le positionnement est requis
IF
    ActionListEmpty()
    !Range(LastSeenBy(Myself), 0)
    !PersonalSpaceDistance(LastSeenBy(Myself), 3)
THEN
    RESPONSE #1
        MoveToObjectOffset(LastSeenBy(Myself), [-10.0]) //# on s'approche sans gêner la cible
        Continue()
    RESPONSE #1
        MoveToObjectOffset(LastSeenBy(Myself), [10.0])
        Continue()
    RESPONSE #1
        MoveToObjectOffset(LastSeenBy(Myself), [0.-10])
        Continue()
    RESPONSE #1
        MoveToObjectOffset(LastSeenBy(Myself), [0.10])
        Continue()
END

//# Moins contraignant, on rentrera forcément dedans après le Continue() du bloc précédent
//# On rentrera également dedans si la cible est à 3 pieds ou moins
IF
    ActionListEmpty()
    !Range(LastSeenBy(Myself), 0)
THEN
    RESPONSE #1
        SendTrigger(Myself, 32)
        FollowObjectFormation(LastSeenBy(Myself), 10, 1) //# position quiconce arrière
END

IF
    !ActionListEmpty()
    Trigger(32)
    !Range(LastSeenBy(Myself), 0)
    !PersonalSpaceDistance(LastSeenBy(Myself), 3)
THEN
    RESPONSE #1
        AttackOneRound(LastSeenBy(Myself))
END

En cas de positionnement, deux cycles de script sont requis. Le premier permet de se positionner, le second d’attaquer.
Le FollowObjectFormation permet de se positionner derrière le personnage mais l’action n’a pas de limite de durée.
La créature va donc « Follow » jusqu’au prochain cycle de script, attention car ActionListEmpty() vaudra faux.
Pendant ce second cycle, le trigger 32 permet de savoir que le personnage devrait être placé derrière sa cible. Nous attaquons la cible compatible la plus proche, sans certitude qu’il s’agisse de la même cible.
Le trigger peut être remplacé par une variable LOCALS.

Avertissement

Plusieurs paramètres sont perfectibles :

  • On ne peut assurer que l’arme actuelle soit compatible avec les attaques sournoises

  • On ne sait pas si les sneak attacks sont activées ou non (les contraintes ne sont pas les mêmes)

  • Seules les valeurs les plus simples de ASSASSINATE sont testées

Réflexion#

  • HasItemCategory : on pourrait autoriser certains types d’arme sans être certain que cette arme soit vraiment compatible pour les voleurs, et cela pourrait retirer toutes les armes des autres categorie qui pourraient être compatible. Enfin, ça ne gère pas les ajouts de catégorie pour les mods.

  • Avec Class(Myself, THIEF), on peut imaginer qu’il utilise des armes pour voleur mais trop limitant. Pas adapté avec la HLA « Use Any Item ».

  • La contrainte CheckStatGT(Myself, 1, BACKSTABDAMAGEMULTIPLIER) est nécessaire pour les attaques sournoises mais est aussi un trick qui améliore la gestion des sneak attacks. Car en général, les personnages ayant un bonus pour les attaques sournoises, en ont également un pour les sneak attacks.