diff --git a/src/main/java/ch/njol/skript/expressions/ExprBlockSound.java b/src/main/java/ch/njol/skript/expressions/ExprBlockSound.java new file mode 100644 index 00000000000..ef207692089 --- /dev/null +++ b/src/main/java/ch/njol/skript/expressions/ExprBlockSound.java @@ -0,0 +1,137 @@ +package ch.njol.skript.expressions; + +import ch.njol.skript.aliases.ItemType; +import ch.njol.skript.doc.Description; +import ch.njol.skript.doc.Examples; +import ch.njol.skript.doc.Name; +import ch.njol.skript.doc.Since; +import ch.njol.skript.expressions.base.SimplePropertyExpression; +import ch.njol.skript.lang.Expression; +import ch.njol.skript.lang.SkriptParser.ParseResult; +import ch.njol.skript.lang.util.SimpleExpression; +import ch.njol.util.Kleenean; +import org.bukkit.Sound; +import org.bukkit.SoundGroup; +import org.bukkit.block.Block; +import org.bukkit.block.data.BlockData; +import org.bukkit.event.Event; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Objects; + +@Name("Block Sound") +@Description({ + "Gets the sound that a given block, blockdata, or itemtype will use in a specific scenario.", + "This will return a string in the form of \"SOUND_EXAMPLE\", which can be used in the play sound syntax.", + "", + "Check out this website for a list of sounds in Minecraft, " + + "or this one to go to the Sounds wiki page." +}) +@Examples({ + "play sound (break sound of dirt) at all players", + "set {_sounds::*} to place sounds of dirt, grass block, blue wool and stone" +}) +@Since("INSERT VERSION") +public class ExprBlockSound extends SimpleExpression { + + public enum SoundType { + BREAK { + @Override + public Sound getSound(SoundGroup group) { + return group.getBreakSound(); + } + }, + + FALL { + @Override + public Sound getSound(SoundGroup group) { + return group.getFallSound(); + } + }, + + HIT { + @Override + public Sound getSound(SoundGroup group) { + return group.getHitSound(); + } + }, + + PLACE { + @Override + public Sound getSound(SoundGroup group) { + return group.getPlaceSound(); + } + }, + + STEP { + @Override + public Sound getSound(SoundGroup group) { + return group.getStepSound(); + } + }; + + public abstract @Nullable Sound getSound(SoundGroup group); + } + + static { + SimplePropertyExpression.register(ExprBlockSound.class, String.class, "(1:break|2:fall|3:hit|4:place|5:step) sound[s]", "blocks/blockdatas/itemtypes"); + } + + @SuppressWarnings("NotNullFieldNotInitialized") + private SoundType soundType; + @SuppressWarnings("NotNullFieldNotInitialized") + private Expression objects; + + @Override + public boolean init(Expression[] exprs, int matchedPattern, Kleenean isDelayed, ParseResult parseResult) { + soundType = SoundType.values()[parseResult.mark - 1]; + objects = exprs[0]; + return true; + } + + @Override + protected String @Nullable [] get(Event event) { + return objects.stream(event) + .map(this::convertAndGetSound) + .filter(Objects::nonNull) + .distinct() + .map(Sound::name) + .toArray(String[]::new); + } + + private @Nullable SoundGroup getSoundGroup(Object object) { + if (object instanceof Block block) { + return block.getBlockData().getSoundGroup(); + } else if (object instanceof BlockData data) { + return data.getSoundGroup(); + } else if (object instanceof ItemType item) { + if (item.hasBlock()) + return item.getMaterial().createBlockData().getSoundGroup(); + } + return null; + } + + private @Nullable Sound convertAndGetSound(Object object) { + SoundGroup group = getSoundGroup(object); + if (group == null) + return null; + return this.soundType.getSound(group); + } + + @Override + public boolean isSingle() { + return objects.isSingle(); + } + + @Override + public @NotNull Class getReturnType() { + return String.class; + } + + @Override + public @NotNull String toString(@Nullable Event event, boolean debug) { + return this.soundType.name().toLowerCase() + " sound of " + objects.toString(event, debug); + } + +} diff --git a/src/test/skript/tests/syntaxes/expressions/ExprBlockSound.sk b/src/test/skript/tests/syntaxes/expressions/ExprBlockSound.sk new file mode 100644 index 00000000000..133ea5a7e7c --- /dev/null +++ b/src/test/skript/tests/syntaxes/expressions/ExprBlockSound.sk @@ -0,0 +1,46 @@ +test "block sounds (1.20+)": + + # === SETUP === + + set {_before} to blockdata of (block at (spawn of world "world")) + set {_20} to whether running minecraft "1.20" + set {_empty} to "INTENTIONALLY_EMPTY" if running minecraft "1.20" else "BLOCK_STONE_PLACE" + + # === TESTS === + + set {_stone::*} to getObjects(stone) + assert break sound of {_stone::*} is "BLOCK_STONE_BREAK" with "break sound of stone wasn't BLOCK_STONE_BREAK" + assert fall sound of {_stone::*} is "BLOCK_STONE_FALL" with "fall sound of stone wasn't BLOCK_STONE_FALL" + assert hit sound of {_stone::*} is "BLOCK_STONE_HIT" with "hit sound of stone wasn't BLOCK_STONE_HIT" + assert place sound of {_stone::*} is "BLOCK_STONE_PLACE" with "place sound of stone wasn't BLOCK_STONE_PLACE" + assert step sound of {_stone::*} is "BLOCK_STONE_STEP" with "step sound of stone wasn't BLOCK_STONE_STEP" + + set {_wool::*} to getObjects(wool) + assert break sound of {_wool::*} is "BLOCK_WOOL_BREAK" with "break sound of stone wasn't BLOCK_WOOL_BREAK" + assert fall sound of {_wool::*} is "BLOCK_WOOL_FALL" with "fall sound of stone wasn't BLOCK_WOOL_FALL" + assert hit sound of {_wool::*} is "BLOCK_WOOL_HIT" with "hit sound of stone wasn't BLOCK_WOOL_HIT" + assert place sound of {_wool::*} is "BLOCK_WOOL_PLACE" with "place sound of stone wasn't BLOCK_WOOL_PLACE" + assert step sound of {_wool::*} is "BLOCK_WOOL_STEP" with "step sound of stone wasn't BLOCK_WOOL_STEP" + + assert break sound of (water, lava and bubble column) is ("INTENTIONALLY_EMPTY" if {_20} is true else "BLOCK_STONE_BREAK") with "break sound of water, lava, or bubble column wasn't INTENTIONALLY_EMPTY" + assert fall sound of (water, lava and bubble column) is ("INTENTIONALLY_EMPTY" if {_20} is true else "BLOCK_STONE_FALL") with "fall sound of water, lava, or bubble column wasn't INTENTIONALLY_EMPTY" + assert hit sound of (water, lava and bubble column) is ("INTENTIONALLY_EMPTY" if {_20} is true else "BLOCK_STONE_HIT") with "hit sound of water, lava, or bubble column wasn't INTENTIONALLY_EMPTY" + assert place sound of (water, lava and bubble column) is ("INTENTIONALLY_EMPTY" if {_20} is true else "BLOCK_STONE_PLACE") with "place sound of water, lava, or bubble column wasn't INTENTIONALLY_EMPTY" + assert step sound of (water, lava and bubble column) is ("INTENTIONALLY_EMPTY" if {_20} is true else "BLOCK_STONE_STEP") with "step sound of water, lava, or bubble column wasn't INTENTIONALLY_EMPTY" + + assert break sound of (diamond, diamond sword and {_none}) is not set with "break sound of non-block item shouldn't be set" + assert fall sound of (diamond, diamond sword and {_none}) is not set with "fall sound of non-block item shouldn't be set" + assert hit sound of (diamond, diamond sword and {_none}) is not set with "hit sound of non-block item shouldn't be set" + assert place sound of (diamond, diamond sword and {_none}) is not set with "place sound of non-block item shouldn't be set" + assert step sound of (diamond, diamond sword and {_none}) is not set with "step sound of non-block item shouldn't be set" + + # === CLEANUP === + + set blockdata of {_block} to {_before} + +local function getObjects(i: itemtype) :: objects: + if (block at (spawn of world "world")) is not {_i}: + set block at (spawn of world "world") to {_i} + set {_block} to block at (spawn of world "world") + set {_data} to blockdata of {_block} + return {_block}, {_data} and {_i}