Inscrit le: 14 Oct 2009 Messages: 719 Sujets: 40 Spécialité en worldedit: Les bugs Médailles: 1 (En savoir plus...)
Posté le: 19/05/12 10:15 Sujet du message: Introduction au vJass
J'ai remarqué qu'il n'y avait aucun tutoriel pour le vJass sur ce forum, j'ai donc décidé d'en faire un, d'une part parce qu'il y a plusieurs demandes, d'autre part parce que c'est toujours plus agréable que d'aller sur un forum anglais. Ce tutoriel expliquera les bases du vJass, des globals jusqu'aux structures.
Il n'est pas indispensable de tout connaître du Jass, on peut directement passer du Gui au vJass, mais il est vivement recommandé d'avoir quelques notions.
I/ Le vJass, c'est quoi?
a) Explications
Le vJass est tout simplement une extension du Jass, permettant de le manipuler beaucoup plus facilement. Il faut cependant garder à l'esprit que du vJass restera du Jass une fois compilé. Il a donc les même limitations et les mêmes capacités, bien qu'il soit plus facile d'utilisation, offrant entre autre une interface orientée objet. Le moyen le plus simple de s'en servir est d'utiliser le Jass NewGen Pack que vous pourrez télécharger ici. Ce programme peut être considéré comme un virus par votre ordinateur, mais ça n'en est pas un. Si vous rencontrez des problèmes, mettez le sur la liste d'exception de votre anti-virus.
En plus de donner la possibilité de coder en vJass, cet outil offre aussi de nouvelles fonctionnalités assez intéressantes :
- Création de cartes plus grandes qu'avec l'éditeur officiel;
- Édition des textures sol;
- Possibilité de choisir soit même l'identifiant d'un objet;
- Activation du mode sans limite permettant de placer autant de destructibles sur la carte que l'on veut entre autre.
b) Mise à jour du JassHelper
Avant d'utiliser du vJass, il est préférable de se servir de la dernière version du JassHelper. Une fois le Jass NewGen Pack téléchargé, extrayez-le puis téléchargez ce fichier. Extrayez-le puis ouvrez-le, et ouvrez le sous-dossier executable. Copiez les trois fichiers et collez-les dans le dossier jasshelper du dossier jassnewgenpack que vous avez extrait tout à l'heure. Confirmez pour remplacer les fichiers déjà existants.
Le JassHelper étant souvent amélioré, je poste ici le lien pour le tenir à jour.
Voilà, votre JassHelper est mis à jour. Notez que si vous utilisez du vJass, il faut enregistrer la carte avant de pouvoir la tester.
II/ La déclaration de variables globales
Au lieu de passer par la petite boîte de dialogue habituelle pour créer des variables globales (qui n'est d'ailleurs pas pratique du tout), le vJass permet de les déclarer à l'intérieur même du code. Au début vous trouverez sûrement cette possibilité assez inutile, mais cela va devenir intéressant lorsque nous utiliseront l'encapsulation (qui fera l'objet de notre quatrième partie).
Mais pour l'instant nous n'allons que nous attarder sur la déclaration en elle même de ces variables. Pour ce faire, il suffit de préciser le type de variable, suivi de son nom, et d'une éventuelle valeur par défaut, le tout dans un bloc globals :
Jass:
globals
unit Heros = null
endglobals
Vous pouvez bien-sûr déclarer plusieurs variables dans le même bloc de cette manière :
Jass:
globals
unit Heros = null
integer level = 1
endglobals
Pour des variables à déploiement, c'est le même principe :
Jass:
globals
unit array Heros
endglobals
Il est aussi possible de déclarer des variables ayant un déploiement supérieur à 8191 ou en deux dimension :
Jass:
globals
unit array Heros1[15000]
unit array Heros2[50][50]
endglobals
Soyez cependant vigilant avec ces types de variable : si le déploiement dépasse 8191, plusieurs variables seront créées lors de la compilation, et le code sera moins performant, utilisant une fonction pour se référer à la bonne variable.
Ces variables seront donc accessibles dans toutes les fonctions de la carte, mais en plus de cela, nous n'avons plus besoin de préciser le mot-clef udg_.
Ainsi cette fonction est tout à fait correcte, pourtant la variable Heros n'est pas précédée de udg_ :
Jass:
function KillHeros takes nothing returns nothing
call KillUnit(Heros)
endfunction
Notez aussi que les variables globales peuvent être constantes, et ne seront donc pas modifiables au cours du jeu. Cela peut être utile pour paramétrer plus facilement un sort ou un système, sans avoir à chercher toutes les valeurs dans le code :
Jass:
globals
constant real TIMEOUT = 0.03
endglobals
III/ Les bibliothèques et les scopes
Les bibliothèques (library en anglais) et les scopes permettent chacune de classer le code, et de le rendre plus réutilisable.
a) Les bibliothèques
Il n'est pas toujours pratique d'utiliser le script personnalisé de la carte pour déclarer de nouvelle fonctions : lorsque l'on en a plus d'une vingtaine, et que certaines en requièrent d'autres qui doivent être placées plus haut, cela devient très vite difficile de tout classer correctement.
Les librairies permettent justement de classer notre code, et de le placer dans le bon ordre dans le script personnalisé lors de la compilation.
Voici la syntaxe à suivre pour déclarer un librairie :
Jass:
library MaLibrairie
endlibrary
Il suffit ensuite de placer nos fonctions à l'intérieur pour être certain qu'elles soient ensuite utilisables par toutes les autres fonctions :
Il est aussi possible de déclarer des globals à l'intérieur même d'une librairie :
Jass:
library KillHeros
globals
unit Heros = CreateUnit(Player(0), 'Hpal', 0, 0, 0)
endglobals
function KillHeros takes nothing returns nothing
call KillUnit(Heros)
endfunction
endlibrary
Si une librairie en nécessite une autre, et donc que cette dernière doit être placée plus haut dans le script personnalisé de la carte, il faut utiliser les attributs needs, uses, ou encore requires :
Jass:
library MaLibrairie uses Heros
endlibrary
library Heros
endlibrary
Attention cependant aux boucles : si la librairie Heros nécessitait aussi la librairie MaLibrairie, cela déclencherait une erreur.
Les librairies permettent aussi de déclarer des initializers, qui sont des fonctions appelées à l'initialisation de la carte :
Jass:
library Heros initializer onInit
globals
unit Heros
endglobals
function onInit takes nothing returns nothing
set Heros = CreateUnit(Player(0), 'Hpal', 0, 0, 0)
endfunction
endlibrary
b) Les scopes
Les scopes reposent sur le même principe que les librairies : elles regroupent le code. Elles ne le placent cependant pas au début du script personnalisé de la carte, il ne sera donc pas utilisable par toutes les fonctions, mais pourra toutes les utiliser (du moins celles faisant partie de librairies).
Jass:
scope MySpell initializer onInit
function Action takes nothing returns nothing
call KillUnit(GetSpellTargetUnit())
endfunction
function onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
loop
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddAction(t, function Action)
endfunction
endscope
IV/ L'encapsulation
Une des choses les plus intéressantes du vJass reste l'encapsulation. Cela permet de rendre une donnée, comme une variable ou une fonction, privée, la rendant inaccessible pour d'autres blocs. On pourra ainsi déclarer plusieurs fois la fonction onInit, en faisant attention de la privatiser, sans risque d'erreur. De même que si vous récupérer du code créé par une autre personne, vous ne risquerez pas de modifier des données qui ne devraient pas l'être.
Pour encapsuler une donnée, il est nécessaire qu'elle appartienne à un bloc, que ce soit une librairie, une scope, ou une structure (que nous étudierons dans notre cinquième partie) : seules les fonctions à l'intérieur de ce bloc pourront y accéder. Pour ce faire, il suffit qu'elle soit précédée de l'attribut private.
function KillHeros takes nothing returns nothing
call KillUnit(Heros)
endfunction
endlibrary
Ce code déclenchera une erreur : la variable Heros étant privée, il est impossible que la fonction KillHeros puisse y accéder n'appartenant pas au même bloc.
Jass:
scope MySpell1 initializer onInit
private function onInit takes nothing returns nothing
//Initialisation du sort
endfunction
endscope
scope MySpell2 initializer onInit
private function onInit takes nothing returns nothing
//Inititalisation du sort
endfunction
endscope
De même que ce code ne sera pas source d'erreur : les deux fonction onInit étant privées, il est possible de les déclarer deux fois. Pour que cela soit possible, le compilateur vJass rajoutera tout simplement les préfixes MySpell1_ et MySpell2_ pour que chacunes des fonctions aient des noms différents.
Notez qu'il existe aussi l'attribut public, qui lui, rend la donnée accessible dans toute la carte. Il n'est cependant pas nécessaire de l'utiliser, toutes les données étant publiques par défaut.
V/ Les structures
a) Notion de structure
Une structure est un regroupement d'éléments caractéristiques à un objet. Tout objet peut donc être considéré comme une structure, qu'il soit concret (une maison), ou abstrait (un point). Une fois déclaré, cet objet peut être utilisé comme une variable : les structures permettent de créer nos propres types de variables.
Dans l'exemple du point, une structure le définissant contiendrait trois données, trois réels, chacun caractérisant une coordonnée.
En vJass, on peut donc s'en servir pour toute sorte de choses, mais elles se limitent le plus souvent à une sauvegarde de données. On les déclare de cette manière :
Jass:
struct MaStruct
endstruct
b) Les attributs d'une structure
Pour mieux expliquer comment fonctionne une structure, nous utiliserons l'exemple d'un héros. Nous ne considèrerons que cinq de ses attributs : l'unité en elle-même, le niveau du héros, sa force, son agilité, et pour finir son intelligence.
Les attributs de la structure se déclarent un peu de la même manière que les globals :
Il est aussi possible de leur donner une valeur par défaut.
Voilà notre structure déclarée, nous allons pouvoir nous en servir. Pour créer une instance de structure, on utilise la fonction create précédée d'un point et du type de structure :
function CreateHeros takes nothing returns nothing
local Heros h = Heros.create()
endfunction
On crée donc ici une instance de la structure et on l'assigne à une variable : comme je vous l'ai dit tout à l'heure, une structure est un type de variable. Maintenant que cela est fait, on va pouvoir modifier les attributs de notre héros : on va ainsi créer l'unité, lui ajouter un niveau, de la force, de l'agilité et de l'intelligence. Pour se référer à un attribut d'une instance de structure, on utilise un point suivi du nom de l'attribut :
function CreateHeros takes nothing returns nothing
local Heros h = Heros.create()
set h.whichUnit = CreateUnit(Player(0), 'H000', 0, 0, 0)
set h.level = 1
set h.strenght = 16
set h.agility = 14
set h.intelligence = 20
endfunction
Les membres whichUnit, level, strenght, agility, et intelligence ne seront donc que modifiés pour l'instance de structure en question, h.
c) Les fonctions propres à une structure
Dans le cas de notre héros, certaines fonctions comme lui faire gagner un niveau seraient utiles : ce sont des fonctions propres à la structure, que l'on appelle des methods. Une méthode fonctionne de la même manière qu'une fonction : elle peut prendre des arguments, et peut retourner une valeur. De même que pour l'appeler, on utilise aussi call mais étant un attribut de la structure, on s'y réfère de la même manière que pour les données, à savoir avec un point suivi du nom de la méthode.
Nous allons donc créer une méthode augmentant le niveau du héros :
method LevelUp takes nothing returns nothing
set this.level = this.level + 1
set this.strenght = this.strenght + 4
set this.agility = this.agility + 3
set this.intelligence = this.intelligence + 5
call DestroyEffect(AddSpecialEffectTarget(this.whichUnit, "Abilities\\Spells\\Human\\ReviveHuman\\ReviveHuman.mdl", "origin"))
endmethod
endstruct
function CreateHeros takes nothing returns nothing
local Heros h = Heros.create()
set h.whichUnit = CreateUnit(Player(0), 'H000', 0, 0, 0)
set h.level = 1
set h.strenght = 16
set h.agility = 14
set h.intelligence = 20
call h.LevelUp()
endfunction
Cela demande quelques explications tout de même. La méthode LevelUp n'est appelée que pour l'instance de structure en question : les autres Heros ne verront pas leur niveau augmenter.
L'attribut this à l'intérieur de la méthode permet donc de se référer à l'instance de structure pour laquelle elle a été appelée.
d) Les membres statiques
Comme nous l'avons vu, les membres de notre structure n'appartiennent qu'à une instance de celle-ci. Or il peut être utile d'avoir des données qui sont communes à toutes les instances de cette structure, comme par exemple le nombre de héros créés. Au lieu d'utiliser un bloc globals, on peut se servir des membres statiques. On utilise l'attribut static pour les définir :
function CreateHeros takes nothing returns nothing
local Heros h = Heros.create()
set h.whichUnit = CreateUnit(Player(0), 'H000', 0, 0, 0)
set h.level = 1
set h.strenght = 16
set h.agility = 14
set h.intelligence = 20
set h.count = h.count + 1
endfunction
Vous remarquerez que l'on utilise h.count mais il est aussi possible de se servir de Heros.count pour s'y référer, count étant un attribut propre à la structure elle-même et non à l'instance h.
function CreateHeros takes nothing returns nothing
local Heros h = Heros.create()
set h.whichUnit = CreateUnit(Player(0), 'H000', 0, 0, 0)
set h.level = 1
set h.strenght = 16
set h.agility = 14
set h.intelligence = 20
call h.CountUp()
endfunction
De même qu'à la place de h.CountUp(), Heros.CountUp() aurait pu fonctionner.
Faîtes cependant attention à une chose : dans la méthode CountUp, nous avons utilisé Heros.count. Il aurait été possible de n'écrire que count, les membres statiques étant considéré comme des variables globales à l'intérieur de la structure. De même pour les méthodes, à l'intérieur de la structure, il est possible de s'y référer en n'écrivant que le nom. Mais cette méthode est à éviter : le code est plus clair en précisant le nom de la structure.
Il faut aussi savoir qu'il existe un mot-clef, thistype (ce type en anglais), renvoyant au type de structure dans laquelle on travaille (il n'est utilisable qu'à l'intérieur d'une structure). Il aurait donc aussi été possible d'écrire thistype.count, thistype étant le type Heros à l'intérieur de cette structure. C'est une convention : plutôt que d'écrire le type de structure, il vaut mieux utiliser thistype.
e) Les constructeurs
Vous aurez peut-être remarqué que nous utilisons la méthode create que nous n'avons pourtant pas déclarée. Cette méthode ainsi que la méthode destroy sont automatiquement créées lorsque vous définissez une structure. Mais il est possible de les modifier en les déclarant nous même. Dans l'exemple de notre héros, il serait préférable de regrouper la fonction CreateHeros avec notre structure, étant elle aussi un membre qui lui est propre :
static method create takes player p, integer id, real x, real y, real a returns thistype
local thistype this = thistype.allocate()
set this.whichUnit = CreateUnit(p, id, x, y, a)
set this.level = 1
set this.strenght = 16
set this.agility = 14
set this.intelligence = 20
return this
endmethod
endstruct
Vous remarquerez d'abord que cette méthode est static. Toutes les méthodes créant une instance de structure doivent être statiques puisque ce ne sont pas des fonctions propres à une instance, mais bien à l'ensemble de la structure. De même que cette méthode retourne thistype, ce qui permet de le stocker dans une variable. Notez aussi que notre variable s'appelle this. Dans les méthodes statiques, cette variable étant libre (elle n'est pas utilisée pour se référer à l'instance de structure comme dans les méthodes non statiques), c'est une convention de l'utiliser.
Mais le plus important réside dans le thistype.allocate(). La méthode allocate est créée directement lorsque vous déclarez une structure. Elle permet de renvoyer une nouvelle instance de structure. Lorsque vous ne modifiez pas la méthode create, elle ne ressemble qu'à cela :
De même qu'il serait utile de modifier la méthode destroy pour exécuter du code lorsqu'elle est appelée. A l'inverse de la méthode create, la méthode destroy n'est pas statique : elle est propre à une instance de structure :
Vous remarquerez la méthode deallocate, qui à l'inverse de la méthode allocate permet de libérer une instance de structure.
Nous allons donc modifier cette méthode de cette manière :
Les structures étant limitées à 8190 instances simultanées, la méthode destroy est importante : elle libère une instance de structure.
f) Quelques précisions
- Une structure ainsi que ses membres (variables et méthodes) peuvent être privés.
- Il faut savoir qu'une instance de structure n'est en fait rien d'autre qu'un entier. Lors de la compilation en Jass, les attributs de la structure deviendront des variables à déploiement. La méthode allocate renvoie donc un entier pour lequel le déploiement des variables est libre, d'où la limite de 8190 instances.
- Les membres de la structure devenant des variables à déploiement, on comprend bien que nous rencontrerons des problème à déclarer un membre à déploiement dans une structure. Le vJass permet cependant de le faire, mais il faut préciser le nombre de déploiement :
Jass:
struct MaStruct
unit array whichUnit[5]
endstruct
Si vous pouvez ne pas le faire, évitez de déclarer un membre à déploiement : ici le nombre d'instances simultanées possibles de MaStruct seront divisées par 5, soit 1638. Pour les membres statiques, vous ne rencontrerez aucun problème étant des variables normales.
- Si jamais vous avez besoin de plus de 8190 instances de structures, c'est possible de le faire de cette manière :
Jass:
struct MaStruct[15000]
endstruct
Encore une fois, c'est à éviter, le code devenant moins performant : plusieurs variables à déploiement sont créées, et pour se référer à une de ces variables, une fonction doit être appelée pour savoir laquelle est la bonne.
- Il existe la méthode onInit (qui doit être statique) se déclenchant à l'initialisation de la carte, sans avoir besoin de préciser un initializer comme pour les librairies ou les scopes. De même que la méthode onDestroy (qui ne doit pas être statique se référant à une instance) se déclenche à la destruction d'une instance. Elle est cependant à éviter : c'est un appel de fonction inutile, la méthode destroy pouvant servir à éxecuter du code.
- Les méthode allocate et deallocate sont créées automatiquement pour faciliter l'utilisation des structures. Il existe cependant un moyen de tout gérer soit même :
Jass:
struct MaStruct extends array
endstruct
Dans cette structure, aucune méthode ne sera créée par défaut : c'est à vous de tout définir. Cela est cependant à éviter. Bien qu'il y ait une légère optimisation du code (on n'appelle plus la méthode allocate pour créer une instance), cela complique les choses, pouvant être source d'erreurs.
g) Les methods operators
Pour l'instant, pour augmenter le niveau de notre héros, on utilise ça :
Jass:
method LevelUp takes nothing returns nothing
set this.level = this.level + 1
set this.strenght = this.strenght + 4
set this.agility = this.agility + 3
set this.intelligence = this.intelligence + 5
call DestroyEffect(AddSpecialEffectTarget(this.whichUnit, "Abilities\\Spells\\Human\\ReviveHuman\\ReviveHuman.mdl", "origin"))
endmethod
Mais si on veux augmenter plus d'une fois le niveau du héros, ça devient embêtant.
On aimerais bien pouvoir faire quelque chose dans le genre :
Jass:
set monHeros.level = monHeros.level + 2
Mais si on laisse à l'utilisateur la possibilité de faire ça (en mettant le membre level en public), lorsqu'il augmenterait le niveau de son héros, ces actions ne se déclencheraient plus :
Jass:
set this.strenght = this.strenght + 4
set this.agility = this.agility + 3
set this.intelligence = this.intelligence + 5
call DestroyEffect(AddSpecialEffectTarget(this.whichUnit, "Abilities\\Spells\\Human\\ReviveHuman\\ReviveHuman.mdl", "origin"))
C'est là que les méthods operators interviennent. Elles permettent une écriture plus intuitive (set monHeros.level = x), tout en exécutant des actions quand on les appelle. Il existe plusieurs types de methods operators :
• Il est parfois utile de simuler l'existence d'un membre dans la structure. Par exemple, pour notre structure héros, on pourrait imaginer un membre speed qui renvoie la vitesse de l'unité. Cela permet à l'utilisateur d'obtenir le speed avec
Jass:
monHeros.speed
plutôt que
Jass:
GetUnitSpeed(monHeros.whichUnit)
. Voici comment déclarer ce type de méthods operators :
Il est important que la méthode ne prenne aucun argument et renvoie quelque chose (ça n'aurait pas de sens sinon). Dans le cas de notre héros, ça donne :
Jass:
method operator speed takes nothing returns real
return GetUnitSpeed(this.whichUnit)
endmethod
• Si on peut simuler des membres, il est alors nécessaire de pouvoir simuler une attribution de valeur à ces membres inexistants (c'est le cas pour set monHeros.level = 2 par exemple). On les déclare de cette manière :
Jass:
method operator level= takes integer l returns nothing
set this.le = l
set this.strenght = this.strenght + 4
set this.agility = this.agility + 3
set this.intelligence = this.intelligence + 5
call DestroyEffect(AddSpecialEffectTarget(this.whichUnit, "Abilities\\Spells\\Human\\ReviveHuman\\ReviveHuman.mdl", "origin"))
endmethod
Elle ne prend qu'un argument (on va pas s'amuser à faire des set struct.membre = x, y, z) et ne retourne rien. Notez que j'ai changé le membre level stockant le niveau du héros en le. En effet, level doit être un membre private pour éviter les problèmes décrits plus haut. Mais on veut pouvoir y accéder simplement avec des methods operators. Il fallait donc changer son nom pour ne pas avoir de redéclaration de membre (level le membre et level la méthode operator).
• Il est aussi possible de faire comme si une instance d'une structure était un tableau, c'est à dire :
Jass:
local MaStruct instance = MaStruct.create()
set instance[5] = 2
call BJDebugMsg(I2S(instance[5]))
L'index utilisé correspond à i, et la valeur assignée correspond à j.
• Dernière catégorie d'opérateurs : les comparaisons '<' et '>'. Voici la syntaxe générale :
local Heros h1 = Heros.create()
local Heros h2 = Heros.create()
if h1 > h2 then //Qui est le plus fort?
//code
endif
Voilà pour les opérateurs, rendant le code plus lisible par rapport à de simples méthodes. Notez que les opérateurs [] et []= peuvent aussi être statiques.
VI/ Les textmacros
a) Explications théoriques
Les textmacros sont des instructions qui seront éxecutées lors de la compilation en Jass. Ils permettent d'une manière générale de rajouter des segments de code, et donc d'éviter des répétitions.
Il faut avant tout déclarer le textmacro ainsi que le code qu'il contient pour l'utiliser :
Une fois déclaré, on peut l'utiliser dans toute autre fonction :
Jass:
function Test takes nothing returns nothing
//! runtextmacro BJDebugMsg()
endfunction
Une fois compilée, la fonction Test ressemblera à cela :
Jass:
function Test takes nothing returns nothing
call BJDebugMsg("test")
endfunction
Vous remarquerez qu'il faut ajouter des parenthèses à BJDebugMsg dans //! run textmacro BJDebugMsg(). Cela vient du fait que les textmacros peuvent prendre des arguments, comme une fonction. Ces arguments sont cependant un peu spéciaux : il ne s'agit pas de réels, ou d'entiers, mais de bouts de code :
Notez que pour se référer à l'argument MESSAGE, il est nécessaire de le placer entre des $, sinon ce textmacro n'affichera que MESSAGE et non l'argument qu'on lui transmet.
On peut maintenant l'utiliser, sachant que lorsque l'on appel le textmacro, les arguments doivent être placés entre des guillemets. :
Jass:
function Test takes nothing returns nothing
//! runtextmacro BJDebugMsg("test")
endfunction
Un autre exemple de textmacro permettant de réaliser des opérations sur deux nombres :
On va l'utiliser pour faire un calcul et mettre le résultat dans une variable :
Jass:
function Test takes nothing returns nothing
local integer i
//! runtextmacro CALCUL("i", "4", "5", "*")
call BJDebugMsg(I2S(i))
endfunction
On obtiendra 20 à la sortie.
b) Une petite mise en pratique
Dans une carte avec beaucoup de héros, il pourrait être très utile de créer un textmacro initialisant leurs sorts pour nous.
De manière habituelle, on utiliserait cette méthode :
private function onInit takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
loop
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddCondition(t, Filter(function Conditions))
call TriggerAddAction(t, function Action)
endfunction
endscope
On va donc créer un textmacro qui va faire tout cela à notre place.
Il aura besoin du nom de l'initialisateur, de la condition, de l'action et de l'identifiant du sort :
private function $INITIALIZER$ takes nothing returns nothing
local trigger t = CreateTrigger()
local integer i = 0
loop
call TriggerRegisterPlayerUnitEvent(t, Player(i), EVENT_PLAYER_UNIT_SPELL_EFFECT, null)
set i = i + 1
exitwhen i == bj_MAX_PLAYER_SLOTS
endloop
call TriggerAddCondition(t, Filter(function $CONDITION$))
call TriggerAddAction(t, function $ACTION$)
endfunction
//! endtextmacro
Cela nous permet maintenant d'initialiser notre sort beaucoup plus facilement :
Jass:
scope MonSort initializer onInit
private function Action takes nothing returns nothing
// Le sort
endfunction
Notez que l'argument CONDITION n'est pas indispensable, mais il permet tout de même d'éviter la déclaration de deux fonctions ayant le même nom (si jamais on voulait réserver la fonction Conditions pour autre chose).
VII/ Considérer les fonctions en tant qu'objets
a) Les fonctions execute et evaluate
Le vJass offre la possibilité de considérer les fonctions comme des objets à l'aide des fonctions execute et evaluate. Cela permet entre autre de faire des appels de fonctions à partir d'un code au-dessus de sa déclaration :
Jass:
function A takes nothing returns nothing
call B.evaluate(5)
endfunction
function B takes real x returns nothing
call BJDebugMsg(R2S(x))
endfunction
Un simple appel de fonction aurait ici déclenché une erreur.
La fonction evaluate : Elle permet de faire un appel de fonction à partir d'un code au-dessus de sa déclaration. Elle n'ouvre cependant pas un nouveau thread, ce qui empêche l'utilisation de Wait. Elle est plus lente qu'un appel normal.
La fonction execute : Même rôle que la fonction evaluate, mais ouvre un nouveau thread. Elle est plus rapide que ExecuteFunc().
L'attribut .name d'une telle fonction renvoie le nom de la fonction une fois compilée.
b) Les fonctions interfaces
Il est possible de déclarer des fonctions interfaces. Celles-ci peuvent alors être sauvegardées dans des variables, envoyées en tant qu'arguments à une fonction et être exécutées à l'aide des fonctions execute et evaluate.
Jass:
function interface f takes nothing returns nothing
Elle peut aussi prendre des arguments et renvoyer une valeur.
Cela permet ainsi de créer des codes exécutant des fonctions pouvant être choisies par l'utilisateur :
Jass:
function interface Action takes nothing returns nothing
function Action1 takes nothing returns nothing
// Action 1
endfunction
function Action2 takes nothing returns nothing
// Action 2
endfunction
function executeAction takes Action func returns nothing
call func.evaluate()
endfunction
Bien-sûr, cet exemple n'a pas vraiment de sens, mais le principe est là.
Notez que les fonctions que l'on envoie en argument à la fonction executeAction sont calquées sur la fonction interface Action, sans quoi ce ne serait pas possible.
VIII/ Pour aller plus loin
Ce qui a été dit dans ce tutoriel suffira à coder quoi que ce soit en vJass. Il existe cependant d'autres fonctionnalités et subtilités comme l'héritage que vous pourrez découvrir en lisant la documentation du vJass (en anglais).
Inscrit le: 14 Oct 2009 Messages: 719 Sujets: 40 Spécialité en worldedit: Les bugs Médailles: 1 (En savoir plus...)
Posté le: 19/05/12 12:48 Sujet du message:
@Tirlititi : Non, je n'essaie pas de te surcharger de boulot .
@Troll-Brain : J'attends tes critiques.
La notion de structure est assez compliquée à expliquer. C'est pourquoi j'aimerais qu'on me dise si à un endroit ce n'est pas très clair.
J'avais pensé à mettre une sixième partie avec une mise en pratique de tout ce qui a été dit pour mieux expliquer. Est-ce nécessaire? _________________
Inscrit le: 21 Fév 2010 Messages: 1785 Sujets: 22 Spécialité en worldedit: La modestie Médailles: 1 (En savoir plus...)
Posté le: 19/05/12 13:10 Sujet du message:
C'est pas grave, je vais me contenter de faire des critiques et laisser TB décider de s'il faut l'approuver ou pas .
Quelques erreurs déjà (très peu nombreuses, faut bien l'avouer) :
Jass:
call h.LevelUp
->
Jass:
call h.LevelUp()
(la même erreur est refaite à chaque duplicata.)
Citation:
Si vous pouvez ne pas le faire, évitez de déclarer un membre à déploiement : ici le nombre d'instances simultanées possibles de MaStruct seront divisées par 5, soit 1638.
- Si jamais vous avez besoin de plus de 8191 instances de structures, c'est possible de le faire de cette manière :
Sinon, ça m'a parut très bien expliqué, les structs, et de façon assez exhaustive. Je crois pas qu'il soit nécessaire de rajouter une partie pour synthétiser.
Par contre, tu peux parler des textmacros, des arrays 2D et des actions .execute/.evaluate, qui sont assez basiques.
Inscrit le: 23 Aoû 2007 Messages: 7143 Sujets: 147 Spécialité en worldedit: le troll, le flood, la vulgarité, mon coeur balance Médailles: 2 (En savoir plus...)
Posté le: 19/05/12 13:23 Sujet du message: Re: Introduction au vJass
Citation:
Le vJass est tout simplement une extension du Jass, permettant de le manipuler beaucoup plus facilement
Il faudrait préciser que le vJass n'apporte que du candy eye stuff, c'est à dire que le vJass reste du jass à la fin.
Autrement dit on ne peut pas s'affranchir des limitations inhérentes du jass, ce n'est pas un nouveau langage en soi.
Tout ce qui est possible en vJass est aussi possible en jass, certes d'une façon plus contraignante, et parfois on devrait éditer le script de la map (comme la déclaration de variables globales autre que celle que peut fournir l'éditeur de variable, mais c'est une limite de l'éditeur officiel, pas du jass)
Citation:
b) Mise à jour du JassHelper
Il serait bien de donner aussi le lien du "nouveau" jasshelper par Cohadar.
Citation:
Les librairies permettent justement de classer notre code, et en le plaçant au début du script personnalisé automatiquement.
C'est confus et ne veut pas dire grand chose, il faudrait plutôt simplement parler du script de la map.
Citation:
Faîtes cependant attention à une chose : dans la méthode CountUp, nous avons utilisé Heros.count. Il aurait été possible de n'écrire que count, les membres statiques étant considéré comme des variables globales à l'intérieur de la structure. De même pour les méthodes, à l'intérieur de la structure, il est possible de s'y référer en n'écrivant que le nom. Faîtes cependant attention : si une autre fonction porte le même nom, il vaut mieux le préciser pour éviter les confusions.
Omettre le this. est une très mauvaise pratique, de plus il me semble que l'on peut changer le comportement de jasshelper pour les this. implicites ou non avec un tag dans le fichier jasshelper.conf.
Citation:
La méthode allocate renvoie donc un entier pour lequel le déploiement des variables est libre, d'où la limite de 8191 instances.
C'est 8190, l'index 0 n'étant pas utilisé -> struct "null".
Et l'index 8191 non plus à cause de ce bug.
Citation:
- Il existe la méthode onInit (qui doit être statique) se déclenchant à l'initialisation de la carte, sans avoir besoin de préciser un initializer comme pour les librairies ou les scopes. De même que la méthode onDestroy (qui ne doit pas être statique se référant à une instance) se déclenche à la destruction d'une instance. Elle est cependant à éviter : c'est un appel de fonction inutile, la méthode destroy pouvant servir à éxecuter du code.
C'est vrai la plupart du temps, sauf si tu utilises des struct qui extend d'autres structs, dans ce cas l'emploi des method onDestroy/onCreate est impératif.
Faudrait aussi mentionner la documentation de jasshelper fournie avec jasshelper.
Consulter le code jass généré à partir du vJass, fichier outputwar3map.j, ou output_war3map.j (selon le jasshelper utilisé) présent dans le sous dossier logs est utile pour savoir comment est compilé le vJass.
Surtout éviter d'appeler une method qui se situe au dessus de celle qui l'appelle, sous peine de se retrouver avec un ugly TriggerEvaluate au lieu d'un simple call de function.
L'utilisateur n'en sera même pas averti ...
Ce comportement peut être changé en définissant le tag [forcemethodevaluate] dans le fichier jasshelper.conf situé dans le dossier du JNGP, (et non le sous dossier jasshelper).
Je recommande vivement de le faire d'ailleurs.
Tirlititi a écrit:
C'est pas grave, je vais me contenter de faire des critiques et laisser TB décider de s'il faut l'approuver ou pas .
Euh, je ne suis pas modérateur de cette section. _________________
Dernière édition par Troll-Brain le 19/05/12 13:27; édité 2 fois
Inscrit le: 14 Oct 2009 Messages: 719 Sujets: 40 Spécialité en worldedit: Les bugs Médailles: 1 (En savoir plus...)
Posté le: 19/05/12 15:09 Sujet du message:
J'ai édité mon premier poste pour corriger toutes les fautes et rajouter quelques précision (notamment celles de Troll-Brain).
Pour les déploiement 2D, c'est fait.
Je parlerai des actions .execute et .evaluate mais aussi des fonction interfaces non?
Sinon les textmacros, je veux bien mais je ne trouve pas ça très utile.
Quand tu parles des fonctions implement, tu veux dire les modules que l'ont ajoute à une structure?
@Lord_Demon_X : Euh, je n'ai pas très bien compris ton problème, mais ton code ne fonctionnera pas : tu déclare deux fois le même nom de variable.
@Troll-Brain : Le lien que j'ai donné renvoie au JassHelper de Cohadar. J'ai quand même rajouté un lien vers la page à cause des probables mises à jour. _________________
Inscrit le: 23 Aoû 2007 Messages: 7143 Sujets: 147 Spécialité en worldedit: le troll, le flood, la vulgarité, mon coeur balance Médailles: 2 (En savoir plus...)
Posté le: 19/05/12 18:07 Sujet du message:
Pour les sized array (2d ou non) c'est la même chose, il faut utiliser une taille < 8191 (8190 ou moins), sinon autant de variables que nécessaire seront utilisés pour simuler cette variable array.
Il faut comprendre qu'utiliser une taille > 8190 générera plusieurs variables en jass (autant que nécessaire, avec une limite que je ne connais pas mais qui générera une erreur à la compilation si franchie).
Chaque lecture/écriture de cette variable en vJass se traduira une suite de if/then/elseif en jass.
Faut vraiment en avoir besoin et avoir conscience du code que cela génère.
Perso, j'en ai jamais eu besoin, et c'est devenu plus ou moins obsolète avec l'apparition du type hashtable.
EDIT :
Haha je savais que j'aurais du utiliser le conditionnel.
J'ai pas testé, mais en y réfléchissant pour les sized array, la limite devrait être en effet 8191, car contrairement à une struct où l'index 0 n'est pas utilisé (considéré comme une instance "null"), l'index 0 d'une variable array peut tout à fait être utilisé, on exclue toujours l'index 8191 pour la même raison, mais de 0 à 8190 inclus, ca fait bien une taille de 8191. _________________
Inscrit le: 23 Aoû 2007 Messages: 7143 Sujets: 147 Spécialité en worldedit: le troll, le flood, la vulgarité, mon coeur balance Médailles: 2 (En savoir plus...)
Posté le: 20/05/12 20:27 Sujet du message:
C'est runtextmacro et non run textmacro (toujours tester ses codes avant de poster :p)
Ah un truc que j'ai oublié de dire aussi, je ne suis pas certain que tes exemples avec les utilisations de globals fonctionnent, tout dépend comment la fonction main gère leur valeur "initiale" (autrement dit comment le vJass gère la définition des variables globales), je ne m'en rappelle plus.
Jass:
library Heros initializer onInit
globals
unit Heros
endglobals
function onInit takes nothing returns nothing
set Heros = gg_unit_Hpal_0001
endfunction
endlibrary
Je ne suis pas certain que l'unité soit créée quand Heros est définie. (il faut vérifier le code jass compilé)
Jass:
library Heros
globals
private unit Heros = gg_unit_Hpal_0001
endglobals
endlibrary
library KillHeros
function KillHeros takes nothing returns nothing
call KillUnit(Heros)
endfunction
endlibrary
Ne devrait pas fonctionner du tout car au moment de la définition de Heros, l'unité Paladin préplacé ne devrait pas être créé (les variables des unités préplacés sont définies dans une fonction, peut être directement dans main)
Oui je sais c'était juste pour montrer une erreur, mais pas la peine d'en rajouter _________________
Inscrit le: 14 Oct 2009 Messages: 719 Sujets: 40 Spécialité en worldedit: Les bugs Médailles: 1 (En savoir plus...)
Posté le: 20/05/12 20:33 Sujet du message:
Ah oui c'est vrai j'avais déjà rencontré des problèmes pour attribuer à une variable une unité pré placée sur la carte.
Mais bon, c'était juste pour donner un exemple .
Je modifierai tout ça demain, et je rajouterai une partie sur les fonctions .execute/.evaluate et sur les fonctions interfaces. _________________
// Pour certaines features du vJass, comme les fonctions interface
call ExecuteFunc("jasshelper__initstructs22630203")
// Appel des initializers librairies/scopes/structures
call ExecuteFunc("LIBRARIES__initFunction")
// etc...
// Initialisation des globales (la valeur par défaut que l'on met dans l'éditeur de variables)
call InitGlobals()
// Initialisation des triggers GUI
call InitCustomTriggers()
// Lancement des triggers "Map Initialization"
call RunInitializationTriggers()
Les globales "gg_" sont donc initialisées avant le moindre code vJass.
Pour le 2ème, par contre, c'est vrai que ça risque pas de marcher ^^.
Je crois qu'il est question de mettre InitGlobals() avant dans le jasshelper de Cohadar. _________________
Toutes les heures sont au format GMT + 1 Heure Aller à la page 1, 2, 3, 4, 5, 6Suivante
Page 1 sur 6
Vous ne pouvez pas poster de nouveaux sujets dans ce forum Vous ne pouvez pas répondre aux sujets dans ce forum Vous ne pouvez pas éditer vos messages dans ce forum Vous ne pouvez pas supprimer vos messages dans ce forum Vous ne pouvez pas voter dans les sondages de ce forum