Zum Hauptinhalt springen

FreeMinecraftModels API und Entwicklerhandbuch

Die generierte Java-Referenz enthält durchsuchbare Klassen, Methoden und Signaturen sowie die Quellversion und den Commit jedes Moduls.

FreeMinecraftModels ist sowohl ein eigenständiges Plugin als auch eine API-Oberfläche für andere Plugins.

Die gemeinsame Einrichtung von Abhängigkeiten und Laufzeit beschreibt die Java-API-Übersicht. Das Abhängigkeitsbeispiel verwendet die veröffentlichte Version 2.12.9; der aktuelle Quellcode ist 2.12.9. Stimme deine Abhängigkeit auf das installierte Plugin ab.

Maven-Repository​

<repository>
<id>magmaguy-repo-releases</id>
<name>MagmaGuy's Repository</name>
<url>https://repo.magmaguy.com/releases</url>
</repository>
<repository>
<id>magmaguy-repo-snapshots</id>
<name>MagmaGuy's Snapshot Repository</name>
<url>https://repo.magmaguy.com/snapshots</url>
</repository>

Abhängigkeit​

<dependency>
<groupId>com.magmaguy</groupId>
<artifactId>FreeMinecraftModels</artifactId>
<version>2.12.9</version>
<scope>provided</scope>
</dependency>

Verwende es als compileOnly/provided. Schließe das Plugin nicht in deine eigene JAR ein (kein Shading).

Zentrale Einstiegspunkte​

  • ModeledEntityManager.modelExists(String)
  • ModeledEntityManager.reload()
  • ModeledEntityManager.getAllEntities()
  • ModeledEntityManager.getDynamicEntities()
  • ModeledEntityManager.propEntities()
  • DisguiseAPI — Spieler als geladene Modelle verkleiden / Verkleidung entfernen
  • LocationAPI — Dungeon-Detektoren und Schutzanbieter registrieren (versorgt Lua em.location.*-Prädikate)
  • ScriptedItemAPI — Drittanbieter-ItemStacks mit Identitäts- und Anzeigedaten eines in FMM definierten Items versehen
  • ItemConstructionSnapshot — Item- und Anzeigemodelldaten auf dem Server-Thread erfassen, um Items in einem anderen Thread zu erstellen

ModeledEntityManager.getAllEntities(), getDynamicEntities() und propEntities() liefern Momentaufnahmen zurück. Das Verändern der zurückgegebenen Menge oder Map ändert FMMs aktive Registries nicht. Verwende modelExists(String) für eine Existenzprüfung, statt eine vollständige Registry vorzuhalten und wiederholt zu kopieren.

Zentrale Laufzeittypen​

  • ModeledEntity
  • StaticEntity
  • DynamicEntity
  • PropEntity

Entitäten erstellen​

StaticEntity preview = StaticEntity.create("example_model", location);
DynamicEntity mobModel = DynamicEntity.create("example_model", livingEntity);
DynamicEntity mount = DynamicEntity.createWithInvisibility("example_model", livingEntity);
PropEntity prop = PropEntity.spawnPropEntity("example_model", location);

Alle Erstellungspfade geben null zurück, wenn die angeforderte Modell-ID nicht geladen ist.

PropEntity.spawnPropEntity gibt zusätzlich null zurück, wenn auf dem Zielblock bereits ein Prop desselben Modells geladen ist — das Duplikat wird abgelehnt statt gestapelt, und eine Warnung [FMM Props] Prevented duplicate prop spawn ... wird protokolliert. Prüfe den Rückgabewert immer auf null; nur eine nicht-null-Rückgabe bestätigt, dass der Prop tatsächlich erstellt wurde.

createWithInvisibility ist eine Variante, die einen Unsichtbarkeitstrank anwendet, anstatt die Entität vor Clients zu verbergen. Dadurch bleibt die Entität clientseitig getrackt, was für die Fahrzeugsteuerung erforderlich ist (intern von /fmm mount verwendet) und es den Display-Bones des Modells auf Java-Clients ermöglicht, auf der Entität zu reiten; siehe displayBonesRideTheirEntity.

Nützliche Laufzeit-Methoden​

  • ModeledEntity#setDisplayName(String) — setzt das vom Modell verwaltete Namensschild
  • ModeledEntity#setDisplayNameVisible(boolean) — steuert dessen Sichtbarkeit
  • ModeledEntity#setLeftClickCallback(...)
  • ModeledEntity#setRightClickCallback(...)
  • ModeledEntity#setHitboxContactCallback(...)
  • ModeledEntity#setModeledEntityHitByProjectileCallback(...)
  • ModeledEntity#playAnimation(String, boolean blend, boolean loop) -- gibt false zurück, wenn der Name weder zu einem eingebauten Zustand noch zu einer Animation im Modell passt, oder nachdem death angefordert wurde. Bei einer benutzerdefinierten Animation spielt blend = true sie auf der Aktionsebene über idle oder walk ab, und false lässt sie die gesamte Pose übernehmen; loop gilt nur für benutzerdefinierte Animationen. Siehe Animationen überblenden
  • ModeledEntity#stopCurrentAnimations() -- beendet benutzerdefinierte Animationen und Angriffe und springt zu idle; tut nichts, sobald der Tod begonnen hat
  • ModeledEntity#hasAnimation(String)
  • ModeledEntity#damage(double) / damage(Entity damager, double) / damage(Entity damager) / damage(Projectile) -- läuft über die DamageableComponent der Entität
  • ModeledEntity#attack(LivingEntity) / attack(LivingEntity, double damage)
  • ModeledEntity#teleport(Location, boolean teleportUnderlyingEntity)
  • ModeledEntity#setUnderlyingEntity(Entity) / ModeledEntity.getModeledEntity(Entity) -- statische Rückwärtssuche von einer Bukkit-Entität zum daran angehängten Modell, oder null
  • ModeledEntity#showUnderlyingEntity(Player) / hideUnderlyingEntity(Player) -- Sichtbarkeit der zugrunde liegenden Vanilla-Entität pro Spieler
  • ModeledEntity#getEntityID() -- gibt den Modell-ID-String zurück
  • ModeledEntity#getModelInstanceId() -- UUID pro Instanz, stabil über die Lebensdauer des Modells
  • ModeledEntity#isRemoved() / isDying() -- Lebenszyklus-Flags
  • ModeledEntity#getLocation() -- gibt die aktuelle Location zurück
  • ModeledEntity#getSpawnLocation() -- gibt die Location zurück, an der das Modell erstellt wurde
  • ModeledEntity#getWorld() -- gibt die World zurück
  • ModeledEntity#getSkeleton() / getSkeletonBlueprint() / getMountPointManager() -- Zugriff auf die Laufzeitstruktur
  • ModeledEntity#getInteractionComponent() / getHitboxComponent() / getDamageableComponent() / getAnimationComponent() -- die Komponentenobjekte hinter den obigen Komfortmethoden
  • ModeledEntity.getLoadedModeledEntities() -- statische Live-Menge aller geladenen modellierten Entitäten
  • ModeledEntity#getViewers() -- gibt das HashSet<UUID> der Spieler zurück, die die Entität sehen können
  • ModeledEntity#getNametagBones() -- gibt List<Bone> der Nametag-Bones zurück (nützlich zum Platzieren zusätzlichen Texts)
  • ModeledEntity#getScaleModifier() / setScaleModifier(double)
  • ModeledEntity#removeWithDeathAnimation() -- entfernt mit der Todesanimation (falls vorhanden)
  • ModeledEntity#removeWithMinimizedAnimation() -- entfernt mit einer schrumpfenden Animation
  • ModeledEntity#remove() -- entfernt die Entität und alle Bones sofort
  • ModeledEntity#setTintColor(Color) / getTintColor() -- wendet eine dauerhafte Färbung über den Lederrüstung-Färbekanal an. Schadensblitze überschreiben die Färbung kurzzeitig und blenden dann zurück. Übergib null, um zu löschen.
  • ModeledEntity#setViewDistanceOverride(int) / getEffectiveViewDistance() -- überschreibt DefaultConfig.maxModelViewDistance für eine einzelne Entität. Übergib -1, um zum pluginweiten Standard zurückzukehren.
  • DynamicEntity#setSyncMovement(boolean)
  • DynamicEntity#isDamagesOnContact() / setDamagesOnContact(boolean) -- steuert, ob die Entität Spielern durch Hitbox-Kontakt Schaden zufügt
  • DynamicEntity.isDynamicEntity(Entity) / DynamicEntity.getDynamicEntity(Entity) -- statische Suchen ausgehend von einer Bukkit-Entität
  • DynamicEntity#getBodyLocation() -- die Position der Entität mit dem Yaw des Modells. Ist weightedTurning an, folgt dieser Yaw dem Körper-Yaw des Mobs mit Drehträgheit und kann dem Mob daher hinterherhinken, während sich das Modell dreht. getLocation() gibt dieselbe Position zurück, solange die Bewegungssynchronisierung an ist
  • Bone#getBoneLocation()

Licht, Neigung, Zentrierung und Klicks​

  • ModeledEntity#setBrightnessOverride(int blockLight, int skyLight) -- beleuchtet jeden Anzeige-Bone mit einer festen Stufe, jeweils 0 bis 15, statt mit dem Licht an der Position des Bones. 15, 15 ist volle Helligkeit. Betrachter erhalten die Änderung mit der nächsten Bone-Aktualisierung. Nur Java-Anzeige-Bones: Bedrock- und Armor-Stand-Bones behalten ihr Weltlicht
  • ModeledEntity#clearBrightnessOverride() / hasBrightnessOverride() / getBrightnessOverrideBlockLight() / getBrightnessOverrideSkyLight() -- heben das feste Licht auf oder lesen es. Die Getter geben ohne Überschreibung -1 zurück
  • DynamicEntity#setTravelOrientation(TravelOrientation) / getTravelOrientation() -- neigt das Modell mit seiner Bewegung. TravelOrientation ist ein Record aus einem Style (UPRIGHT, TOP_FIRST, FRONT_FIRST), startSpeed, fullSpeed, maxTilt und turnSpeed; TravelOrientation.UPRIGHT ist der Standard, und null stellt ihn wieder her. Siehe Bewegungsausrichtung
  • ModeledEntity#setBodyTilt(Quaterniondc) / getBodyTilt() -- neigt das gesamte Modell durch eine Rotation im Weltraum um die Mitte seiner Würfel, nach seiner Blickrichtung und seinen Animationen. null stellt es aufrecht. Eine Bewegungsausrichtung außer UPRIGHT setzt dies in jedem Tick; bei einem Modell mit UPRIGHT bleibt eine von dir gesetzte Neigung bestehen
  • ModeledEntity#setCenteredOnBody(boolean) / isCenteredOnBody() / getBodyOffset() -- zeichnet das Modell mit der Mitte seiner Würfel auf der Mitte seiner Entität. Siehe Zentrierung auf den Körper
  • DynamicEntity#setClickSurface(boolean) / isClickSurface() -- bei true erhalten Clients eine unsichtbare Interaction-Entität über dem Modell, sodass Spieler das Modell anklicken, das sie sehen, statt die eigene Box des Körpers. Sie übernimmt die Größe des hitbox-Bones des Modells oder, falls es keinen hat, die eigenen Grenzen des Modells und wirkt innerhalb von zwei Ticks. Standardmäßig aus, weil eine Box, die ein langes oder gedrehtes Modell abdeckt, auch Klicks in ihren leeren Ecken abfängt
  • DynamicEntity#isSteerableMount() / hasSteeringRider() / seatSteeringRider(Player) -- ein Modell, das von einem gesattelten Pferd getragen wird, ist ein lenkbares Reittier. seatSteeringRider setzt den Spieler als lenkenden Reiter auf das Pferd selbst, wenn es niemand reitet, und gibt zurück, ob er es nun reitet. Ein Rechtsklick auf ein solches Modell tut das standardmäßig
  • DynamicEntity#liftSaddleTo(Bone) -- skaliert den Mob so, dass sein Sattel in der Ruhepose des Modells auf dem angegebenen Sitz-Bone landet, während das Modell seine Größe behält. /fmm mount verwendet es mit dem ersten m_-Bone des Modells. Es tut nichts, wenn der Server die Sattelhöhe nicht melden kann

Namensschilder und Blickziele​

ModeledEntity stellt jetzt setDisplayNameLines(List<String>), setDisplayNameScale(float), setDisplayNameLineGap(double), setDisplayNameVisible(boolean) und getNameplateLocation() bereit. Ein Tag-Bone ist optional. Ohne ihn sitzt das Namensschild einen viertel Block (vier Pixel) über dem höchsten Würfel des Modells in Ruhe, skaliert mit der Entität.

Mehrzeilige Namen:

  • setDisplayName("Top line\nBottom line") und setDisplayNameLines(List<String>) nehmen Zeilen in Leserichtung, von oben nach unten. Die unterste Zeile bleibt am Anker, zusätzliche Zeilen reichen nach oben. Ausdrückliche Zeilenumbrüche legen die Zeilen fest; Text wird nicht umbrochen.
  • Ein leerer Name blendet das Namensschild aus. setDisplayNameVisible(false) blendet jede Zeile aus und bleibt bei einer Textänderung wirksam.
  • setDisplayNameScale(float) skaliert den Text unabhängig von der Größe des Modells. setDisplayNameLineGap(double) legt den freien Abstand zwischen den Zeilen fest, in Blöcken bei Skalierung 1, standardmäßig 0.1. Eine Zeile ist bei Skalierung 1 0.225 Blöcke hoch, aufeinanderfolgende Zeilen liegen also standardmäßig 0.325 Blöcke auseinander.
  • Hat ein Modell mehrere tag_-Bones, verankert der alphabetisch erste das Namensschild, einschließlich seiner Bewegung und Animation.
  • Java-Spieler sehen Text Displays, die sowohl die Glyphen als auch den Abstand skalieren. Bedrock-Spieler sehen separate Marker-Armor-Stand-Labels, die sich nicht skalieren lassen, ihr Abstand bleibt also bei Skalierung 1. Damit die Bedrock-Labels gewählt werden, braucht der Backend-Server Floodgate oder Geyser, um Bedrock-Spieler zu erkennen; ein reines Proxy-Setup ohne Identitätsweiterleitung kann sie nicht zuverlässig wählen.
  • Sichtweite, Unsichtbarkeit, Entfernen und Neuladen folgen dem Modell. Unveränderter Text und unveränderte Positionen senden keine Pakete.

Wie EliteMobs Namen und Rolle eines NPCs mit einem FreeMinecraftModels-Modell anordnet, steht unter NPCs erstellen.

DynamicEntity.setLookTarget(Location) gibt dem Skelett eine Kopie eines visuellen Blickziels, ohne die zugrunde liegende Entität zu drehen oder zu bewegen. Übergib null, um es freizugeben.

Besonderheiten von PropEntity​

  • PropEntity.isPropEntity(ArmorStand) / PropEntity.getPropEntityID(ArmorStand) -- identifiziert einen Prop anhand seines zugrunde liegenden Armor Stands
  • PropEntity.hasLoadedPropOnSameBlock(String entityID, Location) -- die Duplikatprüfung, die spawnPropEntity intern ausführt; rufe sie vorab auf, wenn du verzweigen willst, statt auf null zu prüfen
  • PropEntity.respawnPropEntityFromArmorStand(String entityID, ArmorStand) -- baut einen Prop um einen Armor Stand herum neu auf, der ein Chunk-Neuladen überlebt hat
  • PropEntity.getPropEntities() -- Momentaufnahme der geladenen Props, indiziert nach Rüstungsständer-UUID; Änderungen daran verändern die aktive Registry nicht
  • PropEntity#setPersistent(boolean) -- schaltet die Persistenz des zugrunde liegenden Armor Stands um
  • PropEntity#setCustomDataString(NamespacedKey, String) / getCustomDataString(NamespacedKey) -- liest/schreibt eigene PDC-Werte am Prop. Das ist derselbe Speicher, den die Lua-Helfer set_persistent_data / get_persistent_data unter dem Namensraum fmm_lua_<key> verwenden
  • PropEntity#remove() / remove(boolean showRealBlocks) -- entfernt das Modell, lässt aber den persistenten Eintrag bestehen
  • PropEntity#permanentlyRemove() -- entfernt das Modell und seinen persistenten Eintrag
  • PropEntity#setVoxelizeConfig(boolean voxelize, boolean solidify) / applySolidify() -- die Laufzeitentsprechungen der YML-Felder voxelize: / solidify:
  • PropEntity#showFakePropBlocksToPlayer(Player) / showRealBlocksToPlayer(Player) und die ...ToAllPlayers()-Varianten -- steuern die paket-only Barrier-Blöcke, die einem solidifizierten Prop clientseitige Kollision geben

Integrationen für magische Waffen​

Verwende MagicWeaponAPI.isOperational(), bevor du dich auf den Magiedienst verlässt, isWeapon(String) zur Prüfung einer definierten ID und applyWeaponData(ItemStack, String), um Waffenidentität und Anzeige anzuwenden, ohne Name, Lore, Level oder Verzauberungen des ursprünglichen Items zu ersetzen. Die Registrierung des Dienstes allein beweist noch nicht, dass er betriebsbereit ist.

Ein optionaler MagicAttackResolver kann Zauber ablehnen, Ziele filtern und priorisieren, Kampfdaten zum Abschusszeitpunkt erfassen und Schaden berechnen. Er muss die bereitgestellte einmalig verwendbare MagicDamageApplication nutzen und darf dem Ziel nicht direkt Schaden zufügen. FMM verwaltet Eingaben, Projektilflug, Kollision und Schadensanwendung.

MagicProjectileTravelEvent ist eine synchrone, abbrechbare Kollisionsabfrage entlang des durchlaufenen Segments, einschließlich einer Prüfung beim Einschlag. Ein Abbruch absorbiert das Projektil ohne Schaden oder Explosion. Ein Segment kann mehrfach geprüft werden; Listener dürfen das Event daher nicht als Schadensbenachrichtigung behandeln.

Event-Oberfläche​

Rechtsklick-Eingaben lösen zunächst ModeledEntityInteractEvent für die tatsächliche zugrunde liegende Entität aus. Es erbt die Handler von PlayerInteractEntityEvent, sodass gewöhnliche Listener zum Schutz von Entitäten es abbrechen können. Ein Abbruch verhindert das Rechtsklick-Event des Modells und seinen Callback. Diese Prüfung ist unabhängig von der Berechtigung, Blöcke zu platzieren oder abzubauen.

So greifen die Rechtsklick-Prüfungen ineinander:

  • Block- und Modellinteraktionen werden getrennt entschieden. Das Abbrechen eines PlayerInteractEvent hält die Block- oder Item-Aktion abgebrochen, aber der Rechtsklick kann trotzdem über FMMs Reichweiten- und Blockverdeckungsprüfungen ein Modell auswählen. Das Abbrechen von Linksklicks funktioniert wie bisher.
  • ModeledEntityInteractEvent verwendet die Haupthand. FMM löst ModeledEntityRightClickEvent erst aus, nachdem dieser Dispatch abgeschlossen ist, und ein abgebrochenes ModeledEntityRightClickEvent stoppt den Modell-Callback, das Prop-Verhalten und das Standard-Aufsitzen.
  • Das Bridge-Event ist eine Berechtigungsprüfung, kein weiterer Klick. FMMs eigene Eingabe-, Magiewaffen- und Prop-Router ignorieren es. Ein Plugin, das bei nativen Entity-Interaktions-Events Aktionen ausführt, kann ModeledEntityInteractEvent überspringen, um nicht doppelt zu handeln; Schutz-Listener sollten es weiter behandeln.
  • Ein Modell ohne zugrunde liegende Bukkit-Entität kann kein Bukkit-Entity-Event auslösen. Es löst weiterhin das abbrechbare ModeledEntityRightClickEvent aus. Persistente Props und EliteMobs-NPCs haben zugrunde liegende Entitäten und durchlaufen beide Prüfungen.

Generische Interaktions-Events:

  • ModeledEntityLeftClickEvent
  • ModeledEntityRightClickEvent
  • ModeledEntityHitboxContactEvent
  • ModeledEntityHitByProjectileEvent

Alle vier sind cancelbare Bukkit-Events; FMMs eingebaute Erkennungspfade lösen sie im primären Server-Thread aus. Ein abgebrochenes Event verhindert FMMs zugehörigen Callback bzw. das Standardverhalten. Hitbox-Kontakt-Scans laufen alle zwei Server-Ticks; Klick-Events haben weiterhin ein kurzes Deduplizierungsfenster pro Spieler, damit der Paket-Interaktions- und der OBB-Raytrace-Pfad dieselbe Aktion nicht zweimal auslösen können.

Lebenszyklus-Events:

  • FmmReloadedEvent -- wird 40 Ticks nach erfolgreicher vollständiger Initialisierung auf dem Hauptthread eingeplant, auch beim Start und nach einem erfolgreichen vollständigen Reload. Das gezielte Neuladen importierter Inhalte löst es bei Erfolg ebenfalls aus. Wird eine Anfrage während der Vorbereitung abgelehnt, bleiben aktive Laufzeit und Modelle erhalten; ein Abschlussereignis wird nicht gesendet.

Die öffentliche Interaktions-Oberfläche ist bewusst unabhängig vom Modelltyp. Die älteren Varianten StaticEntity*Event, DynamicEntity*Event und PropEntity*Event sowie ResourcePackGenerationEvent gehören nicht mehr zur aktuellen API. Verwende stattdessen die vier generischen Events für modellierte Entitäten und FmmReloadedEvent.

Verbraucher-Plugins, die langlebige DynamicEntity- oder PropEntity-Referenzen halten (EliteMobs, BetterStructures usw.), müssen dieses Event behandeln, indem sie ihre Modellanhänge an den überlebenden zugrunde liegenden Entitäten neu erstellen. Ohne dies werden diese Entitäten nach einem Reload unsichtbar, weil FMM die Display-Entitäten während onDisable abgebaut hat, während die Referenz des Verbrauchers nun veraltet ist.

@EventHandler
public void onFmmReloaded(FmmReloadedEvent event) {
for (MyTrackedEntity tracked : myEntities) {
if (tracked.bukkitEntity != null && tracked.bukkitEntity.isValid()) {
tracked.fmmModel = DynamicEntity.create("my_model", tracked.bukkitEntity);
}
}
}

ModeledEntityHitByProjectileEvent und OBB-Projektil-Erkennung​

FMM verwendet OBB-Erkennung (Oriented Bounding Box) für Projektile gegen modellierte Entitäten. Wenn ein Projektil mit der OBB-Hitbox einer modellierten Entität kollidiert, löst FMM ein ModeledEntityHitByProjectileEvent aus. Dies ist ein standardmäßiges, cancelbares Bukkit-Event.

Wichtige Details:

  • Die Erkennung prüft das Segment zwischen erfassten Positionen, um Durchquerungen dünner Modelle zu erkennen; ohne vorherige Position verwendet sie eine Überlappungsprüfung
  • Kandidaten auf diesem Segment werden nach Nähe aufgelöst, unabhängig von der Iterationsreihenfolge der Modell-Registry
  • Ein nicht durchschlagendes Projektil wird an genau ein modelliertes Ziel geleitet. Ein Pfeil mit Durchschlag kann Durchschlagsstufe + 1 verschiedene modellierte Ziele in Flugreihenfolge treffen, ohne doppelt auszulösen, wenn sowohl die OBB- als auch die zugrunde liegende Vanilla-Hitbox ihn registrieren
  • Der Standard-Handler für dynamische Entitäten leitet den Einschlag als echtes Projektil-Schadensereignis an die zugrunde liegende lebende Entität weiter und nutzt dabei die Geschwindigkeit des Projektils zum Einschlagszeitpunkt. Das erhält die Projektil-Ursache, die Behandlung durch Kampf-Plugins und die Zuordnung zum Schützen
  • Ein Abbruch des Events verhindert FMMs Callback- bzw. Standard-Schadensverhalten, aber die Kollision verbraucht dennoch das Modellziel-Budget dieses Projektils. Ein abgebrochener nicht durchschlagender Treffer verbraucht das Projektil also trotzdem
@EventHandler
public void onProjectileHitModel(ModeledEntityHitByProjectileEvent event) {
ModeledEntity target = event.getEntity();
Projectile projectile = event.getProjectile();
// Cancel to prevent damage
event.setCancelled(true);
}

Item- und Modell-Hilfen​

ModelItemFactory​

Factory-Klasse zum programmgesteuerten Erstellen modellbezogener ItemStacks.

// Create a prop placement item (uses "model_id" PDC key)
ItemStack placementItem = ModelItemFactory.createModelItem("lamp_post", Material.STICK);

// Create a custom item from config (uses "fmm_item_id" PDC key)
PropScriptConfigFields config = ItemScriptManager.getItemDefinitions().get("magic_sword");
ItemStack customItem = ModelItemFactory.createCustomItem("magic_sword", config);
  • createModelItem(String modelId, Material material) -- erstellt ein Platzierungs-Item für Props. Auf 1.21.4+ wird automatisch das Display-Modell-Rendering angewendet, falls ein Display-JSON existiert.
  • createCustomItem(String itemId, PropScriptConfigFields config) -- erstellt ein benutzerdefiniertes Item mit Name, Lore, Verzauberungen und Display-Modell aus der vereinheitlichten Konfiguration.
  • formatModelName(String modelId) -- Hilfsmittel, das eine Modell-ID wie 01_em_flame_sword in Flame Sword konvertiert.

DisplayModelRegistry​

Einfache Registry, die nachverfolgt, welche Modelle ein Display-JSON verfügbar haben.

// Check if a model has a display model JSON registered
boolean has3D = DisplayModelRegistry.hasDisplayModel("magic_sword");
  • register(String modelId) -- registriert eine Modell-ID (wird intern während des Reloads aufgerufen)
  • hasDisplayModel(String modelId) -- prüft die Registrierung des Display-Modells; eine fehlerhafte .json wird nicht allein aufgrund ihrer Existenz registriert
  • getRegisteredModels() -- gibt ein unveränderliches Set<String> aller Modell-IDs zurück, die Display-Modelle registriert haben
  • shutdown() -- löscht alle Registrierungen

ItemScriptManager​

Trotz des beibehaltenen Klassennamens handelt es sich jetzt um einen Katalog definierter Items und magischer Waffen. Die Skriptlaufzeit für ausgerüstete Items wurde entfernt. getItemDefinitions() stellt registrierte Item-Definitionen bereit; getWeaponCatalog() stellt validierte Waffendefinitionen bereit. Die früheren APIs für aktive Skripte, Ausrüstungsaktualisierungen und das Entfernen von Spielerskripten existieren nicht mehr.

ScriptedItemAPI​

  • isValidItemId(String): prüft die exakte ID des definierten Items (Konfigurationsdateiname ohne .yml).
  • getItemConfig(String): gibt die Definition oder null zurück.
  • applyScriptedItemData(ItemStack, String): setzt fmm_item_id und, sofern vorhanden, das registrierte Anzeigemodell. Gibt bei ungültiger Item-ID oder fehlenden Item-Metadaten false zurück.

Diese Methode verändert Name, Lore und Verzauberungen des ursprünglichen Items nicht und bindet kein Lua-Verhalten an. Wende Verzauberungen separat über MagmaCore an. Spannzustände von Bögen und Armbrüsten gehören zur Definition des Basis-Anzeigemodells; hänge _idle nicht an die definierte Item-ID an.

ModelItemAPI​

Verwende ModelItemAPI.applyDisplayModel(ItemStack, String), wenn das ursprüngliche Item nur ein FMM-Anzeigemodell benötigt. Die Methode prüft die Registry der Anzeigemodelle und setzt keine Identität eines definierten Items. Das unterscheidet sich von der Auswahl einer FMM-Waffen- oder Item-Definition.

ItemConstructionSnapshot​

Für Plugins, die Items außerhalb des Server-Threads erstellen. Rufe ItemConstructionSnapshot.capture() auf dem Server-Thread auf; die Methode kopiert die aktuellen Definitionen der definierten Items, die Anzeigemodelle und die Arten magischer Waffen. Ein Worker-Thread kann danach item(id) oder weapon(id) aufrufen und die zurückgegebenen ItemData mit apply(ItemStack) auf einen ItemStack anwenden. Dabei werden für ein definiertes Item fmm_item_id und, sofern registriert, das Anzeigemodell gesetzt. weapon(id) gibt null zurück, es sei denn, die ID gehört zu einer magischen Waffe und der Magie-Dienst war zum Zeitpunkt der Erfassung betriebsbereit. Rufe vor dem Veröffentlichen der erstellten Items requireCurrent() auf dem Server-Thread auf: Die Methode wirft eine ConcurrentModificationException, wenn sich die Definitionen von FMM seit der Erfassung geändert haben, zum Beispiel nach einem Neuladen.

DisguiseAPI​

Öffentlicher Einstiegspunkt für die Spieler-Verkleidungsfunktion. Drittanbieter-Plugins sollten diese Klasse aufrufen statt des internen DisguiseManager, damit interne Refactorings sicher bleiben.

import com.magmaguy.freeminecraftmodels.api.DisguiseAPI;

// Disguise a player as a loaded model. Replaces any existing disguise cleanly.
boolean ok = DisguiseAPI.disguise(player, "dragon");

// Undisguise (returns true if a disguise was removed).
DisguiseAPI.undisguise(player);

// Query state.
boolean disguised = DisguiseAPI.isDisguised(player);
String modelID = DisguiseAPI.getDisguiseModelID(player); // null if not disguised

// Snapshot of all currently disguised players.
Collection<Player> all = DisguiseAPI.getDisguisedPlayers();
  • disguise(Player, String modelID) -- entfernt die alte Verkleidung vor dem Ersetzungsversuch; eine unbekannte ID liefert danach false. Übergib einen verbundenen Spieler und eine exakte geladene ID. Die API normalisiert IDs nicht und prüft keine Befehlsberechtigungen
  • undisguise(Player) -- gibt true zurück, wenn eine Verkleidung entfernt wurde
  • isDisguised(Player) -- schnelle Bool-Prüfung
  • getDisguiseModelID(Player) -- gibt die aktive Modell-ID oder null zurück
  • getDisguisedPlayers() -- unveränderliche Momentaufnahme der verkleideten Spieler

FMM kombiniert die Standardsichtbarkeit des Spielers, einen Unsichtbarkeitseffekt und Änderungen an der Ausrüstungsanzeige. Ein entfernter Effekt wird nur wiederhergestellt, wenn diese Verkleidung ihn ursprünglich hinzugefügt hat. Ein bereits vorhandener Effekt wird nicht übernommen oder wiederhergestellt. Anders als die API prüft der Befehl das Modell vor dem Ersetzen. Grenzen von Lebenszyklus und Clientdarstellung stehen unter Spielerverkleidungen.

LocationAPI​

Öffentliche API, mit der Plugins zur Dungeon-Erkennung und zu Regions-Schutzprüfungen beitragen können. Die registrierten Prädikate versorgen FMMs Lua-Prüfungen em.location.is_in_dungeon und em.location.is_protected (verwendet von vorgefertigten Skripten wie pickupable.lua und storage_double.lua).

Plugins übergeben ein einfaches Predicate<Location>, sodass keine geshadeten FMM-Typen Plugin-Klassenlader überqueren.

import com.magmaguy.freeminecraftmodels.api.LocationAPI;

// On your plugin's enable, after WorldGuard/EliteMobs/etc. are available.
LocationAPI.registerDungeonLocator("EliteMobs",
location -> EliteMobs.isInsideDungeon(location));

LocationAPI.registerProtectionProvider("WorldGuard",
location -> WorldGuardBridge.isProtected(location));
  • registerDungeonLocator(String providerName, Predicate<Location> predicate) — jedes registrierte Prädikat, das true zurückgibt, markiert den Ort als „in einem Dungeon"
  • registerProtectionProvider(String providerName, Predicate<Location> predicate) — jedes registrierte Prädikat, das true zurückgibt, markiert den Ort als geschützt
  • unregisterDungeonLocator(String providerName) und unregisterProtectionProvider(String providerName) entfernen das unter diesem Namen registrierte Prädikat. Rufe sie im onDisable deines Plugins auf, damit FMM die deaktivierte Instanz nicht mehr aufruft

Eine erneute Registrierung unter demselben Provider-Namen ersetzt das vorherige Prädikat, sodass ein Plugin, das sich bei jeder Aktivierung neu registriert, keine Kopien anhäuft. Ein null-Provider-Name registriert ein Prädikat, das weder ersetzt noch abgemeldet werden kann.

Operatoren können die Registrierung mit /fmm location überprüfen, das die Anzahl der aktiven Provider meldet und beide Prädikate an ihrem aktuellen Standort testet.

Schutz-Provider und Prop-Platzierung​

Dieselben Schutz-Provider treiben auch die Prop-Platzierungsprüfung preventPropPlacementInProtectedRegions an, aber diese Prüfung ruft canBuild(player, location) auf statt des rein ortsbezogenen isProtected(location). Der Unterschied ist wichtig:

ProviderVerhalten von canBuild
Eingebauter WorldGuard-AdapterBerücksichtigt WorldGuards eigenen Bypass und überlässt dann WorldGuards testBuild die Entscheidung — Regionsmitglieder und -besitzer können also normal bauen
Eingebauter GriefPrevention-AdapterKein Claim am Ort bedeutet erlaubt; innerhalb eines Claims überlässt er GriefPreventions eigener Baurechtsprüfung für diesen Spieler die Entscheidung
Über LocationAPI.registerProtectionProvider registrierter ProviderFällt auf !isProtected(location) zurück — rein ortsbezogen, blockiert alle an einem Ort, den dein Prädikat als geschützt bezeichnet

Dieser Fallback ist beabsichtigt und konservativ: Ein Predicate<Location> kann keine spielerspezifische Berechtigung ausdrücken, also erfindet FMM keine. Wenn du echtes spielerbewusstes Verhalten für dein eigenes Regionssystem willst, implementiere MagmaCores RegionProtectionProvider direkt, überschreibe canBuild und registriere ihn dann mit LocationQueryRegistry.registerProtectionProvider, statt den Prädikat-Komfort-Wrapper zu verwenden.

Zwei weitere wissenswerte Verhaltensweisen:

  • Adapter-Fehler scheitern kontrolliert. Wirft ein Provider während einer Bau-Abfrage einen Fehler, wird die Platzierung abgelehnt und eine Warnung mit dem Namen des Providers protokolliert. Es fällt nie stillschweigend auf „erlaubt" durch.
  • Jeder registrierte Provider muss zustimmen. Der erste Provider, der nein sagt, gewinnt; über LocationOwnership registrierte Besitz-Provider sind weiterhin rein ortsbezogen und blockieren alle.

Die Item-Platzierung prüft zuerst die angrenzende Zelle. Voxelisierte Props prüfen zusätzlich ihre gedrehte Grundfläche mit gerundeten Hitbox-Abmessungen und mindestens einem Block; das Volumen der Paketbarrieren wird anders berechnet. Direkte Aufrufe von PropEntity.spawnPropEntity führen die Platzierungsprüfungen des Spieler-Items nicht aus. Lies Konfiguration, bevor du Platzierung über ein anderes Plugin anbietest.

PropScriptConfigFields​

Liest die neben dem Modell liegende YAML-Datei für Props und definierte Items. scripts:-Listen von Props binden unabhängige Lua-Skripte. Eine Definition mit material: registriert hingegen ein gehaltenes Item und darf keine nicht leere scripts:-Liste enthalten.

Getrennte Beispiele für Props und gehaltene Items findest du in der Modellkonfiguration, das Waffenschema unter magische Waffen.

Lua-Skripting​

FreeMinecraftModels unterstützt Lua-Skripte für Props über die MagmaCore-Skript-Engine. Skriptdateien werden in plugins/FreeMinecraftModels/scripts/ abgelegt und über eine begleitende YML-Konfiguration neben der Modelldatei an Modelle gebunden. Die Skriptdatei auf der Festplatte muss auf .lua enden; Konfigurationseinträge dürfen die Endung enthalten oder weglassen.

Props binden jedes unter scripts: aufgeführte Skript als eigenständige Instanz. Effekte gehaltener Items verwenden die separate Laufzeit des Verzauberungs-Providers und können keine nicht leere Item-Liste scripts: verwenden.

Prop-Skript-Hooks​

HookAuslöser
on_spawnProp wird in die Welt gespawnt
on_game_tickJeden Tick, während der Prop existiert
on_zone_enterEin Spieler betritt eine per Skript erstellte überwachte Zone
on_zone_leaveEin Spieler verlässt eine per Skript erstellte überwachte Zone
on_destroyProp wird endgültig entfernt (nicht beim Entladen eines Chunks oder beim Neuladen)
on_left_clickSpieler klickt den Prop links an
on_right_clickSpieler klickt den Prop rechts an
on_projectile_hitEin Projektil trifft den Prop; context.projectile ist das Projektil und context.event.player der Schütze

Item-Skript-Hooks​

Die Lua-Hooks für ausgerüstete Items und ihre context.item-Laufzeit wurden eingestellt. Verwende Verzauberungsdefinitionen für Item-Effekte. Prop-Hooks werden weiterhin unterstützt.

Kontexttabelle für Prop-Skripte​

Prop-Skripte erhalten eine context-Tabelle. Hier ist eine Zusammenfassung der wichtigsten APIs -- siehe Lua Prop API für alle Details.

context.prop:

  • model_id -- der Blueprint-Modellname
  • current_location -- der aktuelle Standort des Props
  • play_animation(name, blend, loop) -- fordert die genannte Animation an (blend und loop sind standardmäßig true, was eine loopende Animation über das idle des Props legt); die Anfrage wird im nächsten Tick des Modells angewendet
  • stop_animation() — beendet benutzerdefinierte Animationen und kehrt zu idle zurück, sofern verfügbar; unterbricht keine abschließende Todesanimation
  • hurt_visual() -- spielt den Verletzungs-(rotes Blinken)-Visualeffekt am Prop ab
  • pickup() -- stellt das Entfernen des Props und das Fallenlassen seines Platzierungs-Items in die Warteschlange
  • mount(player) -- stellt einen Aufsteigeversuch in die Warteschlange; ein Rückgabewert von true bedeutet, dass Spieler und Mount-Manager gültig waren, nicht dass letztlich ein Sitzplatz zugewiesen wurde
  • dismount(player) -- stellt eine Absteigeprüfung in die Warteschlange; ein Rückgabewert von true bedeutet, dass Spieler und Mount-Manager gültig waren
  • get_passengers() -- gibt eine Liste der derzeit auf dem Prop sitzenden Spieler zurück
  • spawn_elitemobs_boss(filename, x, y, z) -- spawnt einen EliteMobs-Boss an absoluten Koordinaten in der aktuellen Welt des Props

context.event:

  • Verfügbar in den Prop-Hooks on_left_click, on_right_click, on_zone_enter, on_zone_leave und on_projectile_hit
  • cancel(), uncancel(), is_cancelled, sofern der zugrunde liegende Hook abbrechbar ist
  • player -- der Spieler, der das Event ausgelöst hat; in on_projectile_hit die lebende Entität, die das Projektil abgeschossen hat

context.world:

  • spawn_entity(entity_type, x, y, z) -- spawnt eine Vanilla-Entität oder gibt nil zurück, wenn der Entitätstyp ungültig ist
  • set_block_at(x, y, z, material) -- stellt eine Blockänderung in die Warteschlange, sofern das Material gültig ist; nicht geladene Chunks werden übersprungen
  • Plus Partikel, Sounds, Block-Abfragen, Blitze und Suche nach nahen Entitäten

context.cooldowns:

  • check_local(key, ticks) -- prüft und startet einen Cooldown pro Skript
  • global_ready() / set_global(ticks) — gemeinsamer Cooldown für den Prop

Spieler-Objekte (aus context.player oder context.event.player):

  • get_held_item() -- gibt das Item zurück, das der Spieler hält; type ist der Bukkit-Materialname in Großbuchstaben
  • is_holding_elitemobs_item(filename) -- prüft den EliteMobs-Dateinamen-Tag des Items in der Haupthand, etwa shroomwhispers_shears.yml. Anzeigenamen und Übersetzungen beeinflussen diese Prüfung nicht. Gibt bei leerer Hand, einem anderen Item oder nicht verfügbarer EliteMobs-Integration false zurück.
  • consume_held_item() -- plant die Entnahme eines Items aus der Haupthand ein, wie sie bei Ausführung der Aufgabe vorliegt; liefert kein Zahlungsergebnis
  • has_item(material) -- prüft, ob der Spieler ein Item hat
  • send_message(text) -- sendet eine Chat-Nachricht an den Spieler
  • game_mode -- der aktuelle Spielmodus des Spielers

Identifiziere EliteMobs-Questitems anhand ihres Dateinamens:

if not player:is_holding_elitemobs_item("shroomwhispers_shears.yml") then
return
end

Diese Methode wird von FMMs EliteMobs-Integration bereitgestellt. Ein gewöhnliches Item erhält den erforderlichen Dateinamen-Tag nicht durch Umbenennen.

Prop-Skript-Beispiel​

return {
api_version = 1,

on_spawn = function(context)
context.prop:play_animation("idle", true, true)
end,

on_right_click = function(context)
if context.cooldowns:check_local("activate", 40) then
context.prop:play_animation("activate", false, false)
end
end
}

Item-Skript-Beispiel​

Das frühere Beispiel für ausgerüstete Items wird nicht mehr unterstützt. Die aktuelle Erstellung von Items ist unter magische Waffen und Verzauberungsdefinitionen beschrieben.

Aus dem Quellcode bauen​

Der Quellcode liegt auf GitHub. FreeMinecraftModels ist ein Maven-Projekt für Java 21:

mvn -DskipTests package
  • Die Plugin-Jar wird nach target/FreeMinecraftModels.jar geschrieben.
  • MagmaCore, bStats und die anderen mitgelieferten Bibliotheken werden in der Phase package per Shading eingebunden. MagmaCore wird nach com.magmaguy.freeminecraftmodels.magmacore verschoben (Relocation), der Server braucht also keine separate MagmaCore-Installation.
  • Maven löst MagmaCore 2.2.0-SNAPSHOT aus dem Repository von MagmaGuy auf. Braucht der Quellcode MagmaCore-Änderungen, die dort noch nicht veröffentlicht sind, baue MagmaCore und veröffentliche es zuerst in deinem lokalen Maven-Repository. Mit -Dmagmacore.version=<version> baust du gegen eine andere MagmaCore-Version.
  • Ist die Umgebungsvariable MC_DIST_DIR gesetzt, kopiert der Build die Jar zusätzlich in diesen Ordner.

Importierte Modelle, Texturen, Schriften und Inhaltspakete behalten die Lizenzen ihrer Autoren. Das Erzeugen eines Ressourcenpakets daraus ändert diese Rechte nicht.

Hinweise​

  • FreeMinecraftModels ist eine installierte Plugin-Abhängigkeit, keine einbettbare Bibliothek.
  • Wenn dein Plugin frisch importierte Modelle benötigt, rufe ModeledEntityManager.reload() auf, anstatt zu versuchen, den FreeMinecraftModels-Status selbst neu aufzubauen.
  • Der aktuelle FMM-Quellcode hängt von MagmaCore 2.2.0-SNAPSHOT ab, das die gemeinsame Lua-Engine, LocationQueryRegistry und WorldFolderResolver bereitstellt.
  • FreeMinecraftModels deklariert WorldGuard, WorldEdit, GriefPrevention, Vault, floodgate und Geyser-Spigot als softdepend. WorldGuard und GriefPrevention stellen die eingebauten spielerbezogenen Schutzadapter bereit. WorldEdit ist eine WorldGuard-Abhängigkeit, kein eigenständiger FMM-Schutzadapter. Der Shop benötigt Vault, einen Economy-Provider und aktivierte Shop-Konfiguration. Die Bedrock-Darstellung benötigt auch ein kompatibles Paket und die passende Integration.
  • ModeledEntityManager.reload() fordert dieselbe asynchrone Vorbereitung und denselben vollständigen Lebenszyklus wie /fmm reload an und wartet nicht auf den Abschluss. Rufe es vom Hauptthread auf und verwende die erfolgreiche Lebenszyklus-Rückmeldung als Nachweis, nicht die void-Rückgabe. Ungültige Kandidateninhalte können den Reload ablehnen, während die aktive Laufzeit erhalten bleibt.