Metin2 Land
Découvrez le MMORPG RaiderZ

Rechercher dans la communauté

Affichage des résultats pour les étiquettes 'tutoriel'.



Plus d’options de recherche

  • Rechercher par étiquettes

    Saisir les étiquettes en les séparant par une virgule.
  • Rechercher par auteur

Type du contenu


Forums

  • Inscription & Connexion
    • Inscription
    • Connexion
  • Espace communautaire
    • Funky-Emulation
    • Recrutements
    • Bureau du Staff
    • Suggestions & Améliorations
    • Discussions Générales
    • Commerce
    • Espace Gaming
  • Emulation de jeux
    • Dofus
    • Aura Kingdom
    • GTA V Online
    • RaiderZ
    • Metin 2
    • Habbo
    • Wakfu
    • World of Warcraft
    • Autres jeux
  • Espace divers
    • Programmation
    • Infographie
    • Audio / Vidéo
    • Discord
    • Mobile
    • Divers

Calendriers

  • Community Calendar

Catégories

  • Articles

Catégories

  • Émulations de jeux
  • Infographie et Audiovisuel
  • Programmation
  • Autres

404 résultats trouvés

  1. Salut à toi ! Aujourd'hui je vais t'apprendre comment changer les files Metin2 que tu ais une machine virtuelle ou un serveur dédié. Vous trouverez la plupart des files dans la section Serveur de Téléchargement: Contenu Masqué Mais vous pouvez très bien les avoir trouvé un autre forum: le processus ne change pas. Des files Metin2 se découpent en trois parties: Les fichiers serveurs. Souvent archivés dans une archive au format .tar.gz Les tables SQL. C'est la base de données de votre serveur Le client. I. Installer les fichiers serveur 1. Vous avez normalement une archive au format .tar.gz (ou autre) qui doit contenir les fichiers serveur avec plusieurs dossiers tel que channel1, share, auth, db, etc .. Placez l'archive dans le dossier /usr sur WinSCP 2. Dans votre machine virtuelle (ou putty), tapez la commande: cd /usr/metin2 && tar xvf metin2.tar.gz J'exécute deux commandes. Déjà je me rends dans le dossier metin2 et ensuite j'extrais les fichiers de l'archive. Une fois l'extraction terminé, vous aurez un dossier metin2 dans le dossier usr (voir screen ci-dessus) II. Installation de la base de données SQL 1. Vous avez normalement une archive avec des fichiers .sql (account, common, etc) .. Ce sont des fichiers SQL que l'on va devoir installer sur notre base de données. 2. Connectez vous à votre base de données sur Navicat. 3. Faites un clique droit sur votre connection puis cliquez sur New Database 4. Dans la fenêtre qui s'ouvre, mettez account dans Enter Database Name puis cliquez sur OK 5. Une fois la base de données créé, double cliquez dessus pour l'ouvrir puis faites un clic droit dessus et sélectionnez Execute SQL File 6. Sélectionnez le fichier account.sql. Cochez la deuxième case comme sur le screen ci-dessus et cliquez sur Start 7. Une fois l'exécution terminé, vous en avez fini avec la table account. Répétez les étapes 3, 4, 5 et 6 plusieurs fois en créant toutes les bases de données nécessaires: common, log, metin2_runup, hotbackup et player Par exemple pour la base de données player, je vais appelé ma database player au lieu de account. Et au lieu de choisir le fichier account.sql, je vais choisir le fichier player.sql III. Connecter le serveur et la base de données 1. Il va falloir maintenant configurer les fichiers serveur pour qu'ils se connecter à la base de données SQL. 2. Vous avez des dossiers de channel. Le dossier channel1, channel2, etc .. Chaque channel contient 5 dossiers core (core1, core2, etc ..). Chaque dossier core contient un fichier appelé CONFIG. Il faut configurer le fichier CONFIG pour chaque core ! Ouvrez ce fichier avec un éditeur de texte. Attention ! Sur les files <= 2012, il n'y a pas de dossiers core, juste un fichier CONFIG par channel ! (contrairement au 2012+ où il y a 5 fichiers CONFIG par channel, un par core). 3. Dans ce fichier CONFIG, vous devez apercevoir cette partie là: 4. La configuration est organisé de cette sorte HOST user password table 5. Sur le screen ci-dessus, la configuration dit: Connecte toi à MySQL avec l'utilisateur metin2 et le mot de passe epv4minq. Changez ces informations en fonction de votre user MySQL. Ne touchez pas au mot player, common et log, juste les informations de connexion ! Une fois terminé, enregistrez le fichier et faites la même chose pour tous les channels (et tous les cores) Si vous avez la machine virtuelle du tutoriel Créer un serveur Metin2, les informations de connexion sont. Host: localhost User: metin2 Mot de passe: serveur 6. Faites la même chose pour le fichier CONFIG du dossier auth et le fichier CONFIG du dossier game99 7. Faites la même chose avec le fichier conf.txt du dossier db. La forme change un petit peu mais ça reste la même chose. Changez juste localhost metin2 et epv4minq IV. Fin du tutoriel Vos files sont maintenant changés ! Il ne vous reste plus qu'à configurer l'IP sur votre nouveau client: Contenu Masqué Si tu as besoin d'aide, n'hésite pas à aller créer un topic dans la catégorie Aide / Question / Support de la section Metin2. La communauté sera là pour vous aider.A bientôt !
  2. Bonjour à tous ! Tutoriel court et rapide pour créer un nouveau monde sur votre serveur RaiderZ. /!\ Vous êtes obligés d'avoir au moins un monde pour pouvoir jouer /!\ Pour commencer, ouvrez simplement Navicat, connectez vous à la BDD de votre serveur, et allez dans la base de donnée account, table "world". Pour rentrer un monde, appuyez sur "Inser", ou cliquez sur le + en bas de la fenêtre: Puis remplissez comme sur ce screen: server_id: ID du serveur de Login (par défaut sur le DevKit: 101) world_id: ID du monde (1,2,3,etc..) name: Nom du monde host_adress: Adresse des serveurs du monde (si tout est sur la même machine, c'est 127.0.0.1) port: Port du serveur de Login type: LoginServer curr_player: Laisser vide max_player: Nombre maximum de joueurs sur le monde last_update_time: Laisser vide servable: mettre t (pour true) alive_timeout: Laisser vide state: FINE order_num: Numéro du monde dans la liste des mondes côté client (commencez à 1) Voilà, vous avez un monde sur votre serveur ! Vous pouvez lancer votre serveur et vous connecter
  3. Salut à toi ! Tu n'as jamais fait de serveur Metin2 ? Ce tutoriel est fait pour toi ! Je vais t'expliquer comment créer un serveur de A à Z ! Tu pourras ensuite le modifier à ta guise et le modifier à ton image. Lors de ce tutoriel, vous allez apprendre à créer un serveur en localhost ! C'est-à-dire un serveur rien que pour vous, vous serez le seul à pouvoir vous y connecter. Prêt ? Alors commençons ! I. Téléchargement VirtualBox - Contenu Masqué Navicat - Contenu Masqué WinSCP - Contenu Masqué Le serveur (le VDI) - Contenu Masqué Le client - Contenu Masqué II. La machine virtuelle 1. Installez VirtualBox. Il nous permettra de virtualiser le système d'exploitation FreeBSD. 2. Lancez le logiciel VirtualBox 3. Cliquez sur le bouton Nouvelle, en haut à gauche 4. Choisissez un nom pour votre machine virtuelle. Dans Type, choisissez BSD et dans Version, FreeBSD (32bit ou 64bit en fonction de votre ordinateur). Et cliquez sur Suivant 5. Allouez la mémoire vive à votre machine virtuelle en fonction de votre ordinateur. Le minimum conseillé est 1go (1024Mo). N'hésitez pas à en donner plus le temps que le curseur est dans la barre verte. Cliquez sur Suivant. 6. Cochez la case " Utiliser un fichier de disque dur virtuel existant " et choisissez le fichier VDI (.vdi) que vous avez téléchargé. Cliquez ensuite sur Créer. 7. Votre machine virtuelle est crée. Configurons la carte réseau. Clique droit sur votre VM (virtual machine) puis Configuration 8. Dans le menu Réseau, mettez " Accès par pont " dans mode d'accès réseau et dans nom, mettez votre carte réseau (carte Wifi si vous êtes en WIF ou Ethernet si vous êtes en Ethernet). Cliquez ensuite sur OK. 9. Votre machine virtuelle est maintenant créée ! III. Lancement du serveur Metin2 1. Pour lancer votre machine virtuelle, il suffit de cliquer deux fois sur votre machine. Une fenêtre s'ouvre et votre système d'exploitation démarre. Patientez. On vous demande ensuite de vous connecter à un utilisateur. 2. Dans login, entrez: root pour login et dans Password, mettez mcncc.com. Vous êtes maintenant connecté et vous avez accès à tout le système d'exploitation. Dans le terminal, tapez cd /usr/metin2 4. Vous voila dans le dossier de votre serveur metin2. Pour démarrer le serveur il suffit de taper: sh start.sh (et sh stop.sh pour l'arrêter). Entrez ensuite le nombre de channel que vous voulez ouvrir. Pour commencer, un seul suffit. 5. Votre serveur Metin2 est lancé ! Il ne vous reste plus qu'à configurer votre client ! Bien entendu, laissez votre fenêtre Virtual Box ouverte pour que votre serveur reste ouvert. IV. Se connecter In Game ! 1. Commencez par extraire le client de Client 2014.rar sur votre bureau. 2. Il faut configurer le client de sorte à ce qu'il se connecte sur l'adresse IP de votre serveur. Il existe un tutoriel pour vous apprendre à le faire: Contenu Masqué 3. Pour obtenir l'IP de votre machine, il suffit de faire la commande ifconfig sur votre machine virtuelle et de repérer l'adresse IP commençant par 192.168 (l'ip locale) 4. Dans mon cas, je vois bien 192.168.43.205 C'est celle-ci ! 5. Une fois la configuration terminée, lancez metin2client.exe qui se trouve à la racine de votre client. 6. Connectez vous avec ces identifiants. User: admin Mot de passe: test 7. Choisissez votre personnage et ça y est, vous voila sur votre premier serveur privé Metin2 ! V. Accéder aux fichiers du serveur 1. Pour accéder aux fichiers du serveur, il faut installer le logiciel WinSCP. Une fois installé, lancez le. 2. Cliquez sur nouveau site et remplissez comme l'image ci dessous 3. Vous voici connecté à WinSCP. Vous avez tout simplement un accès à votre système d'exploitation FreeBSD mais cette fois avec un explorateur de fichier, et non en ligne de commande. 4. Déplacez-vous dans l’arborescence. Cliquez sur .. pour revenir à la racine du serveur, cliquez sur le dossier usr puis metin2. Vous tombez sur: 5. Ceci sont les fichiers serveur de votre serveur Metin2 ! VI. Accéder à la base de données du serveur 1. La base de données contient toutes les données de votre serveur (comptes, joueurs, etc ..). Pour y accéder, installez Navicat et lancez-le. 2. Cliquez sur le bouton Connection puis MySQL 3. Remplissez comme l'image ci-dessous: 4. La connexion s'est créé dans le volet de gauche. Il suffit juste de cliquer 2 fois dessus pour s'y connecter. 5. Ceci est la base de donnée de votre serveur Metin2 ! VII. Fin du tutoriel C'est tout bon, vous avez tous les outils pour créer un serveur Metin2 à votre image FAQ des débutants à voir absolument: Contenu Masqué Si tu as besoin d'aide, n'hésite pas à aller créer un topic dans la catégorie Aide / Question / Support de la section Metin2. La communauté sera là pour vous aider. Bon courage, jeune apprenti !
  4. Bonjour à tous, Petit tutoriel sur comment créer un compte sur RaiderZ. Pour commencer, allez dans Navicat, puis ouvrez la base de donnée RZ_ACCOUNT. Ensuite, suivez le screen: cliquez sur Queries, puis New Query: Dans le champ principal, entrez la fonction suivante: SELECT rz_account_insert('Username', 'blue', '[email protected]'); Remplacez Username par le nom d'utilisateur, et l'adresse mail par l'adresse mail du joueur. /!\Attention: peu importe le mot de passe que vous mettrez, la fonction mettra automatiquement le mot de passe "blue" au compte! /!\ Une fois la fonction tapée, cliquez sur "Run", et vous devriez avoir une fenêtre qui ressemble à peu près à ça: Voilà, votre compte est créé !
  5. Salut ! Avant de suivre cette partie, je vous invite à lire la précédente : Je vais ici essayer de vous faire découvrir HTML, CSS, JS et PHP. Voici les différents objectives de cette partie : Découvrir les éléments basiques de l'HTML tels que : La doctype Différents balises : Les principales balises telles que le p,h1,ul,td... Les principales balises orphelines telles que le hr, br... Les formulaires qui seront très utiles pour la suite, Les tableaux, Les liaisons : CSS, JS. PHP : Découverte de PDO : Créer sa requête : Préparation, Insertion de données, Exécution, Traiter les données : Nombre de ligne affectée, Colonne affectée, Ligne des colonnes affectées. Divers. Nous avons un emploi du temps assez chargé, nous ferions mieux de nous y mettre maintenant ! HTML: HTML est un langage de balisage. Il dispose de balise. Je vous propose une petite liste, qui résume les principales balises. @ASIKOO Il faudrait un système pour faire des cartes mentales ! ! (Je sais pas si c'est toi que je devais taguer, mais tu m'as l'air d'être le seul actif...). Sauf si je précise que les balises sont orphelines, elles devront être ouvertes puis fermées. Utilisez pour les fermer la même balise que pour les ouvrir, mais ajoutez un "/" exemple : <p>Message</p> Des balises assez spéciales : <!DOCTYPE html> Elle au dessus de tout votre code HTML. Elle est obligatoire, même si le code marche sans, pas de question, c'est obligatoire. Balise orpheline. <head>.. </head> Se place après la balise de doctype. Elle contient les informations complémentaires de la pages telles que : Le titre, Les mots clés, Les importations de : CSS, JS. Toutes les méta-informations. <body> .. </body> Se place après la balise de fermeture </head> Contient littéralement le corps de la page. C'est tout ce qui sera affiché au client. Les balises principales : Texte brut : <p> : Permet de rédiger un paragraphe. <h1>,<h2>... <h6> Permet d'écrire un titre plus ou moins important. L'évolution de l'importance est décroissante par rapport à la valeur associée à h. Les tableaux, un tableau ce compose de ces balises : <table> : Désigne le début et la fin du tableau, <tr> : Désigne une ligne du tableau, <td> : Désigne une cellule du tableau. On va donc créer un tableau comme ceci : <table> --> On crée le tableau <tr> --> On crée une nouvelle ligne <td>.. </td> --> On crée les colonnes en désignant les cellules </tr> --> On marque la fin de la ligne <tr> --> On crée une nouvelle ligne <td>.. </td> --> On crée les colonnes en désignant les cellules </tr> --> On marque la fin de la ligne </table> --> On marque la fin du tableau Il existe aussi (mais nous en reparlerons si nécessaire) : <thead> <tfoot> <tbody> Liste dite ordonnée : <ol> <li> élément </li> </ol> Liste dite non ordonnée : <ul> <li> élément </li> </ul> Je n'ai pas d'autre balise en tête dans l'instant présent. Si une balise que l'on va utiliser n'est pas présente dans ce petit regroupement, je vous l'expliquerais le moment venu. Je vais maintenant vous parlez des liaisons. Si vous ne le savez pas encore l'html, le css, et le javascript ne se mettent pas dans le même fichier pour des raisons évidentes de : Propretés, Efficacités Facilités. Il y a deux types de lignes différents qui se mettent tous les deux dans l'entête (head) du fichier html : <link href="assets/css/style.css" rel="stylesheet"> Ici, nous importons, créons un lien entre les deux fichiers. Notez que la seule valeur à changer est celle du href="" (Qui désigne une destination) vers la destination du fichier voulu. <script src="assets/js/modernizr.js"></script> Cette balise est assez bizarre je sais. Vous n'avez qu'à changer la destination nécessaire dans la valeur de l'attribue src. Je vous propose d'entamer maintenant PHP, PDO : PHP --> PDO: Bien, je vous rappelle que nous avons déjà crée notre connexion PDO stockée dans la variable db. Je vous propose pour commencer de déjà vous apprendre à créer une fonction en php. Une fonction vous permet d'effectuer un protocole plus ou moins long sans devoir le retaper entièrement. C'est le même principe que les fonctions en mathématiques, sauf qu'ici, nous ne faisons pas qu'ajouter des nombres, multiplier, etc... La structure d'une fonction est assez simple : <?php function GetUserIP(){ } ?> Ici, nous avons crée notre fonction nommée GetuserIP. Si votre fonction prends en compte des paramètres, (comme un x en mathématiques par exemple), vous les ajouterez entre les parenthèses, sous forme de variable, et séparés par des virgules. Ici, je vous montre une fonction assez technique, ça va peut-être, être même la fonction la plus compliquée que nous allons faire avec ce CMS. Car clairement... Il n'y a rien de compliqué sur un CMS Metin2. Bien, avant de commencer à écrire notre code, il sera préférable de savoir exactement ce que va faire la fonction. Pour des raisons de sécurités, nous allons vérifier que l'utilisateur dispose bien d'une IP valide, et que le serveur peut "capter". On ne sera pas à l'abri des VPNs, mais c'est déjà mieux que rien. Nous allons définir pour commencer trois variables qui contiendront : client L'ip de la personne enregistrée si possible forward L'ip malgré un proxy remote L'ip du client qui demande la page courante Pour cela nous allons utiliser une super variable. Si vous avez lu l'autre tutoriel, vous savez que les supers variables commencent toutes pas : $_ ici, nous allons utiliser la super variable server, donc : $_SERVEUR. Pour client et forward, nous allons mettre un arobase devant le dollars. Pour un soucis de type, je m'attarde pas là dessus, nous ne l'utiliserons plus de toute façon... Vous ne pouvez pas deviner le code, sauf si vous chercher des heures sur la doc, cela donne : <?php $client = @$_SERVER['HTTP_CLIENT_IP']; $forward = @$_SERVER['HTTP_X_FORWARDED_FOR']; $remote = $_SERVER['REMOTE_ADDR']; ?> Bien, nous allons maintenant vérifier si au moins client ou forward à le format d'une IP, sinon, nous utiliserons remote. Nous allons pour ça faire un bloc de condition facilement représentable grâce à ce schéma : On définie la variable client On définie la variable forward On définie la variable remote Si client ressemble à une IP valide, si oui : On assimile sa valeur à une autre variable nommée IP Si client n'a pas l'air d'être une IP valide : On vérifie si forwad là, si oui : On assimile sa valeur à une autre variable nommée IP Sinon : On assimile la valeur de remote à la variable nommée IP. Pour vérifier son format, on va utiliser une filtre, pas d'expression régulière c'est démodée. Vous ne pouvez pas le deviner, cela donne : <?php $client = @$_SERVER['HTTP_CLIENT_IP']; $forward = @$_SERVER['HTTP_X_FORWARDED_FOR']; $remote = $_SERVER['REMOTE_ADDR']; if(filter_var($client, FILTER_VALIDATE_IP)){ $ip = $client; }elseif(filter_var($forward, FILTER_VALIDATE_IP)){ $ip = $forward; }else{ $ip = $remote; } ?> On utilise donc les outils if --> Il veut dire "Si", soit : Si ce que je te dis dans la parenthèse est vrai (=true) alors : Il fait ce qu'il y a entre les accolades. Si la condition est respectée, on sort de la boucle. elseif marche comme le if à une différence près : Il ne peut être placé qu'après un if Ne s'exécutera que si le if n'est pas respecté Si la condition est respectée, on sort de la boucle. else --> Si aucune des conditions n'est respectées, le code fait ce qui se trouve dans le else. Sachez que additionner des if à la suite, et mettre des elseif n'a pas le même effet. Si vous enchainez des if, le code va tous les essayer un par un. Si vous mettez des elseif, il va tous les vérifier dans l'ordre, mais dès qu'il aura trouvé une solution, il ne va plus suivre les autres elseif de ce bloc. Il ne nous reste plus qu'une chose à faire : Vérifier si la variable IP est définie Si non : On sort de la condition en affichant une erreur Si oui : On retourne l'IP en sortit de variable Je vous montre une autre façon de faire un if (pas de panique, il n'en n'existe que deux) je vous explique après, notre fonction donne : <?php function GetUserIp(){ $client = @$_SERVER['HTTP_CLIENT_IP']; $forward = @$_SERVER['HTTP_X_FORWARDED_FOR']; $remote = $_SERVER['REMOTE_ADDR']; if(filter_var($client, FILTER_VALIDATE_IP)){ $ip = $client; }elseif(filter_var($forward, FILTER_VALIDATE_IP)){ $ip = $forward; }else{ $ip = $remote; } if(!isset($ip)) exit("ERREUR"); return $ip; } ?> Quelques petites précisions : La méthode exit permet de retourner une erreur et d'arrêter le script actuel, il va bloquer l'utilisateur. La méthode return permet de sortir de la fonction en retournant une valeur, ici l'ip. La méthode isset() permet de vérifier si une valeur est définie ou non. Ajouter un ! devant des méthodes telles que isset inverse leur fonctionnement. En quelque sorte nous faisons ici : Si l'ip n'est pas définie : Tu dégages le mec avec un bon coup de pied au cul Sinon, tu le laisse passer ! Et voilà vous avez fait votre première fonction ! Et pas des plus facile en plus. S'il y a du code que vous ne comprenez pas. La divine bible est là pour vous. Bien, allons enfin au vif du sujet : PDO. Pour envoyer une query au serveur, nous allons : Stocker notre requête dans une variable Lui attribuer des paramètres si nécessaires Exécuter la requête. Pour cela nous allons stocker notre requête dans une variable en utilisant un facteur d'affectation. Nous allons pour ça nous servir de la variable DB de la dernière fois. Si vous utilisez la variable $db dans une fonction, il faudrait que vous refassiez un include dans cette même fonction. Bien, on va préparer une query, on va utiliser : db->prepare(query); On va assigner ça à la variable $Akihira par exemple, tapons : <?php $Akihira = $db->prepare(); ?> Bien, nous allons taper une query au hasard, par exemple une assez simple : <?php $Akihira = $db->prepare("SELECT * FROM account.account"); ?> Bien, ici pas besoin d'attribuer des paramètres, vous pouvez exécuter votre fonction comme ceci : <?php $Akihira = $db->prepare("SELECT * FROM account.accoun"); $Akihira->execute(); ?> Maintenant, nous avons trois façon de récupérer des informations sur ce que nous avons fait : Savoir combien de ligne ont été affectée, ici cela nous renvoie le nombre de compte crée en réalité? rowCount(); Sélectionner les lignes affectées avec les colonnes indiqués fetch(); Sélectionner toutes les les colonnes de toutes les lignes affectées. fetchAll(); Bien, pour le rowCount(); c'est le plus simple, tapez : <?php $Akihira = $db->prepare("SELECT * FROM account.accoun"); $Akihira->execute(); $Akihira = $Akihira->rowCount(); ?> Ici, comme nous avons tout sélectionner ("*"), au niveau où vous êtes pour l'instant, il n'y a pas de différence, mais ne vous habituez pas à ça, il y en aura bientôt ! Pour le fetch, il va vous créer un tableau, il va falloir utiliser une méthode spécial pour obtenir les résultats. Mais on va voir ça dans un prochaine tutoriel où je vous parlerais plus en détails des tableaux, et on verra aussi la boucle foreach je pense pour pouvoir parler plus en détail du fetchAll. En attendant je vous dis merci d'avoir lu, et référé vous à la doc en attendant Salut !
  6. Salut ! Comme les tutoriels de @Takuma on été (si j'ai compris) totalement détruit par le changement de moteur forum, je vais essayer de reprendre ses tutoriels avec plus de détails, et en publiant du contenue régulièrement. J'espère faire au moins aussi bien qu'il l'a fait. Voilà pourquoi j'ai repris son nom de poste, et que je vais partir sur la même branche que lui. Introduction : Vous voulez apprendre à créer votre CMS metin2 ? Mais vous ne vous pensez pas capable d'apprendre tant de langage de programmation ? Pas d'inquiétude. Je vais vous guider pas à pas (dans l'élimination des pages de publicité... Ah non désolé ça c'est une pub !) pour vous familiariser avec l'HTML et le CSS. Je vous donnerais également quelques astuces en JavaScript, chose que il me semble @Takuma n'avait pas fait. Puis dans le même temps, nous allons parler de PHP, et même l'utiliser. HTML : L'HTML est un langage de balisage. Il permet de donner une structure à votre code. Tout seul, il ne rend rien de beau. la mise en page s'effectue avec un autre langage. CSS : Nous parlions de mise en page ? La voici. Le CSS est un lagage de style, il permet de mettre en page votre site web. Vous pouvez déjà commencer rien qu'avec ses connaissances basiques à créer un site web. Cependant, il sera nommé site statique. Pour la simple raison que aucune interaction ne sera possible avec votre site. Ces deux langages permettent une création de page de présentation par exemple. Impossible de faire un espace membre digne de ce nom ou autre. JavaScript/JS : C'est un langage de script. Il permet de faire des pages webs interactives. Malheureusement, nous ne pouvons nous contenter de celui-ci. Il est accessible au client, c'est à dire qu'il peut le modifier, et faire ce qu'il veut de son côté. PHP : Le PHP ! Mon petit chouchou. Le PHP lui, par rapport aux trois aux langages ci-dessus, est exclusivement interprété par le serveur, le client ne recevra jamais aucune ligne de PHP. Votre PHP est traduit dans les trois langages ci-dessus avant d'être envoyer au client. Cela vous permet des opérations sécurisées et efficaces. Ne négligez cependant pas la sécurité de votre site web, elle est primordiale. PHP est très pratique, mais s'il n'est pas maîtrisé, en mettre sur votre site peut-être comme vous tirez une balle dans le pied. Notre site web, se devra d'être complet. Comme je n'ai pas le temps de créer à la main un design, je ne l'évoquerai pas. Vous pouvez cependant utiliser vous des designs déjà partagés. Ou même créer le votre, vous trouvez tout ce dont vous avez besoin sur internet en vous renseignant sur l'HTML et le CSS. Avant de vous dire ce que contiendra notre site web, je dois vous parler de la canon, la belle, la jolie, la sacrée, la légendaire, la divine et céleste documentation PHP ! Elle est complète, claire, disponible en français, courte et efficace. Ce sera pour mes tutoriels votre bible. Oubliez vos religions elle, vous prierez elle, et seulement elle. Bien ! Que va contenir notre futur site web alors ? Un système de news, vous pourrez les gérer entièrement sans aucune manipulation technique grâce à son panneau d'administration qui vous permettra : Ajouter une news, Supprimer une news, Modifier une news, Épingler des news afin qu'elles soient visibles en priorité par rapport aux autres. Un espace membre complet : Inscriptions : Nom de compte, Mot de passe crypté Mot de passe clair si l'utilisateur le désire en cas de perte de mot de passe pour ne pas avoir à remettre à 0 son mot de passe : Panneau de prévention en quoi cette option est déconseillée. Mail valide. Acceptation des règles. Connexion, Gestion du compte : Suppression du compte, Modification du mot de passe, Gestion du mot de passe entrepôt si celui-ci est ouvert IG (activé), Changer son adresse mail. Inscription avec validation par mail. Un système de vote : Vérification du vote : Si le joueur a bien voté Si le temps entre les vote est respecté Accréditation des crédits si le vote est validé. Classements : Classement joueurs complet : Top 10 en page d'accueil, Classement habituel avec les pages. Classement de guilde complet : Top 10 en page d'accueil en fonction du niveau et des victoires. Classement habituel avec les pages. Option permettant de trier le classement par ordre décroissant/croissant. Option permettant de chosire le facteur pris en compte pour la classement : Niveau Nom (A->Z ou inversement) Nom du chef (A->Z ou inversement) Energie etc... Un ItemShop complet et opérationnel : Gestion des catégories : Ajout, Suppression, Modifications (ordre, nom, etc...). Gestion des items : Ajout, Suppression, Modifications (ordre, prix, nom, etc..). Les icons seront placés automatiquement grâce au pack icon qui sera uplodad dans une destination bien spéciale. Système de langue : Un fichier spécifique par langue, Modifiable sur le site, Choix pour chaque personne la langue désirée qui sera stockée grâce à un système de cookies s'ils sont acceptés par le client. Système de support : Comptes : Super-Administrateur (compte d'ID 1) : Ajout de Super Administrateur, Suppression, Tous les droits. Administrateurs (Implementor) : Toute la modérations et l'administration nécessaire. Modérateur (GM) : Toute la modération permettant de répondre au sujet sans avoir tous les droits d'un administrateur. Système d'installation : Configuration générale du site : Nom, Date, Copyrtight, etc... Configuration des base de données : Base de données du site : Nom de la base de données. Base de données du jeu : Nom des bases de données : Player, Account, Common. Adresse IP, Utilisateur, Mot de passe, Port, Administration : Configuration générale du site, Gestion des GM, Configuration des langues Gestion du jeu. J'oublie beaucoup de paramètre. Mais quand nous aurons déjà fait tout ça, nous aurons déjà travailler un petit bout de temps ! Vous êtes prêts ? Démarrage du tutoriel... Les pré-requis: Pour suivre ce tutoriel, vous n'aurez pas besoin de beaucoup de chose, mais elles seront toutes obligatoires. Un éditeur de texte adapté. Je ne compte pas vous faire un cours sur l'édition du code avec un bloc note, foncez prendre un éditeur adapté tel que : Sublime texte NotePad++ Atome DreamWeaver Un serveur WEB : Distant : Hebergement mutualisé VPS, dédié avec Apache Locale : EasyPHP, WAMP, XAMP. Des bases de données : Player Common Account Website Si vous avez besoin d'aide pour ceci, je vous aiderais en pv. Où non demandez à @Takuma ça l'occupera, d'après ce qu'il m'a dit il n'a rien à faire de ces vacances d'étudiant. Fainéant. L'arborescence : Mon arborescence est beaucoup utilisée, mais est des fois assez bizarre. Je vais m'adapter à une arborescence plus connu, cela donne : assets css js img pages db.php functions.php admin capatcha index.php Nous ajouterons plus tard des dossiers comme celui de l'installateur par exemple, mais celui-ci se fera à la fin. En attendant pour conclure cette première partie au même point que @Takuma l'avait fait, je vais vous faire créer une connexion PDO. La connexion à la base de données principale : Bien, pour ceci, nous allons nous rendre dans notre fichier index.php : Nous allons tout d'abord dire au site : "Je veux écrire du PHP", pour cela il n'existe qu'un moyen (enfin deux... mais utilisez celui là) : <?php //J'écris ici mon code PHP ?> Notez que tout le contenue précédé de // dans sa ligne est nommé commentaire. C'est à dire qu'il ne sera pas interprété par PHP, vous pouvez mette ce que vous voulez. Ce code ne sera pas visible pas le client non plus. Ici, à la différence de @Takuma Je vais directement inclure mon fichier nommé functions.php dans mon index. Ce fichier va contenir toutes les fonctions PHP, mais aussi la connexion à la base de données, vous verrez. <?php require_once('assets/functions.php'); ?> Il existe trois façon d'inclure du code d'un fichier dans un autre : require_once include require Ces méthodes ont toutes les trois le même but, mais elles ont des subtilités : include : Vous pouvez importez autant de fois que vous voulez le fichier. Si le fichier n'est pas trouvé le code ne s'arrête pas, et continue quand même. require : Vous pouvez inclure autant de fois que nécessaire le fichier dans un autre. Cependant, si une seule fois le fichier n'est pas trouvé, le code s'arrête net, puis affiche une erreur. require_once : Vous pouvez inclure seulement une seule fois un fichier dans un autre, et celui-ci doit obligatoirement être là, sinon erreur et arrêt immédiat du code. Bien, une fois ceci tapez, ouvrons notre fichier functions.php (assets/functions.php), puis tapez : <?php require('db.php'); ?> Ici, nous donnons cette instruction au fichier : Tu importes le fichier db.php qui se trouve dans la même destination que toi, si tu ne le trouves pas, tu coupes tout Franky ! Pourquoi ne pas avoir faire un require_once ? Vous comprendrez plus tard, nous allons importer souvent ce fichier. Passons pour finir à l'édition de notre fichier db.php : Nous allons créer quatre variables : IP : Elle contiendra l'adresse d'accès à nos base de données USER : Elle contiendra l'utilisateur PASS : Elle contiendra le mot de passe WEBDBNAME : Elle contiendra le nom de la base de données du site. Nous allons comme @Takuma créer une variable (un peu spécial) qui va contenir notre connexion PDO. Créons déjà nos variables, en PHP pour créer une variable nous utilisons : $ puis le nom de la variable (ne doit pas commencer par _ car c'est le prefix des super variables, nous verrons ça plus tard). Cela donne : <?php $IP = "192.168.1.29"; $USER = "root"; $PASS = "pF5tfiUH3f7KDyT49rQu52Hu88"; $WEBDBNAME = "website" ; ?> Adaptez selon vos identifiants. Nous allons par contre rajouter un petit code qui vous nous permettre d'afficher absolument tous les warnings/errors possibles et imaginables dès qu'il y en aura en ajoutant ces deux lignes avant la déclaration de notre variable IP : <?php ini_set('display-errors', true); error_reporting(E_ALL); ?> (Je n'ajoute les balises PHP que pour que vous puissiez profiter de la coloration syntaxique). Bien créons maintenant notre connexion dans un bloc try cela donne en théorie : Essaye de : Te connecter Si cela echoue : Tu coupes le code Tu affiches l'erreur Bien, je vous donne le code, vous ne pouvez pas le devinez de toute façon, en tout cela donne : <?php ini_set('display-errors', true); error_reporting(E_ALL); $IP = "192.168.1.29"; $USER = "root"; $PASS = "pF5tfiUH3f7KDyT49rQu52Hu88"; $WEBDBNAME = "website" ; try { $db = new PDO('mysql:host='.$IP.';dbname='.$WEBDBNAME, $USER, $PASS,array(PDO::MYSQL_ATTR_INIT_COMMAND => 'SET NAMES utf8')); } catch (PDOException $e) { print "DATABASE ERROR : " . $e->getMessage() . "<br/>"; die; } ?> Et voilà, vous avez crée votre variable $db qui contient les informations nécessaires à l'utilisation complète de PDO. J'espère avoir été claire, désolé j'ai pas vraiment l'habitude de faire des tutoriels. D'habitude je reste un peu inactive, en ninja sur les autres forums. Bonne lecture !
  7. Bonjour à tous ! Aujourd’hui je vais vous apprendre à mettre en place un serveur privé RaiderZ ! /!\ Ce tutoriel ne demande aucune connaissance particulière en informatique, et est accessible à tous /!\ Pré-requis : Un ordinateur ou un VPS utilisant Windows 7 ou plus PostGreSQL Driver ODBC PostgreSQL Navicat 12.0, DataGrip, ou n’importe quel programme de gestion de BDD fonctionnant avec PostgreSQL Le DevKit de RaiderZ Notepad++ /!\ Ce tutoriel est réalisé avec PostgreSQL 10.1 et Navicat 12.0.13 /!\ I) Installation de PostgreSQL Lancez le fichier d’installation, et suivez les étapes jusqu’à la création du mot de passe de l’utilisateur par défaut. Entrez le mot de passe que vous voulez (c’est celui qui sera utilisé plus tard pour configurer le serveur). Une fois l'installation finie, décochez la case Stack Builder et cliquez sur "Terminer" II) Installation de Navicat → Voir[Partage]Navicat 12.0.13 III) Configuration de PostgreSQL A) Création des BDD Pour créer les tables, allez dans votre menu démarrer, et cherchez: Lancez le programme, et validez par "Entrée" jusqu'au mot de passe: Vous vous souvenez du mot de passe que vous avez mis à l'installation? Tapez le /!\ Le mot de passe ne s'affiche pas quand vous le tapez, c'est normal ! /!\ Une fois que vous êtes connecté, vous pouvez taper la commande suivante: Puis validez par "Entrée". Faites la même chose pour les deux bases de données restantes, en validant bien par "Entrée", et en oubliant pas le ; à la fin: Vous pouvez fermer, vos tables sont créées! B) Configuration de Navicat La configuration de Navicat est assez simple, commencez par ajouter une nouvelle connexion PostgreSQL: Puis configurez le serveur comme sur le screen: Dans le champ "password", mettez votre mot de passe PostgreSQL. C) Restauration des tables C'est sans doute la partie la plus compliquée du tutoriel, accrochez vous ! Cherchez le dossier d'installation de PosgreSQL, et allez dans le dossier "bin": /!\ Si vous ne l'avez pas modifié, c'est C:\Program Files (x86)\PostgreSQL\10\bin /!\ Une fois que vous avez trouvé le dossier, ouvrez une fenêtre de commande et faites un cd pour arriver jusqu'au dossier: Vous êtes prêt à lancer les commandes ! Pour créer les tables, vous devez utiliser la commande suivante: psql : Nom du programme principal de PostgreSQL -U postgres : Nom d'utilisateur pour se connecter à la BDD -d rz_account : Nom de la base de donnée -f C:\Users\...\rz_accountdb.sql : Fichier de sauvegarde à restaurer Une fois la commande tapée, validez avec "Entrée", vous devriez avoir ceci qui s'affiche dans la fenêtre de commande: Vous connaissez la chanson: tapez votre mot de passe PostgreSQL Une fois le mot de passe validé, la restauration se fera automatiquement, et vous devriez voir ceci: Faites la même chose, mais pour la base de donnée rz_gamedb avec le fichier rz_gamedb.sql la commande devrait mettre quelques dizaines de secondes à se finir. Après ça, vous pouvez fermer la console, on en a fini avec elle ! Pour vérifier que vos tables sont bien mises, vous pouvez allez vérifier dans Navicat: IV) Configuration des files Dans chaque dossier du serveur se situe un fichier server.ini. C’est lui qui contient la configuration De chaque programme du serveur. Vous devez ouvrir chaque fichier server.ini, et remplacer à l’intérieur de cette ligne : PASSWORD = "password" password par votre mot de passe PostgreSQL. Exemple: V) Installer le driver ODBC Ouvrez le fichier d’installation des drivers ODBC, et installez le. Allez dans Panneau de Configuration → Outils d’administration → Sources de données ODBC (32 bit): Une fois dans le menu des sources de données, rendez vous dans l’onglet Utilisateur, cliquez sur « Ajouter », et sélectionnez « PostgreSQL Unicode » dans la liste, puis cliquez à nouveau sur « Ajouter »: Configurez le driver comme suit : Cliquez ensuite sur "Tester", si ce message: s'affiche, c'est que tout est bon ! V) Relier le client au serveur Pour relier le client au serveur, ouvrez le fichier « Run RaiderZ.bat », et changez l’adresse IP par celle de votre serveur. Exemple : ./START Raiderz.exe localhost Exemple : ./START Raiderz.exe 142.89.32.16 Voilà, vous avez un serveur complet fonctionnel ! Il ne vous reste plus qu'à le lancer ! VI) Lancement du serveur Pour lancer le serveur, vous devez simplement lancer, dans n'importe quel ordre, les quatre programmes: LoginServer AppServer GameServer MasterServer VII) Quitter le serveur Pour quitter le serveur, vous avez juste à fermer le programme "MasterServer", et le reste suivra ! Pour continuer: Si le tutoriel vous a été utile, laissez un point de réputation ou un commentaire, ça fait toujours plaisir !
  8.  tutoriel

    Bonjour. Je sais pas quoi faire.. du coup je vais vous faire un petit tutoriel sur les tuples en Python. 1. Définition : Un tuple, c'est une collection servant à créer des listes. On va dire des listes complexes pour le coup. Par exemple un tuple peut être imbriqué dans un autre, ça permet de créer des hiérarchies. Pour vous donnez une image simple, imaginez qu'on fait une liste de liste. Par exemple votre ordinateur : C -->Plusieurs fichiers qui eux même contiennent plusieurs fichiers, etc.... L'avantage des tuples c'est qu'on peut créer des choses complexes, qu'on ne peut pas faire avec des listes simples. Par contre, par rapport au liste, les tuples ne peuvent pas être changés. On peut créer un nouveau tuple portant le même nom, et modifier d'une certaine manière, mais vous pouvez pas le faire avec un tuple qui existe déjà. Après avoir lu ça, vous pensez peut être que ce n'est pas pratique ? Bah... Niveau sécurité, c'est le top ! Et je vous parle pas de la rapidité ! 2.Les possibilités : Pour obtenir les possibilités disponible avec les tuples, ou même ce que vous voulez tapez : dir(....) Par exemple, si je crée un tuple nommé MonTuple, je vais taper : dir(MonTuple) Python me retourne donc : Vous obtenez donc la liste de ce que vous pouvez faire, la voici ici : ['__add__', '__class__', '__contains__', '__delattr__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__getitem__', '__getnewargs__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__iter__', '__le__', '__len__', '__lt__', '__mul__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__rmul__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', 'count', 'index'] Notez que si je dis "liste" c'est vrai, si vous ne comprenez pas, je vous renvoie là : Contenu Masqué Ne vous fiez par contre pas à ce qu'il vous dit... Des fois Python il ment un peu 3. Exemples avec démonstration : Bien, pour commencer, créons un tuple que l'on va nommer... Takuma ! Comme je suis pas du tout égoïste : Contenu Masqué Bien, vous allez voir pourquoi je vous ai dis ça : Prenons la première fonction "add", bien essayons ! Tapons : Takuma = ('ASIKOO', 'Kijaru', 'Kobalt') Maintenant tapons : Takuma = Takuma.__add__(('MeiLaPoire',)) Et voilà... Vous avez ajouté un nouveau tuple à Takuma, et le résultat a été placé dans une nouvelle copie de Takuma. L'ancienne version de Takuma est détruite après cet appel. En gros... Le fonction __add__() accepte uniquement des tuples en entrée. Si vous créez un tuple qui ne comprend qu'une seule entrée, et que vous ne mettez pas de virgule, Python va vous gronder : 4. Créer un tuple dans un tuple : Bien, comme je vous l'aider, on va pouvoir faire des tuples dans des tuples, créons par exemple le tuple "FunkyEmu", contenant les team FE et la team Emu : FunkyEmu = ('TeamBidon1', 'Teambidon2') Bien, maintenant nous allons ajouter à FunkyEmu, sa team et ses membres, tapons : FunkyEmu = FunkyEmu.__add__(('TeamFE',('Calypso', 'Mei', 'Galet', 'Ant0k'))) Maintenant, pour qu'il nous retourne la Team FE, tapons : FunkyEmu[3] Python nous retourne alors : ('Calypso', 'Mei', 'Galet', 'Ant0k') Bien ! Félicitations, vous avez dompter la bête. Petite dédicace à : Car je viens de voir ton message du coup je le fais Pour ceux qui ne le savent pas car je ne l'ai pas dis dans le tuto : Immuables = Qui ne peut pas changer. C'est pour ça qu'un nouveau tuple se crée à chaque fois. Cordialement.
  9.  tutoriel

    Bonjour à tous, je viens vous faire un petit partage qui va vous permettre de rajouter des boutons sur votre interface pour ouvrir des pages Internet (Forum & Site par exemple). Voici le rendu: Vous pouvez, si vous le voulez, changer le nom des éléments du code. Faites aussi attention aux tabulations !!!! Je ne l'ai pas trouvé sur funky donc j’espère avoir fais une bonne action.
  10. Salut à toi ! Tu n'as jamais fait de serveur Metin2 ? Ce tutoriel est fait pour toi ! Je vais t'expliquer comment créer un serveur de A à Z ! Tu pourras ensuite le modifier à ta guise et créer un serveur à ton image ! Lors de ce tutoriel, vous allez apprendre à créer un serveur avec HAMACHI ! Vous pourrez ainsi vous connecter avec vos amis via le réseau Hamachi ! Prêt ? Alors commençons ! Attention !! Ce tutoriel ne fonctionnera seulement avec des files Metin2 <= 2012 (ce sont les files 2014 installés sur cette VM). Il faut donc installer les filles 2012 une fois le serveur configuré. Aujourd'hui, aucune solution n'a été trouvé pour faire fonctionner un serveur Hamachi avec des files 2014+. Problème rencontré: Quand une personne externe choisi un personnage lors de la sélection, il se fait instantanément déconnecté par le serveur. I. Téléchargement VirtualBox - Contenu Masqué Navicat - Contenu Masqué WinSCP - Contenu Masqué Le serveur (le VDI) - Contenu Masqué Le client - Contenu Masqué Hamachi - Contenu Masqué Port (Le fichier #138) - Contenu Masqué II. La machine virtuelle 1. Avant de se lancer dans la création de la machine, installez Hamachi et lancez le. Vérifiez que Hamachi est bien actif. Vous devriez avoir cela (avec votre propre IP Hamachi). Dans mon cas, mon IP Hamachi est: 25.73.148.152 (Notez bien votre IP Hamachi, nous en aurons besoin ! 2. Installez VirtualBox. Il nous permettra de virtualiser le système d'exploitation FreeBSD. 3. Lancez le logiciel VirtualBox 4. Cliquez sur le bouton Nouvelle, en haut à gauche 5. Choisissez un nom pour votre machine virtuelle. Dans Type, choisissez BSD et dans Version, FreeBSD (32bit ou 64bit en fonction de votre ordinateur). Et cliquez sur Suivant 6. Allouez la mémoire vive à votre machine virtuelle en fonction de votre ordinateur. Le minimum conseillé est 1go (1024Mo). N'hésitez pas à en donner plus le temps que le curseur est dans la barre verte. Cliquez sur Suivant. 7. Cochez la case " Utiliser un fichier de disque dur virtuel existant " et choisissez le fichier VDI (.vdi) que vous avez téléchargé. Cliquez ensuite sur Créer. 8. Votre machine virtuelle est crée. Configurons la carte réseau. Clique droit sur votre VM (virtual machine) puis Configuration 9. Dans le menu Réseau, mettez " Accès par pont " dans mode d'accès réseau et dans nom, mettez la carte réseau Hamachi !). Cliquez ensuite sur OK. 10. Votre machine virtuelle est maintenant créée ! III. Configuration de la machine virtuelle 1. Pour lancer votre machine virtuelle, il suffit de cliquer deux fois sur votre machine. Une fenêtre s'ouvre et votre système d'exploitation démarre. Patientez. On vous demande ensuite de vous connecter à un utilisateur. 2. Dans login, entrez: root pour login et dans Password, mettez mcncc.com. Vous êtes maintenant connecté et vous avez accès à tout le système d'exploitation. Tapez la commande sysinstall dans le terminal. Vous tomberez sur: 3. Vous pouvez naviguer dans ce menu avec les flèches du haut et du bas puis la touche Entrée pour sélectionner le choix. 4. Dans l'ordre, allez sur Configure, puis Networking puis Interfaces. Vous tombez sur: 5. Choisissez em0. Une petite fenêtre s'ouvre. Répondez No. Une autre s'ouvre, répondez No aussi. Vous tombez sur la configuration de l'interface réseau. 6. Configurez comme sur le screen ci-dessous. Remplacez VOTRE_IP_HAMACHI par l'IP que vous avons vu tout à l'heure sur le logiciel Hamachi, dans mon cas 25.73.148.152. (le petit 1 u I)). Et remplacez IP_HAMACHI_100 par votre IP Hamachi .100. L'IP .100 est tout simplement votre IP Hamachi mais avec le nombre 100 à la fin. Par exemple dans mon cas, l'IP Hamachi est: 25.73.148.152, alors mon IP .100 sera: 25.73.148.100. 7. Une fois la configuration terminé, validez avec le bouton OK. Vous tomberez sur le screen ci-dessous. Répondez Yes à la question. 8. Appuyez sur la touche Echap (ou le bouton Cancel) jusqu'à quitter cette interface. 9. Tapez la commande /etc/rc.d/netif restart pour prendre en compte le changement. Les informations de votre interface réseau s'affichent: 10. L'adresse IP devrait s'afficher. Je l'ai entouré en rouge. C'est celle qui se termine par .100. Pensez à bien noter votre adresse IP (celle qui s'affiche sur votre machine et non celle du screen). Nous en aurons besoin. IV. Configuration de portmap 1. Commencez par ouvrir le logiciel portmap.exe. Vous avez une liste de port, il va falloir tous les configurer sur votre IP .100 2. Pour chaque ports (un port = une ligne), répétez les étapes 2 et 3. Sélectionnez la ligne souhaité puis cliquez sur l'icône en haut à gauche. 3. Une petite fenêtre s'ouvre. Mettez votre IP .100 dans le champ indiqué (voir screen ci-dessous) et valider en cliquant sur le bouton (entouré en rouge): 4.Une fois tous les ports configuré, vous devriez voir votre adresse IP .100 configuré sur tous les ports dans la colonne indiqué ci-dessous. (Dans le cas du screen, c'est mon IP .100) 5. Pour chaque ligne (les sélectionner une par une), cliquez sur le bouton vert qui se trouve en haut pour les activer (le cercle vert avec l'icône blanche dedans). Vous devriez avoir toutes les pastilles de couleur verte ! 6. La configuration de portmap est terminé ! Laissez la fenêtre ouverte, sinon cela ne fonctionnera plus ! V. Lancement du serveur Metin2 1. Retournons sur la machine virtuelle (VirtualBox). Tapez la commande: cd /usr/metin2 (voir screen ci-dessous). 2. Vous voila dans le dossier de votre serveur metin2. Pour démarrer le serveur il suffit de taper: sh start.sh (et sh stop.sh pour l'arrêter). Entrez ensuite le nombre de channel que vous voulez ouvrir. Pour commencer, un seul suffit. 3. Votre serveur Metin2 est lancé ! Il ne vous reste plus qu'à configurer votre client ! Bien entendu, laissez votre fenêtre Virtual Box ouverte pour que votre serveur reste ouvert. VI. Créer son réseau Hamachi ! 1. Afin que vous puissiez vous connecter tous ensemble sur le serveur, il faut créer un réseau Hamachi. Pour cela, sur Hamachi, cliquez sur Réseau puis Créer un réseau. 2. Dans ID Réseau, mettez le nom du réseau (ce que vous souhaitez). Et dans Mot de passe, le mot de passe d'accès à votre réseau. Cliquez ensuite sur Créer. Votre réseau Hamachi est créé. 3. Attention ! Pour que les joueurs puissent se connecter à votre serveur, il faut qu'ils rejoignent votre réseau. Pour cela, au lieu de cliquer sur Créer un réseau (dans le menu Réseau), cliquez sur Rejoindre un réseau. Ensuite il suffit juste de rentrer le nom du réseau puis de cliquer sur Rejoindre. 4. Par exemple sur mon réseau asikoojtm, j'ai un joueur qui la rejoint. (je l'ai caché pour ne pas voir son nom mais c'est Asikoo). VI. Se connecter In Game ! 1. Commencez par extraire le client de Client 2014.rar sur votre bureau. 2. Il faut configurer le client de sorte à ce qu'il se connecte sur l'adresse IP de votre serveur. Il existe un tutoriel pour vous apprendre à le faire: Contenu Masqué 3. Dans le tutoriel, on vous demande de mettre une adresse IP. Pour votre client Metin2, il faut configurer votre client sur l'adresse IP .100. Par contre, pour ceux qui veulent se connecter sur votre serveur Hamachi, il faut configurer le client sur l'IP Hamachi normale (Celle qui ne termine pas par 100). Donc un client pour vous et un client pour vos joueurs. 4. Dans mon cas, 25.73.148.100 pour mon client à moi et 25.73.148.152 pour mes joueurs. 5. Une fois la configuration terminée, lancez metin2client.exe qui se trouve à la racine de votre client. 6. Connectez vous avec ces identifiants. User: admin Mot de passe: test 7. Choisissez votre personnage et ça y est, vous voila sur votre premier serveur privé Metin2 ! VII. Accéder aux fichiers du serveur 1. Pour accéder aux fichiers du serveur, il faut installer le logiciel WinSCP. Une fois installé, lancez le. 2. Cliquez sur nouveau site et remplissez comme l'image ci dessous 3. Vous voici connecté à WinSCP. Vous avez tout simplement un accès à votre système d'exploitation FreeBSD mais cette fois avec un explorateur de fichier, et non en ligne de commande. 4. Déplacez-vous dans l’arborescence. Cliquez sur .. pour revenir à la racine du serveur, cliquez sur le dossier usr puis metin2. Vous tombez sur: 5. Ceci sont les fichiers serveur de votre serveur Metin2 ! VIII. Accéder à la base de données du serveur 1. La base de données contient toutes les données de votre serveur (comptes, joueurs, etc ..). Pour y accéder, installez Navicat et lancez-le. 2. Cliquez sur le bouton Connection puis MySQL 3. Remplissez comme l'image ci-dessous puis cliquez sur Ok: 4. La connexion s'est créé dans le volet de gauche. Il suffit juste de cliquer 2 fois dessus pour s'y connecter. 5. Ceci est la base de donnée de votre serveur Metin2 ! IX. Fin du tutoriel C'est tout bon, vous avez tous les outils pour créer un serveur Metin2 à votre image FAQ des débutants à voir absolument: Contenu Masqué Si tu as besoin d'aide, n'hésite pas à aller créer un topic dans la catégorie Aide / Question / Support de la section Metin2. La communauté sera là pour vous aider. Bon courage, jeune apprenti !
  11. Bonjour/Bonsoir ! Dans ce tutoriel, je vais vous expliquer comment passer de Granny 2.4 à Granny 2.9. Ce tutoriel se passera dans les sources client. Donc, pour commencer, cherchez "granny_common_2_9_12_0_release.zip" dans le fichier extern de vos sources Novaline (si vous ne l'avez pas, le lien de téléchargement de l'archive serra mis à la fin du tutoriel). Une fois ce fichier trouvé, il vous faudra l'extraire puis prendre granny.h dans le dossier include puis le remplacer dans vos sources client (dans le fichier include). Prennez les fichiers .lib et .pdb dans le dossier lib/win32 puis mettez le dans vos sources client (fichier lib). Une fois ceci fais, attaquons le code ! (Attention, il faut faire les tabulation sois même !) Rendez vous dans Mesh.cpp du projet EterGrnLib. Cherchez: int * boneIndices = GrannyGetMeshBindingToBoneIndices(pgrnMeshBinding); Remplacez par: int * boneIndices = (int*)GrannyGetMeshBindingToBoneIndices(pgrnMeshBinding); Cherchez: return GrannyGetMeshBindingToBoneIndices(m_pgrnMeshBindingTemp); Remplacez par: return (int*)GrannyGetMeshBindingToBoneIndices(m_pgrnMeshBindingTemp); Cherchez: m_pgrnMeshDeformer = GrannyNewMeshDeformer(pgrnInputType, pgrnOutputType, GrannyDeformPositionNormal); Remplacez par: m_pgrnMeshDeformer = GrannyNewMeshDeformer(pgrnInputType, pgrnOutputType, GrannyDeformPositionNormal, GrannyAllowUncopiedTail); Voilà, nous en avons finis avec la partie Mesh.cpp vous pouvez enregistrer. Maintenant, ouvrez maintenant ModelInstanceUpdate.cpp du même projet. Cherchez: GrannyUpdateModelMatrix(m_pgrnModelInstance, fSecondsElapsed, (const float *) pMatrix, (float *) pMatrix); Remplacez par: GrannyUpdateModelMatrix(m_pgrnModelInstance, fSecondsElapsed, (const float *) pMatrix, (float *) pMatrix, false); Nous en avons aussi finis avec ModelInstanceUpdate.cpp vous pouvez enregistrer. Maintenant, ouvrez Material.cpp du même projet. Cherchez: granny_variant twoSideResult = GrannyFindMatchingMember(pgrnMaterial->ExtendedData.Type, pgrnMaterial->ExtendedData.Object, "Two-sided"); if (NULL != twoSideResult.Type) GrannyConvertSingleObject(twoSideResult.Type, twoSideResult.Object, TwoSidedFieldType, &twoSided); Remplacez par: granny_variant twoSideResult; if (GrannyFindMatchingMember(pgrnMaterial->ExtendedData.Type, pgrnMaterial->ExtendedData.Object, "Two-sided", &twoSideResult) && NULL != twoSideResult.Type) GrannyConvertSingleObject(twoSideResult.Type, twoSideResult.Object, TwoSidedFieldType, &twoSided, NULL); Vous en avez finis avec cette partie aussi, vous pouvez enregistrer. Maintenant, ouvrez ModelInstanceModel.cpp du projet EterGrnLib (toujours le même). Cherchez: return GrannyGetMeshBindingToBoneIndices(m_vct_pgrnMeshBinding[iMeshBinding]); Remplacez par: return (int*)GrannyGetMeshBindingToBoneIndices(m_vct_pgrnMeshBinding[iMeshBinding]); Vous en avez finis avec ModelInstanceModel.cpp et le projet EterGrnLib, vous pouvez enregistrer. Maintenant, allez dans le projet UserInterface et ouvrez UserInterface.cpp. Cherchez: static void GrannyError(granny_log_message_type Type, granny_log_message_origin Origin, char const *Error, void *UserData) { TraceError("GRANNY: %s", Error); } Remplacez par: static void GrannyError(granny_log_message_type Type, granny_log_message_origin Origin, char const* File, granny_int32x Line, char const *Error, void *UserData) { TraceError("GRANNY: %s", Error); } Voilà, vous en avez finis avec les sources client. Vous pouvez recompiler ! Pour finaliser cette installation, rendez vous dans l'archive du début et puis allez dans lib/win32. Prenez granny2.dll et remplacer l'ancien de votre client. Voilà, c'est tout pour ce tuto, je vous remercie de la lecture. En cas de problème, veuillez aller dans la section Aide/Question/Support. Lien de l'archive: Contenu Masqué (Merci à Liberty de l'avoir upload) Source du code: Metin2Dev Cordialement.
  12.  tutoriel

    Bonsoir à tous, Voici un tutoriel pour pouvoir ban un joueur connecté directement en jeu. Le tutoriel de base vient de Metin2Dev par Sanchez. Je l'ai modifié à un endroit, parce qu'elle fonctionnait pas pour moi. 1. Créer une table sql dans la base account : SET FOREIGN_KEY_CHECKS=0; -- ---------------------------- -- Table structure for `ban_ingame` -- ---------------------------- DROP TABLE IF EXISTS `ban_ingame`; CREATE TABLE `ban_ingame` ( `id` int(10) unsigned NOT NULL AUTO_INCREMENT, `name` varchar(24) CHARACTER SET ascii NOT NULL, `begins` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `finish` datetime NOT NULL DEFAULT '0000-00-00 00:00:00', `reason` varchar(256) CHARACTER SET ascii DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=MyISAM AUTO_INCREMENT=3 DEFAULT CHARSET=latin5; -- ---------------------------- -- Records of ban_ingame -- ---------------------------- 2. Ouvrez le fichier cmd.cpp ( dans le dossier game) et cherchez la ligne suivante : ACMD(do_block_chat); Ajoutez juste en dessous : ACMD(do_ban); 3. Cherchez la ligne suivante : { "block_chat_list",do_block_chat_list, 0, POS_DEAD, GM_PLAYER } et ajoutez ceci en dessous : { "ban", do_ban, 0, POS_DEAD, GM_IMPLEMENTOR }, 4. Cherchez la fonction suivante : ACMD(do_block_chat) { // GM이 아니거나 block_chat_privilege가 없는 사람은 명령어 사용 불가 if (ch && (ch->GetGMLevel() < GM_HIGH_WIZARD && ch->GetQuestFlag("chat_privilege.block") <= 0)) { ch->ChatPacket(CHAT_TYPE_INFO, LC_TEXT("그런 명령어는 없습니다")); return; } char arg1[256]; argument = one_argument(argument, arg1, sizeof(arg1)); if (!*arg1) { if (ch) ch->ChatPacket(CHAT_TYPE_INFO, "Usage: block_chat (0 to off)"); return; } const char* name = arg1; long lBlockDuration = parse_time_str(argument); if (lBlockDuration < 0) { if (ch) { ch->ChatPacket(CHAT_TYPE_INFO, "잘못된 형식의 시간입니다. h, m, s를 붙여서 지정해 주십시오."); ch->ChatPacket(CHAT_TYPE_INFO, "예) 10s, 10m, 1m 30s"); } return; } sys_log(0, "BLOCK CHAT %s %d", name, lBlockDuration); LPCHARACTER tch = CHARACTER_MANAGER::instance().FindPC(name); if (!tch) { CCI * pkCCI = P2P_MANAGER::instance().Find(name); if (pkCCI) { TPacketGGBlockChat p; p.bHeader = HEADER_GG_BLOCK_CHAT; strlcpy(p.szName, name, sizeof(p.szName)); p.lBlockDuration = lBlockDuration; P2P_MANAGER::instance().Send(&p, sizeof(TPacketGGBlockChat)); } else { TPacketBlockChat p; strlcpy(p.szName, name, sizeof(p.szName)); p.lDuration = lBlockDuration; db_clientdesc->DBPacket(HEADER_GD_BLOCK_CHAT, ch ? ch->GetDesc()->GetHandle() : 0, &p, sizeof(p)); } if (ch) ch->ChatPacket(CHAT_TYPE_INFO, "Chat block requested."); return; } if (tch && ch != tch) tch->AddAffect(AFFECT_BLOCK_CHAT, POINT_NONE, 0, AFF_NONE, lBlockDuration, 0, true); } // END_OF_BLOCK_CHAT Ajoutez juste après ceci : ACMD(do_ban) { // Args char arg1[256], arg2[256], arg3[256]; // Local variables const char* szName; const char* szReason; char szReasonEscape[1024]; int iDuration; one_argument(two_arguments(argument, arg1, sizeof(arg1), arg2, sizeof(arg2)), arg3, sizeof(arg3)); // Invalid syntax if (!*arg1 || !*arg2 || !*arg3) { ch->ChatPacket(CHAT_TYPE_INFO, "Invalid Syntax, usage: tip: don't use spaces in the reason, use _"); return; } szName = arg1; iDuration = atoi(arg2); szReason = arg3; DBManager::instance().EscapeString(szReasonEscape, sizeof(szReasonEscape), szReason, strlen(szReason)); if (iDuration <= 0) { ch->ChatPacket(CHAT_TYPE_INFO, "Duration can't be 0 or minus."); return; } LPCHARACTER tch = CHARACTER_MANAGER::instance().FindPC(szName); if (!tch) { ch->ChatPacket(CHAT_TYPE_INFO, "%s is not playing", szName); return; } if (!tch->GetDesc()) { ch->ChatPacket(CHAT_TYPE_INFO, "%s don't have desc", szName); return; } if (tch == ch) { ch->ChatPacket(CHAT_TYPE_INFO, "What's wrong with you? Don't ban yourself"); return; } if (tch->GetGMLevel() > GM_PLAYER) { ch->ChatPacket(CHAT_TYPE_INFO, "Do not ban GMs"); return; } std::auto_ptr msg(DBManager::instance().DirectQuery("INSERT INTO account2.ban_ingame (id, name, begins, finish, reason) VALUES ('%d', '%s', NOW(), FROM_UNIXTIME(UNIX_TIMESTAMP(CURRENT_TIMESTAMP()) + %i), '%s')", tch->GetAID(), tch->GetName(), iDuration * 3600, szReasonEscape)); DBManager::instance().DirectQuery("UPDATE account2.account SET availDt = FROM_UNIXTIME(UNIX_TIMESTAMP(CURRENT_TIMESTAMP()) + %i) WHERE id = %d", iDuration * 3600, tch->GetAID()); tch->GetDesc()->DelayedDisconnect(5); sys_log(0, "%s[%d] banned %s for %i hours with reason: %s", ch->GetName(), ch->GetPlayerID(), szName, iDuration, szReasonEscape); ch->ChatPacket(CHAT_TYPE_INFO, "%s has been banned for %i hours with reason: %s", szName, iDuration, szReasonEscape); } INFORMATIONS : std::auto_ptr msg(DBManager::instance().DirectQuery("INSERT INTO account.ban_ingame (id, name, begins, finish, reason) VALUES ('%d', '%s', NOW(), FROM_UNIXTIME(UNIX_TIMESTAMP(CURRENT_TIMESTAMP()) + %i), '%s')", tch->GetAID(), tch->GetName(), iDuration * 3600, szReason)); Va insérer les informations dans votre table "ban_ingame". Si vous changez le nom de table dans navicat, n'oubliez pas de changer le nom de la fonction ici aussi. DBManager::instance().DirectQuery("UPDATE account.account SET status = 'BLOCK' WHERE id = %d", tch->GetAID()); Va bannir le compte du joueur dans la table account.account , dans la colonne statut, celui-ci passera de "OK" à "BLOCK". Le compte banni se déconnecte automatiquement.
  13.  tutoriel

    Bonjour, J'ai décidé de refaire ce tutoriel Aujourd'hui je vais vous expliquer comment ajouté l'option "détruire" lorsque vous voulez jeter un item. Ca va être assez long, mais facile si vous suivez bien le tutoriel. Commençons ______________________________________________________________________ PRÉREQUIS ______________________________________________________________________ 1.Des source Serveur 2.Des sources Client 3.Un client ______________________________________________________________________ 1.SOURCE SERVEUR ______________________________________________________________________ Ouvrez votre "packet.h" et recherchez: HEADER_CG_ITEM_DROP2 = 20, Ajoutez ceci juste en dessous: HEADER_CG_ITEM_DESTROY = 21, Recherchez maintenant: typedef struct command_item_drop2 { BYTE header; TItemPos Cell; DWORD gold; BYTE count; } TPacketCGItemDrop2; Ajoutez ceci juste en dessous: typedef struct command_item_destroy { BYTE header; TItemPos Cell; } TPacketCGItemDestroy; Ouvrez maintenant le fichier packet_info.cpp et recherchez: Set(HEADER_CG_ITEM_DROP2, sizeof(TPacketCGItemDrop2), "ItemDrop2", true); Ajoutez ceci juste en dessous: Set(HEADER_CG_ITEM_DESTROY, sizeof(TPacketCGItemDestroy), "ItemDestroy", true); Ouvrez maintenant le fichier input_main.cpp et recherchez la fonction: void CInputMain::ItemDrop2(LPCHARACTER ch, const char * data) Ajoutez cette fonction juste après: void CInputMain::ItemDestroy(LPCHARACTER ch, const char * data) { struct command_item_destroy * pinfo = (struct command_item_destroy *) data; if (ch) ch->DestroyItem(pinfo->Cell); } Cherchez maintenant ceci: case HEADER_CG_ITEM_DROP2: if (!ch->IsObserverMode()) ItemDrop2(ch, c_pData); break; Et ajoutez: case HEADER_CG_ITEM_DESTROY: if (!ch->IsObserverMode()) ItemDestroy(ch, c_pData); break; Ouvrez maintenant le fichier char_item.cpp et recherchez la fonction: bool CHARACTER::DropItem(TItemPos Cell, BYTE bCount) Ajoutez cette fonction juste au-dessus: bool CHARACTER::DestroyItem(TItemPos Cell) { LPITEM item = NULL; if (!CanHandleItem()) { if (NULL != DragonSoul_RefineWindow_GetOpener()) ChatPacket(CHAT_TYPE_INFO, LC_TEXT("°*È*âÀ» ¿¬ »óÅ¿¡¼*´Â ¾ÆÀÌÅÛÀ» ¿Å±æ ¼ö ¾ø½À´Ï´Ù.")); return false; } if (IsDead()) return false; if (!IsValidItemPosition(Cell) || !(item = GetItem(Cell))) return false; if (item->IsExchanging()) return false; if (true == item->isLocked()) return false; if (quest::CQuestManager::instance().GetPCForce(GetPlayerID())->IsRunning() == true) return false; if (item->GetCount() <= 0) return false; SyncQuickslot(QUICKSLOT_TYPE_ITEM, Cell.cell, 255); ITEM_MANAGER::instance().RemoveItem(item); ChatPacket(CHAT_TYPE_INFO, LC_TEXT("L'item %s est desormais detruit."), item->GetName()); return true; } Ouvrez maintenant le fichier char.h et recherchez: bool DropItem(TItemPos Cell, BYTE bCount=0); Ajoutez ceci juste au-dessus: bool DestroyItem(TItemPos Cell); Ouvrez maintenant le fichier input.h et recherchez: void ItemDrop2(LPCHARACTER ch, const char * data); Ajoutez ceci juste en dessous: void ItemDestroy(LPCHARACTER ch, const char * data); ______________________________________________________________________ 2. SOURCE CLIENT ______________________________________________________________________ Ouvrez le fichier packet.h et recherchez: HEADER_CG_ITEM_DROP2 = 20, Ajoutez ceci juste en dessous: HEADER_CG_ITEM_DESTROY = 21, Recherchez maintenant: typedef struct command_item_drop2 { BYTE header; TItemPos pos; DWORD gold; BYTE count; } TPacketCGItemDrop2; Ajoutez ceci juste en dessous: typedef struct command_item_destroy { BYTE header; TItemPos pos; }TPacketCGItemDestroy; Ouvrez maintenant le fichier PythonNetworkStreamPhaseGameItem.cpp et recherchez la fonction: bool CPythonNetworkStream::SendItemDropPacketNew(TItemPos pos, DWORD elk, DWORD count) Ajoutez cette fonction juste en dessous: bool CPythonNetworkStream::SendItemDestroyPacket(TItemPos pos) { if (!__CanActMainInstance()) return true; TPacketCGItemDestroy itemDestroyPacket; itemDestroyPacket.header = HEADER_CG_ITEM_DESTROY; itemDestroyPacket.pos = pos; if (!Send(sizeof(itemDestroyPacket), &itemDestroyPacket)) { Tracen("SendItemDestroyPacket Error"); return false; } return SendSequence(); } Ouvrez maintenant le fichier PythonNetworkStreamModule.cpp et recherchez la fonction: PyObject* netSendItemDropPacket(PyObject* poSelf, PyObject* poArgs) Ajoutez cette fonction juste en dessous: PyObject* netSendItemDestroyPacket(PyObject* poSelf, PyObject* poArgs) { TItemPos Cell; if (!PyTuple_GetInteger(poArgs, 0, &Cell.cell)) return Py_BuildException(); CPythonNetworkStream& rkNetStream = CPythonNetworkStream::Instance(); rkNetStream.SendItemDestroyPacket(Cell); return Py_BuildNone(); } Recherchez maintenant: { "SendItemDropPacketNew", netSendItemDropPacketNew, METH_VARARGS }, Ajoutez ceci juste après: { "SendItemDestroyPacket", netSendItemDestroyPacket, METH_VARARGS }, Ouvrez maintenant le fichier PythonNetworkStream.h et recherchez la fonction: bool SendItemDropPacketNew(TItemPos pos, DWORD elk, DWORD count); Ajoutez ceci juste après: bool SendItemDestroyPacket(TItemPos pos); ______________________________________________________________________ 3. PYTHON CLIENT ______________________________________________________________________ Ouvrez le fichier uicommon.py du pack root et recherchez la class: class QuestionDialog(ui.ScriptWindow): Juste après celle class, ajoutez la class suivante: class QuestionDialogItem(ui.ScriptWindow): def __init__(self): ui.ScriptWindow.__init__(self) self.__CreateDialog() def __del__(self): ui.ScriptWindow.__del__(self) def __CreateDialog(self): pyScrLoader = ui.PythonScriptLoader() pyScrLoader.LoadScriptFile(self, "uiscript/questiondialogitem.py") self.board = self.GetChild("board") self.textLine = self.GetChild("message") self.acceptButton = self.GetChild("accept") self.destroyButton = self.GetChild("destroy") self.cancelButton = self.GetChild("cancel") def Open(self): self.SetCenterPosition() self.SetTop() self.Show() def Close(self): self.Hide() def SetWidth(self, width): height = self.GetHeight() self.SetSize(width, height) self.board.SetSize(width, height) self.SetCenterPosition() self.UpdateRect() def SAFE_SetAcceptEvent(self, event): self.acceptButton.SAFE_SetEvent(event) def SAFE_SetCancelEvent(self, event): self.cancelButton.SAFE_SetEvent(event) def SetAcceptEvent(self, event): self.acceptButton.SetEvent(event) def SetDestroyEvent(self, event): self.destroyButton.SetEvent(event) def SetCancelEvent(self, event): self.cancelButton.SetEvent(event) def SetText(self, text): self.textLine.SetText(text) def SetAcceptText(self, text): self.acceptButton.SetText(text) def SetCancelText(self, text): self.cancelButton.SetText(text) def OnPressEscapeKey(self): self.Close() return True Ouvrez maintenant le fichier game.py et cherchez la def suivante: def __DropItem(self, attachedType, attachedItemIndex, attachedItemSlotPos, attachedItemCount): Recherchez ce bout de code dans la fonction (2x): itemDropQuestionDialog = uiCommon.QuestionDialog() Remplacez par: itemDropQuestionDialog = uiCommon.QuestionDialogItem() Toujours dans la même fonction, recherchez: itemDropQuestionDialog.SetAcceptEvent(lambda arg=True: self.RequestDropItem(arg)) Ajoutez ceci juste après: itemDropQuestionDialog.SetDestroyEvent(lambda arg=True: self.RequestDestroyItem(arg)) Recherchez maintenant la fonction: def RequestDropItem(self, answer): Ajoutez cette fonction juste après: def RequestDestroyItem(self, answer): if not self.itemDropQuestionDialog: return if answer: dropType = self.itemDropQuestionDialog.dropType dropNumber = self.itemDropQuestionDialog.dropNumber if player.SLOT_TYPE_INVENTORY == dropType: if dropNumber == player.ITEM_MONEY: return else: self.__SendDestroyItemPacket(dropNumber) self.itemDropQuestionDialog.Close() self.itemDropQuestionDialog = None constInfo.SET_ITEM_DROP_QUESTION_DIALOG_STATUS(0) Recherchez ensuite la fonction: def __SendDropItemPacket(self, itemVNum, itemCount, itemInvenType = player.INVENTORY): Et ajoutez celle-ci juste après: def __SendDestroyItemPacket(self, itemVNum, itemInvenType = player.INVENTORY): if uiPrivateShopBuilder.IsBuildingPrivateShop(): chat.AppendChat(chat.CHAT_TYPE_INFO, localeInfo.DROP_ITEM_FAILURE_PRIVATE_SHOP) return net.SendItemDestroyPacket(itemVNum) Ouvrez maintenant le fichier locale_interface.txt et ajoutez ceci: DESTROY Destroy Placez le fichier questiondialogitem.py dans votre pack uiscript ! questiondialogitem.py Screen du système: Voilà c'est fini ! Source du totoriel : .Avenue™ de Metin2Dev Traduction: moi Cordialement, #Saw.
  14. Bonjour à tous pour mon premier tutoriel ! Je vais vous expliquer comment mettre ses propres bonus sur ces item a partir de la base de donnée, ne vous inquiétez pas, c'est très simple =) 1 PV max 2 PM max 3 VIT 4 INT 5 STR 6 DEX 7 Vitesse d'attaque 8 Vitesse de déplacement 9 Vitesse du sort 10 Régénération des HP 11 Régénération EP 12 Chance d'empoisoner 13 Chance de provoquer un étourdissement 14 Chance de ralentir 15 Chance de faire une attaque critique 16 Chance de coup percant 17 Bonus contre les demi-humains 18 Bonus contre les animaux 19 Bonus contre les Orcs 20 Bonus contre les mystiques 21 Bonus contre les Mort-vivants 22 Bonus contre le Mal 23 Dommages absorbés par les PV 24 Dommages absorbés par les PM 25 Chance de prendre des PM aux ennemis 26 Chance de récupérer des PM lorsque vous touchez 27 Chance de bloquer un coup au Corps-à-corps 28 Chance d'éviter les flèches 29 Défensse à l'épée 30 Défensse à l'épée à 2 mains 31 Défensse à la dague 32 Défensse au gong 33 Défensse à l'éventail 34 Résistance aux flèches 35 Résistance au feu 36 Résistance à la lumière 37 Résistance à la magie 38 Résistance au vent 39 Chance de détourner une attaque au corps à corps 40 Chance de détourner une malédiction 41 Résistance contre le poison 42 Chance de restaurer les PM 43 Chance d'obtenir un bonus EXP 44 Chance de drop le double de YANG 45 Chance de drop le double d'objet 46 Augmentation des effets de la potion 47 Chance de réstaurer les PV 48 Immunise contre l'étourdissement 49 Immunise contre le ralentissement 50 Immunise contre les chutes 52 Portée de l'Arc 53 Valeur d'attaque 54 Défense 55 Valeur d'attaque magique 56 Défense magique 58 Max endurance 59 Bonus contre les Guerriers 60 Bonus contre les Ninja 61 Bonus contre les Sura 62 Bonus contre les Shamans 63 Bonus contre les monstres 64 Valeur d'attaque (%) 65 Défense (%) 66 EXP (%) 67 Chance de drop d'objet 68 Chance de drop pour un Yang 69 PV max % et 70 PM max % Donner par PsychoNeko Merci. 71 Dégats de compétence 72 Dégats moyen 73 Résistance contre les dégats de compétence 74 Résistance contre les dégats moyen 76 Bonus CyberCafé EXP 77 iCafé Chance de trouver des yangs 78 Chance de parer une attaque Guerrier 79 Chance de parer une attaque Ninja 80 Chance de parer une attaque Sura 81 Chance de parer une attaque Shaman Id: C'est l'identifiant attribué par le serveur, cet identifiant est défini de 1 à un nombre très élevé ! Owner id: Si votre item est dans votre entrepôt, c'est l'ID de votre compte; s'il est dans votre inventaire c'est celui de votre personnage. Window: C'est la fenêtre dans laquelle l'item (armes ou autre ...) est placé ( Inventory => Inventaire ; Safebox => Magasinier ; Equipement => l'objet est équiper; Mall = entrepôt IS ) Pos: C'est la position à laquelle se trouve l'objet ou le numéro de la case si vous préférez. Count: Le nombre d'objets que vous avez s'ils sont empilables sinon ça reste 1. Vnum: C'est le code de votre item ( lorsque vous faites /i et votre code) Socket0 à 5: De socket0 à socket2, ce sont les pierres qui sont sur vos armes/armures. Les socket 3, 4 et 5 sont inutilisés. Les AttrType0 à 4: Ce sont les bonus 1/5. C'est ici que nous plaçons le code bonus cité en haut. Vous pouvez mettre ceux de votre choix ! Les AttrType5 à 6: Les 6/7 Comme au-dessus vous pouvez mettre ceux de votre choix ! AttrValue0 à 4: Les valeur désirée pour les bonus 1/5 vous pouvez mettre jusqu’à 32767 . AttrValue5 à 6: Comme au-dessus mais ce sont les bonus 6/7. Si vous voulez partager ce tutoriel merci de me demander l'accord avant.Si vous voulez d'autres tutoriels de ce genre ou même autre chose dites-le moi je tâcherai de le faire suivant mes connaissances source: History/Liberty
  15. Bonjour à tous ! Je vais vous expliquer comment fonctionnent les bases de données de RaiderZ, comment s'y repérer, et à quoi servent les différentes tables. Pour commencer, le moteur de la base de donnée est PostGreSQL. C'est un choix fait parce que les tables utilisent des types de données personnalisés, qui ont nécessité un moteur les supportant. Le jeu comporte trois bases de données, Account, Game et Log. Account La base de donnée Account contient les tables des comptes, des serveurs, et des mondes. Pour les tables: rz_account : Contient les informations sur les comptes des joueurs; rz_server: Contient les informations sur les serveurs du jeu; rz_server_status: Permet de récupérer les statuts des serveurs; rz_world: Contient les informations sur les mondes lancés. Game: rz_buff: Contient les informations sur les buffs rz_character: Contient les informations sur les personnages rz_cutscene: Contient les informations sur les cinématiques rz_emblem: ?? rz_equipment: Contient les informations sur les items équipés par les joueurs rz_faction: Contient les informations sur les relations entre les joueurs et les factions rz_gem_enchant: Contient les informations sur les enchantements de gemmes rz_guidebook: Contient les ID des items qui servent d'aide à la première connexion rz_inventory: Contient les données des inventaires des joueurs rz_palette: ?? rz_quest: Contient les informations sur les quêtes en cours rz_quest_history: Contient les informations sur les quêtes terminées rz_recipe: Contient les recettes de craft connues par les joueurs rz_storage: Contient les items du dépôt des joueurs rz_talent: Contient les informations sur les talents appris par les joueurs rz_talent_cooltime: Contient les informations sur les talents actuellement utilisés rz_user_item: Contient les informations sur les items en circulation dans le jeu Log: La BDD Log est vide pour le moment, elle doit contenir les informations sur les logs du jeu, pour les connexions, les items utilisés, les talents appris, etc... Je continue à chercher l'utilité des tables restantes, si vous le savez n'hésitez pas à le dire !
  16.  tutoriel

    Buff de groupe. 1) Qu'est ce c'est? 2) Les prérequis. 3) Le code. 1) Qu'est ce c'est? Le "buff de groupe" vous permet de "buffer" les personnes de votre groupes ( à partir d'une certaines distance seulement) en même temps que vous vous "buffez" vous-même. 2) Les prérequis. Sources serveur. Un client. Des files. Navicat. 3) Le code. Server/Game/Src/skill.h Cherchez: SKILL_FLAG_FIRE = (1 << 26), Ajoutez en-dessous ↓: SKILL_FLAG_PARTY = (1 << 27), Server/Game/Scr/char_skill.cpp Cherchez: SKILL_RESIST_PENETRATE Ajoutez en-dessous ↓: struct FPartyPIDCollector { std::vector <DWORD> vecPIDs; FPartyPIDCollector() { } void operator () (LPCHARACTER ch) { vecPIDs.push_back(ch->GetPlayerID()); } }; Screen de changement: Cherchez: if (IS_SET(pkSk->dwFlag, SKILL_FLAG_SELFONLY)) ComputeSkill(dwVnum, this); Ajoutez en-dessous ↓: else if (IS_SET(pkSk->dwFlag, SKILL_FLAG_PARTY) && !GetParty()) ComputeSkill(dwVnum, this); else if (IS_SET(pkSk->dwFlag, SKILL_FLAG_PARTY) && GetParty()) { FPartyPIDCollector f; GetParty()->ForEachOnMapMember(f, GetMapIndex()); for (std::vector <DWORD>::iterator it = f.vecPIDs.begin(); it != f.vecPIDs.end(); it++) { LPCHARACTER ch = CHARACTER_MANAGER::instance().FindByPID(*it); ComputeSkill(dwVnum, ch); } } Server/Game/Src/guild.cpp Cherchez: if ((pkSk->dwFlag & SKILL_FLAG_SELFONLY)) { // 이미 걸려 있으므로 사용하지 않음. if (ch->FindAffect(pkSk->dwVnum)) return; victim = ch; } Ajoutez en-dessous ↓: if ((pkSk->dwFlag & SKILL_FLAG_PARTY)) { if (ch->FindAffect(pkSk->dwVnum)) return; victim = ch; } Navicat Ouvrez Navicat et lancez cet query dans player/skill_proto : UPDATE `skill_proto` SET `setFlag`='PARTY' WHERE (`dwVnum`='94'); UPDATE `skill_proto` SET `setFlag`='PARTY' WHERE (`dwVnum`='95'); UPDATE `skill_proto` SET `setFlag`='PARTY' WHERE (`dwVnum`='96'); UPDATE `skill_proto` SET `setFlag`='REMOVE_BAD_AFFECT,PARTY' WHERE (`dwVnum`='109'); UPDATE `skill_proto` SET `setFlag`='PARTY' WHERE (`dwVnum`='110'); UPDATE `skill_proto` SET `setFlag`='PARTY' WHERE (`dwVnum`='111'); Si les querys ne fonctionnent pas: Ouvrez votre database player faites un design table sur skill_proto. cliquez sur : setFlag Vous verrez alors l'encadré "value" cliquez sur les "..." et ajoutez à la 27 ième position (donc la 28 ième ligne) : Merci Takuma pour cette remarque! 'PARTY' Correction (pour ceux ayant déjà installé le système) : Cherchez : if (IS_SET(pkSk->dwFlag, SKILL_FLAG_PARTY) && !GetParty()) pkVictim = this; Remplacez par -> if (IS_SET(pkSk->dwFlag, SKILL_FLAG_SELFONLY)) pkVictim = this; Faites le 2 fois car vous l'avez remplacé 2 fois. Sources: Metin2Dev. Traduction/Tutoriel/Correction: Metin2Dev et moi. Cordialement, History.
  17. Bonjour à tous ! Petit tutoriel assez court pour vous apprendre à utiliser le DevKit de RaiderZ ! Pour commencer, le matériel nécessaire: Visual Studio 2017 Community Edition ou plus Commencez par décompresser les sources dans un dossier: Le dossier "Develop" doit être dans le même dossier que "SDK". Ensuite, pour ouvrir les sources, ouvrez la solution All.sln Vous devriez voir l'ensemble des projets: Les sources sont séparées en grandes catégories: Client contient les projets relatifs au lanceur du jeu; Common contient les projets communs au client et au serveur; mdk contient les sources du moteur de jeu; Serveur contient les sources des serveurs. Pour compiler, vous devez sélectionner une des configurations suivantes dans le menu des configurations: Client: RELEASE_CLIENT Serveur: RELEASE_SERVER Une fois la bonne configuration de solution sélectionnée, vous n'aurez plus qu'à lancer la compilation ! Les fichiers sortiront dans le dossier Develop/Out/Nom_Du_Projet/Nom_De_La_Configuration. Voilà pour les explications basiques ! Si vous rencontrez un problème sur l'utilisation des sources, passez en A/Q/S !
  18. Salut. Ce bouton, vous a jamais soûlé car il marche pas ? : Moi si. Je partage donc vite fait le debug que je fais je ne sais quand. Dans votre intrologin.py: Dans : class LoginWindow(ui.ScriptWindow): Trouvez : def __init__(self, stream): Ajoutez dans la fonction : self.HakConnexionStop = False Allez dans la fonction Close qui se trouve un petit peu plus bas et ajoutez : self.HakConnexionStop = None Cherchez ensuite : def OnConnectFailure(self): Vous aurez dans la fonction : if self.isNowCountDown: return Ajoutez en dessous : if self.HakConnexionStop: self.HakConnexionStop = False return Cherchez : def Connect(self, id, pwd): Vous trouverez : self.stream.popupWindow.Open(localeInfo.LOGIN_CONNETING, localeInfo.UI_CANCEL) Changez cette ligne en : self.stream.popupWindow.Open(localeInfo.LOGIN_CONNETING, self.HakOnConnexionSTOP, localeInfo.UI_CANCEL) Après la fonction, ajoutez : def HakOnConnexionSTOP(self): self.HakConnexionStop = True self.stream.popupWindow.Close() Vous n'avez plus qu'à repack et le bouton marchera. Quand on clique sur le bouton, je n'efface pas les inputs comme pouvait le faire l'erreur de connexion etc. Si c'est nécessaire je peux vous dire comment faire. Code original = Frozen, forum étrangé.
  19. Bonjour, Voici un script pour voir le statut de son serveur <?php $connect = TRUE; // Autoriser ou non la connexion $port = '8085'; // Port du serveur $ip = 'localhost'; // adresse (ip) du serveur // Mise en place du texte du nom de serveur echo 'Votre nom de serveur'; // Verification du statut if (! $sock = @fsockopen($ip, $port, $num, $error, 5)) // Si il est hors ligne echo ' : OFF'; else{ // Si il est en ligne echo ' : ON'; fclose($sock); } ?> Voili voilou Cordialement, Deathart
  20.  tutoriel

    Bonsoir ! Après une semaine à essayer de compiler le client de metin2, je vous fait un petit tutoriel. Tout d'abord, il vous faut les sources de metin: Contenu Masqué Pour compiler le client, je vous conseille la branche novaline mais vous pouvez très bien utiliser la branche mainline. Ensuite, vous conseille Visual Studio 2013, car le 2008 est foireux et vous risquez d'avoir des erreurs dans votre compilation (je parle en connaissance de cause ) Vous pouvez le télécharger ici: Contenu Masqué Etape 1, préparez son environnement Une fois les sources et le logiciel téléchargé, ouvrez le. Cliquez sur l'icône ouvrir un fichier et choisissez ce sln: Metin2Client_VC90.sln Il se trouve dans Srcs\Client ----------------------------------------------------------------------- Maintenant, créez un dossier lib et include où vous voulez. Vous pouvez le faire à la racine de votre disque dur, sur le bureau, peu importe. On va dans chaque dossier, mettre les fichiers nécessaire au code source. DOSSIER LIB Srcs\Tools\WorldEditor\extern\lib Mettez le contenu de ce dossier, dans le dossier lib que vous avez créé. Srcs\Tools\RAD Game Tools\granny\common\granny_common_2_9_12_0_release.zip Ouvrez ce .zip et rendez-vous dans lib/win32. Vous placez le contenu du dossier win32 dans le dossier lib que vous avez créé. Srcs\Extern\lib Mettez le contenu de ce dossier, dans le dossier lib que vous avez créé. DOSSIER INCLUDE Srcs\Tools\WorldEditor\extern\include Mettez le contenu de ce dossier, dans le dossier include que vous avez créé. Srcs\Extern\include Mettez le contenu de ce dossier, dans le dossier include que vous avez créé. !/ N'hésitez surtout pas à remplacer /! ------------------------------------------------------------------------ Avant de lier vos dossier à votre code, je vous conseille de changer le mode de compilation. Personnellement, j'utilise le mod Release qui me permet d'avoir un lanceur d’environ 3200Ko Pour changer le mod de compilation, faites un clic droit sur votre solution (votre .sln) dans visual studio puis cliquez sur propriété. Allez dans Propriétés de configuration ---> Configuration. Cliquez sur Gestionnaire de Configuration et dans configuration de la solution active, vous choisissez Release. Vos projets vont se mettre en Release, il vous reste juste à fermer la fenêtre et cliquer sur OK. ------------------------------------------------------------------------ Maintenant, il faut lier chaque projet de votre solution à vos dossiers. Faites un clic droit sur UserInterface puis cliquez sur Propriété. Allez dans Configuration ---> Vc++ et dans répertoire include, vous choisissez votre dossier include, comme sur le screen. Dans Répertoires de bibliothèques, vous choisissez votre dossier lib. Vous faites exactement la même chose pour les 15 autres projets (EterPack, EterLib, etc ...) ------------------------------------------------------------------------ Maintenant il vous suffit juste d'appuyer sur ces touches pour compiler (en même temps): CTRL + SHIFT + B Pour recompiler le client, faites un clic droit sur la solution puis: Régénérer la solution Malheureusement, la compilation est assez longue. Le .exe final se trouvera dans le dossier /UserInterface/Release Les erreurs fréquentes Impossible d'ouvrir mon sln Votre sln n'utilise juste pas le bonne version. Vous pouvez résoudre ce problème tout simplement. Éditez avec NotePad votre SLN et à la place de: Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio 2013 Mettez(si vous utilisez Visual 2008): Microsoft Visual Studio Solution File, Format Version 9.00 # Visual Studio 2008 Ca varie en fonction de la version de votre IDE, pensez à bien le retenir 16>.AffectFlagContainer.cpp(68 ) : error C2440: 'initializing' : cannot convert from 'CAffectFlagContainer::Element' to 'char &' 16>.\AffectFlagContainer.cpp(68 ) : error C2440: 'initializing' : cannot convert from 'CAffectFlagContainer::Element' to 'char &' Cliquez deux fois sur CAffectFlagContainer et cela va vous amener à un BYTE. Faites clic droit sur BYTE et cliquez sur Atteindre la définition Il suffit juste de remplacer char par unsigned char. 6>.PythonGraphic.cpp(294) : error C2664: 'CPythonGraphic::SaveJPEG' : cannot convert parameter 2 from 'char *' to 'LPBYTE' 6>.\PythonGraphic.cpp(294) : error C2664: 'CPythonGraphic::SaveJPEG' : cannot convert parameter 2 from 'char *' to 'LPBYTE' Double cliquez sur l'erreur et ça va vous amener sur la ligne de l'erreur. Effacez cette ligne et mettez: bool bSaved = SaveJPEG(c_pszFileName, (LPBYTE)pbyBuffer, uWidth, uHeight); Erreur Python Il arrive parfois, si vous utilisez Python-2.2, vous ayez une erreur. La même chose pour Python2.7. Trouvez donc dans un premier temps, l'erreur qui concerne cette ligne: PyAPI_FUNC(struct symtable *) PySymtable_Build(mod_ty, const char *, PyFutureFeatures *); Et commentez la. (Pour commenter: //commentaire) module' object has no attribute 'EQUIPMENT_RING1 Erreur Sysser 'module' object has no attribute 'EQUIPMENT_RING1' Dans le fichier: Locale_inc.h. Vous supprimez tout et vous mettez: #define LOCALE_SERVICE_SINGAPORE #define ENABLE_COSTUME_SYSTEM #define ENABLE_ENERGY_SYSTEM #define ENABLE_DRAGON_SOUL_SYSTEM #define ENABLE_NEW_EQUIPMENT_SYSTEM 16>cryptlib-5.6.1MTd.lib(iterhash.obj) : error LNK2001: unresolved external symbol \"void __cdecl std::_Xbad_alloc(void)" ([email protected]@@YAXXZ) 16>cryptlib-5.6.1MTd.lib(iterhash.obj) : error LNK2001: unresolved external symbol "void __cdecl std::_Xbad_alloc(void)" ([email protected]@@YAXXZ) Si vous avez des erreurs avec la lib Cryptlib-5.6.1MT, il faut recompiler cette lib. Pour se faire, avec Visual Studio, ouvrez le fichier cryptlib.2008.sln qui se trouve dans le dossier: Srcs\Extern\cryptopp Vous importez la solution, vous compilez avec CTRL + SHIFT + B et une fois terminé, la lib se trouvera dans Srcs\Extern\lib. Sous le nom de: cryptlib-5.6.1MTd.lib Attention ! Il faut compiler la lib en MT et sur le même toolset que le client ! Dans tous les cas, vous pouvez très simplement trouver cette lib avec la date du fichier. 5>mss32.lib(mss32.dll) : error LNK2026: module unsafe pour l'image SAFESEH. 5>mss32.lib(mss32.dll) : error LNK2026: module unsafe pour l'image SAFESEH. Il suffit de désactiver / SAFESEH. Pour cela, clique droit sur UserInterface, propriétés puis cliquez sur éditeur de liens, allez dans avancé puis sur la ligne Image avec gestionnaire d'exceptions sécurisés, vous mettez non. Vous régénérez la solution, et le tour est joué TraceBack - Error: Bad magic number in lib libtraceback.pyc L'erreur dans la fenêtre LOG: TraceBack - Error: Bad magic number in lib lib\traceback.pyc Le soucis vient du fait que votre lanceur est dans une certaine version de python alors que vos libs (Dans client, le dossier lib) utilisent une autre version de Python. En fait, on peut appeler le magic number, une clé pong mais avec un contexte bien différent. Vos libs, quand vous les éditez en HEXA, contiennent le nombre magique correspondant à votre python.dll python27.dll a son propre magic number tout comme python22. C'est une sorte de laison. Pour résoudre ce problème, vous avez deux choix: Passez vos libs client à la version python de votre lanceur ou alors compiler votre lanceur avec la version Python correspondant à votre client. TraceBack Hé oui, vous pouvez aussi avoir une fenêtre d'erreur mais juste avec l'erreur TraceBack. Cela signifie qu'un de votre dossier pack n'est pas apte à prendre en compte ce lanceur compilé (je simplifie, sinon j'écris un topic sur cette erreur). En gros c'est que vos fichiers n'utilisent pas les bonnes fonctions. Dans ce cas, je vous conseille de télécharger ce client: Contenu Masqué Vous essayez de prendre les fichiers root et voir si votre client se lance sans cette erreur. (vous faites vos modif après) Si root ne fonctionne pas, essayé locale_* ou alors essayez uiscript. Il y en a bien un qui va résoudre votre soucis. La plus part du temps, c'est root. LoginWindow.__LoadScript.LoadObject - :Python int too large to convert to C long À l'entête du fichier: PythonUtils.cpp, ajoutez: #define PyLong_AsLong PyLong_AsLongLong #define PyLong_AsUnsignedLong PyLong_AsUnsignedLongLong Vous pouvez apprendre à passer de Python 2.2 à Python 2.7 grâce à ce tutoriel : Contenu Masqué Source: Moi et en m'aidant de m2d pour la correction de certaines erreurs
  21.  tutoriel

    Bonjour ! Je vous apprends à cacher vos MPs comme vous cachez vos quêtes mais cette fois-ci avec Ctrl + P par exemple. Dans votre game.py (pack root) : Cherchez : onPressKeyDict[app.DIK_Q] Ajoutez en dessous : onPressKeyDict[app.DIK_P] = self.__PressMPKey Cherchez ensuite : app.RotateCamera(app.CAMERA_TO_NEGATIVE) Ajoutez en dessous : [TAB]if __PressPKey(self): [TAB][TAB]if app.IsPressed(app.DIK_LCONTROL) or app.IsPressed(app.DIK_RCONTROL): [TAB][TAB][TAB]if 0==interfaceModule.IsWhisperHide: [TAB][TAB][TAB][TAB]interfaceModule.IsWhisperHide = 1 [TAB][TAB][TAB][TAB]self.interface.HideAllWhisperButton() [TAB][TAB][TAB]else: [TAB][TAB][TAB][TAB]interfaceModule.IsWhisperHide= 0 [TAB][TAB][TAB][TAB]self.interface.ShowAllWhisperButton() Remplacez les [TAB] par des tabulations grâce à votre éditeur de texte fonction remplacer. Dans votre interfacemodule.py cherchez : IsQBHide = 0 Ajoutez en dessous : IsWhisperHide = 0 Vous pouvez maintenant repack votre client. Appuyez sur CTRL + P avec des mps et la magie opère. Cordialement. Source : Mt2dev
  22. Bonjour, Tutoriel assez court pour vous apprendre à faire ceci : Dépackez votre root et ouvrez votre uitarget.py. Cherchez : def __init__(self): Vous aurez : hpGauge = ui.Gauge() hpGauge.SetParent(self) hpGauge.MakeGauge(130, "red") hpGauge.Hide() Ajoutez : hpPercenttxt = ui.TextLine() hpPercenttxt.SetParent(self) hpPercenttxt.SetPosition(160, 13) hpPercenttxt.SetText("") hpPercenttxt.Hide() Ce qui donne : def __init__(self): ui.ThinBoard.__init__(self) name = ui.TextLine() name.SetParent(self) name.SetDefaultFontName() name.SetOutline() name.Show() hpGauge = ui.Gauge() hpGauge.SetParent(self) hpGauge.MakeGauge(130, "red") hpGauge.Hide() hpPercenttxt = ui.TextLine() hpPercenttxt.SetParent(self) hpPercenttxt.SetPosition(160, 13) hpPercenttxt.SetText("") hpPercenttxt.Hide() closeButton = ui.Button() closeButton.SetParent(self) closeButton.SetUpVisual("d:/ymir work/ui/public/close_button_01.sub") closeButton.SetOverVisual("d:/ymir work/ui/public/close_button_02.sub") closeButton.SetDownVisual("d:/ymir work/ui/public/close_button_03.sub") closeButton.SetPosition(30, 13) Cherchez : self.name = name self.hpGauge = hpGauge Ajoutez en dessous : self.hpPercenttxt = hpPercenttxt Cherchez : def Destroy(self): Ajoutez : self.hpPercenttxt = None Cherchez : def ResetTargetBoard(self): Vous aurez : self.hpGauge.Hide() Ajoutez : self.hpPercenttxt.Hide() Cherchez : def SetHP(self, hpPercentage): Vous aurez : self.UpdatePosition() Ajoutez : self.hpPercenttxt.SetPosition(200 + 7*self.nameLength-205, 13) self.hpPercenttxt.Show() Enfin, cherchez : self.hpGauge.SetPercentage(hpPercentage, 100) Et puis ajoutez : self.hpPercenttxt.SetText("%d%%" % (hpPercentage)) Vous pouvez repacker, c'est fini ! Source : Mt2dev Cordialement, Takuma.
  23. Yop ! Autre partie, avec celle-ci vous allez pouvoir annuler presque tous les cheats pour les débutants (m2bob, lalaker etc....). Quel est le principe ? J'ai précisé "débutant" car on peut contourner cette méthode, mais peut évident pour les débutants. Notre client va scan en permanence les processus démarrés, et si il y en a un qui correspond à notre BlackList PAF ! :leloup: Pour ça, on va reprendre notre ancien fichier, aller tout à la fin, et on code ! Let's go ! D'abord, vous allez devoirs ajouter ces imports : import thread import time Si vous avez modifié le fichier, vérifiez que vous ne les avez pas déjà. Si vous commencez sur un nouveau fichier ajoutez aussi : import os On va créer une fonction qu'on va nommer processf : import osimport thread import timedef processf(): Bien, on va maintenant mettre tout dans une structure try pour éviter les erreurs : import os import thread import time def processf(): try: On crée aussi l'except: si ça échoue, et on dit au programme d'attendre, de "dormir" pendant 5 secondes : import os import thread import time def processf(): try: pass except: time.sleep(5) # attendre 5 secondes Bien, maintenant on va faire ce qu'on appelle une boucle infinie, avec un while. On va lui dire, "Le temps que 1 est égale à 1 tu fais ça !" Et comme vous vous doutez, un sera toujours égale à un, on appelle ça une boucle infinie. import os import thread import time def processf(): try: while 1 == 1: except: time.sleep(5) Tout ce qui va se trouver après le while et avec une tabulation en plus va se répéter à l'infini ! Bien, créons maintenant notre liste de processus,notre BlackList, vous allez vous même la remplir, je vais mettre deux trucs simples : import os import thread import time def processf(): try: while 1 == 1: Black_List = ["skype.exe", "chrome.exe"] except: time.sleep(5) Ici bien sûr les processus sont bidons ! Je ne connais pas les processus de Mt2bob etc, à voue de les chercher avec votre gestionnaire des tâches. Bien, maintenant, pour chaque membre, nous allons ouvrir notre liste de processus. Nous allons ensuite la mettre entièrement en minuscule. Cela vous donne l'avantage de ne pas être gêner avec la casse : import osimport thread import time def processf(): try: while 1 == 1: Black_List = ["inyector.exe","cheatengine-x86_64.exe","ollydbg.exe","skype.exe"] for p in os.popen("tasklist"): process = p.lower() except: time.sleep(5) Bien, maintenant nous allons devoir vérifier si pour chaque membre de notre Black_List on peut trouver son nom dans notre process qui contient la liste de tous les processus démarrés. for Member in Black_List: Ensuite, si il trouve le Member dans process : if process.find(Member) != -1: On isole le nom du processus : p = process.split() Puis on le ferme : os.popen("taskkill /im %s /f" % p[0]) Puis, on lui dit d'attendre 5 secondes avant de recommencer : time.sleep(5) Bien ! ça donne : import os import thread import time def processf(): try: while 1 == 1: Black_List = ["inyector.exe","cheatengine-x86_64.exe","ollydbg.exe","skype.exe"] for p in os.popen("tasklist"): process = p.lower() for Member in Black_List: if process.find(Member) != -1: p = process.split() os.popen("taskkill /im %s /f" % p[0]) time.sleep(5) except: time.sleep(5) Il ne nous reste plus car relancer la fonction en cas d'erreur, ajoutez alors: import os import thread import time def processf(): try: while 1 == 1: Black_List = ["inyector.exe","cheatengine-x86_64.exe","ollydbg.exe","skype.exe"] for p in os.popen("tasklist"): process = p.lower() for Member in Black_List: if process.find(Member) != -1: p = process.split() os.popen("taskkill /im %s /f" % p[0]) time.sleep(5) except:time.sleep(5)thread.start_new_thread(processf, ()) On utilisez ici les threads. C'est pour le lancer en parallèle par abus de langage on va dire. Voilà ! N'oubliez pas de l'import si ce n'est pas déjà fait. Je vous laisse le soin de remplir la liste de processus ! Bonne chance !
  24. Bonjour, Aujourd'hui, je vais vous apprendre à créer votre rétro habbo avec PlusEmu, phpmyadmin et XAMP au passage Pour cela, vous allez avoir besoin de : XAMPP, pour pouvoir gérer la partit web et la base de donnés Lien : Contenu Masqué Le CMS: Contenu Masqué C'est le site WEB (le CMS) que l'on va utiliser pour le serveur Habbo. Il nous permettra d'accéder à l'hôtel ainsi que de gérer la maintenance du serveur (Membres, appartements, etc ...) Les fichiers Habbo (Emulateur + Base de données + SWF): Contenu Masqué Vous aurez aussi besoin de l'émulateur avec sa base de donnée ! Lien : Contenu Masqué Une fois ceci, fait, nous pouvons commencer. Rendez vous dans le dossier web de XAMPP (htdocs) déplacez-y tous les fichiers contenue dans l'archive du site : Démarrez ensuite XAMPP, installer Apache et Mysql en appuyant, puis démarrez les (appuyez sur les boutons en rouge pour les installer et en bleu pour les démarrer), une fois fait, vous devriez avoir ceci : Il se peut que Apache utilise le même port que skype, pour ça changez le dans les paramètres de WAMP. Rendez maintenant sur phpmyadmin : Contenu Masqué Appuyez sur "nouvelle base de données" : Nommez là : funkyEmu , puis appuyez sur "créer" : Ensuite, faites "importer" : Faites "parcourir", puis sélectionner le habbo.sql se trouvant dans l'archive de l'émulateur : Si le fichier ne veut pas s'upload, c'est qu'il est trop gros pour phpmyadmin, allez dans xampp\php\php.ini et modifier cette ligne : Ensuite, uploadez votre habbo.sql, cela peut prendre un peu de temps. Une fois fait, vous devriez avoir quelques tables d'ajouté(et d'autres, mais bon tout ne tient pas dans un screen) : Allez ensuite dans votre dossier : htdocs\includes\settings.inc.php Verfiez que les informations sont bien remplis comme ça, ou changez le nom de la base de données (les // sont là pour vous aidez) si vous n'avez pas mis "FunkyEMU". Enregistrez, puis rendez-vous sur : Contenu Masqué Sélectionnez ensuite "Butterfly", puis faites "Valider l'installation" Une fois fait, cliquez sur "Supprimer fichier d'installation". Vous allez ensuite vous créez un compte sur votre site. Allez ensuite ici : Contenu Masqué Allez dans votre base de donnée, puis la table "users" : Changez ensuite le "1" de "rank" en "10" Rendez vous ensuite sur votre site, relancez la page, et un bouton est apparue : Cliquez dessus, puis allez dans "configuration" Vous aurez ceci : Configurez les comme ceci : Variables : Contenu Masquégame/gamedata/external_variables.txt Texts : Contenu Masquégame/gamedata/external_flash_texts.txt Furnidata : Contenu Masquégame/gamedata/furnidata.xml Productdata : Contenu Masquégame/gamedata/productdata.txt Banner : Ne pas y toucher Base : Contenu Masquégame/gordon/PRODUCTION-201601012205-226667486/ Habbo SWF : Contenu Masquégame/gordon/PRODUCTION-201601012205-226667486/Habbo.swf Ce qui donne : Ensuite, il va falloir faire un petit changement dans votre panneau de configuration, allez dans : Faites parametres suplémentaire, puis : Changez le symbole décimal par un "." Faites "Appliquer", et le tour est joué ! Ensuite, aller dans l'émulateur : Emulateur PlusEmu\bin\Debug\config.ini Vous metterez ceci : Code : ## MySQL Configuration db.hostname=127.0.0.1 db.port=3306 db.username=root db.password= db.name=FunkyEmu Changer le si vous n'avez pas mis "FunkyEmu" en nom de base de données. Enregistrez, puis lancer l'émulateur grâce à : Plus Emulator.exe qui se trouve dans le meme dossier que le config.ini Vous aurez normalement ceci : Vous pouvez donc vous connectez sur votre rétro habbo ! Pour être staff, il vous suffit de vous mettre au "rank" 8 de la même façons que vous vous êtes mis "rank" 10 toute à l'heure. je sais que le tutoriel à déjà été fait mais apparemment Navicat créer des erreurs, je ne sais pas je n'ai pas essayez mais au moins, voici de quoi rendre tout le monde heureux. Bon jeu ! Cordialement.
  25. Hello Funky Emu ! Pour la sortie tant attendue de la V6 de notre beau forum, j'avais bien envie de reprendre un peu de service en vous partageant quelques notions et nouveaux concepts d'un univers que j'apprécie tout particulièrement depuis 2 ans : le Web. Plus précisément, je vais essayer de vous dresser le tableau d'une toile qui est sujette à de fortes mutations depuis quelques années, tant au niveau de son utilisation par ses grands acteurs que pour ses fondements techniques, très différents de la norme d'il y a dix ans. Ayant démarré le développement informatique sur ce même forum il y a maintenant 6 ans sur du web en PHP (pour des CMS Dofus), vous retracer mon exploration de ce domaine et ses technologies me fait vraiment plaisir, et j'espère que ça vous intéressera tout autant que moi. Bien que maintenant assez éloigné de l'émulation, il m'a semblé que quelque soit le jeux émulé, son écosystème (CMS, émulateurs, autres outils) n'évoluait que très peu au cours des années et qu'il faisait souvent appel aux mêmes architectures, librairies et autres composants : bref, que l'émulation ne bénéficie pas toujours des progrès de l'aire du numérique. C'est donc dans cette idée que je tente d'amener un petit vent frais sur la section programmation, en espérant que certains soient suffisamment curieux pour me faire des retours et me dire si approfondir le sujet plus loin les intéresserait, ou même mettre en application eux-même ces outils à travers des projets persos. Sommaire Petit historique NodeJS, du Javascript côté serveur NPM Les Single Page Application ReactJS Angular Le packaging Le Backend Les SPA isomorphiques Conclusion 1/ Petit historique Commençons par rappeler rapidement la traditionnelle stack (pile de technologies) LAMP encore très utilisée aujourd'hui pour des sites webs : Linux + Apache + Mysql + PHP. Ce n'est bien sûr pas la seule solution qu'il y avait avant les nouvelles technologies, nous pouvons par exemple citer Ruby on Rails ou J2EE qui ont aussi eu leur lot de popularité, mais LAMP reste certainement l'architecture la plus connue et répandue. Dans ce schéma, le navigateur de l'utilisateur (client) envoie une requête HTTP au serveur Web Apache (serveur), Apache exécute le fichier php demandé qui lui renvoie la page HTML après avoir requêté si besoin MySQL. Il ne reste donc à Apache plus qu'à renvoyer cette page au navigateur alors prêt à l'afficher, et exécuter le code javascript y étant contenu. A noter que chaque changement de page entraîne alors une nouvelle requête consommant des ressources serveur pour un traitement souvent en bonne partie identique à celui de la requête précédente. Jusqu'à il y a quelques années, le javascript exécuté chez notre client était majoritairement utilisé pour des animations esthétiques ou pour dynamiser certaines pages via des requêtes AJAX. Entre les mauvaises performances et incompatibilités des différents moteurs Javascript (= composant d'un navigateur chargé de l'exécution du javascript), ce langage a longtemps été moqué et c'est pourquoi il n'était alors utilisé que pour des tâches visuelles ou peu critiques. V8, la résurrection du Javascript V8, c'est un moteur Javascript open-source développé par Google pour son navigateur Chrome, la première release datant de 2008. Ce qu'il a de particulier, ce sont ses performances : utilisant une compilation JIT (Just In Time: a la volée) du Javascript en bytecode (langage machine), V8 s'est avéré être assez performant pour, au cours de ces dernières années, remettre complètement en question la place du Javascript. C'est aussi une course à la performance dans laquelle se sont ainsi lancés tous les géants du web : Mozilla sur son moteur Javascript SpiderMonkey (le tout premier moteur javascript initialement développé par/pour Netscape), Apple sur son JavascriptCore (Safari), Microsoft sur Chakra (Edge/IE), ... L'enjeux de cette course, c'est ni plus ni moins que de savoir qui pourra le plus bénéficier des nouvelles possibilités ouvertes par ce langage propulsé à une échelle bien supérieure à tout ce qu'il a pu connaître. Mais Jamie, à quoi ressemble donc ce nouveau champs des possibles !? 2/ NodeJS, du Javascript côté serveur Pour commencer, non, NodeJS n'est bien sûr pas la première solution utilisant du JS côté serveur. L'expérience avait bien déjà été réalisée par Netscape avec la sortie de Rhino en 1997, un autre moteur Javascript prévu pour du server-side. Mais si l'utilisation de NodeJS a tout simplement explosé quelques années seulement après sa sortie en 2009, c'est sans aucun doute parce que son auteur Ryan Dahl a su les réunir les bons éléments au bon moment : Un moteur javascript assez performant pour rendre le JS côté serveur viable L'implémentation d'une gestion de modules au format CommonJS (Nativement et avant le standard ES2015, le Javascript ne permet pas d'inclure d'autres fichiers) Des modules permettant l'interaction avec des composants de base comme le système d'exploitation, le système de fichier, les sockets Un gestionnaire de module (npm) permettant l'organisation d'une communauté open source publiant des modules répondant à tout type de besoin L'asynchrone du Javascript utilisé avec un modèle concurrentiel (synchronisation des tâches asynchrones) fiable et efficace. Lorsqu'il reçoit une requête, un serveur Java standard crée un nouveau thread pour la gérer : ce modèle peut causer des problèmes d'accès concurrentiel à la mémoire, de nombreux threads bloqués en attente du retour d'une opération, un trop grand nombre de threads pour les capacités de la machine ... A son inverse, NodeJS fonctionne avec un unique thread disposant d'une queue listant toutes les tâches devant être exécutées. Lorsque vient le tour du traitement d'une nouvelle requête, les instructions sont donc exécutées linéairement jusqu'au prochain appel asynchrone dont le traitement du retour sera ajouté à la file d'attente. Mais tant qu'une instruction asynchrone n'est pas terminée, Node continue de traiter les autres requêtes sans être bloqué. De cette façon, la productivité de votre thread est maximisée, à l'opposé d'un langage comme le Java qui encapsulera ses appels bloquants dans de multiples threads régulièrement rendus 'inactifs'. Bien sûr, cela ne veut pas dire qu'un environnement node sera toujours plus performant que du Java, loin de là, mais c'est sûrement vrai pour de nombreux services webs ne nécessitant pas de lourds calculs CPU. Si vous êtes intéressé par le sujet, cet article décrit le fonctionnement de Node un peu plus en détail et dans quelles situations il prend tout son intérêt. Ce sont je pense les éléments clés de cette plateforme de développement JS. Pour les curieux, voilà les quelques lignes suffisant à ouvrir un serveur en écoute avec NodeJS (extrait de la doc nodejs) : Comme vous pouvez le voir, ce serveur est minimaliste. A vous d'indiquer le code retour de chaque réponse (200 pour un succès), de renvoyer votre contenu avec une autre méthode (end), et même le dispatch vers la bonne unité d'exécution en fonction de l'URL devra être fait en regardant celle-ci vous même via le paramètre req. Certains seront ennuyés par tout ce superflu, d'autres apprécieront le grand contrôle qu'ils ont sur le cycle de vie d'une requête. Quoi qu'il en soit, n'oubliez pas que de nombreux modules vous prémâchant le travail existent à tous les niveaux. C'est ce que fait très bien ExpressJS pour la partie HTTP : Je pourrai vous parler des nombreux autres modules et middlewares venant se plugger à express (comme pour de la gestion de session), mais ce n'est pas l'objectif de ce sujet. Retenez qu'une solution web à base de node + express serait très bien pour écrire une API (afin d'interfacer votre utilisation d'une base de donnée ou tout autre service), ou simplement servir des fichiers, mais que pour un site web classique (comme un CMS), Node sera largement insuffisant. On pourrait bien sûr entièrement réaliser un site avec uniquement NodeJS, mais on se retrouverait à générer puis renvoyer bêtement de l'HTML au navigateur en perdant tout l'intérêt du langage par rapport à du PHP . Nous allons rapidement parler du package manager de Node sur la prochaine partie, juste avant d'entamer la partie la plus intéressante, au sujet de ces technologies JS révolutionnaires qui déferlent sur notre web. 3/ NPM Comme mentionné au début de l'introduction de Node, celui-ci dispose de son propre package manager : Node Package Manager. Il a aussi son propre site, sur lequel vous pouvez chercher et souvent facilement trouver réponse à votre besoin. Une telle communauté s'est aggloméré autour de npm que son utilisation s'est étendue à tout type de projet JS, utilisant ou non Node. Par exemple, si vous devez faire une application entièrement front ne nécessitant même pas l'utilisation de node, vous commencerez quand même sûrement par créer un projet npm, télécharger les modules de vos technos, puis de setup votre bundler (c'est la 7ème partie) de sorte à pouvoir produire une solution finale contenant chacune des dépendances installées par npm. Et pour le bonheur de tous, l'utilisation de npm est des plus simples : npm init : cette commande permet de créer un nouveau projet npm en initialisant tout simplement le fichier package.json, la configuration de npm pour ce projet npm install : lorsque vous téléchargez / clonez un projet npm (qu'on repère donc à la présence d'un package.json), cette commande téléchargera chacune des dépendances listées dans le package.json npm install --save package_name : Si vous ajoutez un nom devant npm install, cela installera simplement le package en question. L'option --save permet d'ajouter aussi la nouvelle dépendance au fichier package.json, afin qu'il soit installé lors d'un simple npm install. npm uninstall --save package_name : vous l'aurez compris, c'est l'opération inverse de celle-ci dessus. --save retire donc la ligne du package.json. npm run script_name : au début de votre package.json, vous pouvez voir un objet scripts. Il contient toutes les commandes shell que vous pouvez exécuter avec cette commande, en lui passant en paramètre le nom du script désiré. Très utile pour minifier l'appel de longues commandes que vous devez régulièrement utiliser. Nous verrons un cas d'utilisation très pratique partie 7. npm start : si votre objet scripts contient bien une entrée 'start', exécute celle-ci. C'est une forme raccourcie de npm run start qui ne fonctionne que pour start, une commande réservée au lancement de votre application. C'est donc généralement la deuxième et dernière commande à exécuter après avoir téléchargé une application npm simple, juste après le npm install. Bon, on parle beaucoup d'application js, mais quand est-ce qu'on y vient vraiment ?! Et bien, l'heure est venue ... 4/ Les Single Page Application Lors du petit historique de ce début de sujet, nous disions qu'avec une stack LAMP classique, chaque changement de page / envoi de formulaire entraînait une nouvelle requête entre le client et le serveur. Et si je vous disais qu'aujourd'hui, le serveur pourrait ne plus recevoir la moindre requête d'un utilisateur une fois que celui-ci a eu sa première page demandée ? Que les changements de page se feraient donc de manière quasi instantanée sans que le client doive attendre le rafraîchissement de sa page ? C'est ce qu'est une SPA, une application dont l'intégralité du code se trouve dans une seule page. Une fois celle-ci téléchargée, le client peut naviguer de manière très fluide à travers les différents écrans de l'application, n'envoyant de nouvelles requêtes que pour valider un formulaire ou récupérer de nouvelles données à afficher dans une portion bien précise de la page. En fait, c'est un type d'application qu'on a tous déjà l'habitude de côtoyer : n'importe quel réseau social moderne (Facebook, Twitter, Linkedin, Youtube, ...) est une SPA. Qu'est-ce qui nous empêche donc de nous tourner vers cette solution moderne ? Les pré-requis sont assez limités : connaissance du Javascript, curiosité des nouvelles technologies. Pour le premier point, la curiosité peut aussi faire l'affaire. Pour le deuxième, c'est précisément ce sur quoi j'espère vous ouvrir. De nombreux frameworks web front-end défilent depuis quelques années, chacun avec une approche du problème différente, mais de manière générale plus travaillée au cours des années. Certains diront peut-être que je vulgarise un peu trop le 'problème', mais je pense que cette problématique cruciale et commune à tous les frameworks est ni plus ni moins que la synchronisation entre l'état d'une application et son DOM (Document Object Model). Par cet état, j'entends l'ensemble des variables définies statiquement ou dynamiquement (depuis une requête au backend par exemple), pour être ensuite affichées au milieu du code HTML. Le DOM, c'est l'arborescence de chaque bloc html (en partant de la racine <html>) construite en mémoire du navigateur après l'interprétation du code HTML, ou de toute manipulation faite en Javascript. Par exemple, pour les quelques courageux addicts au VanillaJS, faire un elem.appendChild(document.createElement('span')) revient à ajouter dynamiquement un <span> dans elem, via ce fameux DOM virtuel. Même chose pour les déjà-plus-raisonnables pro-jQuery : $(".inner").append("<p>Test</p>"); Cependant, ces fonctions sont pratiques quand il s'agit de changer quelques parties précises de notre page, mais deviennent très vite laborieuses si on veut construire et mettre à jour l'ensemble de notre page html en Javascript. C'est donc notamment à ce problème que répondent les célèbres frameworks front-end Ember, Backbone, React, Angular et de nombreux autres. Je me contenterai de ne vous présenter succinctement que les deux derniers, étant les plus modernes / populaires et aussi les deux seuls que je maîtrise. Ces librairies vous permettront de construire des applications web entières comme de plus petits modules que vous pourrez tout aussi aisément intégrer dans un site déjà existant, comme par exemple un chat. 5/ ReactJS ReactJS, c'est le petit bébé développé par et pour Facebook (entre autres) depuis 2013. Si le mobile intéresse certains, il se trouve que Facebook a aussi sorti en 2015 React-Native, un framework JS mobile permettant de développer des applications compatibles Android et iOS, utilisant la même organisation et suivant les mêmes principes que React. En moins de 3 ans de développement, des milliers de contributeurs ont déjà rejoint le projet et les releases se suivent à intervalles très courtes, de sorte que React-Native soit déjà tout à fait viable en production . Je tiens aussi à arrêter tout de suite les médisants : non, react-native n'est pas un framework hybride (Xamarin, Ionic ... : applications mobiles embarquant une webview), ou alors à moitié. React-Native utilise JavascriptCore (Moteur JS de Safari) pour exécuter votre code JS qui va faire appel à des composants codés nativement pour les deux plateformes : votre application communiquera donc bien avec le système hôte et non un moteur de rendu. Mais revenons à nos moutons, React. Et quoi de mieux pour introduire une technologie que son implémentation de l'indétrônable Hello World: Contenu Masqué Sa particularité, c'est son organisation centrée autour de ses components. Un component, c'est comme un nouveau bloc html qui reçoit des paramètres et se formate correctement tout seul. Dans l'exemple ci-dessus, le component HelloWorld déclaré au début du fichier index.jsx ne reçoit qu'un seul paramètre name pour afficher une simple div avec. <HelloWorld name="Funky Emulation" /> Cette syntaxe HTML, qui peut paraître déroutante au beau milieu du code Javascript, est en réalité transformée en une instruction Javascript valide par le module JSX à l'exécution : React.createElement(HelloWorld, {name: 'Funky Emulation'}) On se doute qu'une application React serait tout simplement immonde à écrire sans ce petit JSX incontournable, dont vous pouvez d'ailleurs voir la balise d'inclusion au début du fichier html d'exemple. Bien, je vous ai montré l'éternel et simple exemple d'Hello World, mais qu'en est-il si on veut dynamiser un peu tout ça ? Une TODOLIST par exemple ? > Plunkr < Dans ce bout de code, plusieurs choses nouvelles. Déjà, nos components sont codés en classes Javascript (feature disponible depuis ES2015), une façon de faire que je préfère nettement aux simples fonctions qui ne permettent pas d'organiser les différentes fonctions de traitement du component aussi clairement qu'avec une classe. Remarquons par ailleurs que chacune de ces classes hérite de React.Component, la classe mère de tous les Components. Ensuite, de nouveaux attributs HTML sont apparus : onSubmit={this.onSubmit.bind(this)} dans le form du component Input permet d'appeler la méthode onSubmit lors de l'évènement onsubmit (envoi du formulaire). De même, onChange={this.onChange.bind(this)} situé juste en dessous permet d'appeler la méthode onChange à chaque nouvelle valeur de l'input. A noter la présence d'accolade lors de l'assignation de ces attributs, qui permet d'injecter du code JS dans ce 'pseudo html' interprété par JSX. Nous pouvons aussi voir une nouvelle assignation this.state = ... dans chaque constructeur : c'est une variable propre à React et contenant l'état interne d'un component. Avec le HelloWorld, nous avons vu la notion de props permettant d'injecter des variables au sein de component, afin de faire descendre une information depuis un component container jusqu'à un component contenu (par la suite, j'appellerai le container 'parent', et le contenu 'enfant'). La différence du state, c'est qu'il ne sera pas rempli par son component parent mais par son propre traitement (le curValue du component Input), ou par l'action d'un component enfant (le state.todolist dans le component App). Le point commun et l'intérêt de ces deux variables, c'est que le moindre changement de leur valeur forcera l'actualisation du component. Pour le state, l'affichage ne sera cependant rafraîchi que en le modifiant via la fonction setState (héritée de React.Component), afin d'en informer React. Il y a un deuxième point capital à savoir pour le bon déroulé du refresh et très spécifique au fonctionnement de React : lorsqu'on modifie une valeur contenue dans state ou transmise en props à un component enfant, et si c'est un objet, il doit être un clone modifié de l'ancienne valeur. Ce qui explique l'étrange syntaxe que j'ai utilisé pour la fonction d'ajout d'un TODO dans mon component App : newTodo(txt) { this.setState({ todolist: [txt, ...this.state.todolist] }) } La valeur assignée à todolist est en fait un nouveau tableau composé en premier de la nouvelle valeur, suivie de toutes les valeurs de l'ancien tableau (ce que permet de faire les '...', encore une nouveauté du JS). C'est grâce à la référence de ce nouvel objet que React pourra détecter les changements entre l'ancien state (ou props) et le nouveau : il parcourt récursivement chaque attribut du nouveau state (ou props), compare sa référence à celle correspondante de l'ancien state, et met à jour la partie de l'interface associée si elles sont différentes. Voilà un petit schéma (pardonnez mes tristes talents artistiques ) résumant les différentes interactions entre les 3 components (App, Input, Todolist) : App injecte : Sa méthode à appeler lors de l'ajout d'une entrée en props de Input Sa TODOLIST contenu dans le state en props de Todolist Input met à jour state.curValue à chaque changement du champs texte. Input appelle props.onAddTodo lors de l'envoi du formulaire en lui fournissant state.curValue onAddTodo() met à jour le state de App en reconstruisant le tableau todolist avec la nouvelle valeur Automatiquement, voyant que le props.todolist transmis à Todolist est un nouveau tableau, React déclenche la mise à jour du component C'est souvent de cette manière que sera construite une application React : les composants enfants interagissant avec l'utilisateur traiteront les données remplies par celui-ci en interne (state), pour ensuite les faire remonter au parent via une fonction transmise dans ses props, permettant au parent de stocker l'information et de la redispatcher à d'autres components. De manière générale, ce découpage en composants bien distincts est une excellente habitude à prendre et applicable dans n'importe quel contexte de développement, que ce soit ou non du Javascript, et même si ce n'est pas pour du web. Vous êtes ainsi forcé d'organiser clairement votre application en différentes unités chacune assignée à une tâche bien précise, dans un ensemble cohérent mais non couplé (lié trop étroitement) que vous interconnecterez par de l'injection de dépendance, à l'opposée d'une classe depuis laquelle vous instancierez ses dépendances. L'intérêt est multiple: chaque unité est facilement remplaçable, testable, et réutilisable. De plus, le travail collectif sur un tel projet en est aussi grandement simplifié. Bref, c'était juste une petite parenthèse pour vous sensibiliser à cette notion de conception utile dans tout contexte de dév, et que les attentifs pourront remarquer dans la prochaine partie ... 6/ AngularJS AngularJS, quant à lui, a été publié par Google pour la première fois en Octobre 2010, avant la sortie d'Angular en Mai 2016. Angular est officiellement la v2 d'AngularJS, mais en fait pas vraiment, leur plus gros point commun reste sûrement leur nom. En effet, après 6 ans de travail sur AngularJS et les retours de nombreux contributeurs, Google a certes gardé les grands axes de sa philosophie mais a construit très différemment Angular en fonction des points noirs relevés durant cette longue période : le système de détection de changement (synchronisation datas / views) a été bien plus réfléchi et ainsi largement optimisé, plusieurs notions ont été purement et simplement supprimées ou remplacées par soucis de simplification (AngularJS utilise de nombreux types de composants différents, ce qui est un frein à sa prise en main / bonne utilisation). Ces changements drastiques entre les deux versions a d'ailleurs été source de controverse auprès d'un grand nombre de développeurs, qui n'avaient juste plus affaire à leur outil habituel. Si la question vous intéresse, cet article et de nombreux autres en parlent en long et en large. Si je ne vous parle ici que d'AngularJS, ce n'est absolument pas parce que je suis un vieil aigri nostalgique rebutant Angular, mais uniquement parce je n'ai pas encore eu l'occasion d'utiliser ce dernier, bien qu'il m'intéresse tout à fait. Et en raison de mon idéal de l'évolution des choses où AngularJS disparaîtrait inévitablement dans l'ombre de son successeur, je vais vous dresser une présentation plus succincte d'AngularJS que pour ReactJS, vous laissant l'opportunité de vous y intéresser mais avec tout de même ma suggestion de vous tourner plus vers Angular. Exception faite des cas où on peut avoir tout intérêt à rester sur AngularJS, l'article cité au dessus en parle en fin de page. Quoi qu'il en soit, les grandes lignes directrices de la v1 restent selon moi intéressantes et instructives à voir. Comme pour React, commençons ce petit aperçu par un classique Hello World : Contenu Masqué Une première chose devrait vous sauter aux yeux, c'est la légèreté de la syntaxe par rapport à React. angular.module('app', []) : cette ligne renvoie un nouvel objet module lié au block html ayant l'attribut "ng-app='app'". Notez la présence du deuxième paramètre permettant de lier d'autres modules dépendants au nôtre dès sa création. Pour accéder au module 'app' après sa création, omettez simplement ce deuxième paramètre. Sinon, il sera réécrit et toutes les ressources liées perdues. .controller('MyCtrl', ... : cette instruction chaînée au module lui associe un nouveau contrôleur, nommé 'MyCtrl' et implémenté par la fonction en deuxième paramètre function ($scope) { $scope.name = 'Funky Emulation }) : La fonction en question, ainsi responsable de son scope Nous avons donc 3 notions centrales d'AngularJS : Le module, est un objet lié à un block html (ayant le bon ng-app) et contenant toutes les ressources nécessaires à la gestion du block en question. Parmi celles-ci, il y a cette liste de modules dépendants renseignée en deuxième paramètre du premier appel d'angular.module, et pouvant servir tout type de besoin : un module router pour amener le client sur la bonne vue en fonction de l'url, un module facilitant l'accès aux cookies, ... Bien souvent, ce sont des modules que vous pourrez télécharger via npm. Le contrôleur, est une simple fonction responsable de la logique métier de son block html lié et ses enfants. J'appellerai par la suite ce block la vue du contrôleur. Donc, la liaison entre le contrôleur et sa vue s'établit simplement avec l'attribut ng-controller sur cette dernière, que vous pouvez voir juste après l'ouverture du body. Le contrôleur accède à toutes ses dépendances via ses paramètres, et dans notre cas il n'en a qu'une : le $scope. Le $scope : cette variable est comparable au state d'un component dans React. C'est un objet contenant toutes les valeurs accessibles depuis la vue (et ses enfants) et uniquement depuis celle-ci. Donc quand je crée un $scope.name depuis mon contrôleur, j'injecte la même variable name au sein de ma vue. La documentation d'AngularJS à ce sujet est très complète et très instructive. Côté html, il suffit simplement d'utiliser une double paire d'accolade ({{variable}}) pour afficher une variable du scope. Cette syntaxe, repéré par AngularJS lors de son initialisation, crée un one-way-data-binding. En d'autres mots, la paire d'accolade devient un simple miroir de la valeur initialisée / modifiée depuis le contrôleur. Si la manière avec laquelle AngularJS maintient cette synchronisation (nommée dirty checking) vous intéresse, je vous invite à lire cet article qui discute et compare les façons de procéder de plusieurs frameworks dont React et AngularJS. En quelques mots et comme l'indique son nom, le dirty checking est aussi simple que bête, en testant chaque variable de scope affichée (toutes ne le sont pas forcément) après chaque action (un évènement onclick, un formulaire changeant de valeur, une requête ajax ...), à la recherche des différences. Si je vous parle du one-way-data-binding, c'est qu'il existe un two-way-data-binding : c'est ainsi qu'on crée un lien bidirectionnel entre une variable du scope et le champs d'un formulaire HTML. A l'inverse de React, plus besoin donc de dispatch la nouvelle valeur lors d'un évènement onChange, la synchronisation est maintenue "toute seule". Sans plus attendre, voici donc la simplissime implémentation de la TODOLIST cette fois avec AngularJS : Contenu Masqué Au niveau de l'index, on peut voir que notre vue est maintenant une simple balise fermante avec un attribut ng-include. C'était uniquement pour vous montrer qu'on peut bien sûr découper son template en plusieurs fichiers pour les inclure dans un deuxième temps. A noter que ce ng-include a toutefois un coût : le fichier en question est chargé depuis le client via une requête AJAX. Jetons maintenant un oeil au todolist.html, on peut y voir trois nouveaux attributs : ng-submit : équivalent du onSubmit chez React, permet d'appeler une méthode lors de l'envoi du formulaire ng-repeat : très utile, permet de répéter le block en question autant de fois que le tableau indiqué en paramètre a de valeurs, chaque nouveau block accédant à sa valeur via le nom placé avant le in ng-model : un autre trésor d'AngularJS aussi utile que ng-repeat, c'est le fameux attribut permettant la synchronisation entre le formulaire et la variable renseignée en paramètre. À noter aussi que cette variable est attribut d'un objet, c'est très important et permet d'éviter tout problème de synchronisation dans ce cadre-là. Une petite recherche google vous renseignera sûrement sur les raisons derrière ce petit 'tricks'. En fait, l'essentiel est déjà là, comme vous le comprendrez en voyant le contrôleur. Celui-ci se contente d'initialiser les variables todolist / form.todo, puis de déclarer son callback permettant l'ajout d'une entrée avec la réinitialisation du champs texte. Magique, n'est-ce pas ? Il y aurait de nombreuses (presque trop) autres notions à vous présenter au sujet d'AngularJS mais je ne m'y avancerai pas pour les raisons évoquées au début de cette partie. Si vous voulez aller plus loin avec AngularJS (ce que je peux complètement comprendre, c'est quand même très sexy ), je vous recommande de commencer par voir ce que sont les services, une des forces principales qui m'ont le plus plu au début (et c'est toujours le cas) ; une démonstration de l'injection de dépendances dans toute sa splendeur. Le tutoriel disponible sur le SDZ est par ailleurs et comme souvent un très bon début. 7/ Le packaging À travers les deux dernières parties sur des frameworks front-end, nous avons pu constater une chose : c'est un univers composé de multiples dépendances. Au final, tout comme le développement d'un software quelque soit le langage utilisé, sauf si votre application est une calculatrice en ligne de commande. Et bien souvent, un software peut s'installer voir s'utiliser directement via un unique exécutable. Qu'en est-il de notre application ? Nous n'allons tout de même pas inclure des dizaines de fichiers JS différents dans notre fichier html ? Ça marcherait, mais ce n'est sûrement pas la meilleure solution, ne serait-ce que pour le maintien de ces dépendances. Non, a la place, plusieurs outils permettent une gestion poussée de la compilation de tous vos fichiers en un bundle, ce fichier final et unique qu'il suffira de joindre à votre .html pour lancer l'application. Parmi les outils (taskrunners, packagers ou encore bundlers) les plus complets et avancés, on peut citer webpack ou gulp : tous deux permettent la concaténation de tous vos fichiers, leur minification, optimisation, lint-age (lint est une opération de débuggage de base) ... Il est aussi possible d'ajouter des traitements personnalisés, comme par exemple la précompilation de certaines extensions précises, comme pour des feuilles de style .sass. Personnellement, je n'ai jamais eu envie de m'intéresser à ce genre d'usine à gaz (malgré tout très performante, utile et utilisée) et leur configuration syntaxiquement bien trop verbeuse à mon goût. A la place, je vous propose d'écrire vos propres tâches npm de bundling. Les seuls pré-requis, c'est donc d'être sur un projet npm et d'installer les quelques modules nécessaires. Sans perdre de temps, voilà quelques tâches opérant un premier bundling de base : "bundle": "browserify ./entrypoint.js -o ./public/bundle.js", "bundle:watch": "npm run bundle && watchify ./entrypoint.js -o ./public/bundle.js -v", "bundle:prod": "browserify ./entrypoint.js | uglifyjs -mc > ./public/bundle.min.js" Ces lignes sont à placer avant la fin de l'objet "scripts", dans votre package.json. Chaque script s'utilise en ligne de commande dans le répertoire du package.json ; npm run bundle/bundle:watch/... Nous utilisons ici : browserify : cet utilitaire parcourt chaque fichier de votre application en commençant par le point d'entrée indiqué en premier paramètre, puis en suivant récursivement toutes ses inclusions de fichiers (instructions require) watchify : celui-là, il surveille tous vos fichiers et appelle browserify à chacune de leur modification. Très pratique pour la période de développement uglifyjs : Un incontournable une fois la phase de production arrivée, uglifyjs minifie votre code et renomme notamment tous vos symboles (variables / classes ...) Ne pouvant coder JS sans utiliser de feature Ecmascript récent, j'utilise aussi systématiquement babel, un transpileur Javascript . Son travail est tout simplement de réécrire chaque utilisation de syntaxe récente par un équivalent forcément portable. Pour lier babel au parcourt récursif de browserify, il nous faut aussi installer babelify. Nous pouvons alors dire à browserify de systématiquement appliquer babelify avec ces quelques lignes à ajouter dans votre package.json : "browserify": { "transform": ["babelify"] } Enfin, il faut indiquer à babel quels plugins nous voulons appliquer aux fichiers. En effet, beaucoup de nouvelles syntaxes sont apparues avec les dernières versions JS et chaque plugin babel n'en implémente que quelques unes. Chacun de ces plugins est un module node à installer, pour finalement les déclarer dans un fichier .babelrc à placer aux côtés du fichier package.json : { "presets": ["es2017", "es2015"], "plugins": ["transform-object-rest-spread"] } Ici, nous ajoutons la destructuration d'objet, ainsi que tous les plugins contenus dans les preset es2017 et es2015. Regardez sur ces pages babel pour installer les modules correspondant si vous les désirez. A noter que suivant votre environnement, toutes ces features JS peuvent ou non être disponibles. Sur un navigateur et à l'heure actuelle, babel est indispensable. Côté backend, avec node, ce site indique la portabilité de chaque feature sur les différentes versions de node. Une fois votre bundle généré, il suffit alors de l'inclure dans votre petite page .html servie par votre backend. 8/ Le Backend Bon, nous avons commencé ce topic par parler un peu de l'intérêt de NodeJS comme base pour développer une API web, avant de rapidement virer vers le front-end et toutes ses composantes. Je trouvais important de donner la priorité à la question NodeJS par soucis historique, et je ne suis certainement pas la bonne personne pour vous en apprendre plus sur le sujet. Cependant, il est quand même temps que nous revenions un peu dans les coulisses, s'assurer qu'on puisse bien répondre aux besoins de la scène Au sein d'une stack LAMP, nous disions que l'accès aux bases de données se faisait depuis le code PHP. Ce qui est logique, puisqu'il est responsable de la bonne génération des pages webs. Mais dans notre cas, le site web est entièrement construit en Javascript ... côté client. Vous voyez le problème ? Fournir au monde entier les identifiants de votre BDD n'est sûrement pas la meilleure des idées, puisque le code JS en aurait obligatoirement besoin pour requêter le SGBD. L'idée est donc la suivante ; si on ne peut parler à notre SGBD directement depuis le front-end, on va devoir ajouter un interlocuteur : cette fameuse API pour laquelle NodeJS semble être parfaitement approprié. Ainsi, les accès à votre SGBD resteront stockés côté back-end, lequel sera interrogé par le front-end. A partir de là, vous avez deux options : Codez vous même chaque requête nécessaire avec le SGBD, chacune ayant son url d'accès définie depuis Node (n'oublions pas Express !). C'est l'option la plus rapide si vous n'avez que quelques requêtes à gérer Tournez vous vers des API de stockage déjà existantes et reconnues, il n'en manque pas. Il vous faudra quelques jours pour bien la prendre en main, mais vous ne regretterez pas votre choix en voyant tous les bienfaits que ça vous aura apporté. Effectivement, cette API vous fera oublier le SQL et ses malheurs, proposant généralement quelque chose comme un ORM pour opérer intuitivement et directement sur vos entités, les liant entre elles selon la configuration que vous aurez au préalable défini dans des fichiers, laissant à l'API le soucis de créer et formater la base de donnée, écrire correctement les requêtes de jointure ... Vous pourrez alors complètement séparer la logique propre au SGBD de celle propre aux différentes opérations à faire sur vos données, permettant encore et toujours de pouvoir plus facilement remplacer votre SGBD, tester vos opérations, etc. Enfin, tout ce code implémentant vos différentes actions sur votre BDD ne sera pas non plus à faire si vous devez développer un autre front comme une application mobile, celle-ci n'aura qu'à requêter votre API comme le fait votre premier front. N'étant pas vraiment mon rayon, je me contenterai de vous conseiller ces api assez connues : Sails, Parse. Si le NoSQL vous intéresse, vous pouvez aussi setup votre propre API pour MongoDB 9/ Une SPA isomorphique Vous avez maintenant eu l'aperçu d'une bonne partie d'une stack JS moderne telle qu'on peut facilement la voir aujourd'hui (quoi que, pas sûr que soit toujours vrai dans une institution vieillissante comme la SNCF ) : Un backend constitué au moins d'un serveur NodeJS pouvant servir d'API mais servant surtout nos fichiers statiques (images/css/js), souvent un SGBD accompagné de son API, ainsi qu'une application front développée avec un framework comme Angular ou React, magnifiquement packagée puis servie au navigateur depuis notre NodeJS. Pour la petite culture, on vient presque de citer une stack assez connue : MEAN, pour MongoDB + Express + Angular + NodeJS. Express, je vous en parlais comme d'un module d'interface aux requêtes HTTP dans la partie sur Node, comme quoi c'est bien un module incontournable avec ce dernier. Avec tout ça, vous avez quelque chose de tout à fait pro, scalable, et tutti cuenti. Je tenais simplement à vous apporter une dernière petite notion: ce sont les applications isomorphiques. Au début de la partie sur les SPA, j'ai introduit les framework front-end comme des librairies permettant la conception d'application fonctionnant uniquement chez le client. En fait, ce n'est pas tout à fait vrai. Ce qui est sûr, c'est qu'une fois l'application téléchargée et lancée par le navigateur client, le backend n'a plus rien à faire avec ce front-end, sauf pour les requêtes avec le SGBD. Mais au moment où le serveur reçoit la première requête du client, qu'en est-il ? Dans un cas simple, il se contente de retourner la SPA, avant que le navigateur initialise l'application et commence ses premiers traitements. Mais ne pourrait-on pas prémâcher le travail du navigateur ? Puisqu'on peut maintenant exécuter du code JS autant côté client que serveur, on pourrait effectivement réaliser le premier rendu du front avant même de retourner celui-ci, de sorte que le navigateur n'ai plus qu'à afficher les vues déjà insérées avec leurs données pré-injectées par le serveur. L'intérêt, c'est de gagner de précieuses ms au chargement du site, des ms qui se sentiront notamment sur les mobiles, ainsi que pour votre SEO. Voilà donc ce qu'est qu'une application isomorphique. Ce n'est bien sûr pas un impératif à mettre obligatoirement en place, mais c'est un plus que certains gros services ne peuvent même pas se permettre d'ignorer, et que d'autres plus petits sont bien heureux d'adopter. Dans les grandes lignes, l'implémentation d'une telle app passe en premier lieu par s'assurer que toutes les dépendances du front-end (modules comme les appels d'API propres au navigateur) sont disponibles aussi et de la même manière sur le back-end. Par exemple, tous les appels fetch (= requête ajax), une API propre aux navigateurs, nécessiteront l'utilisation d'un polyfill (implémentation d'une feature indisponible en raison d'un mauvais environnement / version) côté serveur. Si ces requêtes font appel à des services tournant sur le même serveur que celui délivrant l'application, il serait bien plus optimisé de carrément remplacer ces fetch par l'appel direct aux bonnes fonctions. Ce problème de dépendance peut aussi s'appliquer à certains modules clés comme un module de routage, d'où l'intérêt d'autres modules prévus à cet effet, comme par exemple Universal-Router chez React. Suivant comment votre framework stocke / gère ses données, il faudra aussi réfléchir à comment ré-injecter celles-ci dans l'application une fois qu'elle arrive chez le client. Une étape qui commencera inévitablement par la sérialisation des données dans une variable JS dynamiquement ajoutée à la page par le serveur. Bref, de nombreux articles existent à ce sujet pour les grands frameworks comme React ou Angular, je laisse donc les curieux se documenter eux-même. 10/ Conclusion Et voilà, c'est sur cette dixième et dernière partie que je peux enfin conclure cet article (plus un article qu'un tutoriel proprement dit), après quelques soirées puis une dernière journée complète de rédaction. Me remettre derrière mon clavier pour vous apporter mon savoir comme j'en avais l'habitude il y a quelques années m'a fait vraiment plaisir, d'autant que c'est quelque chose que je voulais faire depuis quelques temps, peinant juste à trouver un point de ralliement entre l'activité du forum et mes centres d'intérêts, qui ont bien évolué depuis mes débuts ici. C'est finalement la sortie de la V6 qui m'a fait me décider, pour essayer de survoler avec vous le maximum de composantes d'un web en pleine évolution : on a vu le radical changement de rôle du Javascript, passant du langage boiteux servant à faire des animations à celui implémentant toute la logique d'une application de manière dynamique et directement chez le client, avec à son opposé un backend maintenant centré sur le stockage et le traitement de l'information en un service réutilisable depuis tout front-end. J'aurais pu faire un vrai tutoriel plus poussé sur l'un des sujets abordés ici mais je pense que ce n'était pas le plus intéressant. Quelque soit le jeux ou le forum, beaucoup de gens bossant dans l'émulation n'ont sûrement jamais eu connaissance de plusieurs des notions / technos abordées ici et c'est "dommage", parce qu'il suffit de savoir qu'une chose existe pour pouvoir exploiter tout son intérêt le bon moment venu, aussi obscure paraisse la chose jusqu'à ce moment. C'était pour moi aussi une bonne occasion de passer en revues et compléter mes connaissances, en malmenant mon pauvre chromium avec les je-ne-sais-combien d'onglet wikipédia et autres qui ont dû être ouverts. Je remercie les quelques courageux qui ont eu la patience de lire le sujet jusqu'ici, et n'hésitez pas à me faire des retours : si une partie n'est pas claire, mérite plus d'illustration, ou que l'une d'elle vous intéresse assez pour vouloir un tutoriel plus approfondi à ce sujet (ou sur tout autre sujet en JS). Bonne soirée la Funky Family !
Metin2 Land