Paper
Paper copied to clipboard
Wrap properly damage source and deprecate Bukkit's lazy enum
Closes #7415 Supersedes https://github.com/PaperMC/Paper/pull/7980
Bukkit has for years stored damage history into a single enum which loses 90% of available damage history and cause a bunch of issue about the future (when Mojang decide to add new damage source for example)
This PR wrap the whole nms DamageSource.
The main reasons of this changes
- Parity with vanilla (Bukkit lost some cause and create cause that doesn't exists in vanilla) (see supersedes PR javadoc)
- Maintainability (Bukkit has a single method with over 100 lines just to determine the right cause)
- API (bukkit has no API to create custom damage source (different from vanilla) or just to apply a certain cause to an entity)
New API With this PR you can create your own damage source using a builder Example:
DamageOrigin origin = DamageOrigin.of(VanillaDamageType.SONIC_BOOM).scalesWithDifficulty(true).build();
entity.damage(50.5D, origin);
entity.damage(23.1D, DamageOrigins.lava());
entity.damage(12.1D, DamageOrigins.sweetBerryBush(), EventContext.fromBlock(blockPos));
// some damage origin would normally be linked to a block in order to be compatible with bukkit events
DamageOrigin originComplex = DamageOrigin.of(type).deathMessage(killed -> Component.translatable("translatableKey", killed.getName())).build();
All damage origin must be categorized under a damage type and you can create them in the methods called before the registries freeze in paper plugin bootstrapper. Example:
@Override
public void beforeFreeze(final RegistryPreFreezeEvent<DamageType, DamageType.Builder> event) {
event.registryView().register(NEW_DAMAGE_TYPE, builder -> {
builder.name("my_damage").foodExhaustion(5.0F).deathMessageFormat(DeathMessageFormat.FALL_VARIANTS).effects(DamageEffect.FREEZING).scale(DamageScale.ALWAYS);
});
}
The builder is already prefilled with the default values. The name will be used as part of the translatable key when an entity died due to this damage cause If you want to change the death message without using the default behaviour (using the damage type name as key) you needs to use a callback function in the damage origin builder.
All the damage types (including plugin/datapack made one) will be under the DAMAGE_TYPE registry
available with RegistryAccess.registryAccess().getRegistry(RegistryKey.DAMAGE_TYPE)
Note this registry will not be available during bootstrap so don't try to get the value found in the VanillaDamageTypes class to get their keys! (in the future the key generation will generate those needed keys in this context automatically via a gradle task)
You can also get the hurting sound or if an entity is invulnerable to a specific damage origin and to create explosion with custom origin. The damage type tags are also available
boolean invulnerable = entity.isInvulnerableTo(origin);
Sound sound = entity.getHurtSound(origin);
world.createExplosion(null, position.offset(0, 2, 0), 2, false, true, origin);
boolean isFireDamage = origin.isTagged(DamageTypeTags.IS_FIRE)
Additionally the damage origin is now available into the EntityDeathEvent, PlayerDeathEvent, VehicleDamageEvent, VehicleDestroyEvent, PlayerAttackEntityCooldownResetEvent, EntityResurrectEvent and TameableDeathMessageEvent
However i have decided to not change how the events works basically only the cause changes but the same events will be called for the same entity/block/action In the future we could probably get rid of the bukkit logic that enforce such damage to be a block damage or not and that has issue with concurrency.
Credits: Javadoc for bukkit api has been taken from linked PR
I have updated the PR with the following things:
- Keep the old damage cause behavior
- Move the builders and the static helpers in the API
- Fallback to a default translatable component in case of a null function for death message
- Add ~some utilities methods to check if a damage origin is similiar to another and~ some string representation
- Removed the damage attribute cause even if vanilla doesn't use multiple things (except for fireball: a projectile and fire damage), Mojang can adds a new damage at any time so i have just decided for now to lock certain damage type excluding fire and projectile cause
- Change the internal api logic with how you handle stuff in your branch using unsafe bukkit and abstract class to cast the builders ~- Avoid a backed nms builder and override the interface instead like you did~
Also, please resolve the conversations to help clear some of the old requests.
Once/if merged what will be its target version?
I think i will target the latest version
Rebased and added more example in the initial comment for anyone interested. Also now the plugin API can't trigger issue like in the 8340, the incompatible association will now throw a sweet error in a precondition (only for the vanilla damage, the custom one made by builder can already break this limit). Only the plugin that abuse of the nms hurt method could or if paper/bukkit hasn't been updated properly when mc is updated. The test is now more simple and will not fail to static init the vanilla damage origin class like before (i don't even know how the build was green??).
Rebased for 1.19.3 Mojang while fixing of https://bugs.mojang.com/browse/MC-200006 has introduced a new type of damage source, the PointDamageSource. This is a generic damage source with the source position of the damage in order to check if the target is well covered holding a shield or not. I have added the api to support this, you could for example now specify from which position a damage has been dealt to support shield covering for all non entities things (so blocks, tile entity, particle, fake entity etc...). In vanilla, this is used for bed/respawn anchor explosion. This also allow you to change the source position of the entity damage origin as well by default it use the direct attacker position. But you could change this if your custom entity shot particle as projectile for example... The main advantage using this over a rewrite of the logic in the plugin is a better compatibility for others plugins (i.e: the damage modifier api would catch this change). For the entity damage origin, the source position is nullable to be able to ignore the shield at all but still hurt the armor of the entity. The bypass armor flag has the priority over this new feature. For example to make more realistic cactus that will not hit you if you're covered you could do in the EntityDamageByBlockEvent somethings like:
if (e.getOrigin().getType() == VanillaDamageType.CACTUS) {
if (e.getEntity() instanceof LivingEntity lv) {
e.setCancelled(true);
lv.damage(3, DamageOrigin.of(VanillaDamageType.CACTUS, e.getDamager().getLocation()).build());
// cactus damage are 1 point but this is just an example to show you that you need at least 3 point of damage
// if you want to hurt the shield otherwise the damage will always be blocked if well covered
lv.setNoDamageTicks(2 + (lv.getMaximumNoDamageTicks() / 2));
}
}
There's also some changes for all the falling block derivated damage origin. They're now a EntityDamageOrigin (the entity will be the falling block before the fall). You should use the VanillaDamageOrigin preset if you don't know what to put in the builder.
Alright now this PR may get a little crazy. We should rebase this ontop our registry manipulation API, and instead offer this using the registry API. Where now since data types are datadriven, it should fit much nicer.
Thankfully, we should be able to reuse alot of the current types. See how to add with patches like: https://github.com/PaperMC/Paper/pull/8920/files#diff-55fea1eaf44930f0c6191d94c7899eac708ca1a9ce8bbc59fe8a5a9386b50282 https://github.com/PaperMC/Paper/pull/8920/files#diff-14cba2e4e302853f09258899df92cd388eb6d279d13caad61c99b7b0798ff05b
https://github.com/PaperMC/Paper/pull/8920
Rebased for the 1.19.4 This now depends on the registry/tag modification api to have the same features like in the .3 release (and a little bit more!) The damage origin now require a damage type instead of a simple name. The damage type can be created by a plugin or a datapack. For plugin (paper plugin only) you need to use the methods in the bootstrapper before that the registries are frozen. A lot of configuration has been moved from the DamageOrigin builder to the DamageType builder but for plugin, it doesn't make sense to have them in the bootstrapper, and also in the bootstrapper nothing exists yet so no context for the damage. That's why some methods are duplicated in both builder notably the death message, food exhaustion and damage scaling. Normally for the death message you would need to provide custom translation through a resource pack or using adventure global translator but for non static death message that require more context you can use the damage origin like before. The damage type now allow to customize the hurt sound for players. A lot of getter (methods like IsFromExplosion, canBypassArmor etc...) from the DamageOrigin has been migrated to damage type tags. The inheritance with entity/point damage origin no longer exist anymore (check the isIndirect method to know that or the getter). All damage origin can now have a source position even the static one. I have updated the first comment for more example.
Other news non related to this update: You can now damage all entities not only the living one like the damage command. The restriction for plugin damage with bukkit events is no longer enforced generally that's the plugin responsibility to handle that properly with the others plugins. The type / tag is now the recommended way to check a specific damage origin in the event (and the direct hashcode equality between vanilla created origin and plugin origin are no longer sure). Some methods like isRaw / isIndirect / isVanilla also help to know who made the damage origin or if the direct entity is different from the entity.
Woulld it make sense to add plugin instance into DamageOrigins all ofXXX methods - making it mandatory? that way plugin will know what other plugin is the cause for the damage.
i don't think i want to link the damage origin paired with a plugin and it can't be mandatory since the server itself called those methods (the generic especially) as fallback damage origin. I know that you only speak about the ofXXX methods but they do the same internally. But can't you just put some sort of marker just before the damage are applied on the entity? That idea isn't specific to this feature and can be apply to any api that call an event or something else
It looks like there is nothing more to do for this PR. Most of the recent commits are rebases for newer MC version. What prevents it from being ready for review / unmarked as draft?
Really hope to see this soon added into paper :)
spigot has an upstream PR, but also this PR is waiting for some other PRs currently in review.