diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java index b7a639590..03aac1937 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/plugin/command/feature/TotemAnimationCommand.java @@ -30,10 +30,12 @@ import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser; import org.incendo.cloud.context.CommandContext; import org.incendo.cloud.context.CommandInput; import org.incendo.cloud.parser.flag.CommandFlag; +import org.incendo.cloud.parser.standard.FloatParser; import org.incendo.cloud.suggestion.Suggestion; import org.incendo.cloud.suggestion.SuggestionProvider; import java.util.Map; +import java.util.Optional; import java.util.concurrent.CompletableFuture; public class TotemAnimationCommand extends BukkitCommandFeature { @@ -46,6 +48,7 @@ public class TotemAnimationCommand extends BukkitCommandFeature { public Command.Builder assembleCommand(CommandManager manager, Command.Builder builder) { return builder .flag(FlagKeys.SILENT_FLAG) + .flag(CommandFlag.builder("no-sound")) .required("players", MultiplePlayerSelectorParser.multiplePlayerSelectorParser()) .required("id", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() { @Override @@ -53,7 +56,16 @@ public class TotemAnimationCommand extends BukkitCommandFeature { return CompletableFuture.completedFuture(plugin().itemManager().cachedTotemSuggestions()); } })) - .flag(CommandFlag.builder("sound").withComponent(NamespacedKeyParser.namespacedKeyParser()).build()) + .optional("sound", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() { + @Override + public @NonNull CompletableFuture> suggestionsFuture(@NonNull CommandContext context, @NonNull CommandInput input) { + return CompletableFuture.completedFuture(plugin().soundManager().cachedSoundSuggestions()); + } + })) + .optional("volume", FloatParser.floatParser(0f)) + .optional("pitch", FloatParser.floatParser(0f, 2f)) + .optional("min-volume", FloatParser.floatParser(0f)) + .optional("min-pitch", FloatParser.floatParser(0f, 2f)) .handler(context -> { NamespacedKey namespacedKey = context.get("id"); Key key = Key.of(namespacedKey.namespace(), namespacedKey.value()); @@ -62,6 +74,16 @@ public class TotemAnimationCommand extends BukkitCommandFeature { handleFeedback(context, MessageConstants.COMMAND_TOTEM_NOT_TOTEM, Component.text(key.toString())); return; } + Optional soundKey = context.optional("sound"); + SoundData soundData = null; + if (soundKey.isPresent()) { + float volume = context.getOrDefault("volume", 1.0f); + float pitch = context.getOrDefault("pitch", 1.0f); + float minVolume = context.getOrDefault("min-volume", 1.0f); + float minPitch = context.getOrDefault("min-pitch", 1.0f); + soundData = SoundData.of(KeyUtils.namespacedKey2Key(soundKey.get()), SoundData.SoundValue.ranged(minVolume, volume), SoundData.SoundValue.ranged(minPitch, pitch)); + } + boolean removeSound = context.flags().hasFlag("no-sound"); MultiplePlayerSelector selector = context.get("players"); for (Player player : selector.values()) { BukkitServerPlayer serverPlayer = BukkitAdaptors.adapt(player); @@ -69,13 +91,11 @@ public class TotemAnimationCommand extends BukkitCommandFeature { if (VersionHelper.isOrAbove1_21_2()) { item.setJavaComponent(ComponentTypes.DEATH_PROTECTION, Map.of()); } - NamespacedKey soundKey = context.flags().get("sound"); - SoundData soundData = null; - if (soundKey != null) { - soundData = SoundData.of(KeyUtils.namespacedKey2Key(soundKey), SoundData.SoundValue.FIXED_1, SoundData.SoundValue.FIXED_1); - } - PlayerUtils.sendTotemAnimation(serverPlayer, item, soundData); + // TODO 存在第一次进服 未正确移除图腾声音的问题 + PlayerUtils.sendTotemAnimation(serverPlayer, item, soundData, removeSound); } + + // TODO 消息提示 }); } diff --git a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/PlayerUtils.java b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/PlayerUtils.java index 37ddf5429..c0b242549 100644 --- a/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/PlayerUtils.java +++ b/bukkit/src/main/java/net/momirealms/craftengine/bukkit/util/PlayerUtils.java @@ -2,6 +2,7 @@ package net.momirealms.craftengine.bukkit.util; import com.mojang.datafixers.util.Pair; import net.momirealms.craftengine.bukkit.item.BukkitItemManager; +import net.momirealms.craftengine.bukkit.item.ComponentTypes; import net.momirealms.craftengine.bukkit.nms.FastNMS; import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine; import net.momirealms.craftengine.bukkit.plugin.reflection.minecraft.CoreReflections; @@ -56,15 +57,19 @@ public final class PlayerUtils { } } - public static void sendTotemAnimation(Player player, Item totem, @Nullable SoundData sound) { + public static void sendTotemAnimation(Player player, Item totem, @Nullable SoundData sound, boolean removeSound) { List packets = new ArrayList<>(); try { Object totemItem = totem.getLiteralObject(); Item previousMainHandItem = player.getItemInHand(InteractionHand.MAIN_HAND); - boolean flag = previousMainHandItem.id().equals(ItemKeys.TOTEM_OF_UNDYING); + boolean isMainHandTotem; + if (VersionHelper.isOrAbove1_21_2()) { + isMainHandTotem = previousMainHandItem.hasComponent(ComponentTypes.DEATH_PROTECTION); + } else { + isMainHandTotem = previousMainHandItem.id().equals(ItemKeys.TOTEM_OF_UNDYING); + } Object previousOffHandItem = player.getItemInHand(InteractionHand.OFF_HAND).getLiteralObject(); - - if (flag) { + if (isMainHandTotem) { packets.add(NetworkReflections.constructor$ClientboundSetEquipmentPacket.newInstance( player.entityID(), List.of(Pair.of(CoreReflections.instance$EquipmentSlot$MAINHAND, BukkitItemManager.instance().uniqueEmptyItem().item().getLiteralObject())) )); @@ -73,7 +78,7 @@ public final class PlayerUtils { player.entityID(), List.of(Pair.of(CoreReflections.instance$EquipmentSlot$OFFHAND, totemItem)) )); packets.add(NetworkReflections.constructor$ClientboundEntityEventPacket.newInstance(player.serverPlayer(), (byte) 35)); - if (flag) { + if (isMainHandTotem) { packets.add(NetworkReflections.constructor$ClientboundSetEquipmentPacket.newInstance( player.entityID(), List.of(Pair.of(CoreReflections.instance$EquipmentSlot$MAINHAND, previousMainHandItem.getLiteralObject())) )); @@ -81,12 +86,13 @@ public final class PlayerUtils { packets.add(NetworkReflections.constructor$ClientboundSetEquipmentPacket.newInstance( player.entityID(), List.of(Pair.of(CoreReflections.instance$EquipmentSlot$OFFHAND, previousOffHandItem)) )); - - if (sound != null) { + if (sound != null || removeSound) { packets.add(NetworkReflections.constructor$ClientboundStopSoundPacket.newInstance( FastNMS.INSTANCE.method$ResourceLocation$fromNamespaceAndPath("minecraft", "item.totem.use"), CoreReflections.instance$SoundSource$PLAYERS )); + } + if (sound != null) { packets.add(FastNMS.INSTANCE.constructor$ClientboundSoundPacket( FastNMS.INSTANCE.method$Holder$direct(FastNMS.INSTANCE.constructor$SoundEvent(KeyUtils.toResourceLocation(sound.id()), Optional.empty())), CoreReflections.instance$SoundSource$PLAYERS, @@ -94,7 +100,6 @@ public final class PlayerUtils { RandomUtils.generateRandomLong() )); } - player.sendPackets(packets, false); } catch (ReflectiveOperationException e) { BukkitCraftEngine.instance().logger().warn("Failed to send totem animation"); diff --git a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java index 31327c6c2..3b83cedc0 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java +++ b/core/src/main/java/net/momirealms/craftengine/core/plugin/CraftEngine.java @@ -172,6 +172,7 @@ public abstract class CraftEngine implements Plugin { // collect illegal characters from minecraft:default font this.fontManager.delayedLoad(); this.advancementManager.delayedLoad(); + this.soundManager.delayedLoad(); if (reloadRecipe) { // convert data pack recipes this.recipeManager.delayedLoad(); diff --git a/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java b/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java index 3c5886417..0972a6eed 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/sound/AbstractSoundManager.java @@ -8,6 +8,7 @@ import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.plugin.config.IdSectionConfigParser; import net.momirealms.craftengine.core.plugin.locale.LocalizedResourceConfigException; import net.momirealms.craftengine.core.util.*; +import org.incendo.cloud.suggestion.Suggestion; import java.nio.file.Path; import java.util.*; @@ -21,6 +22,7 @@ public abstract class AbstractSoundManager implements SoundManager { protected final SoundParser soundParser; protected final SongParser songParser; protected final Map customSoundsInRegistry = new HashMap<>(); + protected final List soundSuggestions = new ArrayList<>(); public AbstractSoundManager(CraftEngine plugin) { this.plugin = plugin; @@ -43,6 +45,22 @@ public abstract class AbstractSoundManager implements SoundManager { this.byId.clear(); this.byNamespace.clear(); this.songs.clear(); + this.soundSuggestions.clear(); + } + + @Override + public void delayedLoad() { + for (Key key : VANILLA_SOUND_EVENTS) { + this.soundSuggestions.add(Suggestion.suggestion(key.asString())); + } + for (Key key : this.byId.keySet()) { + this.soundSuggestions.add(Suggestion.suggestion(key.asString())); + } + } + + @Override + public List cachedSoundSuggestions() { + return this.soundSuggestions; } @Override diff --git a/core/src/main/java/net/momirealms/craftengine/core/sound/SoundData.java b/core/src/main/java/net/momirealms/craftengine/core/sound/SoundData.java index 339c860ae..af0356c6e 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/sound/SoundData.java +++ b/core/src/main/java/net/momirealms/craftengine/core/sound/SoundData.java @@ -57,6 +57,11 @@ public record SoundData(Key id, SoundValue volume, SoundValue pitch) { } static SoundValue ranged(float min, float max) { + if (min > max) { + return new Ranged(max, min); + } else if (min == max) { + return SoundValue.fixed(max); + } return new Ranged(min, max); } diff --git a/core/src/main/java/net/momirealms/craftengine/core/sound/SoundManager.java b/core/src/main/java/net/momirealms/craftengine/core/sound/SoundManager.java index 55eeb6e4b..10ca914c1 100644 --- a/core/src/main/java/net/momirealms/craftengine/core/sound/SoundManager.java +++ b/core/src/main/java/net/momirealms/craftengine/core/sound/SoundManager.java @@ -3,7 +3,9 @@ package net.momirealms.craftengine.core.sound; import net.momirealms.craftengine.core.plugin.Manageable; import net.momirealms.craftengine.core.plugin.config.ConfigParser; import net.momirealms.craftengine.core.util.Key; +import org.incendo.cloud.suggestion.Suggestion; +import java.util.List; import java.util.Map; public interface SoundManager extends Manageable { @@ -12,5 +14,7 @@ public interface SoundManager extends Manageable { ConfigParser[] parsers(); + List cachedSoundSuggestions(); + Map sounds(); }