mirror of
https://github.com/Xiao-MoMi/craft-engine.git
synced 2025-12-25 01:49:30 +00:00
Merge pull request #81 from iqtesterrr/dev
Implement totem animation feature
This commit is contained in:
@@ -69,6 +69,13 @@ search_recipe_admin:
|
||||
- /craftengine item search-recipe
|
||||
- /ce item search-recipe
|
||||
|
||||
totem:
|
||||
enable: true
|
||||
permission: ce.command.admin.totem
|
||||
usage:
|
||||
- /craftengine totem
|
||||
- /ce totem
|
||||
|
||||
# Debug commands
|
||||
debug_set_block:
|
||||
enable: true
|
||||
|
||||
@@ -52,4 +52,5 @@ command.item.give.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:
|
||||
command.search_recipe.not_found: "<red>No recipe found for this item</red>"
|
||||
command.search_usage.not_found: "<red>No usage found for this item</red>"
|
||||
command.search_recipe.no_item: "<red>Please hold an item before running this command</red>"
|
||||
command.search_usage.no_item: "<red>Please hold an item before running this command</red>"
|
||||
command.search_usage.no_item: "<red>Please hold an item before running this command</red>"
|
||||
command.totem.not_totem: "<red>'<arg:0>' is not type of totem_of_undying</red>"
|
||||
@@ -52,4 +52,5 @@ command.item.give.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:
|
||||
command.search_recipe.not_found: "<red>No se encontró ninguna receta para este objeto</red>"
|
||||
command.search_usage.not_found: "<red>No se encontró ningún uso para este objeto</red>"
|
||||
command.search_recipe.no_item: "<red>Por favor, sostén un objeto antes de ejecutar este comando</red>"
|
||||
command.search_usage.no_item: "<red>Por favor, sostén un objeto antes de ejecutar este comando</red>"
|
||||
command.search_usage.no_item: "<red>Por favor, sostén un objeto antes de ejecutar este comando</red>"
|
||||
command.totem.not_totem: "<red>'<arg:0>' no es del tipo totem_of_undying</red>"
|
||||
@@ -52,4 +52,5 @@ command.item.give.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:
|
||||
command.search_recipe.not_found: "<red>找不到此物品的配方</red>"
|
||||
command.search_usage.not_found: "<red>找不到此物品的用途</red>"
|
||||
command.search_recipe.no_item: "<red>请手持物品后再执行此命令</red>"
|
||||
command.search_usage.no_item: "<red>请手持物品后再执行此命令</red>"
|
||||
command.search_usage.no_item: "<red>请手持物品后再执行此命令</red>"
|
||||
command.totem.not_totem: "<red>'<arg:0>' 不是 totem_of_undying 类型</red>"
|
||||
@@ -52,4 +52,5 @@ command.item.give.failure.not_exist: "<red><lang:argument.item.id.invalid:'<arg:
|
||||
command.search_recipe.not_found: "<red>找不到此物品的配方</red>"
|
||||
command.search_usage.not_found: "<red>找不到此物品的用途</red>"
|
||||
command.search_recipe.no_item: "<red>執行此命令前請手持物品</red>"
|
||||
command.search_usage.no_item: "<red>執行此命令前請手持物品</red>"
|
||||
command.search_usage.no_item: "<red>執行此命令前請手持物品</red>"
|
||||
command.totem.not_totem: "<red>'<arg:0>' 不是 totem_of_undying 類型</red>"
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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";
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -35,6 +35,7 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
|
||||
protected final Set<EquipmentGeneration> equipmentsToGenerate;
|
||||
// Cached command suggestions
|
||||
protected final List<Suggestion> cachedSuggestions = new ArrayList<>();
|
||||
protected final List<Suggestion> cachedTotemSuggestions = new ArrayList<>();
|
||||
|
||||
protected void registerDataFunction(Function<Object, ItemModifier<I>> function, String... alias) {
|
||||
for (String a : alias) {
|
||||
@@ -65,6 +66,7 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
|
||||
super.clearModelsToGenerate();
|
||||
this.customItems.clear();
|
||||
this.cachedSuggestions.clear();
|
||||
this.cachedTotemSuggestions.clear();
|
||||
this.legacyOverrides.clear();
|
||||
this.modernOverrides.clear();
|
||||
this.customItemTags.clear();
|
||||
@@ -120,6 +122,11 @@ public abstract class AbstractItemManager<I> extends AbstractModelGenerator impl
|
||||
return Collections.unmodifiableCollection(this.cachedSuggestions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Suggestion> cachedTotemSuggestions() {
|
||||
return Collections.unmodifiableCollection(this.cachedTotemSuggestions);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<List<ItemBehavior>> getItemBehavior(Key key) {
|
||||
Optional<CustomItem<I>> customItemOptional = getCustomItem(key);
|
||||
|
||||
@@ -85,5 +85,7 @@ public interface ItemManager<T> extends Reloadable, ModelGenerator, ConfigSectio
|
||||
|
||||
Collection<Suggestion> cachedSuggestions();
|
||||
|
||||
Collection<Suggestion> cachedTotemSuggestions();
|
||||
|
||||
Object encodeJava(Key componentType, @Nullable Object component);
|
||||
}
|
||||
@@ -20,4 +20,5 @@ public interface MessageConstants {
|
||||
TranslatableComponent.Builder COMMAND_SEARCH_RECIPE_NO_ITEM = Component.translatable().key("command.search_recipe.no_item");
|
||||
TranslatableComponent.Builder COMMAND_SEARCH_USAGE_NOT_FOUND = Component.translatable().key("command.search_usage.not_found");
|
||||
TranslatableComponent.Builder COMMAND_SEARCH_USAGE_NO_ITEM = Component.translatable().key("command.search_usage.no_item");
|
||||
TranslatableComponent.Builder COMMAND_TOTEM_NOT_TOTEM = Component.translatable().key("command.totem.not_totem");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user