Il reste moins de 24h pour s'inscrire à la MFFJam !

[1.7.10 -> 1.12] Synchroniser ses données avec DBSynchronizer



  • Sommaire

    Introduction

    Bonjour à tous,
    Dans ce tutoriel je vais vous apprendre à utiliser la library DBSynchronizer (Data Base Synchronizer) que j'ai conçu moi-même.
    Cette library vous servira à simplifier grandement la synchronisation de données entre le serveur et le/les client(s) dans Minecraft. Cette library vous servira également à sauvegarder ces mêmes données dans la sauvegarde du monde.

    Prenons un exemple :

    Imaginez que vous créez un mod de magie que nous allons appeler Magikum et que vous avez besoin de stocker les données suivantes :

    • Le nombre de point de mana de chaque joueurs
    • Le niveau en magie de chaque joueurs : “apprenti”, “magicien” ou “grand sorcier”
    • La position des orbes qui sera reset a chaque fois qu'on relancera le monde (parce-que pourquoi pas ?)

    Vous êtes donc conscient qu'il faudra sauvegarder dans la worldsave le nombre de point de mana de chaque joueur et le niveau en magie de chaque joueur.
    Vous êtes aussi conscient qu'il faudra synchroniser toutes ces données afin que le serveur et chaque clients puissent y accéder en temps réel et les modifier.

    Ça fait beaucoup à gérer pour pas grand chose n'est-ce pas ? D'autant plus si votre mod présente beaucoup d'éléments à synchroniser.
    Et bien la library DBSynchronizer est faite pour vous !
    Oui, ça fait un peu pub dit comme ça, mais bon, j'en suis fier de ma library. 😄

    Pré-requis

    Il est nécessaire d'avoir créé la classe principale de son mod, un tuto est disponible sur le site.

    Même si vous en aurez pas besoin ici, il est préférable d'avoir déjà manipulé les packets et d'avoir quelques notion sur le network. Un tuto est disponible sur le site.
    De cette façon vous comprendrez mieux ce que vous faites (essayez au moins de lire le pré-requis).

    Enfin, dans ce tutoriel je m'adresse à des personnes qui ont un niveau assez basique en java : il faut savoir ce qu'est un objet, connaitre les notions d'héritages et gérer les exceptions quand il y en a. Voici un cours OpenClassroom très complet (il est conseillé d'avoir lu les 2 premières parties).
    Si vous n'avez pas ces notions vous pouvez quand même continuer mais dans ce cas je vous souhaite bon courage !

    Ha, et n'hésitez pas à cliquer sur les images pour les agrandir 😉

    Tutoriel

    Implémenter DBSynchronizer :

    Pour la 1.7.10 :

    Premièrement vous devez télécharger DBSynchronizer ici faites attentions à bien télécharger la version pour les développeurs.
    Petite précision importante : les utilisateurs qui téléchargeront votre mod devront aussi télécharger DBSynchronizer mais cette fois, la version joueur.

    Ensuite il faut que vous mettiez le jar que que vous venez de télécharger dans un dossier libs (et non pas lib) que vous aurez créé à la racine du projet afin que lors du build de votre mod, le gradle trouve bien les fichiers de la library.

    package-explorer 1.7.10

    Pour finir, il faut paramétrer votre projet pour qu'il détecte DBSynchronizer comme étant une library. Pour cela, il faut faire clic droit sur votre projet (Minecraft) puis Properties → Java Build Path → Libraries → Add Jars…
    Ensuite vous sélectionner : Minecraft/libs/DBSynchronizer.jar

    ib-config 1.7.10

    Puis vous faites OK mais ne fermez pas la fenêtre tout de suite car il faut lier la javadoc. Pour ce faire il faut que vous cliquiez sur la petite flèche à coté de DBSynchronizer puis sur Javadoc location: (none) puis sur Edit… et dans Javadoc location path vous entrez l'url suivante : http://www.trcgames.com/dbsynchronizer/javadoc

    javadoc-config

    Enfin cliquez sur OK puis à nouveau sur OK.

    Voilà !
    DBSynchronizer est maintenant bien implémenté à votre mod, vous pouvez désormais l'utiliser en toute tranquillité.

    Pour les autres versions :

    Premièrement vous devez télécharger DBSynchronizer ici.

    Ensuite il faut que vous mettiez le jar que que vous venez de télécharger dans le dossier mods, lui même situé dans le dossier run. En effet DBSynchronizer est un mod, donc les utilisateurs qui téléchargeront votre mod devront aussi télécharger DBSynchronizer et le mettre dans leur dossier mods.
    Vous devez également mettre le jar dans un dossier libs que vous aurez créé à la racine du projet afin que lors du build de votre mod, le gradle trouve bien les fichiers de la library.

    package-explorer

    Pour finir, il faut paramétrer votre projet pour qu'il détecte DBSynchronizer comme étant une library. Pour cela, il faut faire clic droit sur votre projet (MDKExample) puis Properties → Java Build Path → Libraries → Add Jars…
    Ensuite vous sélectionner : MDKExample/run/mods/DBSynchronizer.jar

    lib-config

    Puis vous faites OK mais ne fermez pas la fenêtre tout de suite car il faut lier la javadoc. Pour ce faire il faut que vous cliquiez sur la petite flèche à coté de DBSynchronizer puis sur Javadoc location: (none) puis sur Edit… et dans Javadoc location path vous entrez l'url suivante : http://www.trcgames.com/dbsynchronizer/javadoc

    javadoc-config

    Enfin cliquez sur OK puis à nouveau sur OK.

    Voilà !
    DBSynchronizer est maintenant bien implémenté à votre mod, vous pouvez désormais l'utiliser en toute tranquilité.

    Obtenir la base de données :

    La base de données va vous servir à stocker des données qui seront ensuite automatiquement synchronisées (et sauvegardées si vous le souhaitez).
    Pour récupérer l'instance de la base de données vous devez tout simplement faire :

    DatabaseGetter.getInstance (modID);
    

    modID étant l'ID de votre mod (merci captain obvious).
    Cette fonction va vous retourner un objet Database qui pourra être une ServerDatabase ou une ClientDatabase, ça dépendra si vous vous trouverez du côté server ou du côté client mais dans tous les cas la façon de l'utiliser est la même.

    Ce qu'il faut que vous sachiez, c'est que la base de donnée est liée à un monde. Cela vaut dire que vous ne pourrez pas l'obtenir si aucun monde n'est chargé (normal). Ce qui veut aussi dire que chaque monde aura sa propre base de données.
    Donc si une exception est lancé quand vous quand vous récupérez la base de données c'est peut-être qu'aucun monde n'est chargé à ce moment là.

    Petite parenthèse :

    Ne castez pas l'instance de la base de données en ClientDatabase ou en ServerDatabase car ça vous donnerait accès à des méthodes dont vous ne devriez pas y avoir l'accès. Les manipuler pourrait aller jusqu'à corrompre une sauvegarde, désynchroniser les clients/server ou faire freeze le server (je vous aurait prévenu 😉 ).

    Dernières restrictions :
    Vous ne pouvez pas obtenir la base de données depuis un Thread que vous aurez créé, même si ça a l'air de fonctionner, ne le faites pas. (désolé mais peut être que dans les prochaines versions ça changera).
    Et évitez de stocker l'instance dans un variable de classe, exemple :

    import com.trcgames.dbSynchronizer.DatabaseGetter;
    import com.trcgames.dbSynchronizer.database.Database;
    
    public class Exemple {
        
        //fortement déconseillé
        private Database database = DatabaseGetter.getInstance (Magikum.MODID);
        
        public void methode (){
            
            //correcte
            Database database = DatabaseGetter.getInstance (Magikum.MODID);
        }
    }
    

    Fonctionnement de la base de données et sauvegarde de données :

    C'est bien beau d'avoir une base de données mais maintenant il faut savoir comment l'utiliser !
    Pour ça, vous devez d'abord comprendre ce qu'est un DBFolder.

    Un DBFolder est un objet qui pourra stocker plein de types de variables différentes, mais aussi d'autres DBFolder. En fait, un DBFolder c'est un peu comme un NBTTagCompound.

    Vous pouvez stocker/modifier des données avec une clé :

    DBFolder folder = new DBFolder ();
    
    folder.setInt ("mon entier", 42);
    folder.setBoolean ("mon booléen", true);
    folder.setBlockPos ("ma position", new BlockPos (34, 70, -20));
    //etc…
    

    Vous pouvez aussi les récupérer en utilisant la clé :

    int monEntier = folder.getInt ("mon int");
    boolean monBooleen = folder.getBoolean ("mon booléen");
    BlockPos maPosition = folder.getBlockPos ("ma position");
    //etc..
    

    Voir même les supprimer, toujours en utilisant la clé :

    folder.removeInt ("mon int");
    folder.removeBoolean ("mon booléen");
    folder.removeBlockPos ("ma position");
    //etc...
    

    Il est possible d'utiliser la même clé pour deux données différentes du moment que les données ne sont pas du même type.

    Si vous ne l'avez pas déjà compris, c'est en fait ce qui vous servira à stocker des données comme, le nombre de point de mana de chaque joueurs, le niveau en magie de chaque joueurs et la position des orbes.
    C'est cool non ? C'est normal c'est moi qui l'ai fait. 😄

    Et bien devinez quoi ?
    La base de donnée vous donne accès à deux DBFolder que vous pourrez modifier à votre guise.

    Vous pouvez obtenir un **DBFolder **dont le contenu sera sauvegardé quand on quittera le monde avec getPersistentFolder() et un autre dont le contenu ne sera pas sauvegardé avec getNonPersistentFolder().

    Database database = DatabaseGetter.getInstance (Magikum.MODID);
    DBFolder dossierPersistant = database.getPersistentFolder();
    DBFolder dossierNonPersistant = database.getNonPersistentFolder();
    

    Pour que ça soit plus clair pour vous, reprenons notre exemple du mod Magikum.
    On pourrait architecturer notre base de données de cette manière :

    arbre

    Ce qui donnerait au niveau du code :

    Database database = DatabaseGetter.getInstance (Magikum.MODID);
    DBFolder dossierPersistant = database.getPersistentFolder();
    DBFolder dossierNonPersistant = database.getNonPersistentFolder();
    
    DBFolder pointsDeMana = new DBFolder ();
    dossierPersistant.setDBFolder ("points de mana", pointsDeMana); // vu qu'on fait un "set", si le DBFolder "points de mana" existe déjà, alors il sera remplacé
    pointsDeMana.setInt ("TheRedColossus", 30);
    pointsDeMana.setInt ("cpw", 96);
    pointsDeMana.setInt ("Notch", 9000);
    
    DBFolder niveau = dossierPersistant.getDBFolder ("niveau"); // cette fois ci on fait un "get", donc si le DBFolder "niveau" n'existe pas encore, alors il sera créé et stocké
    niveau.setString ("TheRedColossus", "apprenti");
    niveau.setString ("cpw", "magicien");
    niveau.setString ("Notch", "grand sorcier");
    
    dossierNonPersistant.setBlockPos ("orbe de lumière", new BlockPos (-254, 127, 50));
    dossierNonPersistant.setBlockPos ("orbe obscure", new BlockPos (1038, 68, 547));
    dossierNonPersistant.setBlockPos ("orbe de puisssance", new BlockPos (-102, 5, -10));
    

    Voilà, c'est tout !
    C'est tout ?

    Oui, effectivement, toutes les données qui seront stockées sur la base de données seront automatiquement sauvegardées si elles se situes dans le persistentFolder, que vous soyez côté serveur ou client. D'autant plus, à chaque fois que vous ajouterez, modifierez ou supprimerez une donnée, ce sera synchronisé avec le serveur et tous les clients.
    Ça veut dire que si vous modifiez la position d'une orbe depuis un client, la modification sera prise en compte sur le serveur puis sur tous les autres clients dans les centièmes de seconde qui vont suivre. Donc si ensuite vous récupérez la positions de l'orbe depuis le serveur ou un autre client (si vous êtes en multijoueurs) la modification aura été prise en compte.
    On ne peut pas faire plus simple !

    Résultat final

    Si vous avez envi de savoir ce qu'il y a dans un DBFolder (pour débugger par exemple), vous pouvez utiliser la méthode printInConsole :

    DBFolder folder = new DBFolder ();
    folder.printInConsole (true); // Mettez true si votre console supporte les caractères unicodes sinon mettez false.
    

    Ce qui donne pour notre persistentFolder :

    (DBFolder) persistent folder
    ├(DBFolder) point de mana
    │├(int) TheRedColossus : 30
    │├(int) cpw : 96
    │└(int) Notch : 9000
    └(DBFolder) niveau
     ├(String) TheRedColossus : apprenti
     ├(String) cpw : magicien
     └(String) Notch : grand sorcier
    

    et pour notre **nonPersistentFolder **:

    (DBFolder) non-persistent folder
    ├(BlockPos) orbe de lumière : x=-254 y=127 z=50
    ├(BlockPos) orbe obscure : x=1038 y=68 z=547
    └(BlockPos) orbe de puisssance : x=-102 y=5 z=-10
    

    Sur ce, merci d'avoir suivit ce tutoriel, j'espère que ma library vous sera utile !
    Pour ce qui est des questions, n'hésitez pas à les poser, et si vous trouvez des bugs sur ma library, je serais très reconnaissant que vous m'en avertissiez. Et au contraire, si la library fonctionne parfaitement ça me serait aussi utile que vous me le dîtes.

    Crédits

    The Colossus



  • Salut !
    J'ai fait comme tu as dis.
    Le client se lance sans problème mais pas le serveur qui, d'après les logs, fini par :
    https://pastebin.com/GZZ5cSkt



  • Ok je m'occupe de ça. Je te dis quand j'ai trouvé.
    Est-ce que tu peux me montre ton code ?



  • En fait, j'ai juste fait la base du mon mod (Annotation Mod + Proxy). J'ai juste installer le Synchroniser mais je ne l'utilise pas dans le code.



  • Est-ce que tu peux me faire un screenshot du package explorer pour que je vois comment tu l'as installé ?





  • Je n'ai pas pris en compte le fait que il y a eu beaucoup de changements après la 1.7.10
    Du coup la version de DBSynchronizer pour la 1.7.10 est complètement bugée et même la façon dont on doit l'installer est différente (du coup le tutoriel ne colle pas trop pour la 1.7.10).

    Je vais essayer de régler ça rapidement. Désolé du désagrément.



  • Okay merci, je vais attendre du coup



  • Voilà !
    En fait pour la 1.7.10 c'est un peu particulié, il y a une version de DBSynchronizer pour les joueurs et une autre pour les développeurs.
    Je te laisse suivre à nouveau la partie installation du tutoriel car c'est complètement différent pour la 1.7.10.

    Merci de ta patience !



  • Okay merci, je reviens si j'ai des problèmes 🙂

    EDIT : Ca fonctionne nickel. Petite question : si on met ce mod sur serveur, il faut aussi prendre la version pour joueur ?



  • C'est simple : il faut la version développeur uniquement quand t'es dans ton IDE.
    Sinon quand tu joue ou quand tu héberge un Minecraft Sever il te faut la version joueur.



  • Okay encore merci 🙂



  • Salut !
    J'utilise ta création pour la première fois pour faire une sorte de stockage à XP, j'ai fait ce code :

    
    public void onBlockClicked(World world, int x, int y, int z, EntityPlayer player)
    {
    if(player.isSneaking())
    {
    if(player.experienceLevel >= 1)
    {
    player.experienceLevel -= 1;
    Database database = DatabaseGetter.getInstance(Reference.MOD_ID);
    DBFolder dossier = database.getPersistentFolder();
    
    DBFolder xpStock = new DBFolder();
    dossier.setDBFolder("xpStock", xpStock);
    xpStock.setInt(player.getDisplayName(), xpStock.getInt(player.getDisplayName()) + 1);
    xpStock.printInConsole(true);
    }
    }
    }
    
    

    Cependant, comme on peut le voir dans la console :

    
    [10:46:21] [Client thread/INFO] [STDOUT]: [com.trcgames.dbSynchronizer.database.DBFolder:printInConsole:211]: (DBFolder) xpStock
    [10:46:21] [Client thread/INFO] [STDOUT]: [com.trcgames.dbSynchronizer.database.DBFolder:printInConsole:281]: ?(int) Player180 : 1
    [10:46:21] [Server thread/INFO] [STDOUT]: [com.trcgames.dbSynchronizer.database.DBFolder:printInConsole:211]: (DBFolder) xpStock
    [10:46:21] [Server thread/INFO] [STDOUT]: [com.trcgames.dbSynchronizer.database.DBFolder:printInConsole:281]: ?(int) Player180 : 1
    [10:46:21] [Client thread/INFO] [STDOUT]: [com.trcgames.dbSynchronizer.database.DBFolder:printInConsole:211]: (DBFolder) xpStock
    [10:46:21] [Client thread/INFO] [STDOUT]: [com.trcgames.dbSynchronizer.database.DBFolder:printInConsole:281]: ?(int) Player180 : 1
    [10:46:21] [Server thread/INFO] [STDOUT]: [com.trcgames.dbSynchronizer.database.DBFolder:printInConsole:211]: (DBFolder) xpStock
    [10:46:21] [Server thread/INFO] [STDOUT]: [com.trcgames.dbSynchronizer.database.DBFolder:printInConsole:281]: ?(int) Player180 : 1
    
    

    Le nombre n'augmente pas.

    Pouvez-vous m'aider ?



  • Bonjour,
    Tu recréé à chaque fois un nouveau **DBFolder **(xpStock). C'est donc normal que son contenu soit reset à chaque fois. Il faut donc que tu remplaces :

    DBFolder xpStock = new DBFolder();
    dossier.setDBFolder("xpStock", xpStock);
    

    par :

    DBFolder xpStock = dossier.getDBFolder ("xpStock");
    

    Petit bonus :
    Si le **persistentFolder **ne contient pas encore le **DBFolder **"xpStock" au moment de l'appel de getDBFolder() il créera un nouveau **DBFolder **qu'il nommera "xpStock", le stockera et te le retournera.
    Donc tu n'as pas de souci à te faire de ce côté là.

    Bonne journée.



  • Merci beaucoup !


  • Rédacteurs

    D'après ce que j'ai compris, si un changement de données s'effectue sur le client, alors les données sont aussi modifiés sur le serveur. Ça peut être utile dans certains cas mais dans la majorité des cas cela va donner une faille exploitable par les joueurs pour tricher



  • Oui effectivement. Je n'y avais pas pensé. 😕
    Peut être que je peux régler ce problème en faisant en sorte que le serveur doit au préalable autoriser tel ou tel client à accéder à tel ou tel donnée. De ce fait les clients n'auront accès qu'aux données dont le server leur a autorisé l'accès. Les autres données seront disponibles qu'en lecture seule.

    Qu'en pensez-vous ?


  • Rédacteurs

    Tu fais un système d'approbation du changement de donnée. Une fonction du style :

    
    public boolean canClientChangeData(EntityPlayerMP player, DBFolder currentDatas, DBFolder changedDatas)
    
    

    En gros par défaut ça retourne toujours faux (parce qu'il vaut mieux interdire l'écriture des données par le client par défaut). Si la fonction renvoie vrai les données actuelles sont remplacées par les nouvelles, sinon c'est les anciennes qui restent. Au niveau de l'emplacement de la fonction je te laisse l'intégrer comme bon te semble. Tu peux tout à fait mettre la fonction dans le DBFolder en changeant le prototype, enfin bon, voilà.



  • Ok, c'est bien à ça que je pensais. Merci.
    Je vais m'y mettre tout de suite !



  • Salut, je suis devant un nouveau problème. Je veux me servir de DBSynchronizer pour faire une sorte de cooldown sur mon block :
    Voici la classe de mon bloc où on bloque l'activation si le cooldown n'est pas fini : https://pastebin.com/K0kGvmjD
    Et la classe de ma Tile Entity où le cooldown descend : https://pastebin.com/xwBZzxBA

    Le problème c'est que le cooldown ne descend jamais. Le bloc me dit toujours d'attendre 1 seconde (sauf la première fois)