跳到主要内容

MythicMobs 兼容工具

Universal-Mythic(简称 UM)是 MythicMobs 跨版本兼容工具,抹平 MM4 和 MM5 的巨大 API 差异,提供统一的接口用于怪物、物品、技能的管理和交互。

重要提示

MythicMobs 的维护与开发不规范,MM4 与 MM5 的 API 差距巨大。使用 UM 工具可以避免在升级 MM 版本时重写代码。

依赖配置

Gradle 配置

taboolib {
description {
dependencies {
name("MythicMobs")
}
}
// ⚠️ 必须重定向,否则多个使用 UM 的插件会冲突
relocate("ink.ptms.um", "你的包名.um")
}

repositories {
maven("https://nexus.maplex.top/repository/maven-public/")
}

dependencies {
taboo("ink.ptms:um:1.2.1")
}
必须重定向

无论什么版本都需要重定向 ink.ptms.um 包名,否则两个使用 UM 的插件会产生冲突导致崩溃。

版本说明

  • TabooLib 6.2.X: 推荐使用 ink.ptms:um:1.2.1
  • TabooLib 6.0.X: 使用 ink.ptms:um:1.0.0-beta-34

最新版本请查看 GitHub Releases

基础 API

所有 API 通过 Mythic.API 入口访问:

import ink.ptms.um.Mythic

// 判断 MythicMobs 是否已加载
if (Mythic.isLoaded()) {
val api = Mythic.API

// 判断是否 MM4.X 版本
if (api.isLegacy) {
println("MythicMobs 4.X")
} else {
println("MythicMobs 5.X")
}
}

物品管理

获取 MythicItem

// 获取 MythicItem 实例
val item = Mythic.API.getItem("example_sword")

if (item != null) {
println("物品 ID: ${item.internalName}")
println("显示名称: ${item.displayName}")
println("数量: ${item.amount}")

// 生成 ItemStack
val itemStack = item.generateItemStack(5)
}

获取 ItemStack 对应的 MythicItem ID

val itemStack: ItemStack = player.inventory.itemInMainHand

// 获取物品 ID
val itemId = Mythic.API.getItemId(itemStack)
if (itemId != null) {
println("这是 MythicItem: $itemId")
}
版本差异
  • MM4.X: getItemId() 返回物品名称
  • MM5.X: getItemId() 返回内部 NBT 标识

UM 自动处理版本差异,开发者无需关心。

直接构建 ItemStack

// 不指定玩家
val itemStack = Mythic.API.getItemStack("example_sword")

// 为特定玩家构建(支持变量替换)
val itemStack = Mythic.API.getItemStack("example_sword", player)

获取所有物品

// 获取所有物品 ID 列表
val itemIds = Mythic.API.getItemIDList()
itemIds.forEach { println(it) }

// 获取所有 Item 实例
val items = Mythic.API.getItemList()
items.forEach { item ->
println("${item.internalName}: ${item.displayName}")
}

怪物管理

获取 ActiveMob

// 通过实体获取
val entity: Entity = ...
val mob = Mythic.API.getMob(entity)

if (mob != null) {
println("怪物 ID: ${mob.id}")
println("显示名称: ${mob.displayName}")
println("等级: ${mob.level}")
println("姿态: ${mob.stance}")
println("阵营: ${mob.faction}")
}

// 通过 UUID 获取
val mob = Mythic.API.getMob(UUID.fromString("..."))

Mob 属性

val mob = Mythic.API.getMob(entity) ?: return

// 获取 MobType
val mobType = mob.type
println("怪物类型: ${mobType.id}")

// 获取实体对象
val bukkitEntity = mob.entity

// 获取实体类型
val entityType = mob.entityType

// 访问配置
val config = mob.config
val health = config.getDouble("Health", 20.0)

仇恨管理

val mob = Mythic.API.getMob(entity) ?: return
val target = player as LivingEntity

// 增加仇恨
mob.addThreat(entity, target, 100.0)

// 减少仇恨
mob.reduceThreat(entity, target, 50.0)

获取 MobType

// 获取 MobType 实例
val mobType = Mythic.API.getMobType("example_boss")

if (mobType != null) {
println("怪物 ID: ${mobType.id}")
println("显示名称: ${mobType.displayName}")
println("实体类型: ${mobType.entityType}")

// 生成怪物到指定位置
val spawnedMob = mobType.spawn(location, level = 5.0)
}

获取所有怪物

// 获取所有 Mob ID 列表
val mobIds = Mythic.API.getMobIDList()
mobIds.forEach { println(it) }

技能系统

释放技能

// 基础用法
Mythic.API.castSkill(
caster = player,
skillName = "example_skill"
)

// 完整参数
Mythic.API.castSkill(
caster = player, // 施法者
skillName = "example_skill", // 技能名称
trigger = targetEntity, // 触发器(可选)
origin = player.location, // 技能释放位置
et = setOf(targetEntity1, targetEntity2), // 目标实体
lt = setOf(location1, location2), // 目标位置
power = 1.5f // 技能强度
)

技能触发器

// 获取技能触发器
val trigger = Mythic.API.getSkillTrigger("onAttack")

// 获取默认触发器
val defaultTrigger = Mythic.API.getDefaultSkillTrigger()

从字符串创建技能

// 将 MythicMobs 技能字符串转换为技能实例
val skill = Mythic.API.getSkillMechanic("message{m=\"Hello World\"}")

if (skill != null) {
println("技能延迟: ${skill.delay}")

// 执行技能
skill.execute(
trigger = Mythic.API.getDefaultSkillTrigger(),
entity = player,
target = targetEntity
)

// 检查冷却
if (skill.onCooldown(player)) {
val cooldown = skill.getCooldown(player)
println("冷却中: $cooldown 秒")
}

// 设置冷却
skill.setCooldown(player, 10.0)
}

获取玩家目标

// 获取玩家准星瞄准的实体
val targetEntity = Mythic.API.getTargetedEntity(player)

if (targetEntity != null) {
println("玩家正在瞄准: ${targetEntity.type}")
}

注册自定义技能

EntityTargetSkill(实体目标技能)

import ink.ptms.um.event.MobSkillLoadEvent
import ink.ptms.um.skill.type.EntityTargetSkill
import ink.ptms.um.skill.SkillResult
import taboolib.common.platform.event.SubscribeEvent

@SubscribeEvent
fun onSkillLoad(event: MobSkillLoadEvent) {
if (event.nameIs("damage_ap", "ap_damage")) {
event.register(object : EntityTargetSkill {

// 从配置读取参数(支持占位符)
val attribute = event.config.getPlaceholderString(
arrayOf("attribute", "att"),
""
)

val damage = event.config.getPlaceholderDouble(
arrayOf("damage", "d"),
10.0
)

override fun cast(meta: SkillMeta, entity: Entity): SkillResult {
if (entity !is LivingEntity) return SkillResult.ERROR

val damager = meta.caster.entity as? LivingEntity
?: return SkillResult.ERROR

// 获取占位符值
val attributeValue = attribute.get(meta.caster)
val damageValue = damage.get(meta.caster)

// 执行技能逻辑
entity.damage(damageValue, damager)

return SkillResult.SUCCESS
}
})
}
}

技能配置示例:

# MythicMobs/Skills/custom.yml
damage_ap:
Skills:
- damage_ap{attribute="fire&ice";damage=50} @Target

NoTargetSkill(无目标技能)

@SubscribeEvent
fun onSkillLoad(event: MobSkillLoadEvent) {
if (event.nameIs("aoe_explosion")) {
event.register(object : NoTargetSkill {

val radius = event.config.getPlaceholderDouble(
arrayOf("radius", "r"),
5.0
)

override fun cast(meta: SkillMeta): SkillResult {
val caster = meta.caster.entity
val location = caster.location
val radiusValue = radius.get(meta.caster)

// 获取范围内的实体
val nearbyEntities = caster.getNearbyEntities(
radiusValue,
radiusValue,
radiusValue
)

nearbyEntities.filterIsInstance<LivingEntity>().forEach {
it.damage(20.0)
}

return SkillResult.SUCCESS
}
})
}
}

LocationTargetSkill(位置目标技能)

@SubscribeEvent
fun onSkillLoad(event: MobSkillLoadEvent) {
if (event.nameIs("spawn_particle")) {
event.register(object : LocationTargetSkill {

val particle = event.config.getString("particle", "FLAME")
val amount = event.config.getInt("amount", 10)

override fun cast(meta: SkillMeta, location: Location): SkillResult {
location.world?.spawnParticle(
Particle.valueOf(particle),
location,
amount
)

return SkillResult.SUCCESS
}
})
}
}

SkillConfig 配置读取

SkillConfig 提供丰富的配置读取方法:

val config = event.config

// 基础类型
val bool = config.getBoolean("enabled", true)
val string = config.getString("message", "默认消息")
val int = config.getInt("amount", 10)
val double = config.getDouble("damage", 5.0)
val float = config.getFloat("speed", 1.0f)
val long = config.getLong("cooldown", 1000L)

// 支持别名(多个 key)
val value = config.getString(arrayOf("damage", "dmg", "d"), "10")

// 带占位符的值(支持 MythicMobs 变量)
val placeholderString = config.getPlaceholderString(
arrayOf("message", "msg"),
"默认值"
)
val placeholderInt = config.getPlaceholderInt(
arrayOf("amount", "amt"),
10
)
val placeholderDouble = config.getPlaceholderDouble(
arrayOf("damage", "dmg"),
10.0
)
val placeholderFloat = config.getPlaceholderFloat(
arrayOf("speed"),
1.0f
)

// 在技能中使用占位符值
override fun cast(meta: SkillMeta, entity: Entity): SkillResult {
val message = placeholderString.get(meta.caster)
val damage = placeholderDouble.get(meta.caster)
// ...
}

// 颜色
val color = config.getColor("color", "255,0,0")

SkillResult 返回值

返回值说明
SUCCESS技能成功执行
ERROR执行出错
INVALID_TARGET无效目标
INVALID_CONFIG配置错误
CONDITION_FAILED条件未满足
REQUIRES_PREMIUM需要高级版
INVALID_VERSION版本不兼容
MISSING_COMPATIBILITY缺少兼容性依赖

注册自定义掉落

import ink.ptms.um.event.MobDropLoadEvent
import taboolib.common.platform.event.SubscribeEvent

@SubscribeEvent
fun onDropLoad(event: MobDropLoadEvent) {
// 注册自定义掉落
event.registerItem { dropMeta ->
// dropMeta.dropper: 掉落者(施法者)
// dropMeta.cause: 触发者(杀手)
// dropMeta.amount: 掉落数量

val killer = dropMeta.cause as? Player
if (killer != null && killer.hasPermission("example.bonus")) {
// 给有权限的玩家额外掉落
ItemStack(Material.DIAMOND, dropMeta.amount.toInt() * 2)
} else {
ItemStack(Material.DIAMOND, dropMeta.amount.toInt())
}
}
}

动态注册与移除

注册物品

import java.io.File

val itemFile = File("path/to/items.yml")

// 注册物品
val success = Mythic.API.registerItem(itemFile, "custom_sword")
if (success) {
println("物品注册成功")
} else {
println("物品已存在或注册失败")
}

// 移除物品
val removed = Mythic.API.unregisterItem("custom_sword")
if (removed) {
println("物品已移除")
}

注册怪物

val mobFile = File("path/to/mobs.yml")

// 注册怪物
val success = Mythic.API.registerMob(mobFile, "custom_boss")
if (success) {
println("怪物注册成功")
} else {
println("怪物已存在或注册失败")
}

// 移除怪物
val removed = Mythic.API.unregisterMob("custom_boss")
if (removed) {
println("怪物已移除")
}

实际应用示例

自定义伤害技能系统

object CustomSkillManager {

@SubscribeEvent
fun registerSkills(event: MobSkillLoadEvent) {
// 真实伤害技能
if (event.nameIs("true_damage")) {
event.register(object : EntityTargetSkill {
val damage = event.config.getPlaceholderDouble(
arrayOf("damage", "d"),
10.0
)

override fun cast(meta: SkillMeta, entity: Entity): SkillResult {
if (entity !is LivingEntity) return SkillResult.INVALID_TARGET

val damageValue = damage.get(meta.caster)
val health = entity.health - damageValue
entity.health = health.coerceAtLeast(0.0)

return SkillResult.SUCCESS
}
})
}

// 范围减速技能
if (event.nameIs("area_slow")) {
event.register(object : LocationTargetSkill {
val radius = event.config.getDouble("radius", 5.0)
val duration = event.config.getInt("duration", 100)
val amplifier = event.config.getInt("amplifier", 1)

override fun cast(meta: SkillMeta, location: Location): SkillResult {
location.world?.getNearbyEntities(location, radius, radius, radius)
?.filterIsInstance<LivingEntity>()
?.forEach { entity ->
entity.addPotionEffect(
PotionEffect(
PotionEffectType.SLOW,
duration,
amplifier
)
)
}

return SkillResult.SUCCESS
}
})
}
}
}

Boss 战斗系统

object BossSystem {

fun spawnBoss(location: Location, playerCount: Int) {
val mobType = Mythic.API.getMobType("world_boss") ?: return

// 根据玩家数量调整等级
val level = 1.0 + (playerCount * 0.5)
val boss = mobType.spawn(location, level)

println("Boss 已生成: ${boss.displayName} (Lv.$level)")
}

@SubscribeEvent
fun onBossDeath(event: MobDeathEvent) {
val mob = event.mob

if (mob.id == "world_boss") {
val killer = event.killer as? Player ?: return

// 给予击杀奖励
val reward = Mythic.API.getItemStack("boss_reward", killer)
if (reward != null) {
killer.inventory.addItem(reward)
}
}
}
}

物品识别系统

object ItemChecker {

fun checkPlayerHand(player: Player) {
val itemStack = player.inventory.itemInMainHand
val itemId = Mythic.API.getItemId(itemStack)

if (itemId != null) {
val item = Mythic.API.getItem(itemId)
if (item != null) {
player.sendMessage("你手持 MythicItem: ${item.displayName}")

// 读取物品配置
val config = item.config
val customValue = config.getString("CustomData.power", "0")
player.sendMessage("物品能量: $customValue")
}
} else {
player.sendMessage("这不是 MythicItem")
}
}
}

常见问题

如何判断 MythicMobs 是否已加载?

if (Mythic.isLoaded()) {
// MythicMobs 已加载
} else {
// MythicMobs 未加载
}

MM4 和 MM5 的主要区别是什么?

UM 自动处理以下版本差异:

  • 物品 ID 获取方式:MM4 使用物品名,MM5 使用内部 NBT
  • API 结构:完全不同的包名和类结构
  • 技能系统:不同的触发器和参数系统

使用 UM 后无需关心这些差异。

为什么必须重定向包名?

多个插件使用 UM 时,如果不重定向,会加载同一个 UM 实例导致冲突。重定向后每个插件都有独立的 UM 副本。

技能配置中的占位符如何使用?

使用 getPlaceholder*() 方法读取的值支持 MythicMobs 的占位符系统,如 <caster.level><target.hp> 等。

如何获取技能的 SkillMeta?

在自定义技能的 cast() 方法中,meta 参数包含:

  • meta.caster: 施法者信息
  • meta.trigger: 触发者
  • meta.origin: 原点位置
  • meta.power: 技能强度
  • meta.entityTargets: 目标实体集合
  • meta.locationTargets: 目标位置集合

如何调试自定义技能?

override fun cast(meta: SkillMeta, entity: Entity): SkillResult {
try {
// 技能逻辑
return SkillResult.SUCCESS
} catch (e: Exception) {
e.printStackTrace()
return SkillResult.ERROR
}
}

检查服务器日志获取错误信息。