Перейти к основному содержимому

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) вместо того, чтобы удерживать и многократно копировать целый реестр.

Основные типы среды выполнения

  • ModeledEntity
  • StaticEntity
  • DynamicEntity
  • PropEntity

Создание сущностей

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-сущности, либо null
  • ModeledEntity#showUnderlyingEntity(Player) / hideUnderlyingEntity(Player) -- видимость базовой ванильной сущности для отдельного игрока
  • ModeledEntity#getEntityID() -- возвращает строку с ID модели
  • ModeledEntity#getModelInstanceId() -- UUID конкретного экземпляра, неизменный на протяжении жизни модели
  • ModeledEntity#isRemoved() / isDying() -- флаги жизненного цикла
  • ModeledEntity#getLocation() -- возвращает текущий Location
  • ModeledEntity#getSpawnLocation() -- возвращает Location, в котором модель была создана
  • ModeledEntity#getWorld() -- возвращает World
  • ModeledEntity#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 выполняет внутри; вызовите её заранее, если хотите ветвление вместо проверки на null
  • PropEntity.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() при подключении и предупреждайте автора контента. См. Заметки по созданию моделей.

События

Общие события взаимодействия:

  • ModeledEntityLeftClickEvent
  • ModeledEntityRightClickEvent
  • ModeledEntityHitboxContactEvent
  • ModeledEntityHitByProjectileEvent

Все четыре — отменяемые 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, если для этой модели существует .json display-модели
  • 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/unequip
  • removePlayer(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 предмета зарегистрирован в определениях предметов FMM
  • applyScriptedItemData(ItemStack itemStack, String itemId) -- проставляет PDC-тег и модель предмета на существующий ItemStack. Возвращает true при успехе, false, если ID предмета невалиден или у ItemStack нет meta. Замечание про луки/арбалеты: если у указанного itemId нет display-модели, но у itemId + "_idle" она есть (то есть у предмета есть модели состояний лука/арбалета), метод автоматически использует модель _idle в качестве display-модели
  • getItemConfig(String itemId) -- возвращает PropScriptConfigFields для данного ID предмета, либо null, если не найдено
Интеграция с EliteMobs

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 модели или null
  • getDisguisedPlayers() -- неизменяемый снимок замаскированных игроков

Замаскированные игроки делаются невидимыми для других и остаются такими до снятия маскировки — ведро молока, очистка эффектов маяком и подобные действия не нарушают невидимость.

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 допустимых целей.