Warcraft III

Tutorial - vJass

Sommaire

Librairies

Introduction

Le problème dans l'éditeur de war3 c'est qu'il est impossible de contrôler aisément l'ordre des déclencheurs, pour l'écriture du script de la map lors de la sauvegarde. Cela devient un réel problème lorsque l'on doit récupérer du code créé par un autre mappeur, tout se place dans le script personnalisé de la map le plus souvent, ce qui n'est pas pratique du tout, ça crée un beau bordel au final.
Les librairies vous permettront donc de ranger votre code dans n'importe quel ordre !, sachant qu'au moment de la sauvegarde de votre map tout sera mis dans le bon ordre.

Syntaxe

library <nom_de_la_librairie>
// Votre code habituel
endlibrary

Exemple

library A
function FunctionA takes nothing returns nothing
endfunction
endlibrary

library B
function FunctionB takes nothing returns nothing
endfunction
endlibrary

Avec cet exemple vous pouvez être certain que les fonctions FunctionA et FunctionB seront au début du script de votre map. Donc toutes les autres fonctions pourront faire appel à ces 2 fonctions. Dans l'exemple les 2 fonctions garderont le même ordre, un élément du langage permet d'influer sur cet ordre.

Avec l'attribut needs il est possible d'ordonner soi-même les fonctions dans le script qui est généré à la sauvegarde. Pour cela on utilise un système de dépendance, une librairie a besoin des fonctions d'une ou plusieurs autres librairies pour fonctionner.

Dans l'éditeur on écrira:

library A needs B
function FunctionA takes nothing returns nothing
	call FunctionB()
endfunction
endlibrary

library B
function FunctionB takes nothing returns nothing
endfunction
endlibrary

Et à après la sauvegarde on obtiendra :

function FunctionB takes nothing returns nothing
endfunction
function FunctionA takes nothing returns nothing
call FunctionB()
endfunction

Sans l'attribut needs cela n'aurait pu fonctionner, car la fonction FunctionA a besoin de la fonction FunctionB.
Pour faire dépendre une librairie de plusieurs autres librairies il suffit de séparer les requis par une virgule.

library A needs B, C
function FunctionA takes nothing returns nothing
	call FunctionB()
	call FunctionC()
endfunction
endlibrary

library B
function FunctionB takes nothing returns nothing
endfunction
endlibrary

library C
function FunctionC takes nothing returns nothing
endfunction
endlibrary

Après la sauvegarde:

function FunctionB takes nothing returns nothing
endfunction
function FunctionC takes nothing returns nothing
endfunction
function FunctionA takes nothing returns nothing
call FunctionB()
call FunctionC()
endfunction
N'oubliez pas que:

Droit d'accès - Encapsulation

Introduction

Il est possible d'ajouter un contrôle d'accès sur les variables globales, les fonctions, les structure et plus encore (cela sera expliqué au fur et à mesure), on appelle ça l'encapsulation des données.
Ce contrôle permet de limiter l'accès à certaines données afin d'avoir un code facilement portable. Il existe 2 types d'accès pour le vJass: public et privé. Ces accès se limitent aux blocs qui les contiennent (librairie, structure, scope).

Accès public

L'accès public ce définit par le mot clé "public" devant la donnée à protéger. Un accès public ne sert pas à grand chose, car par défaut tous les accès sont public.

library A
globals
	public real x = 0.0
endglobals

public function FuncA takes nothing returns nothing
	set x = 18.0	// Autorisé
endfunction
endlibrary

function FuncB takes nothing returns nothing
set x = 24.0	// Autorisé
call FuncA()	// Autorisé
endfunction

Dans cet exemple rien ne change, c'est tout à fait classique. Cela montre que l'on a le droit d'accéder à la variable x en dehors de la librairie A

Accès privé

L'accès privé se définit par le mot clé "private" devant la donnée à protéger, les éléments privés sont uniquement accessibles par les blocs qui les contiennent.
Cela permet également d'avoir un même nom de variable globale dans plusieurs bloc, dans différentes librairies par exemple.

library A
globals
	public real x = 0.0
	private real y= 0.0
endglobals

public function FuncA takes nothing returns nothing
	set x = 18.0	// Autorisé
	set y = 12.5	// Autorisé
endfunction

private function FuncC takes nothing returns nothing
	set x = 34.0	// Autorisé
	set y = 84.5	// Autorisé
endfunction
endlibrary

function FuncB takes nothing returns nothing
set x = 24.0	// Autorisé
set y = 106.047	// Interdit
call FuncA()	// Autorisé
call FuncC()	// Interdit
endfunction

Explication: La fonction FuncC et la variable globale y ne peuvent être utilisées QUE dans la librairie A, en dehors de ce bloc, une erreur surviendra à la sauvegarde.

Structure:

Introduction:

Une structure peut être considéré comme un objet avec ses propriétés et ses fonctions. Comme par exemple une unité, elle possèdes certaines propriétés: points de vie, points de mana, dégâts d'attaque, modèle, nom etc... et des fonctions qui lui sont propres: déplacement, attaque, modification du nombre de points de vie, jouer une animation etc...

Il est possible de considérer n'importe quel élément comme un objet: un stylo, un ordinateur, une personne, une arme, un sort. Et également des concepts plus abstrait tel qu'un point (location pour l'éditeur).
Les structures permettront de créer soi-même ses propres types.

Syntaxe

struct <type_structure>
// corps de la structure
endstruct

Le corps de la structure peut se diviser en 2 parties: la partie déclarative et la partie contenant les fonctions propres à la structure que l'on nommera des méthodes.

Exemple

struct point
real x	
real y = 0.0	// L'assignation d'une valeur dans la partie déclarative est facultative, cela correspond à une valeur par défaut.
endstruct

Emploi d'une structure

Après sa déclaration une structure peut être utilisée comme n'importe quel autre type de variable. Elles doivent être créées puis détruites car il ne peut exister plus de 8190 instances d'une même structure. Cela laisse une grande marge de main d'oeuvre mais il faut être prudent dans le cas d'une structure utilisée souvent, s'il existe de trop nombreuses instances inutilisées et non détruites cela peut avoir un impact sur une partie de plusieurs heures. Cependant ne vous inquiétez pas, personnellement il ne m'est jamais arrivé de dépasser cette limite, faut vraiment le vouloir ^^.

La création se fera à l'aide du mot-clé "create()" précédé du type de la structure de cette manière: <type_structure>.create()
La destruction se fera avec le mot-clé "destroy(<instance_de_struct>)" précédé du type de la structure de cette manière: <type_structure>.destroy(<instance_de_struct>) ou alors l'instance de la structure suivi du mot clé "destroy()" <instance_de_struct>.destroy()

function testPoint takes nothing returns nothing
local point p = point.create()
set p.x = 18.0
set p.y = 34.0
call BJDebugMsg( "x:"+R2S(p.x)+" ; y:"+R2S(p.y) )	// Affichera "x:18.0 ; y:34.0"
call point.destroy(p)	ou	call p.destroy()	// Libère une instance
endfunction

Autre exemple :

function afficherPoint takes point p returns nothing
call BJDebugMsg( "x:"+R2S(p.x)+" ; y:"+R2S(p.y) )
endfunction

function creerPoint takes real x, real y returns point
local point p = p.create()
set p.x = x
set p.y = y
return p
endfunction

function testPoint2 takes nothing returns nothing
local point p = creerPoint( 10.0, -354.14)
call afficherPoint(p)		// Affichera "x:10.0 ; y:-354.14"
call p.destroy()
endfunction

Note pour les puristes: il est inutile de nullifier les instances de structure car en réalité ce sont des entiers.

Une structure peut elle même être composée de structure(s), pour une instance de structure, la valeur 0 signifie que la structure n'a pas encore été créée. Une instance de structure qui n'est pas créée est inutilisable.

struct pairPoint
point p1 = 0	// il est interdit d'utiliser point.create() pour la valeur par défaut.
point p2 = 0
endstruct

function testPair takes nothing returns nothing
local pairPoint pP = pairPoint.create()

set pP.p1 = point.create()
set pP.p1.x = 34.18
set pP.p1.y = -23.56

set pP.p2 = creerPoint( 91.35, 59.1 )
call afficherPoint( pP.p1)		// Affichera "x:34.18; y:-23.56"
call afficherPoint( pP.p2)		// Affichera "x: 91.35; y:59.1 "
call pP.p1.destroy()
call pP.p2.destroy()
call pP.destroy()
endfunction

Il est également possible d'avoir des globales avec un type de structure.

globals
point point_global = 0		// Autorisé
point point_x = point.create()	// Interdit
endglobals

Les méthodes

Les méthodes sont des fonctions directement intégrés à la structure, la syntaxe est la même que pour les fonctions sauf que l'on remplace "function" par "method" et "endfunction" par "endmethod". Les méthodes doivent être déclarées dans le corps de la structure, comme pour les attributs.
Nous allons reprendre notre structure de point et l'éteindre.

struct point
real x = 0.0
real y = 0.0

method afficherPoint takes nothing returns nothing
	call BJDebugMsg( "x:"+R2S(this.x)+" ; y:"+R2S(this.y) )
endmethod

method placerPoint takes real x, real y returns nothing
	set this.x = x
	set this.y = y
endmethod
endstruct

function testPointMethod takes nothing returns nothing
local point p = point.create()
call p.placerPoint( 24.001, 59.02)
call p.afficherPoint()		// Vous savez ce que ça va afficher maintenant !
call p.destroy()
endfunction

Pour l'appel des méthodes le principe est le même que pour accéder aux attributs de la structure: <instance>.<méthode>(<paramètres_de_la_méthode>)
Vous l'aurez compris (ou pas) le mot-clé "this" permet de pointer sur l'instance elle-même. Ce mot-clé ne peut être employé qu'à l'intérieur des méthodes de la structures.

"this" est optionnel: Vous pouvez utiliser un simple '.' à la place de celui-ci. (exemple: set .x = 18.0). Cependant ça marche 1 fois sur 2 donc à utiliser avec modération, ou pas du tout.

Encapsulation

Les structures bénéficient de l'encapsulation des données (accès privé et accès public)
Les attributs de la structures et les méthodes peuvent bénéficier d'un droit d'accès. Ainsi un attribut privé sera modifiable uniquement au sein des méthodes de la structure, tout comme une méthode privée qui ne pourra être exécuté qu'au sein des méthodes de la structure. Les éléments public seront accessible de n'importe où à l'intérieur et à l'extérieur de la structure.

struct point
private real x = 0.0
private real y = 0.0
private boolean init = false		// le point a-t-il été initialisé ?

method afficherPoint takes nothing returns nothing
	call BJDebugMsg( "x:"+R2S(this.x)+" ; y:"+R2S(this.y) )
endmethod

method placerPoint takes real x, real y returns nothing
	set this.x = x
	set this.y = y
	if this.init == false then
		set this.init = true
	endif
endmethod

method IsInit takes nothing returns boolean
	return this.init
endmethod
endstruct

Dans cet exemple un troisième attribut de type booléen a été ajouté: "init". Cette variable indique si le point a été initialisé ou si les valeurs x et y n'ont pas été modifiés, la variable est privé car l'utilisateur (développeur) ne dois pas avoir le droit de modifier cette valeur, cependant il bénéficie d'un droit de lecture sur la valeur grâce à la méthode "IsInit" qui renvoie la valeur de l'attribut "init". Les attributs 'x' et 'y' sont également privés, elles ne peuvent être lus, ni modifiés, cependant la méthode placerPoint permet de modifier leurs valeurs.

Héritage

Pour expliquer simplement l'héritage prenez par exemple l'être humain. A partir de cet être humain nous pouvons avoir deux extensions possible: l'homme et la femme. Les deux extensions ont beaucoup d'attributs en commun (relatif à l'être humain) et réalisent les mêmes actions: boire, manger, réfléchir, pisser xD, mais ne le font pas de la même manière. Les actions possibles sont regroupées au sein de l'être humain, l'homme et la femme apporteront la définition de ces actions.

Mais à quoi cela peut-il bien servir ?

Vous intégrez dans votre map deux types de soldat, les êtres humains et les morts vivants. Vous souhaitez que les deux soient considérés comme des unités et qu'ils aient des fonctions communes, mais qui réagissent différemment selon que l'unité soit un mort vivant ou un être humain. Les deux régénèrent leur points de vie, mais pas de la manière, les humains en permanence et les mort-vivants uniquement sur de la flétrissure.

Interfaces

L'interface forme la base de votre type (le soldat), c'est dans l'interface que nous allons déclarer les attributs communs et les actions communes mais sans les définir !, leur définition seront dans les structures qui héritent de l'interface (structure être humain et structure vampire).

Syntaxe au travers de l'exemple

interface soldat
real regen = 0.0
unit u = null
method Init takes player p, real x, real y returns nothing	// Initialisera la structure
method RegenPv takes nothing returns nothing	// On ne déclare que l'en-tête de la méthode
endinterface

struct humain extends soldat
method Init takes player p, real x, real y returns nothing
	call BJDebugMsg( "Je suis un humain")
	set this.u = CreateUnit( p, 'hfoo', x, y, 0.0)	// Création d'un footman
	set this.regen = 1.0
endmethod

method RegenPv takes nothing returns nothing
	call SetUnitState( this.u, UNIT_STATE_LIFE, GetUnitState(this.u, UNIT_STATE_LIFE) + this.regen)
endmethod
endstruct

struct mort_vivant extends soldat
method Init takes player p, real x, real y returns nothing
	call BJDebugMsg( "Je suis un mort-vivant")
	set this.u = CreateUnit( p, 'ugho', x, y, 0.0)	// Création d'une ghoule
	set this.regen = 2.5
endmethod

method RegenPv takes nothing returns nothing
	if IsPointBlighted(GetUnitX(this.u), GetUnitY(this.u)) then
		// La régénération ne se fait que sur terre flétrit, mais elle est plus importante
		call SetUnitState( this.u, UNIT_STATE_LIFE, GetUnitState(this.u, UNIT_STATE_LIFE) + this.regen * 2)
	endif
endmethod
endstruct

Nos structures sont déclarées nous allons pouvoir les utiliser.

Exemple d'utilisation

function test takes nothing returns nothing
local soldat array soldats
local integer a = 0

set soldats[0] = humain.create()
set soldats[1] = humain.create()
set soldats[2] = mort_vivant.create()
set soldats[3] = humain.create()
set soldats[4] = mort_vivant.create()

loop
	exitwhen a == 5
	call soldats[a].Init()
	call soldats[a].RegenPv()
	set a = a + 1
endloop
endfunction

A la sortie nous obtiendrons 3 fantassins et 2 ghoules avec en affichage:

Je suis un humain
Je suis un humain
Je suis un mort-vivant
Je suis un humain
Je suis un mort-vivant

Vous pouvez vous amuser à rajouter un type Elfe qui ne régénère que la nuit.