diff --git a/bukkit/src/main/java/co/aikar/commands/ACFBukkitListener.java b/bukkit/src/main/java/co/aikar/commands/ACFBukkitListener.java index b404cb81..16d7ffef 100644 --- a/bukkit/src/main/java/co/aikar/commands/ACFBukkitListener.java +++ b/bukkit/src/main/java/co/aikar/commands/ACFBukkitListener.java @@ -54,13 +54,14 @@ class ACFBukkitListener implements Listener { this.manager.readPlayerLocale(player); this.plugin.getServer().getScheduler().runTaskLater(this.plugin, () -> manager.readPlayerLocale(player), 20); } else { - this.manager.setPlayerLocale(player, this.manager.getLocales().getDefaultLocale()); + this.manager.setIssuerLocale(player, this.manager.getLocales().getDefaultLocale()); this.manager.notifyLocaleChange(this.manager.getCommandIssuer(player), null, this.manager.getLocales().getDefaultLocale()); } } @EventHandler - public void onPlayerJoin(PlayerQuitEvent event) { - manager.issuersLocale.remove(event.getPlayer().getUniqueId()); + public void onPlayerQuit(PlayerQuitEvent quitEvent) { + //cleanup + manager.issuersLocale.remove(quitEvent.getPlayer().getUniqueId()); } } diff --git a/bukkit/src/main/java/co/aikar/commands/BukkitCommandIssuer.java b/bukkit/src/main/java/co/aikar/commands/BukkitCommandIssuer.java index 17edffe4..24c9d4d3 100644 --- a/bukkit/src/main/java/co/aikar/commands/BukkitCommandIssuer.java +++ b/bukkit/src/main/java/co/aikar/commands/BukkitCommandIssuer.java @@ -25,8 +25,11 @@ package co.aikar.commands; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.jetbrains.annotations.NotNull; +import java.nio.charset.StandardCharsets; import java.util.Objects; +import java.util.UUID; public class BukkitCommandIssuer implements CommandIssuer { private final BukkitCommandManager manager; @@ -52,6 +55,16 @@ public class BukkitCommandIssuer implements CommandIssuer { return isPlayer() ? (Player) sender : null; } + @Override + public @NotNull UUID getUniqueId() { + if (isPlayer()) { + return ((Player) sender).getUniqueId(); + } + + //generate a unique id based of the name (like for the console command sender) + return UUID.nameUUIDFromBytes(sender.getName().getBytes(StandardCharsets.UTF_8)); + } + @Override public CommandManager getManager() { return manager; diff --git a/bukkit/src/main/java/co/aikar/commands/BukkitCommandManager.java b/bukkit/src/main/java/co/aikar/commands/BukkitCommandManager.java index bb733f8a..80248ab5 100644 --- a/bukkit/src/main/java/co/aikar/commands/BukkitCommandManager.java +++ b/bukkit/src/main/java/co/aikar/commands/BukkitCommandManager.java @@ -26,7 +26,6 @@ package co.aikar.commands; import co.aikar.commands.apachecommonslang.ApacheCommonsExceptionUtil; import co.aikar.timings.lib.MCTiming; import co.aikar.timings.lib.TimingManager; -import com.google.common.collect.Maps; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.Server; @@ -52,7 +51,6 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Objects; -import java.util.UUID; import java.util.logging.Level; import java.util.logging.Logger; @@ -80,7 +78,6 @@ public class BukkitCommandManager extends CommandManager< protected BukkitLocales locales; private boolean cantReadLocale = false; protected boolean autoDetectFromClient = true; - protected Map issuersLocale = Maps.newConcurrentMap(); @SuppressWarnings("JavaReflectionMemberAccess") public BukkitCommandManager(Plugin plugin) { @@ -93,7 +90,9 @@ public class BukkitCommandManager extends CommandManager< this.formatters.put(MessageType.SYNTAX, new BukkitMessageFormatter(ChatColor.YELLOW, ChatColor.GREEN, ChatColor.WHITE)); this.formatters.put(MessageType.INFO, new BukkitMessageFormatter(ChatColor.BLUE, ChatColor.DARK_GREEN, ChatColor.GREEN)); this.formatters.put(MessageType.HELP, new BukkitMessageFormatter(ChatColor.AQUA, ChatColor.GREEN, ChatColor.YELLOW)); + Bukkit.getPluginManager().registerEvents(new ACFBukkitListener(this, plugin), plugin); + getLocales(); // auto load locales this.localeTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> { if (this.cantReadLocale || !this.autoDetectFromClient) { @@ -341,26 +340,6 @@ public class BukkitCommandManager extends CommandManager< } } - public Locale setPlayerLocale(Player player, Locale locale) { - Locale old = this.issuersLocale.put(player.getUniqueId(), locale); - if (!Objects.equals(old, locale)) { - this.notifyLocaleChange(getCommandIssuer(player), old, locale); - } - return old; - } - - @Override - public Locale getIssuerLocale(CommandIssuer issuer) { - if (usingPerIssuerLocale() && issuer.getIssuer() instanceof Player) { - UUID uniqueId = ((Player) issuer.getIssuer()).getUniqueId(); - Locale locale = issuersLocale.get(uniqueId); - if (locale != null) { - return locale; - } - } - return super.getIssuerLocale(issuer); - } - public boolean usePerIssuerLocale(boolean usePerIssuerLocale, boolean autoDetectFromClient) { boolean old = this.usePerIssuerLocale; this.usePerIssuerLocale = usePerIssuerLocale; diff --git a/bungee/src/main/java/co/aikar/commands/ACFBungeeListener.java b/bungee/src/main/java/co/aikar/commands/ACFBungeeListener.java new file mode 100644 index 00000000..a8ca5e15 --- /dev/null +++ b/bungee/src/main/java/co/aikar/commands/ACFBungeeListener.java @@ -0,0 +1,37 @@ +package co.aikar.commands; + +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.PlayerDisconnectEvent; +import net.md_5.bungee.api.event.PostLoginEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.event.EventHandler; + +import java.util.concurrent.TimeUnit; + +public class ACFBungeeListener implements Listener { + + private final BungeeCommandManager manager; + private final Plugin plugin; + + public ACFBungeeListener(BungeeCommandManager manager, Plugin plugin) { + this.manager = manager; + this.plugin = plugin; + } + + @EventHandler + public void onPlayerJoin(PostLoginEvent loginEvent) { + ProxiedPlayer player = loginEvent.getPlayer(); + + //the client settings are sent after a successful login + Runnable task = () -> manager.readLocale(player); + plugin.getProxy().getScheduler().schedule(plugin, task, 1, TimeUnit.SECONDS); + } + + @EventHandler + public void onDisconnect(PlayerDisconnectEvent disconnectEvent) { + //cleanup + ProxiedPlayer player = disconnectEvent.getPlayer(); + manager.issuersLocale.remove(player.getUniqueId()); + } +} diff --git a/bungee/src/main/java/co/aikar/commands/BungeeCommandIssuer.java b/bungee/src/main/java/co/aikar/commands/BungeeCommandIssuer.java index 2737bc0b..9d2ed573 100644 --- a/bungee/src/main/java/co/aikar/commands/BungeeCommandIssuer.java +++ b/bungee/src/main/java/co/aikar/commands/BungeeCommandIssuer.java @@ -25,8 +25,11 @@ package co.aikar.commands; import net.md_5.bungee.api.CommandSender; import net.md_5.bungee.api.connection.ProxiedPlayer; +import org.jetbrains.annotations.NotNull; +import java.nio.charset.StandardCharsets; import java.util.Objects; +import java.util.UUID; public class BungeeCommandIssuer implements CommandIssuer { private final BungeeCommandManager manager; @@ -57,6 +60,16 @@ public class BungeeCommandIssuer implements CommandIssuer { return sender instanceof ProxiedPlayer; } + @Override + public @NotNull UUID getUniqueId() { + if (isPlayer()) { + return ((ProxiedPlayer) sender).getUniqueId(); + } + + //generate a unique id based of the name (like for the console command sender) + return UUID.nameUUIDFromBytes(sender.getName().getBytes(StandardCharsets.UTF_8)); + } + @Override public void sendMessageInternal(String message) { sender.sendMessage(ACFBungeeUtil.color(message)); diff --git a/bungee/src/main/java/co/aikar/commands/BungeeCommandManager.java b/bungee/src/main/java/co/aikar/commands/BungeeCommandManager.java index 06c65603..8ebd6e21 100644 --- a/bungee/src/main/java/co/aikar/commands/BungeeCommandManager.java +++ b/bungee/src/main/java/co/aikar/commands/BungeeCommandManager.java @@ -26,13 +26,17 @@ package co.aikar.commands; import co.aikar.commands.apachecommonslang.ApacheCommonsExceptionUtil; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.plugin.Plugin; import java.lang.reflect.Method; import java.lang.reflect.Parameter; import java.util.HashMap; import java.util.List; +import java.util.Locale; import java.util.Map; +import java.util.concurrent.TimeUnit; import java.util.logging.Level; import java.util.logging.Logger; @@ -57,8 +61,16 @@ public class BungeeCommandManager extends CommandManager< this.formatters.put(MessageType.SYNTAX, new BungeeMessageFormatter(ChatColor.YELLOW, ChatColor.GREEN, ChatColor.WHITE)); this.formatters.put(MessageType.INFO, new BungeeMessageFormatter(ChatColor.BLUE, ChatColor.DARK_GREEN, ChatColor.GREEN)); this.formatters.put(MessageType.HELP, new BungeeMessageFormatter(ChatColor.AQUA, ChatColor.GREEN, ChatColor.YELLOW)); + getLocales(); // auto load locales + plugin.getProxy().getPluginManager().registerListener(plugin, new ACFBungeeListener(this, plugin)); + + //BungeeCord has no event for listening for client setting changes + plugin.getProxy().getScheduler().schedule(plugin, () -> { + ProxyServer.getInstance().getPlayers().forEach(this::readLocale); + }, 5, 5, TimeUnit.SECONDS); + // TODO more default dependencies for bungee registerDependency(plugin.getClass(), plugin); registerDependency(Plugin.class, plugin); @@ -93,6 +105,17 @@ public class BungeeCommandManager extends CommandManager< return locales; } + public void readLocale(ProxiedPlayer player) { + if (!player.isConnected()) { + return; + } + + //This can be null if we didn't received a settings packet + Locale locale = player.getLocale(); + if (locale != null) { + setIssuerLocale(player, player.getLocale()); + } + } @Override public void registerCommand(BaseCommand command) { diff --git a/core/src/main/java/co/aikar/commands/CommandIssuer.java b/core/src/main/java/co/aikar/commands/CommandIssuer.java index 6a253cfd..0ca25dfa 100644 --- a/core/src/main/java/co/aikar/commands/CommandIssuer.java +++ b/core/src/main/java/co/aikar/commands/CommandIssuer.java @@ -25,6 +25,9 @@ package co.aikar.commands; import co.aikar.locales.MessageKey; import co.aikar.locales.MessageKeyProvider; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; public interface CommandIssuer { /** @@ -50,6 +53,11 @@ public interface CommandIssuer { getManager().sendMessage(this, MessageType.INFO, MessageKeys.INFO_MESSAGE, "{message}", message); } + /** + * @return the unique id of that issuer + */ + @NotNull UUID getUniqueId(); + /** * Has permission node * @param permission diff --git a/core/src/main/java/co/aikar/commands/CommandManager.java b/core/src/main/java/co/aikar/commands/CommandManager.java index 79b84b56..d8a6df39 100644 --- a/core/src/main/java/co/aikar/commands/CommandManager.java +++ b/core/src/main/java/co/aikar/commands/CommandManager.java @@ -27,6 +27,7 @@ import co.aikar.commands.annotation.Dependency; import co.aikar.locales.MessageKeyProvider; import com.google.common.collect.HashBasedTable; import com.google.common.collect.Lists; +import com.google.common.collect.Maps; import com.google.common.collect.Sets; import com.google.common.collect.Table; import org.jetbrains.annotations.NotNull; @@ -35,14 +36,16 @@ import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Parameter; -import java.util.Arrays; import java.util.HashMap; import java.util.IdentityHashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Set; import java.util.Stack; +import java.util.UUID; + @SuppressWarnings("WeakerAccess") public abstract class CommandManager < @@ -76,6 +79,8 @@ public abstract class CommandManager < protected MF defaultFormatter; protected int defaultHelpPerPage = 10; + protected Map issuersLocale = Maps.newConcurrentMap(); + private Set unstableAPIs = Sets.newHashSet(); public static CommandOperationContext getCurrentCommandOperationContext() { @@ -344,7 +349,25 @@ public abstract class CommandManager < }); } + public Locale setIssuerLocale(IT issuer, Locale locale) { + I commandIssuer = getCommandIssuer(issuer); + + Locale old = issuersLocale.put(commandIssuer.getUniqueId(), locale); + if (!Objects.equals(old, locale)) { + this.notifyLocaleChange(commandIssuer, old, locale); + } + + return old; + } + public Locale getIssuerLocale(CommandIssuer issuer) { + if (usingPerIssuerLocale()) { + Locale locale = issuersLocale.get(issuer.getUniqueId()); + if (locale != null) { + return locale; + } + } + return getLocales().getDefaultLocale(); } diff --git a/jda/src/main/java/co/aikar/commands/JDACommandEvent.java b/jda/src/main/java/co/aikar/commands/JDACommandEvent.java index fb46d1a6..644cda75 100644 --- a/jda/src/main/java/co/aikar/commands/JDACommandEvent.java +++ b/jda/src/main/java/co/aikar/commands/JDACommandEvent.java @@ -3,6 +3,9 @@ package co.aikar.commands; import net.dv8tion.jda.core.entities.Message; import net.dv8tion.jda.core.entities.MessageEmbed; import net.dv8tion.jda.core.events.message.MessageReceivedEvent; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; public class JDACommandEvent implements CommandIssuer { @@ -34,6 +37,14 @@ public class JDACommandEvent implements CommandIssuer { return false; } + @Override + public @NotNull UUID getUniqueId() { + // Discord id only have 64 bit width (long) while UUIDs have twice the size. + // In order to keep it unique we use 0L for the first 64 bit. + long authorId = event.getAuthor().getIdLong(); + return new UUID(0, authorId); + } + @Override public boolean hasPermission(String permission) { CommandPermissionResolver permissionResolver = this.manager.getPermissionResolver(); diff --git a/sponge/src/main/java/co/aikar/commands/ACFSpongeListener.java b/sponge/src/main/java/co/aikar/commands/ACFSpongeListener.java new file mode 100644 index 00000000..72b6b639 --- /dev/null +++ b/sponge/src/main/java/co/aikar/commands/ACFSpongeListener.java @@ -0,0 +1,28 @@ +package co.aikar.commands; + +import org.spongepowered.api.entity.living.player.Player; +import org.spongepowered.api.event.Listener; +import org.spongepowered.api.event.Order; +import org.spongepowered.api.event.entity.living.humanoid.player.PlayerChangeClientSettingsEvent; +import org.spongepowered.api.event.filter.cause.First; +import org.spongepowered.api.event.network.ClientConnectionEvent; + +public class ACFSpongeListener { + + private final SpongeCommandManager manager; + + public ACFSpongeListener(SpongeCommandManager manager) { + this.manager = manager; + } + + @Listener(order = Order.POST) + public void onSettingsChange(PlayerChangeClientSettingsEvent changeSettingsEvent, @First Player targetPlayer) { + //this event will be fired on join as well as every time the player changes it + manager.setIssuerLocale(targetPlayer, targetPlayer.getLocale()); + } + + @Listener + public void onDisconnectCleanup(ClientConnectionEvent.Disconnect disconnectEvent, @First Player player) { + manager.issuersLocale.remove(player.getUniqueId()); + } +} diff --git a/sponge/src/main/java/co/aikar/commands/SpongeCommandIssuer.java b/sponge/src/main/java/co/aikar/commands/SpongeCommandIssuer.java index 2c6f48e0..b3f95979 100644 --- a/sponge/src/main/java/co/aikar/commands/SpongeCommandIssuer.java +++ b/sponge/src/main/java/co/aikar/commands/SpongeCommandIssuer.java @@ -23,11 +23,15 @@ package co.aikar.commands; +import org.jetbrains.annotations.NotNull; import org.spongepowered.api.command.CommandSource; import org.spongepowered.api.entity.living.player.Player; import org.spongepowered.api.text.serializer.TextSerializers; +import org.spongepowered.api.util.Identifiable; +import java.nio.charset.StandardCharsets; import java.util.Objects; +import java.util.UUID; public class SpongeCommandIssuer implements CommandIssuer { @@ -49,6 +53,16 @@ public class SpongeCommandIssuer implements CommandIssuer { return this.source; } + @Override + public @NotNull UUID getUniqueId() { + if (isPlayer()) { + return ((Identifiable) source).getUniqueId(); + } + + //generate a unique id based of the name (like for the console command sender) + return UUID.nameUUIDFromBytes(source.getName().getBytes(StandardCharsets.UTF_8)); + } + public Player getPlayer() { return isPlayer() ? (Player) source : null; } @@ -68,7 +82,6 @@ public class SpongeCommandIssuer implements CommandIssuer { return this.source.hasPermission(permission); } - @Override public boolean equals(Object o) { if (this == o) return true; diff --git a/sponge/src/main/java/co/aikar/commands/SpongeCommandManager.java b/sponge/src/main/java/co/aikar/commands/SpongeCommandManager.java index db54aca2..19798dc4 100644 --- a/sponge/src/main/java/co/aikar/commands/SpongeCommandManager.java +++ b/sponge/src/main/java/co/aikar/commands/SpongeCommandManager.java @@ -68,6 +68,8 @@ public class SpongeCommandManager extends CommandManager< this.formatters.put(MessageType.HELP, new SpongeMessageFormatter(TextColors.AQUA, TextColors.GREEN, TextColors.YELLOW)); getLocales(); // auto load locales + Sponge.getEventManager().registerListeners(plugin, new ACFSpongeListener(this)); + //TODO more default dependencies for sponge registerDependency(plugin.getClass(), plugin); } @@ -196,5 +198,4 @@ public class SpongeCommandManager extends CommandManager< public SpongeConditionContext createConditionContext(CommandIssuer issuer, String config) { return new SpongeConditionContext((SpongeCommandIssuer) issuer, config); } - }