LethalBreed manuel de siège Champ de distance Manuel English

Pathfinding par flow field

Les zombies ne suivent pas un A* classique calculé par entité : un flow field (carte de coût partagée) est recalculé pour toute une dimension, et chacun lit localement la direction descendante. Le champ encode aussi s'il faut casser un bloc ou construire un pont, et il ferme les colonnes où l'eau monte jusqu'au plan des joueurs.

Recalcul et étendue

Le champ est recalculé tous les 10 ticks (0.5s), hors thread serveur (pool dédié), sur une grille bornée à 192 blocs plus 24 blocs de marge autour des sujets sous surveillance.

GPU avec repli CPU

Si OpenCL est disponible, le GPU est utilisé automatiquement (useGpu est un kill-switch : passer à false force le CPU, true ne force pas le GPU s'il n'y en a pas). Toute erreur GPU retombe silencieusement sur le CPU. Il n'existe aucun seuil de taille : un GPU présent prend tous les calculs, quelle que soit la grille. De toute façon, le plus petit champ que le jeu construise fait déjà 49×49 (un joueur seul plus la marge de 24 blocs par défaut).

Le CPU utilise un Bellman-Ford parallèle multi-cœurs (cœurs disponibles − 2 threads), pas un Dijkstra mono-cœur : un serveur dédié headless sans GPU tourne donc identiquement, juste sur plusieurs cœurs plutôt qu'un seul.

Le GPU et le CPU sont conçus pour rester numériquement identiques : mêmes poids d'arête (orthogonal/diagonal, configurables) et mêmes pénalités casser/construire (le kernel OpenCL lit le même tableau de coût que le solveur CPU). Activer ou désactiver le GPU ne devrait donc pas modifier les trajets empruntés.

Casse de blocs taille-aware

La hauteur de casse pour avancer est calculée depuis la vraie hauteur de la boîte de collision du zombie (arrondi supérieur, plancher 1, plafond 4 blocs) : un corps agrandi par la variation aléatoire casse donc plus haut qu'un gabarit normal.

À portée de bras, les mains s'arrêtent

Avant de choisir entre grimper, descendre et casser, le pilote pose une question plus simple : la cible est-elle déjà atteignable. Trois mesures y répondent ensemble. Moins de meleeStopRange blocs à l'horizontale entre les deux centres, pas plus de meleeStopHeight bloc d'écart vertical, et une ligne de vue que le jeu accorde. Il en manque une, la réponse est non et l'activation continue son chemin.

Quand la réponse est oui, tout s'arrête là : le zombie mord comme n'importe quel monstre du jeu de base, sans casser un bloc, sans poser un pont, sans démarrer un pilier. La question est posée avant même le tri par niveau de détail, donc elle vaut pour le plus lointain des zombies suivis comme pour celui qui est sur toi.

C'est ce qui évite le spectacle d'un mur qui tombe derrière un joueur déjà au contact. Sans ce test, un zombie coincé contre un coin de la pièce pendant qu'il frappe compterait ses activations sans progrès, se déclarerait bloqué et se mettrait à creuser, alors que sa cible est à un bras. Les deux valeurs s'abaissent jusqu'à 0, ce qui rend le test impossible à satisfaire et rend aux zombies leur pioche à bout portant.

Une seule brèche par groupe

Dix zombies contre un mur, chacun tapant son propre bloc de devant, c'est dix blocs à 10 % et aucun trou. Le coordinateur de brèche existe pour ça. Il y en a un par dimension, et tout zombie sur le point de casser lui soumet trois choses : la colonne qu'il a devant lui, la case d'où il attaque, et la cible de chasse que cette brèche sert.

La réponse suit quatre cas. Un zombie déjà engagé sur sa colonne la garde, et l'inscrit comme point de ralliement plutôt que de se laisser tirer vers celle du voisin en plein travail. Sinon le coordinateur cherche les brèches ouvertes à moins de breachRadius blocs qui servent à peu près la même cible, deux rayons de tolérance sur la cible elle-même pour que deux murs sans rapport ne fusionnent pas. Si la plus proche est justement sa colonne, il la rafraîchit. Et si le groupe tient déjà ses maxConcurrentBreaches brèches, il adopte la plus proche : l'appelant le réoriente vers la case d'approche du premier casseur, si bien que la foule s'entonne sur un seul trou. Reste le cas où il y a de la place, et il ouvre la sienne.

Une brèche que personne ne travaille pendant breachGraceTicks est oubliée, ce qui libère le créneau, et celle dont la colonne ne bloque plus le passage disparaît aussi : le trou est fait, il n'y a plus rien à coordonner.

La concentration paie en vitesse. Le casseur compte les zombies distincts qui ont demandé ce bloc dans la fenêtre de grâce, et multiplie la progression par 1 + (casseurs − 1) × breakConcentrationPerBreaker, plafonné par breakConcentrationCap. Quatre zombies sur la même pierre vont 2,8 fois plus vite qu'un seul ; le plafond par défaut est atteint à six, et une horde entière n'ira jamais au-delà de quatre fois. Une porte en bois cède en quelques secondes, une pierre tient, et c'est toujours le même mur qui s'ouvre, pas dix éraflures côte à côte.

CombatMoveConfig : contact et brèche
ChampDescriptionDéfaut
meleeStopRangeDistance horizontale sous laquelle la cible compte comme atteignable, ligne de vue comprise. À 0, plus aucun arrêt : les zombies creusent au contact.2.0
meleeStopHeightÉcart vertical maximal pour que cet arrêt s'applique. Au-delà, la cible est en hauteur ou en contrebas, donc les manoeuvres de bloc reprennent.1.5
breachEnabledInterrupteur de la coordination. À false, chacun casse sa propre colonne, comme avant.true
breachRadiusRayon dans lequel une brèche existante est adoptée au lieu d'en ouvrir une autre. Plus large, plus la foule s'entonne loin.6.0
maxConcurrentBreachesBrèches ouvertes en même temps dans un groupe. 1 impose strictement un bloc à la fois.1
breachGraceTicksTicks sans travail avant qu'une brèche soit oubliée et son créneau rendu.20 (1s)
breakConcentrationPerBreakerGain de vitesse par casseur supplémentaire sur le même bloc. À 0, la vitesse ne dépend plus du nombre.0.6
breakConcentrationCapPlafond du multiplicateur, pour qu'un tas de zombies ne pulvérise pas la pierre.4.0

Escalade et descente

Escalade : impulsion de vitesse verticale réelle + pose d'un bloc support à chaque palier, jamais de téléportation. Trois conditions, pas deux : la cible doit être à au moins 2 blocs de hauteur et au plus 5 blocs à l'horizontale, le zombie doit être bloqué (deux activations consécutives sans progresser vers elle) et il doit être suffisamment proche pour être piloté finement. Un zombie qui se rapproche encore continue au sol. Plafonné à 24 blocs de hauteur totale. Le pilier pose toujours des blocs pour grimper (il se tient sur ce qu'il construit) ; il n'existe aucune variante d'escalade sans pose de bloc.

Descente : par ordre de préférence, marche ou chute courte sûre (jusqu'à 3 blocs) → escalier déjà taillé dans le terrain → puits vertical sûr → construction d'un escalier au-dessus du vide en dernier recours. En règle générale, il ne casse pas le dernier bloc surplombant un vide profond. Une exception : si la cible est à moins de 1.5 bloc à l'horizontale (donc quasiment sous ses pieds), il creuse quand même droit vers le bas pour la suivre dans le trou plutôt que de rester planté au-dessus.

Au pied d'un mur d'obsidienne, deux blocs de terre posés l'un sur l'autre par un zombie, et le zombie debout dessus.
Le début d'un pilier, pris au deuxième palier. Le bloc vient de nulle part, il est marqué, et il disparaît de lui-même au bout de 600 ticks.

L'eau qui barre la route

Le classifieur de cellules ouvre sur une question d'eau, avant même de chercher un appui dans la colonne. Il lit waterAvoidDepth blocs empilés à partir du plan de référence : deux par défaut, réglable de 1 à 8. Si tous sont de l'eau, source ou courant, la case devient infranchissable, et le solveur la traite dès lors comme un mur plein, à contourner comme n'importe quelle pierre. Un gué d'un seul bloc, lui, garde son coût ordinaire. La lave n'entre pas dans ce test : posée sur un sol dur, sa colonne reste passable au tarif normal.

Le plan de référence est la hauteur moyenne des joueurs que le mod peut prendre pour cible, arrondie au bloc où sont leurs pieds. Une seule tranche par dimension, partagée par toute la horde, et sans joueur ciblable il n'y a pas de champ du tout : un zombie lancé sur un villageois entre dans l'eau sans le moindre détour.

Ces deux blocs sont un chiffre livré, pas la taille de celui qui lit la case, et un champ calculé une fois pour toute la dimension ne peut de toute façon pas connaître la stature de chacun. Un grand gabarit se voit donc interdire une eau de deux blocs qu'il aurait passée la tête au sec.

Le détour a un angle mort, et il est large. Les deux cases lues sont celle des pieds des joueurs et celle juste au-dessus. Un étang à ras de sa berge a sa surface un bloc plus bas : les deux cases testées sont de l'air, la colonne repasse par les règles ordinaires, la recherche d'appui descend jusqu'à 4 blocs (flowVerticalTolerance) et trouve le fond. La route traverse le plan d'eau au lieu d'en faire le tour. Pour qu'un lac soit contourné, il faut que sa surface monte au niveau où les joueurs se tiennent : une cave inondée, un bassin retenu sur une hauteur, un joueur posté sur une île au milieu.

Rien de tout cela ne passe par le pathfinder du jeu. PathType et le malus de fluide restent tels quels, setCanFloat n'est jamais appelé. Un malus vaut pour l'entité entière et refuse un gué aussi sûrement qu'un lac, alors que la profondeur appartient au lieu et se juge case par case.

Le champ est relu à chaque réémission de trajet, quelle que soit la distance. Quand cette lecture échoue, la navigation repart en ligne droite vers la cible. Cela arrive faute de champ actif, parce que le zombie est sorti de la grille ou posé sur une case infranchissable, parce que sa case ne porte pas de direction, ou parce que la navigation du jeu ne sait pas atteindre le point de passage.

Le zombie déjà dans le lac est dans le deuxième cas : sa propre case est celle que le classifieur a fermée. Mais dès que sa tête passe sous l'eau il quitte la logique de trajet avant même de la consulter, et repart vers la berge, ce que décrit Mood, fuite et survie. Quand aucun contour n'existe, la horde n'attend pas au bord : le champ n'ayant plus rien à lui proposer, elle marche droit et se noie.

Un effet de bord vaut d'être connu. Le test de l'eau passe avant la branche du mur cassable, donc deux blocs immergés empilés au plan de référence, escaliers, dalles ou barrières, font contourner une barrière que le même zombie aurait cassée au sec.

CombatMoveConfig : champs liés à l'eau
ChampDescriptionDéfaut
cannotSwimInterrupteur des deux moitiés : l'eau profonde devient un mur pour le champ, et un zombie la tête sous l'eau se noie. À false, l'eau redevient ordinaire pour tout le monde.true
waterAvoidDepthNombre de blocs d'eau empilés à partir du plan de référence qui rendent la colonne infranchissable (borné de 1 à 8).2
drownGraceTicksAir disponible avant les premiers dégâts de noyade (borné de 1 à 280, soit 14 s au maximum).40 (2s)
drownDamageCran de dégâts ajouté à chaque activation passée le sursis. À 0, le zombie reste sous l'eau indéfiniment sans jamais mourir.4.0
floatInWaterNage en surface, le comportement inverse. Neutralisé tant que cannotSwim est actif, avec tout son bloc de réglages de vitesse et de plongée.true

Ces cinq champs se règlent en jeu comme les autres, par les trois interfaces. Éteindre cannotSwim en cours de partie rend la nage à tout zombie qui a une cible, dès son activation suivante, parce que le pilote du mod relit l'option à chaque passage ; celui qui n'a pas de cible reste sur le pilotage du jeu. Ce qui ne revient qu'aux zombies apparus ensuite, c'est le ballottement de surface du jeu de base, accroché une seule fois à la création de l'entité. Dans le fichier JSON, waterAvoidDepth rejoint floatInWater et le bloc water* sous Water ; les trois noms qui ne portent ni water ni float tombent dans Misc.

Le bond et la prise

Un zombie qui poursuit une cible peut se jeter dessus au lieu de la rejoindre au pas. La tentative n'a lieu qu'une activation sur leapCooldownActivations, et seulement si le tirage passe leapChance. La cible doit être dans la fenêtre leapMinRange à leapMaxRange à l'horizontale, et pas plus de leapMaxVerticalDiff blocs au-dessus ou en dessous. Un bond trop court se terminerait par un pas ordinaire, un bond trop long tomberait à côté.

Avant de partir, le zombie sonde le sol devant lui, leapLandingScanDist blocs en avant et leapLandingScanDepth blocs sous ses pieds. S'il n'y trouve rien de dur, il ne saute pas. C'est ce qui l'empêche de se lancer dans un ravin pour rien, sans lui interdire le saut au-dessus d'une tranchée d'un bloc.

La suite est nouvelle. Quand un bond se termine sur sa proie plutôt qu'à côté, le zombie s'accroche. Il cesse de naviguer, se pose à l'endroit qu'occuperait un passager, et mord pendant une durée tirée entre leapClingMinSeconds et leapClingMaxSeconds. La prise vaut leapClingDamage point de vie par seconde, ce qui est peu par choix : la plus longue coûte cinq points sur les vingt d'un joueur sans armure, et une morsure ordinaire fait plus mal. Elle sert à immobiliser, pas à achever.

Ce qui compte comme un atterrissage sur la proie tient à deux mesures. La distance horizontale entre les deux centres doit rester sous leapClingReachSq, exprimée au carré pour éviter une racine par test, et le zombie ne peut pas être plus de leapClingMaxDropBelow bloc sous elle. Il est censé retomber dessus, pas tendre le bras vers le haut depuis le sol.

Une fois accroché, le zombie est reposé à chaque tick sur la tête de sa proie. Si le plafond ne laisse pas la place à un corps de 1,95 bloc, il se glisse dans l'empreinte de la proie elle-même, au niveau de ses pieds, ce qui évite d'avoir les yeux dans la roche et d'étouffer à raison d'un point par tick. Quand aucune des deux places n'est libre, il lâche. Il lâche aussi si sa victime meurt, disparaît ou change de dimension, et une sauvegarde qui tombe pendant la morsure ne laisse rien derrière elle.

Les dégâts sont mis de côté avant d'être remis. Le jeu de base avale toute blessure qui ne dépasse pas la précédente dans les dix ticks qui suivent, donc un quart de coeur régulier ne serait compté qu'une fois sur deux. Le mod accumule ce qu'il doit et le donne d'un coup dès que la fenêtre se rouvre. La poussée qui accompagne normalement un coup de monstre est annulée au passage : le jeu répond à une direction nulle par une direction au hasard et un peu de hauteur, ce qui ferait sauter la proie deux fois par seconde sous un zombie censé la tenir.

Le même zombie ne peut pas recommencer avant leapClingCooldownActivations activations, six secondes au rythme par défaut, soit plus long que la plus longue prise. Un zombie seul ne cloue donc jamais un joueur indéfiniment. À plusieurs, c'est une autre histoire.

Côté image, un zombie accroché n'est pas debout : les bras se referment vers le bas, les genoux remontent, la tête est penchée dans la proie et hoche au rythme des morsures. Deux ondes de vitesses différentes portent ce mouvement, l'une pour le hochement et l'autre pour le roulis, si bien que la boucle ne se voit pas sur cinq secondes. L'information vient du serveur par un seul booléen synchronisé, et deux zombies accrochés au même moment ne sont jamais en phase, chacun comptant depuis sa propre naissance.

LeapConfig : le bond
ChampDescriptionDéfaut
leapEnabledInterrupteur du bond. À false, les zombies rejoignent toujours leur cible au pas, et la prise ne peut plus se déclencher non plus.true
leapCooldownActivationsActivations entre deux tentatives, pour un zombie donné.12
leapChanceProbabilité de sauter sur une activation éligible.0.4
leapMinRangeDistance horizontale en dessous de laquelle le zombie est déjà trop près pour sauter.2.5
leapMaxRangeDistance horizontale au-delà de laquelle le bond tomberait court.8.0
leapHorizontalSpeedVitesse horizontale imprimée au départ.0.55
leapUpwardImpulsion verticale du départ.0.42
leapMaxVerticalDiffÉcart vertical maximal, en valeur absolue, pour que le bond parte.3.0
leapLandingScanDistBlocs sondés en avant à la recherche d'un sol avant de s'engager.3
leapLandingScanDepthBlocs sondés vers le bas par ce même test.3
LeapConfig : la prise
ChampDescriptionDéfaut
leapClingEnabledInterrupteur de la prise. À false, un bond qui touche sa cible rebondit comme avant.true
leapClingMinSecondsDurée la plus courte qu'une prise puisse tirer.2.0
leapClingMaxSecondsDurée la plus longue. Les deux champs sont triés avant le tirage, donc écrire un maximum sous le minimum inverse simplement les bornes au lieu de casser la prise.5.0
leapClingDamagePoints de vie retirés par seconde de prise. À 0, le zombie tient sa proie sans lui faire de mal.1.0
leapClingReachSqDistance horizontale au carré en dessous de laquelle l'atterrissage compte comme une touche.1.0
leapClingMaxDropBelowBlocs sous la proie où le zombie peut encore s'accrocher.1.0
leapClingCooldownActivationsActivations avant qu'un même zombie puisse reprendre.24

Les dix-sept champs vivent sous Leap dans config/oas/lethalbreed.json et se règlent aussi par les trois interfaces. Éteindre leapClingEnabled pendant une prise n'y met pas fin sur-le-champ : celle qui court va au bout de sa durée, aucune autre ne commence.