Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,7 @@
*
* <p>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 {
Expand All @@ -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
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe use @Deprecated(forRemoval = true) if we know its tottaly broken?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, seems sensible to eventually want to get rid of it. I will add a commit for it.

default SoundTypeBuilder category(SoundSource category) {
return this;
}

/**
* Sets an optional replace boolean, which on true allows this sound type to override others.
*
* <p>The default category is {@link SoundSource#NEUTRAL}. GUI elements should use {@link SoundSource#MASTER}.
* <p>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.
Expand Down Expand Up @@ -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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -38,24 +33,22 @@
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<Entry> sounds = new ArrayList<>();

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;
}

Expand Down Expand Up @@ -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<Entry> sounds, SoundSource category, Optional<String> subtitle) {
private static final Map<String, SoundSource> CATEGORIES = Arrays.stream(SoundSource.values()).collect(Collectors.toMap(SoundSource::getName, Function.identity()));
private static final Codec<SoundSource> 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<Entry> sounds, boolean replace, Optional<String> subtitle) {
/**
* @see net.minecraft.client.resources.sounds.SoundEventRegistrationSerializer
*/
public static final Codec<SoundType> 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<Entry> 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)
Expand All @@ -124,10 +129,10 @@ private record Entry(Identifier name, RegistrationType type, float volume, float
);
private static final Codec<Entry> 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);
Expand Down
Original file line number Diff line number Diff line change
@@ -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());
}
}
Loading
Loading