Cours de Scripts pour les Quiches. Leçon Trois.

Introduction
Le but de cette série de leçons, est d'apprendre a se servir des scripts a n'importe qui.

Commençons.
Cette leçon va tenter de couvrir ce qui est sans doutes l'aspect le plus important de l'art du scripteur. Comment faire en sorte que votre code fasse quelque chose... seulement dans certaines conditions !

Un peu de théorie tout d'abord. Le format de base d'une condition, ou d'un test est :


if ( condition ) { fait_cecifait_celafait_autre_chose; }

(Ce n'est pas un vrai script, juste un prototype.)

De façon simple, si la condition est remplie, le script va faire ce qu'il y a entre les {}. Sinon, il ne fera rien.

Il est a noter qu'une erreur classique consiste a mettre un ; après la ligne commençant par un " if ". Or il n'en faut pas.

Voilà, vous savez maintenant comment écrire un test. Il ne reste plus qu'a savoir comment écrire la condition. Il y a énormément de types de condition différents, et beaucoup de petites règles qui en découlent. Je vais seulement vous en expliquer les bases de façon a ce que vous soyez capable de chercher la suite par vous mêmes.

La plupart des conditions se résument a une comparaison. Par exemple :

if (nCount == 1)

Comme nous l'avons vu, un " = " correspond a une assignation de valeur, il en faut deux " == " pour tester cette valeur.

if (nCount == 1)
Correspond a se demander si nCount est égal à 1 ?
Rappelons nous que nCount est une variable, et est en fait équivalent a un nombre. Si nCount contient au moment du test la valeur 1, alors la condition sera réalisée. Si nCount contient 7, alors elle ne le sera pas.

Quand on parle de conditions, il est difficile d'éluder le nom de Boole. Je ne vais pas aller bien loin, qu'il vous suffise de savoir que c'était un mathématicien qui énonça certaines lois, incontournables en informatiques.

Une condition est soit VRAI ( TRUE ) soit FAUSSE ( FALSE ).
FALSE vaut 0. TRUE vaut 1.
Regardez donc un interrupteur, celui de votre ordinateur ou celui de votre cafetière électrique... Vous y verrez sûrement d'un coté un 0, de l'autre un 1. 1 pour allumé, 0 pour éteint. Bienvenus dans un monde binaire...

Tout ce qui n'est pas TRUE est FALSE et inversement.

Mais revenons a nos moutons...

L'autre façon d'énoncer une condition, est au travers d'une fonction prédéfinie. Considérons donc l'exemple suivant.
Script d'exemple.
Ecrivons donc un script avec une condition simple. Il y aura un certain nombre de nouvelles fonctions que nous expliquerons après.

- Ouvrez votre module de Test.
- Créez une nouvelle Area, forest tileset, dimension 4 par 2. Appelée la " Test Area 002 ".
- A une extrémité, placez une " Start location "
- A l'autre extrémité placez un npc. Juste un " Commoner " pour plus de simplicité.
- Changez son tag en " GARDE "
- Allez dans ses scripts. Supprimez les tous.
- Allez dans le " OnPerceive " et entrez ce script.


object oVu 
GetLastPerceived(); 
void main() 

   if (
GetIsPC(oVu)) 
   { 
      
ActionSpeakString("Bienvenu mon ami."); 
   } 
}

- Sauvegardez le avec notre convention de nomage en tm_garde_op ( " op " pour " OnPerceive " )
- Validez tout, sauvegardez et allez tester votre module.

Lorsque vous vous approchez du garde, il parle. Mais si vous restez a coté de lui, il ne dit plus rien. Eloignez vous, revenez...

Reprenons notre jeu des Questions/Réponses pour expliquer ce script.

Encore un autre " handle ", a quoi sert donc ce " OnPerceive " ?
Ce handle, appelle le script associé des que le npc remarque quelque chose. Si ce quelque chose est invisible, ou caché et que le npc ne le remarque pas, alors le script ne sera pas appelé.
Nous voulons que notre garde réagisse en voyant quelqu'un. D'ou l'utilisation du " OnPerceive ".

Ré explique moi donc pourquoi tu met des choses avant le main. Ca ne ressemble pas a ce qu'il y avait dans la leçon deux.
Et pourtant, c'est la même chose... ou presque. Il s'agit juste d'un autre type de variables. Nous avions crée une variable pour stocker un integer, maintenant nous voulons une variable pour y stocker un objet.

Je l'ai déjà dit, mais je me répéterai. Quasiment tout dans le jeu est un Objet. Les npc, les pc, les objets, les waypoints... et quasiment toutes les fonctions sont écrites afin de manipuler des objets.

Nous créons donc ici une variable temporaire, que nous appelons oVu ( commençant par o pour se rappeler qu'il s'agit d'un objet ). Et nous stockons une valeur dedans.

Et cette valeur est le résultat de la fonction GetLastPerceived(). Cette fonction, comme toutes celles qui commencent par le mot Get, renvoie une donnée. Le nom de la fonction suffit généralement pour savoir ce qu'elle fait. Ici, la fonction renvoie le dernier objet vu par le npc.

On a passé du temps a apprendre ce que c'était qu'une comparaison et maintenant il n'y a une condition sans comparaison ?
Souvenez vous, je vous avais expliquer que tout était soit VRAI, soit FAUX. Si c'est VRAI, j'exécute mon script, si c'est FAUX je fait rien.

C'est exactement ce qu'on veut faire ici. Si le résultat de GetIsPC(oVu) est VRAI alors j'exécute mon script. Si c'est FAUX, je ne fait rien.

GetIsPC() est une fonction qui prend un objet en paramètre et renvoie un booléen. Un booléen ( d'après le nom de Boole ) est un type de variable qui ne peux prendre que deux valeurs... vous l'aurez deviné... TRUE ou FALSE. GetIsPC renverra donc TRUE si oVu est un Pc, FALSE dans tout les autres cas.

Ce qui est exactement ce que nous voulons. Si l'objet perçu par le garde est un Pc, alors le script sera exécuté, sinon, il ne se passera rien.

Vous pouvez aussi écrire, si vous préférez.


if (
GetIsPC(oVu)==TRUE)

de façon a ce que cela ressemble plus a une comparaison... mais ce n'est pas utile.

A t'on forcement besoin de ce test... n'est on pas sur que ce soit toujours une pc qui soit percu par le garde ?
Pour notre petit module de test, il est clair que ce pauvre garde n'a rien d'autre a percevoir. Mais imaginez que ce garde perçoive un gobelin... vous ne voudriez pas qu'il l'appelle mon ami quand même ?

A quoi cela peut il bien me servir de parler a un NPC s'il n'y a personne pour s'en rendre compte ? De toute façon il sont tous amicaux.

Ah oui ?
Voyons un peu ce que l'on peut faire...

Nous allons modifier notre module de façon a ce que notre garde attaque tout personnage ne portant pas... mettons un anneau spécial. Dans le cas ou cet anneau est porté, alors le personnage a droit a une remarque amicale.

- Ouvrez le Toolset. Chargez votre module et choisissez l’ Area002.
- Commençons par créer l’anneau. Go to « Paint Items », "Miscellaneous", "Jewelry", "Rings", "Copper Ring". Placez le près de l’entrée du module.
- Editez les propriétés de l’anneau, changez son tag en « PASSRING ».
- Si vous vous sentez ambitieux, vous pouvez changer le nom de l’anneau, sa description… mais pour les besoins du script, seul son tag importe.
- Choisissez maintenant le garde, et éditez son script « OnPerceive ».


// Amis ou Ennemis : tm_guard_op //
// Doit être placé dans le « OnPerceive » du garde. //
// Le garde va vérifier si le personnage possède l’anneau « PASSRING » et sinon l’attaque. //

object oSeen GetLastPerceived(); 
object oRing GetItemPossessedBy(oSeen"PASSRING"); 
void main() 

// Si ce que voit le garde n’est pas un personnage, il ne fait rien.
   
if (GetIsPC(oSeen)) 
   { 
      if (
oRing == OBJECT_INVALID
      { 
      
// Si le personnage n’a pas l’anneau. Attaque !
         
ActionSpeakString("Meurt donc Etranger!"); 
         
ActionAttack(oSeen); 
      } 
      else 
      { 
// Si le personnage a l’anneau, on le salue.
   
ActionPlayAnimation(ANIMATION_FIREFORGET_GREETING);
         
ActionSpeakString("Bien le bonjour, messire."); 
      } 
   } 
}


Je commence a enrichir les script, en ajoutant de plus en plus de nouvelles commandes. Cela ne devrait plus trop vous poser de problèmes maintenant.

Si vous avez encore des problèmes, chargez le script et lancez le module pour voir ce qu’il fait. Cela devrait être rapidement plus clair. Regardez comment se comporte le garde, suivant que vous ayez ou non l’anneau sur vous.

Argh ! Tu as ajouté une autre initialisation ! Je hait ça !
Tout ce que je peux dire, c’est que le script serait beaucoup plus compliqué sans cela.
object oRing = GetItemPossessedBy(oSeen, "PASSRING");
Une fois de plus, nous créons une nouvelle variable temporaire, qui contiendra un objet. La commande GetItemPossessedBy prend deux paramètres, le premier est l’objet créature que l’on veut tester, et le second est le tag de l’item que l’on cherche sur cette créature.
On sait déjà que oSeen est la personne qui a déclenché le script en étant « Vue » par le garde. ORing est donc l’objet porté par cette personne qui a le tag « PASSRING ».

Mais que se passe t’il si le personnage n’a pas l’anneau ? que vaut oRing dans ce cas la ?
Excellente question. La commande GetItemPossessedBy marche toujours, bien entendu. Mais comme elle ne peut pas trouver l’objet sur la personne, elle renvoi un objet de type OBJECT_INVALID. Une sorte de moyen pour elle de dire qu’elle n’a rien trouvé de ce qu’on lui demandait.

Qu’est ce que toutes ces lignes commençant par des //. Pour une fois elles sont écrites en français ?
Tout ce qui est écrit après des // est ignoré par le script. Cela permet de mettre des commentaires afin de rendre les scripts plus lisibles, et plus facilement compréhensibles.
Prenez l’habitude d’en mettre le plus possible. Cela peut vous sembler rébarbatif quand vous écrivez un script, mais vous serez heureux de les retrouver un ou deux mois plus tard quand vous voudrez le modifier.
C’est également plus gentil pour tous ceux a qui vous donnez de ce script, plus vous expliquez ce que vous faites, plus votre script sera facilement compréhensible.
Pensez de la même façon a citer vos sources et a garder le nom de l’auteur.

Ca commence a devenir compliqué avec tous ces { et ces } Comment je sais ou je dois les mettre ?
Ca peut effectivement devenir compliqué. Plus le script est compliqué, plus les « imbrications » sont délicates. C’est pour cela qu’il faut utiliser l’indentation… comme je l’ai fait.
On peut aussi mettre des lignes blanches, pour aérer le script. Ou utiliser des commentaires pour séparer le script en blocks.
Comme d’habitude, cela deviendra de plus en plus simple avec l’usage.

Qu’est ce que c’est que ce « Else » ?
Vous vous souvenez du format d’un test ?



if ( condition ) { fait_cecifait_celafait_autre_chose; }

Maintenant, un test un peu plus poussé s’écrit comme cela.



if ( condition )
{
fait_ceci;
et_cela;
}
else
{
fait_ca_a_la_place;
et_encore_ca;
}

De façon simple, si la condition n’est pas réalisée, le script passe au « else » a la place.

Si nous regardons notre script. On test si la variable temporaire oRing est égale a OBJECT_INVALID. C’est a dire, si le personnage ne possède pas l’objet, alors attaque. Sinon ( « else » ) soit gentil.
Pour beaucoup, lire un test est une chose compliquée. N’hésitez pas a faire un petit dessin, ou un diagramme.

Qu’est ce que c’est que ces nouvelles commandes ?
Je pense qu’elles sont assez simple a comprendre.
ActionAttack fait attaquer l’objet passé en paramètre.
ActionPlayAnimation fait faire au npc une animation, dans notre cas, celle nommée ANIMATION_FIREFORGET_GREETING. C’est juste un moyen barbare de demander au npc de faire un coucou.

Une dernière petite chose.
Cette leçon est déjà assez grosse, mais j’aimerai ajouter une dernière chose. Bon ok, j’aimerai en ajouter… 251… mais je vais tâcher de ne pas vous assommer. )
Comment changer le comportement de notre garde et lui faire faire exactement l’inverse ? C’est a dire, attaquer si le personnage porte l’anneau ? Disons par exemple que cet anneau est un anneau volé.
Nous pourrions très bien déplacer des blocks de script et les intervertir. Au risque de se tromper. Mais il y a beaucoup plus simple. Il est possible d’obtenir ce résultat en changeant simplement un caractère !
La ou on avait


if (oRing == OBJECT_INVALID)

mettez


if (oRing != OBJECT_INVALID)

« != » est une autre sorte de comparaison. Cela signifie « différent de » ou « non égal à ».

Conclusion.
Il y a beaucoup de nouvelles idées dans cette leçon. Mais quand vous l’aurez maîtrisée, vous aurez déverrouillé le vrai pouvoir derrière la programmation. Combiner les tests avec les variables locales, ouvre toutes sortes de possibilités.

__________________
Amaranthe.
Thoerel, un monde semi-persistant pour Neverwinter Nights.