API pour extensions
Un autre mod atteint LethalBreed par un seul point d'entrée et une poignée de
petites classes et paquets, tous sous oas.dreyka.lethalbreed.api. Rien ici n'exige de
mixin : une extension déclare des options, écoute des événements, lit ce que le mod croit savoir
à l'instant, et ajoute ses propres zombies spéciaux, par des appels qui restent stables d'une
version à l'autre, comme le javadoc de ce package le promet déjà.
| Classe | Sert à |
|---|---|
| LethalBreedAddon | le point d'entrée, appelé deux fois au démarrage |
| LethalBreedApi | déclarer un namespace d'IA, écouter un changement de phase |
| LethalBreedConfigApi | enregistrer ses propres options dans le même fichier de config |
| LethalBreedState | lecture seule : phase, nombre suivi, variant d'un zombie, meutes |
| api.event.* | six événements Fabric : adoption d'un zombie, contamination, humeur, entrée en meute, épuration au spawn, ciblage |
| api.variant.* | ajouter un zombie spécial, passif, actif ou à la mort |
Le point d'entrée
LethalBreedAddon est une interface fonctionnelle avec
une méthode obligatoire et une facultative, appelées à deux moments distincts du démarrage.
Déclarer la classe qui l'implémente sous le point d'entrée
lethalbreed:addon de son propre
fabric.mod.json :
fabric.mod.json"entrypoints": {
"lethalbreed:addon": ["com.example.mymod.MyAddon"]
}
onLethalBreedReady() tourne avant que le fichier de
config soit lu et avant que quoi que ce soit lise un registre : c'est là que va
LethalBreedConfigApi.register, avec tout namespace d'IA autorisé et
tout écouteur de phase, assez tôt pour être connu avant le premier zombie chargé.
onConfigLoaded() est une méthode par défaut vide, appelée une fois le
fichier réellement lu et borné ; ne la surcharger que si une de ses décisions dépend de la valeur
réelle d'une option plutôt que de son défaut.
Une extension complète
Tout ce qui suit compile contre le package tel qu'il est : un namespace autorisé, une option enregistrée à côté de celles du mod, un écouteur de phase, un variant spécial, et un écouteur d'événement qui épargne un boss de l'épuration au spawn du mod.
MyAddon.javapackage com.example.mymod;
import oas.dreyka.lethalbreed.api.LethalBreedAddon;
import oas.dreyka.lethalbreed.api.LethalBreedApi;
import oas.dreyka.lethalbreed.api.LethalBreedConfigApi;
import oas.dreyka.lethalbreed.api.OptionBounds;
import oas.dreyka.lethalbreed.api.event.SpawnCullCallback;
import oas.dreyka.lethalbreed.api.variant.SpecialVariant;
import oas.dreyka.lethalbreed.api.variant.SpecialVariantRegistry;
public final class MyAddon implements LethalBreedAddon {
@Override
public void onLethalBreedReady() {
LethalBreedApi.allowAiNamespace("com.example.mymod.");
LethalBreedConfigApi.register("mymod", "MyMod", MyOptions.class,
new OptionBounds("mymodSpawnChance", 0.0, 1.0));
LethalBreedApi.onPhaseChanged((from, to) ->
MyMod.LOGGER.info("phase moved from {} to {}", from, to));
SpecialVariantRegistry.register(SpecialVariant.of("mymod:frostbite",
SpecialVariant.Kind.ACTIVE, 10, new FrostbiteBehavior()));
SpawnCullCallback.EVENT.register((entity, phase, proposed) ->
entity.getType() != MyEntities.SWAMP_BOSS && proposed);
}
}
MyOptions.javapackage com.example.mymod;
public final class MyOptions {
public static double mymodSpawnChance = 0.2;
}
Chaque champ de la classe support doit commencer par le préfixe revendiqué,
vérifié plutôt que présumé : un champ qui l'aurait raté atterrirait sinon dans l'onglet Misc à
côté de l'option d'un inconnu. Le préfixe lui-même fait au moins trois caractères, et en prendre
un déjà pris, ou un qui capturerait une option déjà existante, lève une
IllegalArgumentException avant que quoi que ce soit ne soit écrit.
Lire ce que le mod sait
LethalBreedState est la moitié lecture seule de la
surface : rien ici ne change quoi que ce soit, et rien n'exige d'écouteur enregistré au préalable.
Tout ce qui traverse la frontière est un type vanilla, un primitif, ou
LethalBreedState.Pack, si bien qu'une extension compilée contre elle
continue de compiler quand les classes d'état interne du mod changent de forme. Thread serveur
uniquement, comme le reste du mod.
lire l'étatint phase = LethalBreedState.phase();
int tracked = LethalBreedState.trackedZombieCount();
SpecialVariant variant = LethalBreedState.variantOf(zombie);
List<LethalBreedState.Pack> packs = LethalBreedState.packs(level);
| Méthode | Répond |
|---|---|
| phase() | la progression de la nuit, zéro sur un monde neuf, sans plafond |
| trackedZombieCount() | les zombies pilotés par le mod, toutes dimensions confondues |
| variantOf(Zombie) | le variant spécial qu'il porte, ou null pour un zombie ordinaire |
| packs(ServerLevel) | chaque meute de cette dimension, en instantané pris à l'appel |
Événements
Six événements Fabric, chacun avec une seule question et une réponse chaînée :
un écouteur reçoit le verdict tel qu'il se tient et renvoie celui qu'il veut, si bien qu'un
écouteur sans avis sur un cas donné renvoie proposed et ne change rien.
Un écouteur qui lève une exception est signalé une fois, dans le journal serveur, puis traité
comme s'il s'était abstenu ; le mod ne s'arrête pas pour une extension cassée.
| Événement | Méthode | Décide |
|---|---|---|
| ContaminationCallback | boolean allowContamination(LivingEntity, boolean) | si une créature attrape la contamination |
| MoodCallback | MoodState mood(Zombie, MoodState) | laquelle des cinq humeurs un zombie tient pour une activation |
| PackJoinCallback | boolean allowJoin(Zombie, long, boolean) | si un zombie est admis dans une meute, id de meute 0 (PackJoinCallback.FOUNDING) signifie une fondation plutôt qu'une adhésion |
| SpawnCullCallback | boolean allowCull(Entity, int, boolean) | si un hostile est épuré (retiré) ; renvoyer true pour l'épurer, false pour qu'il survive |
| TargetCandidateCallback | boolean isValidPrey(Mob, LivingEntity, boolean) | si une créature est une proie pour un zombie |
| ZombieAdoptCallback | boolean allowAdopt(Zombie, boolean) | si un zombie est repris par le cerveau du mod ; renvoyer false le laisse entièrement au jeu de base |
ZombieAdoptCallback est la sortie de secours d'une extension qui a son propre
zombie. Tout ce qui charge en étant un Zombie est repris :
husk, noyé, villageois zombie, et la sous-classe qu'un autre mod aura écrite. C'est le bon
défaut, puisque la horde entière est ce à quoi le mod sert, mais une créature écrite pour faire
autre chose se retrouve les goals retirés, la navigation pilotée par le flow field et une meute
qui l'emmène ailleurs. Refuser l'adoption est total : le zombie garde son IA, n'entre dans
aucune meute, n'est jamais activé. Il reste un hostile dans un monde où le mod épure les
hostiles, donc ce refus s'accompagne presque toujours d'un
SpawnCullCallback. La question est posée à chaque chargement et pas
une seule fois dans la vie du monde, parce que l'extension qui l'a revendiqué peut fort bien
avoir été absente la fois précédente.
TargetCandidateCallback tourne sur la phase large. Plusieurs centaines de
fois par tick sur un serveur chargé, dans le balayage qui chiffre déjà le coût d'une horde. Lire
un attachment, comparer un type d'entité, renvoyer : tout ce qui alloue, parcourt un chunk ou
prend un verrou va ailleurs. Un datapack qui veut seulement épargner une liste fixe de types
d'entités de l'épuration au spawn veut plutôt le tag
lethalbreed:spawn_protected, sans une ligne de code.
Variants spéciaux
Les huit variants livrés entrent par la même porte qu'un variant d'extension : le
tirage, la sauvegarde et le tick lisent tous
SpecialVariantRegistry et ne savent pas distinguer un variant de
l'autre. SpecialVariant.of(id, kind, weight, behavior) est le chemin
court, disponible dès la phase zéro à un poids fixe ; le constructeur complet du record accepte à
la place une phase de déblocage et un poids lus en direct, pour un variant dont la disponibilité
suit une option de config plutôt qu'une constante.
Un id namespacé ("mymod:frostbite") évite la collision
entre deux mods ; les huit livrés sont des mots nus parce que les sauvegardes les tiennent déjà
ainsi. Enregistrer depuis onLethalBreedReady() et ne jamais
désenregistrer : un variant disparu alors qu'un monde tient encore des zombies portant son id
transformerait ces zombies en entrée de sauvegarde qui ne résout plus vers rien. Renommer un
variant garde l'ancien id lisible via
SpecialVariantRegistry.alias(oldId, id).
| Méthode | Appelée |
|---|---|
| onSpawn(Zombie) | PASSIVE, une fois, quand le zombie finit son spawn |
| onUnassign(Zombie) | quand le variant est retiré, pour défaire onSpawn |
| tick(VariantContext) | ACTIVE, sur l'activation propre au zombie, décalée |
| onDeath(Zombie, ServerLevel) | DEATH, pendant que le zombie est encore dans le monde |
Les quatre méthodes ne font rien par défaut, si bien qu'une extension ne
surcharge que celle qu'appelle son Kind et laisse les autres telles
quelles. Une seule instance de VariantBehavior est partagée par tous les
zombies portant ce variant : l'état propre à un zombie va sur le zombie lui-même, via un data
attachment Fabric propre à l'extension, pas sur un champ du behavior.
onUnassign couvre le variant qu'on retire d'un zombie, pas
l'extension qu'on retire du dossier. Un jar sorti de mods/ n'appelle
plus rien : ce que onSpawn a posé de permanent reste dans la NBT des
zombies déjà nés, et le mod les charge sans rien dire puisque c'est un effet ordinaire. Poser un
effet sans durée est donc une décision qui survit à l'extension, et une extension qui veut
pouvoir disparaître proprement pose des durées finies qu'elle renouvelle dans
tick.
Où signaler ce qui casse
Le carnet de rapports du dépôt, github.com/Dreyka-Oas/LethalBreed/issues, avec un compte GitHub. Le mod est sous licence MIT, détaillée sur Installation : écrire une extension contre la surface ci-dessus ne demande aucune permission au-delà, c'est précisément à cela qu'elle sert.