Niveaux et cartes d'EternalTD
Un « niveau » dans EternalTD est une configuration YAML dans plugins/EternalTD/levels/ associée à un dossier de monde modèle dans plugins/EternalTD/worlds/. Lorsqu'un joueur rejoint, EternalTD clone le monde modèle dans le conteneur de mondes du serveur et exécute la session dans cette copie clonée.
Champs de configuration de niveau
| Champ | Type | Défaut | Notes |
|---|---|---|---|
isEnabled | bool | true | Les niveaux désactivés sont ignorés lors du chargement |
levelName | string | null | Nom d'affichage présenté dans les messages, les tableaux de score et les menus NPC |
levelDescription | liste de chaînes | [] | Lignes affichées dans les menus NPC. Prend en charge les variables $highscoreWave et $highscorePlayer |
worldName | string | null | Nom du dossier modèle sous plugins/EternalTD/worlds/ |
startLocation | liste de chaînes | null | Liste des emplacements sérialisés où les créatures apparaissent |
endLocation | liste de chaînes | null | Liste des emplacements sérialisés vers lesquels les créatures se dirigent (les tuiles « rouges ») |
levelLocations | liste de chaînes | null | Chaque case de grille praticable dans le niveau. Les paquets la fournissent ; pour une carte personnalisée, /etd register <level> l'écrit à partir d'une sélection de sol, ou vous pouvez l'écrire à la main |
wavesConfigFile | string | requis | Nom de fichier de la configuration de vagues liée (waves/<name>.yml) |
highscoreWave | int | 0 | Meilleure vague atteinte sur ce niveau |
highscorePlayerName | string | "No one" | Nom d'affichage du joueur qui a établi le score |
environment | enum | NORMAL | Environnement de monde utilisé lors du chargement du monde cloné |
La taille de grille utilisée partout est de 3 blocs par case logique (constante GRID_SIZE dans le code).
waveCount n'est pas utilisé. Sa présence dans un ancien fichier ne fixe pas le nombre de vagues ; EternalTD utilise la configuration de vagues liée.
Format des chaînes de position
Les emplacements dans EternalTD sont sérialisés sous forme de chaînes séparées par des virgules de la forme :
worldName,x,y,z,pitch,yaw
Les chaînes à quatre éléments worldName,x,y,z sont également acceptées, avec pitch et yaw à zéro, ainsi que les chaînes à cinq éléments x,y,z,pitch,yaw sans monde. Pour la grille, les départs et les arrivées d'une session, EternalTD remplace le monde indiqué par celui de l'instance. Les positions du hub et des PNJ nécessitent en revanche que leur monde nommé soit chargé.
Les packs téléchargés fournissent normalement ces valeurs. Pour une carte personnalisée, levelLocations contient une coordonnée entière du bloc central par case de sol jouable de 3×3, espacées de trois blocs. Le chargement ajoute 0.5 à X et Z pour obtenir le centre dans le monde. /etd register <level> écrit cette liste à partir d'une sélection de sol (voir Procédure de création de carte) ; vous pouvez aussi l'écrire à la main.
Cycle de vie du monde
Lorsqu'un joueur rejoint un niveau :
- EternalTD cherche
worldNameparmi les sous-dossiers directs deplugins/EternalTD/worlds/. Les noms contenant un.sont exclus. Les modèles absents, les configurations de vagues indisponibles et les noms non sûrs sont refusés. Il refuse aussi les niveaux dontlevelLocations,startLocationouendLocationest absent, vide ou contient une entrée illisible : « This level has missing or invalid floor, entrance, or exit coordinates. Nothing was started. » - Il refuse une deuxième demande de préparation tant que la première demande du joueur est en cours, ainsi que les joueurs déjà dans une partie, y compris celle d'un autre plugin Nightbreak (« You are already in a match! »). La demande en attente réserve le joueur dans le système de parties partagé : les parties des autres plugins le refusent donc jusqu'à ce qu'elle aboutisse. Une déconnexion, ou 10 minutes sans monde prêt, l'annule et supprime la copie partielle.
- Il réserve le prochain suffixe numérique libre (
<worldName>_0,<worldName>_1, ...). La réservation est synchronisée et mémorise les noms réservés mais pas encore présents sur le disque, de sorte que deux joueurs se connectant en même temps ne peuvent pas entrer en collision sur le même nom d'instance. - Le modèle est copié dans le conteneur de mondes hors du thread principal. Le chargement du monde et l'initialisation de la session restent sur le thread principal.
- Le monde cloné est chargé en tant que monde vide temporaire via le
TemporaryWorldManagerde MagmaCore, afin que la migration Paper 26.1+ soit mise en quarantaine et que les chunks manquants reviennent sous forme de vide plutôt que de terrain fraîchement généré. - EternalTD valide la grille (voir Validation au démarrage de la session) et
InstanceProtectorapplique les protections d'EternalTD. Le système de parties admet ensuite le joueur : il enregistre ses données de récupération, le téléporte au point d'apparition du nouveau monde au tick suivant et lui remet les objets de partie. Si un autre plugin refuse cette téléportation, le joueur quitte la partie et celle-ci se termine. Attendez « Created your world! We're ready to go. » avant de commencer. Le message précédent « Joined level! » ne confirme pas à lui seul la réussite de la session.
Exigences relatives aux mondes modèles
EternalTD valide la racine avant de créer un clone et vérifie chaque descendant pendant la copie. Si un descendant est invalide, le clone partiel est supprimé. Un modèle est rejeté dans les cas suivants :
- ce n'est pas un répertoire, ou si c'est un lien symbolique
- il n'a pas de
level.datà sa racine - il contient un lien symbolique quelque part à l'intérieur
- il contient un fichier nommé
.eternaltd-instance(ce nom est réservé, voir ci-dessous)
Un échec de copie déclenche une restauration et un message d'erreur. Consultez la console si le nettoyage échoue lui aussi ; le message ne prouve pas que tous les fichiers ont été supprimés.
Marqueurs de propriété d'instance
Chaque clone reçoit .eternaltd-instance, qui contient les noms du modèle et de l'instance. EternalTD vérifie ce marqueur avant de supprimer une instance obsolète et avant de supprimer un monde copié dont la validation a échoué. Une copie partielle est supprimée dès que la copie échoue, et le monde d'une session créée est supprimé par le système de parties à la fin de la session (voir ci-dessous).
Les instances obsolètes laissées par un plantage sont nettoyées au chargement des niveaux, et même alors uniquement pour les dossiers portant un marqueur valide et qui ne sont pas actuellement chargés.
Validation au démarrage de la session
Une session ne devient jouable que si toutes les conditions suivantes sont réunies :
levelLocations,startLocationetendLocationsont tous présents et non vides — sinon « This level doesn't have a complete play area, start, and end configuration! »- la zone de jeu elle-même s'analyse en une grille valide — sinon « This level's play area is invalid! »
- chaque case de départ dispose à la fois d'un chemin terrestre et d'un chemin aérien vers une case d'arrivée — sinon « This level does not have complete land and air paths from every start to an end! »
Ces vérifications ont lieu après le chargement du monde copié et avant l'admission du joueur : un échec laisse donc l'état du joueur intact. EternalTD supprime alors le monde temporaire et affiche « Failed to start the level. Your player and the temporary world were rolled back. » Si l'admission elle-même échoue après l'enregistrement des données de récupération, celles-ci sont restaurées immédiatement.
Lorsque la session se termine :
- Toutes les tours restantes sont démantelées (une tour qui lève une erreur lors de son retrait est journalisée et ignorée plutôt que de bloquer le reste).
- Les données de récupération du joueur sont restaurées et il est ramené à l'endroit où il se trouvait juste avant d'entrer dans le niveau, ou au point d'apparition du monde principal si ce monde n'est plus chargé.
- Les règles de protection du monde sont retirées. Toute autre personne encore présente dans le monde cloné, comme un membre de l'équipe possédant
eternaltd.match.bypass, est envoyée au point d'apparition du hub défini dansconfig.yml, ou au point d'apparition du monde principal si aucun hub n'est configuré. - Les tickets de chunk pris sur la zone de jeu sont libérés (voir Validation du chemin).
- Le nettoyage demande le déchargement et la suppression dans les deux dispositions. Un déchargement refusé empêche la suppression. Tant que le plugin est activé, la suppression s'exécute de manière asynchrone 5 secondes après le déchargement. Un monde chargé de nouveau avant le nettoyage est également ignoré ; une suppression incomplète est journalisée.
- La fin est idempotente — un second
end()(par exemple/etd quitimmédiatement suivi de/etd hub) est sans effet.
Règles de protection d'instance
Pendant qu'un niveau est actif, les règles suivantes sont appliquées au monde cloné :
- Explosions désactivées
- Écoulement des liquides désactivé
- Élytres désactivées
- Activation du vol empêchée
- Tir ami empêché
- Apparition vanille des créatures empêchée
Le système de parties protège également le monde : le joueur en partie ne peut pas s'y téléporter, en sortir par téléportation ni s'y téléporter d'un point à un autre, hormis par les déplacements propres à EternalTD, et les autres joueurs sans eternaltd.match.bypass ne peuvent pas s'y téléporter. Voir Règles de la partie.
Procédure de création de carte
Utilisez le modèle comme source modifiable et écrivez la grille dans le YAML du niveau :
- Modifiez la carte dans un monde distinct ou en solo, sauvegardez-la, puis placez le modèle sous
plugins/EternalTD/worlds/<worldName>/. Modifier une copie de partie ne met pas à jour ce modèle. - Créez son YAML, liez une configuration de vagues chargée et renseignez
levelLocations,startLocationetendLocation. Utilisez les blocs centraux du sol pour la grille et des départs/arrivées qui correspondent à ces cases. - Pour utiliser les outils en jeu dans votre monde d'édition, exécutez
/etd reloadpour charger la configuration, puis/etd selectfloor. Le clic gauche définit le coin 1, le clic droit définit le coin 2. Gardez les coins dans le même monde et au même Y de sol ; les longueurs X/Z, extrémités incluses, doivent être des multiples de 3. - Vous pouvez aussi utiliser
/etd selectfloorcoordinates <x1> <y1> <z1> <x2> <y2> <z2>, qui définit les coins dans le monde actuel et soustrait 1 à chaque Y fourni. Indiquez donc le Y du sol plus un. /etd showselection <level>affiche une particuleEND_ROD, visible par vous seul, au-dessus de chaque case de sol que la sélection enregistrerait. Elle ne sauvegarde rien.- Gardez la sélection active pendant
/etd register <level> startouend. Ces commandes ajoutent les coordonnées entières de bloc du joueur et sauvegardent immédiatement, sans soustraire 1 à Y ni vérifier l'appartenance à la grille. Initialisez les deux listes avant de les utiliser. Vous pouvez aussi les modifier directement dans le YAML. - Après ces enregistrements, exécutez
/etd register <level>. La commande écritlevelLocationsà partir de la sélection, en remplaçant la liste existante, sauvegarde le fichier du niveau et efface la sélection. Désactiver le mode de sélection l'efface également. - Sauvegardez le modèle modifié, rechargez et rejoignez via le menu du PNJ ou
/etd join <level>. Attendez le message final indiquant que le monde est prêt, puis vérifiez la carte en jouant.
Le contrôle de sélection calcule les dimensions ainsi, en tronquant le résultat à un entier :
size = (abs(corner1 - corner2) + 1) / 3
L'ordre des coins n'a pas d'importance. Le contrôle examine les blocs centraux à partir du coin X/Z inférieur plus un, puis tous les trois blocs, au Y du coin 1 dans le monde actuel du joueur. Une case est ignorée si son bloc central de sol est traversable ou si le bloc au-dessus ne l'est pas. Ce contrôle ne valide pas chaque bloc de la case 3×3.
Validation du chemin
EternalTD met en cache, pour chaque tuile de départ, le chemin A* le moins coûteux vers une tuile d'arrivée quelconque — un chemin terrestre et un chemin aérien. Avec plusieurs tuiles d'arrivée, il compare les chemins terminés par leur coût et conserve le plus court : une carte comportant plusieurs sorties achemine donc les ennemis vers la plus proche accessible plutôt que vers la première trouvée.
Le cache terrestre est reconstruit au démarrage de la session, à chaque placement de tour et à chaque vente de tour. Le cache aérien n'est calculé qu'une seule fois, lors de la première reconstruction : les chemins aériens ignorent totalement les tours, rien de ce qu'un joueur construit ne peut donc les invalider.
Les ennemis aériens utilisent ce chemin aérien distinct, qui ignore entièrement le blocage par les tours et suit à la place le décalage aérien (4 blocs au-dessus du chemin configuré).
Le chargement de la zone de jeu force également le chargement de chaque chunk contenant une case de grille, afin qu'une longue partie ne puisse pas se bloquer sur un chunk déchargé. Ces tickets de chunk sont libérés à la fin de la session, juste avant la suppression du monde d'instance.
Le cache détermine également si un niveau est jouable tout court : si une tuile de départ n'a pas son chemin terrestre ou son chemin aérien, la session est refusée au moment de la connexion plutôt que de lancer une partie que les ennemis ne peuvent pas terminer. La même vérification s'exécute lors du placement d'une tour : une tour qui murerait une tuile de départ est refusée et l'or n'est pas dépensé.
La recherche des départs et arrivées utilise X/Z pour trouver une case, sans imposer une position centrale exacte ni comparer Y. Une entrée sans case correspondante est ignorée avec un avertissement. Ce message suggère un centre incorrect, mais vérifiez aussi que la case voulue existe dans levelLocations.
Chaque tuile de départ et d'arrivée résolue est également marquée comme non constructible, afin que les joueurs ne puissent pas murer une apparition ou une sortie en construisant dessus. Ces tuiles apparaissent en bleu clair sous la surbrillance de placement.
PNJ et menus de niveau
Les configurations de plugins/EternalTD/npcs/ associent des villageois aux niveaux. Un clic droit ouvre un menu de 9 emplacements, avec des entrées en 0, 2, 4, 6 et 8. Seuls les cinq premiers identifiants configurés sont pris en compte ; ceux qui manquent laissent des espaces, et les suivants sont ignorés avec un avertissement. Un PNJ sans aucun niveau référencé chargé est entièrement ignoré. Cliquez avec le bouton gauche sur une vitre verte pour demander son niveau.
| Champ | Type | Défaut | Notes |
|---|---|---|---|
isEnabled | bool | true | Les NPC désactivés sont ignorés |
levelIDs | liste de chaînes | requis | Noms de fichiers des niveaux que ce NPC propose |
location | string | null | Emplacement d'apparition au format standard worldName,x,y,z,pitch,yaw |
name | string | null pour une configuration personnalisée | Nom du PNJ ; les définitions prédéfinies peuvent en fournir un |
difficulty | string | null pour une configuration personnalisée | Étiquette au-dessus du PNJ ; renseignez-la explicitement |
disguise | string | null | Descripteur LibsDisguises (par exemple custom:etd_tutorial_npc) |
customDisguiseData | string | null | Données de commande LibsDisguises supplémentaires — généralement la longue chaîne de skin de joueur |
L'apparition du PNJ est programmée quatre secondes après la construction de sa configuration et exige que le monde de sa position soit chargé. Le villageois est invulnérable, sans IA, persistant et marqué avec la clé de PNJ d'EternalTD. Si LibsDisguises est activé et les deux champs de déguisement renseignés, EternalTD tente le déguisement ; un descripteur invalide peut laisser un villageois ordinaire.
Un porte-armure flottant portant l'étiquette difficulty apparaît 2,3 blocs au-dessus du PNJ.
Comportement à la connexion
DefaultConfig contrôle la façon dont les joueurs sont gérés dans le monde hub :
setupDone— indicateur suivant si les conseils de configuration initiale ont été complétés (par défautfalse).spawnLocations— par défautetd_spawn,0,65,0,0,0et toujours écrit dans la configuration. Il n'est résolu et utilisé que lorsque le mondeetd_spawnexiste.manageSpawn— par défauttrue. Si ce réglage est activé et la destination chargée, EternalTD demande une téléportation au point d'apparition 1 tick après la connexion.playerGuide— le texte du livre guide en jeu.nightbreak.autoDownloadPluginUpdates— paramètre partagé de MagmaCore (par défautfalse). Lorsqu'activé, les mises à jour du plugin et du contenu sont téléchargées automatiquement au démarrage.nightbreak.applyPluginUpdatesWithoutRestart(paramètre partagé de MagmaCore, par défautnever; égalementwhen-emptyouimmediately) détermine quand une mise à jour du plugin téléchargée est appliquée sans redémarrage. Voir Installation.
À la fin de l'initialisation, EternalTD demande aussi cette téléportation pour les joueurs déjà connectés, sauf ceux qui sont déjà dans le monde de destination. Ce passage exige lui aussi manageSpawn et une destination chargée. Ces chemins de connexion et de rattrapage ne vérifient pas le résultat de la téléportation ; un autre plugin peut donc empêcher l'arrivée.