Version en ligne

Tutoriel : Warcraft III - Introduction au langage "JASS"

Table des matières

Warcraft III - Introduction au langage "JASS"
Qu'est-ce que le JASS ?
J.A.S.S.
Mais qu'est-ce que j'en ai à cirer, moi ?
C'est ben beau tout ça, mais...
Démêler le code
Les Variables
Variables Locales
Variables Globales
Les Arrays
Types de Variables
Armés jusqu'aux dents
Jass Shop Pro
JassCraft
JASS Helper
http://jass.sourceforge.net/
Les Blocs - loop, if, else, elseif...
if, else, elseif
loop
Opérateurs
function
Les Triggers
... ont une forme bien précise.
Les Actions et les Conditions
Les Événements - Pas mal plus compliqué
Laborato - Un Trigger Simple
But
Protocole
Analyse des Résultats
Jouer avec les types de variables
Un petit problème...
Une solution : le Type Casting !
Liste des Type Casters
Variables de Handle Locaux
Game Cache
...H2I et ses amis !
Passer des données à un timer.
Utilité
MUI
Hashtables
common.j
Hashtables

Warcraft III - Introduction au langage "JASS"

Si vous lisez ce tuto , vous devez certainement savoir ce qu'est Warcraft III. En fait, c'est encore mieux si vous avez Warcraft III... et tant qu'à faire, il vous faudrait, en plus, (parce que notre mieux, c'est jamais assez !) avoir Warcraft III The Frozen Throne, l'extension du jeu !

Beuh ! Flamber 30 piastres là-dedans ? :colere2:

Ça a de l'air à ça. Heureusement, vous avez aussi le droit de jouer au jeu que vous aurez acheté. :p

Ah ! Oui, aussi, j'utilise la version anglaise du World Editor. D'où les anglicismes. Je préfère vous en avertir à l'avance. Il est également très préférable, pour une bonne compréhension, de parler anglais plus ou moins couramment, mais ce n'est pas nécessaire.

Il est très importantnécessaire d'avoir lu War3 editor : des bases au complexe, par RaptorTeak et Scraper.

Il est également suggéré d'avoir lu le tutoriel Apprenez à programmer en C ! de M@teo21 ou d'avoir des notions de base en programmation générale.

Qu'est-ce que le JASS ?

J.A.S.S.

J.A.S.S.

Qu'est-ce que le JASS ? Mais qu'est-ce que j'en ai à cirer, moi ?

J.A.S.S.

Just
Another
Scripting
System

Bon, maintenant qu'on a brisé la glace, je vais vous expliquer. Le JASS est le langage de programmation utilisé par le World Editor de Warcraft III. Le JASS est utilisé dans le Trigger Editor et est souvent utilisé par ceux qui sont habitués à la programmation ou bien qui n'aiment pas le fonctionnement du GUI du Trigger Editor.

Peux-tu parler français ?...

D'accord. Le... JASS est le langage de... l'Éditeur de Mondes pour ceux qui n'aiment pas... l'IUG de... l'Éditeur de Déclencheurs... >_

Bon, vous l'aurez compris, tout ça est tellement mal traduit et/ou difficile à traduire que je préfère dire les termes techniques en anglais.

Un GUI est l'interface qui "remplace" le code de programmation, dans ce cas-ci. Lorsque vous ouvrez votre Trigger Editor, vous avez du GUI.Image utilisateur

Sur cette image, la partie GUI du Trigger Editor est entourée de rouge. C'est la partie qui va changer lorsque nous allons commencer à coder.

Bref, ceux qui connaissent un langage de programmation X, ou ceux qui n'aiment pas le Trigger Editor pour une raison Y, ou ceux qui trouvent que le code avec de la couleur dedans, c'est joli (comme moi, par exemple) devraient apprendre le JASS.

Les origines du JASS

En fait, plusieurs raisons portent à penser que le terme JASS est un acronyme inventé de toutes pièces pas les JASSers eux-mêmes. Par exemple, dans le World Editor, on fait toujours référence au JASS en tant que Custom Text. On cherche toujours où Blizzard a décidé d'appeller ce langage le JASS, même si cet événement ne s'est jamais produit.

En tout cas, Blizzard a créé ce langage pour permettre au mappeur de créer une map beaucoup plus efficace, soit plus rapide d'exécution. Cependant, Blizzard n'a pas (ou presque) créé une communauté autour de ce langage. C'est pourquoi ce tutoriel est rédigé, après une durée bien trop longue de 9 ans, 9 ans durant lesquels le JASS a connu un épanouissement incroyable.


Qu'est-ce que le JASS ? Mais qu'est-ce que j'en ai à cirer, moi ?

Mais qu'est-ce que j'en ai à cirer, moi ?

J.A.S.S. C'est ben beau tout ça, mais...

Mais qu'est-ce que j'en ai à cirer, moi ?

C'est le fun, c'est le fun... un langage de script dans Warcraft... mais c'est pas compliqué pour rien ? Je veux dire, imagine, j'ai pas envie de programmer, moi, quand j'ai un Trigger Editor qui marche fârpaitement bien !

En effet, certains pourraient trouver que le JASS est inutile. Cependant, ce langage présente de nombreux avantages plus ou moins subtils qui peuvent être utiles lorsqu'on connaît les notions intermédiaires et avancées de ce langage. Voici une liste des plus importants :

Et là, j'en passe. Mais, surtout, le plus important, selon moi, c'est qu'on n'a pas besoin de cliquer partout comme des déchaînés, d'ouvrir 5 fenêtres différentes en même temps pour une seule action, sans même pouvoir faire autre chose en même temps... ça peut être très "gossant", "chien", "fatiguant"...

N'avez-vous jamais trouvé que le GUI est un peu... énervant ? Voyez-vous, même si on sait déjà à l'avance quelle action on veut ajouter, on doit tout de même faire toutes les étapes complexes pour appeler une fonction, choisir quel type de variable on veut utiliser... ce qui nous prendrait 30 secondes en GUI peut être une affaire de 5 secondes en JASS. Sans parler du fait que vous allez vous user le poignet et la main droite (ou gauche) à force de devoir cliquer et de devoir regarder constamment les fenêtres qui apparaîtront à votre écran, travailler de manière saccadée, si vous faites du GUI.

Par exemple, pour faire une simple action qui va tuer une unité, on doit faire New Action > Unit > Kill > Picked Unit, alors qu'en JASS, on n'a qu'à écrire "call KillUnit( GetEnumUnit( ) )" et on tue notre unité. En GUI, on cherche la fonction dans une liste mal ordonnée et mélangeante, sans fin et mal classée.

Cependant, rien n'est tout noir, rien n'est tout blanc. Si le GUI est plus lent à utiliser une fois appris, il en va tout autrement de l'apprentissage lui-même. Le JASS peut prendre des mois à apprendre et on ne le connaîtra jamais de fond en comble. On ne peut se rappeler par coeur les plus de 10 000 lignes de codes qui contiennent les fonctions qu'on utilise. On apprend par l'expérience, on regarde souvent quelle fonction on devrait utiliser pour faire quelque chose... mais, au moins, une fois qu'on la connaît, cette fonction, elle est plus rapide à intégrer dans son code par la suite. Voici une liste des désavantages du JASS :

Malgré tout, cela vaut la peine d'apprendre le JASS. Après tout, ce tuto n'existerait pas, si ce n'était pas le cas ! :p


J.A.S.S. C'est ben beau tout ça, mais...

C'est ben beau tout ça, mais...

Mais qu'est-ce que j'en ai à cirer, moi ? Démêler le code

C'est ben beau tout ça, mais...

Lorsque j'ouvre mon Trigger Editor, je ne vois pas de code...

Oui, c'est vrai, il faut convertir la partie GUI de vos Triggers en code JASS. Notez que cette action est irréversible (à moins de faire CTRL+Z :p ). Alors, nous allons voir comment changer un Trigger de tous les jours en JASS de tous les jours.

Créez une nouvelle map et détruisez-moi ça, le script de Melee Initialization ! Bon, maintenant que c'est fait, créez un nouveau Trigger (CTRL+H). Vous devriez avoir quelque chose comme ça:

Trigger Editor

Convertissez-moi donc ça en JASS, tant qu'à faire ! Edit > Convert to Custom Text

Ha ! C'est tellement beau que j'en pleure ! :'(

function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
    set gg_trg_Untitled_Trigger_001 = CreateTrigger(  )
    call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Actions )
endfunction
Image utilisateurImage utilisateur

D'accord, maintenant, si vous pleurez, ce ne sera probablement pas pour la même raison que moi, mais... c'est le résultat qui compte ? :ange:

Analysons notre code

Tout d'abord, on va voir le paragraphe après la grosse ligne en commentaire.

//===========================================================================
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
    set gg_trg_Untitled_Trigger_001 = CreateTrigger(  )
    call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Actions )
endfunction

D'accord... rien à comprendre, hein ?
Eh bien, nous allons commencer par le commencement :

function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
endfunction

Pour ceux qui connaissent un peu l'anglais, un langage de programmation ou les deux, vous aurez compris que ceci crée une fonction qui ne prend aucune valeur et qui ne retourne rien. La dernière ligne met une fin à cette fonction ! Wahou !

Les prochaines lignes sont probablement plus weird pour ceux qui connaissent un langage de programmation :

set gg_trg_Untitled_Trigger_001 = CreateTrigger(  )

What the... fudge ? o_O

Eh bien, en JASS, dès qu'on doit donner une valeur à une variable, on utilise le mot-clé set. Ensuite, on doit donner le nom de la variable, puis assigner une valeur. On doit ensuite changer de ligne. gg_trg_Untitled_Trigger_001 est donc une variable.

Pourquoi elle s'appelle comme ça, la variable ? De quel type est-elle ?

La variable s'appelle comme ça pour une raison que nous verrons plus en profondeur dans le chapitre sur les variables. Son type est le type trigger.

Ah ! Je devine ce que vous pensez... le type trigger n'existe pas, hein ? Eh bien, oui ! En JASS, d'ailleurs, il y a plusieurs types de variables plutôt bizarres. Par exemple, une variable peut être de type :

player race unit location attacktype ability
Et bien d'autres...

call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Actions )

Cette ligne de code devrait vous sembler plutôt obscure, elle aussi. Si vous arrivez à la déchiffrer, alors là, vous êtes forts !

Donc, forcément, on appelle une fonction. Cette fonction, selon mon anglais médiocre, ajoutera une Action au Trigger (add, ajouter...). Maintenant, les valeurs passées à la fonction sont de type... trigger et... fonction ? En fait, on appelle ceci une valeur de type code : on envoie la référence d'une fonction qui ne prend aucun paramètre à l'intérieur d'une autre fonction. C'est le cas de function Trig_Untitled_Trigger_001_Actions, qui est une valeur de type code.

Logiquement, la fonction appelée est la fonction Trig_Untitled_Trigger_001_Actions, qui fait... voyons voir... rien ! Mais oui, c'est logique, car notre Trigger à la base ne faisait rien, lui non plus !


Mais qu'est-ce que j'en ai à cirer, moi ? Démêler le code

Démêler le code

C'est ben beau tout ça, mais... Les Variables

Démêler le code

Nous allons maintenant arranger notre code pour qu'il ait l'air présentable et lisible.

Commencez par changer le nom des fonctions pour des noms plus... significatifs.

Mais, mais, mais ! J'ai changé le nom des fonctions, mais... ça ne marche pas ! Regardez mon code !

function actions takes nothing returns nothing
endfunction

//===========================================================================
function init takes nothing returns nothing
    set gg_trg_Untitled_Trigger_001 = CreateTrigger(  )
    call TriggerAddAction( gg_trg_Untitled_Trigger_001, function actions )
endfunction
Image utilisateurImage utilisateur

En effet, ça ne marche pas, pour une raison toute simple, mais, pour la comprendre, il faut savoir comment fonctionne le code d'une carte Warcraft III.

Le code de la map est divisé en deux parties : une partie est constituée des Triggers, et l'autre partie contient le code qui est caché à celui qui fait la map. Certains logiciels, que nous verrons plus tard, nous permettront d'aller modifier ce code. Pour l'instant, il est intouchable, immortel, immunisé et en situation de force. Nous sommes impuissants, incapables, inutiles, des moins que rien...

Bref, ce code-là, on n'y touche pas encore. Pour faire simple, cependant, on pourrait dire que ce code va se modifier avec le nom de notre Trigger. Donc, le mieux qu'on peut faire, c'est changer le nom du trigger pour TEST, par exemple. Il faudra ensuite remplacer le Untitled_Trigger_001 dans le code par TEST :

function actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_TEST takes nothing returns nothing
    set gg_trg_TEST = CreateTrigger(  )
    call TriggerAddAction( gg_trg_TEST, function actions )
endfunction
Image utilisateurImage utilisateur

Voilà ! Nous avons déjà un beau code présentable !


C'est ben beau tout ça, mais... Les Variables

Les Variables

Démêler le code Variables Locales

Bon, j'ai effleuré le sujet des variables dans le premier chapitre, alors je vais expliquer plus en détail les types de variables, leur utilisation, et l'avantage principal du JASS, soit l'utilisation de variables locales.

Variables Locales

Les Variables Variables Globales

Variables Locales

S'il y a un avantage du JASS par rapport au GUI, c'est bien l'utilisation des variables locales. Le JASS permet de déclarer une variable qui ne sera utilisée que dans une fonction, puis qu'on détruira après. Dans le cas du GUI, on ne peut que créer des variables globales et, même si on définit leur valeur comme nulle à la fin d'une fonction, ces variables prennent quand même de l'espace en mémoire, ralentissent le jeu, et tout le tralala...

function maFonction takes nothing returns nothing
     local integer a = 0
     local integer b
          //code...
     set a = 3
     set b = 5
endfunction
Image utilisateurImage utilisateur

Vous aurez compris le principe, je crois.

Libération de Mémoire

Si une variable de type non-natif (voir plus loin dans ce chapitre) est utilisée dans une fonction, il est important de lui enlever la valeur assignée à la fin de la fonction, surtout dans le cas d'une variable globale. (udg_unit est une variable globale)

function maFonction takes nothing returns nothing
     set udg_unit=GetTriggerUnit(    )
     call autreFonction( udg_unit )
     set udg_unit = null
endfunction
Image utilisateurImage utilisateur

Les Variables Variables Globales

Variables Globales

Variables Locales Les Arrays

Variables Globales

Si, un jour, pour une raison quelconque, vous décidiez de lire le code de votre map avant de vous coucher le soir, vous tomberiez probablement sur ceci dans le début du fichier war3map.j :

globals
     trigger gg_trg_abc     = null
     integer udg_cab        = 0
     unit udg_bla_bla       = null
endglobals
Image utilisateurImage utilisateur

Une représentation assez simple de ce que pourraient représenter les variables globales d'une map... assez simple ! :p Ce que vous voyez ici s'appelle un "globals block" car, en bref, c'est un bloc, dans lequel on retrouve les globals ! (remarquez globals et endglobals)

Les variables globales peuvent être utilisées dans n'importe quelle fonction.

Nom des globals
gg_var

Si le nom d'une variable est gg_nomDeLaVariable, alors c'est qu'il s'agit d'une :

Generated
Global

C'est-à-dire une global créée au-to-ma-ti-que-ment selon plusieurs facteurs. Entre autres, les régions que vous définissez dans le World Editor sont stockées dans des variables globals dont le nom commence par gg.

udg_var

Lorsqu'il y a la présence du préfixe "udg", c'est un signe que votre variable est une :

User
Defined
Global

Les UDG sont, très précisément, définies par vous-même. :ange: En fait, si vous allez dans le Trigger Editor et faites CTRL+B, vous allez les voir, ces UDG. N'est-ce pas génial ? Vous pouvez vous-mêmes déclarer vos variables de cette manière et, pour l'instant, ce sera notre seule façon de déclarer une variable globale.

Peu importe le nom que vous utilisez pour votre variable en faisant CTRL+B, vous devez ajouter udg_ devant ce nom, dans le code.


Variables Locales Les Arrays

Les Arrays

Variables Globales Types de Variables

Les Arrays

Comme en C, comme en C++, comme en C#, comme en JavaScript, comme en ActionScript (énumération sans fin), les array sont des "tableaux" de variables. Voici un exemple concret, que vous avez probablement vu un million de fois :

index | | valeur 0 100 1 2 2 30 3 255 4 127

index |

| valeur

0

100

1

2

2

30

3

255

4

127

C'est une représentation plutôt simple de ce qu'est un tableau. On a une variable et un index, sous la forme variable[i]. Chaque index a une valeur assigné à cet index.

En JASS, on doit déclarer un array de la même manière qu'on déclare une variable, mais avec le mot-clé "array" entre le type et le nom de la variable :

function maFonction takes nothing returns nothing
      local integer array var
      set var[0]=100
      set var[1]=2
      set var[2]=30
      set var[3]=255
      set var[4]=127
endfunction
Image utilisateurImage utilisateur

Variables Globales Types de Variables

Types de Variables

Les Arrays Armés jusqu'aux dents

Types de Variables

Il existe de multiples types de variables et de valeurs à transmettre à des fonctions dans le JASS. Voici une liste des principaux types de variables. Pour une liste complète, voir jass.sourceforge.net.

Variables Natives

Les variables natives sont les variables "de tous les jours", qu'on retrouve dans presque n'importe quel langage de programmation.

real

Stocke un nombre décimal.

integer

Stocke un nombre entier.

Le type integer peut aussi désigner un type d'unité, de sort, d'item, de buff... dans ces cas-là, ce type sera un code de 4 caractères entre apostrophes ', et non entre guillemets ". Le premier caractère d'un de ces codes indique le type ou la race. Par exemple, les sorts commencent par 'A et les unités de race humaine par 'h. Les trois autres caractères sont spécifiques et uniques au type d'unité, d'item, de sort... par exemple, 'hfoo' est le code de l'unité footman. Les unités, sorts, etc. définis par l'utilisateur ont des numéros à la place des 3 derniers caractères. Par exemple, la première unité de race humaine créée aura pour code 'h000'.

Pour trouver le code d'une unité ou un autre type, on doit aller dans le Object Editor et trouver l'unité en question. On fait ensuite CTRL+D. Dans le menu de gauche, les noms sont alors remplacés par les identifiants des types. On appuie encore une fois sur CTRL+D si on veut retourner en mode normal.

string

Stocke une chaîne de caractères. Ceux qui n'ont fait que du C ne sont peut-être pas familiers avec ce type. Les string sont toujours entre guillemets ", jamais entre apostrophes '. Si on devait avoir un guillemet dans un string, on mettrait un backslash \ juste avant, pour faire \". Si on doit écrire un backslash dans notre string, on le doublera, pour faire \\.

boolean

Stocke true ou false. (On peut également l'écrire TRUE ou FALSE)

code

Vous vous rappelez du code ?

:diable: <=== Plus gentil que le type code

Ce type ne peut être déclaré en array. En aucun cas. Il conserve une référence qui renvoie à une fonction. On s'en sert souvent pour appeler une fonction dans une fonction. La fonction à laquelle il renvoie ne peut prendre aucun paramètre. Aucun. Jamais. Impossible. Pas moyen. Oubliez ça. Vous comprenez pourquoi il est encore plus méchant que le Diable ?

Pour créer une variable de type code, on doit mettre le mot-clé function devant le nom de la fonction. Par exemple, on peut faire ceci :

local code c = function maFonction
Les types non-natifs ou handle

Tous les types spécifiques à Warcraft III entrent dans cette catégorie. Ce sont les pointeurs en JASS. On y retrouve :

unit

Désigne une unité.

group

Désigne un groupe d'unités.

trigger

Désigne un Trigger.

rect

Désigne une région.

ability

Désigne un sort.

widget

Regroupe les item, les unit et les destructable (doodads destructibles, comme les arbres).

handle

Large catégorie qui regroupe la plupart tous les types non-natifs.

handle

widget

player

trigger

unit

destructable

item

X

X

Par exemple, ce code ne détruit pas l'unité de référence de la variable gg_footman :

function maFonction takes nothing returns nothing
     set gg_footman = null
endfunction
Image utilisateurImage utilisateur

Ceci, par contre, la détruira :

function maFonction takes nothing returns nothing
     call RemoveUnit(gg_footman)
endfunction
Image utilisateurImage utilisateur

Les Arrays Armés jusqu'aux dents

Armés jusqu'aux dents

Types de Variables Jass Shop Pro

Vous aurez probablement remarqué à quel point le World Editor était monotone... tout le texte est en noir... comme sur le Site du Zéro, il n'y a pas de SH :

Syntax
Highlighter

Un SH est un logiciel qui va nous permettre d'ajouter de la couleur à mesure qu'on code, comme quand on utilise Notepad++ pour coder en HTML ou Code::Blocks pour coder en C++... Bref, ce sera plus facile de s'y retrouver.

Nous allons voir trois programmes parmi lesquels vous devriez choisir celui qui, selon vous, serait le meilleur.

Malheureusement, ces logiciels ne fonctionnent pas sous Mac OS. La raison pour laquelle il n'y en a pas sur Macintosh est que le World Editor sous Mac rend le code "funky". Au moins, à part des couleurs, ces logiciels n'amènent pas grand-chose.

Jass Shop Pro

Armés jusqu'aux dents JassCraft

Jass Shop Pro

Image utilisateur

Jass Shop Pro est plutôt destiné à ceux qui ont plus d'expériences ou qui préfèrent la simplicité de compilation à l'efficacité de codage. Ce programme est également moins esthétique ( selon moi ^^ ). Jass Shop Pro permet de tester la map directement sans avoir le World Editor ouvert. Sauvegarder la map est également plus facile ; avec JassCraft, sauvegarder sa map est toute une besogne !

De plus, Jass Shop Pro ( JSP, pour faire court ) permet de modifier tout le code de la map, en particulier celui qui est caché par le World Editor, ce qui nous sera bientôt nécessaire.

Ce logiciel permet aussi de placer des "signets" parmi les quelques milliers de lignes code de la map, afin de pouvoir s'y retrouver plus facilement.

Ce logiciel contient une liste des fonctions natives, mais pas des variables et constantes incluses dans la map. Son outil de recherche et d'auto-complétion du code en souffre beaucoup.

Téléchargement : http://www.wc3c.net/showthread.php?t=78538


Armés jusqu'aux dents JassCraft

JassCraft

Jass Shop Pro JASS Helper

JassCraft

Image utilisateur

Du même créateur que JSP, je n'ai aucune idée pourquoi on a créé ce logiciel plutôt que de faire quelques patches pour JSP, mais le résultat est là : JassCraft. Personnellement, je trouve que la coloration a un air plus "cool" et que les couleurs sont plus "hot". Cependant, il faut prendre en considération qu'on ne peut pas tester la map directement dans JassCraft : il faut garder World Editor ouvert, mais, après tout, on le fait de toute façon lorsqu'on ne travaille pas sur les Triggers.

La principale fonction qui a été ajoutée à JSP est que cet éditeur peut modifier les fichiers .mdl. Puisque je n'ai aucune idée du fonctionnement de ce système, cette fonction semble plutôt inutile.

Un des avantages de ce logiciel est que, pour les débutants, il y a un meilleur interface de recherche de fonctions natives. (une fonction native est une fonction intégrée dans le jeu) Aussi, on peut trouver dans la recherche de fonctions natives des constantes et des variables comme des attacktype. Par exemple, on pourrait trouver ATTACK_TYPE_MEDIUM_SLICE en cherchent ATTACK_TYPE, ce qui peut être très utile quand on ne les connaît pas à l'avance.

Téléchargement : http://www.wc3c.net/showthread.php?t=80051


Jass Shop Pro JASS Helper

JASS Helper

JassCraft http://jass.sourceforge.net/

JASS Helper

Ce logiciel est en fait un hack du World Editor, qui vient également avec des additions pour tous les panels, tels l'Object Editor et le Trigger Editor. Ce logiciel a pour avantage qu'on n'a pas besoin de copier/coller le code, car il est déjà dans le World Editor. Ce logiciel est également nécessaire pour le vJASS. Cependant, il présente plusieurs inconvénients :

Malgré tout ceci, ce serait tout de même un bon choix, car, une fois son installation complexe terminée, il est facile à utiliser et vous aurez déjà installé tout ce dont vous aurez besoin pour le vJASS.

Téléchargement : http://www.wc3c.net/showthread.php?t=88142


JassCraft http://jass.sourceforge.net/

http://jass.sourceforge.net/

JASS Helper Les Blocs - loop, if, else, elseif...

http://jass.sourceforge.net/

http://jass.sourceforge.net/ est un site web qui donne non pas un tutoriel sur le JASS, mais plutôt une bibliothèque sur le JASS. Ce site contient toutes les informations dont on a besoin pour trouver des variables constantes, des types de variables, des fonctions... tout !

L'avantage de ce site web est que, si vous pouvez naviguer sur le site du zéro, vous pouvez naviguer sur ce site, alors même les utilisateurs de Macintosh auront un moyen de trouver les fonctions dont ils auront besoin.

Voici un lien direct vers leur API Browser, qui vous permet de visionner le contenu des 3 scripts inclus dans votre map, soit respectivement common.j, common.ai et Blizzard.j. Si vous ne trouvez pas une fonction dans common.j, essayez de la trouver dans Blizzard.j. Notez que common.ai contient seulement les fonctions qui servent à mieux utiliser l'intelligence artificielle de Warcraft III, surtout utiles à ceux qui créent des Campagnes.

Je vais souvent utiliser les scripts retrouvés sur ce site web comme référence. Notez bien que, puisque ces scripts sont trop vieux, il est probable que les numéros de lignes ne soient plus valides. Ce sont les numéros de lignes que j'utiliserai pour mes références, car il s'agit quand même d'un bon point de repère. Il y aura des liens vers ce site web dans la plupart des codes sources que je vous montrerai, pour vous permettre de voir les fonctions en relation avec celle que je vous montre. Veuillez m'excuser de l'invalidité de certaines informations qui pourraient se trouver sur ce site web.


JASS Helper Les Blocs - loop, if, else, elseif...

Les Blocs - loop, if, else, elseif...

http://jass.sourceforge.net/ if, else, elseif

Nous allons voir une partie plutôt importante du JASS et de n'importe quel langage de programmation, car, en JASS, la syntaxe des "blocs" est différente de celle qu'on utilise dans la plupart des langages. Vous allez comprendre...

if, else, elseif

Les Blocs - loop, if, else, elseif... loop

if, else, elseif

if

Si vous connaissez déjà un langage de programmation quelconque, ou parlez un mausus de mot d'anglais, vous saurez que if correspond à une condition. En JASS, sa syntaxe est un genre de bouillie consituée de celles du C, du Visual Basic et du Python. Si vous connaissez un des trois, vous partez avec une longueur d'avance... ou peut-être bien des habitudes à perdre.

function maFonction takes nothing returns nothing

     if(maVariable==true)then
          set autreVariable=true
     endif

endfunction
Image utilisateurImage utilisateur

Comme vous pouvez le voir, il y a quelques petits détails à remarquer :

Tout d'abord, au lieu des accolades { } qu'on utilise normalement en C ou en C++ pour un bloc, on utilise then et endif.

L'opérateur utilisé ici doit être un ==, contrairement au Visual Basic qui reconnaîtrait un = unique.

else

Le else, ou plutôt, vrai quand la condition précédente est fausse, s'utilise comme suit :

function maFonction takes nothing returns nothing
     if maVariable then
          set autreVariable=true
     else
          set autreVariable=false
     endif
endfunction
Image utilisateurImage utilisateur

Choses à noter :

elseif

Le elseif dit : «Si la condition précédente est fausse, vérifions celle-ci...»

Par exemple, si je voulais décider de tuer une unité si maVariable est true ou de la retirer si maVariable n'est pas true et que autreVariable est true, je ferais comme ceci :

function maFonction takes nothing returns nothing
     if maVariable then
          call KillUnit(gg_footman)
     elseif autreVariable then
          call RemoveUnit(gg_footman)
     endif
endfunction
Image utilisateurImage utilisateur

La façon dont on se sert du elseif est la même que celle pour le if. Encore une fois, on doit le mettre avant le endif.


Les Blocs - loop, if, else, elseif... loop

loop

if, else, elseif Opérateurs

loop

Le loop permet de répéter une action ou plus plusieurs fois, comme le while du C ou du C++.

Voici un exemple de sa syntaxe :

function maFonction takes nothing returns nothing
     local integer i = 0 // Notez qu'il faut initialiser la variable dès le début du code, comme toujours.
          // code...
     
     loop
          exitwhen i==36
          //code...
          set i=i+1
     endloop

endfunction
Image utilisateurImage utilisateur

if, else, elseif Opérateurs

Opérateurs

loop function

Opérateurs

Les opérateurs sont les "signes" qu'on utilise, dans ce cas-ci, dans les conditions et les boucles. Voici une liste des opérateurs qui nous permettront de faire une meilleure utilisation des conditions en JASS.

"=="

Permet de vérifier si une valeur est égale à une autre.

Utilisation :

function maFonction takes nothing returns nothing
     if a==b then
          call autreFonction(   )
     endif
endfunction
Image utilisateurImage utilisateur
"!="

Permet de vérifier si une valeur n'est pas égale à une autre.

Utilisation :

function maFonction takes nothing returns nothing
     if a!=b then
          call autreFonction(   )
     endif
endfunction
Image utilisateurImage utilisateur
">","<"

Permet de vérifier si une valeur est plus petite / plus grande qu'une autre.(comme dans vos cours de mathématiques)

Utilisation :

function maFonction takes nothing returns nothing
     if a>b then
          call autreFonction(   )
     endif
endfunction
Image utilisateurImage utilisateur
">=","<="

Permet de vérifier si une valeur est plus grande ou égale / plus petite ou égale à une autre valeur.

Utilisation :

function maFonction takes nothing returns nothing
     if a>=b then
          call autreFonction(   )
     endif
endfunction
Image utilisateurImage utilisateur
not

La condition est vraie si ce qui suit est faux. Par exemple :

function maFonction takes nothing returns nothing
     if not (a==b) then
          call autreFonction(   )
     endif
endfunction
Image utilisateurImage utilisateur

Correspond à :

function maFonction takes nothing returns nothing
     if a!=b then
          call autreFonction(   )
     endif
endfunction
Image utilisateurImage utilisateur
and

La condition est vraie si les deux composants sont vrais. Par exemple :

function maFonction takes nothing returns nothing
     if a==b and c==d then
          call autreFonction(   )
     endif
endfunction
Image utilisateurImage utilisateur
or

La condition est vraie si un des deux composants est vrai. Par exemple :

function maFonction takes nothing returns nothing
     if a==b or c==d then
          call autreFonction(   )
     endif
endfunction
Image utilisateurImage utilisateur

loop function

function

Opérateurs Les Triggers

function

Eh, oui ! La function est également un bloc ! Et d'ailleurs, un bloc function peut avoir de multiples paramètres que nous n'avons pas encore vus !

Par exemple, si je voulais appeler une fonction qui me donnait le carré d'un nombre et le mettait dans une variable, il faudrait faire :

function maFonction takes nothing returns nothing
     local integer nombre = carre(3)
endfunction
Image utilisateurImage utilisateur

Mais que contiendrait la fonction carre ? Eh bien, elle devrait ressembler à ceci :

function carre takes integer nombre returns integer
     return  nombre*nombre 
endfunction
Image utilisateurImage utilisateur

Et si on voulait faire une fonction qui multiplie 2 nombres ensemble ? Il faudrait lui passer 2 valeurs différentes...

Dans ce cas-là, il faudrait, en effet, passer 2 valeurs à la fonction. Alors, dans la déclaration de notre fonction, nous mettrions une virgule entre les deux paramètres :

function multiplier takes integer a, integer b returns integer
     return  a*b 
endfunction
Image utilisateurImage utilisateur

Par exemple, ce bout de code n'est pas valide :

function maFonction takes nothing returns nothing
     local boolean variable = autreFonction( )
endfunction

function autreFonction takes nothing returns boolean
     return true
endfunction
Image utilisateurImage utilisateur

Dans ce cas-ci, le compilateur arrivera à la ligne 2, qui appelle une fonction dont il n'a jamais entendu parler. Pour y remédier, il faudrait faire :

function autreFonction takes nothing returns boolean
     return true
endfunction

function maFonction takes nothing returns nothing
     local boolean variable = autreFonction( )
endfunction
Image utilisateurImage utilisateur

Opérateurs Les Triggers

Les Triggers

function ... ont une forme bien précise.

Bon, c'est bien beau tout ça, mais on va commencer à faire du véritable codage, des choses concrètes, qu'on peut vraiment utiliser en pratique ! Des choses qui marchent ! Des choses qui...

... ont une forme bien précise.

Les Triggers Les Actions et les Conditions

... ont une forme bien précise.

Structure d'un Trigger
Image utilisateur

Excusez mon talent médiocre en dessin

Voici donc un schéma plutôt simple de ce qu'est un Trigger : lorsqu'un Evénement (Event) se produit, si la Condition est vraie, alors on effectue les Actions. Par exemple :

Event

Condition

Action

Une unité meurt

Joueur auquel l'Unité appartient est le Joueur 1 (rouge)

Ajouter 1 Footman au milieu de la map

Même si ce Trigger ne ferait pas grand-chose d'utile, c'est quand même un Trigger. C'est tout simplement un exemple plus concret qui montre comment serait formé un Trigger.

En JASS

Re-regardons notre code de base d'un Trigger.

function Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_TEST takes nothing returns nothing
    set gg_trg_TEST = CreateTrigger(  )
    call TriggerAddAction( gg_trg_TEST, function Actions )
endfunction

Ça vous dit quelque chose ? Maintenant, nous allons mettre quelque chose au clair...

C'est bien logique, car notre Trigger de départ ne faisait rien. Heureusement, nous allons y remédier.


Les Triggers Les Actions et les Conditions

Les Actions et les Conditions

... ont une forme bien précise. Les Événements - Pas mal plus compliqué

Les Actions et les Conditions

Les Actions

La seule chose que contient le Trigger que nous avons en ce moment, c'est une Action(qui ne fait rien). Au départ, nous avions la fonction Trig_Untitled_Trigger_001_Actions. Maintenant, nous l'avons renommé Actions et avons changé la ligne 7.

TriggerAddAction
call TriggerAddAction( gg_trg_TEST, function Actions )

La fonction TriggerAddAction ajoute une action au Trigger en question. On doit l'utiliser dans la forme suivante :

call TriggerAddAction( trigger Trigger, function fonction )

Bref, il faut spécifier un trigger auquel on ajoutera une action, et une fonction qui sera la référence de l'Action en question. Notez qu'on doit utiliser le mot-clé function avant le nom de la fonction qu'on doit appeler pour les actions à effectuer ; c'est une valeur de type code. Ça vous dit quelque chose ? ^^

Les Conditions

Il manque une Condition à notre Trigger. On pourrait mettre un if dans notre fonction Actions, le résultat serait le même. Cependant, pour aérer notre code et pour lui permettre d'être plus efficace, on ajoute des conditions au Trigger. Nous allons commencer par faire une fonction qui retourne un boolean et qui servira de condition :

function Conditions takes nothing returns boolean
     return true
endfunction

Maintenant, nous allons créer une condition. Juste avant la ligne 7 (le TriggerAddAction), nous allons mettre un TriggerAddCondition.

function InitTrig_TEST takes nothing returns nothing
    set gg_trg_TEST = CreateTrigger(  )
    call TriggerAddCondition( gg_trg_TEST, Condition( function Conditions ) )
    call TriggerAddAction( gg_trg_TEST, function Actions )
endfunction

Quels sont les paramètres de la fonction TriggerAddCondition ?

TriggerAddCondition prend un paramètre de type trigger et un paramètre de type boolexpr.

boolexpr ? o_O

Une variable de type boolexpr (de "boolean expression") appelle une fonction qui retourne un boolean. Pour créer une variable de type boolexpr, on doit faire :

local boolexpr variable = Condition( function maFonction )

En clair, la fonction Condition crée un boolexpr, et le second paramètre de la fonction TriggerAddCondition doit être un boolexpr. Notez qu'on aurait pu mettre le Condition dans une variable locale. On doit passer à la fonction Condition une valeur de type code. Oui, rappelez-vous bien, dès le premier chapitre, je vous ai prévenu... le type code est sournois, traître, il attaque par derrière... faites-y très attention, car on ne peut lui passer de paramètres... :colere2:

Dans tous les cas, la fonction à laquelle on fait référence dans notre fonction Condition doit retourner un boolean en fonction de certaines données. Dans la fonction de référence, on mettra un "if" et un "else". Après le "if", on met "return true". Après le else, on met un "return false".

Une deuxième façon de faire les choses, quand on veut un code efficace (mais parfois plus compliqué à écrire, enfin, question de perception...), serait de mettre notre condition directement dans la valeur du retour de la fonction du boolexpr de notre condition. Vous me suivez ? Voilà un exemple :

function Conditions takes nothing returns boolean
    return GetUnitTypeId(GetTriggerUnit())=='hfoo'
endfunction

Ce bout de code vérifie si GetTriggerUnit, soit l'unité principale du Trigger, est un footman.


... ont une forme bien précise. Les Événements - Pas mal plus compliqué

Les Événements - Pas mal plus compliqué

Les Actions et les Conditions Laborato - Un Trigger Simple

Les Événements - Pas mal plus compliqué

La gestion des événements, c'est de la bave de crapaud, en JASS. Pour l'instant, votre code devrait ressembler à ceci :

function Conditions takes nothing returns boolean
     return true
endfunction

function Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_TEST takes nothing returns nothing
    set gg_trg_TEST = CreateTrigger(  )
    call TriggerAddCondition( gg_trg_TEST, Condition( function Conditions ) )
    call TriggerAddAction( gg_trg_TEST, function Actions )
endfunction

Pour ajouter un événement, on ne peut pas faire comme avec les Actions et les Conditions, il n'y a pas une seule fonction pour ajouter des événements.

Regardons donc le modèle de base de ces fonctions :

native TriggerRegister...Event takes trigger whichTrigger, ... , ... returns event

En clair, il y a plusieurs dizaines de fonctions de ce type, et elles prennent toutes des paramètres différents. C'est là que JassCraft ou JSP (mais surtout JassCraft) intervient. Si vous tapez TriggerRegister dans le champ de recherche prévu à cet effet, alors, comme par magie, vous avez une liste des fonctions qui peuvent être utilisées pour ajouter un événement ! :magicien:Image utilisateur

Personnellement, je trouve que quelques-unes de ces fonctions peuvent êtres particulièrement utiles, et j'en ai donc fait une courte liste.(la liste complète peut être trouvée un peu partout entre les lignes 1057 et 1333 de common.j)

*Notez que cette fonction nécessite The Frozen Throne

Rendus là, vous êtes mieux de parler anglais. :p

Comment je dis ça, moi, playerunitevent, dans TriggerRegisterAnyUnitEventBJ ?

Les playerunitevent sont un type de variable non-natif. Il y a plusieurs constantes pour ces événements, comme, par exemple, EVENT_PLAYER_UNIT_SPELL_EFFECT. Si vous entrez EVENT_PLAYER_UNIT dans JassCraft (ne fonctionne pas avec JSP), vous aurez une belle petite liste de ces constantes.

La liste peut aussi être trouvée à la ligne 445 de common.j.

Le mieux est souvent de mettre ses événements en GUI et ensuite de faire Edit > Convert to Custom Text. Ainsi, tout cela est beaucoup moins compliqué.


Les Actions et les Conditions Laborato - Un Trigger Simple

Laborato - Un Trigger Simple

Les Événements - Pas mal plus compliqué But

Voyons, voyons... nous avons un laboratoire prévu pour le cours de 12h00... vous avez étudié ? Non, bien sûr ! Une chance, vous avez un beau protocole, comme dans votre cours de science au secondaire. Ah, quels beaux souvenirs... :ange:

But

Laborato - Un Trigger Simple Protocole

But

Bon, voici donc votre but :

Votre but est de créer un Trigger qui se déclenchera lorsqu'une unité en attaque une autre. Une condition devra vérifier si l'unité qui attaque est de type footman. Si c'est le cas, on devra tuer l'unité qui attaque.

Il ne vous reste plus qu'à ouvrir le World Editor et à commencer à coder !


Laborato - Un Trigger Simple Protocole

Protocole

But Analyse des Résultats

Protocole

1. Créer un nouveau Trigger.
2. Convertir en JASS (Edit>Convert to Custom Text)
3. Renommer les fonctions du Trigger pour des noms plus représentatifs.
4. Créer une fonction qui retourne un boolean. Retourner true si GetUnitTypeId(unit) de GetAttacker( ) est 'hfoo'. Sinon, retourner false. Notez que 'hfoo'doit être entre apostrophes ''. (Whaa ! Termes compliqués, hein ? >_ )
5. Dans la fonction des actions, appeler KillUnit(unit) avec l'unité qui attaque comme paramètre.
6. Ajouter un événement au Trigger avec TriggerRegisterAnyUnitEventBJ(trigger,playerunitevent), ayant EVENT_PLAYER_UNIT_ATTACKED comme paramètre de type playerunitevent.


But Analyse des Résultats

Analyse des Résultats

Protocole Jouer avec les types de variables

Analyse des Résultats

function Conditions takes nothing returns boolean
    if( GetUnitTypeId( GetAttacker( ) )== 'hfoo' )then
        return true
    else
        return false
    endif
endfunction

function Actions takes nothing returns nothing
    call KillUnit( GetAttacker( ) )
endfunction

//===========================================================================
function InitTrig_Test takes nothing returns nothing
    set gg_trg_Test = CreateTrigger(  )
    call TriggerRegisterAnyUnitEventBJ( gg_trg_Test, EVENT_PLAYER_UNIT_ATTACKED )
    call TriggerAddCondition( gg_trg_Test, Condition( function Conditions ) )
    call TriggerAddAction( gg_trg_Test, function Actions )
endfunction

Voilà ! On a un Trigger :

On connaît maintenant la base et plus encore. Cependant, il nous reste encore quelques notions à apprendre avant de vraiment pouvoir se lancer dans la création d'une map. Notez qu'en JASS, comme en n'importe quoi, les connaissances sont acquises par l'expérience. Alors, dès que vous aurez terminé d'apprendre le JASS, je vous suggère d'aller jouer avec tout cela, afin de mieux connaître les fonctions que vous jugez les plus utiles. Chaque programmeur est unique et on ne peut enseigner qu'une manière de faire les chose. Donc, développez vous-même votre talent en map making. Allez faire quelques maps et, si vous avez des questions, envoyez-moi un message. ;)


Protocole Jouer avec les types de variables

Jouer avec les types de variables

Analyse des Résultats Un petit problème...

Bravo!

Si vous vous êtes rendus jusqu'ici tout en comprenant mon tutoriel, c'est soit que je suis un Dieu pour écrire (très peu probable...) ou que vous êtes les meilleurs... peut-être les deux, qui sait. Dans tous les cas, nous entammons maintenant une partie beaucoup plus avancée du JASS. Nous passons désormais à un autre niveau, car, croyez-moi, vous n'avez encore rien vu.

Mais ! Mais ! Mais ! On n'a presque rien vu ! Je ne sais pas comment faire un Trigger qui [insérez l'effet d'un Trigger ici] !

:lol:

En effet, jeune padawan, mais vous saurez que la meilleur manière d'apprendre, c'est par l'expérience. Maintenant que vous connaissez le fonctionnement des Triggers, vous devriez être en mesure de créer presque n'importe quel Trigger. Cependant, puisque vous ne connaissez pas le nom de toutes les fonctions (et si, un jour, vous les connaissez toutes, donnez-moi votre truc), vous devriez utiliser votre logiciel de colorisation syntaxique (soit Jass Shop Pro ou JassCraft) pour les trouver.

Un petit problème...

Jouer avec les types de variables Une solution : le Type Casting !

Un petit problème...

Voici donc un problème qui risque de vous arriver souvent et il faut savoir y remédier.

Disons, par exemple, que je veux faire un Trigger qui se déclenche quand le Joueur 1 entre un message dans le chat (on assume qu'il s'agit d'un nombre entier). Pas de condition. Je voudrais que ce Trigger affiche le nombre qu'on a entré, multiplié par deux, dans le jeu.

Voici donc ma structure de Trigger :

function Actions takes nothing returns nothing
local string nombre = GetEventPlayerChatString(   )
set nombre = nombre * 2
call BJDebugMsg( nombre )
endfunction

//===========================================================================
function InitTrig_test takes nothing returns nothing
    set gg_trg_test = CreateTrigger(  )
    call TriggerRegisterPlayerChatEvent( gg_trg_test, Player(0), "", false )
    call TriggerAddAction( gg_trg_test, function Actions )
endfunction

Si vous avez de l'oeil (alors là, vous en avez probablement plus que moi), vous aurez remarqué que ce code n'est pas valide du tout.

Ce qui ne vas pas, c'est que notre variable est de type string. Or, un string est du texte, et on ne peut multiplier du texte (essayez, pour voir ! :lol: ). Comment y remédier? On pourrait tenter de changer la ligne 2 :

local integer nombre = GetEventPlayerChatString(  )

Cependant, on aura tout simplement changé le mal de place. Laissez-moi vous expliquer en détails...

Si nous regardons les modèles des fonctions GetEventPlayerChatString et BJDebugMsg, nous verrons ces deux descriptions :

constant native GetEventPlayerChatString takes nothing returns string
//===========================================================================
function BJDebugMsg takes string msg returns nothing
     local integer i = 0
     loop
          call DisplayTimedTextToPlayer(player(i),0,0,60,msg)
          set i = i + 1
          exitwhen i==bj_MAX_PLAYERS
     endloop
endfunction

Le second code peut vous sembler un peu plus complexe que le premier, mais c'est presque complètement de la matière déjà vue, et son contenu n'a pas d'importance.

Ce qu'il faut remarquer, c'est qu'un retourne unstring et l'autre prend unstring.

Donc, forcément, on a un problème, car on veut :

Comment le résoudre ? :o


Jouer avec les types de variables Une solution : le Type Casting !

Une solution : le Type Casting !

Un petit problème... Liste des Type Casters

Une solution : le Type Casting !

Bon, vous l'aurez deviné avec le titre, notre solution, c'est le :

Type
Casting

Le type casting permet de changer le type d'une valeur lorsqu'on veut la passer à une fonction où la stocker dans une variable. Reprenons notre exemple, où on devait faire des opérations sur un nombre :

function Actions takes nothing returns nothing
local string nombre = GetEventPlayerChatString(   )
set nombre = nombre * 2
call BJDebugMsg( nombre )
endfunction

//===========================================================================
function InitTrig_test takes nothing returns nothing
    set gg_trg_test = CreateTrigger(  )
    call TriggerRegisterPlayerChatEvent( gg_trg_test, Player(0), "", false )
    call TriggerAddAction( gg_trg_test, function Actions )
endfunction

Voici donc : on devrait changer GetEventPlayerChatString pour quelque chose qui retournerait la même valeur, mais sous la forme d'integer.

Gné ? o_O

On va tout simplement utiliser une fonction qui sera maintenant notre alliée dans la guerre du JASS, la fonction S2I. C'est une fonction native plutôt simple, mais qui, comme tant d'autres, nous est d'un grand secours.

native S2I  takes string s returns integer

Donc, forcément, on devra lui passer un string. Et il nous donnera l'integer qui y correspond ! :waw: (épiphanie)

Voici donc ce qu'on va faire: nous allons utiliser S2I etI2S (I2S changeant un integer en string) pour convertir tout ce qu'on a besoin de convertir.

function Actions takes nothing returns nothing
local integer nombre = S2I( GetEventPlayerChatString(   ) )
set nombre = nombre * 2
call BJDebugMsg( I2S( nombre ) )
endfunction

//===========================================================================
function InitTrig_test takes nothing returns nothing
    set gg_trg_test = CreateTrigger(  )
    call TriggerRegisterPlayerChatEvent( gg_trg_test, Player(0), "", false )
    call TriggerAddAction( gg_trg_test, function Actions )
endfunction

Et voilà ! On a une fonction qui... fonctionne ! :lol:


Un petit problème... Liste des Type Casters

Liste des Type Casters

Une solution : le Type Casting ! Variables de Handle Locaux

Liste des Type Casters

Voilà donc, on a réussi à régler notre problème, mais il y a beaucoup d'autres situations semblables dans lesquelles on pourrait se retrouver, et il est important de connaître tous les Type Casters. Ne vous inquiétez pas, ils sont assez simple à mémoriser.

Si vous les oubliez, vous pouvez toujours consulter la ligne 815 de common.j.

S2I

Pronnoncé "S tout Aïl", c'est une abbréviation, comme les autres, pour String to Integer("2" se prononce comme "to"...). Cette fonction permet donc de changer un string en integer.

I2S

Contraire de S2I, permet de changer un integer en string.

R2I, I2R

Change un real en integer ou vice-versa, où R représente un real.

R2S

Change un real en string. Il n'existe pas de contraire à cette fonction.


Une solution : le Type Casting ! Variables de Handle Locaux

Variables de Handle Locaux

Liste des Type Casters Game Cache

Wahou! C'est un gros chapitre qui s'en vient, là ! Nous allons voir 2 façons de remplacer les variables globales, dont une que nous allons étudier en profondeur afin de l'exploiter à son plein potentiel !

Game Cache

Variables de Handle Locaux ...H2I et ses amis !

Game Cache

Game
Cache

À la base, un Game Cache devrait renfermer les Héros qu'on doit transférer d'une map à l'autre dans une campagne. En JASS, on l'utilise plutôt que les variables globales : plusieurs variables peuvent avoir le même nom. On peut littéralement détruire une variable, alors que normalement le seul fait qu'un variable existe prend de la place en mémoire. Le game cache peut stocker des boolean, des real, des integer, des string et des unités (et non des variables de type unit, nous reviendrons là-dessus).

Structure du Game Cache

Mission (Catégorie)

Type

Nom

Valeur

"map.w3v"

"X"

string

nom

"Albert"

integer

age

17

real

argent

5.49

"Y"

unit

heros

Arthas

boolean

isLv10

false

integer

damage

35

unit

Unit

Footman

"Z"

string

nom

"Roger"

integer

age

32

Ceci est une exemple d'un (une ?) Game Cache. Celui-ci contient plusieurs variables dans des catégories différentes. Il serait bon de noter que plusieurs variables peuvent avoir le même nom. Si ces variables sont dans deux catégories, elles sont totalement indépendantes l'un de l'autre. Nous avons donc ici la base de la Programmation Orientée Objet : un ancêtre (catégorie / missionKey) et ses descendants (valeurs).

Utiliser un Game Cache

Commencez par créer une variable globale (CTRL+B dans le Trigger Editor) de type Game Cache, et appelez-la... GC, par exemple. Ensuite, créez un Trigger avec cet événement :

Time - Elapsed game time is 0.10 seconds

Vous devriez avoir quelque chose comme ceci :

function Trig_GameCache_Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_GameCache takes nothing returns nothing
    set gg_trg_GameCache = CreateTrigger(  )
    call TriggerRegisterTimerEventSingle( gg_trg_GameCache, 0.10 )
    call TriggerAddAction( gg_trg_GameCache, function Trig_GameCache_Actions )
endfunction

Ce code n'est pas vraiment compliqué, maintenant qu'on s'y connaît, hein ? :soleil:

Tout ce qu'on a, c'est un evénement et une action vide. Dans les actions, maintenant, on devra créer un Game Cache.

function Trig_GameCache_Actions takes nothing returns nothing
        set udg_GC = InitGameCache( "map.w3v" )
endfunction

"map.w3v" doit désigner un fichier de campagne qui contiendra les valeurs dans les différentes missions. Il n'a pas besoin d'exister, le World Editor le créera de lui-même. À moins que vous ne vouliez créer une véritable campagne, son nom ne change pas grand chose, car on ne s'en sert plus. Son extension est toujours .w3v

On vient d'initialiser un Game Cache. Quand on n'en aura plus besoin, on devra le détruire...

function Trig_GameCache_Actions takes nothing returns nothing
        set udg_GC=InitGameCache("map.w3v")
        call FlushStoredGameCache( udg_GC )
endfunction

Et si je voulais seulement détruire une mission en particulier, et non tout le Game Cache ?

Ce n'est pas plus compliqué que ceci :

function Trig_GameCache_Actions takes nothing returns nothing
        set udg_GC=InitGameCache("map.w3v")
        call FlushStoredMission( udg_GC, "missionKey" ) // game cache, nom de la mission
endfunction

Et si je voulais enlever seulement une valeur dans notre mission, qui est dans notre Game Cache ?

Dépendamment du type de la variable à détruire :

function Trig_GameCache_Actions takes nothing returns nothing
        set udg_GC=InitGameCache("map.w3v")
                //actions...
        call FlushStoredInteger(udg_GC, "missionkey", "int"   )// game cache, clé de la mission, nom de la variable
        call FlushStoredBoolean(udg_GC, "missionkey", "bool"  )
        call FlushStoredReal   (udg_GC, "missionkey", "real"  )
        call FlushStoredString (udg_GC, "missionkey", "string")
        call FlushStoredUnit   (udg_GC, "missionkey", "unit"  )
endfunction
Stocker une valeur

Il se peut que vous trouviez tout cela quelque peu répétitif, mais il s'agit tout de même de Store{Type}(gamecache,missionkey,nom,valeur).

function Trig_GameCache_Actions takes nothing returns nothing
        set udg_GC=InitGameCache("map.w3v")
        call StoreInteger( udg_GC, "missionkey", "int", 3 )//game cache,clé de la mission, nom de la variable, valeur
        call StoreBoolean( udg_GC, "missionkey", "bool", true )
        call StoreString ( udg_GC, "missionkey", "string", "abc" )
                //et ainsi de suite...
endfunction
Retrouver une valeur

C'est bien beau tout ça, mais on doit quand même pouvoir retrouver les valeurs qu'on a stockées dans notre Game Cache. Pour une fois, ce n'est pas pareil pour tous les types de variables (Dieu merci, sinon on mourrait d'ennui).

function Trig_GameCache_Actions takes nothing returns nothing
local integer i
local string s
local real r
local boolean b
local unit u
        set udg_GC=InitGameCache("map.w3v")
                //ici, on a stocké des variables, mettons...
        set i = GetStoredInteger( udg_GC,  "missionkey", "i" )//game cache, clé de la mission, nom de la variable
        set s = GetStoredString( udg_GC, "missionkey", "s" )
        set r = GetStoredReal( udg_GC, "missionkey", "r" )
        set b = GetStoredBoolean( udg_GC, "missionkey", "b" )
set u = RestoreUnit( udg_GC, "missionkey", "u", Player(0),0, 0, 0 )
//game cache, clé de la mission, nom de la variable, joueur, x, y, direction
endfunction

Vous avez probablement remarqué que RestoreUnit est beaucoup plus compliqué que les autres fonctions. En effet, cette fonction ne fait pas que stocker la valeur retrouvée dans une variable, elle crée une unité. Voilà pourquoi il nous faut tant de paramètres.

Voilà aussi pourquoi il ne s'agit pas d'une variable de type unit : ce n'est pas un pointeur, mais bien l'unité en tant que tel, qui est stockée dans le Game Cache. Ainsi, on ne peut stocker un pointeur unit (ou n'importe quel autre handle) et pouvoir le retrouver. Que faire ? Eh bien, on aura besoin de...


Variables de Handle Locaux ...H2I et ses amis !

...H2I et ses amis !

Game Cache Passer des données à un timer.

...H2I et ses amis !

Handle
2 (to)
Integer

Cette fonction, qui est créée par l'utilisateur, utilise un bug qu'on appelle couramment le returnreturn bug.

Cette fonction prend un handle et retourne un integer. Son contenu est vraiment bébé-fafa :

function H2I takes handle h returns integer
     return h
     return 0
endfunction

Pourquoi y a-t-il deuxreturn ? C'est bête, une fonction ne peut retourner qu'une valeur !

En fait, le compilateur ParseJASS (qui vérifie la syntaxe dans JassCraft et Jass Shop Pro) trouvera une erreur dans le return h, car on veut retourner un integer alors qu'il s'agit d'un handle. Alors il continuera et trouvera un return 0. 0 étant un entier, il sera satisfait. On pourrait l'enlever, et la map fonctionnerait encore farpaitement.

Cette fonction retourne l'identifiant unique d'un pointeur handle. Il n'y en a jamais deux qui ont le même identifiant, car il est... unique ! :lol:

Utilisation

Si on veut stocker le pointeur du handle, on le stockera sous la forme d'integer dans notre Game Cache. Ensuite, on doit le retrouver comme n'importe quelle variable et le reconvertir en handle ou en unit, player, etc.

function H2I takes handle h returns integer
return h //handle 2 integer
return 0
endfunction

function I2H takes integer i returns handle
return i //integer 2 handle
return null
endfunction

function H2U takes handle h returns unit
return h //handle 2 unit
return null
endfunction

function maFonction takes nothing returns nothing
local unit u = GetTriggerUnit(   )
local unit uu = null
        set udg_GC=InitGameCache( "map.w3v" )
        call StoreInteger( udg_GC, "m", "u", H2I( u ) )
        set uu = H2U( I2H ( GetStoredInteger( udg_GC, "m", "u" ) ) )
endfunction

Pour des raisons de simplicité, on regroupe généralement H2U et I2H en une seule fonction :

function GetStoredUnit takes gamecache gc,string mission,string key returns unit
return( GetStoredInteger( gc, mission, key ) )
return null
endfunction

C'est bon, je peux transférer des variables de tous types dans mon Game Cache, mais qu'est-ce que j'en ai à cirer ?

Vous rappelez-vous, dans le premier chapitre, j'avais dit que...

Citation : L'illustrissime Ashlebede, il y a de cela plusieurs décennies...

Puisque le type code est notre ennemi, nous devons trouver une manière de le contourner. Dans plusieurs cas, on devra appeler une fonction en utilisant une variable de type code. Dans ces cas-là, on aurait deux options pour passer des valeurs à la seconde fonction :

Et c'est là qu'on utilise les timer.


Game Cache Passer des données à un timer.

Passer des données à un timer.

...H2I et ses amis ! Utilité

Passer des données à un timer.

Tant qu'à avoir commencé à utiliser les amis de H2I, nous allons utiliser tous ses amis, sans distinction aucune, notamment de race, de couleur, de sexe, de langue, de religion, d'opinion politique ou de toute autre opinion, d'origine nationale ou sociale, de fortune, de naissance ou de toute autre situation.(1)

1 : Déclaration Universelle des Droits de l'homme.

Bref, on a deux sortes de fonctions :

  1. Stocke une valeur dans un handle.

  2. Va chercher une valeur qui est dans un handle.

Voilà le moment où ça devient compliqué : «dans un handle.»

Mais pensez-y un peu. Il y a des catégories qui contiennent des variables. Les catégories sont des string. Nos handle peuvent être changés en integer. Nos integer peuvent être changés en string...

Eurêka !

La mission (catégorie) dans laquelle nous allons stocker les valeurs portera comme nom l'id du handle ! Voilà ! Quoi de plus simple ? Maintenant, au lieu de créer des arrays de variables globales et tout le tralala, on n'a besoin que de deux éléments : une variable globale de Game Cache et une avec un handle. Quoi de mieux ? On peut enlever la mission et ainsi libérer de la mémoire. (rappelez-vous que même l'existence d'une variable prend de la mémoire)

Voici donc comment on procède :

function maFonction takes nothing returns nothing
local player p = Player(0) //création d'un handle
local integer id = H2I(p)
        set udg_GC=InitGameCache( "map.w3v" )
        call StoreInteger( udg_GC, I2S( id ), "int", GetRandomInt( 1, 100 ) )
        call BJDebugMsg( I2S (GetStoredInteger( udg_GC, I2S( id ), "int" ) ) )
endfunction

Il se peut que vous trouviez ce code très peu clean. C'est pourquoi nous allons remplacer tout cela par des fonctions qui font plus "beau". Voici le script Local Handle Variables de KaTTaNa (j'ai préféré ne pas le copier en entier, pour le raccourcir) :

// ===========================
function H2I takes handle h returns integer
    return h
    return 0
endfunction

// ===========================
function LocalVars takes nothing returns gamecache
    // Replace InitGameCache("jasslocalvars.w3v") with a global variable!!
    return InitGameCache("jasslocalvars.w3v")
endfunction

function SetHandleHandle takes handle subject, string name, handle value returns nothing
    if value==null then
        call FlushStoredInteger(LocalVars(),I2S(H2I(subject)),name)
    else
        call StoreInteger(LocalVars(), I2S(H2I(subject)), name, H2I(value))
    endif
endfunction

function SetHandleInt takes handle subject, string name, integer value returns nothing
    if value==0 then
        call FlushStoredInteger(LocalVars(),I2S(H2I(subject)),name)
    else
        call StoreInteger(LocalVars(), I2S(H2I(subject)), name, value)
    endif
endfunction

          //idem pour SetHandleBoolean, SetHandleReal...

function GetHandleInt takes handle subject, string name returns integer
    return GetStoredInteger(LocalVars(), I2S(H2I(subject)), name)
endfunction

          //idem pour GetHandleReal, GetHandleString...

function GetHandleUnit takes handle subject, string name returns unit
    return GetStoredInteger(LocalVars(), I2S(H2I(subject)), name)
    return null
endfunction
          
          //idem pour tous les types de handle

Copiez/collez plutôt le script à l'aide de ce lien, qui est plus complet. Pour l'utiliser, nous devrons le mettre dans notre partie du Trigger Editor réservée au Custom Script Code.Image utilisateur

Maintenant, nous devons changer la ligne 10 :

return InitGameCache("jasslocalvars.w3v")

Il faudrait remplacer InitGameCache par une variable globale de type gamecache. Nous allons alors l'initialiser manuellement.

return udg_GC

Enfin, on peut utiliser les Variables de Handle Locaux.

Alors, notre code de départ (rappelez-vous, on stockait un nombre au hasard dans un player) deviendrait :

function maFonction takes nothing returns nothing
local player p = Player(0)
        set udg_GC=InitGameCache( "map.w3v" )
        call SetHandleInt( p, "int", GetRandomInt( 1, 100 ) )
        call BJDebugMsg( I2S( GetHandleInt( p, "int" ) )
endfunction

Voilà un code quelque peu potable. Maintenant, passons à la prochaine étape. Puisque celle-ci sera encore plus complexe, je conseille fortement que vous relisiez tout ceci afin de mieux comprendre.

Les timer

Notre bon vieux timer... c'est un type de variable qui est une extension de handle et qui nous permet d'éxécuter une fonction lorsqu'il est expiré. Voici comment cela marche :

function fonction2 takes nothing returns nothing
endfunction

function fonction1 takes nothing returns nothing
        local timer t = CreateTimer( )
    call TimerStart( t, 0.1, false, function fonction2 )
endfunction
native TimerStart           takes timer whichTimer, real timeout, boolean periodic, code handlerFunc returns nothing

On crée ici un timer qui appelera la fonction fonction2 après 0.1 secondes. On le définit à 0.1 car on ne veut pas, dans le cas présent, attendre l'exécution de la fonction. Voici pourquoi : on va créer deux fonctions qui vont faire la même chose, mais il n'est pas question d'utiliser un timer qui nous fera attendre. On s'en sert uniquement pour stocker des données, tester notre nouveau système, voir la théorie. Notez que false ici signifie qu'on ne veut pas que le timer revienne à 0 et se répète après exécution de la fonction.

GetExpiredTimer
native GetExpiredTimer      takes nothing returns timer

Cette fonction retourne un timer. Devinez lequel ! :lol:

En effet, cette fonction nous permet de retrouver le timer que nous utilisions dans l'autre fonction, même s'il s'agissait d'une variable locale. Voilà donc pourquoi on utilise des timer. Ce sont des handle : on peut stocker des valeurs de Game Cache dedans. On n'a pas besoin de variables globales. Les variables locales sont automatiquement détruites lorsque la fonction se termine. Dites-moi si vous trouvez un défaut là-dedans !

function fonction2 takes nothing returns nothing
        local timer t = GetExpiredTimer( )
        call DestroyTimer( t )
endfunction

function fonction1 takes nothing returns nothing
        local timer t = CreateTimer( )
    call TimerStart( t, 0.1, true, function fonction2 )
endfunction

Notez qu'on a pu utiliser true dans le TimerStart, c'est-à-dire que la fonction se répétera à l'infini, sauf si... on le détruit ! C'est exact, plus besoin de boucles et tout ce qui va avec : un timer et deux fonctions.

Et comment peut-on être sur qu'il s'agit du même timer?

C'est "simple" : on n'a qu'à afficher l'identifiant du handle avec un BJDebugMsg. Voici un code que nous démêlerons par la suite, pour les besoins de la cause :

//Ce code affiche l'identifiant d'un timer.
//On le refait dans la seconde fonction pour vérifier.
function fonction2 takes nothing returns nothing
   local timer t = GetExpiredTimer(   ) //récupère le timer de la fonction 1
        call BJDebugMsg( I2S( H2I( t ) ) ) //affiche l'ID du timer dans la fonction 2
    call DestroyTimer( t )
endfunction

function fonction1 takes nothing returns nothing
    local timer t = CreateTimer(   ) //crée un timer
    set udg_GC=InitGameCache( "map.w3v" ) // initie le game cache
        call BJDebugMsg( I2S( H2I( t ) ) ) // affiche l'ID du timer dans la fonction 1
    call TimerStart( t, .01, true, function fonction2 )
endfunction

//===========================================================================
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing 
    set gg_trg_Untitled_Trigger_001 = CreateTrigger(   )
    call TriggerRegisterPlayerChatEvent( gg_trg_Untitled_Trigger_001, Player(0), "", false )//enregistre tous les messages chat du joueur 1
    call TriggerAddAction( gg_trg_Untitled_Trigger_001, function fonction1)
endfunction
Courte analyse

Notre fonction InitTrig_Untitled_Trigger_001 enregistre les messages du Joueur 1 et appelle notre fonction fonction1. Cette dernière crée un timer et affiche son identifiant. Elle part ensuite ce timer, qui se répétera et appellera la fonction fonction2 après 0.01 secondes. Dans fonction2, on récupère le timer et on affiche encore son identifiant unique, pour prouver qu'il s'agit du même timer. Puisqu'on détruit ensuite le timer, la fonction ne se répète pas vraiment.

Testons...

1048670
1048670
1048672
1048672
1048674
1048674
1048676
1048676
1048678
1048678
1048680
1048680
1048682
1048682

Comme vous pouvez le voir avec le résultat, les identifiants uniques sont en groupes de deux.

Maintenant, si on voulait stocker des valeurs dans notre timer, car c'est ce qu'on veut faire, on devrait procéder comme suit :

function fonction2 takes nothing returns nothing
    local timer t = GetExpiredTimer( )
    local integer i = GetHandleInt( t, "int", i )
    call DestroyTimer( t )
endfunction

function fonction1 takes nothing returns nothing
    local timer t = CreateTimer( )
    local integer i = GetRandomInt( 1, 100 )
    call SetHandleInt( t, "int", i )
    call TimerStart( t, 0.1, true, function fonction2 )
endfunction

...H2I et ses amis ! Utilité

Utilité

Passer des données à un timer. MUI

Utilité

Bon, d'accord, mais à quoi ça sert, tout ça ? Plutôt que d'utiliser un timer, je pourrais faire une boucle avec des attentes. Je n'aurais besoin que d'une fonction, donc pas de variable globales et...

Je vous coupe ici. Il existe, en effet, une fonction TriggerSleepAction. Cependant, son temps d'attente minimal est de 0.27 secondes. Même si vous entrez 0.01 secondes, cette fonction fera attendre approximativement 0.27 secondes, dépendamment de la capacité de votre ordinateur. Vous aurez compris le problème. On ne veut parfois pas attendre aussi longtemps. Pensez-y. 0.27 secondes, c'est environ un quart de seconde. L'oeil humain voit environ 30 images par secondes.

Et si vous vouliez simuler le déplacement d'une unité ? Par exemple, les projectiles sont en fait des unités (oui oui ! :p ) qui se déplacent. Alors, on voudrait que cela ait l'air fluide, non ? 4 images par seconde, c'est un septième de la vitesse à laquelle notre oeil peut voir. Vous croyez que ça aura l'air beau, 4 images par seconde ?

Voici donc un exercice qu'on va faire. Nous allons créer un sort qui va ramener, leen-tee-meent, une unité vers une autre. On verra ça comme un crochet qu'on accroche sur une unité et sur lequel on tire. En tout cas, on voudra déplacer une unité vers une autre. Que fera-t-on, alors? On ne peut pas avoir un quart de seconde entre chaque frame : on pourrait presque compter le temps qu'il y a entre chaque mouvement de l'unité. On devrait alors utiliser un timer, puisque ce sera le seul moyen de déplacer une unité de manière fluide.

Pour notre exemple, nous n'allons cependant pas créer de sort. ^^

Ben oui, c'est ça ! Mets-nous l'eau à la bouche, pour nous dire qu'on ne le fera pas ! :(

C'est seulement qu'on n'a pas encore vu comment créer un sort. Leur création étant relativement complexe, nous allons la voir dans la partie du tutoriel qui porte sur... la création de sorts ! Euh... j'y travaille ?

Tant qu'à vous dire la véritié, aussi bien vous die que nous allons également déplacer notre unité uniquement sur l'axe des X. Pourquoi ? Pour la simple et bonne raison que nous aurions besoin de beaucoup de notions de mathématiques plutôt complexes afin de trouver la longueur des deux cathètes de...

Bref, nous allons déplacer une unité sur l'axe des X. Commençons par créer un Trigger qui se déclenchera après 0.1 secondes de jeu. Changez-moi ça, bien sûr, en JASS.

function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
    set gg_trg_Untitled_Trigger_001 = CreateTrigger(  )
    call TriggerRegisterTimerEventSingle( gg_trg_Untitled_Trigger_001, 0.10 )
    call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Actions )
endfunction

Commençons par créer un timer dans notre fonction des actions et stockons-le dans une variable locale. Initialisons également notre Game Cache :

function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing
     local timer t = CreateTimer( )
     set udg_GC = InitGameCache( "map.w3v" )
endfunction

Maintenant que ça c'est fait, on devra passer quelques valeurs au timer. Puisque normalement on aurait dû passer deux unités au timer (lanceur du sort et cible), mais qu'on n'a aucune de ces deux unités, nous allons lui passer... un compteur. Nous allons compter le nombre d'exécutions de la fonction, puis, passé un certain nombre d'exécutions, détruire le timer. Créons tout d'abord une nouvelle fonction et appelons-la via un timer qui aura un temps d'attente de 0.01 secondes.

function Timer takes nothing returns nothing
     local timer t = GetExpiredTimer( )
     local integer compteur = GetHandleInt( t, "compteur" )
endfunction

function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing
     local timer t = CreateTimer( )
     set udg_GC = InitGameCache( "map.w3v" )
     call SetHandleInt( t, "compteur", 500 )
     call TimerStart( t, .01, true, function Timer )
endfunction

On a une fonction qui sera exécutée 500 fois. Voilà. Maintenant, on doit utiliser deux autres variables : une qui va stocker la position X de notre unité et une qui va stocker la position Y de notre unité. Nous allons ensuite changer sa position avec la fonction SetUnitPosition.

native          SetUnitPosition     takes unit whichUnit, real newX, real newY returns nothing

Allez hop ! Au boulot !

function Timer takes nothing returns nothing
     local timer t = GetExpiredTimer( )
     local integer compteur = GetHandleInt( t, "compteur" )
     local real x = GetUnitX( gg_unit_hfoo_0000 )
     local real y = GetUnitY( gg_unit_hfoo_0000 )
call SetUnitPosition( gg_unit_hfoo_0000, x+1, y )
endfunction

function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing
     local timer t = CreateTimer( )
     set udg_GC = InitGameCache( "map.w3v" )
     call SetHandleInt( t, "compteur", 500 )
     call TimerStart( t, .01, true, function Timer )
endfunction

Ici, on augmente la position X de notre footman de 1 par 0.01 secondes. Croyez-moi, c'est suffisant. Pas trop rapide, mais juste assez pour qu'on ne s'ennuie pas à mourir. Maintenant, il nous reste une seule chose à faire : détruire le timer. Nous allons ici enlever 1 à la valeur du compteur stocké dans le timer et insérer ensuite une condition. Si le compteur est à 0, alors on doit détruire le timer.

function Timer takes nothing returns nothing
     local timer t = GetExpiredTimer( )
     local integer compteur = GetHandleInt( t, "compteur" )
     local real x = GetLocationX( GetUnitLoc( gg_unit_hfoo_0000 ) )
     local real y = GetLocationY( GetUnitLoc( gg_unit_hfoo_0000 ) )
call SetUnitPosition( gg_unit_hfoo_0000, x+1, y )
   call SetHandleInt( t, "compteur", compteur-1 )
   if compteur == 0 then
     call FlushHandleLocals( t )
     call DestroyTimer( t )
   endif
endfunction

function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing
     local timer t = CreateTimer( )
     set udg_GC = InitGameCache( "map.w3v" )
     call SetHandleInt( t, "compteur", 500 )
     call TimerStart( t, .01, true, function Timer )
endfunction

FlushHandleLocals est une fonction de notre script importé et sert à enlever tout ce qui est stocké dans un handle.

On a déjà fini ! :D

Voici un exemple utilisant des timer (sans limite de temps) et un autre qui utilise des TriggerSleepAction, pour vous permettre de comparer. Comme vous pouvez le constater, l'unité peut se "débattre" si on n'utilise pas de timer.

Notez que, techniquement, on aurait également dû passer notre unité au timer, pour garder notre MUI. Qu'est-ce que le MUI ? Ah, merci de me le rappeler ! ^^


Passer des données à un timer. MUI

MUI

Utilité Hashtables

MUI

Multi-
Unit
Instanceability

Puisque le mot "instanceability" ne semble pas être dans le dictionnaire de la langue anglaise, j'en déduirai que c'est un mot inventé par des JASSers. MUI s'applique à un trigger qui peut être exécuté plus d'une fois au même moment sans qu'on ait de bugs. Les variables globales, par exemple, ne sont pas MUI, car si le trigger est déclenché deux fois en même temps, les deux instances utiliseront la même variable.

Un exemple de situation où on aurait besoin d'un TriggerMUI serait un sort de poison. Notre sort endommagerait une unité sur un certain temps. Si cet effet était sur plus d'une unité à la fois et que notre sort n'était pas MUI, une seule des deux unités serait endommagée. Ça peut être quelque peu troublant... Nous allons voir quelles méthodes sont MUI et quelles méthodes ne le sont pas.

Variables Globales

Nous allons créer un trigger qui affichera un nombre au hasard, attendra 5 secondes, puis le ré-affichera. Ce trigger se déclenchera à chaque 4 secondes, disons. On vérifiera aussi si une variable globale x n'est pas égale à 2 (on veut que le code se répète seulement 2 fois) . Si c'est le cas, nous allons augmenter x de 1 et ferons les actions. La valeur de x au départ sera, bien sûr, de 0. Commençons par mettre un Événement en GUI.

function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing
endfunction

//===========================================================================
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
    set gg_trg_Untitled_Trigger_001 = CreateTrigger(  )
    call TriggerRegisterTimerEventPeriodic( gg_trg_Untitled_Trigger_001, 4.00 )
    call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Actions )
endfunction

Créez une variable globale de type Integer et appelez-la comme vous voulez. Moi, je l'appellerai i. Nous allons lui assigner une valeur au hasard, avec GetRandomInt. Créez également un autre entier du nom de x. Leurs noms deviendront alors udg_i et udg_x.

set udg_i = GetRandomInt( 1, 100 )

Ensuite, nous allons afficher sa valeur avec un BJDebugMsg.

call BJDebugMsg( "La valeur de i dans la partie 1: " + I2S(udg_i) )

Il nous reste à mettre une attente et un autre BJDebugMsg.

call TriggerSleepAction( 5.00 )
call BJDebugMsg( "La valeur de i dans la partie 2: " + I2S(udg_i) )

Notre code deviendrait alors :

function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing
     set udg_i = GetRandomInt( 1, 100 )
     call BJDebugMsg( "La valeur de i dans la partie 1: " + I2S(udg_i) )
     call TriggerSleepAction( 5.00 )
     call BJDebugMsg( "La valeur de i dans la partie 2: " + I2S(udg_i) )
endfunction

//===========================================================================
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
    set gg_trg_Untitled_Trigger_001 = CreateTrigger(  )
    call TriggerRegisterTimerEventPeriodic( gg_trg_Untitled_Trigger_001, 4.00 )
    call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Actions )
endfunction

Il ne nous reste plus qu'à ajouter une condition : on veut seulement que ce trigger se déclenche deux fois.

function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing

if udg_x != 2 then
     set udg_x = udg_x + 1
     set udg_i = GetRandomInt( 1, 100 )
     call BJDebugMsg( "La valeur de i dans la partie 1: " + I2S(udg_i) )
     call TriggerSleepAction( 5.00 )
     call BJDebugMsg( "La valeur de i dans la partie 2: " + I2S(udg_i) )
endif

endfunction

//===========================================================================
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
    set gg_trg_Untitled_Trigger_001 = CreateTrigger(  )
    call TriggerRegisterTimerEventPeriodic( gg_trg_Untitled_Trigger_001, 4.00 )
    call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Actions )
endfunction
La valeur de i dans la partie 1: 78
La valeur de i dans la partie 1: 67
La valeur de i dans la partie 2: 67
La valeur de i dans la partie 2: 67

Voici le résultat obtenu. Il est à noter, tout d'abord, que les deux trigger vont arriver en même temps. Or, les deux utilisent la même variable i. Donc, forcément, les deux vont se "chevaucher" et alors, notre première exécution sera influencée par la seconde. Puisqu'on a changé la valeur de i entre le moment où on affiche le message 1 dans notre première exécution et le moment où on affiche le message 2, lorsqu'on affiche le second message, la valeur n'est plus la même.

Conclusion : les variables globales sont à bannir.

Variables Locales

Dans le cas des variables locales, ce n'est pas pareil. Notre fonction restera la même, mais nous changerons udg_i pour une variable locale.

function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing
     local integer i = GetRandomInt( 1, 100 )

if(udg_x != 2 )then
     set udg_x = udg_x + 1
     call BJDebugMsg( "La valeur de i dans la partie 1: " + I2S(i) )
     call TriggerSleepAction( 5.00 )
     call BJDebugMsg( "La valeur de i dans la partie 2: " + I2S(i) )
endif

endfunction

//===========================================================================
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
    set gg_trg_Untitled_Trigger_001 = CreateTrigger(  )
    call TriggerRegisterTimerEventPeriodic( gg_trg_Untitled_Trigger_001, 4.00 )
    call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Actions )
endfunction
La valeur de i dans la partie 1: 78
La valeur de i dans la partie 1: 67
La valeur de i dans la partie 2: 78
La valeur de i dans la partie 2: 67

Comme on peut le voir ici, les variables locales sont MUI : même si le trigger est exécuté dans deux instances différentes, elles ne s'influencent pas mutuellement.

Conclusion : vivent les variables locales !

Event Response

Si vous créez un Trigger en GUI et que vous créez une action quelconque, par exemple Unit - Kill, vous pourrez choisir une unité en fonction de l'événement qui a provoqué le déclenchement de votre Trigger.

Image utilisateur

Comme vous pouvez le constater, il y en a toute une beurrée. En fait, il y a un Event Response pour presque chaque événement que vous pouvez appeler. Par exemple, si vous utilisez l'événement Unit - A unit owned by Player 1 (Red) Is attacked, vous aurez Event Response - Attacked Unit et Event Response - Attacking Unit qui pourront vous servir. Triggering Unit s'applique à presque tous les trigger qui impliquent une unité et désigne la principale unité.

La raison pour laquelle je vous dis ça, c'est que les Réponses d'Événements sontMUI. Si, par exemple, on créait un Trigger qui tuait une unité au hasard à chaque 2 secondes et qu'on en créait un autre qui détectait la mort d'une unité, ce serait MUI. D'ailleurs, nous allons le faire, suivant le même modèle qu'avec l'entier i.

Voici le code Trigger qui tue une unité au hasard :

function Trig_Untitled_Trigger_001_Actions takes nothing returns nothing

     call KillUnit( GroupPickRandomUnit(GetUnitsInRectAll(GetPlayableMapRect( ))))

endfunction

//===========================================================================
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
    set gg_trg_Untitled_Trigger_001 = CreateTrigger(  )
    call TriggerRegisterTimerEventPeriodic( gg_trg_Untitled_Trigger_001, 2.00 )
    call TriggerAddAction( gg_trg_Untitled_Trigger_001, function Trig_Untitled_Trigger_001_Actions )
endfunction

Notez qu'on n'a pas eu besoin de limiter le nombre d'exécutions cette fois : on placera seulement 2 unités.

Maintenant, voici le code du Trigger qui réagit à la mort d'une unité. GetDyingUnit est la réponse à l'événement, soit la mort d'une unité.

function Trig_Untitled_Trigger_002_Actions takes nothing returns nothing

    call BJDebugMsg("Nom de l'unité qui vient de mourir: " + GetUnitName(GetDyingUnit( )) )
    call TriggerSleepAction( 10.00 )
    call BJDebugMsg("Nom de l'unité qui est morte plus tôt: " + GetUnitName(GetDyingUnit( )) )

endfunction

//===========================================================================
function InitTrig_Untitled_Trigger_002 takes nothing returns nothing
    set gg_trg_Untitled_Trigger_002 = CreateTrigger(  )
    call TriggerRegisterPlayerUnitEventSimple( gg_trg_Untitled_Trigger_002, Player(0), EVENT_PLAYER_UNIT_DEATH )
    call TriggerAddAction( gg_trg_Untitled_Trigger_002, function Trig_Untitled_Trigger_002_Actions )
endfunction
Nom de l'unité qui vient de mourir: Grunt
Nom de l'unité qui vient de mourir: Ghoul
Nom de l'unité morte plus tôt: Grunt
Nom de l'unité morte plus tôt: Ghoul

Les deux instances n'ont aucunement interagit. Les réponses aux événements sont donc MUI.

Variables de Handle Locaux

Enfin, la partie qu'on voulait voir. Les variables de handle locaux sont, bien sûr, MUI, car chaque handle a un identifiant unique. Il est physiquement impossible d'avoir deux handle qui ne pointent pas vers le même objet qui ont le même identifiant. Voilà pourquoi c'est MUI. Si on a un Trigger qui est en cours pour deux instances différentes, chaque instance aura ses propres variables locales. Donc, forcément, chaque instance aura ses propres variables de handle locaux.

Nous avons déjà créé un Trigger qui prouve que nos variables de handle locaux sont MUI : le code qui prouvait que le timer était le même dans la fonction qui appelle le timer et celle appelée par le timer. Déjà oublié, hein ? C'était dans la partie sur les timer.


Utilité Hashtables

Hashtables

MUI common.j

Hashtables

Avec la version 1.23b de Warcraft, le bug de H2I n'existe plus, donc, forcément, tout notre script de Variables de Handle Locaux s'effondre. Heureusement, avec ce même patch sont arrivés les Hashtables.

Oui oui, la verison 1.23b a été très chiante pour tous les JASSers. Cependant, pour tous ceux qui arrivent après, la tâche est simplifiée. Les Hashtables fonctionnent comme les Game Cache, sauf qu'on peut y stocker tous les types de handle avec un parent. Les différences sont que les Hashtables ne sont pas transférables de map en map et que les identifiants des valeurs dans notre Hashtable sont des integer.

Vous pouvez trouver le script complet des Hashtablesici. Non, vous n'avez pas besoin de le mettre dans votre map, il est déjà dedans, c'est seulement qu'il est d'une version trop récente pour se trouver sur jass.sourceforge.net. Les fonctions les plus importantes sont d'ailleurs surlignées.

Nous devons commencer par initialiser notre Hashtable et le stocker dans une variable de type hashtable.

function maFonction takes nothing returns nothing
    local hashtable h = InitHashtable ( )
endfunction

Maintenant, nous allons créer un timer. Rappelez-vous que nous sommes partis sur une base de MUI et que nous ne voulons pas perdre cet avantage.

function maFonction takes nothing returns nothing
    local hashtable h = InitHashtable( )
    local timer t = CreateTimer( )
endfunction

Nous pouvons maintenant stocker une valeur... disons un integer, dans notre hashtable.

native  SaveInteger                            takes hashtable table, integer parentKey, integer childKey, integer value returns nothing

SaveInteger, tout comme StoreInteger, nous permet de stocker un entier dans notre hashtable. Cependant, notez bien que le parentKey, qui remplace le missionKey, est un integer. Le childKey également. Il n'y a plus de string dans tout ceci. Nous allons maintenant stocker un integer dans notre timer.

function maFonction takes nothing returns nothing
    local hashtable h = InitHashtable( )
    local timer t = CreateTimer( )
    call SaveInteger(h, GetHandleId(t), 1, GetRandomInt(1, 100))
endfunction

Et on n'a même pas besoin d'importer un script ! Maintenant, si on devait retrouver notre valeur, on ferait :

function maFonction takes nothing returns nothing
    local integer i
    local hashtable h = InitHashtable( )
    local timer t = CreateTimer( )
    call SaveInteger(h, GetHandleId(t), 1, GetRandomInt(1, 100))
    set i = LoadInteger(h, GetHandleId(t), 1)
endfunction

Ainsi, notre variable i aurait la valeur de l'entier stocké dans notre timer. Notez que notre identifiant, 1, ne peut être utilisé que pour une variable dans le handle. Cela peut être beaucoup plus mélangeant que les string. Ou ça peut ne pas l'être. Question de perceptions.

Et pour détruire une valeur dans le Hashtable ?...

native  RemoveSavedInteger                                        takes hashtable table, integer parentKey, integer childKey returns nothing
native  RemoveSavedReal                                                takes hashtable table, integer parentKey, integer childKey returns nothing
native  RemoveSavedBoolean                                        takes hashtable table, integer parentKey, integer childKey returns nothing
native  RemoveSavedString                                        takes hashtable table, integer parentKey, integer childKey returns nothing
native  RemoveSavedHandle                                        takes hashtable table, integer parentKey, integer childKey returns nothing

native  FlushParentHashtable                                                takes hashtable table returns nothing
native  FlushChildHashtable                                        takes hashtable table, integer parentKey returns nothing

Les premiers sont assez faciles à comprendre, comme pour les Game Cache. FlushParentHashtable détruit tout ce qui est stocké à l'intérieur d'un Hashtable (et non un parentKey, ne pas vous méprendre). FlushChildHashtable détruit tout ce qui est dans un parentKey (et non un childKey, idem pour ça).

Avantages

Comme vous l'aurez remarqué, le parentKey est un integer. Avec le script de Variables de Handles Locaux, le missionKey était un string. Or, on passait un handle, qui devait ensuite être converti en integer, puis en string, puis stocké dans notre Game Cache. Cependant, la fonction S2I prend beaucoup de temps lors de sa première exécution pour le même nombre. Donc, si on saute cette étape, notre code sera un peu plus rapide.

StringHash
native StringHash takes string s returns integer

Ceux qui ont de l'oeil auront remarqué que le modèle de cette fonction est le même que S2I. La différence est que cette fonction a été ajoutée avec le patch 1.23b et qu'elle permet de changer n'importe quel string en integer. N'importe quel. Même "abc" deviendra un entier. Un entier unique. Pour ceux qui n'aiment pas utiliser des nombres pour nommer leur childKey, on peut plutôt utiliser le StringHash d'un nom, qui sera ainsi changé en entier.

À mon humble avis, cette alternative ne fait que ralentir l'exécution : on appelle une fonction qui n'est pas réellement nécessaire. Pour d'autres, cependant, le code peut sembler beaucoup plus simple. De plus, cette fonction ne semble pas ralentir la map tant que ça. Sans parler du fait que c'est toujours plus rapide que nos variables de handle locaux.

Je suggère fortement à tous ceux qui ont lu ce tutoriel d'un bout à l'autre de commencer à créer des maps. Comme je l'ai déjà mentionné, la meilleure seule manière d'apprendre, c'est par l'expérience. Et l'expérience s'acquiert par essais et erreurs. Donc, essayez, faites des erreurs, et envoyez-moi un message si jamais vous êtes désespéré, en train de pleurer ou avez envie de jeter votre ordinateur par la fenêtre.


MUI common.j

common.j

Hashtables Hashtables

Voici les scripts qui sont dans common.j, mais qui ont été ajoutés après la version 1.23b et qui ne sont donc pas sur jass.sourceforge.net.

Hashtables

common.j

Hashtables

native  InitHashtable    takes nothing returns hashtable

native  SaveInteger                            takes hashtable table, integer parentKey, integer childKey, integer value returns nothing
native  SaveReal                                                takes hashtable table, integer parentKey, integer childKey, real value returns nothing
native  SaveBoolean                                                takes hashtable table, integer parentKey, integer childKey, boolean value returns nothing
native  SaveStr                                                        takes hashtable table, integer parentKey, integer childKey, string value returns boolean
native  SavePlayerHandle                                takes hashtable table, integer parentKey, integer childKey, player whichPlayer returns boolean
native  SaveWidgetHandle                                takes hashtable table, integer parentKey, integer childKey, widget whichWidget returns boolean
native  SaveDestructableHandle                        takes hashtable table, integer parentKey, integer childKey, destructable whichDestructable returns boolean
native  SaveItemHandle                                        takes hashtable table, integer parentKey, integer childKey, item whichItem returns boolean
native  SaveUnitHandle                                        takes hashtable table, integer parentKey, integer childKey, unit whichUnit returns boolean
native  SaveAbilityHandle                                takes hashtable table, integer parentKey, integer childKey, ability whichAbility returns boolean
native  SaveTimerHandle                                        takes hashtable table, integer parentKey, integer childKey, timer whichTimer returns boolean
native  SaveTriggerHandle                                takes hashtable table, integer parentKey, integer childKey, trigger whichTrigger returns boolean
native  SaveTriggerConditionHandle                takes hashtable table, integer parentKey, integer childKey, triggercondition whichTriggercondition returns boolean
native  SaveTriggerActionHandle                        takes hashtable table, integer parentKey, integer childKey, triggeraction whichTriggeraction returns boolean
native  SaveTriggerEventHandle                        takes hashtable table, integer parentKey, integer childKey, event whichEvent returns boolean
native  SaveForceHandle                                        takes hashtable table, integer parentKey, integer childKey, force whichForce returns boolean
native  SaveGroupHandle                                        takes hashtable table, integer parentKey, integer childKey, group whichGroup returns boolean
native  SaveLocationHandle                                takes hashtable table, integer parentKey, integer childKey, location whichLocation returns boolean
native  SaveRectHandle                                        takes hashtable table, integer parentKey, integer childKey, rect whichRect returns boolean
native  SaveBooleanExprHandle                        takes hashtable table, integer parentKey, integer childKey, boolexpr whichBoolexpr returns boolean
native  SaveSoundHandle                                        takes hashtable table, integer parentKey, integer childKey, sound whichSound returns boolean
native  SaveEffectHandle                                takes hashtable table, integer parentKey, integer childKey, effect whichEffect returns boolean
native  SaveUnitPoolHandle                                takes hashtable table, integer parentKey, integer childKey, unitpool whichUnitpool returns boolean
native  SaveItemPoolHandle                                takes hashtable table, integer parentKey, integer childKey, itempool whichItempool returns boolean
native  SaveQuestHandle                                        takes hashtable table, integer parentKey, integer childKey, quest whichQuest returns boolean
native  SaveQuestItemHandle                                takes hashtable table, integer parentKey, integer childKey, questitem whichQuestitem returns boolean
native  SaveDefeatConditionHandle                takes hashtable table, integer parentKey, integer childKey, defeatcondition whichDefeatcondition returns boolean
native  SaveTimerDialogHandle                        takes hashtable table, integer parentKey, integer childKey, timerdialog whichTimerdialog returns boolean
native  SaveLeaderboardHandle                        takes hashtable table, integer parentKey, integer childKey, leaderboard whichLeaderboard returns boolean
native  SaveMultiboardHandle                        takes hashtable table, integer parentKey, integer childKey, multiboard whichMultiboard returns boolean
native  SaveMultiboardItemHandle                takes hashtable table, integer parentKey, integer childKey, multiboarditem whichMultiboarditem returns boolean
native  SaveTrackableHandle                                takes hashtable table, integer parentKey, integer childKey, trackable whichTrackable returns boolean
native  SaveDialogHandle                                takes hashtable table, integer parentKey, integer childKey, dialog whichDialog returns boolean
native  SaveButtonHandle                                takes hashtable table, integer parentKey, integer childKey, button whichButton returns boolean
native  SaveTextTagHandle                                takes hashtable table, integer parentKey, integer childKey, texttag whichTexttag returns boolean
native  SaveLightningHandle                                takes hashtable table, integer parentKey, integer childKey, lightning whichLightning returns boolean
native  SaveImageHandle                                        takes hashtable table, integer parentKey, integer childKey, image whichImage returns boolean
native  SaveUbersplatHandle                                takes hashtable table, integer parentKey, integer childKey, ubersplat whichUbersplat returns boolean
native  SaveRegionHandle                                takes hashtable table, integer parentKey, integer childKey, region whichRegion returns boolean
native  SaveFogStateHandle                                takes hashtable table, integer parentKey, integer childKey, fogstate whichFogState returns boolean
native  SaveFogModifierHandle                        takes hashtable table, integer parentKey, integer childKey, fogmodifier whichFogModifier returns boolean
native  SaveAgentHandle                                        takes hashtable table, integer parentKey, integer childKey, agent whichAgent returns boolean
native  SaveHashtableHandle                                takes hashtable table, integer parentKey, integer childKey, hashtable whichHashtable returns boolean


native  LoadInteger                                        takes hashtable table, integer parentKey, integer childKey returns integer
native  LoadReal                                        takes hashtable table, integer parentKey, integer childKey returns real
native  LoadBoolean                                    takes hashtable table, integer parentKey, integer childKey returns boolean
native  LoadStr                                         takes hashtable table, integer parentKey, integer childKey returns string
native  LoadPlayerHandle                        takes hashtable table, integer parentKey, integer childKey returns player
native  LoadWidgetHandle                        takes hashtable table, integer parentKey, integer childKey returns widget
native  LoadDestructableHandle                takes hashtable table, integer parentKey, integer childKey returns destructable
native  LoadItemHandle                                takes hashtable table, integer parentKey, integer childKey returns item
native  LoadUnitHandle                                takes hashtable table, integer parentKey, integer childKey returns unit
native  LoadAbilityHandle                        takes hashtable table, integer parentKey, integer childKey returns ability
native  LoadTimerHandle                                takes hashtable table, integer parentKey, integer childKey returns timer
native  LoadTriggerHandle                        takes hashtable table, integer parentKey, integer childKey returns trigger
native  LoadTriggerConditionHandle        takes hashtable table, integer parentKey, integer childKey returns triggercondition
native  LoadTriggerActionHandle                takes hashtable table, integer parentKey, integer childKey returns triggeraction
native  LoadTriggerEventHandle                takes hashtable table, integer parentKey, integer childKey returns event
native  LoadForceHandle                                takes hashtable table, integer parentKey, integer childKey returns force
native  LoadGroupHandle                                takes hashtable table, integer parentKey, integer childKey returns group
native  LoadLocationHandle                        takes hashtable table, integer parentKey, integer childKey returns location
native  LoadRectHandle                                takes hashtable table, integer parentKey, integer childKey returns rect
native  LoadBooleanExprHandle                takes hashtable table, integer parentKey, integer childKey returns boolexpr
native  LoadSoundHandle                                takes hashtable table, integer parentKey, integer childKey returns sound
native  LoadEffectHandle                        takes hashtable table, integer parentKey, integer childKey returns effect
native  LoadUnitPoolHandle                        takes hashtable table, integer parentKey, integer childKey returns unitpool
native  LoadItemPoolHandle                        takes hashtable table, integer parentKey, integer childKey returns itempool
native  LoadQuestHandle                                takes hashtable table, integer parentKey, integer childKey returns quest
native  LoadQuestItemHandle                        takes hashtable table, integer parentKey, integer childKey returns questitem
native  LoadDefeatConditionHandle        takes hashtable table, integer parentKey, integer childKey returns defeatcondition
native  LoadTimerDialogHandle                takes hashtable table, integer parentKey, integer childKey returns timerdialog
native  LoadLeaderboardHandle                takes hashtable table, integer parentKey, integer childKey returns leaderboard
native  LoadMultiboardHandle                takes hashtable table, integer parentKey, integer childKey returns multiboard
native  LoadMultiboardItemHandle        takes hashtable table, integer parentKey, integer childKey returns multiboarditem
native  LoadTrackableHandle                        takes hashtable table, integer parentKey, integer childKey returns trackable
native  LoadDialogHandle                        takes hashtable table, integer parentKey, integer childKey returns dialog
native  LoadButtonHandle                        takes hashtable table, integer parentKey, integer childKey returns button
native  LoadTextTagHandle                        takes hashtable table, integer parentKey, integer childKey returns texttag
native  LoadLightningHandle                        takes hashtable table, integer parentKey, integer childKey returns lightning
native  LoadImageHandle                                takes hashtable table, integer parentKey, integer childKey returns image
native  LoadUbersplatHandle                        takes hashtable table, integer parentKey, integer childKey returns ubersplat
native  LoadRegionHandle                        takes hashtable table, integer parentKey, integer childKey returns region
native  LoadFogStateHandle                        takes hashtable table, integer parentKey, integer childKey returns fogstate
native  LoadFogModifierHandle                takes hashtable table, integer parentKey, integer childKey returns fogmodifier
native  LoadHashtableHandle                        takes hashtable table, integer parentKey, integer childKey returns hashtable

native  HaveSavedInteger                                        takes hashtable table, integer parentKey, integer childKey returns boolean
native  HaveSavedReal                                                takes hashtable table, integer parentKey, integer childKey returns boolean
native  HaveSavedBoolean                                        takes hashtable table, integer parentKey, integer childKey returns boolean
native  HaveSavedString                                            takes hashtable table, integer parentKey, integer childKey returns boolean
native  HaveSavedHandle                                     takes hashtable table, integer parentKey, integer childKey returns boolean

native  RemoveSavedInteger                                        takes hashtable table, integer parentKey, integer childKey returns nothing
native  RemoveSavedReal                                                takes hashtable table, integer parentKey, integer childKey returns nothing
native  RemoveSavedBoolean                                        takes hashtable table, integer parentKey, integer childKey returns nothing
native  RemoveSavedString                                        takes hashtable table, integer parentKey, integer childKey returns nothing
native  RemoveSavedHandle                                        takes hashtable table, integer parentKey, integer childKey returns nothing

native  FlushParentHashtable                                                takes hashtable table returns nothing
native  FlushChildHashtable                                        takes hashtable table, integer parentKey returns nothing

Ce tutoriel n'est pas fini. Il manque une partie sur la création de sorts et une partie sur le vJASS. Pour vous rendre encore plus impatients, le vJASS est le JASS orienté objet. Oui oui, ça existe. ^^


common.j