伤害类型与伤害来源

伤害类型与伤害来源

伤害类型表示实体受到哪一种伤害,例如物理、火焰、溺水、魔法、虚空等。区分伤害类型可用于免疫判定(例如烈焰人不受火焰伤害)、附魔(例如爆炸保护仅抵御爆炸伤害)等多种场景。

可以把伤害类型视为伤害来源的模板,或者把伤害来源视为伤害类型的实例。伤害类型在代码中以 ResourceKey 存在,但全部属性定义在数据包中。伤害来源则由游戏根据数据包文件中的值按需创建,还可保存攻击实体等额外上下文。

创建伤害类型

首先创建自己的 DamageType。它属于数据包注册表,因此不在代码中注册,而是在添加对应文件后自动注册。不过,仍需给代码提供获取伤害来源的引用点。为此,定义一个资源键:

public static final ResourceKey<DamageType> EXAMPLE_DAMAGE =
        ResourceKey.create(Registries.DAMAGE_TYPE, Identifier.fromNamespaceAndPath(ExampleMod.MOD_ID, "example"));

有了代码中的引用后,就可以在数据文件中指定属性。文件位于 data/examplemod/damage_type/example.json(将 examplemod 和 example 分别替换为 Mod ID 与资源位置名称),内容如下:

{
    // The death message id of the damage type. The full death message translation key will be
    // "death.attack.examplemod.example" (with swapped-out mod id and name).
    "message_id": "examplemod.example",
    // Whether this damage type's damage amount scales with difficulty or not. Valid vanilla values are:
    // - "never": The damage value remains the same on any difficulty. Common for player-caused damage types.
    // - "when_caused_by_living_non_player": The damage value is scaled if the entity is caused by a
    //   living entity of some sort, including indirectly (e.g. an arrow shot by a skeleton), that is not a player.
    // - "always": The damage value is always scaled. Commonly used by explosion-like damage.
    "scaling": "when_caused_by_living_non_player",
    // The amount of exhaustion caused by receiving this kind of damage.
    "exhaustion": 0.1,
    // The damage effects (currently only sound effects) that are applied when receiving this kind of damage. Optional.
    // Valid vanilla values are "hurt" (default), "thorns", "drowning", "burning", "poking", and "freezing".
    "effects": "hurt",
    // The death message type. Determines how the death message is built. Optional.
    // Valid vanilla values are "default" (default), "fall_variants", and "intentional_game_design".
    "death_message_type": "default"
}

建议 scaling、effects 和 death_message_type 字段在内部分别由 DamageScaling、DamageEffects 与 DeathMessageType 枚举控制。需要时可以扩展这些枚举以添加自定义值。

原版伤害类型也使用相同格式,整合包开发者可按需修改这些值。

创建和使用伤害来源

通常在调用 Entity#hurt 时即时创建 DamageSource。由于伤害类型属于数据包注册表,需要通过 RegistryAccess 查询,可从 Level#registryAccess 获取。调用 DamageSource 构造函数即可创建来源,最多传入四个参数:

DamageSource damageSource = new DamageSource(
        // The damage type holder to use. Query from the registry. This is the only required parameter.
        registryAccess.lookupOrThrow(Registries.DAMAGE_TYPE).getOrThrow(EXAMPLE_DAMAGE),
        // The direct entity. For example, if a skeleton shot you, the skeleton would be the causing entity
        // (= the parameter above), and the arrow would be the direct entity (= this parameter). Similar to
        // the causing entity, this isn't always applicable and therefore nullable. Optional, defaults to null.
        null,
        // The entity causing the damage. This isn't always applicable (e.g. when falling out of the world)
        // and may therefore be null. Optional, defaults to null.
        null,
        // The damage source position. This is rarely used, one example would be intentional game design
        // (= nether beds exploding). Nullable and optional, defaulting to null.
        null
);

警告 DamageSources#source 是 new DamageSource 的包装方法,但它将第二和第三个参数(直接实体与致害实体)的位置对调了。请确认每个值都传给了正确参数。

如果伤害来源完全没有实体或位置上下文,可以将它缓存在字段中。对于包含这类上下文的来源,通常会添加辅助方法:

public static DamageSource exampleDamage(Entity causer) {
    return new DamageSource(
            causer.level().registryAccess().lookupOrThrow(Registries.DAMAGE_TYPE).getOrThrow(EXAMPLE_DAMAGE),
            causer);
}

建议 原版 DamageSource 的工厂方法位于 DamageSources,原版 DamageType 的资源键位于 DamageTypes。实体还提供 Entity#damageSources 作为获取 DamageSources 实例的便捷方法。

伤害来源最主要的用途是 Entity#hurt,实体受到伤害时会调用该方法。要使用自定义类型对实体造成伤害,只需主动调用它:

// The second parameter is the amount of damage, in half hearts.
entity.hurt(exampleDamage(player), 10);

其他与伤害类型相关的行为(例如无敌判定)通常通过伤害类型标签实现。Minecraft 和 NeoForge 都提供这类标签,分别位于 DamageTypeTags 与 Tags.DamageTypes。

数据生成

更多信息见数据包注册表的数据生成。

伤害类型 JSON 文件可以通过数据生成创建。由于它们属于数据包注册表,需要通过 GatherDataEvent#createDatapackRegistryObjects 添加 DatapackBuiltinEntriesProvider,并将伤害类型放入 RegistrySetBuilder:

// In your datagen class
@SubscribeEvent // on the mod event bus
public static void onGatherData(GatherDataEvent.Client event) {
    event.createDatapackRegistryObjects(new RegistrySetBuilder()
        // Add a datapack builtin entry provider for damage types. If this lambda becomes longer,
        // this should probably be extracted into a separate method for the sake of readability.
        .add(Registries.DAMAGE_TYPE, bootstrap -> {
            // Use new DamageType() to create an in-code representation of a damage type.
            // The parameters map to the values of the JSON file, in the order seen above.
            // All parameters except for the message id and the exhaustion value are optional.
            bootstrap.register(EXAMPLE_DAMAGE, new DamageType(EXAMPLE_DAMAGE.identifier(),
                DamageScaling.WHEN_CAUSED_BY_LIVING_NON_PLAYER,
                0.1f,
                DamageEffects.HURT,
                DeathMessageType.DEFAULT)
            )
        })
        // Add datapack providers for other datapack entries, if applicable.
        .add(...)
    );

    // ...
}

来源: NeoForged/Documentation(26.1)。中文翻译依据提交 4a680cdd4d15。