9
0
mirror of https://github.com/Xiao-MoMi/craft-engine.git synced 2025-12-28 03:19:14 +00:00

Implement totem animation feature

This commit is contained in:
iqtester
2025-04-03 22:06:56 +08:00
parent 0e45cad3f0
commit a842a4abec
13 changed files with 159 additions and 5 deletions

View File

@@ -256,6 +256,8 @@ public class BukkitItemManager extends AbstractItemManager<ItemStack> {
CustomItem<ItemStack> customItem = itemBuilder.build();
this.customItems.put(id, customItem);
this.cachedSuggestions.add(Suggestion.suggestion(id.toString()));
if (material == Material.TOTEM_OF_UNDYING)
this.cachedTotemSuggestions.add(Suggestion.suggestion(id.toString()));
// post process
// register tags

View File

@@ -45,7 +45,8 @@ public class BukkitCommandManager extends AbstractCommandManager<CommandSender>
new DebugItemDataCommand(this, plugin),
new DebugSetBlockCommand(this, plugin),
new DebugSpawnFurnitureCommand(this, plugin),
new DebugTargetBlockCommand(this, plugin)
new DebugTargetBlockCommand(this, plugin),
new TotemCommand(this, plugin)
));
final LegacyPaperCommandManager<CommandSender> manager = (LegacyPaperCommandManager<CommandSender>) getCommandManager();
manager.settings().set(ManagerSetting.ALLOW_UNSAFE_REGISTRATION, true);

View File

@@ -0,0 +1,73 @@
package net.momirealms.craftengine.bukkit.plugin.command.feature;
import net.kyori.adventure.text.Component;
import net.momirealms.craftengine.bukkit.plugin.command.BukkitCommandFeature;
import net.momirealms.craftengine.bukkit.util.MaterialUtils;
import net.momirealms.craftengine.bukkit.util.PlayerUtils;
import net.momirealms.craftengine.core.item.CustomItem;
import net.momirealms.craftengine.core.plugin.CraftEngine;
import net.momirealms.craftengine.core.plugin.command.CraftEngineCommandManager;
import net.momirealms.craftengine.core.plugin.command.FlagKeys;
import net.momirealms.craftengine.core.plugin.locale.MessageConstants;
import net.momirealms.craftengine.core.util.Key;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.incendo.cloud.Command;
import org.incendo.cloud.CommandManager;
import org.incendo.cloud.bukkit.data.MultiplePlayerSelector;
import org.incendo.cloud.bukkit.parser.NamespacedKeyParser;
import org.incendo.cloud.bukkit.parser.selector.MultiplePlayerSelectorParser;
import org.incendo.cloud.context.CommandContext;
import org.incendo.cloud.context.CommandInput;
import org.incendo.cloud.suggestion.Suggestion;
import org.incendo.cloud.suggestion.SuggestionProvider;
import java.util.concurrent.CompletableFuture;
public class TotemCommand extends BukkitCommandFeature<CommandSender> {
public TotemCommand(CraftEngineCommandManager<CommandSender> commandManager, CraftEngine plugin) {
super(commandManager, plugin);
}
@Override
public Command.Builder<? extends CommandSender> assembleCommand(CommandManager<CommandSender> manager, Command.Builder<CommandSender> builder) {
return builder
.flag(FlagKeys.SILENT_FLAG)
.required("players", MultiplePlayerSelectorParser.multiplePlayerSelectorParser())
.required("id", NamespacedKeyParser.namespacedKeyComponent().suggestionProvider(new SuggestionProvider<>() {
@Override
public @NonNull CompletableFuture<? extends @NonNull Iterable<? extends @NonNull Suggestion>> suggestionsFuture(@NonNull CommandContext<Object> context, @NonNull CommandInput input) {
return CompletableFuture.completedFuture(plugin().itemManager().cachedTotemSuggestions());
}
}))
.handler(context -> {
NamespacedKey namespacedKey = context.get("id");
Key key = Key.of(namespacedKey.namespace(), namespacedKey.value());
CustomItem<ItemStack> item = plugin().itemManager().getCustomItem(key).orElse(null);
if (item == null) {
handleFeedback(context, MessageConstants.COMMAND_ITEM_GET_FAILURE_NOT_EXIST, Component.text(key.toString()));
return;
}
if (MaterialUtils.getMaterial(item.material()) != Material.TOTEM_OF_UNDYING) {
handleFeedback(context, MessageConstants.COMMAND_TOTEM_NOT_TOTEM, Component.text(key.toString()));
return;
}
ItemStack totem = item.buildItemStack();
MultiplePlayerSelector selector = context.get("players");
for (Player player : selector.values()) {
PlayerUtils.sendTotemAnimation(player, totem);
}
});
}
@Override
public String getFeatureID() {
return "totem";
}
}

View File

@@ -1,5 +1,9 @@
package net.momirealms.craftengine.bukkit.util;
import com.mojang.datafixers.util.Pair;
import net.momirealms.craftengine.bukkit.nms.FastNMS;
import net.momirealms.craftengine.bukkit.plugin.BukkitCraftEngine;
import net.momirealms.craftengine.bukkit.plugin.network.BukkitNetworkManager;
import net.momirealms.craftengine.core.util.RandomUtils;
import org.bukkit.Location;
import org.bukkit.entity.Item;
@@ -11,6 +15,9 @@ import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
import static java.util.Objects.requireNonNull;
public class PlayerUtils {
@@ -140,4 +147,28 @@ public class PlayerUtils {
return actualAmount;
}
public static void sendTotemAnimation(Player player, ItemStack totem) {
ItemStack offhandItem = player.getInventory().getItemInOffHand();
List<Object> packets = new ArrayList<>();
try {
Object previousItem = Reflections.method$CraftItemStack$asNMSCopy.invoke(null, offhandItem);
Object totemItem = Reflections.method$CraftItemStack$asNMSCopy.invoke(null, totem);
Object packet1 = Reflections.constructor$ClientboundSetEquipmentPacket
.newInstance(player.getEntityId(), List.of(Pair.of(Reflections.instance$EquipmentSlot$OFFHAND, totemItem)));
Object packet2 = Reflections.constructor$ClientboundEntityEventPacket
.newInstance(FastNMS.INSTANCE.method$CraftPlayer$getHandle(player), (byte) 35);
Object packet3 = Reflections.constructor$ClientboundSetEquipmentPacket
.newInstance(player.getEntityId(), List.of(Pair.of(Reflections.instance$EquipmentSlot$OFFHAND, previousItem)));
packets.add(packet1);
packets.add(packet2);
packets.add(packet3);
Object bundlePacket = FastNMS.INSTANCE.constructor$ClientboundBundlePacket(packets);
BukkitNetworkManager.instance().sendPacket(player, bundlePacket);
} catch (ReflectiveOperationException e) {
BukkitCraftEngine.instance().logger().warn("Failed to send totem animation");
}
}
}

View File

@@ -3033,6 +3033,32 @@ public class Reflections {
}
}
public static final Class<?> clazz$ClientboundSetEquipmentPacket = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundSetEquipmentPacket"),
BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketPlayOutEntityEquipment")
)
);
public static final Constructor<?> constructor$ClientboundSetEquipmentPacket = requireNonNull(
ReflectionUtils.getConstructor(
clazz$ClientboundSetEquipmentPacket, int.class, List.class
)
);
public static final Class<?> clazz$ClientboundEntityEventPacket = requireNonNull(
ReflectionUtils.getClazz(
BukkitReflectionUtils.assembleMCClass("network.protocol.game.ClientboundEntityEventPacket"),
BukkitReflectionUtils.assembleMCClass("network.protocol.game.PacketPlayOutEntityStatus")
)
);
public static final Constructor<?> constructor$ClientboundEntityEventPacket = requireNonNull(
ReflectionUtils.getConstructor(
clazz$ClientboundEntityEventPacket, clazz$Entity, byte.class
)
);
public static final Method method$Block$defaultBlockState = requireNonNull(
ReflectionUtils.getMethod(
clazz$Block, clazz$BlockState