Pour les auteurs d'addons
Vortex Dread est écrit pour qu'on greffe des choses dessus. Aucun mixin n'est
nécessaire, aucun fork non plus, et rien n'a besoin d'être déclaré dans votre
fabric.mod.json au-delà d'une dépendance sur le mod. Vous ajoutez le jar à
votre classpath, vous appelez la classe d'entrée, et c'est tout. Cette page décrit l'intégralité
de ce qui est public: quatre types, une classe d'appel, deux événements. Un addon qui compile
contre autre chose que ça compile contre de l'interne, et l'interne bouge.
La surface publique
Tout ce qui est stable vit dans oas.dreyka.vortexdread.api
et dans son sous-paquet oas.dreyka.vortexdread.api.event. Deux types
énumérés venus d'ailleurs entrent dans le contrat parce que les signatures les exposent:
oas.dreyka.vortexdread.wind.EfScale, qui porte les six cotes, et
oas.dreyka.vortexdread.tornado.TornadoStage, qui porte les cinq stades.
Ces deux-là ne bougeront pas non plus. Le reste de l'arborescence change au gré des versions sans
que ça compte comme une régression.
Le mod se publie sous le groupe oas.dreyka.vortexdread,
l'artefact vortexdread, version 1.0.0. Aucun
dépôt maven distant n'est ouvert pour l'instant: un ./gradlew
publishToMavenLocal depuis une copie du dépôt met l'artefact dans votre
~/.m2, et une dépendance sur le jar d'une release GitHub marche tout
aussi bien. La surface décrite ici existe depuis la première version publiée.
Le brancher dans votre build
Tout le montage, parce que les lignes qui coincent sont celles que personne
n'écrit. Loom est un plugin Gradle et il n'habite pas sur le portail des plugins, donc
settings.gradle.kts doit nommer le maven de Fabric avant que
build.gradle.kts puisse le réclamer:
pluginManagement {
repositories {
maven("https://maven.fabricmc.net/") { name = "Fabric" }
gradlePluginPortal()
mavenCentral()
}
}
rootProject.name = "votre-addon"
Puis le plugin lui-même, en tête de build.gradle.kts. La
version ci-dessous est celle avec laquelle le mod est compilé, et Java 21 est ce sur quoi tourne
1.21.11:
plugins {
id("fabric-loom") version "1.17.12"
java
}
java {
toolchain.languageVersion = JavaLanguageVersion.of(21)
}
Puis les dépendances, en DSL Kotlin:
dependencies {
minecraft("com.mojang:minecraft:1.21.11")
mappings(loom.officialMojangMappings())
modImplementation("net.fabricmc:fabric-loader:0.19.3")
modImplementation("net.fabricmc.fabric-api:fabric-api:0.141.4+1.21.11")
modImplementation(files("libs/vortexdread-1.0.0.jar"))
}
Les trois mêmes blocs en Groovy, parce qu'un fichier de build écrit dans une seule des deux formes laisse l'auteur de l'autre deviner:
// settings.gradle
pluginManagement {
repositories {
maven { url = 'https://maven.fabricmc.net/'; name = 'Fabric' }
gradlePluginPortal()
mavenCentral()
}
}
// build.gradle
plugins {
id 'fabric-loom' version '1.17.12'
id 'java'
}
dependencies {
minecraft 'com.mojang:minecraft:1.21.11'
mappings loom.officialMojangMappings()
modImplementation 'net.fabricmc:fabric-loader:0.19.3'
modImplementation 'net.fabricmc.fabric-api:fabric-api:0.141.4+1.21.11'
modImplementation files('libs/vortexdread-1.0.0.jar')
}
Prenez modImplementation, jamais
implementation ni compileOnly. Le
jar publié porte des noms intermédiaires pour chaque type de Minecraft qu'il touche, et seule
une configuration qui sait ce qu'est un mod demande à Loom de les retraduire vers les vôtres. En
cas d'erreur, le compilateur sort des cannot access class_2960 ou des
Identifier cannot be converted to class_1937, qui ressemblent à une API
cassée et sont trois lettres manquantes.
Fabric API se déclare sur la même configuration, pour une raison du même ordre.
Les deux rappels décrits plus bas sont des Event de Fabric, donc votre
code touche cette classe à la compilation même s'il n'appelle jamais Fabric API lui-même, et Loom
ne transmet l'access widener de Fabric API qu'à un projet qui déclare le module. Sur
modRuntimeOnly, vous récupérez un
net.fabricmc.fabric.api.event.Event introuvable et, plus surprenant, la
moitié des appels d'enregistrement de vanilla qui passent en privé.
Les mappings ne sont pas libres non plus: le mod est compilé contre les mappings
officiels de Mojang, et toutes les signatures de cette page sont écrites dedans. Un projet sous
Yarn voit ServerWorld là où les tableaux ci-dessous disent
ServerLevel, et les deux ne se rencontrent pas. Dans
fabric.mod.json, l'identifiant dont dépendre est
"vortexdread".
Six types de vanilla apparaissent dans les signatures ci-dessous, et les tableaux n'en donnent que le nom court. D'où ils viennent, pour n'avoir rien à chercher:
import net.minecraft.world.phys.Vec3;
import net.minecraft.world.phys.AABB;
import net.minecraft.world.level.Level;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.block.state.BlockState;
import oas.dreyka.vortexdread.api.VortexDreadApi;
import oas.dreyka.vortexdread.api.TornadoView;
import oas.dreyka.vortexdread.api.event.TornadoLifeCallback;
import oas.dreyka.vortexdread.api.event.BlockTakenCallback;
import oas.dreyka.vortexdread.wind.EfScale;
import oas.dreyka.vortexdread.tornado.TornadoStage;
Ce qu'un addon peut appeler et écouter
Rien ne s'enregistre au démarrage: il n'y a pas d'entrypoint à déclarer, pas
d'interface à implémenter au niveau du mod, pas d'ordre de chargement à respecter. Les deux
événements passent par net.fabricmc.fabric.api.event.Event, la mécanique
que Fabric fournit déjà, donc vous vous abonnez depuis votre propre
onInitialize et le mod vous appelle quand il a quelque chose à dire. Un
écouteur qui lève une exception est signalé une fois par classe, dans le journal du serveur, et
la tempête continue sans lui.
| Point d'entrée | Ce qu'un addon en fait | Depuis |
|---|---|---|
| VortexDreadApi | Classe statique. Liste les tornades d'un monde, trouve la plus proche d'un point, échantillonne le vent, en fait naître une. | 1.0.0 |
| TornadoView | Interface en lecture seule sur une tornade vivante. Treize accesseurs, tous à jour tant que la tempête existe. | 1.0.0 |
| event.TornadoLifeCallback | Naissance, contact et fin. C'est là qu'un système d'alerte ou un tableau des scores se branche. | 1.0.0 |
| event.BlockTakenCallback | Veto sur chaque bloc que la tempête s'apprête à emporter. Ce qu'il faut à un mod de protection de zone. | 1.0.0 |
| wind.EfScale | Les six cotes, avec leur plancher de rafale en mètres par seconde et leur clé de traduction. | 1.0.0 |
| Signature | Ce qu'elle rend | Côté |
|---|---|---|
| tornadoes(Level) | List<TornadoView>, toutes les tornades chargées, sans ordre garanti. | les deux |
| nearest(Level, Vec3) | Optional<TornadoView>, celle dont l'axe est le plus près du point. | les deux |
| windAt(Level, Vec3) | Vec3, le vent en blocs par seconde, somme de toutes les tempêtes qui atteignent ce point. Vecteur nul ailleurs. | les deux |
| spawn(ServerLevel, double, double, EfScale) | TornadoView sur la tornade créée aux coordonnées x et z données. La cote est un plafond, pas une promesse. | serveur |
| Signature | Ce qu'elle rend | Unité |
|---|---|---|
| position() | Le point où l'axe rencontre le sol. | Vec3 |
| alive() | Faux dès que la tempête est finie, et plus rien ne bouge après. | boolean |
| rating() | La cote lue sur la rafale actuelle, qui monte puis redescend. | EfScale |
| peakRating() | La plus forte cote atteinte. C'est contre celle-là qu'une alerte s'écrit. | EfScale |
| stage() | Où elle en est: FORMING, TOUCHDOWN, MATURE, ROPING, GONE. | TornadoStage |
| ageTicks() | Ticks écoulés depuis la formation. | int |
| wind() | Vent tangentiel au rayon de vent maximal. | float, m/s |
| coreRadius() | Rayon de vent maximal. La condensation visible est un peu plus étroite. | float, blocs |
| funnelHeight() | Du sol à la base nuageuse, sous l'axe. | float, blocs |
| descent() | 0 quand l'entonnoir pend hors du nuage, 1 quand il est au sol. | float |
| groundLoad() | 0 propre, 1 enveloppée de ce qu'elle a pris. | float |
| tint() | Couleur que les débris lui ont donnée, empaquetée en 0xRRGGBB. | int |
| influenceBox() | La boîte où le vent porte. Dehors, la tempête ne fait rien du tout. | AABB |
Un addon complet
Celui-ci prévient les joueurs quand un entonnoir se forme et quand il est fini, et
il interdit à la tempête de toucher quoi que ce soit au-dessus de l'altitude 200. C'est un fichier
et il compile tel quel, à condition d'avoir fabric-api et le jar de
Vortex Dread sur le classpath.
package example.stormalarm;
import net.fabricmc.api.ModInitializer;
import net.minecraft.network.chat.Component;
import net.minecraft.server.level.ServerLevel;
import oas.dreyka.vortexdread.api.TornadoView;
import oas.dreyka.vortexdread.api.event.BlockTakenCallback;
import oas.dreyka.vortexdread.api.event.TornadoLifeCallback;
public class StormAlarm implements ModInitializer {
@Override
public void onInitialize() {
TornadoLifeCallback.EVENT.register(new TornadoLifeCallback() {
@Override
public void onFormed(ServerLevel level, TornadoView tornado) {
announce(level, "Entonnoir en formation, " + tornado.peakRating() + " attendue");
}
@Override
public void onTouchdown(ServerLevel level, TornadoView tornado) {
announce(level, "Contact au sol, coeur de " + (int) tornado.coreRadius() + " blocs");
}
@Override
public void onGone(ServerLevel level, TornadoView tornado) {
announce(level, "Fin d'alerte");
}
});
// Rien ne sort du sol au-dessus de 200, quelle que soit la force de la tempete.
BlockTakenCallback.EVENT.register((level, tornado, pos, state) -> pos.getY() < 200);
}
private static void announce(ServerLevel level, String line) {
level.players().forEach(player -> player.sendSystemMessage(Component.literal(line)));
}
}
Les deux méthodes onTouchdown et
onGone ont un corps par défaut vide, donc une classe qui ne veut que la
naissance n'écrit que onFormed.
BlockTakenCallback n'a qu'une méthode et accepte donc une lambda. Rendre
faux laisse le bloc en place, et aucun autre écouteur ne peut revenir dessus. La vue passée aux
deux événements est vivante: la lire dans le même tick va, la garder après la mort de la tempête
ne va pas, puisqu'elle répond faux à alive() à partir de là.
Quatre petites garanties qui évitent à un addon de deviner. Rien ne renvoie jamais
null: tornadoes rend une liste vide sur un niveau sans tempête, et
nearest rend un Optional vide. Une tornade est
une seule instance de TornadoView pendant toute sa vie, comparaison
d'identité comprise, donc un compteur rangé dans une map reste d'un seul morceau à travers mille
rappels. Les deux événements partent sur le fil du serveur, et les trois appels de lecture
répondent du côté qui demande, ce qui permet à une surcouche cliente d'utiliser le même code
qu'une règle serveur. Enfin, BlockTakenCallback reçoit la tornade, la
position et le bloc qui était là, jamais une cause: la cause est la tempête, et un addon qui veut
distinguer les dégâts du vent de ceux des débris lit le type de dégât du côté des entités.
Sans écrire une ligne de Java
Une grande partie de ce qu'un addon ferait se règle depuis
config/oas/vortexdread.json, côté serveur, quarante-deux options réparties
en cinq groupes. Une carte où la tempête ne casse rien, ça tient en une ligne
(breakBlocks à faux). Une carte où seul l'opérateur décide quand une
tornade arrive, c'est naturalTornadoes à faux plus la commande
/vortex spawn ef4. Les valeurs sont bornées et corrigées plutôt que
refusées, donc un chiffre absurde donne une partie jouable et une ligne dans le journal.
Tout le reste est de l'interne. Les paquets
storm, tornado (hors
TornadoStage), wind (hors
EfScale), damage,
debris, entity,
compute, client,
config et mixin n'ont aucune promesse de
stabilité. Appeler ce qui s'y trouve marche aujourd'hui et cassera à une version près, sans que
ce soit traité comme une régression ni corrigé pour vous. Si quelque chose vous manque dans la
surface publique, demandez-le sur la page Issues plutôt que de contourner:
github.com/Dreyka-Oas/VortexDread/issues.
Licence. Vortex Dread est sous licence MIT. Vous pouvez le reprendre, le modifier, le redistribuer et publier un addon commercial dessus sans rien demander. La seule obligation est de conserver l'avis de copyright et le texte de la licence avec toute copie substantielle du code. Une mention dans la description de votre addon fait plaisir et n'est jamais une condition. Le texte de la licence