mirror of
https://github.com/aikar/commands.git
synced 2026-05-31 06:11:55 +00:00
Add support for Per Player Locale on Bukkit - Resolves #62
This commit is contained in:
@@ -26,6 +26,7 @@ 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;
|
||||
@@ -33,10 +34,14 @@ import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandMap;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.command.SimpleCommandMap;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
import org.bukkit.event.player.PlayerQuitEvent;
|
||||
import org.bukkit.event.server.PluginDisableEvent;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
@@ -44,23 +49,29 @@ 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.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class BukkitCommandManager extends CommandManager<CommandSender, ChatColor, BukkitMessageFormatter> {
|
||||
public class BukkitCommandManager extends CommandManager<CommandSender, BukkitCommandIssuer, ChatColor, BukkitMessageFormatter> {
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
protected final Plugin plugin;
|
||||
private final CommandMap commandMap;
|
||||
private final TimingManager timingManager;
|
||||
private final BukkitTask localeTask;
|
||||
protected Map<String, Command> knownCommands = new HashMap<>();
|
||||
protected Map<String, BukkitRootCommand> registeredCommands = new HashMap<>();
|
||||
protected BukkitCommandContexts contexts;
|
||||
protected BukkitCommandCompletions completions;
|
||||
MCTiming commandTiming;
|
||||
protected BukkitLocales locales;
|
||||
private boolean cantReadLocale = false;
|
||||
protected Map<UUID, Locale> issuersLocale = Maps.newConcurrentMap();
|
||||
|
||||
@SuppressWarnings("JavaReflectionMemberAccess")
|
||||
public BukkitCommandManager(Plugin plugin) {
|
||||
@@ -74,6 +85,12 @@ public class BukkitCommandManager extends CommandManager<CommandSender, ChatColo
|
||||
this.formatters.put(MessageType.HELP, new BukkitMessageFormatter(ChatColor.AQUA, ChatColor.GREEN, ChatColor.YELLOW));
|
||||
Bukkit.getPluginManager().registerEvents(new ACFBukkitListener(plugin), plugin);
|
||||
getLocales(); // auto load locales
|
||||
this.localeTask = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
|
||||
if (cantReadLocale) {
|
||||
return;
|
||||
}
|
||||
Bukkit.getOnlinePlayers().forEach(this::readPlayerLocale);
|
||||
}, 5, 5);
|
||||
}
|
||||
|
||||
@NotNull private CommandMap hookCommandMap() {
|
||||
@@ -213,6 +230,49 @@ public class BukkitCommandManager extends CommandManager<CommandSender, ChatColo
|
||||
this.registeredCommands.clear();
|
||||
}
|
||||
|
||||
|
||||
private Field getEntityField(Player player) throws NoSuchFieldException {
|
||||
Class cls = player.getClass();
|
||||
while (cls != Object.class) {
|
||||
if (cls.getName().endsWith("CraftEntity")) {
|
||||
Field field = cls.getDeclaredField("entity");
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
}
|
||||
cls = cls.getSuperclass();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void readPlayerLocale(Player player) {
|
||||
if (!player.isOnline() || cantReadLocale) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
Field entityField = getEntityField(player);
|
||||
if (entityField == null) {
|
||||
return;
|
||||
}
|
||||
Object nmsPlayer = entityField.get(player);
|
||||
if (nmsPlayer != null) {
|
||||
Field localeField = nmsPlayer.getClass().getField("locale");
|
||||
Object localeString = localeField.get(nmsPlayer);
|
||||
if (localeString != null && localeString instanceof String) {
|
||||
String[] split = ACFPatterns.UNDERSCORE.split((String) localeString);
|
||||
Locale locale = split.length > 1 ? new Locale(split[0], split[1]) : new Locale(split[0]);
|
||||
Locale prev = issuersLocale.put(player.getUniqueId(), locale);
|
||||
if (!Objects.equals(locale, prev)) {
|
||||
this.notifyLocaleChange(getCommandIssuer(player), prev, locale);
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
cantReadLocale = true;
|
||||
this.localeTask.cancel();
|
||||
this.log(LogLevel.INFO, "Can't read players locale, you will be unable to automatically detect players language. Only Bukkit 1.7+ is supported for this.", e);
|
||||
}
|
||||
}
|
||||
|
||||
private class ACFBukkitListener implements Listener {
|
||||
private final Plugin plugin;
|
||||
|
||||
@@ -227,6 +287,17 @@ public class BukkitCommandManager extends CommandManager<CommandSender, ChatColo
|
||||
}
|
||||
unregisterCommands();
|
||||
}
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerJoinEvent event) {
|
||||
Player player = event.getPlayer();
|
||||
readPlayerLocale(player);
|
||||
this.plugin.getServer().getScheduler().runTaskLater(this.plugin, () -> readPlayerLocale(player), 20);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerJoin(PlayerQuitEvent event) {
|
||||
issuersLocale.remove(event.getPlayer().getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
public TimingManager getTimings() {
|
||||
@@ -239,7 +310,7 @@ public class BukkitCommandManager extends CommandManager<CommandSender, ChatColo
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandIssuer getCommandIssuer(Object issuer) {
|
||||
public BukkitCommandIssuer getCommandIssuer(Object issuer) {
|
||||
if (!(issuer instanceof CommandSender)) {
|
||||
throw new IllegalArgumentException(issuer.getClass().getName() + " is not a Command Issuer.");
|
||||
}
|
||||
@@ -273,4 +344,14 @@ public class BukkitCommandManager extends CommandManager<CommandSender, ChatColo
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Locale getIssuerLocale(CommandIssuer issuer) {
|
||||
UUID uniqueId = ((Player) issuer.getIssuer()).getUniqueId();
|
||||
Locale locale = issuersLocale.get(uniqueId);
|
||||
if (locale != null) {
|
||||
return locale;
|
||||
}
|
||||
return super.getIssuerLocale(issuer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,7 +36,7 @@ import java.util.HashMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
public class BungeeCommandManager extends CommandManager<CommandSender, ChatColor, BungeeMessageFormatter> {
|
||||
public class BungeeCommandManager extends CommandManager<CommandSender, BungeeCommandIssuer, ChatColor, BungeeMessageFormatter> {
|
||||
|
||||
protected final Plugin plugin;
|
||||
protected Map<String, BungeeRootCommand> registeredCommands = new HashMap<>();
|
||||
@@ -131,7 +131,7 @@ public class BungeeCommandManager extends CommandManager<CommandSender, ChatColo
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandIssuer getCommandIssuer(Object issuer) {
|
||||
public BungeeCommandIssuer getCommandIssuer(Object issuer) {
|
||||
if (!(issuer instanceof CommandSender)) {
|
||||
throw new IllegalArgumentException(issuer.getClass().getName() + " is not a Command Issuer.");
|
||||
}
|
||||
|
||||
@@ -37,6 +37,7 @@ final class ACFPatterns {
|
||||
public static final Pattern PERCENTAGE = Pattern.compile("%", Pattern.LITERAL);
|
||||
public static final Pattern NEWLINE = Pattern.compile("\n");
|
||||
public static final Pattern DASH = Pattern.compile("-");
|
||||
public static final Pattern UNDERSCORE = Pattern.compile("_");
|
||||
public static final Pattern SPACE = Pattern.compile(" ");
|
||||
public static final Pattern SEMICOLON = Pattern.compile(";");
|
||||
public static final Pattern COLON = Pattern.compile(":");
|
||||
|
||||
@@ -50,7 +50,7 @@ public abstract class BaseCommand {
|
||||
private String execSubcommand;
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
private String[] origArgs;
|
||||
CommandManager<?, ?, ?> manager = null;
|
||||
CommandManager<?, ?, ?, ?> manager = null;
|
||||
BaseCommand parentCommand;
|
||||
Map<String, RootCommand> registeredCommands = new HashMap<>();
|
||||
String description;
|
||||
|
||||
@@ -24,15 +24,22 @@
|
||||
package co.aikar.commands;
|
||||
|
||||
import co.aikar.locales.MessageKeyProvider;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Parameter;
|
||||
import java.util.*;
|
||||
import java.util.HashMap;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public abstract class CommandManager <I, FT, F extends MessageFormatter<FT>> {
|
||||
public abstract class CommandManager <I, AI extends CommandIssuer, FT, F extends MessageFormatter<FT>> {
|
||||
|
||||
/**
|
||||
* This is a stack incase a command calls a command
|
||||
@@ -48,11 +55,14 @@ public abstract class CommandManager <I, FT, F extends MessageFormatter<FT>> {
|
||||
protected Map<String, RootCommand> rootCommands = new HashMap<>();
|
||||
protected final CommandReplacements replacements = new CommandReplacements(this);
|
||||
protected ExceptionHandler defaultExceptionHandler = null;
|
||||
|
||||
|
||||
|
||||
protected List<IssuerLocaleChangedCallback<AI>> localeChangedCallbacks = Lists.newArrayList();
|
||||
protected Set<Locale> supportedLanguages = Sets.newHashSet(Locales.ENGLISH, Locales.GERMAN, Locales.SPANISH, Locales.CZECH);
|
||||
protected Map<MessageType, F> formatters = new IdentityHashMap<>();
|
||||
protected F defaultFormatter;
|
||||
protected int defaultHelpPerPage = 10;
|
||||
|
||||
private Set<String> unstableAPIs = Sets.newHashSet();
|
||||
|
||||
public static CommandOperationContext getCurrentCommandOperationContext() {
|
||||
@@ -165,7 +175,7 @@ public abstract class CommandManager <I, FT, F extends MessageFormatter<FT>> {
|
||||
public abstract boolean isCommandIssuer(Class<?> type);
|
||||
|
||||
// TODO: Change this to I if we make a breaking change
|
||||
public abstract CommandIssuer getCommandIssuer(Object issuer);
|
||||
public abstract AI getCommandIssuer(Object issuer);
|
||||
|
||||
public abstract RootCommand createRootCommand(String cmd);
|
||||
|
||||
@@ -273,6 +283,20 @@ public abstract class CommandManager <I, FT, F extends MessageFormatter<FT>> {
|
||||
return message;
|
||||
}
|
||||
|
||||
public void onLocaleChange(IssuerLocaleChangedCallback<AI> onChange) {
|
||||
localeChangedCallbacks.add(onChange);
|
||||
}
|
||||
|
||||
public void notifyLocaleChange(AI issuer, Locale oldLocale, Locale newLocale) {
|
||||
localeChangedCallbacks.forEach(cb -> {
|
||||
try {
|
||||
cb.onIssuerLocaleChange(issuer, oldLocale, newLocale);
|
||||
} catch (Exception e) {
|
||||
this.log(LogLevel.ERROR, "Error in notifyLocaleChange", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public Locale getIssuerLocale(CommandIssuer issuer) {
|
||||
return getLocales().getDefaultLocale();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Copyright (c) 2016-2017 Daniel Ennis (Aikar) - 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 co.aikar.commands;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public interface IssuerLocaleChangedCallback <I extends CommandIssuer> {
|
||||
void onIssuerLocaleChange(I issuer, Locale oldLocale, Locale newLocale);
|
||||
}
|
||||
@@ -72,7 +72,7 @@ public class Locales {
|
||||
public static final Locale WELSH = new Locale("cy");
|
||||
|
||||
|
||||
private final CommandManager<?, ?, ?> manager;
|
||||
private final CommandManager<?, ?, ?, ?> manager;
|
||||
private final LocaleManager<CommandIssuer> localeManager;
|
||||
private final SetMultimap<String, Locale> loadedBundles = HashMultimap.create();
|
||||
|
||||
|
||||
@@ -40,7 +40,7 @@ import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
@SuppressWarnings("WeakerAccess")
|
||||
public class SpongeCommandManager extends CommandManager<CommandSource, TextColor, SpongeMessageFormatter> {
|
||||
public class SpongeCommandManager extends CommandManager<CommandSource, SpongeCommandIssuer, TextColor, SpongeMessageFormatter> {
|
||||
|
||||
protected final PluginContainer plugin;
|
||||
protected Map<String, SpongeRootCommand> registeredCommands = new HashMap<>();
|
||||
@@ -126,7 +126,7 @@ public class SpongeCommandManager extends CommandManager<CommandSource, TextColo
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandIssuer getCommandIssuer(Object issuer) {
|
||||
public SpongeCommandIssuer getCommandIssuer(Object issuer) {
|
||||
if (!(issuer instanceof CommandSource)) {
|
||||
throw new IllegalArgumentException(issuer.getClass().getName() + " is not a Command Issuer.");
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user