|
|||||||||||
|
Construire un niveau Cette section décrit comment construire ses propres niveau. Voyons d'abord comment écrire un fichier source, puis le fonctionnement global d'un niveau, et enfin en détail chacun des constituants. Comment fonctionnent les fichiers sources ?Les fichiers sources sont des fichiers textes de base. Cela permet une écriture de données simple, sans avoir à construire un éditeur complexe. Un simple éditeur de fichier texte suffit !L'analyse dans le programme est par-contre rendu plus complexe. Pour cette raison, l'analyse est réalisée "off-line", loin de l'éxécutable principal (le jeu). Ils sont compilés vers du binaire plus compact, et structurellement vérifié. La syntaxe est validée mais peut-être pas la sémantique (dans le style XML, vous voyez ?), si il y a des problèmes avec des structures qui sont comme "pas prises en compte", vous devez commencer par vérifier votre sémantique, que les noeuds ont le bon nom (le compilo est sensible à la casse !!), et sont placés au bon endroit. Le binaire est ensuite lui-même compressé pour prendre moins de place (grâce à la libz), comme tous les autres fichiers de données, c'est-à-dire les images, les sons/musiques, et le tout est "englué" pour former un seul fichier de données. Actuellement le format est un truc perso, bâtard. Utiliser du zip serait souhaitable (à faire, donc). Pour générer les fichiers textes, il est parfois nécessaire d'effectuer des traitements. Par exemple, obtenir toutes les facettes d'une sphère serait fastidieux à la main. Pour cette raison, on utilise PHP en mode cgi, pour générer nos textes, comme on générerait du HTML. Intérêt supplémentare : on peut réutiliser des constructions déjà codées, pour d'autre fichiers. Par exemple, un format de boite ou de sphère peut être rendu générique par une fonction PHP pour être réutilisé ailleurs. Résumé: FichierPHP -> Fichier Source -> Compilé vers du binaire -> Compressé -> Inclus dans une archive Comment est structuré un fichier source ? Il a le même format que le langage VRML. Ou pour ceux qui ne connaissent pas ce langage, il s'agit d'une sorte de langage C à base de balises. Un exemple : Animation { Nom="MiseEnPlaceX"; Action { Objet="maitre"; T1=-100; T2=100; Translation { V1 { X=-100; Y=0; Z=0; } V2 { X=1000; Y=0; Z=0; } } } Action { Objet="maitre"; T1=-100; T2=100; Translation { V1 { X=-100; Y=0; Z=0; } V2 { X=1000; Y=0; Z=0; } } } }Quelques notes : il y a 4 types de noeuds. Qu'est-ce qu'un niveau ?Evidemment, c'est la question qui nous torture (???) tous. Pour comprendre, repartons de la base.Tous les objets visibles dans le jeu sont à base d'"OBJET"s. Un objet est un ensemble de facettes en 3D, c'est-à-dire de polygones convexes auquels on associe à chaque sommet de 3 à 5 valeurs : 3 coordonnées dans l'espace plus 2 coordonnées de texture qui sont optionnels. Un objet est statique. Il ne change pas au cours du temps. En utilisant ces OBJETs, on crée des "ANIMATION"s. Une animation est un ensemble d'objets, plus des listes d'actions regroupées sous le terme "LISTE d'ACTION". Les objets qu'elle contient sont organisés hiérarchiquement sous forme d'arbre. Chaque objet peut être le fils d'un objet père. Le déplacement du centre d'un père entraine le déplacement de tous ses fils.(c'est un squelette). Chaque objet n'a qu'un seul père, qui peut avoir plusieurs fils, et il peut y avoir plusieurs pères à la racine Chaque liste contient un nom qui permet de la différencier, plus une liste d'ACTION. Une action décrit ce qu'elle fait, et dans quelle segment de temps elle le fait. Ainsi, une action serait de faire un rotation suivant l'axe X de paramètre A1 vers A2, entre le temps T1 et T2, à l'objet O. La liste d'action va éxécuter linéairement toutes les actions pour un temps T donné (on verra comment plus loin). Ces actions contiennent tous ce qu'il est possible de faire de dynamique dans le jeu : Exemple : Objet { Nom="maitre"; Fichier=""; } Objet { Fichier="d_commun/d_portes/p1_1.3do"; Pere="maitre"; Nom="principal"; } Animation { Nom="MiseEnPlaceX"; Action { Objet="maitre"; T1=-100; T2=100; Translation { V1 { X=-1000; Y=0; Z=0; } V2 { X=1000; Y=0; Z=0; } } } Action { Objet="maitre"; T1=-10; T2=10; Rotation { Angle1=0.0; Angle1=3.14; AxeX; } } }Le code ci-dessus montre une animation, qui possède 2 objets reliés par une relation de filiation et une liste d'actions, nommée "MiseEnplaceX", qui contient 2 actions, s'appliquant toutes les deux sur l'objet nommé "maitre". Remarquez que les temps T1 et T2 des actions sont différents. Au temps T=10, on effectue une une rotation d'angle 3.14, puis une translation de {5,0,0}. (oui, c'est linéaire !). NOTE : les temps sont donnés en secondes. Cette construction en objets est intéressante dans le jeu car elle permet de créer simplement des mouvements complexes d'objets, sans mettre des commandes monstrueuses dans le source C. Tout est paramètrable de l'extérieur! Passons à nos niveaux maintenant... (enfin) Un niveau est composé d'un ensemble d'éléments du jeu (nains, options, points de spawn du joueur), d'infos complèmentaires, ainsi que de salles. Une salle est une animation collèe de la liste de liste d'action à effectuer, avec la manière dont ces liste d'actions seront articulées. Petit exemple pratique, avec un extrait du niveau dongeon: Musique="d_dongeon/musique.ogg"; Salle { FichierAnimation="d_dongeon/salle1.3da"; FichierSalle { NouvelleAnimation { NomAnimation="standard"; AllerRetourInfini; T1=0; T2=4; } } } InfoNiveau { NomNiveau="Le Dongeon"; InfoNiveau="Le seul niveau fonctionnel"; InfoNiveau="Des nains dans un dongeon..."; FichierScreenshot=""; } Jeu { SpawnJoueur { Position { X=-14.0; Y=25; Z=20; } } Option { SuperVie; Position { X=7; Y=-10; Z=0.5; } } Option { Teleporteur; Position { X=1; Y=4; Z=0.5; } Cible { X=-4; Y=0; Z=-9.0; } } Option { Arme5; Position { X=2; Y=-2; Z=0.5; } } }Dans ce niveau on positionne plusieurs éléments de jeu: un point de spawn, une super vie (+100), un téléporteur, une arme (la numéro 5). On y associe une musique dont le nom est donné, des infos textuelles sur le niveau qui seront données dans le menu, et 1 salle. Pour cette salle, à moins d'une modification ultérieure, on appliquera une seule liste d'animation nommée standard (qui est dans le fichier d'animation "d_dongeon/salle1.3da", pour lequel on appliquera T=0..4 en boucle infinie (de 0 à 4 puis de 4 à 0, puis de 0 à 4, ...). On ne peut pas modifier la liste de liste d'action à appliquer, mais on peut modifier le type (T1,T2, remplacer AllerRetourInfini par autre chose). Nous verrons plus loin comment. Voilà, vous connaissez les bases de la création de niveau, nous allons faire maintenant un petit détour par les lumières... Les lumières Il est possible d'associer à un objet une lumière. On donne alors le type de lumière (Ambiante, AmbianteDiffuse, Spot), et le niveau d'application : à l'objet lui-même seulement, à l'animation qui le contient, au monde entier. attention, il y a un cout (en termes de performances) à appliquer une lumière, et ce cout augmente avec le type de lumière, et la taille de l'ensemble d'objets auquel il fut l'appliquer. Cette lumière sera fixée au centre de l'objet. Ses paramètres variables ne sont pas modifiés dans l'objet. Il s'agit de son intensité dans chacune des composantes lumineuses (R,G,B,A) et son rayon (pour un spot). L'intensité est de type RGBA, c'est-à-dire rouge, vert, bleu, alpha. Le canal alpha représente la transparence. La transparence peut amener parfois un comportement étrange, mieux vaut ne pas en abuser. Un bon exemple est l'animation de la mort d'un nain de base : celui-ci disparait progressivement par effet de transparence. Le modification des paramètres lumineux est effectuée dans une action d'animation. Passons à l'application de texture pour rendre les nains encore plus effrayants ! Les textures et animations de textures Pour donner une texture à un objet, il faut donner une texture à chacune de ces facettes. Les fichiers de textures sont (actuellement) des PCX, de format carré, dont la taille est une puissance de 2. 32,64,128,256 sont des valeurs courantes correctes. Au-delà, on surcharge la carte 3D ou on ne voit rien. A chacun des points des facettes, on associe alors 2 coordonnées supplémentaires, correspondant aux coordonnées dans la texture, échelonnées de 0 à 1 (en décimal, donc). Les coordonnées sont cycliques, c-est-à-dire que 1,5 est pareil que 0,5 . Il est possible de rajouter de la transparence à nos textures, qui sera bien mieux gérée que la transparence donnée par le canal alpha des lumières. En effet, les textures transparentes sont affichées en dernier, ce qui permet de se combiner intelligemment avec le fond non-transparent. Cette transparence peut-être obtenue de diffèrentes manières: On peut aussi (c'est dingue ce qu'on peut faire, non ? :) ) associer à une texture un comportement particulier. Celle-ci peut devenir transparente additive comme une explosion, c'est ce qui donne l'aspect lumineux, ou avoir des coordonnées de texture mappée sur une sphère (comme l'option de vie), pour faire un effet groovy. Bien ,assez de blabla, on passe aux détails de chacun des types de noeuds. Les ObjetsPortent habituellement une extension .3do, mais on peut mettre ce qu'on veut. On peut aussi directement inclure le code source d'un objet à l'intérieut d'une animation, sans passer par un fichier supplémentaire. Voici ce qu'on peut trouver à la racine :Rajouter une facette Facette : SS-NOEUD Groupe de contrôle de l'aspect visuel des facettes suivantes. Texture : SS-NOEUD Visible : VIDE Invisible : VIDE AttributTexture : SS-NOEUD Animation : TEXTE Comportement dans le jeu SensibleObjet : VIDE InsensibleObjet : VIDE SensibleBalle : VIDE InsensibleBalle : VIDE Modification du comportement de l'objet par rapport au reste du monde AligneCamera : VIDE Lumiere : SS-NOEUD Groupement portal Portal : SS-NOEUDNous allons d'abord voir les facettes, puis le comportement dans le jeu des facettes qui suivent une instruction de modification de comportement, puis la modification du comportement de l'objet, et enfin les portals. Les facettesVoici le contenu générique d'un noeud de facette:Point : SS-NOEUD FacetteAction : SS-NOEUD [OPTIONNEL] Les PointsL'ensemble des points forme une facette. Cet ensemble doit être convexe, ou sinon le résultat est imprévisible."Point" a la structure suivante à son tour: X : DECIMAL Y : DECIMAL Z : DECIMAL TX : DECIMAL [OPTIONNEL] TY : DECIMAL [OPTIONNEL]Tx et Ty peuvent être omis. Dans ce cas on leur attribue une valeur "déduite", des autres points ou logiquement. Mais il ne faut alors pas s'étonner du résultat ! Les FacetteActionAvec la balise FacetteAction, on touche à une propriété qui n'est utile que pour les animations utilisées pour les salles (les autres étant utilisées pour les élements du jeu: nain, joueur, etc.). Elle sert à déclencher une action dans le cas ou un évènement est apparut. Exemple : 1 joueur s'approche d'une porte, ce qui a pour conséquence de l'ouvrir (la porte! :) ). Syntaxe:Groupement par qui: ParMobile : VIDE ParJoueur : VIDE ParEnnemi : VIDE ParInactif : VIDE Groupement comment: DeclencheContact : VIDE DeclencheAction : VIDE DeclenchePresence : DECIMAL Groupement que faire: ActionPousse : SS-NOEUD [DE TYPE POINT] ActionAnimation : SS-NOEUD ActionVie : DECIMALPour spécifier une action, il faut 1 balise de chacun des 3 groupes, dans un ordre quelconques. On choisit qui peut générer l'action. Mobile pour tous les éléments mobiles, joueur, ennemi, ou inactif pour le reste (option) "Le groupement comment" dit ce que l'objet source a du faire pour délcencher l'action. Etre en contact, présence (avec la distance minimum), ou Actionner, ce qui n'a de sens que pour un joueur qui a appuyé sur la touche action. "Le Groupement que faire" nous dit quelle sera la conséquence. L'objet est poussé (par une force donnée), une animation est déclenchée pour la salle dans laquelle l'objet est contenu, ou sa vie est retirée/ajoutée (valeur à l'appui), ce qui permet de créer des zones dangereuses ou au contraire qui redonnent de la vie. Un exemple concret de facette active: Facette { FacetteAction { DeclenchePresence=2.0; ParJoueur; ActionAnimation { NomAnimation="Ouverture"; AllerRetour; T1=0; T2=1; } } Point { X=-5; Y=-5; Z=0.1; } Point { X=-5; Y=5; Z=0.1; } Point { X=5; Y=5; Z=0.1; } Point { X=5; Y=-5; Z=0.1; } } Controle de l'aspect de la facetteTextureTexture sert à changer l'état de la texture courante, ce qui affecte les facettes données par la suite.Fichier : TEXTE BlendingZero : DECIMAL [OPTIONNEL] BlendingFixe : DECIMAL [OPTIONNEL] BlendingFixe2 : DECIMAL [OPTIONNEL] BlendingFixe3 : DECIMAL [OPTIONNEL] BlendingFichier : TEXTE [OPTIONNEL] BlendingFichierZero : TEXTE [OPTIONNEL]Le meilleur moyen de comprendre le fonctionnement du blending est de faire des essais. AnimationAnimation sert à changer l'état de l'animaton de texture courante, ce qui affecte les facettes données par la suite. Il renvoie vers un fichier de type animation. (voir plus loin)VisibilitéVisible et Invisible servent à masquer/afficher les facettes qui suivent. Cela peut servir dans le cas de facettes avec action.Attribut spéciauxAttributTexture regroupe les paramètres supplémentaires de textures qui servent à décrire un comportement particulier : spheremapping (reflexion), blending additif.Reflechi : VIDE NonReflechi : VIDE Additif : VIDE NonAdditif : VIDEil y a à chaque fois 1 paramètre pour activer, un pour désactiver. Controle du comportement dans le jeuCe groupement ne sert que pour les facettes des salles.SensibleObjet et InsensibleObjet servent à rendre la facette "traversable" par un objet mobile. Sensible par défaut SensibleBalle et InsensibleBalle servent à rendre la facette "traversable" par un objet mobile. Sensible par défaut Comportement par rapport au mondeAligneCamera sert à créer des objets dont l'axe Z sera strictement aligné avec la caméra. Cela est très utile pour les objets comme les explosions, ou on veut uniquement afficher des textures les unes à la suite des autres.Lumiere est plus complexe, il permet de spécifier la lumière qui peut être attachée au centre d'un objet. Groupement du type de lumière: Spot : VIDE Ambiante : VIDE AmbianteDiffuse : VIDE Groupement d'application de la lumière: ZoneLumiereObjet : VIDE ZoneLumiereAnimation : VIDE ZoneLumiereScene : VIDEPour obtenir de la lumiere,il faut donc choisir un type de lumiere. Spot crée une lumiere spot. Ambiante crée une lumiere identique à tous les points des objets auquelles elle s'applique et AmbinteDiffue diminue avec l'éloignement de l'objet source, en étant constante pour les objets à l'intérieur de cet objet source, ce qui est très pratique pour créer des salles lumineuses qui n'irradient pas tout le niveau. On choisit ensuite la zone d'application. (trivial). PortalLà, on touche un point particulièrement complexe. Les portals ne servent à rien d'autre que d'aider le moteur3D à afficher plus vite le monde. Ils n'ont donc pas d'intérêt pour le créateur de niveau, sauf si il veut que le niveau soit à peu près jouable. Si vous voulez comprendre en profondeur le fonctionnement des portals, il faut aller lire la section "Théorie du moteur 3D" en bas à gauche. Ici, nous allons décrire comment créer un portal. Note importante: si vous omettez de mettre des portals dans le niveau, vous ne verrez rien !Un portal est une facette qui pointe vers un aute objet, qui peut être ou ne pas être dans la même salle. La structure est donc: Facette: Facette : SS-NOEUD Cible: Objet : TEXTEFacette a été vu précédemment. Notez que mettre ici une facette avec actionfacette n'aura aucun effet. Objet est le nom de l'objet cible vers lequel le portal pointe. Ce nom est celui donné dans l'animation. Le nommage des objets de salle est donc très important, pour avoir des noms uniques, puisqu'ils ont une portée globale vis-à-vis des portals. Voilà pour la création d'objets. Pour faire des essais, je recommande de modifier des objets existants du jeu, et de voir l'impact visuel ou comportemental. Passons maintenant à la description des Animations. Les AnimationsLes animations sont un ensemble d'objets hiérarchiquement triés et de listes d'actions, appelées elles-mêmes "Animation". Attention à la confusion !Objet : SS-NOEUD Animation : SS-NOEUD Rayon : DECIMAL [OPTIONNEL] Centre : SS-NOEUD [OPTIONNEL] type POINT(Rayon et Centre sont particuliers. Si vous ne savez pas à quoi il servent, ne vous en servez pas) Noeud ObjetObjet est à son tour :Nom : TEXTE [OPTIONNEL] Pere : TEXTE [OPTIONNEL] Fichier : TEXTE [OPTIONNEL] Source : SS-NOEUDNom et Pere permettent d'avior une relation de filiation entre objet pour construire des squelettes. Fichier permet de passer le nom d'un fichier à utiliser, ou alors on peut réinjecter le code source de l'objet sous le noeud Source, ce qui est très utile pour les objets à usage unique. Noeud AnimationAnimation est à son tour :Nom : TEXTE Action : SS-NOEUDIl faut associer un nom à chaque liste d'actions, pour pouvoir retrouver la liste ! Ensuite, on applique une liste de noeuds de type actions, dont la syntaxe est donnée ici: Limites temporelles: T1 : DECIMAL T2 : DECIMAL Le nom de l'objet en cause: Objet : TEXTE Type d'action: ActiveObjet : VIDE DesactiveObjet : VIDE Rotation : SS-NOEUD Translation : SS-NOEUD Echelle : SS-NOEUD ActiveSon : SS-NOEUD Lumiere : SS-NOEUDOn donne 2 temps limites, le nom de l'objet qui va subir l'action, puis on spécifie le type de noeuds. Les 2 temps limites servent à délimiter le point entre lequel l'action va se dérouler. Ces temps sont donnés en secondes. T1 doit toujours être inférieur à T2. Entre T1 et T2, l'action est effectuée sur l'objet et la valeur réelle de l'action est linéaire avec le temps T. Par exemple, une rotation de Angle1=0 à Angle2=6.28 entre T1=1 et T2=2, donnera une rotation effective de Angle=3.14 pour T=1.5. ActiveObjet et DesactiveObjet servent à augmenter/diminuer un compteur. Quand ce compteur est strictement négatif, l'objet disparait (mais pas ses fils !), sinon il est visible. Le compteur est initialisé à zéro, donc visible. Rotation a le format suivant: Angle1 : DECIMAL Angle2 : DECIMAL AxeX : VIDE [OPTIONNEL] AxeY : VIDE [OPTIONNEL] AxeZ : VIDE [OPTIONNEL]ON choisit sur quel axe la rotation a lieu (il n'y a pas de rotation sur un axe transversal, il faut le construire soit-même, par rotation/translation), et on spécifie de quelle valeur on tourne. Translation et Echelle ont le format suivant: V1 : SS-NOEUD [TYPE POINT] V2 : SS-NOEUD [TYPE POINT](On peut donc faire une mise à l'échelle non-uniforme sur chacun des axes !!) Pour les lumieres: Lumiere1 : SS-NOEUD [TYPE VALEUR DE LUMIERE] Lumiere2 : SS-NOEUD [TYPE VALEUR DE LUMIERE]Voici comment on spécifie une valeur de lumière : R : DECIMAL G : DECIMAL B : DECIMAL A : DECIMALRed, Green, Blue, Alpha. Les valeurs sont décimales. 0 vaut pas de lumière, 1 vaut pleine lumière. On peut mettre des valeurs en dehors de 0-1 pour faire des effets Funky ! Pour les sons: NOTE:On ne peut associer qu'un seul son en même temps par objet. Le résultat est indéterminé si on fait plus Fichier : TEXTE SensPositif : VIDE [OPTIONNEL] SensNegatif : VIDE [OPTIONNEL] LongueurSon : DECIMAL [OPTIONNEL]Les formats de son supportés sont Ogg, Mp3 et Wav. OggVorbis est ce qui est le mieux, bien sûr. SensPositif et SensNegatif se rapporte au sens de parcours de l'axe temporel, si on veut que le son soit joué quand T augmente ou diminue. Si aucun n'est spécifié, le son est joué dans les 2 sens. LongueurSon (en seconde) sert à couper le son, ou le rallonger avec un blanc. En effet, le son est joué en boucle tant qu'on est dans la zone de temps T1-T2. Avec ce système, on choisit la longueur de la boucle de temps. Vous savez tout sur les Animations. Reste à expérimenter, et passer à la création des salles et des niveaux. Les NiveauxUn niveau est un ensemble de salles, d'informations résiduelles, et d'objets de jeu.Salle : SS-NOEUD Musique : TEXTE InfoNiveau : SS-NOEUD Jeu : SS-NOEUD Les SallesUne salle est basiquement une animation, enrichie d'informations sur les liste d'actions à appliquer, dans quel sens, et quel temps T appliquer et faire varier.FichierAnimation : TEXTE [OPTIONNEL] FichierSalle : SS-NOEUD [OPTIONNEL]Fichier animation renvoie vers un fichier contenant une animation. FichieSalle n'est pas un fichier, mais un ss-noeud. Il contient ceci: NouvelleAnimation : SS-NOEUDC'est la liste des animations à appliquer, et comment les appliquer. Chaque NouvelleAnimation est à son tour: NomAnimation : TEXTE T1 : DECIMAL T2 : DECIMAL PositionDirecte : VIDE AllerSimple : VIDE AllerSimpleInit : VIDE AllerRetour : VIDE AllerRetourInit : VIDELe nom de l'animation à appliquer, et la modification de l'échelle de temps. PositionDirecte : Se positionner à un temps T1 et arrêt AllerSimple : Rejoindre le temps T1 et arrêt AllerSimpleDirect : Aller en temps T1 directement et rejoindre T2, puis stop. AllerRetour : Rejoindre le temps T1 , revenir vers T2 puis faire le retour vers T1 et stop. AllerRetourInit : Directement T1 , revenir vers T2 puis faire le retour vers T1 et stop. AllerRetourInfini : Directement T1 , revenir vers T2, puis en boucle infinie: T1->T2->T1. AllerSimpleInfini : Directement T1 , puis T2, et en boucle infini T1->T2. Les informations résiduelles : musique et texteMusique spécifie la musique de fond à jouer en boucle.InfoNiveau a la structure suivante: NomNiveau : TEXTE FichierScreenshot : TEXTE InfoNiveau : TEXTEOn peut mettre autant de ligne infoniveau que souhaitée, ce texte sert à donner des infos dans le menu de choix du niveau. Pareil pour afficher une image dans FichierScreenshot. NomNiveau porte le nom du niveau de manière explicite, par exemple "Niveau de la mort". Les objets animés du jeuTout est inclus dans "Jeu". C'est une liste des objets présents:Option : SS-NOEUD SpawnJoueur : SS-NOEUD NDJ : SS-NOEUDOption est le plus compliqué: Position : SS-NOEUD [TYPE POINT ] Vie : VIDE SuperVie : VIDE Armure : VIDE SuperArmure : VIDE Munition1 : VIDE Munition2 : VIDE Munition3 : VIDE Munition4 : VIDE Munition5 : VIDE Munition6 : VIDE Munition7 : VIDE Munition8 : VIDE Munition9 : VIDE Munition10 : VIDE Arme1 : VIDE Arme2 : VIDE Arme3 : VIDE Arme4 : VIDE Arme5 : VIDE Arme6 : VIDE Arme7 : VIDE Arme8 : VIDE Arme9 : VIDE Arme10 : VIDE Pigeon : VIDE Annihilation : VIDE Rapidite : VIDE Invulnerabilite : VIDE SuperPuissance : VIDE Sortie : VIDE Teleporteur : VIDE Cible : SS-NOEUD [TYPE POINT ] Reapparait : VIDEIl faut renseigner la position pour chacun des noeuds. Cible est util pour téléporteur uniquement. Reapparait fixe le temps au bout duquel l'option réapparaitra, après avoir été ramassé par un joueur. SpawnJoueur représente un point d'apparition possible d'un joueur: Position : SS-NOEUD [TYPE POINT ]NDJ représente un Nain De Jardin: Position : SS-NOEUD [TYPE POINT ] |
|
Page generated in 0.02 seconds |