Retour aux projets

Reconstruire Lilies en Java

Lilies est un serveur Minecraft de type usine et prestige. La première version était écrite en Skript et a atteint 90 joueurs en ligne. Pour la Saison 1, je porte chaque système vers une suite de plugins Java modulaire, une fonctionnalité à la fois.

Rôle
Propriétaire et seul développeur
Stack
Java 21, Paper 1.21.11, Gradle, SQLite, PacketEvents
Statut
Saison 1 en développement
La liste des joueurs de Lilies affichant 90 joueurs en ligne

Pourquoi réécrire quelque chose qui fonctionnait

Skript m'a permis d'avancer vite. À la fin de la première saison, le serveur tournait sur environ 57 fichiers de script, autour de 816 Ko de code, et il tenait 90 joueurs à la fois. Il avait aussi des limites que je ne pouvais pas corriger depuis Skript : pas de vrais types, pas de tests, des variables globales partagées pour tout, et chaque système qui met la main dans tous les autres.

Avant de porter quoi que ce soit, j'ai audité ligne par ligne l'ancien code. Le registre est arrivé à 1 038 lignes. La pire découverte a été le convoyeur qui payait depuis deux boucles distinctes, si bien que certains joueurs gagnaient environ dix fois ce pour quoi l'économie était équilibrée. Il y avait aussi des duplications d'objets. Chaque ligne est devenue soit une correction dans le nouveau code, soit un test qui prouve que le bug ne peut pas revenir.

La structure

La nouvelle version est un plugin central et un jar par fonctionnalité. Le noyau possède ce dont chaque fonctionnalité a besoin, et chaque fonctionnalité est son propre petit plugin qui peut être construit, testé et désactivé séparément.

Le serveur Paper charge LiliesCore, qui charge un jar pour chaque fonctionnalité Serveur PaperMinecraft 1.21.11 LiliesCorechargeur, données, config Jars économiegénérateurs, convoyeurs, fourneaux Jars socialgangs, échanges, caisses Jar événements14 mini-jeux
  • lilies-api ne contient que des interfaces. Les fonctionnalités communiquent entre elles via le ServicesManager de Bukkit face à ces interfaces, jamais en accédant directement au code des autres.
  • LiliesCore trouve les jars dans son dossier de fonctionnalités et les charge via le propre gestionnaire de plugins de Paper, dans l'ordre des dépendances.
  • Les jars de fonctionnalités sont une trentaine de modules en tout. Chacun expédie sa propre config, ses messages, ses objets et ses menus.

Tout est un fichier de config

Les prix, les taux de drop, les mises en page des menus et chaque ligne de texte du chat vivent en YAML, un dossier par module. Le staff peut lancer /lilies reload pour tout ou pour un seul module, et les nouvelles valeurs sont vérifiées avant de remplacer les anciennes, si bien qu'une faute de frappe laisse la config active en place. Les petites valeurs peuvent être changées en jeu avec /lilies set sans ouvrir un fichier.

Rapide avec un serveur plein

  • SQLite en mode WAL avec un seul thread d'écriture asynchrone, si bien que le thread principal du jeu n'attend jamais le disque. Les profils des joueurs sont mis en cache en mémoire.
  • Les hologrammes et les objets en mouvement sont des affichages uniquement par paquets, que le client interpole, au lieu d'entités téléportées à chaque tick.
  • Un seul planificateur par seconde partagé au lieu d'un minuteur par système.

Un test de charge avec 50 joueurs simulés récoltant 20 cultures par seconde chacun coûte 0,37 ms par tick. Le serveur dispose de 50 ms par tick à dépenser, et il a tenu un 20 TPS stable.

Des tests qui ont attrapé de vrais bugs

La suite comptait 501 tests une fois le noyau terminé, et 780 à la fin de la phase des boucles économiques. Le test le plus utile n'était même pas un test unitaire. Un test de démarrage lance un vrai serveur avec chaque jar chargé, et il a attrapé un bug que les tests unitaires ne pouvaient pas voir.

Quand un joueur clique droit dans le vide, Paper déclenche l'événement d'interaction déjà marqué comme annulé. Sept gestionnaires étaient réglés pour ignorer les événements annulés, si bien que ces fonctionnalités ignoraient discrètement chaque clic dans le vide. Chaque mock des tests unitaires construisait l'événement non annulé, ils passaient donc tous. Le test de démarrage utilisait le vrai serveur et a échoué immédiatement.

Des événements sans objets perdus

Le module événements fait tourner 14 mini-jeux. Rejoindre l'un d'eux prend un instantané de l'inventaire et de l'état du joueur. Chaque sortie d'un événement, que ce soit une victoire, une défaite ou un abandon, restaure d'abord cet instantané. Les récompenses ne sont payées qu'après que la restauration a été enregistrée, si bien qu'un plantage entre les deux ne peut pas donner une récompense et vider un inventaire, ou l'inverse.

Ce que j'en ai retenu

  • Auditer avant de porter a fait que le nouveau code corrige les anciens bugs au lieu de les recopier.
  • Les mocks sont d'accord avec vos hypothèses. Un test contre le vrai serveur est celui qui vous contredit.
  • Mettre chaque nombre dans la config fait que l'équilibrage de l'économie n'a plus besoin d'un développeur.