伤害类型与伤害来源(Damage Types & Damage Sources)
伤害类型 (damage type)用于表示对一个 实体 造成的是哪种伤害——比如物理伤害、火焰伤害、溺水伤害、魔法伤害、虚空伤害等。区分不同的伤害类型可以用于实现各种免疫机制(例如烈焰人不会受到火焰伤害)、附魔效果(比如爆炸保护只会防护爆炸伤害)以及更多其他用途。
可以把伤害类型看作是伤害来源(damage source)的模板。换句话说,伤害来源可以理解为某个伤害类型的一个实例。伤害类型在代码中以 ResourceKey 的形式存在,但其所有属性都定义在数据包(data pack)中。而伤害来源则是在游戏运行时根据数据包文件中的值按需创建的。伤害来源还可以包含更多上下文信息,比如攻击的实体。
创建伤害类型(Creating Damage Types)
首先,你需要创建自己的 DamageType。DamageType 属于 数据包注册表(datapack registry),因此新的 DamageType 并不是通过代码注册的,而是在添加对应的数据文件后自动注册。不过,我们仍然需要为代码提供一个获取伤害来源的入口。可以通过指定一个 资源键(resource key) 来实现:
public static final ResourceKey<DamageType> EXAMPLE_DAMAGE =
ResourceKey.create(Registries.DAMAGE_TYPE, ResourceLocation.fromNamespaceAndPath(ExampleMod.MOD_ID, "example"));
现在我们可以在代码中引用它了,接下来需要在数据文件中指定一些属性。数据文件的位置为 data/examplemod/damage_type/example.json(将 examplemod 和 example 替换为你的模组 id 和资源名称),内容如下:
{
// 此伤害类型的死亡消息 ID。完整的死亡消息翻译键将会是
// "death.attack.examplemod.example"(其中 mod id 和 name 会被替换)。
"message_id": "examplemod.example",
// 此伤害类型的伤害数值是否随难度变化。原版可用的取值有:
// - "never":无论难度如何,伤害值都保持不变。通常用于玩家造成的伤害类型。
// - "when_caused_by_living_non_player":如果伤害由某种活体实 体(包括间接方式,例如骷髅射出的箭)造成,且不是玩家,则伤害值会随难度变化。
// - "always":伤害值总是随难度变化。通常用于爆炸类伤害。
"scaling": "when_caused_by_living_non_player",
// 受到该类型伤害时造成的饥饿值消耗。
"exhaustion": 0.1,
// 受到该类型伤害时应用的伤害效果(目前仅支持音效)。可选项。
// 原版可用的取值有 "hurt"(默认)、"thorns"、"drowning"、"burning"、"poking" 和 "freezing"。
"effects": "hurt",
// 死亡消息类型。决定死亡消息的构建方式。可选项。
// 原版可用的取值有 "default"(默认)、"fall_variants" 和 "intentional_game_design"。
"death_message_type": "default"
}
scaling、effects 和 death_message_type 这三个字段在内部分别由枚举 DamageScaling、DamageEffects 和 DeathMessageType 控制。如果有需要,这些枚举可以被 扩展 以添加自定义取值。
此格式同样被用于原版的伤害类型,资源包开发者可以根据需要修改这些数值。
创建与使用伤害来源(Damage Sources)
DamageSource 通常会在调用 Entity#hurt 时即时创建。请注意,由于伤害类型是一个 数据包注册表(datapack registry),因此你需要通过 RegistryAccess 来查询它们,可以通过 Level#registryAccess 获得。要创建一个 DamageSource,只需调用 DamageSource 构造函数,最多可传入四个参数:
DamageSource damageSource = new DamageSource(
// 要使用的伤害类型持有者(damage type holder)。需要从注册表(Registry)中查询。这是唯一必需的参数。
registryAccess.lookupOrThrow(Registries.DAMAGE_TYPE).getOrThrow(EXAMPLE_DAMAGE),
// 直接实体(direct entity)。例如,如果一个骷髅射中了你,骷髅是造成伤害的实体
// (= 上面的参数),而箭矢则是直接实体(= 这个参数)。类似于造成伤害的实体,这个参数并非总是适用,因此可以为 null。可选,默认为 null。
null,
// 造成伤害的实体(causing entity)。并非总是适用(比如掉出世界时),因此也可以为 null。可选,默认为 null。
null,
// 伤害来源的位置(damage source position)。很少用到,比如游戏设计中有意为之的情况
// (= 下界床爆炸)。可以为 null,且为可选参数,默认值为 null。
null
);
DamageSources#source 是对 new DamageSource 的包装,它会交换第二和第三个参数(直接实体和造成伤害的实体)。请确保你为正确的参数提供了正确的值。
如果某个 DamageSource 完全没有实体或位置信息,那么将其缓存在一个字段中是合理的。对于包含实体或位置信息的 DamageSource,通常会添加一些辅助方法,例如:
public static DamageSource exampleDamage(Entity causer) {
return new DamageSource(
causer.level().registryAccess().lookupOrThrow(Registries.DAMAGE_TYPE).getOrThrow(EXAMPLE_DAMAGE),
causer);
}
原版(Vanilla)的 DamageSource 工厂方法可以在 DamageSources 中找到,原版的 DamageType 资源键可以在 DamageTypes 中找到。实体(Entity)还提供了 Entity#damageSources 方法,这是一个便捷的获取 DamageSources 实例的方法。
伤害来源(damage sources)最主要的用途就是 Entity#hurt。每当一个实体受到伤害时,都会调用这个方法。要用我们自定义的伤害类型对实体造成伤害,只需直接调用 Entity#hurt:
// 第二个参数是伤害值,单位为半颗心。
entity.hurt(exampleDamage(player), 10);
其他与伤害类型相关的行为,比如无敌检查,通常是通过伤害类型 标签 实现的。这些标签既有 Minecraft 自带的,也有 NeoForge 提供的,分别可以在 DamageTypeTags 和 Tags.DamageTypes 下找到。
数据生成(Datagen)
更多信息请参考 数据包注册表的数据生成。
伤害类型的 JSON 文件可以通过 数据生成 自动生成。由于伤害类型是一个数据包注册表(datapack registry),我们可以通过 GatherDataEvent#createDatapackRegistryObjects 添加一个 DatapackBuiltinEntriesProvider,并将我们的伤害类型放入 RegistrySetBuilder 中:
// 在你的数据生成(datagen)类中
@SubscribeEvent
public static void onGatherData(GatherDataEvent.Client event) {
event.createDatapackRegistryObjects(new RegistrySetBuilder()
// 为伤害类型(damage types)添加一个数据包内置条目提供者(datapack builtin entry provider)。
// 如果这个 lambda 表达式变得更长,出于可读性考虑,应该将其提取到一个单独的方法中。
.add(Registries.DAMAGE_TYPE, bootstrap -> {
// 使用 new DamageType() 创建一个代码中的伤害类型表示。
// 这些参数依次对应上文 JSON 文件中的各项值。
// 除了消息 id 和消耗值(exhaustion value)外,其余参数都是可选的。
bootstrap.register(EXAMPLE_DAMAGE, new DamageType(EXAMPLE_DAMAGE.location(),
DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER,
0.1f,
DamageEffects.HURT,
DeathMessageType.DEFAULT)
)
})
// 如果适用,可以为其他数据包条目(datapack entries)添加数据包提供者。
.add(...)
);
// ...
}