feat: simplify sound events, fix update explosion packet

This commit is contained in:
mworzala 2024-04-19 07:53:32 -04:00
parent 9894425f12
commit 8a829ebe47
No known key found for this signature in database
GPG Key ID: B148F922E64797C7
21 changed files with 1913 additions and 1793 deletions

View File

@ -30,7 +30,7 @@ public class Generators {
generator.generate(resource("potion_effects.json"), "net.minestom.server.potion", "PotionEffect", "PotionEffectImpl", "PotionEffects");
generator.generate(resource("potions.json"), "net.minestom.server.potion", "PotionType", "PotionTypeImpl", "PotionTypes");
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("sounds.json"), "net.minestom.server.sound", "SoundEvent", "BuiltinSoundEvent", "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");
generator.generate(resource("trim_materials.json"), "net.minestom.server.item.armor", "TrimMaterial", "TrimMaterialImpl", "TrimMaterials");

View File

@ -49,9 +49,20 @@ public class DyeColorGenerator extends MinestomCodeGenerator {
.addSuperinterface(ClassName.get("net.kyori.adventure.util", "RGBLike"))
.addModifiers(Modifier.PUBLIC).addJavadoc("AUTOGENERATED by " + getClass().getSimpleName());
ClassName networkBufferCN = ClassName.get("net.minestom.server.network", "NetworkBuffer");
ParameterizedTypeName networkBufferTypeCN = ParameterizedTypeName.get(networkBufferCN.nestedClass("Type"), dyeColorCN);
ClassName binaryTagSerializerCN = ClassName.get("net.minestom.server.utils.nbt", "BinaryTagSerializer");
ParameterizedTypeName binaryTagSerializerTypeCN = ParameterizedTypeName.get(binaryTagSerializerCN, dyeColorCN);
// Fields
dyeColorEnum.addFields(
List.of(
FieldSpec.builder(networkBufferTypeCN, "NETWORK_TYPE", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("$T.fromEnum($T.class)", networkBufferCN, dyeColorCN)
.build(),
FieldSpec.builder(binaryTagSerializerTypeCN, "NBT_TYPE", Modifier.PUBLIC, Modifier.STATIC, Modifier.FINAL)
.initializer("$T.fromEnumStringable($T.class)", binaryTagSerializerCN, dyeColorCN)
.build(),
FieldSpec.builder(colorCN, "textureDiffuseColor", Modifier.PRIVATE, Modifier.FINAL).build(),
FieldSpec.builder(colorCN, "textColor", Modifier.PRIVATE, Modifier.FINAL).build(),
FieldSpec.builder(colorCN, "fireworkColor", Modifier.PRIVATE, Modifier.FINAL).build(),

View File

@ -7,6 +7,7 @@ import net.minestom.server.advancements.notifications.Notification;
import net.minestom.server.advancements.notifications.NotificationCenter;
import net.minestom.server.adventure.MinestomAdventure;
import net.minestom.server.adventure.audience.Audiences;
import net.minestom.server.coordinate.Point;
import net.minestom.server.coordinate.Pos;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
@ -37,9 +38,16 @@ import net.minestom.server.item.component.BlockPredicates;
import net.minestom.server.item.component.ItemBlockState;
import net.minestom.server.monitoring.BenchmarkManager;
import net.minestom.server.monitoring.TickMonitor;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.packet.server.play.ExplosionPacket;
import net.minestom.server.particle.Particle;
import net.minestom.server.particle.data.BlockParticleData;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.utils.MathUtils;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.time.TimeUnit;
import net.minestom.server.world.DimensionType;
import org.jetbrains.annotations.NotNull;
import java.time.Duration;
import java.util.List;
@ -87,6 +95,8 @@ public class PlayerInit {
itemEntity.setInstance(player.getInstance(), playerPos.withY(y -> y + 1.5));
Vec velocity = playerPos.direction().mul(6);
itemEntity.setVelocity(velocity);
player.sendPacket(makeExplosion(playerPos, velocity));
})
.addListener(PlayerDisconnectEvent.class, event -> System.out.println("DISCONNECTION " + event.getPlayer().getUsername()))
.addListener(AsyncPlayerConfigurationEvent.class, event -> {
@ -164,6 +174,20 @@ public class PlayerInit {
event.getInstance().setBlock(event.getBlockPosition(), block);
});
private static final byte[] AIR_BLOCK_PARTICLE = NetworkBuffer.makeArray(new BlockParticleData(Block.AIR)::write);
private static @NotNull ExplosionPacket makeExplosion(@NotNull Point position, @NotNull Vec motion) {
return new ExplosionPacket(
position.x(), position.y(), position.z(),
0, new byte[0],
(float) motion.x(), (float) motion.y(), (float) motion.z(),
ExplosionPacket.BlockInteraction.KEEP,
Particle.BLOCK.id(), AIR_BLOCK_PARTICLE,
Particle.BLOCK.id(), AIR_BLOCK_PARTICLE,
SoundEvent.of(NamespaceID.from("not.a.real.sound"), 0f)
);
}
static {
InstanceManager instanceManager = MinecraftServer.getInstanceManager();

View File

@ -42,6 +42,7 @@ public enum DyeColor implements RGBLike {
BLACK(new Color(0x1d1d21), new Color(0x0), new Color(0x1e1b1b), 29);
public static final NetworkBuffer.Type<DyeColor> NETWORK_TYPE = NetworkBuffer.fromEnum(DyeColor.class);
public static final BinaryTagSerializer<DyeColor> NBT_TYPE = BinaryTagSerializer.fromEnumStringable(DyeColor.class);
private final Color textureDiffuseColor;

View File

@ -49,7 +49,7 @@ interface PotionEffects {
PotionEffect ABSORPTION = PotionEffectImpl.get("minecraft:absorption");
PotionEffect SATURATION = PotionEffectImpl.get("minecraft:saturationModifier");
PotionEffect SATURATION = PotionEffectImpl.get("minecraft:saturation");
PotionEffect GLOWING = PotionEffectImpl.get("minecraft:glowing");

View File

@ -10,11 +10,11 @@ import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
import net.kyori.adventure.title.Title;
import net.kyori.adventure.title.TitlePart;
import net.minestom.server.coordinate.Vec;
import net.minestom.server.entity.Entity;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.play.*;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.utils.NamespaceID;
import net.minestom.server.utils.TickUtils;
import org.jetbrains.annotations.NotNull;
@ -111,15 +111,12 @@ public class AdventurePacketConvertor {
* @return the sound packet
*/
public static @NotNull ServerPacket createSoundPacket(@NotNull Sound sound, double x, double y, double z) {
final SoundEvent minestomSound = SoundEvent.fromNamespaceId(sound.name().asString());
final NamespaceID soundName = NamespaceID.from(sound.name().asString());
SoundEvent minestomSound = SoundEvent.fromNamespaceId(soundName);
if (minestomSound == null) minestomSound = SoundEvent.of(soundName, null);
final long seed = sound.seed().orElse(ThreadLocalRandom.current().nextLong());
if (minestomSound == null) {
return new SoundEffectPacket(sound.name().asString(), null, sound.source(),
new Vec(x, y, z), sound.volume(), sound.pitch(), seed);
} else {
return new SoundEffectPacket(minestomSound, null, sound.source(),
new Vec(x, y, z), sound.volume(), sound.pitch(), seed);
}
return new SoundEffectPacket(minestomSound, sound.source(), (int) x, (int) y, (int) z, sound.volume(), sound.pitch(), seed);
}
/**
@ -136,14 +133,12 @@ public class AdventurePacketConvertor {
if (!(emitter instanceof Entity entity))
throw new IllegalArgumentException("you can only call this method with entities");
final SoundEvent minestomSound = SoundEvent.fromNamespaceId(sound.name().asString());
final long seed = sound.seed().orElse(ThreadLocalRandom.current().nextLong());
final NamespaceID soundName = NamespaceID.from(sound.name().asString());
SoundEvent minestomSound = SoundEvent.fromNamespaceId(soundName);
if (minestomSound == null) minestomSound = SoundEvent.of(soundName, null);
if (minestomSound != null) {
return new EntitySoundEffectPacket(minestomSound, null, sound.source(), entity.getEntityId(), sound.volume(), sound.pitch(), seed);
} else {
return new EntitySoundEffectPacket(sound.name().asString(), null, sound.source(), entity.getEntityId(), sound.volume(), sound.pitch(), seed);
}
final long seed = sound.seed().orElse(ThreadLocalRandom.current().nextLong());
return new EntitySoundEffectPacket(minestomSound, sound.source(), entity.getEntityId(), sound.volume(), sound.pitch(), seed);
}
/**

View File

@ -366,8 +366,7 @@ public class LivingEntity extends Entity implements EquipmentHandler {
// TODO: separate living entity categories
soundCategory = Source.HOSTILE;
}
sendPacketToViewersAndSelf(new SoundEffectPacket(sound, null, soundCategory,
getPosition(), 1.0f, 1.0f, 0));
sendPacketToViewersAndSelf(new SoundEffectPacket(sound, soundCategory, getPosition(), 1.0f, 1.0f, 0));
}
});

View File

@ -8,6 +8,7 @@ import net.minestom.server.entity.Player;
import net.minestom.server.entity.pathfinding.PFColumnarSpace;
import net.minestom.server.instance.block.Block;
import net.minestom.server.instance.block.BlockHandler;
import net.minestom.server.instance.generator.Generator;
import net.minestom.server.instance.heightmap.Heightmap;
import net.minestom.server.network.packet.server.SendablePacket;
import net.minestom.server.network.packet.server.play.ChunkDataPacket;
@ -233,11 +234,11 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter,
}
/**
* Gets if this chunk will or had been loaded with a {@link ChunkGenerator}.
* Gets if this chunk will or had been loaded with a {@link Generator}.
* <p>
* If false, the chunk will be entirely empty when loaded.
*
* @return true if this chunk is affected by a {@link ChunkGenerator}
* @return true if this chunk is affected by a {@link Generator}
*/
public boolean shouldGenerate() {
return shouldGenerate;
@ -247,7 +248,7 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter,
* Gets if this chunk is read-only.
* <p>
* Being read-only should prevent block placing/breaking and setting block from an {@link Instance}.
* It does not affect {@link IChunkLoader} and {@link ChunkGenerator}.
* It does not affect {@link IChunkLoader} and {@link Generator}.
*
* @return true if the chunk is read-only
*/
@ -259,7 +260,7 @@ public abstract class Chunk implements Block.Getter, Block.Setter, Biome.Getter,
* Changes the read state of the chunk.
* <p>
* Being read-only should prevent block placing/breaking and setting block from an {@link Instance}.
* It does not affect {@link IChunkLoader} and {@link ChunkGenerator}.
* It does not affect {@link IChunkLoader} and {@link Generator}.
*
* @param readOnly true to make the chunk read-only, false otherwise
*/

View File

@ -27,7 +27,7 @@ public interface IChunkLoader {
}
/**
* Loads a {@link Chunk}, all blocks should be set since the {@link ChunkGenerator} is not applied.
* Loads a {@link Chunk}, all blocks should be set since the {@link net.minestom.server.instance.generator.Generator} is not applied.
*
* @param instance the {@link Instance} where the {@link Chunk} belong
* @param chunkX the chunk X

View File

@ -66,7 +66,7 @@ public sealed interface ItemComponent<T> extends StaticProtocolObject permits It
ItemComponent<LodestoneTracker> LODESTONE_TRACKER = declare("lodestone_tracker", LodestoneTracker.NETWORK_TYPE, LodestoneTracker.NBT_TYPE);
ItemComponent<FireworkExplosion> FIREWORK_EXPLOSION = declare("firework_explosion", FireworkExplosion.NETWORK_TYPE, FireworkExplosion.NBT_TYPE);
ItemComponent<FireworkList> FIREWORKS = declare("fireworks", FireworkList.NETWORK_TYPE, FireworkList.NBT_TYPE);
ItemComponent<Void> PROFILE = declare("profile", null, null); //todo
ItemComponent<HeadProfile> PROFILE = declare("profile", HeadProfile.NETWORK_TYPE, HeadProfile.NBT_TYPE);
ItemComponent<String> NOTE_BLOCK_SOUND = declare("note_block_sound", NetworkBuffer.STRING, BinaryTagSerializer.STRING);
ItemComponent<Void> BANNER_PATTERNS = declare("banner_patterns", null, null); //todo
ItemComponent<DyeColor> BASE_COLOR = declare("base_color", DyeColor.NETWORK_TYPE, DyeColor.NBT_TYPE);

View File

@ -0,0 +1,87 @@
package net.minestom.server.item.component;
import net.kyori.adventure.nbt.CompoundBinaryTag;
import net.kyori.adventure.nbt.IntArrayBinaryTag;
import net.kyori.adventure.nbt.StringBinaryTag;
import net.minestom.server.entity.PlayerSkin;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.utils.nbt.BinaryTagSerializer;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.List;
import java.util.UUID;
public record HeadProfile(@Nullable String name, @Nullable UUID uuid, @NotNull List<Property> properties) {
public static final HeadProfile EMPTY = new HeadProfile(null, null, List.of());
public static final NetworkBuffer.Type<HeadProfile> NETWORK_TYPE = new NetworkBuffer.Type<HeadProfile>() {
@Override
public void write(@NotNull NetworkBuffer buffer, HeadProfile value) {
buffer.writeOptional(NetworkBuffer.STRING, value.name);
buffer.writeOptional(NetworkBuffer.UUID, value.uuid);
buffer.writeCollection(Property.NETWORK_TYPE, value.properties);
}
@Override
public HeadProfile read(@NotNull NetworkBuffer buffer) {
return new HeadProfile(buffer.readOptional(NetworkBuffer.STRING), buffer.readOptional(NetworkBuffer.UUID), buffer.readCollection(Property.NETWORK_TYPE, Short.MAX_VALUE));
}
};
public static final BinaryTagSerializer<HeadProfile> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> new HeadProfile(
tag.get("name") instanceof StringBinaryTag string ? string.value() : null,
tag.get("uuid") instanceof IntArrayBinaryTag intArray ? BinaryTagSerializer.UUID.read(intArray) : null,
Property.NBT_LIST_TYPE.read(tag.getList("properties"))
),
profile -> {
CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder();
if (profile.name != null) builder.putString("name", profile.name);
if (profile.uuid != null) builder.put("uuid", BinaryTagSerializer.UUID.write(profile.uuid));
if (!profile.properties.isEmpty()) builder.put("properties", Property.NBT_LIST_TYPE.write(profile.properties));
return builder.build();
}
);
public HeadProfile(@NotNull PlayerSkin playerSkin) {
this(null, null, List.of(new Property("textures", playerSkin.textures(), playerSkin.signature())));
}
public @Nullable PlayerSkin skin() {
for (Property property : properties) {
if ("textures".equals(property.name)) {
return new PlayerSkin(property.value, property.signature);
}
}
return null;
}
public record Property(@NotNull String name, @NotNull String value, @Nullable String signature) {
public static final NetworkBuffer.Type<Property> NETWORK_TYPE = new NetworkBuffer.Type<Property>() {
@Override
public void write(@NotNull NetworkBuffer buffer, Property value) {
buffer.write(NetworkBuffer.STRING, value.name);
buffer.write(NetworkBuffer.STRING, value.value);
buffer.writeOptional(NetworkBuffer.STRING, value.signature);
}
@Override
public Property read(@NotNull NetworkBuffer buffer) {
return new Property(buffer.read(NetworkBuffer.STRING), buffer.read(NetworkBuffer.STRING), buffer.readOptional(NetworkBuffer.STRING));
}
};
public static final BinaryTagSerializer<Property> NBT_TYPE = BinaryTagSerializer.COMPOUND.map(
tag -> new Property(tag.getString("name"), tag.getString("value"),
tag.get("signature") instanceof StringBinaryTag signature ? signature.value() : null),
property -> {
CompoundBinaryTag.Builder builder = CompoundBinaryTag.builder();
builder.putString("name", property.name);
builder.putString("value", property.value);
if (property.signature != null) builder.putString("signature", property.signature);
return builder.build();
}
);
public static final BinaryTagSerializer<List<Property>> NBT_LIST_TYPE = NBT_TYPE.list();
}
}

View File

@ -27,6 +27,7 @@ import java.util.*;
import java.util.function.BiConsumer;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@ApiStatus.Experimental
public final class NetworkBuffer {
@ -84,6 +85,24 @@ public final class NetworkBuffer {
return NetworkBufferTypeImpl.fromEnum(enumClass);
}
public static <T> Type<T> lazy(@NotNull Supplier<Type<T>> supplier) {
return new NetworkBuffer.Type<>() {
private Type<T> type;
@Override
public void write(@NotNull NetworkBuffer buffer, T value) {
if (type == null) type = supplier.get();
type.write(buffer, value);
}
@Override
public T read(@NotNull NetworkBuffer buffer) {
if (type == null) type = supplier.get();
return null;
}
};
}
ByteBuffer nioBuffer;
final boolean resizable;
int writeIndex;

View File

@ -6,17 +6,12 @@ import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static net.minestom.server.network.NetworkBuffer.*;
public record EntitySoundEffectPacket(
// only one of soundEvent and soundName may be present
@Nullable SoundEvent soundEvent,
@Nullable String soundName,
@Nullable Float range, // Only allowed with soundName
@NotNull SoundEvent soundEvent,
@NotNull Sound.Source source,
int entityId,
float volume,
@ -24,64 +19,18 @@ public record EntitySoundEffectPacket(
long seed
) implements ServerPacket.Play {
public EntitySoundEffectPacket {
Check.argCondition(soundEvent == null && soundName == null, "soundEvent and soundName cannot both be null");
Check.argCondition(soundEvent != null && soundName != null, "soundEvent and soundName cannot both be present");
Check.argCondition(soundName == null && range != null, "range cannot be present if soundName is null");
}
public EntitySoundEffectPacket(@NotNull SoundEvent soundEvent, @Nullable Float range, @NotNull Sound.Source source,
int entityId, float volume, float pitch, long seed) {
this(soundEvent, null, range, source, entityId, volume, pitch, seed);
}
public EntitySoundEffectPacket(@NotNull String soundName, @Nullable Float range, @NotNull Sound.Source source,
int entityId, float volume, float pitch, long seed) {
this(null, soundName, range, source, entityId, volume, pitch, seed);
}
public EntitySoundEffectPacket(@NotNull NetworkBuffer reader) {
this(fromReader(reader));
}
private EntitySoundEffectPacket(@NotNull EntitySoundEffectPacket packet) {
this(packet.soundEvent, packet.soundName, packet.range, packet.source, packet.entityId, packet.volume, packet.pitch, packet.seed);
}
private static @NotNull EntitySoundEffectPacket fromReader(@NotNull NetworkBuffer reader) {
int soundId = reader.read(VAR_INT);
SoundEvent soundEvent;
String soundName;
Float range = null;
if (soundId == 0) {
soundEvent = null;
soundName = reader.read(STRING);
range = reader.readOptional(FLOAT);
} else {
soundEvent = SoundEvent.fromId(soundId - 1);
soundName = null;
}
return new EntitySoundEffectPacket(
soundEvent,
soundName,
range,
this(reader.read(SoundEvent.NETWORK_TYPE),
reader.readEnum(Sound.Source.class),
reader.read(VAR_INT),
reader.read(FLOAT),
reader.read(FLOAT),
reader.read(LONG)
);
reader.read(LONG));
}
@Override
public void write(@NotNull NetworkBuffer writer) {
if (soundEvent != null) {
writer.write(VAR_INT, soundEvent.id() + 1);
} else {
writer.write(VAR_INT, 0);
writer.write(STRING, soundName);
writer.writeOptional(FLOAT, range);
}
writer.write(SoundEvent.NETWORK_TYPE, soundEvent);
writer.write(VAR_INT, AdventurePacketConvertor.getSoundSourceValue(source));
writer.write(VAR_INT, entityId);
writer.write(FLOAT, volume);

View File

@ -17,23 +17,23 @@ public record ExplosionPacket(double x, double y, double z, float radius,
@NotNull BlockInteraction blockInteraction,
int smallParticleId, byte @NotNull [] smallParticleData,
int largeParticleId, byte @NotNull [] largeParticleData,
@NotNull String soundName, boolean hasFixedSoundRange, float soundRange) implements ServerPacket.Play {
@NotNull SoundEvent sound) implements ServerPacket.Play {
public static final SoundEvent DEFAULT_SOUND = SoundEvent.ENTITY_GENERIC_EXPLODE;
private static @NotNull ExplosionPacket fromReader(@NotNull NetworkBuffer reader) {
double x = reader.read(DOUBLE), y = reader.read(DOUBLE), z = reader.read(DOUBLE);
float radius = reader.read(FLOAT);
byte[] records = reader.readBytes(reader.read(VAR_INT) * 3);
float playerMotionX = reader.read(FLOAT), playerMotionY = reader.read(FLOAT), playerMotionZ = reader.read(FLOAT);
BlockInteraction blockInteraction = BlockInteraction.values()[reader.read(VAR_INT)];
BlockInteraction blockInteraction = reader.readEnum(BlockInteraction.class);
int smallParticleId = reader.read(VAR_INT);
byte[] smallParticleData = readParticleData(reader, Particle.fromId(smallParticleId));
int largeParticleId = reader.read(VAR_INT);
byte[] largeParticleData = readParticleData(reader, Particle.fromId(largeParticleId));
String soundName = reader.read(STRING);
boolean hasFixedSoundRange = reader.read(BOOLEAN);
float soundRange = hasFixedSoundRange ? reader.read(FLOAT) : 0;
SoundEvent sound = reader.read(SoundEvent.NETWORK_TYPE);
return new ExplosionPacket(x, y, z, radius, records, playerMotionX, playerMotionY, playerMotionZ,
blockInteraction, smallParticleId, smallParticleData, largeParticleId, largeParticleData,
soundName, hasFixedSoundRange, soundRange);
sound);
}
private static byte @NotNull [] readParticleData(@NotNull NetworkBuffer reader, Particle particle) {
@ -75,13 +75,13 @@ public record ExplosionPacket(double x, double y, double z, float radius,
this(x, y, z, radius, records, playerMotionX, playerMotionY, playerMotionZ,
BlockInteraction.DESTROY, Particle.EXPLOSION.id(), new byte[] {},
Particle.EXPLOSION_EMITTER.id(), new byte[] {},
SoundEvent.ENTITY_GENERIC_EXPLODE.name(), false, 0);
DEFAULT_SOUND);
}
private ExplosionPacket(@NotNull ExplosionPacket packet) {
this(packet.x, packet.y, packet.z, packet.radius, packet.records, packet.playerMotionX, packet.playerMotionY, packet.playerMotionZ,
packet.blockInteraction, packet.smallParticleId, packet.smallParticleData, packet.largeParticleId, packet.largeParticleData,
packet.soundName, packet.hasFixedSoundRange, packet.soundRange);
packet.sound);
}
@Override
@ -100,9 +100,7 @@ public record ExplosionPacket(double x, double y, double z, float radius,
writer.write(RAW_BYTES, smallParticleData);
writer.write(VAR_INT, largeParticleId);
writer.write(RAW_BYTES, largeParticleData);
writer.write(STRING, soundName);
writer.write(BOOLEAN, hasFixedSoundRange);
if (hasFixedSoundRange) writer.write(FLOAT, soundRange);
writer.write(SoundEvent.NETWORK_TYPE, sound);
}
@Override

View File

@ -7,17 +7,12 @@ import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.ServerPacketIdentifier;
import net.minestom.server.sound.SoundEvent;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import static net.minestom.server.network.NetworkBuffer.*;
public record SoundEffectPacket(
// only one of soundEvent and soundName may be present
@Nullable SoundEvent soundEvent,
@Nullable String soundName,
@Nullable Float range, // Only allowed with soundName
@NotNull SoundEvent soundEvent,
@NotNull Source source,
int x,
int y,
@ -27,67 +22,25 @@ public record SoundEffectPacket(
long seed
) implements ServerPacket.Play {
public SoundEffectPacket {
Check.argCondition(soundEvent == null && soundName == null, "soundEvent and soundName cannot both be null");
Check.argCondition(soundEvent != null && soundName != null, "soundEvent and soundName cannot both be present");
Check.argCondition(soundName == null && range != null, "range cannot be present if soundName is null");
public SoundEffectPacket(@NotNull SoundEvent soundEvent, @NotNull Source source, @NotNull Point position, float volume, float pitch, long seed) {
this(soundEvent, source, position.blockX(), position.blockY(), position.blockZ(), volume, pitch, seed);
}
private static @NotNull SoundEffectPacket fromReader(@NotNull NetworkBuffer reader) {
int soundId = reader.read(VAR_INT);
SoundEvent soundEvent;
String soundName;
Float range = null;
if (soundId == 0) {
soundEvent = null;
soundName = reader.read(STRING);
range = reader.readOptional(FLOAT);
} else {
soundEvent = SoundEvent.fromId(soundId - 1);
soundName = null;
}
return new SoundEffectPacket(
soundEvent,
soundName,
range,
public SoundEffectPacket(@NotNull NetworkBuffer reader) {
this(reader.read(SoundEvent.NETWORK_TYPE),
reader.readEnum(Source.class),
reader.read(INT) * 8,
reader.read(INT) * 8,
reader.read(INT) * 8,
reader.read(FLOAT),
reader.read(FLOAT),
reader.read(LONG)
);
reader.read(LONG));
}
public SoundEffectPacket(@NotNull SoundEvent soundEvent, @Nullable Float range, @NotNull Source source,
@NotNull Point position, float volume, float pitch, long seed) {
this(soundEvent, null, range, source, position.blockX(), position.blockY(), position.blockZ(), volume, pitch, seed);
}
public SoundEffectPacket(@NotNull String soundName, @Nullable Float range, @NotNull Source source,
@NotNull Point position, float volume, float pitch, long seed) {
this(null, soundName, range, source, position.blockX(), position.blockY(), position.blockZ(), volume, pitch, seed);
}
public SoundEffectPacket(@NotNull NetworkBuffer reader) {
this(fromReader(reader));
}
private SoundEffectPacket(@NotNull SoundEffectPacket packet) {
this(packet.soundEvent, packet.soundName, packet.range, packet.source,
packet.x, packet.y, packet.z, packet.volume, packet.pitch, packet.seed);
}
@Override
public void write(@NotNull NetworkBuffer writer) {
if (soundEvent != null) {
writer.write(VAR_INT, soundEvent.id() + 1);
} else {
writer.write(VAR_INT, 0);
writer.write(STRING, soundName);
writer.writeOptional(FLOAT, range);
}
writer.write(SoundEvent.NETWORK_TYPE, soundEvent);
writer.write(VAR_INT, AdventurePacketConvertor.getSoundSourceValue(source));
writer.write(INT, x * 8);
writer.write(INT, y * 8);

View File

@ -0,0 +1,58 @@
package net.minestom.server.sound;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.registry.Registry;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
record BuiltinSoundEvent(NamespaceID namespace, int id) implements StaticProtocolObject, SoundEvent {
private static final Registry.Container<BuiltinSoundEvent> CONTAINER = Registry.createStaticContainer(Registry.Resource.SOUNDS,
(namespace, properties) -> new BuiltinSoundEvent(NamespaceID.from(namespace), properties.getInt("id")));
public static final NetworkBuffer.Type<SoundEvent> NETWORK_TYPE = new NetworkBuffer.Type<>() {
@Override
public void write(@NotNull NetworkBuffer buffer, SoundEvent value) {
switch (value) {
case BuiltinSoundEvent soundEvent -> buffer.write(NetworkBuffer.VAR_INT, soundEvent.id + 1);
case CustomSoundEvent soundEvent -> {
buffer.write(NetworkBuffer.VAR_INT, 0); // Custom sound
buffer.write(NetworkBuffer.STRING, soundEvent.name());
buffer.writeOptional(NetworkBuffer.FLOAT, soundEvent.range());
}
}
}
@Override
public SoundEvent read(@NotNull NetworkBuffer buffer) {
int id = buffer.read(NetworkBuffer.VAR_INT) - 1;
if (id != -1) return getId(id);
NamespaceID namespace = NamespaceID.from(buffer.read(NetworkBuffer.STRING));
return new CustomSoundEvent(namespace, buffer.readOptional(NetworkBuffer.FLOAT));
}
};
static SoundEvent get(@NotNull String namespace) {
return CONTAINER.get(namespace);
}
static SoundEvent getSafe(@NotNull String namespace) {
return CONTAINER.getSafe(namespace);
}
static SoundEvent getId(int id) {
return CONTAINER.getId(id);
}
static Collection<? extends SoundEvent> values() {
return CONTAINER.values();
}
@Override
public String toString() {
return name();
}
}

View File

@ -0,0 +1,8 @@
package net.minestom.server.sound;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
record CustomSoundEvent(@NotNull NamespaceID namespace, @Nullable Float range) implements SoundEvent {
}

View File

@ -2,33 +2,67 @@ package net.minestom.server.sound;
import net.kyori.adventure.key.Key;
import net.kyori.adventure.sound.Sound;
import net.minestom.server.registry.StaticProtocolObject;
import net.minestom.server.network.NetworkBuffer;
import net.minestom.server.registry.ProtocolObject;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
public sealed interface SoundEvent extends StaticProtocolObject, Sound.Type, SoundEvents permits SoundEventImpl {
/**
* Can represent a builtin/vanilla sound or a custom sound.
*/
public sealed interface SoundEvent extends ProtocolObject, Sound.Type, SoundEvents permits BuiltinSoundEvent, CustomSoundEvent {
static @NotNull Collection<@NotNull SoundEvent> values() {
return SoundEventImpl.values();
@NotNull NetworkBuffer.Type<SoundEvent> NETWORK_TYPE = NetworkBuffer.lazy(() -> BuiltinSoundEvent.NETWORK_TYPE); //todo what is the init issue here??
static @NotNull Collection<? extends SoundEvent> values() {
return BuiltinSoundEvent.values();
}
/**
* Get a builtin sound event by its namespace ID. Will never return a custom/resource pack sound.
*
* @param namespaceID the namespace ID of the sound event
* @return the sound event, or null if not found
*/
static @Nullable SoundEvent fromNamespaceId(@NotNull String namespaceID) {
return SoundEventImpl.getSafe(namespaceID);
return BuiltinSoundEvent.getSafe(namespaceID);
}
/**
* Get a builtin sound event by its namespace ID. Will never return a custom/resource pack sound.
*
* @param namespaceID the namespace ID of the sound event
* @return the sound event, or null if not found
*/
static @Nullable SoundEvent fromNamespaceId(@NotNull NamespaceID namespaceID) {
return fromNamespaceId(namespaceID.asString());
}
/**
* Get a builtin sound event by its protocol ID. Will never return a custom/resource pack sound.
*
* @param id the ID of the sound event
* @return the sound event, or null if not found
*/
static @Nullable SoundEvent fromId(int id) {
return SoundEventImpl.getId(id);
return BuiltinSoundEvent.getId(id);
}
/**
* Create a custom sound event. The {@link NamespaceID} should match a sound provided in the resource pack.
* @param namespaceID the namespace ID of the custom sound event
* @param range the range of the sound event, or null for (legacy) dynamic range
* @return the custom sound event
*/
static @NotNull SoundEvent of(@NotNull NamespaceID namespaceID, @Nullable Float range) {
return new CustomSoundEvent(namespaceID, range);
}
@Override
default @NotNull Key key() {
return StaticProtocolObject.super.key();
return ProtocolObject.super.key();
}
}

View File

@ -1,33 +0,0 @@
package net.minestom.server.sound;
import net.minestom.server.registry.Registry;
import net.minestom.server.utils.NamespaceID;
import org.jetbrains.annotations.NotNull;
import java.util.Collection;
record SoundEventImpl(NamespaceID namespace, int id) implements SoundEvent {
private static final Registry.Container<SoundEvent> CONTAINER = Registry.createStaticContainer(Registry.Resource.SOUNDS,
(namespace, properties) -> new SoundEventImpl(NamespaceID.from(namespace), properties.getInt("id")));
static SoundEvent get(@NotNull String namespace) {
return CONTAINER.get(namespace);
}
static SoundEvent getSafe(@NotNull String namespace) {
return CONTAINER.getSafe(namespace);
}
static SoundEvent getId(int id) {
return CONTAINER.getId(id);
}
static Collection<SoundEvent> values() {
return CONTAINER.values();
}
@Override
public String toString() {
return name();
}
}

View File

@ -4,6 +4,7 @@ import net.kyori.adventure.nbt.*;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.minestom.server.item.ItemStack;
import net.minestom.server.utils.UniqueIdUtils;
import org.jetbrains.annotations.NotNull;
import java.io.IOException;
@ -154,6 +155,21 @@ public interface BinaryTagSerializer<T> {
);
BinaryTagSerializer<ItemStack> ITEM = COMPOUND.map(ItemStack::fromItemNBT, ItemStack::toItemNBT);
BinaryTagSerializer<UUID> UUID = new BinaryTagSerializer<>() {
@Override
public @NotNull BinaryTag write(java.util.@NotNull UUID value) {
return UniqueIdUtils.toNbt(value);
}
@Override
public java.util.@NotNull UUID read(@NotNull BinaryTag tag) {
if (!(tag instanceof IntArrayBinaryTag intArrayTag)) {
throw new IllegalArgumentException("unexpected uuid type: " + tag.type());
}
return UniqueIdUtils.fromNbt(intArrayTag);
}
};
@NotNull BinaryTag write(@NotNull T value);
@NotNull T read(@NotNull BinaryTag tag);