diff --git a/fabric-data-generation-api-v1/src/client/java/net/fabricmc/fabric/api/client/datagen/v1/builder/SoundTypeBuilder.java b/fabric-data-generation-api-v1/src/client/java/net/fabricmc/fabric/api/client/datagen/v1/builder/SoundTypeBuilder.java
index 982e15ff5cf..87182f73b6e 100644
--- a/fabric-data-generation-api-v1/src/client/java/net/fabricmc/fabric/api/client/datagen/v1/builder/SoundTypeBuilder.java
+++ b/fabric-data-generation-api-v1/src/client/java/net/fabricmc/fabric/api/client/datagen/v1/builder/SoundTypeBuilder.java
@@ -37,8 +37,7 @@
*
*
Use in conjunction with {@link FabricSoundsProvider} to generate sound definitions.
*
- * @see net.minecraft.client.sounds.SoundManager
- * @see net.minecraft.client.sounds.WeighedSoundEvents
+ * @see net.minecraft.client.resources.sounds.SoundEventRegistration
*/
@ApiStatus.NonExtendable
public interface SoundTypeBuilder {
@@ -64,11 +63,20 @@ static SoundTypeBuilder of() {
}
/**
- * Sets the sound category the sound event must play on.
+ * @deprecated Category is not a field interpreted by vanilla in the sounds file,
+ * calling this method will have no effect.
+ */
+ @Deprecated(forRemoval = true)
+ default SoundTypeBuilder category(SoundSource category) {
+ return this;
+ }
+
+ /**
+ * Sets an optional replace boolean, which on true allows this sound type to override others.
*
- *
The default category is {@link SoundSource#NEUTRAL}. GUI elements should use {@link SoundSource#MASTER}.
+ *
The default is false.
*/
- SoundTypeBuilder category(SoundSource category);
+ SoundTypeBuilder replace(boolean replace);
/**
* Sets an optional translation key string to use for the sound's subtitle.
@@ -134,6 +142,8 @@ public String getSerializedName() {
/**
* Builder for creating a weighted sound entry that can be played for a particular sound type.
+ *
+ * @see net.minecraft.client.resources.sounds.Sound
*/
@ApiStatus.NonExtendable
interface EntryBuilder {
diff --git a/fabric-data-generation-api-v1/src/client/java/net/fabricmc/fabric/impl/datagen/client/SoundTypeBuilderImpl.java b/fabric-data-generation-api-v1/src/client/java/net/fabricmc/fabric/impl/datagen/client/SoundTypeBuilderImpl.java
index 65fc3e33643..f90305bacb5 100644
--- a/fabric-data-generation-api-v1/src/client/java/net/fabricmc/fabric/impl/datagen/client/SoundTypeBuilderImpl.java
+++ b/fabric-data-generation-api-v1/src/client/java/net/fabricmc/fabric/impl/datagen/client/SoundTypeBuilderImpl.java
@@ -17,14 +17,9 @@
package net.fabricmc.fabric.impl.datagen.client;
import java.util.ArrayList;
-import java.util.Arrays;
import java.util.List;
-import java.util.Locale;
-import java.util.Map;
import java.util.Objects;
import java.util.Optional;
-import java.util.function.Function;
-import java.util.stream.Collectors;
import com.mojang.datafixers.util.Either;
import com.mojang.serialization.Codec;
@@ -38,14 +33,13 @@
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.resources.Identifier;
import net.minecraft.sounds.SoundEvent;
-import net.minecraft.sounds.SoundSource;
import net.fabricmc.fabric.api.client.datagen.v1.builder.SoundTypeBuilder;
public final class SoundTypeBuilderImpl implements SoundTypeBuilder {
private static final Logger LOGGER = LoggerFactory.getLogger(SoundTypeBuilderImpl.class);
- private SoundSource category = SoundSource.NEUTRAL;
+ private boolean replace = false;
@Nullable
private String subtitle;
private final List sounds = new ArrayList<>();
@@ -53,9 +47,8 @@ public final class SoundTypeBuilderImpl implements SoundTypeBuilder {
public SoundTypeBuilderImpl() { }
@Override
- public SoundTypeBuilder category(SoundSource category) {
- Objects.requireNonNull(category, "Sound event category must not be null.");
- this.category = category;
+ public SoundTypeBuilder replace(boolean replace) {
+ this.replace = replace;
return this;
}
@@ -93,26 +86,38 @@ public SoundType build() {
}
}
- return new SoundType(sounds, category, Optional.ofNullable(subtitle));
+ return new SoundType(sounds, replace, Optional.ofNullable(subtitle));
}
- public record SoundType(List sounds, SoundSource category, Optional subtitle) {
- private static final Map CATEGORIES = Arrays.stream(SoundSource.values()).collect(Collectors.toMap(SoundSource::getName, Function.identity()));
- private static final Codec SOUND_CATEGORY_CODEC = Codec.stringResolver(SoundSource::getName, name -> CATEGORIES.getOrDefault(name.toLowerCase(Locale.ROOT), SoundSource.NEUTRAL));
+ /**
+ * Record of the sound event registration class for data generation.
+ *
+ * @see net.minecraft.client.resources.sounds.SoundEventRegistration
+ */
+ public record SoundType(List sounds, boolean replace, Optional subtitle) {
+ /**
+ * @see net.minecraft.client.resources.sounds.SoundEventRegistrationSerializer
+ */
public static final Codec CODEC = RecordCodecBuilder.create(instance -> instance.group(
Entry.CODEC.listOf().fieldOf("sounds").forGetter(SoundType::sounds),
- SOUND_CATEGORY_CODEC.fieldOf("category").forGetter(SoundType::category),
+ Codec.BOOL.optionalFieldOf("replace", false).forGetter(SoundType::replace),
Codec.STRING.optionalFieldOf("subtitle").forGetter(SoundType::subtitle)
).apply(instance, SoundType::new));
}
- private record Entry(Identifier name, RegistrationType type, float volume, float pitch, int weight, int attenuationDistance, boolean stream, boolean preload) {
+ /**
+ * Record of the sound class to use for data generation.
+ *
+ * @see net.minecraft.client.resources.sounds.Sound
+ */
+ public record Entry(Identifier name, RegistrationType type, float volume, float pitch, int weight,
+ int attenuationDistance, boolean stream, boolean preload) {
private static final Codec MAP_CODEC = RecordCodecBuilder.create(instance -> instance.group(
Identifier.CODEC.fieldOf("name").forGetter(Entry::name),
RegistrationType.CODEC.optionalFieldOf("type", RegistrationType.FILE).forGetter(Entry::type),
- Codec.FLOAT.optionalFieldOf("volume", EntryBuilder.DEFAULT_VOLUME).forGetter(Entry::volume),
- Codec.FLOAT.optionalFieldOf("pitch", EntryBuilder.DEFAULT_PITCH).forGetter(Entry::pitch),
- Codec.INT.optionalFieldOf("weight", EntryBuilder.DEFAULT_WEIGHT).forGetter(Entry::weight),
+ Codec.floatRange(Float.MIN_VALUE, 1.0F).optionalFieldOf("volume", EntryBuilder.DEFAULT_VOLUME).forGetter(Entry::volume),
+ Codec.floatRange(0.5F, 2.0F).optionalFieldOf("pitch", EntryBuilder.DEFAULT_PITCH).forGetter(Entry::pitch),
+ Codec.intRange(1, Integer.MAX_VALUE).optionalFieldOf("weight", EntryBuilder.DEFAULT_WEIGHT).forGetter(Entry::weight),
Codec.INT.optionalFieldOf("attenuation_distance", EntryBuilder.DEFAULT_ATTENUATION_DISTANCE).forGetter(Entry::attenuationDistance),
Codec.BOOL.optionalFieldOf("stream", false).forGetter(Entry::stream),
Codec.BOOL.optionalFieldOf("preload", false).forGetter(Entry::preload)
@@ -124,10 +129,10 @@ private record Entry(Identifier name, RegistrationType type, float volume, float
);
private static final Codec CODEC = Codec.xor(STRING_CODEC, MAP_CODEC).xmap(Either::unwrap, sound -> {
if (sound.type() != RegistrationType.FILE
- || sound.volume() != 1F
- || sound.pitch() != 1F
- || sound.weight() != 1
- || sound.attenuationDistance() != 16
+ || sound.volume() != EntryBuilder.DEFAULT_VOLUME
+ || sound.pitch() != EntryBuilder.DEFAULT_PITCH
+ || sound.weight() != EntryBuilder.DEFAULT_WEIGHT
+ || sound.attenuationDistance() != EntryBuilder.DEFAULT_ATTENUATION_DISTANCE
|| sound.stream()
|| sound.preload()) {
return Either.right(sound);
diff --git a/fabric-data-generation-api-v1/src/test/java/net/fabricmc/fabric/test/datagen/client/SoundsTypeBuilderTest.java b/fabric-data-generation-api-v1/src/test/java/net/fabricmc/fabric/test/datagen/client/SoundsTypeBuilderTest.java
new file mode 100644
index 00000000000..2b8ad737743
--- /dev/null
+++ b/fabric-data-generation-api-v1/src/test/java/net/fabricmc/fabric/test/datagen/client/SoundsTypeBuilderTest.java
@@ -0,0 +1,162 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.test.datagen.client;
+
+import java.util.List;
+import java.util.Optional;
+
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import net.minecraft.SharedConstants;
+import net.minecraft.resources.Identifier;
+import net.minecraft.server.Bootstrap;
+import net.minecraft.sounds.SoundEvents;
+
+import net.fabricmc.fabric.api.client.datagen.v1.builder.SoundTypeBuilder;
+import net.fabricmc.fabric.impl.datagen.client.SoundTypeBuilderImpl;
+
+public class SoundsTypeBuilderTest {
+ @BeforeAll
+ static void beforeAll() {
+ SharedConstants.tryDetectVersion();
+ Bootstrap.bootStrap();
+ }
+
+ @Test
+ public void buildSoundType1() {
+ SoundTypeBuilderImpl.SoundType expected = new SoundTypeBuilderImpl.SoundType(
+ List.of(
+ new SoundTypeBuilderImpl.Entry(Identifier.withDefaultNamespace("mob/parrot/idle1"),
+ SoundTypeBuilder.RegistrationType.FILE, 0.7F, 1.0F, 1,
+ 16, false, false),
+ new SoundTypeBuilderImpl.Entry(Identifier.withDefaultNamespace("mob/parrot/idle2"),
+ SoundTypeBuilder.RegistrationType.FILE, 1.0F, 1.0F, 1,
+ 16, false, false),
+ new SoundTypeBuilderImpl.Entry(SoundEvents.ANVIL_HIT.location(),
+ SoundTypeBuilder.RegistrationType.SOUND_EVENT, 1.0F, 1.0F, 100,
+ 16, false, false),
+ new SoundTypeBuilderImpl.Entry(SoundEvents.ARMOR_EQUIP_GENERIC.value().location(),
+ SoundTypeBuilder.RegistrationType.SOUND_EVENT, 1.0F, 1.0F, 1,
+ 16, false, false),
+ new SoundTypeBuilderImpl.Entry(Identifier.withDefaultNamespace("mob/parrot/idle"),
+ SoundTypeBuilder.RegistrationType.FILE, 0.3F, 0.5F, 1,
+ 8, true, true)
+ ),
+ true,
+ Optional.of("subtitles.minecraft.block.anvil.use")
+ );
+
+ SoundTypeBuilderImpl.SoundType soundType = ((SoundTypeBuilderImpl) SoundTypeBuilder.of(SoundEvents.ANVIL_USE)
+ .sound(SoundTypeBuilder.EntryBuilder.ofFile(Identifier.withDefaultNamespace("mob/parrot/idle"))
+ .volume(0.7F), 1)
+ .sound(SoundTypeBuilder.EntryBuilder.ofFile(Identifier.withDefaultNamespace("mob/parrot/idle2")))
+ .sound(SoundTypeBuilder.EntryBuilder.ofEvent(SoundEvents.ANVIL_HIT)
+ .weight(100))
+ .sound(SoundTypeBuilder.EntryBuilder.ofEvent(SoundEvents.ARMOR_EQUIP_GENERIC))
+ .sound(SoundTypeBuilder.EntryBuilder.ofFile(Identifier.withDefaultNamespace("mob/parrot/idle"))
+ .volume(0.3F).pitch(0.5F).stream(true).preload(true).attenuationDistance(8)
+ ).replace(true)).build();
+
+ soundTypeEquals(expected, soundType);
+ }
+
+ @Test
+ public void buildSoundType2() {
+ SoundTypeBuilderImpl.SoundType expected = new SoundTypeBuilderImpl.SoundType(
+ List.of(
+ new SoundTypeBuilderImpl.Entry(Identifier.withDefaultNamespace("mob/creeper/hurt"),
+ SoundTypeBuilder.RegistrationType.FILE, 1.0F, 2.0F, 1,
+ 16, false, false),
+ new SoundTypeBuilderImpl.Entry(SoundEvents.STONE_BREAK.location(),
+ SoundTypeBuilder.RegistrationType.SOUND_EVENT, 1.0F, 1.0F, 1,
+ 16, false, false),
+ new SoundTypeBuilderImpl.Entry(Identifier.withDefaultNamespace("block/beacon/power"),
+ SoundTypeBuilder.RegistrationType.FILE, Float.MIN_VALUE, 0.5F, 1,
+ 0, false, false)
+ ),
+ false,
+ Optional.empty()
+ );
+
+ SoundTypeBuilderImpl.SoundType soundType = ((SoundTypeBuilderImpl) SoundTypeBuilder.of()
+ .sound(SoundTypeBuilder.EntryBuilder.ofFile(Identifier.withDefaultNamespace("mob/creeper/hurt"))
+ .volume(1.0F).pitch(2.0F))
+ .sound(SoundTypeBuilder.EntryBuilder.ofEvent(SoundEvents.STONE_BREAK)
+ .weight(1))
+ .sound(SoundTypeBuilder.EntryBuilder.ofFile(Identifier.withDefaultNamespace("block/beacon/power"))
+ .volume(Float.MIN_VALUE).pitch(0.5F).stream(false).preload(false).attenuationDistance(0)
+ )).build();
+
+ soundTypeEquals(expected, soundType);
+ }
+
+ @Test
+ public void buildSoundType3() {
+ SoundTypeBuilderImpl.SoundType expected = new SoundTypeBuilderImpl.SoundType(
+ List.of(
+ new SoundTypeBuilderImpl.Entry(Identifier.withDefaultNamespace("sound"),
+ SoundTypeBuilder.RegistrationType.FILE, 1.0F, 1.0F, 1,
+ 16, false, false)
+ ),
+ false,
+ Optional.of("super_subtitle")
+ );
+
+ SoundTypeBuilderImpl.SoundType soundType = ((SoundTypeBuilderImpl) SoundTypeBuilder.of()
+ .subtitle("super_subtitle")
+ .sound(SoundTypeBuilder.EntryBuilder.ofFile(Identifier.withDefaultNamespace("sound")))).build();
+
+ soundTypeEquals(expected, soundType);
+ }
+
+ /**
+ * Assert that the expected and specified sound type equal.
+ *
+ * @param expected sound type to be expected
+ * @param soundType sound type to assert against
+ */
+ public void soundTypeEquals(SoundTypeBuilderImpl.SoundType expected, SoundTypeBuilderImpl.SoundType soundType) {
+ Assertions.assertEquals(expected.subtitle(), soundType.subtitle());
+ Assertions.assertEquals(expected.replace(), soundType.replace());
+ Assertions.assertEquals(expected.sounds().size(), soundType.sounds().size());
+
+ for (int i = 0; i < expected.sounds().size(); i++) {
+ SoundTypeBuilderImpl.Entry expectedEntry = expected.sounds().get(i);
+ SoundTypeBuilderImpl.Entry entry = soundType.sounds().get(i);
+ entryEquals(expectedEntry, entry);
+ }
+ }
+
+ /**
+ * Assert that all fields of the expected and specified entry equal each other.
+ *
+ * @param expected entry with fields to be expected
+ * @param entry entry to assert against
+ */
+ public void entryEquals(SoundTypeBuilderImpl.Entry expected, SoundTypeBuilderImpl.Entry entry) {
+ Assertions.assertEquals(expected.name().getNamespace(), entry.name().getNamespace());
+ Assertions.assertEquals(expected.type().name(), entry.type().name());
+ Assertions.assertEquals(expected.stream(), entry.stream());
+ Assertions.assertEquals(expected.preload(), entry.preload());
+ Assertions.assertEquals(expected.attenuationDistance(), entry.attenuationDistance());
+ Assertions.assertEquals(expected.weight(), entry.weight());
+ Assertions.assertEquals(expected.volume(), entry.volume());
+ Assertions.assertEquals(expected.pitch(), entry.pitch());
+ }
+}
diff --git a/fabric-data-generation-api-v1/src/test/java/net/fabricmc/fabric/test/datagen/client/SoundsTypeCodecTest.java b/fabric-data-generation-api-v1/src/test/java/net/fabricmc/fabric/test/datagen/client/SoundsTypeCodecTest.java
new file mode 100644
index 00000000000..75892db65e6
--- /dev/null
+++ b/fabric-data-generation-api-v1/src/test/java/net/fabricmc/fabric/test/datagen/client/SoundsTypeCodecTest.java
@@ -0,0 +1,177 @@
+/*
+ * Copyright (c) 2016, 2017, 2018, 2019 FabricMC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package net.fabricmc.fabric.test.datagen.client;
+
+import java.util.List;
+import java.util.Map;
+
+import com.google.gson.Gson;
+import com.google.gson.GsonBuilder;
+import com.google.gson.JsonElement;
+import com.google.gson.reflect.TypeToken;
+import com.mojang.serialization.Codec;
+import com.mojang.serialization.DataResult;
+import com.mojang.serialization.JsonOps;
+import org.junit.jupiter.api.Assertions;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.Test;
+
+import net.minecraft.SharedConstants;
+import net.minecraft.client.resources.sounds.Sound;
+import net.minecraft.client.resources.sounds.SoundEventRegistration;
+import net.minecraft.client.resources.sounds.SoundEventRegistrationSerializer;
+import net.minecraft.resources.Identifier;
+import net.minecraft.server.Bootstrap;
+import net.minecraft.sounds.SoundEvents;
+import net.minecraft.util.RandomSource;
+
+import net.fabricmc.fabric.api.client.datagen.v1.builder.SoundTypeBuilder;
+import net.fabricmc.fabric.impl.datagen.client.SoundTypeBuilderImpl;
+
+public class SoundsTypeCodecTest {
+ /**
+ * Codec copied from {@link net.fabricmc.fabric.api.client.datagen.v1.provider.FabricSoundsProvider} to use in testing, as it is not accessible.
+ */
+ private static final Codec