API FreeMinecraftModels и руководство разработчика
FreeMinecraftModels — это и самостоятельный плагин, и API-поверхность для других плагинов.
Maven-репозиторий
<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>
Зависимость
<dependency>
<groupId>com.magmaguy</groupId>
<artifactId>FreeMinecraftModels</artifactId>
<version>LATEST.VERSION.HERE</version>
<scope>provided</scope>
</dependency>
Используйте как compileOnly/provided. Не шейдите плагин в свой jar.
Основные точки входа
ModeledEntityManager.modelExists(String)ModeledEntityManager.reload()ModeledEntityManager.getAllEntities()ModeledEntityManager.getDynamicEntities()ModeledEntityManager.propEntities()DisguiseAPI— маскировка и снятие маскировки игроков под загруженные моделиLocationAPI— регистрация детекторов подземелий и провайдеров защиты (питают Lua-предикатыem.location.*)ScriptedItemAPI— пометка сторонних ItemStack-ов метаданными скриптуемых предметов FMM
ModeledEntityManager.getAllEntities(), getDynamicEntities() и propEntities() возвращают копии на момент вызова. Изменение возвращённого множества или карты не изменяет живые реестры FMM. Для проверки существования используйте modelExists(String) вместо того, чтобы удерживать и многократно копировать целый реестр.
Основные типы среды выполнения
ModeledEntityStaticEntityDynamicEntityPropEntity
Создание сущностей
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);
Все пути создания возвращают null, если запрашиваемый ID модели не загружен.
PropEntity.spawnPropEntity дополнительно возвращает null, когда реквизит той же модели уже загружен на целевом блоке — дубликат отклоняется, а не складывается стопкой, и логируется предупреждение [FMM Props] Prevented duplicate prop spawn .... Всегда проверяйте возвращаемое значение на null; ненулевой возврат — единственное подтверждение того, что реквизит действительно создан.
createWithInvisibility — это вариант, который применяет зелье невидимости вместо скрытия сущности от клиентов. Это сохраняет сущность отслеживаемой на стороне клиента, что нужно для управления транспортом (используется внутри /fmm mount).
Полезные методы среды выполнения
ModeledEntity#setDisplayName(String)— молча ничего не делает, если модель не содержит костьtag_(см. предупреждение ниже)ModeledEntity#setDisplayNameVisible(boolean)— то же требованиеModeledEntity#setLeftClickCallback(...)ModeledEntity#setRightClickCallback(...)ModeledEntity#setHitboxContactCallback(...)ModeledEntity#setModeledEntityHitByProjectileCallback(...)ModeledEntity#playAnimation(String, boolean blend, boolean loop)-- возвращаетfalse, когда имя не соответствует ни встроенному состоянию, ни анимации в модели.blendставит в очередь, а не выполняет плавный переход;loopприменяется только к пользовательским анимациям. См. АнимацииModeledEntity#stopCurrentAnimations()ModeledEntity#hasAnimation(String)ModeledEntity#damage(double)/damage(Entity damager, double)/damage(Entity damager)/damage(Projectile)-- проходят черезDamageableComponentсущностиModeledEntity#attack(LivingEntity)/attack(LivingEntity, double damage)ModeledEntity#teleport(Location, boolean teleportUnderlyingEntity)ModeledEntity#setUnderlyingEntity(Entity)/ModeledEntity.getModeledEntity(Entity)-- статический обратный поиск модели, привязанной к Bukkit-сущности, либоnullModeledEntity#showUnderlyingEntity(Player)/hideUnderlyingEntity(Player)-- видимость базовой ванильной сущности для отдельного игрокаModeledEntity#getEntityID()-- возвращает строку с ID моделиModeledEntity#getModelInstanceId()--UUIDконкретного экземпляра, неизменный на протяжении жизни моделиModeledEntity#isRemoved()/isDying()-- флаги жизненного циклаModeledEntity#getLocation()-- возвращает текущийLocationModeledEntity#getSpawnLocation()-- возвращаетLocation, в котором модель была созданаModeledEntity#getWorld()-- возвращаетWorldModeledEntity#getSkeleton()/getSkeletonBlueprint()/getMountPointManager()-- доступ к структуре во время выполненияModeledEntity#getInteractionComponent()/getHitboxComponent()/getDamageableComponent()/getAnimationComponent()-- объекты-компоненты, стоящие за удобными методами вышеModeledEntity.getLoadedModeledEntities()-- статическое живое множество всех загруженных моделированных сущностейModeledEntity#getViewers()-- возвращаетHashSet<UUID>игроков, которые видят сущностьModeledEntity#getNametagBones()-- возвращаетList<Bone>костей nametag (полезно для размещения дополнительного текста)ModeledEntity#getScaleModifier()/setScaleModifier(double)ModeledEntity#removeWithDeathAnimation()-- удаляет с анимацией смерти (если есть)ModeledEntity#removeWithMinimizedAnimation()-- удаляет с анимацией уменьшенияModeledEntity#remove()-- немедленно удаляет сущность и все костиModeledEntity#setTintColor(Color)/getTintColor()-- применяет постоянный оттенок через канал краски кожаной брони. Вспышки урона ненадолго перекрывают оттенок и затем возвращаются к нему. Передайтеnull, чтобы очистить.ModeledEntity#setViewDistanceOverride(int)/getEffectiveViewDistance()-- переопределяетDefaultConfig.maxModelViewDistanceдля одной сущности. Передайте-1, чтобы вернуть значение по умолчанию для всего плагина.DynamicEntity#setSyncMovement(boolean)DynamicEntity#isDamagesOnContact()/setDamagesOnContact(boolean)-- управляет тем, наносит ли сущность урон игрокам при контакте хитбоксовDynamicEntity.isDynamicEntity(Entity)/DynamicEntity.getDynamicEntity(Entity)-- статический поиск по Bukkit-сущностиDynamicEntity#getBodyLocation()-- расположение, ориентированное по телу, отличается отgetLocation()Bone#getBoneLocation()
Особенности PropEntity
PropEntity.isPropEntity(ArmorStand)/PropEntity.getPropEntityID(ArmorStand)-- определение реквизита по его опорной стойке для брониPropEntity.hasLoadedPropOnSameBlock(String entityID, Location)-- проверка дубликатов, которуюspawnPropEntityвыполняет внутри; вызовите её заранее, если хотите ветвление вместо проверки на nullPropEntity.respawnPropEntityFromArmorStand(String entityID, ArmorStand)-- пересобирает реквизит вокруг стойки для брони, пережившей перезагрузку чанкаPropEntity.getPropEntities()-- живая карта загруженных реквизитов с ключом по UUID стойки для брониPropEntity#setPersistent(boolean)-- переключает постоянство опорной стойки для брониPropEntity#setCustomDataString(NamespacedKey, String)/getCustomDataString(NamespacedKey)-- чтение/запись собственных значений PDC на реквизите. Это то же хранилище, которое используют Lua-помощникиset_persistent_data/get_persistent_data, в пространстве имёнfmm_lua_<key>PropEntity#remove()/remove(boolean showRealBlocks)-- удаляет модель, но оставляет постоянную записьPropEntity#permanentlyRemove()-- удаляет модель и её постоянную записьPropEntity#setVoxelizeConfig(boolean voxelize, boolean solidify)/applySolidify()-- эквиваленты времени выполнения для YML-полейvoxelize:/solidify:PropEntity#showFakePropBlocksToPlayer(Player)/showRealBlocksToPlayer(Player)и варианты...ToAllPlayers()-- управляют пакетными блоками-барьерами, которые дают затвердевшему реквизиту столкновения на стороне клиента
tag_ в моделиsetDisplayName и setDisplayNameVisible проходят по костям тегов имён модели, которые существуют только для костей, чьё имя начинается с tag_. У модели без такой кости этот список пуст, поэтому оба вызова завершаются успешно и ничего не делают — ни исключения, ни строки в логе.
Запасного варианта нет. DynamicEntity скрывает от клиентов лежащую в основе живую сущность, поэтому ванильный тег имени моба тоже не показывается. В итоге получается совершенно безымянный моб, хотя ваш плагин задал имя без ошибок.
Если ваш плагин именует модели (боссов, NPC, что угодно видимое игроку), требуйте кость tag_ в поставляемых вами моделях либо проверяйте getNametagBones().isEmpty() при подключении и предупреждайте автора контента. См. Заметки по созданию моделей.
События
Общие события взаимодействия:
ModeledEntityLeftClickEventModeledEntityRightClickEventModeledEntityHitboxContactEventModeledEntityHitByProjectileEvent
Все четыре — отменяемые Bukkit-события; встроенные пути определения FMM рассылают их в основном потоке сервера. Отмена одного из них предотвращает соответствующий обратный вызов/поведение FMM по умолчанию. Сканирование контакта хитбоксов выполняется каждые два тика сервера; у событий клика по-прежнему есть короткое окно дедупликации для каждого игрока, чтобы пути пакетного взаимодействия и OBB-трассировки не вызвали одно и то же действие дважды.
События жизненного цикла:
FmmReloadedEvent-- срабатывает после завершения последовательности инициализации FMM при старте и после каждого/fmm reload. Всегда срабатывает в основном потоке сервера.
Публичная поверхность взаимодействия намеренно не зависит от типа модели. Более старые варианты StaticEntity*Event, DynamicEntity*Event и PropEntity*Event, а также ResourcePackGenerationEvent, больше не входят в текущий API. Используйте вместо них четыре общих события моделированных сущностей выше и FmmReloadedEvent.
Плагины-потребители, которые держат долгоживущие ссылки на DynamicEntity или PropEntity (EliteMobs, BetterStructures и т. п.), обязаны обрабатывать это событие, заново создавая привязки моделей к уцелевшим базовым сущностям. Без этого такие сущности станут невидимыми после перезагрузки, потому что FMM снёс display-сущности в onDisable, а ссылка потребителя теперь устарела.
@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 и определение попаданий снарядами по OBB
FMM использует OBB (ориентированный bounding box) для определения попаданий снарядов по моделированным сущностям. Когда снаряд пересекает OBB-хитбокс моделированной сущности, FMM рассылает ModeledEntityHitByProjectileEvent. Это стандартное отменяемое Bukkit-событие.
Ключевые детали:
- Определение просматривает весь отрезок движения снаряда с предыдущего тика, поэтому быстрые стрелы не могут «протуннелировать» сквозь тонкую модель между выборками
- Кандидаты на этом отрезке разрешаются в порядке от ближайшего, независимо от порядка обхода реестра моделей
- Непробивающий снаряд направляется к одной моделированной цели. Стрела с зачарованием Piercing может поразить
pierce level + 1различных моделированных целей в порядке пролёта, без двойного срабатывания, когда попадание наблюдают и OBB, и ванильные опорные хитбоксы - Обработчик динамической сущности по умолчанию передаёт попадание базовой живой сущности как настоящее событие урона от снаряда, используя скорость снаряда на момент удара. Это сохраняет причину урона от снаряда, обработку боевыми плагинами и атрибуцию стрелка
- Отмена события предотвращает обратный вызов/урон FMM по умолчанию, но столкновение всё равно расходует бюджет моделированных целей этого снаряда. Поэтому отменённое непробивающее попадание всё равно расходует снаряд
@EventHandler
public void onProjectileHitModel(ModeledEntityHitByProjectileEvent event) {
ModeledEntity target = event.getModeledEntity();
Projectile projectile = event.getProjectile();
// Cancel to prevent damage
event.setCancelled(true);
}
Утилиты для предметов и моделей
ModelItemFactory
Фабричный класс для программного создания ItemStack-ов, связанных с моделями.
// 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)-- создаёт предмет размещения для реквизита. На 1.21.4+ автоматически применяет рендеринг display-модели, если существует display JSON.createCustomItem(String itemId, PropScriptConfigFields config)-- создаёт пользовательский предмет с именем, lore, зачарованиями и display-моделью из унифицированного конфига.formatModelName(String modelId)-- утилита, которая преобразует ID модели вроде01_em_flame_swordвFlame Sword.
DisplayModelRegistry
Простой реестр, который отслеживает, у каких моделей доступен display JSON.
// Check if a model has a display model JSON registered
boolean has3D = DisplayModelRegistry.hasDisplayModel("magic_sword");
register(String modelId)-- регистрирует ID модели (вызывается внутренне при перезагрузке)hasDisplayModel(String modelId)-- возвращаетtrue, если для этой модели существует.jsondisplay-моделиgetRegisteredModels()-- возвращает неизменяемыйSet<String>всех ID моделей с зарегистрированными display-моделямиshutdown()-- очищает все регистрации
ItemScriptManager
Управляет жизненным циклом Lua-скриптов для пользовательских предметов на каждого игрока (модели с заданным material: в YML-конфиге).
// Get all registered custom item definitions
Map<String, PropScriptConfigFields> items = ItemScriptManager.getItemDefinitions();
// Get the active Lua script instance for a player + item
ScriptInstance instance = ItemScriptManager.getActiveScript(playerUUID, "magic_sword");
// Get all active scripts for a player
Map<String, ScriptInstance> scripts = ItemScriptManager.getActiveScripts(playerUUID);
scanForCustomItems(File modelsFolder)-- сканирует YML-конфиги моделей в поисках пользовательских предметовupdateEquippedScripts(Player player)-- сравнивает экипированные предметы с запущенными скриптами, вызывая хуки equip/unequipremovePlayer(Player player)-- завершает все скрипты для игрока (вызывайте при выходе)getItemDefinitions()-- возвращает мапу ID предмета вPropScriptConfigFields
ScriptedItemAPI
Публичный API для внешних плагинов, чтобы интегрироваться с системой скриптуемых предметов FMM. Это позволяет другим плагинам помечать собственные ItemStack-и данными скриптуемых предметов FMM (PDC-тег + модель предмета), чтобы для этих предметов срабатывали Lua-хуки FMM, без того, чтобы FMM перезаписывал имя предмета, lore или зачарования.
// Check if a scripted item definition exists
boolean exists = ScriptedItemAPI.isValidItemId("flame_blade");
// Apply FMM scripted item data to an existing ItemStack
// This sets:
// - The fmm_item_id PDC tag (so FMM's script system recognizes the item)
// - The item model (1.21.4+) from FMM's display model registry
// Does NOT modify name, lore, enchantments, or any other item properties.
boolean success = ScriptedItemAPI.applyScriptedItemData(itemStack, "flame_blade");
// Get the config for a scripted item
PropScriptConfigFields config = ScriptedItemAPI.getItemConfig("flame_blade");
isValidItemId(String itemId)-- возвращаетtrue, если ID предмета зарегистрирован в определениях предметов FMMapplyScriptedItemData(ItemStack itemStack, String itemId)-- проставляет PDC-тег и модель предмета на существующий ItemStack. Возвращаетtrueпри успехе,false, если ID предмета невалиден или у ItemStack нет meta. Замечание про луки/арбалеты: если у указанногоitemIdнет display-модели, но уitemId + "_idle"она есть (то есть у предмета есть модели состояний лука/арбалета), метод автоматически использует модель_idleв качестве display-моделиgetItemConfig(String itemId)-- возвращаетPropScriptConfigFieldsдля данного ID предмета, либоnull, если не найдено
EliteMobs использует этот API внутренне через поле конфигурации scriptedItem. Когда пользовательский предмет EliteMobs задаёт scriptedItem: flame_blade, EliteMobs строит свой предмет как обычно (имя, lore, зачарования, уровень), а затем вызывает ScriptedItemAPI.applyScriptedItemData(), чтобы поверх добавить модель и поведение скриптов FMM.
DisguiseAPI
Публичная точка входа для функции маскировки игроков. Сторонним плагинам следует обращаться к этому классу, а не к внутреннему DisguiseManager, чтобы внутренние рефакторинги оставались безопасными.
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)-- возвращаетfalse, если ID модели не загруженundisguise(Player)-- возвращаетtrue, если маскировка была снятаisDisguised(Player)-- быстрая булева проверкаgetDisguiseModelID(Player)-- возвращает активный ID модели илиnullgetDisguisedPlayers()-- неизменяемый снимок замаскированных игроков
Замаскированные игроки делаются невидимыми для других и остаются такими до снятия маскировки — ведро молока, очистка эффектов маяком и подобные действия не нарушают невидимость.
LocationAPI
Публичный API для плагинов, чтобы вносить вклад в обнаружение подземелий и проверки защиты регионов. Зарегистрированные предикаты питают проверки em.location.is_in_dungeon и em.location.is_protected в Lua-скриптах FMM (используются предзаготовленными скриптами вроде pickupable.lua и storage_double.lua).
Плагины передают обычный Predicate<Location>, поэтому шейженные типы FMM не пересекают classloader-ы плагинов.
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)— любой зарегистрированный предикат, возвращающийtrue, помечает локацию как «в подземелье»registerProtectionProvider(String providerName, Predicate<Location> predicate)— любой зарегистрированный предикат, возвращающийtrue, помечает локацию как защищённую
Операторы могут проверить регистрацию через /fmm location, которая сообщает живое количество провайдеров и проверяет оба предиката в их текущей позиции.
Провайдеры защиты и размещение реквизитов
Те же провайдеры защиты управляют и проверкой размещения реквизитов preventPropPlacementInProtectedRegions, но эта проверка вызывает canBuild(player, location), а не зависящий только от локации isProtected(location). Разница существенна:
| Провайдер | Поведение canBuild |
|---|---|
| Встроенный адаптер WorldGuard | Учитывает собственный bypass WorldGuard, затем делегирует его testBuild — поэтому участники и владельцы региона могут строить нормально |
| Встроенный адаптер GriefPrevention | Отсутствие привата в локации означает «разрешено»; внутри привата делегирует собственной проверке прав на строительство GriefPrevention для этого игрока |
Провайдер, зарегистрированный через LocationAPI.registerProtectionProvider | Откатывается к !isProtected(location) — только по локации, блокирует всех в локации, которую ваш предикат называет защищённой |
Этот откат намеренно консервативен: Predicate<Location> не может выразить права конкретного игрока, и FMM не станет их выдумывать. Если вы хотите по-настоящему учитывающее игрока поведение для собственной системы регионов, реализуйте RegionProtectionProvider из MagmaCore напрямую и переопределите canBuild, затем зарегистрируйте его через LocationQueryRegistry.registerProtectionProvider, а не через удобную обёртку с предикатом.
Ещё два поведения, о которых стоит знать:
- Сбои адаптера завершаются «закрыто». Если провайдер выбрасывает исключение во время запроса на строительство, размещение отклоняется, а в лог пишется предупреждение с именем провайдера. Он никогда не проваливается молча в «разрешено».
- Согласны должны быть все зарегистрированные провайдеры. Побеждает первый провайдер, сказавший «нет»; провайдеры владения, зарегистрированные через
LocationOwnership, по-прежнему работают только по локации и блокируют всех.
PropScriptConfigFields
Унифицированный класс конфигурации для YML-файлов конфигов моделей. Используется как скриптами реквизита, так и пользовательскими предметами.
# Example: torch_01.yml
isEnabled: true
scripts:
- torch_glow.lua
material: STICK # If set, model becomes a custom item
name: "&eMagic Torch" # Custom display name (optional)
lore: # Custom lore lines (optional)
- "&7Glows in the dark"
enchantments: # Enchantments (optional, format: NAME,LEVEL)
- "FIRE_ASPECT,1"
Ключевые методы: isCustomItem(), getParsedMaterial(), getParsedEnchantments(), getScripts().
Lua-скрипты
FreeMinecraftModels поддерживает Lua-скрипты как для реквизита, так и для пользовательских предметов через движок скриптов MagmaCore 2.0. Файлы скриптов размещаются в plugins/FreeMinecraftModels/scripts/ и привязываются к моделям через соседний YML-конфиг рядом с файлом модели. Файл скрипта на диске обязан оканчиваться на .lua; записи в конфиге могут указывать расширение или обходиться без него.
Реквизит привязывает каждый скрипт, перечисленный в scripts:, как независимый экземпляр. Пользовательские предметы в настоящее время привязывают только первый валидный скрипт из списка для каждой пары игрок/предмет.
Хуки скриптов реквизита
| Хук | Срабатывание |
|---|---|
on_spawn | Реквизит появляется в мире |
on_game_tick | Каждый тик, пока реквизит жив |
on_zone_enter | Игрок входит в отслеживаемую зону, созданную скриптом |
on_zone_leave | Игрок покидает отслеживаемую зону, созданную скриптом |
on_destroy | Реквизит удалён |
on_left_click | Игрок кликает по реквизиту левой кнопкой |
on_right_click | Игрок кликает по реквизиту правой кнопкой |
on_projectile_hit | Зарезервировано: принимается валидацией, но в текущей среде выполнения не рассылается скриптам реквизита |
Хуки скриптов предметов
Пользовательские предметы (модели с заданным material:) поддерживают 22 Lua-хука:
| Хук | Срабатывание |
|---|---|
on_equip | Предмет попадает в отслеживаемый слот экипировки |
on_unequip | Предмет покидает отслеживаемый слот экипировки |
on_game_tick | Каждый тик, пока предмет экипирован |
on_attack_entity | Игрок атакует сущность, держа предмет |
on_kill_entity | Игрок убивает сущность, держа предмет |
on_take_damage | Игрок получает урон, имея предмет экипированным |
on_shield_block | Игрок блокирует щитом |
on_shoot_bow | Игрок стреляет из лука |
on_projectile_hit | Снаряд игрока во что-то попадает |
on_projectile_launch | Игрок выпускает снаряд |
on_right_click | Игрок кликает правой кнопкой с предметом |
on_left_click | Игрок кликает левой кнопкой с предметом |
on_shift_right_click | Игрок делает shift+правый клик с предметом |
on_shift_left_click | Игрок делает shift+левый клик с предметом |
on_interact_entity | Игрок кликает правой по сущности с предметом |
on_swap_hands | Игрок меняет предмет между руками |
on_drop | Игрок выбрасывает предмет |
on_break_block | Игрок ломает блок, держа предмет |
on_consume | Игрок употребляет предмет |
on_item_damage | Предмет теряет прочность |
on_fish | Игрок использует удочку |
on_death | Игрок умирает с экипированным предметом |
Скрипты предметов получают context.item (с ID предмета и информацией об игроке) вместо context.prop.
Таблица контекста скрипта реквизита
Скрипты реквизита получают таблицу context. Ниже сводка по ключевым API — полные подробности см. в Lua API реквизита.
context.prop:
model_id-- имя модели-шаблонаcurrent_location-- текущая позиция реквизитаplay_animation(name, blend, loop)-- проигрывает названную анимацию (blend и loop по умолчаниюtrue)stop_animation()-- останавливает все текущие анимацииhurt_visual()-- проигрывает визуальную анимацию урона (красная вспышка) на реквизитеpickup()-- ставит в очередь удаление реквизита и выброс его предмета размещенияmount(player)-- ставит в очередь попытку посадки; возвратtrueозначает, что игрок и менеджер посадки были валидны, а не что место было в итоге назначеноdismount(player)-- ставит в очередь проверку высадки; возвратtrueозначает, что игрок и менеджер посадки были валидныget_passengers()-- возвращает список игроков, в данный момент сидящих на реквизитеspawn_elitemobs_boss(filename, x, y, z)-- спавнит босса EliteMobs по абсолютным координатам в текущем мире реквизита
context.event:
- Доступно в хуках реквизита
on_left_click,on_right_click,on_zone_enterиon_zone_leave, а также в хуках предметов, вызванных игроком cancel(),uncancel(),is_cancelled, когда базовый хук отменяемplayer-- игрок, инициировавший событие
context.world:
spawn_entity(entity_type, x, y, z)-- спавнит ванильную сущность либо возвращаетnil, если тип сущности невалиденset_block_at(x, y, z, material)-- ставит в очередь смену блока, если материал валиден; незагруженные чанки пропускаются- Плюс частицы, звуки, запросы блоков, молнии и поиск ближайших сущностей
context.cooldowns:
check_local(key, ticks)-- проверяет и запускает перезарядку для конкретного скриптаglobal_ready()/set_global(ticks)-- общая перезарядка для реквизита или игрока-владельца
Объекты игрока (из context.player или context.event.player):
get_held_item()-- возвращает предмет, который держит игрок;type— это имя материала Bukkit в верхнем регистреconsume_held_item()-- убирает один экземпляр удерживаемого предметаhas_item(material)-- проверяет, есть ли у игрока предметsend_message(text)-- отправляет игроку чат-сообщениеgame_mode-- текущий игровой режим игрока
Пример скрипта реквизита
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
}
Пример скрипта предмета
return {
api_version = 1,
on_equip = function(context)
context.player:send_message("&6You equipped the Flame Blade!")
end,
on_attack_entity = function(context)
-- Fire effect on hit
context.player:send_message("&cBurn!")
end,
on_unequip = function(context)
context.player:send_message("&7Flame Blade sheathed.")
end
}
Заметки
- FreeMinecraftModels является зависимостью в виде установленного плагина, а не встраиваемой библиотекой.
- Если вашему плагину нужны свежеимпортированные модели, вызывайте
ModeledEntityManager.reload(), а не пытайтесь пересобрать состояние FreeMinecraftModels самостоятельно. - Все плагины в экосистеме Nightbreak теперь зависят от MagmaCore 2.2.0-SNAPSHOT, который содержит общий движок Lua-скриптов, используемый и скриптами реквизита FreeMinecraftModels, и Lua-силами EliteMobs, плюс общие
LocationQueryRegistryиWorldFolderResolver. - FreeMinecraftModels объявляет WorldGuard, WorldEdit, GriefPrevention, Vault, floodgate и Geyser-Spigot как
softdepend. Ни один из них не требуется для запуска плагина, но они открывают конкретные функции: WorldGuard/WorldEdit/GriefPrevention питаютLocationAPIи проверку размещения реквизитов с учётом игрока, Vault включает магазин мебели, а floodgate/Geyser-Spigot включают пер-модельный Bedrock-бэкенд. ModeledEntityManager.reload()выполняет полный цикл перезагрузки плагина (onDisable/onLoad/onEnable). Вызывайте его в основном потоке сервера.ModeledEntityManager.reload()выполняет полный цикл перезагрузки плагина (onDisable/onLoad/onEnable). Вызывайте его в основном потоке сервера.
Представления реестров моделей, объектов и предметов — неизменяемые снимки; изменения должны возвращаться в основной поток. Старые семейства DynamicEntity*Event, StaticEntity*Event и PropEntity*Event больше не входят в API; используйте текущие события, включая ModeledEntityHitByProjectileEvent. Снаряды выполняют упорядоченное пересечение, а Piercing может пройти через pierce level + 1 допустимых целей.