Can I Use a Item?#
Il y a deux enjeux :
Savoir si l’objet est utilisable par le personnage
Savoir si le joueur souhaite que le personnage puisse utiliser l’objet
Une question simple permet de répondre à ces deux questions de manière efficace : l’objet est-il équipé ?
Si oui, on considère que :
Il est « normalement » utilisable par le personnage (puisqu’il a pu s’en équiper)
Le joueur ou le moddeur autorise son utilisation (sinon il fallait le laisser dans le sac)
Cela permet ainsi d’assurer au joueur de contraindre facilement le script :
Quelle potion/munition/arme/… sont autorisées ? Celles équipées
Lesquelles ne sont pas autorisées ? Celles dans le sac
Combien peut-il en utiliser ? Le nombre d’équipés
Ajouter/enlever des objets pendant le combat
Cerise sur le gâteau, cela permet aussi d’améliorer les performances du script en limitant le nombre de slots à checker.
Snippets de base#
Il s’agit d’une base qui peut/doit être adapté aux besoins.
IF
ActionListEmpty()
!GlobalTimerNotExpired("BD_Cast", "LOCALS") //# BD_Cast is expired
Global("BDAI_DISABLE_ITEMS", "LOCALS", 0) //# Using items is authorized
!StateCheck(Myself, STATE_SLEEPING | STATE_HELPLESS | STATE_REALLY_DEAD) //# ACTION actions are usable
CheckStat(Myself, 0, CASTERHOLD)
HasItemEquiped("ITEM_FILENAME", Myself) //# ITEM_FILENAME is equiped
//# TODO: Code spécifique
THEN
REPONSE #1
UseItem("ITEM_FILENAME", Myself)
SetGlobalTimer("BD_Cast", "LOCALS", ONE_MINUTE)
END
On vérifie que :
un round s’est déroulé depuis la dernière action
le personnage puisse utiliser des objets
l’objet est équipé
l’action UseItem puisse s’exécuter
le personnage n’est pas en train de réaliser une action
IF
ActionListEmpty()
!GlobalTimerNotExpired("BD_Cast", "LOCALS") //# BD_Cast is expired
Global("BDAI_DISABLE_ITEMS", "LOCALS", 0) //# Using items is authorized
!StateCheck(Myself, STATE_SLEEPING | STATE_HELPLESS | STATE_REALLY_DEAD) //# ACTION actions are usable
CheckStat(Myself, 0, CASTERHOLD)
HasItemEquiped("ITEM_FILENAME", Myself) //# ITEM_FILENAME is equiped
OR(3)
!ButtonDisabled(BUTTON_QUICKITEM1)
!ButtonDisabled(BUTTON_QUICKITEM2)
!ButtonDisabled(BUTTON_QUICKITEM3)
//# TODO: Code spécifique
THEN
REPONSE #1
UseItem("ITEM_FILENAME", Myself)
SetGlobalTimer("BD_Cast", "LOCALS", ONE_MINUTE)
END
On vérifie que :
Au moins un des BUTTON_QUICKITEM est débloqué (cas du double illusoire par exemple)
IF
ActionListEmpty()
!GlobalTimerNotExpired("BD_Cast", "LOCALS") //# BD_Cast is expired
Global("BDAI_DISABLE_ITEMS", "LOCALS", 0) //# Using items is authorized
!StateCheck(Myself, STATE_SLEEPING | STATE_HELPLESS | STATE_REALLY_DEAD) //# ACTION actions are usable
CheckStat(Myself, 0, CASTERHOLD)
HasItemEquiped("ITEM_FILENAME", Myself) //# ITEM_FILENAME is equiped
!ButtonDisabled(BUTTON_USEITEM)
//# TODO: Code spécifique
THEN
REPONSE #1
UseItem("ITEM_FILENAME", Myself)
SetGlobalTimer("BD_Cast", "LOCALS", ONE_MINUTE)
END
On vérifie que :
Le bouton des capacités d’équipement est actif
Trigger UseItem#
Il est intéressant - et courant dans les scripts - de pouvoir utiliser un objet spécifique même dans le sac.
Avant cela, il faut posséder l’objet.
HasItem et NumItems#
Deux triggers existent pour déterminer si un personnage possède un objet spécifique.
Effet |
HasItem |
NumItems |
|---|---|---|
S’arrête dès que trouvé |
✅ |
❌ |
Check le sac à dos |
✅ |
✅ |
Check les sacs de contenance |
✅ |
❌ |
HasItem est généralement utilisé avant l’utilisation des objets. Il est potentiellement plus rapide.
NumItems est globalement inutilisé.
Mais il y a un problème :
UseItem ne peut pas cibler les objets dans les sacs de contenance.
Or HasItem renvoie vrai si l’objet est présent dans un sac de contenance.
Ainsi, même si HasItem est vrai, cela n’assure pas que UseItem sera fonctionnel.
Il est donc conseillé de privilégier NumItemsGT("ITEM_FILENAME", Myself, 0), notamment pour les membres du groupe.
Note#
Si la créature ne possède pas l’objet, il est possible de le créer avant avec GiveItemCreate("ITEM_FILENAME", Myself, 1, 1, 1).
Attention tout de même :
Pour les membres du groupe, cela affiche une StrRef
Si l’inventaire est plein, l’objet dans le premier slot du sac est abandonné
Il convient donc de le privilégier pour les non-membres du groupe.
Gestion des objets rechargeables#
HasItem et NumItems sont pratiques, notamment pour les objets à charge unique comme les potions.
Mais il arrive que des objets possèdent plusieurs charges et que le fait de posséder l’objet ne suffise pas à savoir s’il est utilisable ou non. Cela arrive fréquemment avec les objets équipés.
Malheureusement, il n’existe pas de solution pour connaître le nombre de charges restantes d’un objet.
Les moddeurs procèdent alors à l’inverse : ils comptent le nombre de charges utilisées.
Puis, après un repos du groupe, les compteurs sont remis à 0.
IF
PartyRested()
THEN
RESPONSE #100
SetGlobal("ITEM_FILENAME", "GLOBAL", 0)
END
IF
//# …
GlobalLT("ITEM_FILENAME", "GLOBAL", 2)
THEN
RESPONSE #100
UseItem("ITEM_FILENAME", Myself)
IncrementGlobal("ITEM_FILENAME", "GLOBAL", 1)
END
Cela a plusieurs limites importantes :
L’utilisation de plusieurs objets de même nom vont entrer en conflit
Le nombre de charge doit être défini et connu en amont (ici, 2)
Si l’utilisation de l’objet a échoué, la variable est tout de même incrémentée (suite à l’utilisation maladroite de
HasItempar exemple)L’utilisation manuelle des objets par le joueur ou un autre script n’est pas prise en compte
Après une action
Rest, les variables ne sont pas remises à 0Chaque objet nécessite sa propre variable
On se rend vite compte que ce n’est pas parfait en l’état. Il convient donc de chercher des alternatives.
Alternatives#
GLOBAL ou LOCALS#
GLOBAL a l’avantage de fonctionner pour l’intégralité du groupe, par exemple si deux personnages différents utilisent chacun une charge d’un même objet.
LOCALS permet d’individualiser l’utilisation, ainsi un ENEMY pourra utiliser l’objet sans bloquer son utilisation par les membres du groupe.
La solution va dépendre du contexte.
L’utilisation de LOCALS nous paraît plus générique et c’est la solution qui a été choisie ici mais ce n’est pas une obligation.
WeiDU#
On pourrait incrémenter les variables dans l’objet même.
Cela permettrait de comptabiliser que les utilisations réelles de l’objet (3.), et cela fonctionnerait même si c’est le joueur qui utilise l’objet manuellement (4.).
Cela impliquerait de remplacer les GLOBAL par des LOCALS.
Problème : si l’objet n’a plus de charge, la variable ne sera jamais incrémentée et le personnage essayera toujours d’utiliser l’objet.
Unusable, le blocage par l’apprentissage#
Le trigger Unusable nous apporte une information utile ici.
Une fois l’objet déchargé, ce trigger vaut vrai lors du cycle de script suivant. Cela est intéressant si couplé avec le timer BD_Cast.
IF
PartyRested()
THEN
RESPONSE #100
SetGlobal("ITEM_FILENAME_IS_EMPTY", "LOCALS", 0)
END
IF
Unusable("ITEM_FILENAME")
THEN
RESPONSE #100
SetGlobal("ITEM_FILENAME_IS_EMPTY", "LOCALS", 1)
Continue()
END
IF
//# …
!GlobalTimerNotExpired("BD_Cast", "LOCALS")
Global("ITEM_FILENAME_IS_EMPTY", "LOCALS", 0)
THEN
RESPONSE #100
UseItem("ITEM_FILENAME", Myself)
SetGlobalTimer("BD_Cast", "LOCALS", ONE_ROUND)
END
Cela permet de bloquer l’utilisation d’un objet sans avoir à connaître son nombre de charge (2.).
Que l’utilisation de l’objet échoue ou non importe peu (3.).
Au pire des cas, le personnage essayera qu’une seule fois d’utiliser l’objet avant de le bloquer (4.).
La problématique de l’action Rest est réduite aux objets vidés (dont la variable IS_EMPTY = 1) (5.).
Problème : cela ne permet pas de gérer efficacement les objets qui auraient plusieurs capacités (dès qu’une capacité est vidée, l’objet est bloqué).
BitSet / BitCheck (optionnel)#
Grâce au trigger Unusable, on ne sauvegarde qu’une valeur binaire (0 ou 1) dans les variables plutôt qu’un nombre d’utilisation.
Il devient alors possible d’utiliser une seule variable avec l’action BitSet. Chaque bit correspond à un objet, on peut donc sauvegarder jusqu’à 32 variables en une seule.
Lors du PartyRested(), une seule variable est à reset. Cela permet de le rajouter simplement à d’autres scripts/objets.
Exemple avec deux objets :
IF
PartyRested()
!Global("EMPTY_ITEMS", "LOCALS", 0)
THEN
RESPONSE #100
SetGlobal("EMPTY_ITEMS", "LOCALS", 0) //# Une seule variable est à reset
END
IF
Unusable("ITEM_FILENAME1")
THEN
RESPONSE #100
BitSet("EMPTY_ITEMS", "LOCALS", BIT1) //# Chaque bit correspond à un objet
Continue()
END
IF
Unusable("ITEM_FILENAME2")
THEN
RESPONSE #100
BitSet("EMPTY_ITEMS", "LOCALS", BIT2)
Continue()
END
IF
//# …
!GlobalTimerNotExpired("BD_Cast", "LOCALS")
!BitCheck("EMPTY_ITEMS", "LOCALS", BIT1)
THEN
RESPONSE #100
SetGlobalTimer("BD_Cast", "LOCALS", ONE_ROUND)
UseItem("ITEM_FILENAME1", Myself)
END
IF
//# …
!GlobalTimerNotExpired("BD_Cast", "LOCALS")
!BitCheck("EMPTY_ITEMS", "LOCALS", BIT2)
THEN
RESPONSE #100
SetGlobalTimer("BD_Cast", "LOCALS", ONE_ROUND)
UseItem("ITEM_FILENAME2", Myself)
END
Autre#
Enchaînement#
Quand Enchaînement est en cours, privilégiez l’utilisation des sorts… en bloquant par défaut l’utilisation des objets.
CheckStat(Myself, 0, AURACLEANSING)