diff --git a/src/main/java/net/minestom/server/entity/PlayerSkin.java b/src/main/java/net/minestom/server/entity/PlayerSkin.java index 8c11364e1..0caec108e 100644 --- a/src/main/java/net/minestom/server/entity/PlayerSkin.java +++ b/src/main/java/net/minestom/server/entity/PlayerSkin.java @@ -9,6 +9,7 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.IOException; +import java.util.Objects; /** * Contains all the data required to store a skin. @@ -26,24 +27,6 @@ public class PlayerSkin { this.signature = signature; } - /** - * Gets the skin textures value. - * - * @return the textures value - */ - public String getTextures() { - return textures; - } - - /** - * Gets the skin signature. - * - * @return the skin signature - */ - public String getSignature() { - return signature; - } - /** * Gets a skin from a Mojang UUID. * @@ -98,4 +81,42 @@ public class PlayerSkin { } } + /** + * Gets the skin textures value. + * + * @return the textures value + */ + public String getTextures() { + return textures; + } + + /** + * Gets the skin signature. + * + * @return the skin signature + */ + public String getSignature() { + return signature; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object object) { + if (this == object) return true; + if (object == null || getClass() != object.getClass()) return false; + PlayerSkin that = (PlayerSkin) object; + return Objects.equals(textures, that.textures) && + Objects.equals(signature, that.signature); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hash(textures, signature); + } + } diff --git a/src/main/java/net/minestom/server/item/ItemStack.java b/src/main/java/net/minestom/server/item/ItemStack.java index 8e7003725..a35131159 100644 --- a/src/main/java/net/minestom/server/item/ItemStack.java +++ b/src/main/java/net/minestom/server/item/ItemStack.java @@ -690,6 +690,9 @@ public class ItemStack implements DataContainer, PublicCloneable { if (material == Material.WRITTEN_BOOK) return new WrittenBookMeta(); + if(material == Material.FIREWORK_STAR) + return new FireworkEffectMeta(); + if (material == Material.FIREWORK_ROCKET) return new FireworkMeta(); diff --git a/src/main/java/net/minestom/server/item/firework/FireworkEffect.java b/src/main/java/net/minestom/server/item/firework/FireworkEffect.java new file mode 100644 index 000000000..f160aea43 --- /dev/null +++ b/src/main/java/net/minestom/server/item/firework/FireworkEffect.java @@ -0,0 +1,166 @@ +package net.minestom.server.item.firework; + +import net.minestom.server.chat.ChatColor; +import org.jetbrains.annotations.NotNull; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; + +import java.util.Objects; + +public class FireworkEffect { + + private final boolean flicker; + private final boolean trail; + private final FireworkEffectType type; + private final ChatColor color; + private final ChatColor fadeColor; + + /** + * Initializes a new firework effect. + * + * @param flicker {@code true} if this explosion has the Twinkle effect (glowstone dust), otherwise {@code false}. + * @param trail {@code true} if this explosion hsa the Trail effect (diamond), otherwise {@code false}. + * @param type The shape of this firework's explosion. + * @param color The primary color of this firework effect. + * @param fadeColor The secondary color of this firework effect. + */ + public FireworkEffect(boolean flicker, boolean trail, FireworkEffectType type, ChatColor color, ChatColor fadeColor) { + this.flicker = flicker; + this.trail = trail; + this.type = type; + this.color = color; + this.fadeColor = fadeColor; + } + + /** + * Retrieves a firework effect from the given {@code compound}. + * + * @param compound The NBT connection, which should be a fireworks effect. + * @return A new created firework effect. + */ + public static FireworkEffect fromCompound(@NotNull NBTCompound compound) { + + ChatColor primaryColor = null; + ChatColor secondaryColor = null; + + if (compound.containsKey("Colors")) { + int[] color = compound.getIntArray("Colors"); + primaryColor = ChatColor.fromRGB((byte) color[0], (byte) color[1], (byte) color[2]); + } + + if (compound.containsKey("FadeColors")) { + int[] fadeColor = compound.getIntArray("FadeColors"); + secondaryColor = ChatColor.fromRGB((byte) fadeColor[0], (byte) fadeColor[1], (byte) fadeColor[2]); + + } + + boolean flicker = compound.containsKey("Flicker") && compound.getByte("Flicker") == 1; + boolean trail = compound.containsKey("Trail") && compound.getByte("Trail") == 1; + FireworkEffectType type = compound.containsKey("Type") ? FireworkEffectType.byId(compound.getByte("Type")) : FireworkEffectType.SMALL_BALL; + + + return new FireworkEffect( + flicker, + trail, + type, + primaryColor, + secondaryColor); + } + + /** + * Whether the firework has a flicker effect. + * + * @return {@code 1} if this explosion has the flicker effect, otherwise {@code 0}. + */ + public byte getFlicker() { + return (byte) (this.flicker ? 1 : 0); + } + + /** + * Whether the firework has a trail effect. + * + * @return {@code 1} if this explosion has the trail effect, otherwise {@code 0}; + */ + public byte getTrail() { + return (byte) (this.trail ? 1 : 0); + } + + /** + * Retrieves type of the firework effect. + * + * @return The firework type as a byte. + */ + public byte getType() { + return this.type.getType(); + } + + /** + * Retrieves array of integer values corresponding to the primary colors of this firework's explosion. + *

+ * If custom color codes are used, the game renders is as "Custom" in the tooltip, but the proper color is used + * in the explosion. + * + * @return An array of integer values corresponding to the primary colors of this firework's explosion. + */ + public int[] getColors() { + return new int[]{ + this.color.getRed(), + this.color.getGreen(), + this.color.getBlue() + }; + } + + /** + * Retrieves array of integer values corresponding to the fading colors of this firework's explosion. + *

+ * Same handling of custom colors as Colors. + * + * @return An array of integer values corresponding to the fading colors of this firework's explosion. + */ + public int[] getFadeColors() { + return new int[]{ + this.fadeColor.getRed(), + this.fadeColor.getGreen(), + this.fadeColor.getBlue() + }; + } + + /** + * Retrieves the {@link FireworkEffect} as a {@link NBTCompound}. + * + * @return The firework effect as a nbt compound. + */ + public NBTCompound asCompound() { + NBTCompound explosionCompound = new NBTCompound(); + explosionCompound.setByte("Flicker", this.getFlicker()); + explosionCompound.setByte("Trail", this.getTrail()); + explosionCompound.setByte("Type", this.getType()); + + explosionCompound.setIntArray("Colors", this.getColors()); + explosionCompound.setIntArray("FadeColors", this.getFadeColors()); + + return explosionCompound; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + FireworkEffect that = (FireworkEffect) o; + return flicker == that.flicker && + trail == that.trail && + type == that.type && + Objects.equals(color, that.color) && + Objects.equals(fadeColor, that.fadeColor); + } + + /** + * {@inheritDoc} + */ + @Override + public int hashCode() { + return Objects.hash(flicker, trail, type, color, fadeColor); + } +} diff --git a/src/main/java/net/minestom/server/item/firework/FireworkEffectType.java b/src/main/java/net/minestom/server/item/firework/FireworkEffectType.java new file mode 100644 index 000000000..1762934ad --- /dev/null +++ b/src/main/java/net/minestom/server/item/firework/FireworkEffectType.java @@ -0,0 +1,52 @@ +package net.minestom.server.item.firework; + +import java.util.HashMap; +import java.util.Map; + +/** + * An enumeration that representing all available firework types. + */ +public enum FireworkEffectType { + + SMALL_BALL((byte) 0), + LARGE_BAR((byte) 1), + STAR_SHAPED((byte) 2), + CREEPER_SHAPED((byte) 3), + BURST((byte) 4), + ; + + private static final Map BY_ID = new HashMap<>(); + + static { + for (FireworkEffectType value : values()) { + BY_ID.put(value.type, value); + } + } + + private final byte type; + + FireworkEffectType(byte type) { + this.type = type; + } + + /** + * Retrieves a {@link FireworkEffectType} by the given {@code id}. + * + * @param id The identifier of the firework effect type. + * @return A firework effect type or {@code null}. + */ + public static FireworkEffectType byId(byte id) { + return BY_ID.get(id); + } + + /** + * Retrieves the type of the firework effect. + * + * @return The type of the firework effect as a byte. + */ + public byte getType() { + return type; + } + +} + diff --git a/src/main/java/net/minestom/server/item/metadata/FireworkEffectMeta.java b/src/main/java/net/minestom/server/item/metadata/FireworkEffectMeta.java new file mode 100644 index 000000000..045dee42e --- /dev/null +++ b/src/main/java/net/minestom/server/item/metadata/FireworkEffectMeta.java @@ -0,0 +1,92 @@ +package net.minestom.server.item.metadata; + +import net.minestom.server.item.firework.FireworkEffect; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; + +public class FireworkEffectMeta extends ItemMeta { + + private FireworkEffect fireworkEffect; + + /** + * Retrieves the firework effect for this meta. + * + * @return The current firework effect, or {@code null} if none. + */ + @Nullable + public FireworkEffect getFireworkEffect() { + return fireworkEffect; + } + + /** + * Changes the {@link FireworkEffect} for this item meta. + * + * @param fireworkEffect The new firework effect, or {@code null}. + */ + public void setFireworkEffect(@Nullable FireworkEffect fireworkEffect) { + this.fireworkEffect = fireworkEffect; + } + + /** + * Whether if this item meta has an effect. + * + * @return {@code true} if this item meta has an effect, otherwise {@code false}. + */ + public boolean hasFireworkEffect() { + return this.fireworkEffect != null; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasNbt() { + return this.hasFireworkEffect(); + } + + /** + * {@inheritDoc} + */ + @Override + public boolean isSimilar(@NotNull ItemMeta itemMeta) { + if (!(itemMeta instanceof FireworkEffectMeta)) { + return false; + } + + FireworkEffectMeta fireworkEffectMeta = (FireworkEffectMeta) itemMeta; + return fireworkEffectMeta.fireworkEffect == this.fireworkEffect; + } + + /** + * {@inheritDoc} + */ + @Override + public void read(@NotNull NBTCompound compound) { + if (compound.containsKey("Explosion")) { + this.fireworkEffect = FireworkEffect.fromCompound(compound.getCompound("Explosion")); + } + + } + + /** + * {@inheritDoc} + */ + @Override + public void write(@NotNull NBTCompound compound) { + if (this.fireworkEffect != null) { + compound.set("Explosion", this.fireworkEffect.asCompound()); + } + } + + /** + * {@inheritDoc} + */ + @NotNull + @Override + public ItemMeta clone() { + FireworkEffectMeta fireworkEffectMeta = (FireworkEffectMeta) super.clone(); + fireworkEffectMeta.fireworkEffect = this.fireworkEffect; + return fireworkEffectMeta; + } +} diff --git a/src/main/java/net/minestom/server/item/metadata/FireworkMeta.java b/src/main/java/net/minestom/server/item/metadata/FireworkMeta.java index 90a2d70b2..fc645914f 100644 --- a/src/main/java/net/minestom/server/item/metadata/FireworkMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/FireworkMeta.java @@ -1,47 +1,171 @@ package net.minestom.server.item.metadata; +import net.minestom.server.item.firework.FireworkEffect; import org.jetbrains.annotations.NotNull; import org.jglrxavpok.hephaistos.nbt.NBTCompound; +import org.jglrxavpok.hephaistos.nbt.NBTList; +import org.jglrxavpok.hephaistos.nbt.NBTTypes; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.CopyOnWriteArrayList; + +/** + * Represents a firework rocket meta data and its effects. + */ public class FireworkMeta extends ItemMeta { - private boolean flicker; - private boolean trail; - private FireworkType type; - private int[] colors; - private int[] fadeColors; - + private List effects = new CopyOnWriteArrayList<>(); private byte flightDuration; - // TODO Explosions list - @Override - public boolean hasNbt() { - return false; + /** + * Adds a firework effect to this firework. + * + * @param effect The firework effect to be added. + */ + public void addFireworkEffect(FireworkEffect effect) { + this.effects.add(effect); } + /** + * Adds an array of firework effects to this firework. + * + * @param effects An array of firework effects to be added. + */ + public void addFireworkEffects(FireworkEffect... effects) { + this.effects.addAll(Arrays.asList(effects)); + } + + /** + * Removes a firework effect from this firework. + * + * @param index The index of the firework effect to be removed. + * @throws IndexOutOfBoundsException If index {@literal < 0 or index >} {@link #getEffectSize()} + */ + public void removeFireworkEffect(int index) throws IndexOutOfBoundsException { + this.effects.remove(index); + } + + /** + * Removes a firework effects from this firework. + * + * @param effect The effect to be removed. + */ + public void removeFireworkEffect(FireworkEffect effect) { + this.effects.remove(effect); + } + + /** + * Retrieves a collection with all effects in this firework. + * + * @return A collection with all effects in this firework. + */ + public List getEffects() { + return effects; + } + + /** + * Retrieves the size of effects in this firework. + * + * @return The size of the effects. + */ + public int getEffectSize() { + return this.effects.size(); + } + + /** + * Removes all effects from this firework. + */ + public void clearEffects() { + this.effects.clear(); + } + + /** + * Whether this firework has any effects. + * + * @return {@code true} if this firework has any effects, otherwise {@code false}. + */ + public boolean hasEffects() { + return this.effects.isEmpty(); + } + + /** + * Changes the flight duration of this firework. + * + * @param flightDuration The new flight duration for this firework. + */ + public void setFlightDuration(byte flightDuration) { + this.flightDuration = flightDuration; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasNbt() { + return this.flightDuration == 0 || !this.effects.isEmpty(); + } + + /** + * {@inheritDoc} + */ @Override public boolean isSimilar(@NotNull ItemMeta itemMeta) { return false; } + /** + * {@inheritDoc} + */ @Override public void read(@NotNull NBTCompound compound) { + this.effects.clear(); + if (compound.containsKey("Fireworks")) { + NBTCompound fireworksCompound = compound.getCompound("Fireworks"); + if (fireworksCompound.containsKey("Flight")) { + this.flightDuration = fireworksCompound.getAsByte("Flight"); + } + + if (fireworksCompound.containsKey("Explosions")) { + NBTList explosions = fireworksCompound.getList("Explosions"); + + for (NBTCompound explosion : explosions) { + this.effects.add(FireworkEffect.fromCompound(explosion)); + } + } + + } } + /** + * {@inheritDoc} + */ @Override public void write(@NotNull NBTCompound compound) { + NBTCompound fireworksCompound = new NBTCompound(); + fireworksCompound.setByte("Flight", this.flightDuration); + NBTList explosions = new NBTList<>(NBTTypes.TAG_Compound); + for (FireworkEffect effect : this.effects) { + explosions.add(effect.asCompound()); + } + + fireworksCompound.set("Explosions", explosions); + + compound.set("Fireworks", fireworksCompound); } + /** + * {@inheritDoc} + */ @NotNull @Override public ItemMeta clone() { - return null; - } + FireworkMeta fireworkMeta = (FireworkMeta) super.clone(); + fireworkMeta.effects = this.effects; + fireworkMeta.flightDuration = this.flightDuration; - public enum FireworkType { - SMALL_BALL, LARGE_BALL, STAR_SHAPED, CREEPER_SHAPED, BURST + return fireworkMeta; } - } diff --git a/src/main/java/net/minestom/server/item/metadata/ItemMeta.java b/src/main/java/net/minestom/server/item/metadata/ItemMeta.java index 899b84285..d68a6d3e1 100644 --- a/src/main/java/net/minestom/server/item/metadata/ItemMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/ItemMeta.java @@ -44,6 +44,9 @@ public abstract class ItemMeta implements PublicCloneable { */ public abstract void write(@NotNull NBTCompound compound); + /** + * {@inheritDoc} + */ @NotNull @Override public ItemMeta clone() { diff --git a/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java b/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java index ff2012e24..f7f49dbc4 100644 --- a/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java +++ b/src/main/java/net/minestom/server/item/metadata/PlayerHeadMeta.java @@ -1,19 +1,89 @@ package net.minestom.server.item.metadata; +import net.minestom.server.MinecraftServer; +import net.minestom.server.entity.Player; import net.minestom.server.entity.PlayerSkin; +import net.minestom.server.utils.Utils; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import org.jglrxavpok.hephaistos.nbt.NBTCompound; +import org.jglrxavpok.hephaistos.nbt.NBTList; +import org.jglrxavpok.hephaistos.nbt.NBTTypes; +import java.util.UUID; + +/** + * Represents a skull that can have an owner. + */ public class PlayerHeadMeta extends ItemMeta { - private String playerName; + private UUID skullOwner; private PlayerSkin playerSkin; - @Override - public boolean hasNbt() { - return playerSkin != null; + /** + * Sets the owner of the skull. + * + * @param player The new owner of the skull. + * @return {@code true} if the owner was successfully set, otherwise {@code false}. + */ + public boolean setOwningPlayer(@NotNull Player player) { + if (player.getSkin() != null) { + this.skullOwner = player.getUuid(); + this.playerSkin = player.getSkin(); + return true; + } + return false; } + /** + * Retrieves the owner of the head. + * + * @return The head's owner. + */ + @Nullable + public UUID getSkullOwner() { + return skullOwner; + } + + /** + * Changes the owner of the head. + * + * @param skullOwner The new head owner. + */ + public void setSkullOwner(@NotNull UUID skullOwner) { + this.skullOwner = skullOwner; + } + + /** + * Retrieves the skin of the head. + * + * @return The head's skin. + */ + @Nullable + public PlayerSkin getPlayerSkin() { + return playerSkin; + } + + /** + * Changes the skin of the head. + * + * @param playerSkin The new skin for the head. + */ + public void setPlayerSkin(@NotNull PlayerSkin playerSkin) { + this.playerSkin = playerSkin; + } + + /** + * {@inheritDoc} + */ + @Override + public boolean hasNbt() { + return this.skullOwner != null || playerSkin != null; + } + + /** + * {@inheritDoc} + */ @Override public boolean isSimilar(@NotNull ItemMeta itemMeta) { if (!(itemMeta instanceof PlayerHeadMeta)) @@ -22,19 +92,67 @@ public class PlayerHeadMeta extends ItemMeta { return playerHeadMeta.playerSkin == playerSkin; } + /** + * {@inheritDoc} + */ @Override public void read(@NotNull NBTCompound compound) { + if (compound.containsKey("SkullOwner")) { + NBTCompound skullOwnerCompound = compound.getCompound("SkullOwner"); + + if (skullOwnerCompound.containsKey("Id")) { + this.skullOwner = Utils.intArrayToUuid(skullOwnerCompound.getIntArray("Id")); + } + + if (skullOwnerCompound.containsKey("Properties")) { + NBTCompound propertyCompound = skullOwnerCompound.getCompound("Properties"); + + if (propertyCompound.containsKey("textures")) { + NBTList textures = propertyCompound.getList("textures"); + if (textures != null) { + NBTCompound nbt = textures.get(0); + this.playerSkin = new PlayerSkin(nbt.getString("Value"), nbt.getString("Signature")); + } + } + + } + + } } + /** + * {@inheritDoc} + */ @Override public void write(@NotNull NBTCompound compound) { + NBTCompound skullOwnerCompound = new NBTCompound(); + // Sets the identifier for the skull + skullOwnerCompound.setIntArray("Id", Utils.uuidToIntArray(this.skullOwner)); + + if (this.playerSkin == null) { + this.playerSkin = PlayerSkin.fromUuid(this.skullOwner.toString()); + } + + NBTList textures = new NBTList<>(NBTTypes.TAG_Compound); + textures.add(new NBTCompound().setString("Value", this.playerSkin.getTextures()).setString("Signature", this.playerSkin.getSignature())); + skullOwnerCompound.set("Properties", new NBTCompound().set("textures", textures)); + + compound.set("SkullOwner", skullOwnerCompound); } + /** + * {@inheritDoc} + */ @NotNull @Override public ItemMeta clone() { - return null; + PlayerHeadMeta playerHeadMeta = (PlayerHeadMeta) super.clone(); + playerHeadMeta.skullOwner = this.skullOwner; + playerHeadMeta.playerSkin = this.playerSkin; + return playerHeadMeta; } + + } diff --git a/src/main/java/net/minestom/server/utils/clone/PublicCloneable.java b/src/main/java/net/minestom/server/utils/clone/PublicCloneable.java index 45b620907..bcfaf74cb 100644 --- a/src/main/java/net/minestom/server/utils/clone/PublicCloneable.java +++ b/src/main/java/net/minestom/server/utils/clone/PublicCloneable.java @@ -9,6 +9,11 @@ import org.jetbrains.annotations.NotNull; */ public interface PublicCloneable extends Cloneable { + /** + * Creates and returns a copy of this object. + * + * @return A clone of this instance. + */ @NotNull T clone(); }