跳到主要内容

FreeMinecraftModels API 与开发者指南

生成的 Java 参考文档提供可搜索的类、方法和签名,并记录各模块的源码版本与提交。

FreeMinecraftModels 既是独立插件,也为其他插件提供 API 接口。

通用依赖和运行环境配置请参阅 Java API 索引。依赖示例使用已发布的 2.12.1,当前源码版本为 2.12.3。请使用与服务器已安装插件一致的依赖版本。

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>2.12.1</version>
<scope>provided</scope>
</dependency>

请使用 compileOnly/provided 范围。不要将插件着色(shade)进你自己的 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);

如果请求的模型 ID 未加载,所有创建路径都会返回 null

此外,当目标方块上已经加载了同一模型的道具时,PropEntity.spawnPropEntity 也会返回 null —— 重复的道具会被拒绝而不是叠加,并会记录一条 [FMM Props] Prevented duplicate prop spawn ... 警告。请始终对它的返回值做 null 检查;非 null 的返回值是道具确实被创建出来的唯一确认。

createWithInvisibility 是一个变体,会应用隐身药水效果而不是从客户端隐藏该实体。这样实体在客户端仍受追踪,对于载具转向是必需的(被 /fmm mount 内部使用)。

实用的运行时方法

  • ModeledEntity#setDisplayName(String) —— 设置模型的名牌
  • ModeledEntity#setDisplayNameVisible(boolean) —— 控制名牌是否可见
  • ModeledEntity#setLeftClickCallback(...)
  • ModeledEntity#setRightClickCallback(...)
  • ModeledEntity#setHitboxContactCallback(...)
  • ModeledEntity#setModeledEntityHitByProjectileCallback(...)
  • ModeledEntity#playAnimation(String, boolean blend, boolean loop) —— 当名称既不匹配内置状态也不匹配模型中的动画时返回 falseblend 是排队而不是交叉淡入淡出;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>(可用于附加额外文字)
  • 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()

名牌与注视目标

ModeledEntity 提供 setDisplayNameLines(List<String>)setDisplayNameScale(float)setDisplayNameLineGap(double)setDisplayNameVisible(boolean)getNameplateLocation()。名牌骨骼可选;没有时,名牌锚点位于缩放后的碰撞箱上方。

DynamicEntity.setLookTarget(Location) 为骨架设置视觉上的注视目标,并复制传入的位置,不会旋转或移动底层实体。传入 null 可解除。

PropEntity 专有方法

  • PropEntity.isPropEntity(ArmorStand) / PropEntity.getPropEntityID(ArmorStand) —— 从道具背后的盔甲架识别该道具
  • PropEntity.hasLoadedPropOnSameBlock(String entityID, Location) —— spawnPropEntity 内部执行的重复检查;如果你想分支处理而不是判空,可以先调用它
  • 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() —— voxelize: / solidify: YML 字段的运行时等价物
  • PropEntity#showFakePropBlocksToPlayer(Player) / showRealBlocksToPlayer(Player) 以及对应的 ...ToAllPlayers() 变体 —— 控制那些仅存在于数据包中、为实体化道具提供客户端碰撞的屏障方块

魔法武器集成

使用魔法服务前先检查 MagicWeaponAPI.isOperational()isWeapon(String) 检查武器定义 ID,applyWeaponData(ItemStack, String) 应用武器身份与显示数据,保留原物品的名称、描述、等级和附魔。服务已注册不代表它已能正常工作。

可选的 MagicAttackResolver 能拒绝施法、筛选目标并排序、保存发射时的战斗信息以及计算伤害。必须使用传入的一次性 MagicDamageApplication,不能直接对目标造成伤害。输入、飞行、碰撞和伤害应用由 FMM 负责。

MagicProjectileTravelEvent 是同步、可取消的碰撞查询,会检查完整移动线段,也包括命中时的检查。取消会吸收飞行物,不造成伤害或爆炸。同一线段可能被检查多次,因此监听器不能把它当作伤害通知。

事件接口

右键输入首先对真实的底层实体触发 ModeledEntityInteractEvent。它继承 PlayerInteractEntityEvent 的处理器,因此常规实体保护监听器可以取消它。取消后不会触发模型右键事件及回调。这项检查与放置、破坏方块的权限不同。

通用交互事件:

  • ModeledEntityLeftClickEvent
  • ModeledEntityRightClickEvent
  • ModeledEntityHitboxContactEvent
  • ModeledEntityHitByProjectileEvent

这四个都是可取消的 Bukkit 事件;FMM 内置的检测路径会在服务器主线程上派发它们。取消其中之一会阻止 FMM 对应的回调/默认行为。碰撞箱接触扫描每两个服务器 tick 运行一次;点击事件仍有一个短暂的按玩家去重窗口,以确保数据包交互路径和 OBB 射线检测路径不会把同一个动作触发两次。

生命周期事件:

  • FmmReloadedEvent —— 完整初始化成功后,安排在 40 刻后于主线程触发,包括启动和成功的完整重新加载。仅重新加载导入内容的流程也会在成功时触发。准备阶段被拒绝的请求保留当前运行状态,不会发出重新加载完成事件

公开的交互接口刻意与模型类型无关。旧的 StaticEntity*EventDynamicEntity*EventPropEntity*Event 变体,以及 ResourcePackGenerationEvent,都已不再属于当前 API。请改用上面那四个通用的模型化实体事件和 FmmReloadedEvent

持有长期 DynamicEntityPropEntity 引用的消费者插件(EliteMobs、BetterStructures 等)必须处理此事件,在仍存活的底层实体上重新创建其模型附着。否则那些实体会在重载后变为不可见,因为 FMM 在 onDisable 期间已经销毁了 display 实体,而消费者的引用已经失效。

@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 事件。

关键细节:

  • 检测会检查已跟踪位置之间的线段,以发现穿过薄模型的情况;没有前一个位置时,改用重叠检查
  • 该轨迹上的候选目标按由近及远解析,与模型注册表的遍历顺序无关
  • 非穿透抛射物只会被路由到一个模型化目标。带穿透的箭矢可以按飞行顺序击中 穿透等级 + 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 JSON 会自动应用 display 模型渲染。
  • createCustomItem(String itemId, PropScriptConfigFields config) —— 从统一配置创建带有名称、Lore、附魔和 display 模型的自定义物品。
  • formatModelName(String modelId) —— 工具方法,将 01_em_flame_sword 之类的模型 ID 转换为 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) —— 检查显示模型是否已注册;格式错误的 .json 不会仅因文件存在就被注册
  • getRegisteredModels() —— 返回所有已注册 display 模型 ID 的不可变 Set<String>
  • shutdown() —— 清除所有注册

ItemScriptManager

虽然保留了原类名,它现在负责物品定义和魔法武器目录,不再运行装备物品的脚本。getItemDefinitions() 返回已注册物品定义,getWeaponCatalog() 返回已验证的武器定义。旧的活动脚本、装备更新和玩家脚本清除 API 已移除。

ScriptedItemAPI

  • isValidItemId(String):检查精确的物品定义 ID,即去掉 .yml 的配置文件名。
  • getItemConfig(String):返回定义,不存在则返回 null
  • applyScriptedItemData(ItemStack, String):设置 fmm_item_id,并在有已注册显示模型时应用该模型。物品 ID 无效或缺少物品元数据时返回 false

此方法不会改变原物品的名称、描述或附魔,也不会附加 Lua 行为。附魔请通过 MagmaCore 单独应用。弓和弩的拉弓状态属于基础显示模型定义;不要给物品 ID 添加 _idle

ModelItemAPI

如果只需要 FMM 显示模型,请使用 ModelItemAPI.applyDisplayModel(ItemStack, String)。它检查显示模型注册表,不设置物品定义的身份信息。这与选择 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) —— 尝试替换前先移除旧伪装;未知 ID 会在移除后返回 false。请传入在线玩家和准确的已加载 ID;API 不会规范化 ID 或检查命令权限
  • undisguise(Player) —— 若有伪装被移除则返回 true
  • isDisguised(Player) —— 快速布尔检查
  • getDisguiseModelID(Player) —— 返回活动的模型 ID 或 null
  • getDisguisedPlayers() —— 不可修改的伪装玩家快照

FMM 结合玩家默认可见性、隐身药水效果和装备显示覆盖。只有本次伪装最初添加的效果被移除时才会补回;伪装前已有的效果不由其管理或恢复。命令会先验证模型再替换,与 API 不同。生命周期及客户端显示限制见玩家伪装

LocationAPI

供插件注册地下城检测和区域保护检查的公共 API。注册的谓词用于 FMM 的 Lua 检查 em.location.is_in_dungeonem.location.is_protected,例如内置的 pickupable.luastorage_double.lua 脚本会使用它们。

插件传入的是简单的 Predicate<Location>,因此没有被着色的 FMM 类型跨插件类加载器。

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 适配器先遵循 WorldGuard 自身的绕过设置,然后交给 WorldGuard 的 testBuild —— 因此区域成员和拥有者可以正常建造
内置 GriefPrevention 适配器该位置没有领地即视为允许;在领地内则交给 GriefPrevention 对该玩家的建造权限判定
通过 LocationAPI.registerProtectionProvider 注册的提供者回退到 !isProtected(location) —— 仅基于位置,会阻止所有人在你的谓词判定为受保护的位置建造

Predicate<Location> 无法表达针对具体玩家的权限。如果你的区域系统需要按玩家检查权限,请直接实现 MagmaCore 的 RegionProtectionProvider,覆盖 canBuild,并通过 LocationQueryRegistry.registerProtectionProvider 注册。

还有两个值得了解的行为:

  • 适配器失败时以拒绝方式失败。 如果某个提供者在建造查询期间抛出异常,放置会被拒绝,并记录一条指明该提供者的警告。它绝不会悄悄地退化为"允许"。
  • 每个已注册的提供者都必须同意。 第一个说"不"的提供者说了算;通过 LocationOwnership 注册的归属提供者仍然是仅基于位置的,会阻止所有人。

物品放置先检查相邻格。体素化道具还会按四舍五入且至少一格的碰撞箱尺寸,检查旋转后的占用范围;数据包屏障体积使用另一种计算。直接调用 PropEntity.spawnPropEntity 不会执行玩家放置物品的检查。通过其他插件提供放置功能前,请阅读配置

PropScriptConfigFields

读取模型旁边的 YAML,用于道具和物品定义。道具的 scripts: 列表会绑定独立的 Lua 脚本。指定 material: 则注册手持物品,不能再使用非空的 scripts: 列表。

分别适用的示例见模型配置,武器配置格式见魔法武器

Lua 脚本

FMM 通过 MagmaCore 引擎支持道具 Lua 脚本。文件放在 plugins/FreeMinecraftModels/scripts/,通过模型旁边的 YML 绑定。磁盘上的文件名必须以 .lua 结尾;配置中可包含或省略扩展名。

道具会把 scripts: 中的所有脚本绑定为独立实例。手持物品的效果通过独立的附魔提供者执行,不能使用非空的物品 scripts: 列表。

道具脚本钩子

钩子触发时机
on_spawn道具被生成到世界中
on_game_tick道具存在期间的每个 tick
on_zone_enter玩家进入脚本创建的受监视区域
on_zone_leave玩家离开脚本创建的受监视区域
on_destroy道具被移除
on_left_click玩家左键点击道具
on_right_click玩家右键点击道具
on_projectile_hit保留项:校验时会被接受,但在当前运行时并不会派发给道具脚本

物品脚本钩子

装备物品的 Lua 钩子及 context.item 已移除。物品效果请使用附魔定义。道具钩子仍受支持。

道具脚本上下文表

道具脚本会收到一个 context 表。下面是关键 API 的简要说明 —— 完整内容请参阅 Lua 道具 API

context.prop

  • model_id —— 蓝图模型名称
  • current_location —— 道具的当前位置
  • play_animation(name, blend, loop) —— 请求指定动画(blend 与 loop 默认为 true);接受请求不保证立即播放
  • stop_animation() —— 清空播放队列,若有 idle 则返回该动画;不能中断终止状态的死亡动画
  • hurt_visual() —— 在道具上播放受伤(红色闪烁)视觉效果
  • pickup() —— 排入一次移除道具并掉落其放置物品的操作
  • mount(player) —— 排入一次骑乘尝试;返回 true 只表示玩家和骑乘管理器有效,并不表示最终分配到了座位
  • dismount(player) —— 排入一次下马检查;返回 true 只表示玩家和骑乘管理器有效
  • get_passengers() —— 返回当前骑乘该道具的玩家列表
  • spawn_elitemobs_boss(filename, x, y, z) —— 在道具当前所在世界的绝对坐标处生成一个 EliteMobs Boss

context.event

  • 在道具的 on_left_clickon_right_clickon_zone_enteron_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.playercontext.event.player):

  • get_held_item() —— 返回玩家手持的物品;type 是大写的 Bukkit 材质名称
  • is_holding_elitemobs_item(filename):检查主手物品的 EliteMobs 文件名标签,例如 shroomwhispers_shears.yml。显示名称和翻译不会影响此检查。空手、物品不匹配或 EliteMobs 集成不可用时返回 false
  • consume_held_item() —— 安排移除一个主手物品,以任务执行时的主手内容为准;不返回付款结果
  • has_item(material) —— 检查玩家是否拥有某物品
  • send_message(text) —— 向玩家发送聊天消息
  • game_mode —— 玩家当前的游戏模式

使用文件名标识 EliteMobs 任务物品:

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

此方法由 FMM 的 EliteMobs 集成提供。重命名普通物品不会为其添加所需的文件名标签。

道具脚本示例

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
}

物品脚本示例

旧的装备物品示例已不受支持。请参阅魔法武器附魔定义,使用当前系统创建物品。

说明

  • FreeMinecraftModels 是作为已安装插件的依赖,而不是可嵌入的库。
  • 如果你的插件需要刚导入的模型,请调用 ModeledEntityManager.reload(),而不要尝试自行重建 FreeMinecraftModels 的状态。
  • 当前 FMM 源码依赖 MagmaCore 2.2.0-SNAPSHOT,由其提供共享 Lua 引擎、LocationQueryRegistryWorldFolderResolver
  • FreeMinecraftModels 将 WorldGuard、WorldEdit、GriefPrevention、Vault、floodgate 和 Geyser-Spigot 声明为 softdepend。WorldGuard 与 GriefPrevention 提供内置的玩家保护适配器;WorldEdit 是 WorldGuard 的依赖项,不是独立的 FMM 保护适配器。商店需要 Vault、经济提供者及已启用的商店配置。Bedrock 显示也需要兼容的资源包与集成路径。
  • ModeledEntityManager.reload() 请求与 /fmm reload 相同的异步准备和完整生命周期,不会等待完成。请从主线程调用,并以成功的生命周期反馈确认结果,而不是 void 返回值。无效的候选内容可能导致重新加载被拒绝,同时保留运行中的状态。