Serveur minecraft

Modding Minecraft avec PHP – Bâtiments de code! – SitePoint – Monter un serveur MineCraft

Par Titanfall , le 1 janvier 2020 - 13 minutes de lecture

J'ai toujours voulu créer un mod Minecraft. Malheureusement, je n'ai jamais beaucoup aimé réapprendre Java, et cela m'a toujours semblé une exigence. Jusque récemment.

Écran de démarrage Minecraft

Grâce à une persévérance acharnée, j'ai découvert un moyen de créer des mods Minecraft, sans vraiment connaître Java. Il y a quelques astuces et mises en garde qui nous permettront de créer tous les mods que nous désirons, dans le confort de notre propre PHP.

Ce n'est que la moitié de l'aventure. Dans un autre article, nous verrons un éditeur Minecraft JavaScript 3D soigné. Si cela ressemble à quelque chose que vous aimeriez apprendre, assurez-vous de vérifier ce post.

La plupart du code de ce didacticiel se trouve sur Github. J'ai testé tous les bits JavaScript de la dernière version de Chrome et tous les bits PHP de PHP 7.0. Je ne peux pas promettre que cela aura exactement la même apparence dans d'autres navigateurs ou fonctionnera de la même manière dans d'autres versions de PHP, mais les concepts de base sont universels.

Préparer les choses

Comme vous le verrez dans un peu, nous allons communiquer des charges entre PHP et un serveur Minecraft. Nous aurons besoin d'un script à exécuter aussi longtemps que nous aurons besoin des fonctionnalités du mod. Nous pourrions utiliser une boucle occupée traditionnelle:

tandis que (vrai) 
    // écoute les demandes des joueurs
    // apporter des modifications au jeu

    sommeil (1);

… Ou nous pourrions faire quelque chose d'un peu plus intéressant.

J'ai beaucoup aimé l'AMPHP. Il s'agit d'une collection de bibliothèques PHP asynchrones, y compris des éléments comme les serveurs et les clients HTTP et une boucle d'événements. Ne vous inquiétez pas si vous n'êtes pas familier avec ces choses. Nous allons le prendre gentiment et lentement.

Commençons par créer une boucle d'événements et une fonction pour surveiller les modifications apportées à un fichier. Nous devons installer la boucle d'événements et les bibliothèques de systèmes de fichiers:

compositeur nécessite amphp / ampli
compositeur nécessite amphp / fichier

Ensuite, nous pouvons démarrer une boucle d'événements et vérifier qu'elle fonctionne comme prévu:

nécessite __DIR__. "/vendor/autoload.php";

Amp  run (fonction () 
    Amp  repeat (fonction () 
        // écoute les demandes des joueurs
        // apporter des modifications au jeu
    , 1000);
);

Ceci est similaire à la boucle infinie que nous avions, sauf qu'elle n'est pas bloquante. Cela signifie que nous pourrons effectuer davantage d'opérations simultanées, en attendant des opérations qui bloqueraient normalement le processus.

Un petit détour au pays des promesses

En plus de ce code wrapper, AMPHP fournit également une interface soignée basée sur des promesses. Vous connaissez peut-être déjà ce concept (à partir de JavaScript), mais voici un exemple rapide:

$ éventuellement = asyncOperation ();

$ éventuellement
    -> puis (fonction ($ data) 
        // faire quelque chose avec $ data
    )
    -> catch (fonction (Exception $ e) 
        // oups, quelque chose s'est mal passé!
    );

Les promesses sont un moyen de représenter des données que nous n'avons pas encore – d'éventuelles valeurs. Cela peut être quelque chose de lent (comme une opération de système de fichiers ou une requête HTTP).

Le fait est que nous n'avons pas immédiatement la valeur. Et au lieu d'attendre la valeur au premier plan (ce qui bloquerait traditionnellement le processus), nous l'attendons en arrière-plan. En attendant en arrière-plan, nous pouvons faire d'autres travaux significatifs au premier plan.

AMPHP va encore plus loin en promettant des générateurs. Tout cela est un peu intense à expliquer en une seule séance, mais restez avec moi.

Les générateurs sont une simplification syntaxique des itérateurs. Autrement dit, ils réduisent la quantité de code que nous devons écrire, pour permettre l'itération sur des valeurs non encore définies dans un tableau. De plus, ils permettent d'envoyer des données dans la fonction qui génère ces valeurs (pendant la génération). Vous commencez à ressentir un modèle ici?

Les générateurs nous permettent de construire le prochain élément du tableau à la demande. Les promesses représentent une valeur éventuelle. Par conséquent, nous pouvons réutiliser les générateurs pour générer une liste d'étapes (ou de comportement), qui sont exécutées à la demande.

Cela peut être plus facile à comprendre en regardant un code:

utilisez Amp  File  Driver;

fonction getContents (fichiers $ du pilote, $ chemin d'accès, $ précédent) 
    $ next = yield $ files-> mtime ($ path);

    if ($ précédent! == $ suivant) 
        return yield $ files-> get ($ path);
    

    return null;

Voyons comment cela fonctionnerait en exécution synchrone:

  1. Appeler pour getContents
  2. Appeler pour $ files-> mtime ($ path) (imaginez que ce n'était qu'un proxy pour filemtime)
  3. Attendre filemtime rendre
  4. Appeler pour $ files-> get ($ path) (imaginez que ce n'était qu'un proxy pour file_get_contents)
  5. Attendre file_get_contents rendre

Avec des promesses, nous pouvons éviter le blocage, au prix de quelques nouvelles fermetures:

fonction getContents ($ files, $ path, $ previous) 
    $ files-> mtime ($ path) -> then (
        fonction ($ suivant) utiliser ($ précédent) 
            if ($ précédent! == $ suivant) 
                $ files-> get ($ path) -> then (
                    fonction ($ data) 
                        // faire quelque chose avec $ data
                    
                )
            

            // faire quelque chose avec null
        
    );

Étant donné que les promesses peuvent être chaînées, nous pourrions réduire cela à:

fonction getContents ($ files, $ path, $ previous) 
    $ files-> mtime ($ path) -> then (
        fonction ($ suivant) utiliser ($ précédent) 
            if ($ précédent! == $ suivant) 
                return $ files-> get ($ path);
            

            // faire quelque chose avec null
        
    ) -> puis (
        fonction ($ data) 
            // faire quelque chose avec les données
        
    );

Je ne sais pas pour vous, mais cela me semble encore un peu désordonné. Alors, comment les générateurs s'intègrent-ils à cela? Eh bien, AMPHP utilise le rendement mot-clé pour évaluer les promesses. Regardons le getContents fonctionner à nouveau:

fonction getContents (fichiers $ du pilote, $ chemin d'accès, $ précédent) 
    $ next = yield $ files-> mtime ($ path);

    if ($ précédent! == $ suivant) 
        return yield $ files-> get ($ path);
    

    return null;

$ files-> mtime ($ path) renvoie une promesse. Au lieu d'attendre la fin de la recherche, la fonction s'arrête de fonctionner lorsqu'elle rencontre le rendement mot-clé. Après un certain temps, AMPHP est informé que l'opération de statistiques est terminée et il reprend cette fonction.

Ensuite, si les horodatages ne correspondent pas, fichiers-> get ($ path) récupère le contenu. Ceci est une autre opération de blocage, donc rendement suspend à nouveau la fonction. Lorsque le fichier est lu, AMPHP relancera cette fonction (en retournant le contenu du fichier).

Ce code ressemble à l'alternative synchrone, mais utilise des promesses (de manière transparente) et des générateurs pour le rendre non bloquant.

AMPHP diffère un peu des spécifications Promises A + en ce que les promesses AMPHP ne prennent pas en ensuite méthode. D'autres implémentations PHP, comme React / Promise et Guzzle Promises, le font. L'important est de comprendre la nature éventuelle des promesses, et comment elles peuvent être interfacées avec des générateurs, pour prendre en charge cette syntaxe asynchrone succincte.

Écoute des journaux

La dernière fois que j'ai écrit sur Minecraft, il s'agissait d'utiliser la porte d'une maison Minecraft pour déclencher une alarme réelle. En cela, nous avons brièvement abordé le processus de récupération des données d'un serveur Minecraft et de PHP.

Nous avons mis un peu plus de temps pour y arriver, cette fois-ci, mais nous faisons essentiellement la même chose. Regardons le code pour identifier les commandes des joueurs:

define ("LOG_PATH", "/path/to/logs/latest.log");

$ files = Amp  File  filesystem ();

// obtenir des données de référence

$ commandes = [];
$ timestamp = yield $ filesystem-> mtime (LOG_PATH);

// écoute les demandes des joueurs

Amp  repeat (function () use ($ files, & $ command, & $ timestamp) 
    $ contents = rendement de getContents (
        $ files, LOG_PATH, $ timestamp
    );

    if (! vide ($ contents)) 
        $ lines = array_reverse (explode (PHP_EOL, $ contents));

        foreach ($ lignes as $ line) 
            $ isCommand = stristr ($ line, ">>")! == false;
            $ isNotRepeat =! in_array ($ ligne, $ commandes);

            if ($ isCommand && $ isNotRepeat) 
                // exécute la commande mod

                array_push ($ commandes, $ ligne);

                imprimer "exécution:". $ line. PHP_EOL;
                Pause;
            
        
    
, 500);

Nous commençons par obtenir l'horodatage du fichier de référence. Nous utilisons ceci pour déterminer si le fichier a changé (dans le getContents une fonction). Nous créons également une liste vide, où nous stockons toutes les commandes que nous avons déjà exécutées. Cette liste nous aidera à éviter d'exécuter deux fois la même commande.

Vous devez remplacer /path/to/logs/latest.log avec le chemin d'accès aux fichiers journaux de votre serveur Minecraft. Je recommande d'exécuter le serveur Minecraft autonome, qui devrait mettre logs / latest.log dans le répertoire racine.

Nous avons dit Amp repeat pour exécuter cette fermeture tous les 500 millisecondes. Pendant ce temps, nous vérifions les modifications de fichiers. Si l'horodatage a changé, nous divisons les lignes du fichier journal en un tableau et l'inversons (afin de lire d'abord les messages les plus récents).

Si une ligne contient «>>» (comme cela se produirait si un joueur tapait «> une commande»), nous supposons que cette ligne contient une instruction de commande.

reconnaître les commandes

Création de plans

L'une des choses les plus chronophages de Minecraft est la construction de grandes structures. Ce serait beaucoup plus facile si je pouvais les planifier (en utilisant un constructeur JavaScript 3D chic), puis les placer dans le monde en utilisant une commande spéciale.

Nous pouvons utiliser une version légèrement modifiée du générateur que j'ai couvert dans l'autre article susmentionné pour générer une liste d'emplacements de blocs personnalisés:

Création de placements de blocs personnalisés

Pour le moment, ce constructeur ne permet que le placement de blocs de terre. La structure de tableau qu'il génère est la X, y, et z coordonnées de chaque bloc de terre placé (après le rendu de la scène initiale). Nous pouvons copier ceci dans le script PHP sur lequel nous travaillons. Nous devons également comprendre comment identifier la commande exacte pour construire la structure que nous concevons:

$ isCommand = stristr ($ line, ">>")! == false;
$ isNotRepeat =! in_array ($ ligne, $ commandes);

if ($ isCommand && $ isNotRepeat) 
    array_push ($ commandes, $ ligne);
    executeCommand ($ line);
    Pause;


// ...plus tard

fonction executeCommand ($ raw) 
    $ command = trim (
        substr ($ raw, stripos ($ raw, ">>") + 3)
    );

    if ($ command === "build") 
        $ blocks = [
            // ...from the 3D builder
        ];

        foreach ($ block as $ block) 
            // ... place chaque bloc
        
    

Chaque fois que nous recevons une commande, nous pouvons la transmettre au executeCommand une fonction. Là, nous extrayons de la seconde > jusqu'à la fin de la ligne. Nous avons seulement besoin d'identifier construire commandes pour le moment.

Parler au serveur

L'écoute des journaux est une chose, mais comment communiquer avec le serveur? Le serveur autonome lance un serveur de chat d'administration (appelé RCON). Il s'agit du même serveur de chat d'administration qui active les mods dans d'autres jeux, comme Counter-Strike.

Il s'avère que quelqu'un a déjà construit un client RCON (bien que bloquant), et récemment j'ai écrit un joli wrapper pour cela. Nous pouvons l'installer avec:

compositeur nécessite théorie / constructeur

Permettez-moi de m'excuser pour la taille de cette bibliothèque. J'ai inclus une version du serveur autonome Minecraft, afin de pouvoir créer des tests automatisés pour la bibliothèque. Quelle course…

Nous devons configurer notre serveur autonome afin de pouvoir y établir des connexions RCON. Ajoutez ce qui suit à la server.properties fichier, dans le même dossier que le serveur pot:

enable-query = true
enable-rcon = true
query.port = 25565
rcon.port = 25575
rcon.password = mot de passe

Après un redémarrage, nous devrions pouvoir nous connecter au serveur à l'aide d'un code ressemblant à ce qui suit:

$ builder = new Client ("127.0.0.1", 25575, "mot de passe");
$ builder-> exec ("/ dis bonjour au monde");

Nous pouvons moderniser notre executeCommand fonction pour construire une structure complète:

fonction executeCommand ($ builder, $ raw) 
    $ command = trim (
        substr ($ raw, stripos ($ raw, ">>") + 3)
    );

    if (stripos ($ command, "build") === 0) 
        $ parts = explode ("", $ command);

        if (count ($ parts) < 4) 
            print "invalid coordinates";
            return;
        

        $x = $parts[1];
        $y = $parts[2];
        $z = $parts[3];

        $blocks = [
            // ...from the 3D builder
        ];

        $builder->exec ("/ say building ...");

        foreach ($ blocks as $ block) 
            $ dx = $ block[0] + $ x;
            $ dy = $ block[1] + $ y;
            $ dz = $ block[2] + $ z;

            $ builder-> exec (
                "/ setblock $ dx $ dy $ dz saleté"
            );

            usleep (500000);
        
    

Le nouveau et amélioré executeCommand vérifie si la commande (un message ressemblant à > construire) commence par le mot «construire».

Si le constructeur n'était pas bloquant, il serait préférable d'utiliser donner un nouvel Amp Pause (500), au lieu de usé (500000). Nous devons également traiter executeCommand comme une fonction de générateur, où nous l'appelons, ce qui signifie utiliser yield executeCommand (...).

Si c'est le cas, la commande est divisée par des espaces, pour obtenir le X, y, et z coordonne l'endroit où la conception doit être construite. Ensuite, il prend le tableau que nous avons généré par le concepteur et place chaque bloc dans le monde.

le produit "fini"

D'où d'ici?

Vous pouvez probablement imaginer de nombreuses extensions amusantes de ce simple script de type mod que nous venons de créer. Le concepteur pourrait être élargi pour créer des arrangements composés de nombreux types et configurations de blocs différents.

Le script mod peut être étendu pour recevoir des mises à jour via une API JSON, afin que le concepteur puisse soumettre des conceptions nommées, et construire La commande pourrait spécifier exactement la conception que le joueur veut construire.

Je vais vous laisser ces idées comme un exercice. N'oubliez pas de consulter la publication JavaScript associée, et si vous avez des idées ou des commentaires à partager, veuillez le faire dans les commentaires!

Click to rate this post!
[Total: 0 Average: 0]

Commentaires

Laisser un commentaire

Votre commentaire sera révisé par les administrateurs si besoin.