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 entfernenLocationAPI— Dungeon-Detektoren und Schutzanbieter registrieren (versorgt Luaem.location.*-Prädikate)ScriptedItemAPI— Drittanbieter-ItemStacks mit Identitäts- und Anzeigedaten eines in FMM definierten Items versehenItemConstructionSnapshot— 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
ModeledEntityStaticEntityDynamicEntityPropEntity
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 NamensschildModeledEntity#setDisplayNameVisible(boolean)— steuert dessen SichtbarkeitModeledEntity#setLeftClickCallback(...)ModeledEntity#setRightClickCallback(...)ModeledEntity#setHitboxContactCallback(...)ModeledEntity#setModeledEntityHitByProjectileCallback(...)ModeledEntity#playAnimation(String, boolean blend, boolean loop)-- gibtfalsezurück, wenn der Name weder zu einem eingebauten Zustand noch zu einer Animation im Modell passt, oder nachdemdeathangefordert wurde. Bei einer benutzerdefinierten Animation spieltblend = truesie auf der Aktionsebene über idle oder walk ab, undfalselässt sie die gesamte Pose übernehmen;loopgilt nur für benutzerdefinierte Animationen. Siehe Animationen überblendenModeledEntity#stopCurrentAnimations()-- beendet benutzerdefinierte Animationen und Angriffe und springt zu idle; tut nichts, sobald der Tod begonnen hatModeledEntity#hasAnimation(String)ModeledEntity#damage(double)/damage(Entity damager, double)/damage(Entity damager)/damage(Projectile)-- läuft über dieDamageableComponentder EntitätModeledEntity#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, odernullModeledEntity#showUnderlyingEntity(Player)/hideUnderlyingEntity(Player)-- Sichtbarkeit der zugrunde liegenden Vanilla-Entität pro SpielerModeledEntity#getEntityID()-- gibt den Modell-ID-String zurückModeledEntity#getModelInstanceId()--UUIDpro Instanz, stabil über die Lebensdauer des ModellsModeledEntity#isRemoved()/isDying()-- Lebenszyklus-FlagsModeledEntity#getLocation()-- gibt die aktuelleLocationzurückModeledEntity#getSpawnLocation()-- gibt dieLocationzurück, an der das Modell erstellt wurdeModeledEntity#getWorld()-- gibt dieWorldzurückModeledEntity#getSkeleton()/getSkeletonBlueprint()/getMountPointManager()-- Zugriff auf die LaufzeitstrukturModeledEntity#getInteractionComponent()/getHitboxComponent()/getDamageableComponent()/getAnimationComponent()-- die Komponentenobjekte hinter den obigen KomfortmethodenModeledEntity.getLoadedModeledEntities()-- statische Live-Menge aller geladenen modellierten EntitätenModeledEntity#getViewers()-- gibt dasHashSet<UUID>der Spieler zurück, die die Entität sehen könnenModeledEntity#getNametagBones()-- gibtList<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 AnimationModeledEntity#remove()-- entfernt die Entität und alle Bones sofortModeledEntity#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. Übergibnull, um zu löschen.ModeledEntity#setViewDistanceOverride(int)/getEffectiveViewDistance()-- überschreibtDefaultConfig.maxModelViewDistancefü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ügtDynamicEntity.isDynamicEntity(Entity)/DynamicEntity.getDynamicEntity(Entity)-- statische Suchen ausgehend von einer Bukkit-EntitätDynamicEntity#getBodyLocation()-- die Position der Entität mit dem Yaw des Modells. IstweightedTurningan, 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 istBone#getBoneLocation()
Licht, Neigung, Zentrierung und Klicks
ModeledEntity#setBrightnessOverride(int blockLight, int skyLight)-- beleuchtet jeden Anzeige-Bone mit einer festen Stufe, jeweils0bis15, statt mit dem Licht an der Position des Bones.15, 15ist volle Helligkeit. Betrachter erhalten die Änderung mit der nächsten Bone-Aktualisierung. Nur Java-Anzeige-Bones: Bedrock- und Armor-Stand-Bones behalten ihr WeltlichtModeledEntity#clearBrightnessOverride()/hasBrightnessOverride()/getBrightnessOverrideBlockLight()/getBrightnessOverrideSkyLight()-- heben das feste Licht auf oder lesen es. Die Getter geben ohne Überschreibung-1zurückDynamicEntity#setTravelOrientation(TravelOrientation)/getTravelOrientation()-- neigt das Modell mit seiner Bewegung.TravelOrientationist ein Record aus einemStyle(UPRIGHT,TOP_FIRST,FRONT_FIRST),startSpeed,fullSpeed,maxTiltundturnSpeed;TravelOrientation.UPRIGHTist der Standard, undnullstellt ihn wieder her. Siehe BewegungsausrichtungModeledEntity#setBodyTilt(Quaterniondc)/getBodyTilt()-- neigt das gesamte Modell durch eine Rotation im Weltraum um die Mitte seiner Würfel, nach seiner Blickrichtung und seinen Animationen.nullstellt es aufrecht. Eine Bewegungsausrichtung außerUPRIGHTsetzt dies in jedem Tick; bei einem Modell mitUPRIGHTbleibt eine von dir gesetzte Neigung bestehenModeledEntity#setCenteredOnBody(boolean)/isCenteredOnBody()/getBodyOffset()-- zeichnet das Modell mit der Mitte seiner Würfel auf der Mitte seiner Entität. Siehe Zentrierung auf den KörperDynamicEntity#setClickSurface(boolean)/isClickSurface()-- beitrueerhalten 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 deshitbox-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ängtDynamicEntity#isSteerableMount()/hasSteeringRider()/seatSteeringRider(Player)-- ein Modell, das von einem gesattelten Pferd getragen wird, ist ein lenkbares Reittier.seatSteeringRidersetzt 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äßigDynamicEntity#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 mountverwendet es mit dem erstenm_-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")undsetDisplayNameLines(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äßig0.1. Eine Zeile ist bei Skalierung 10.225Blöcke hoch, aufeinanderfolgende Zeilen liegen also standardmäßig0.325Blö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 StandsPropEntity.hasLoadedPropOnSameBlock(String entityID, Location)-- die Duplikatprüfung, diespawnPropEntityintern ausführt; rufe sie vorab auf, wenn du verzweigen willst, statt aufnullzu prüfenPropEntity.respawnPropEntityFromArmorStand(String entityID, ArmorStand)-- baut einen Prop um einen Armor Stand herum neu auf, der ein Chunk-Neuladen überlebt hatPropEntity.getPropEntities()-- Momentaufnahme der geladenen Props, indiziert nach Rüstungsständer-UUID; Änderungen daran verändern die aktive Registry nichtPropEntity#setPersistent(boolean)-- schaltet die Persistenz des zugrunde liegenden Armor Stands umPropEntity#setCustomDataString(NamespacedKey, String)/getCustomDataString(NamespacedKey)-- liest/schreibt eigene PDC-Werte am Prop. Das ist derselbe Speicher, den die Lua-Helferset_persistent_data/get_persistent_dataunter dem Namensraumfmm_lua_<key>verwendenPropEntity#remove()/remove(boolean showRealBlocks)-- entfernt das Modell, lässt aber den persistenten Eintrag bestehenPropEntity#permanentlyRemove()-- entfernt das Modell und seinen persistenten EintragPropEntity#setVoxelizeConfig(boolean voxelize, boolean solidify)/applySolidify()-- die Laufzeitentsprechungen der YML-Feldervoxelize:/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
PlayerInteractEventhä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. ModeledEntityInteractEventverwendet die Haupthand. FMM löstModeledEntityRightClickEventerst aus, nachdem dieser Dispatch abgeschlossen ist, und ein abgebrochenesModeledEntityRightClickEventstoppt 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
ModeledEntityRightClickEventaus. Persistente Props und EliteMobs-NPCs haben zugrunde liegende Entitäten und durchlaufen beide Prüfungen.
Generische Interaktions-Events:
ModeledEntityLeftClickEventModeledEntityRightClickEventModeledEntityHitboxContactEventModeledEntityHitByProjectileEvent
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 + 1verschiedene 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 wie01_em_flame_swordinFlame Swordkonvertiert.
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.jsonwird nicht allein aufgrund ihrer Existenz registriertgetRegisteredModels()-- gibt ein unveränderlichesSet<String>aller Modell-IDs zurück, die Display-Modelle registriert habenshutdown()-- 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 odernullzurück.applyScriptedItemData(ItemStack, String): setztfmm_item_idund, sofern vorhanden, das registrierte Anzeigemodell. Gibt bei ungültiger Item-ID oder fehlenden Item-Metadatenfalsezurü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 danachfalse. Übergib einen verbundenen Spieler und eine exakte geladene ID. Die API normalisiert IDs nicht und prüft keine Befehlsberechtigungenundisguise(Player)-- gibttruezurück, wenn eine Verkleidung entfernt wurdeisDisguised(Player)-- schnelle Bool-PrüfunggetDisguiseModelID(Player)-- gibt die aktive Modell-ID odernullzurückgetDisguisedPlayers()-- 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, dastruezurückgibt, markiert den Ort als „in einem Dungeon"registerProtectionProvider(String providerName, Predicate<Location> predicate)— jedes registrierte Prädikat, dastruezurückgibt, markiert den Ort als geschütztunregisterDungeonLocator(String providerName)undunregisterProtectionProvider(String providerName)entfernen das unter diesem Namen registrierte Prädikat. Rufe sie imonDisabledeines 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:
| Provider | Verhalten von canBuild |
|---|---|
| Eingebauter WorldGuard-Adapter | Berücksichtigt WorldGuards eigenen Bypass und überlässt dann WorldGuards testBuild die Entscheidung — Regionsmitglieder und -besitzer können also normal bauen |
| Eingebauter GriefPrevention-Adapter | Kein 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 Provider | Fä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
LocationOwnershipregistrierte 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
| Hook | Auslöser |
|---|---|
on_spawn | Prop wird in die Welt gespawnt |
on_game_tick | Jeden Tick, während der Prop existiert |
on_zone_enter | Ein Spieler betritt eine per Skript erstellte überwachte Zone |
on_zone_leave | Ein Spieler verlässt eine per Skript erstellte überwachte Zone |
on_destroy | Prop wird endgültig entfernt (nicht beim Entladen eines Chunks oder beim Neuladen) |
on_left_click | Spieler klickt den Prop links an |
on_right_click | Spieler klickt den Prop rechts an |
on_projectile_hit | Ein 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-Modellnamecurrent_location-- der aktuelle Standort des Propsplay_animation(name, blend, loop)-- fordert die genannte Animation an (blend und loop sind standardmäßigtrue, was eine loopende Animation über das idle des Props legt); die Anfrage wird im nächsten Tick des Modells angewendetstop_animation()— beendet benutzerdefinierte Animationen und kehrt zuidlezurück, sofern verfügbar; unterbricht keine abschließende Todesanimationhurt_visual()-- spielt den Verletzungs-(rotes Blinken)-Visualeffekt am Prop abpickup()-- stellt das Entfernen des Props und das Fallenlassen seines Platzierungs-Items in die Warteschlangemount(player)-- stellt einen Aufsteigeversuch in die Warteschlange; ein Rückgabewert vontruebedeutet, dass Spieler und Mount-Manager gültig waren, nicht dass letztlich ein Sitzplatz zugewiesen wurdedismount(player)-- stellt eine Absteigeprüfung in die Warteschlange; ein Rückgabewert vontruebedeutet, dass Spieler und Mount-Manager gültig warenget_passengers()-- gibt eine Liste der derzeit auf dem Prop sitzenden Spieler zurückspawn_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_leaveundon_projectile_hit cancel(),uncancel(),is_cancelled, sofern der zugrunde liegende Hook abbrechbar istplayer-- der Spieler, der das Event ausgelöst hat; inon_projectile_hitdie lebende Entität, die das Projektil abgeschossen hat
context.world:
spawn_entity(entity_type, x, y, z)-- spawnt eine Vanilla-Entität oder gibtnilzurück, wenn der Entitätstyp ungültig istset_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 Skriptglobal_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;typeist der Bukkit-Materialname in Großbuchstabenis_holding_elitemobs_item(filename)-- prüft den EliteMobs-Dateinamen-Tag des Items in der Haupthand, etwashroomwhispers_shears.yml. Anzeigenamen und Übersetzungen beeinflussen diese Prüfung nicht. Gibt bei leerer Hand, einem anderen Item oder nicht verfügbarer EliteMobs-Integrationfalsezurück.consume_held_item()-- plant die Entnahme eines Items aus der Haupthand ein, wie sie bei Ausführung der Aufgabe vorliegt; liefert kein Zahlungsergebnishas_item(material)-- prüft, ob der Spieler ein Item hatsend_message(text)-- sendet eine Chat-Nachricht an den Spielergame_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.jargeschrieben. - MagmaCore, bStats und die anderen mitgelieferten Bibliotheken werden in der Phase
packageper Shading eingebunden. MagmaCore wird nachcom.magmaguy.freeminecraftmodels.magmacoreverschoben (Relocation), der Server braucht also keine separate MagmaCore-Installation. - Maven löst MagmaCore
2.2.0-SNAPSHOTaus 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_DIRgesetzt, 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-SNAPSHOTab, das die gemeinsame Lua-Engine,LocationQueryRegistryundWorldFolderResolverbereitstellt. - 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 reloadan und wartet nicht auf den Abschluss. Rufe es vom Hauptthread auf und verwende die erfolgreiche Lebenszyklus-Rückmeldung als Nachweis, nicht dievoid-Rückgabe. Ungültige Kandidateninhalte können den Reload ablehnen, während die aktive Laufzeit erhalten bleibt.