LethalBreed manuel de siège API pour greffons Manuel English

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à.

La surface, et le rôle de chaque classe
ClasseSert à
LethalBreedAddonle point d'entrée, appelé deux fois au démarrage
LethalBreedApidéclarer un namespace d'IA, écouter un changement de phase
LethalBreedConfigApienregistrer ses propres options dans le même fichier de config
LethalBreedStatelecture 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);
LethalBreedState, méthode par méthode
MéthodeRé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.

Les six événements, et ce que chacun décide
ÉvénementMéthodeDécide
ContaminationCallbackboolean allowContamination(LivingEntity, boolean)si une créature attrape la contamination
MoodCallbackMoodState mood(Zombie, MoodState)laquelle des cinq humeurs un zombie tient pour une activation
PackJoinCallbackboolean 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
SpawnCullCallbackboolean allowCull(Entity, int, boolean)si un hostile est épuré (retiré) ; renvoyer true pour l'épurer, false pour qu'il survive
TargetCandidateCallbackboolean isValidPrey(Mob, LivingEntity, boolean)si une créature est une proie pour un zombie
ZombieAdoptCallbackboolean 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).

VariantBehavior, une méthode par nature
MéthodeAppelé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.