All Downloads are FREE. Search and download functionalities are using the official Maven repository.

dev.jorel.commandapi.Converter Maven / Gradle / Ivy

/*******************************************************************************
 * Copyright 2018, 2021 Jorel Ali (Skepter) - MIT License
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy of
 * this software and associated documentation files (the "Software"), to deal in
 * the Software without restriction, including without limitation the rights to
 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
 * the Software, and to permit persons to whom the Software is furnished to do so,
 * subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
 * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
 * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
 * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
 * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *******************************************************************************/
package dev.jorel.commandapi;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.LivingEntity;
import org.bukkit.plugin.java.JavaPlugin;

import dev.jorel.commandapi.arguments.Argument;
import dev.jorel.commandapi.arguments.GreedyStringArgument;
import dev.jorel.commandapi.executors.NativeCommandExecutor;
import dev.jorel.commandapi.wrappers.NativeProxyCommandSender;

/**
 * 'Simple' conversion of Plugin commands
 */
public final class Converter {

	// Cannot be instantiated
	private Converter() {
	}

	private static final List> PLAIN_ARGUMENTS = List.of(new GreedyStringArgument("args"));
	private static final Set CALLER_METHODS = Set.of("isPermissionSet", "hasPermission",
			"addAttachment", "removeAttachment", "recalculatePermissions", "getEffectivePermissions", "isOp", "setOp");

	/**
	 * Convert all commands stated in Plugin's plugin.yml file into
	 * CommandAPI-compatible commands
	 * 
	 * @param plugin The plugin which commands are to be converted
	 */
	public static void convert(JavaPlugin plugin) {
		CommandAPI.logInfo("Converting commands for " + plugin.getName() + ":");
		for (String commandName : plugin.getDescription().getCommands().keySet()) {
			convertPluginCommand(plugin, commandName, PLAIN_ARGUMENTS);
		}
	}

	/**
	 * Convert a command stated in Plugin's plugin.yml file into
	 * CommandAPI-compatible commands
	 * 
	 * @param plugin  The plugin where the command is registered
	 * @param cmdName The command to convert
	 */
	public static void convert(JavaPlugin plugin, String cmdName) {
		convertPluginCommand(plugin, cmdName, PLAIN_ARGUMENTS);
	}

	/**
	 * Convert a command stated in Plugin's plugin.yml file into
	 * CommandAPI-compatible commands
	 * 
	 * @param plugin    The plugin where the command is registered
	 * @param cmdName   The command to convert
	 * @param arguments The arguments that should be used to parse this command
	 */
	public static void convert(JavaPlugin plugin, String cmdName, Argument... arguments) {
		convertPluginCommand(plugin, cmdName, Arrays.asList(arguments));
	}

	/**
	 * Convert a command stated in Plugin's plugin.yml file into
	 * CommandAPI-compatible commands
	 * 
	 * @param plugin    The plugin where the command is registered
	 * @param cmdName   The command to convert
	 * @param arguments The arguments that should be used to parse this command
	 */
	public static void convert(JavaPlugin plugin, String cmdName, List> arguments) {
		convertPluginCommand(plugin, cmdName, arguments);
	}

	/**
	 * Convert the provided command name into a CommandAPI-compatible command
	 * 
	 * @param cmdName The name of the command (without the leading /). For commands
	 *                such as //set in WorldEdit, this parameter should be "/set"
	 */
	public static void convert(String cmdName) {
		convertCommand(cmdName, PLAIN_ARGUMENTS);
	}

	/**
	 * Convert the provided command name with its list of arguments into a
	 * CommandAPI-compatible command
	 * 
	 * @param cmdName   The name of the command (without the leading /). For
	 *                  commands such as //set in WorldEdit, this parameter should
	 *                  be "/set"
	 * @param arguments The arguments that should be used to parse this command
	 */
	public static void convert(String cmdName, List> arguments) {
		convertCommand(cmdName, arguments);
	}

	private static void convertCommand(String commandName, List> arguments) {
		CommandAPI.logInfo("Converting command /" + commandName);

		// No arguments
		new CommandAPICommand(commandName).withPermission(CommandPermission.NONE).executesNative((sender, args) -> {
			Bukkit.dispatchCommand(mergeProxySender(sender), commandName);
			return;
		}).register();

		// Multiple arguments
		CommandAPICommand multiArgs = new CommandAPICommand(commandName).withPermission(CommandPermission.NONE)
				.withArguments(arguments).executesNative((sender, args) -> {
					// We know the args are a String[] because that's how converted things are
					// handled in generateCommand()
					CommandSender proxiedSender = mergeProxySender(sender);
					Bukkit.dispatchCommand(proxiedSender, commandName + " " + String.join(" ", (String[]) args.args()));
				});

		multiArgs.setConverted(true);
		multiArgs.register();
	}
	
	private static String[] unpackAliases(Object aliasObj) {
		if (aliasObj == null) {
			return new String[0];
		} else if (aliasObj instanceof String aliasStr) {
			return new String[] { aliasStr };
		} else if (aliasObj instanceof List aliasList) {
			return aliasList.toArray(new String[0]);
		} else {
			throw new IllegalStateException("Invalid type for aliases. Expected String or List, but got " + aliasObj.getClass().getSimpleName());
		}
	}

	private static void convertPluginCommand(JavaPlugin plugin, String commandName, List> arguments) {
		CommandAPI.logInfo("Converting " + plugin.getName() + " command /" + commandName);
		/* Parse the commands */
		Map cmdData = plugin.getDescription().getCommands().get(commandName);

		if (cmdData == null) {
			CommandAPI.logError("Couldn't find /" + commandName + " in " + plugin.getName()
					+ "'s plugin.yml. Are you sure you're not confusing it with an alias?");
			return;
		}

		// Convert stupid YAML aliases to a String[] for CommandAPI
		String[] aliases = unpackAliases(cmdData.get("aliases"));
		if (aliases.length != 0) {
			CommandAPI.logInfo("Aliases for command /" + commandName + " found. Using aliases " + Arrays.deepToString(aliases));
		}

		// Convert YAML to description
		String fullDescription = null;
		Object descriptionObj = cmdData.get("description");
		if (descriptionObj != null && descriptionObj instanceof String descriptionStr) {
			fullDescription = descriptionStr;
		}

		// Convert YAML to CommandPermission
		CommandPermission permissionNode = null;
		String permission = (String) cmdData.get("permission");
		if (permission == null) {
			permissionNode = CommandPermission.NONE;
		} else {
			CommandAPI.logInfo("Permission for command /" + commandName + " found. Using " + permission);
			permissionNode = CommandPermission.fromString(permission);
		}
		
		NativeCommandExecutor executor = (sender, args) -> {
			org.bukkit.command.Command command = plugin.getCommand(commandName);
			
			if (command == null) {
				command = CommandAPIBukkit.get().getSimpleCommandMap()
						.getCommand(commandName);
			}

			CommandSender proxiedSender = CommandAPI.getConfiguration().shouldSkipSenderProxy(plugin.getName())
					? sender.getCallee()
					: mergeProxySender(sender);

			if (args.args() instanceof String[] argsArr) {
				command.execute(proxiedSender, commandName, argsArr);
			} else {
				command.execute(proxiedSender, commandName, new String[0]);
			}
		};

		// No arguments
		new CommandAPICommand(commandName)
			.withPermission(permissionNode)
			.withAliases(aliases)
			.withFullDescription(fullDescription)
			.executesNative(executor)
			.register();

		// Multiple arguments
		new CommandAPICommand(commandName)
			.withPermission(permissionNode)
			.withAliases(aliases)
			.withArguments(arguments)
			.withFullDescription(fullDescription)
			.executesNative(executor)
			.setConverted(true)
			.register();
	}

	/*
	 * https://www.jorel.dev/blog/posts/Simplifying-Bukkit-CommandSenders/
	 */
	private static CommandSender mergeProxySender(NativeProxyCommandSender proxySender) {
		// Add all interfaces
		Set> calleeInterfacesList = new HashSet<>();
		Class currentClass = proxySender.getCallee().getClass();
		if (currentClass.isInterface()) {
			calleeInterfacesList.add(currentClass);
		}
		while (currentClass != null) {
			calleeInterfacesList.addAll(Arrays.asList(currentClass.getInterfaces()));
			currentClass = currentClass.getSuperclass();
		}
		Class[] calleeInterfaces = calleeInterfacesList.toArray(new Class[0]);

		InvocationHandler handler = (Object p, Method method, Object[] args) -> {
			switch(method.getName()) {
				case "getLocation":
					return proxySender.getLocation();
				case "getBlock":
					return proxySender.getLocation().getBlock();
				case "getEyeLocation":
					if(proxySender.getCallee() instanceof LivingEntity livingEntity) {
						Location loc = proxySender.getLocation();
						loc.setY(loc.getY() + livingEntity.getEyeHeight());
						return loc;
					} else {
						// This case should never happen. If it does, please let me know!
						return proxySender.getLocation();
					}
				case "getWorld":
					return proxySender.getWorld();
				default:
					return method.invoke(
							CALLER_METHODS.contains(method.getName()) ? proxySender.getCaller() : proxySender.getCallee(),
							args);
			}
		};

		return (CommandSender) Proxy.newProxyInstance(CommandSender.class.getClassLoader(), calleeInterfaces, handler);
	}

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy