From 9439b62ff08dfecd2f239cbe6556d620b0d840fb Mon Sep 17 00:00:00 2001 From: mworzala Date: Wed, 1 Nov 2023 22:35:18 -0400 Subject: [PATCH] chore: port DamageType cleanup change from MelonHell 1.19.4 PR (cherry picked from commit 9eab3d4f8b684db78a205468d6d10d94781ff855) --- .../java/net/minestom/codegen/Generators.java | 1 + .../java/net/minestom/demo/PlayerInit.java | 6 +- .../server/entity/damage/DamageTypes.java | 95 ++++++++++ .../minestom/server/entity/LivingEntity.java | 24 ++- .../net/minestom/server/entity/Player.java | 13 +- .../ai/target/LastEntityDamagerTarget.java | 5 +- .../minestom/server/entity/damage/Damage.java | 179 ++++++++++++++++++ .../server/entity/damage/DamageType.java | 167 +++++----------- .../server/entity/damage/DamageTypeImpl.java | 77 ++++++++ .../server/entity/damage/EntityDamage.java | 22 ++- .../entity/damage/EntityProjectileDamage.java | 22 +-- .../entity/damage/PositionalDamage.java | 19 ++ .../event/entity/EntityDamageEvent.java | 28 +-- .../minestom/server/registry/Registry.java | 22 +++ 14 files changed, 492 insertions(+), 188 deletions(-) create mode 100644 src/autogenerated/java/net/minestom/server/entity/damage/DamageTypes.java create mode 100644 src/main/java/net/minestom/server/entity/damage/Damage.java create mode 100644 src/main/java/net/minestom/server/entity/damage/DamageTypeImpl.java create mode 100644 src/main/java/net/minestom/server/entity/damage/PositionalDamage.java diff --git a/code-generators/src/main/java/net/minestom/codegen/Generators.java b/code-generators/src/main/java/net/minestom/codegen/Generators.java index ed7e400e2..469ad0749 100644 --- a/code-generators/src/main/java/net/minestom/codegen/Generators.java +++ b/code-generators/src/main/java/net/minestom/codegen/Generators.java @@ -33,6 +33,7 @@ public class Generators { generator.generate(resource("particles.json"), "net.minestom.server.particle", "Particle", "ParticleImpl", "Particles"); generator.generate(resource("sounds.json"), "net.minestom.server.sound", "SoundEvent", "SoundEventImpl", "SoundEvents"); generator.generate(resource("custom_statistics.json"), "net.minestom.server.statistic", "StatisticType", "StatisticTypeImpl", "StatisticTypes"); + generator.generate(resource("damage_types.json"), "net.minestom.server.entity.damage", "DamageType", "DamageTypeImpl", "DamageTypes"); // Generate fluids new FluidGenerator(resource("fluids.json"), outputFolder).generate(); diff --git a/demo/src/main/java/net/minestom/demo/PlayerInit.java b/demo/src/main/java/net/minestom/demo/PlayerInit.java index 99208d809..860b21568 100644 --- a/demo/src/main/java/net/minestom/demo/PlayerInit.java +++ b/demo/src/main/java/net/minestom/demo/PlayerInit.java @@ -10,7 +10,7 @@ import net.minestom.server.entity.Entity; import net.minestom.server.entity.GameMode; import net.minestom.server.entity.ItemEntity; import net.minestom.server.entity.Player; -import net.minestom.server.entity.damage.DamageType; +import net.minestom.server.entity.damage.Damage; import net.minestom.server.event.Event; import net.minestom.server.event.EventNode; import net.minestom.server.event.entity.EntityAttackEvent; @@ -28,13 +28,11 @@ import net.minestom.server.item.metadata.BundleMeta; import net.minestom.server.monitoring.BenchmarkManager; import net.minestom.server.monitoring.TickMonitor; import net.minestom.server.utils.MathUtils; -import net.minestom.server.utils.chunk.ChunkUtils; import net.minestom.server.utils.time.TimeUnit; import net.minestom.server.world.DimensionType; import java.time.Duration; import java.util.*; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicReference; @@ -51,7 +49,7 @@ public class PlayerInit { if (entity instanceof Player) { Player target = (Player) entity; - target.damage(DamageType.fromEntity(source), 5); + target.damage(Damage.fromEntity(source, 5)); } if (source instanceof Player) { diff --git a/src/autogenerated/java/net/minestom/server/entity/damage/DamageTypes.java b/src/autogenerated/java/net/minestom/server/entity/damage/DamageTypes.java new file mode 100644 index 000000000..b1b11a6bf --- /dev/null +++ b/src/autogenerated/java/net/minestom/server/entity/damage/DamageTypes.java @@ -0,0 +1,95 @@ +package net.minestom.server.entity.damage; + +/** + * Code autogenerated, do not edit! + */ +@SuppressWarnings("unused") +interface DamageTypes { + DamageType WITHER = DamageTypeImpl.get("minecraft:wither"); + + DamageType SONIC_BOOM = DamageTypeImpl.get("minecraft:sonic_boom"); + + DamageType WITHER_SKULL = DamageTypeImpl.get("minecraft:wither_skull"); + + DamageType DRY_OUT = DamageTypeImpl.get("minecraft:dry_out"); + + DamageType TRIDENT = DamageTypeImpl.get("minecraft:trident"); + + DamageType ON_FIRE = DamageTypeImpl.get("minecraft:on_fire"); + + DamageType FALL = DamageTypeImpl.get("minecraft:fall"); + + DamageType MOB_ATTACK = DamageTypeImpl.get("minecraft:mob_attack"); + + DamageType MOB_PROJECTILE = DamageTypeImpl.get("minecraft:mob_projectile"); + + DamageType THROWN = DamageTypeImpl.get("minecraft:thrown"); + + DamageType FALLING_STALACTITE = DamageTypeImpl.get("minecraft:falling_stalactite"); + + DamageType FIREBALL = DamageTypeImpl.get("minecraft:fireball"); + + DamageType FALLING_BLOCK = DamageTypeImpl.get("minecraft:falling_block"); + + DamageType PLAYER_EXPLOSION = DamageTypeImpl.get("minecraft:player_explosion"); + + DamageType STING = DamageTypeImpl.get("minecraft:sting"); + + DamageType UNATTRIBUTED_FIREBALL = DamageTypeImpl.get("minecraft:unattributed_fireball"); + + DamageType IN_WALL = DamageTypeImpl.get("minecraft:in_wall"); + + DamageType IN_FIRE = DamageTypeImpl.get("minecraft:in_fire"); + + DamageType ARROW = DamageTypeImpl.get("minecraft:arrow"); + + DamageType HOT_FLOOR = DamageTypeImpl.get("minecraft:hot_floor"); + + DamageType DROWN = DamageTypeImpl.get("minecraft:drown"); + + DamageType STARVE = DamageTypeImpl.get("minecraft:starve"); + + DamageType GENERIC_KILL = DamageTypeImpl.get("minecraft:generic_kill"); + + DamageType DRAGON_BREATH = DamageTypeImpl.get("minecraft:dragon_breath"); + + DamageType MOB_ATTACK_NO_AGGRO = DamageTypeImpl.get("minecraft:mob_attack_no_aggro"); + + DamageType LAVA = DamageTypeImpl.get("minecraft:lava"); + + DamageType OUTSIDE_BORDER = DamageTypeImpl.get("minecraft:outside_border"); + + DamageType FLY_INTO_WALL = DamageTypeImpl.get("minecraft:fly_into_wall"); + + DamageType LIGHTNING_BOLT = DamageTypeImpl.get("minecraft:lightning_bolt"); + + DamageType PLAYER_ATTACK = DamageTypeImpl.get("minecraft:player_attack"); + + DamageType FREEZE = DamageTypeImpl.get("minecraft:freeze"); + + DamageType FALLING_ANVIL = DamageTypeImpl.get("minecraft:falling_anvil"); + + DamageType OUT_OF_WORLD = DamageTypeImpl.get("minecraft:out_of_world"); + + DamageType MAGIC = DamageTypeImpl.get("minecraft:magic"); + + DamageType SWEET_BERRY_BUSH = DamageTypeImpl.get("minecraft:sweet_berry_bush"); + + DamageType FIREWORKS = DamageTypeImpl.get("minecraft:fireworks"); + + DamageType EXPLOSION = DamageTypeImpl.get("minecraft:explosion"); + + DamageType BAD_RESPAWN_POINT = DamageTypeImpl.get("minecraft:bad_respawn_point"); + + DamageType STALAGMITE = DamageTypeImpl.get("minecraft:stalagmite"); + + DamageType THORNS = DamageTypeImpl.get("minecraft:thorns"); + + DamageType INDIRECT_MAGIC = DamageTypeImpl.get("minecraft:indirect_magic"); + + DamageType CRAMMING = DamageTypeImpl.get("minecraft:cramming"); + + DamageType CACTUS = DamageTypeImpl.get("minecraft:cactus"); + + DamageType GENERIC = DamageTypeImpl.get("minecraft:generic"); +} diff --git a/src/main/java/net/minestom/server/entity/LivingEntity.java b/src/main/java/net/minestom/server/entity/LivingEntity.java index 8b2fe5d6a..d326f9cf2 100644 --- a/src/main/java/net/minestom/server/entity/LivingEntity.java +++ b/src/main/java/net/minestom/server/entity/LivingEntity.java @@ -6,6 +6,7 @@ import net.minestom.server.attribute.AttributeInstance; import net.minestom.server.collision.BoundingBox; import net.minestom.server.coordinate.Point; import net.minestom.server.coordinate.Vec; +import net.minestom.server.entity.damage.Damage; import net.minestom.server.entity.damage.DamageType; import net.minestom.server.entity.metadata.LivingEntityMeta; import net.minestom.server.event.EventDispatcher; @@ -48,7 +49,7 @@ public class LivingEntity extends Entity implements EquipmentHandler { protected boolean isDead; - protected DamageType lastDamageSource; + protected Damage lastDamage; // Bounding box used for items' pickup (see LivingEntity#setBoundingBox) protected BoundingBox expandedBoundingBox; @@ -310,26 +311,29 @@ public class LivingEntity extends Entity implements EquipmentHandler { } } + public boolean damage(@NotNull DamageType type, float amount) { + return damage(new Damage(type, null, null, null, amount)); + } + /** * Damages the entity by a value, the type of the damage also has to be specified. * - * @param type the damage type - * @param value the amount of damage + * @param damage the damage to be applied * @return true if damage has been applied, false if it didn't */ - public boolean damage(@NotNull DamageType type, float value) { + public boolean damage(@NotNull Damage damage) { if (isDead()) return false; - if (isInvulnerable() || isImmune(type)) { + if (isInvulnerable() || isImmune(damage.getType())) { return false; } - EntityDamageEvent entityDamageEvent = new EntityDamageEvent(this, type, value, type.getSound(this)); + EntityDamageEvent entityDamageEvent = new EntityDamageEvent(this, damage, damage.getSound(this)); EventDispatcher.callCancellable(entityDamageEvent, () -> { // Set the last damage type since the event is not cancelled - this.lastDamageSource = entityDamageEvent.getDamageType(); + this.lastDamage = entityDamageEvent.getDamage(); - float remainingDamage = entityDamageEvent.getDamage(); + float remainingDamage = entityDamageEvent.getDamage().getAmount(); if (entityDamageEvent.shouldAnimate()) { sendPacketToViewersAndSelf(new EntityAnimationPacket(getEntityId(), EntityAnimationPacket.Animation.TAKE_DAMAGE)); @@ -410,8 +414,8 @@ public class LivingEntity extends Entity implements EquipmentHandler { * * @return the last damage source, null if not any */ - public @Nullable DamageType getLastDamageSource() { - return lastDamageSource; + public @Nullable Damage getLastDamageSource() { + return lastDamage; } /** diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index c1e28eff0..699252cd2 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -69,7 +69,6 @@ import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.network.player.PlayerSocketConnection; import net.minestom.server.recipe.Recipe; import net.minestom.server.recipe.RecipeManager; -import net.minestom.server.registry.Registry; import net.minestom.server.resourcepack.ResourcePack; import net.minestom.server.scoreboard.BelowNameTag; import net.minestom.server.scoreboard.Team; @@ -98,8 +97,6 @@ import org.jctools.queues.MpscUnboundedXaddArrayQueue; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import org.jglrxavpok.hephaistos.nbt.NBT; -import org.jglrxavpok.hephaistos.nbt.NBTType; import java.nio.charset.StandardCharsets; import java.time.Duration; @@ -415,8 +412,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable, // get death screen text to the killed player { - if (lastDamageSource != null) { - deathText = lastDamageSource.buildDeathScreenText(this); + if (lastDamage != null) { + deathText = lastDamage.buildDeathScreenText(this); } else { // may happen if killed by the server without applying damage deathText = Component.text("Killed by poor programming."); } @@ -424,8 +421,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable, // get death message to chat { - if (lastDamageSource != null) { - chatMessage = lastDamageSource.buildDeathMessage(this); + if (lastDamage != null) { + chatMessage = lastDamage.buildDeathMessage(this); } else { // may happen if killed by the server without applying damage chatMessage = Component.text(getUsername() + " was killed by poor programming."); } @@ -920,7 +917,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, @Override public boolean isImmune(@NotNull DamageType type) { if (!getGameMode().canTakeDamage()) { - return type != DamageType.VOID; + return type != DamageType.OUT_OF_WORLD; } return super.isImmune(type); } diff --git a/src/main/java/net/minestom/server/entity/ai/target/LastEntityDamagerTarget.java b/src/main/java/net/minestom/server/entity/ai/target/LastEntityDamagerTarget.java index 6c9ce05af..6e318a6d2 100644 --- a/src/main/java/net/minestom/server/entity/ai/target/LastEntityDamagerTarget.java +++ b/src/main/java/net/minestom/server/entity/ai/target/LastEntityDamagerTarget.java @@ -3,6 +3,7 @@ package net.minestom.server.entity.ai.target; import net.minestom.server.entity.Entity; import net.minestom.server.entity.EntityCreature; import net.minestom.server.entity.ai.TargetSelector; +import net.minestom.server.entity.damage.Damage; import net.minestom.server.entity.damage.DamageType; import net.minestom.server.entity.damage.EntityDamage; import org.jetbrains.annotations.NotNull; @@ -21,8 +22,8 @@ public class LastEntityDamagerTarget extends TargetSelector { @Override public Entity findTarget() { - final DamageType damageType = entityCreature.getLastDamageSource(); - if (!(damageType instanceof EntityDamage entityDamage)) { + final Damage damage = entityCreature.getLastDamageSource(); + if (!(damage instanceof EntityDamage entityDamage)) { // No damager recorded, return null return null; } diff --git a/src/main/java/net/minestom/server/entity/damage/Damage.java b/src/main/java/net/minestom/server/entity/damage/Damage.java new file mode 100644 index 000000000..37b86d76c --- /dev/null +++ b/src/main/java/net/minestom/server/entity/damage/Damage.java @@ -0,0 +1,179 @@ +package net.minestom.server.entity.damage; + +import net.kyori.adventure.text.Component; +import net.minestom.server.coordinate.Point; +import net.minestom.server.entity.Entity; +import net.minestom.server.entity.LivingEntity; +import net.minestom.server.entity.Player; +import net.minestom.server.sound.SoundEvent; +import net.minestom.server.tag.TagHandler; +import net.minestom.server.tag.Taggable; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +/** + * Represents a type of damage, required when calling {@link LivingEntity#damage(Damage)} + * and retrieved in {@link net.minestom.server.event.entity.EntityDamageEvent}. + *

+ * This class can be extended if you need to include custom fields and/or methods. + */ +public class Damage implements Taggable { + + private final DamageType type; + private final Entity source; + private final Entity attacker; + private final Point sourcePosition; + private final TagHandler tagHandler = TagHandler.newHandler(); + + private float amount; + + /** + * Creates a new damage type. + * + * @param type the type of this damage + * @param amount amount of damage + */ + public Damage(@NotNull DamageType type, @Nullable Entity source, @Nullable Entity attacker, @Nullable Point sourcePosition, float amount) { + this.type = type; + this.source = source; + this.attacker = attacker; + this.sourcePosition = sourcePosition; + this.amount = amount; + } + + /** + * Gets the type of this damage. + *

+ * It does not have to be unique to this object.o + * + * @return the damage type + */ + public @NotNull DamageType getType() { + return type; + } + + /** + * Gets the "attacker" of the damage. + * This is the indirect cause of the damage, like the shooter of a projectile, or null if there was none. + * + * @return the attacker + */ + public @Nullable Entity getAttacker() { + return attacker; + } + + /** + * Gets the direct source of the damage. + * This is the entity that directly causes the damage, like a projectile, or null if there was none. + * + * @return the source + */ + public @Nullable Entity getSource() { + return source; + } + + /** + * Gets the position of the source of the damage, or null if there is none. + * This may differ from the source entity's position. + * + * @return The source position + */ + public @Nullable Point getSourcePosition() { + return sourcePosition; + } + + /** + * Builds the death message linked to this damage type. + *

+ * Used in {@link Player#kill()} to broadcast the proper message. + * + * @param killed the player who has been killed + * @return the death message, null to do not send anything + */ + public @Nullable Component buildDeathMessage(@NotNull Player killed) { + return Component.translatable("death.attack." + type.messageId(), Component.text(killed.getUsername())); + } + + /** + * Convenient method to create an {@link EntityProjectileDamage}. + * + * @param shooter the shooter + * @param projectile the actual projectile + * @param amount amount of damage + * @return a new {@link EntityProjectileDamage} + */ + public static @NotNull Damage fromProjectile(@Nullable Entity shooter, @NotNull Entity projectile, float amount) { + return new EntityProjectileDamage(shooter, projectile, amount); + } + + /** + * Convenient method to create an {@link EntityDamage}. + * + * @param player the player damager + * @param amount amount of damage + * @return a new {@link EntityDamage} + */ + public static @NotNull EntityDamage fromPlayer(@NotNull Player player, float amount) { + return new EntityDamage(player, amount); + } + + /** + * Convenient method to create an {@link EntityDamage}. + * + * @param entity the entity damager + * @param amount amount of damage + * @return a new {@link EntityDamage} + */ + public static @NotNull EntityDamage fromEntity(@NotNull Entity entity, float amount) { + return new EntityDamage(entity, amount); + } + + public static @NotNull PositionalDamage fromPosition(@NotNull DamageType type, @NotNull Point sourcePosition, float amount) { + return new PositionalDamage(type, sourcePosition, amount); + } + + /** + * Builds the text sent to a player in his death screen. + * + * @param killed the player who has been killed + * @return the death screen text, null to do not send anything + */ + public @Nullable Component buildDeathScreenText(@NotNull Player killed) { + return Component.translatable("death.attack." + type.messageId()); + } + + /** + * Sound event to play when the given entity is hit by this damage. Possible to return null if no sound should be played + * + * @param entity the entity hit by this damage + * @return the sound to play when the given entity is hurt by this damage type. Can be null if no sound should play + */ + public @Nullable SoundEvent getSound(@NotNull LivingEntity entity) { + if (entity instanceof Player) { + return getPlayerSound((Player) entity); + } + return getGenericSound(entity); + } + + protected SoundEvent getGenericSound(@NotNull LivingEntity entity) { + return SoundEvent.ENTITY_GENERIC_HURT; + } + + protected SoundEvent getPlayerSound(@NotNull Player player) { + if (type == DamageType.ON_FIRE) return SoundEvent.ENTITY_PLAYER_HURT_ON_FIRE; + return SoundEvent.ENTITY_PLAYER_HURT; + } + + @Override + public @NotNull TagHandler tagHandler() { + return tagHandler; + } + + public float getAmount() { + return amount; + } + + public void setAmount(float amount) { + this.amount = amount; + } +} diff --git a/src/main/java/net/minestom/server/entity/damage/DamageType.java b/src/main/java/net/minestom/server/entity/damage/DamageType.java index 785ff9bbd..deec1d4db 100644 --- a/src/main/java/net/minestom/server/entity/damage/DamageType.java +++ b/src/main/java/net/minestom/server/entity/damage/DamageType.java @@ -1,131 +1,60 @@ package net.minestom.server.entity.damage; -import net.kyori.adventure.text.Component; -import net.minestom.server.entity.Entity; -import net.minestom.server.entity.LivingEntity; -import net.minestom.server.entity.Player; -import net.minestom.server.sound.SoundEvent; -import net.minestom.server.tag.TagHandler; -import net.minestom.server.tag.Taggable; +import net.minestom.server.registry.ProtocolObject; +import net.minestom.server.registry.Registry; +import net.minestom.server.utils.NamespaceID; +import org.jetbrains.annotations.Contract; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; -/** - * Represents a type of damage, required when calling {@link LivingEntity#damage(DamageType, float)} - * and retrieved in {@link net.minestom.server.event.entity.EntityDamageEvent}. - *

- * This class can be extended if you need to include custom fields and/or methods. - */ -public class DamageType implements Taggable { - - public static final DamageType VOID = new DamageType("attack.outOfWorld"); - public static final DamageType GRAVITY = new DamageType("attack.fall"); - public static final DamageType ON_FIRE = new DamageType("attack.onFire") { - @Override - protected SoundEvent getPlayerSound(@NotNull Player player) { - return SoundEvent.ENTITY_PLAYER_HURT_ON_FIRE; - } - }; - private final String identifier; - private final TagHandler tagHandler = TagHandler.newHandler(); +import java.util.Collection; +public sealed interface DamageType extends ProtocolObject, DamageTypes permits DamageTypeImpl { /** - * Creates a new damage type. + * Returns the damage type registry. * - * @param identifier the identifier of this damage type, - * does not need to be unique + * @return the damage type registry */ - public DamageType(@NotNull String identifier) { - this.identifier = identifier; - } - - /** - * Gets the identifier of this damage type. - *

- * It does not have to be unique to this object.o - * - * @return the damage type identifier - */ - public @NotNull String getIdentifier() { - return identifier; - } - - /** - * Builds the death message linked to this damage type. - *

- * Used in {@link Player#kill()} to broadcast the proper message. - * - * @param killed the player who has been killed - * @return the death message, null to do not send anything - */ - public @Nullable Component buildDeathMessage(@NotNull Player killed) { - return Component.translatable("death." + identifier, Component.text(killed.getUsername())); - } - - /** - * Convenient method to create an {@link EntityProjectileDamage}. - * - * @param shooter the shooter - * @param projectile the actual projectile - * @return a new {@link EntityProjectileDamage} - */ - public static @NotNull DamageType fromProjectile(@Nullable Entity shooter, @NotNull Entity projectile) { - return new EntityProjectileDamage(shooter, projectile); - } - - /** - * Convenient method to create an {@link EntityDamage}. - * - * @param player the player damager - * @return a new {@link EntityDamage} - */ - public static @NotNull EntityDamage fromPlayer(@NotNull Player player) { - return new EntityDamage(player); - } - - /** - * Convenient method to create an {@link EntityDamage}. - * - * @param entity the entity damager - * @return a new {@link EntityDamage} - */ - public static @NotNull EntityDamage fromEntity(@NotNull Entity entity) { - return new EntityDamage(entity); - } - - /** - * Builds the text sent to a player in his death screen. - * - * @param killed the player who has been killed - * @return the death screen text, null to do not send anything - */ - public @Nullable Component buildDeathScreenText(@NotNull Player killed) { - return Component.translatable("death." + identifier); - } - - /** - * Sound event to play when the given entity is hit by this damage. Possible to return null if no sound should be played - * - * @param entity the entity hit by this damage - * @return the sound to play when the given entity is hurt by this damage type. Can be null if no sound should play - */ - public @Nullable SoundEvent getSound(@NotNull LivingEntity entity) { - if (entity instanceof Player) { - return getPlayerSound((Player) entity); - } - return getGenericSound(entity); - } - - protected SoundEvent getGenericSound(@NotNull LivingEntity entity) { - return SoundEvent.ENTITY_GENERIC_HURT; - } - - protected SoundEvent getPlayerSound(@NotNull Player player) { - return SoundEvent.ENTITY_PLAYER_HURT; - } + @Contract(pure = true) + @NotNull Registry.DamageTypeEntry registry(); @Override - public @NotNull TagHandler tagHandler() { - return tagHandler; + default @NotNull NamespaceID namespace() { + return registry().namespace(); } -} + + default double exhaustion() { + return registry().exhaustion(); + } + + default String messageId() { + return registry().messageId(); + } + + default String scaling() { + return registry().scaling(); + } + + NBTCompound asNBT(); + + static @NotNull Collection<@NotNull DamageType> values() { + return DamageTypeImpl.values(); + } + + static DamageType fromNamespaceId(@NotNull String namespaceID) { + return DamageTypeImpl.getSafe(namespaceID); + } + + static DamageType fromNamespaceId(@NotNull NamespaceID namespaceID) { + return fromNamespaceId(namespaceID.asString()); + } + + static @Nullable DamageType fromId(int id) { + return DamageTypeImpl.getId(id); + } + + static NBTCompound getNBT() { + return DamageTypeImpl.getNBT(); + } +} \ No newline at end of file diff --git a/src/main/java/net/minestom/server/entity/damage/DamageTypeImpl.java b/src/main/java/net/minestom/server/entity/damage/DamageTypeImpl.java new file mode 100644 index 000000000..2873cad2d --- /dev/null +++ b/src/main/java/net/minestom/server/entity/damage/DamageTypeImpl.java @@ -0,0 +1,77 @@ +package net.minestom.server.entity.damage; + +import net.minestom.server.registry.Registry; +import org.jetbrains.annotations.NotNull; +import org.jglrxavpok.hephaistos.nbt.NBT; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; +import org.jglrxavpok.hephaistos.nbt.NBTType; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; + +record DamageTypeImpl(Registry.DamageTypeEntry registry, int id) implements DamageType { + private static final Registry.Container CONTAINER; + + static { + AtomicInteger i = new AtomicInteger(); + CONTAINER = Registry.createContainer(Registry.Resource.DAMAGE_TYPES, + (namespace, properties) -> new DamageTypeImpl(Registry.damageType(namespace, properties), i.getAndIncrement())); + } + + static DamageType get(@NotNull String namespace) { + return CONTAINER.get(namespace); + } + + static DamageType getSafe(@NotNull String namespace) { + return CONTAINER.getSafe(namespace); + } + + static DamageType getId(int id) { + return CONTAINER.getId(id); + } + + @Override + public NBTCompound asNBT() { + var elem = new HashMap(); + elem.put("exhaustion", NBT.Float(registry.exhaustion())); + elem.put("message_id", NBT.String(registry.messageId())); + elem.put("scaling", NBT.String(registry.scaling())); + return NBT.Compound(elem); + } + + static Collection values() { + return CONTAINER.values(); + } + + @Override + public String toString() { + return name(); + } + + @Override + public int id() { + return id; + } + + private static NBTCompound lazyNbt = null; + + static NBTCompound getNBT() { + if (lazyNbt == null) { + var damageTypes = values().stream() + .map((damageType) -> NBT.Compound(Map.of( + "id", NBT.Int(damageType.id()), + "name", NBT.String(damageType.name()), + "element", damageType.asNBT() + ))) + .toList(); + + lazyNbt = NBT.Compound(Map.of( + "type", NBT.String("minecraft:damage_type"), + "value", NBT.List(NBTType.TAG_Compound, damageTypes) + )); + } + return lazyNbt; + } +} \ No newline at end of file diff --git a/src/main/java/net/minestom/server/entity/damage/EntityDamage.java b/src/main/java/net/minestom/server/entity/damage/EntityDamage.java index ff6bd2979..425238cf2 100644 --- a/src/main/java/net/minestom/server/entity/damage/EntityDamage.java +++ b/src/main/java/net/minestom/server/entity/damage/EntityDamage.java @@ -6,13 +6,10 @@ import org.jetbrains.annotations.NotNull; /** * Represents damage inflicted by an {@link Entity}. */ -public class EntityDamage extends DamageType { +public class EntityDamage extends Damage { - private final Entity source; - - public EntityDamage(@NotNull Entity source) { - super("entity_source"); - this.source = source; + public EntityDamage(@NotNull Entity source, float amount) { + super(DamageType.MOB_ATTACK, source, source, null, amount); } /** @@ -20,8 +17,13 @@ public class EntityDamage extends DamageType { * * @return the source */ - @NotNull - public Entity getSource() { - return source; + @Override + public @NotNull Entity getSource() { + return super.getSource(); } -} + + @Override + public @NotNull Entity getAttacker() { + return getSource(); + } +} \ No newline at end of file diff --git a/src/main/java/net/minestom/server/entity/damage/EntityProjectileDamage.java b/src/main/java/net/minestom/server/entity/damage/EntityProjectileDamage.java index e7d6f5de9..5f7ab9727 100644 --- a/src/main/java/net/minestom/server/entity/damage/EntityProjectileDamage.java +++ b/src/main/java/net/minestom/server/entity/damage/EntityProjectileDamage.java @@ -7,15 +7,10 @@ import org.jetbrains.annotations.Nullable; /** * Represents damage inflicted by an entity, via a projectile. */ -public class EntityProjectileDamage extends DamageType { +public class EntityProjectileDamage extends Damage { - private final Entity shooter; - private final Entity projectile; - - public EntityProjectileDamage(@Nullable Entity shooter, @NotNull Entity projectile) { - super("projectile_source"); - this.shooter = shooter; - this.projectile = projectile; + public EntityProjectileDamage(@Nullable Entity shooter, @NotNull Entity projectile, float amount) { + super(DamageType.MOB_PROJECTILE, projectile, shooter, null, amount); } /** @@ -25,7 +20,7 @@ public class EntityProjectileDamage extends DamageType { */ @NotNull public Entity getProjectile() { - return projectile; + return getSource(); } /** @@ -35,6 +30,11 @@ public class EntityProjectileDamage extends DamageType { */ @Nullable public Entity getShooter() { - return shooter; + return getAttacker(); } -} + + @Override + public @NotNull Entity getSource() { + return super.getSource(); + } +} \ No newline at end of file diff --git a/src/main/java/net/minestom/server/entity/damage/PositionalDamage.java b/src/main/java/net/minestom/server/entity/damage/PositionalDamage.java new file mode 100644 index 000000000..12c414bac --- /dev/null +++ b/src/main/java/net/minestom/server/entity/damage/PositionalDamage.java @@ -0,0 +1,19 @@ +package net.minestom.server.entity.damage; + +import net.minestom.server.coordinate.Point; +import org.jetbrains.annotations.NotNull; + +/** + * Represents damage that is associated with a certain position. + */ +public class PositionalDamage extends Damage { + + public PositionalDamage(@NotNull DamageType type, @NotNull Point sourcePosition, float amount) { + super(type, null, null, sourcePosition, amount); + } + + @Override + public @NotNull Point getSourcePosition() { + return super.getSourcePosition(); + } +} \ No newline at end of file diff --git a/src/main/java/net/minestom/server/event/entity/EntityDamageEvent.java b/src/main/java/net/minestom/server/event/entity/EntityDamageEvent.java index 4d06bd4ef..b0c2e5a95 100644 --- a/src/main/java/net/minestom/server/event/entity/EntityDamageEvent.java +++ b/src/main/java/net/minestom/server/event/entity/EntityDamageEvent.java @@ -2,6 +2,7 @@ package net.minestom.server.event.entity; import net.minestom.server.entity.Entity; import net.minestom.server.entity.LivingEntity; +import net.minestom.server.entity.damage.Damage; import net.minestom.server.entity.damage.DamageType; import net.minestom.server.event.trait.CancellableEvent; import net.minestom.server.event.trait.EntityInstanceEvent; @@ -15,17 +16,14 @@ import org.jetbrains.annotations.Nullable; public class EntityDamageEvent implements EntityInstanceEvent, CancellableEvent { private final Entity entity; - private final DamageType damageType; - private float damage; + private final Damage damage; private SoundEvent sound; private boolean animation = true; private boolean cancelled; - public EntityDamageEvent(@NotNull LivingEntity entity, @NotNull DamageType damageType, - float damage, @Nullable SoundEvent sound) { + public EntityDamageEvent(@NotNull LivingEntity entity, @NotNull Damage damage, @Nullable SoundEvent sound) { this.entity = entity; - this.damageType = damageType; this.damage = damage; this.sound = sound; } @@ -42,28 +40,10 @@ public class EntityDamageEvent implements EntityInstanceEvent, CancellableEvent * @return the damage type */ @NotNull - public DamageType getDamageType() { - return damageType; - } - - /** - * Gets the damage amount. - * - * @return the damage amount - */ - public float getDamage() { + public Damage getDamage() { return damage; } - /** - * Changes the damage amount. - * - * @param damage the new damage amount - */ - public void setDamage(float damage) { - this.damage = damage; - } - /** * Gets the damage sound. * diff --git a/src/main/java/net/minestom/server/registry/Registry.java b/src/main/java/net/minestom/server/registry/Registry.java index 9886982c9..e343c512a 100644 --- a/src/main/java/net/minestom/server/registry/Registry.java +++ b/src/main/java/net/minestom/server/registry/Registry.java @@ -53,6 +53,11 @@ public final class Registry { return new PotionEffectEntry(namespace, main, null); } + @ApiStatus.Internal + public static DamageTypeEntry damageType(String namespace, @NotNull Properties main) { + return new DamageTypeEntry(namespace, main, null); + } + @ApiStatus.Internal public static Map> load(Resource resource) { Map> map = new HashMap<>(); @@ -409,6 +414,23 @@ public final class Registry { } } + public record DamageTypeEntry(NamespaceID namespace, float exhaustion, + String messageId, + String scaling, + @Nullable String effects, + @Nullable String deathMessageType, + Properties custom) implements Entry { + public DamageTypeEntry(String namespace, Properties main, Properties custom) { + this(NamespaceID.from(namespace), + (float) main.getDouble("exhaustion"), + main.getString("message_id"), + main.getString("scaling"), + main.getString("effects"), + main.getString("death_message_type"), + custom); + } + } + public record EnchantmentEntry(NamespaceID namespace, int id, String translationKey, double maxLevel,