FreeMinecraftModels API 與開發者指南
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。不要將外掛 shade 進你自己的 jar 中。
核心入口點
ModeledEntityManager.modelExists(String)ModeledEntityManager.reload()ModeledEntityManager.getAllEntities()ModeledEntityManager.getDynamicEntities()ModeledEntityManager.propEntities()DisguiseAPI— 將玩家偽裝/取消偽裝為已載入的模型LocationAPI— 註冊地城偵測器與保護提供者(提供給 Luaem.location.*判斷使用)ScriptedItemAPI— 為第三方 ItemStack 標記 FMM 腳本化物品的中繼資料
核心執行階段類型
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);
如果請求的模型 ID 未載入,所有建立路徑都會回傳 null。
createWithInvisibility 是一種變體,會套用隱形藥水效果,而不是對客戶端隱藏實體。這會保持實體在客戶端被追蹤,這對於載具操控(由 /fmm mount 在內部使用)是必要的。
實用的執行階段方法
ModeledEntity#setDisplayName(String)ModeledEntity#setDisplayNameVisible(boolean)ModeledEntity#setLeftClickCallback(...)ModeledEntity#setRightClickCallback(...)ModeledEntity#setHitboxContactCallback(...)ModeledEntity#setModeledEntityHitByProjectileCallback(...)ModeledEntity#playAnimation(String, boolean, boolean)ModeledEntity#stopCurrentAnimations()ModeledEntity#hasAnimation(String)ModeledEntity#getEntityID()-- 回傳模型 ID 字串ModeledEntity#getLocation()-- 回傳當前的LocationModeledEntity#getWorld()-- 回傳WorldModeledEntity#getViewers()-- 回傳可以看到該實體的玩家HashSet<UUID>ModeledEntity#getNametagBones()-- 回傳名牌骨骼的List<Bone>(用於放置額外文字很有用)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)-- 控制實體是否會透過碰撞箱接觸對玩家造成傷害Bone#getBoneLocation()
事件介面
通用互動事件:
ModeledEntityLeftClickEventModeledEntityRightClickEventModeledEntityHitboxContactEventModeledEntityHitByProjectileEvent
也存在針對 StaticEntity、DynamicEntity 與 PropEntity 的類型化變體。
生命週期事件:
-
ResourcePackGenerationEvent-- 在 FMM 完成生成或重新生成資源包時觸發(在啟動時與/fmm reload時)。可監聽此事件以觸發在生成資源包上的後處理。 -
FmmReloadedEvent-- 在 FMM 完成啟動時的初始化序列和每次/fmm reload後觸發。永遠在主伺服器執行緒上觸發。持有長期存在的
DynamicEntity或PropEntity參考的消費者外掛(EliteMobs、BetterStructures 等)必須處理此事件,在仍存在的底層實體上重新建立其模型附件。如果不這樣做,那些實體在重新載入後會變成不可見,因為 FMM 在onDisable期間拆除了 display entity,而消費者的參考現在已失效。@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(定向碰撞箱)命中偵測來處理對模型化實體的拋射物。當拋射物與模型化實體的 OBB 碰撞箱相交時,FMM 會觸發 ModeledEntityHitByProjectileEvent。這是一個標準的可取消 Bukkit 事件。
關鍵細節:
- 箭矢傷害計算支援
POWER附魔加成 - 會尊重
PIERCING附魔 -- 箭矢可以穿透到其他目標 - 取消事件可防止傷害並完全阻擋命中
@EventHandler
public void onProjectileHitModel(ModeledEntityHitByProjectileEvent event) {
ModeledEntity target = event.getModeledEntity();
Projectile projectile = event.getProjectile();
// 取消以防止傷害
event.setCancelled(true);
}
物品與模型公用程式
ModelItemFactory
用於以程式化方式建立模型相關 ItemStack 的工廠類別。
// 建立道具放置物品(使用 "model_id" PDC 鍵)
ItemStack placementItem = ModelItemFactory.createModelItem("lamp_post", Material.STICK);
// 從設定建立自訂物品(使用 "fmm_item_id" PDC 鍵)
PropScriptConfigFields config = ItemScriptManager.getItemDefinitions().get("magic_sword");
ItemStack customItem = ModelItemFactory.createCustomItem("magic_sword", config);
createModelItem(String modelId, Material material)-- 為道具建立放置物品。在 1.21.4+ 上,如果存在顯示 JSON 會自動套用 display model 渲染。createCustomItem(String itemId, PropScriptConfigFields config)-- 從統一設定建立帶有名稱、物品說明、附魔與 display model 的自訂物品。formatModelName(String modelId)-- 公用程式,將像01_em_flame_sword這樣的模型 ID 轉換為Flame Sword。
DisplayModelRegistry
追蹤哪些模型擁有可用的顯示 JSON 的簡單註冊表。
// 檢查模型是否註冊了顯示模型 JSON
boolean has3D = DisplayModelRegistry.hasDisplayModel("magic_sword");
register(String modelId)-- 註冊模型 ID(在重新載入期間內部呼叫)hasDisplayModel(String modelId)-- 如果此模型有.json顯示模型存在則回傳truegetRegisteredModels()-- 回傳所有已註冊顯示模型的模型 ID 的不可變Set<String>shutdown()-- 清除所有註冊
ItemScriptManager
管理自訂物品(YML 設定中設定了 material: 的模型)的每位玩家 Lua 腳本的生命週期。
// 取得所有已註冊的自訂物品定義
Map<String, PropScriptConfigFields> items = ItemScriptManager.getItemDefinitions();
// 取得玩家 + 物品的有效 Lua 腳本實例
ScriptInstance instance = ItemScriptManager.getActiveScript(playerUUID, "magic_sword");
// 取得玩家所有有效的腳本
Map<String, ScriptInstance> scripts = ItemScriptManager.getActiveScripts(playerUUID);
scanForCustomItems(File modelsFolder)-- 掃描模型 YML 設定以尋找自訂物品updateEquippedScripts(Player player)-- 比對已裝備物品與執行中的腳本,觸發 equip/unequip 鉤子removePlayer(Player player)-- 關閉玩家的所有腳本(在退出時呼叫)getItemDefinitions()-- 回傳物品 ID 對應到PropScriptConfigFields的對映
ScriptedItemAPI
供外部外掛與 FMM 腳本化物品系統整合的公開 API。這讓其他外掛可以將自己的 ItemStack 標記上 FMM 腳本化物品資料(PDC 標籤 + 物品模型),使 FMM 的 Lua 腳本鉤子能對這些物品觸發,而 FMM 不會覆寫物品的名稱、物品說明或附魔。
// 檢查腳本化物品定義是否存在
boolean exists = ScriptedItemAPI.isValidItemId("flame_blade");
// 將 FMM 腳本化物品資料套用到現有的 ItemStack
// 這會設定:
// - fmm_item_id PDC 標籤(讓 FMM 的腳本系統識別該物品)
// - 物品模型(1.21.4+),來自 FMM 的顯示模型註冊表
// 不會修改名稱、物品說明、附魔或任何其他物品屬性。
boolean success = ScriptedItemAPI.applyScriptedItemData(itemStack, "flame_blade");
// 取得腳本化物品的設定
PropScriptConfigFields config = ScriptedItemAPI.getItemConfig("flame_blade");
isValidItemId(String itemId)-- 如果物品 ID 已在 FMM 的物品定義中註冊則回傳trueapplyScriptedItemData(ItemStack itemStack, String itemId)-- 將 PDC 標籤和物品模型蓋印到現有的 ItemStack。成功則回傳true,物品 ID 無效或 ItemStack 沒有 meta 時回傳false。弓/弩注意: 如果給定的itemId沒有顯示模型但itemId + "_idle"有(即該物品有弓/弩狀態模型),該方法會自動使用_idle模型作為顯示模型getItemConfig(String itemId)-- 回傳指定物品 ID 的PropScriptConfigFields,找不到時回傳null
EliteMobs 透過 scriptedItem 設定欄位在內部使用此 API。當 EliteMobs 自訂物品設定 scriptedItem: flame_blade 時,EliteMobs 正常建構其物品(名稱、物品說明、附魔、等級),然後呼叫 ScriptedItemAPI.applyScriptedItemData() 在其上加入 FMM 的模型與腳本行為。
DisguiseAPI
玩家偽裝功能的公開入口點。第三方外掛應呼叫此類別,而非內部的 DisguiseManager,以使內部重構保持安全。
import com.magmaguy.freeminecraftmodels.api.DisguiseAPI;
// 將玩家偽裝為已載入的模型。會乾淨地取代任何現有的偽裝。
boolean ok = DisguiseAPI.disguise(player, "dragon");
// 取消偽裝(如果移除了偽裝則回傳 true)。
DisguiseAPI.undisguise(player);
// 查詢狀態。
boolean disguised = DisguiseAPI.isDisguised(player);
String modelID = DisguiseAPI.getDisguiseModelID(player); // 未偽裝時為 null
// 所有當前被偽裝玩家的快照。
Collection<Player> all = DisguiseAPI.getDisguisedPlayers();
disguise(Player, String modelID)-- 如果模型 ID 未載入則回傳falseundisguise(Player)-- 如果移除了偽裝則回傳trueisDisguised(Player)-- 快速布林檢查getDisguiseModelID(Player)-- 回傳活躍的模型 ID 或nullgetDisguisedPlayers()-- 偽裝中玩家的不可修改快照
被偽裝的玩家對其他人是隱形的,並保持這個狀態直到取消偽裝 — 牛奶桶、信標效果清除以及類似的互動都無法破壞隱形效果。
LocationAPI
供外掛貢獻地城偵測與區域保護檢查的公開 API。註冊的判斷會提供給 FMM 的 Lua em.location.is_in_dungeon 與 em.location.is_protected 檢查使用(由 pickupable.lua 與 storage_double.lua 等預製腳本使用)。
外掛傳入一般的 Predicate<Location>,所以沒有 shade 過的 FMM 類型會跨外掛 classloader。
import com.magmaguy.freeminecraftmodels.api.LocationAPI;
// 在你的外掛 enable 時,在 WorldGuard/EliteMobs/等等可用之後。
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 驗證註冊情況,該指令會回報即時的提供者數量並對當前位置測試這兩種判斷。
PropScriptConfigFields
模型 YML 設定檔的統一設定類別。同時被道具腳本與自訂物品使用。
# 範例:torch_01.yml
isEnabled: true
scripts:
- torch_glow.lua
material: STICK # 設定時,模型會成為自訂物品
name: "&eMagic Torch" # 自訂顯示名稱(可選)
lore: # 自訂物品說明行(可選)
- "&7Glows in the dark"
enchantments: # 附魔(可選,格式:NAME,LEVEL)
- "FIRE_ASPECT,1"
主要方法:isCustomItem()、getParsedMaterial()、getParsedEnchantments()、getScripts()。
Lua 腳本
FreeMinecraftModels 透過 MagmaCore 2.0 腳本引擎支援針對道具與自訂物品的 Lua 腳本。腳本檔案放置於 plugins/FreeMinecraftModels/scripts/,並透過放在模型檔案旁的同伴 YML 設定繫結到模型。
道具腳本鉤子
| 鉤子 | 觸發時機 |
|---|---|
on_spawn | 道具被生成到世界中 |
on_game_tick | 道具存在的每個 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 | 物品裝備期間的每個 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 表格
道具腳本會接收一個 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)-- 讓玩家騎上道具dismount(player)-- 讓玩家從道具上下來get_passengers()-- 回傳當前騎在道具上的玩家清單spawn_elitemobs_boss(filename, x, y, z)-- 在道具的相對位置生成 EliteMobs Boss
context.event:
- 在
on_left_click、on_right_click與on_projectile_hit中可用 cancel()、uncancel()、is_cancelledplayer-- 觸發事件的玩家
context.world:
spawn_entity(entity_type, location)-- 生成原版實體set_block_at(location, material)-- 在世界中設定方塊- 加上粒子、音效、方塊查詢、閃電以及附近實體查找
玩家物件(來自 context.event.player):
get_held_item()-- 回傳玩家手持的物品consume_held_item()-- 移除一個手持物品has_item(material)-- 檢查玩家是否擁有物品send_message(text)-- 向玩家發送聊天訊息game_mode-- 玩家當前的遊戲模式
道具腳本範例
function on_spawn(context)
context.prop.play_animation("idle", true, true)
end
function on_right_click(context)
context.prop.play_animation("activate", false, false)
end
物品腳本範例
function on_equip(context)
context.event.player.send_message("&6You equipped the Flame Blade!")
end
function on_attack_entity(context)
-- 命中時的火焰效果
context.event.player.send_message("&cBurn!")
end
function on_unequip(context)
context.event.player.send_message("&7Flame Blade sheathed.")
end
說明
- FreeMinecraftModels 是一個已安裝外掛的依賴,並非可嵌入的函式庫。
- 如果你的外掛需要新匯入的模型,請呼叫
ModeledEntityManager.reload(),而不要試著自行重建 FreeMinecraftModels 的狀態。 - Nightbreak 生態系中的所有外掛現在都依賴 MagmaCore 2.2.0-SNAPSHOT,其中包含 FreeMinecraftModels 道具腳本與 EliteMobs Lua 能力共用的 Lua 腳本引擎,加上共用的
LocationQueryRegistry與WorldFolderResolver。 - FreeMinecraftModels 將 WorldGuard、WorldEdit、GriefPrevention 與 Vault 宣告為
softdepend。任一項都不是啟動外掛所必需的,但在存在時會解鎖特定功能:WorldGuard/WorldEdit/GriefPrevention 提供給LocationAPI,Vault 則啟用家具商店。