diff --git a/.factorypath b/.factorypath new file mode 100644 index 0000000..610dea5 --- /dev/null +++ b/.factorypath @@ -0,0 +1,2 @@ + + diff --git a/.gitignore b/.gitignore index 1917a1a..84dff78 100644 --- a/.gitignore +++ b/.gitignore @@ -224,3 +224,4 @@ TheButtonAutoFlair/out/artifacts/Autoflair/Autoflair.jar *.xml TBMC/ +/.apt_generated/ diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs index 672496e..edf183d 100644 --- a/.settings/org.eclipse.jdt.core.prefs +++ b/.settings/org.eclipse.jdt.core.prefs @@ -1,12 +1,14 @@ -eclipse.preferences.version=1 -org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled -org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 -org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve -org.eclipse.jdt.core.compiler.compliance=1.8 -org.eclipse.jdt.core.compiler.debug.lineNumber=generate -org.eclipse.jdt.core.compiler.debug.localVariable=generate -org.eclipse.jdt.core.compiler.debug.sourceFile=generate -org.eclipse.jdt.core.compiler.problem.assertIdentifier=error -org.eclipse.jdt.core.compiler.problem.enumIdentifier=error -org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning -org.eclipse.jdt.core.compiler.source=1.8 +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.methodParameters=do not generate +org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=1.8 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.processAnnotations=enabled +org.eclipse.jdt.core.compiler.source=1.8 diff --git a/META-INF/services/javax.annotation.processing.Processor b/META-INF/services/javax.annotation.processing.Processor new file mode 100644 index 0000000..c52a464 --- /dev/null +++ b/META-INF/services/javax.annotation.processing.Processor @@ -0,0 +1 @@ +buttondevteam.lib.AnnotationProcessor \ No newline at end of file diff --git a/pom.xml b/pom.xml index 8609c02..ba1b5fd 100644 --- a/pom.xml +++ b/pom.xml @@ -9,12 +9,6 @@ src/main/java - - src - - **/*.java - - src/main/resources @@ -34,6 +28,7 @@ 1.8 1.8 + -proc:none @@ -76,7 +71,7 @@ target - resources + src/main/resources @@ -114,7 +109,7 @@ - org.apache.commons + commons-io commons-io 1.3.2 provided diff --git a/src/main/java/buttondevteam/core/CommandCaller.java b/src/main/java/buttondevteam/core/CommandCaller.java index cb1b050..2b27f9e 100644 --- a/src/main/java/buttondevteam/core/CommandCaller.java +++ b/src/main/java/buttondevteam/core/CommandCaller.java @@ -5,43 +5,35 @@ import org.bukkit.command.Command; import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandSender; import org.bukkit.command.PluginCommand; -import org.bukkit.entity.Player; import org.bukkit.plugin.java.JavaPlugin; import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.chat.TBMCCommandBase; public class CommandCaller implements CommandExecutor { - private static final String REGISTER_ERROR_MSG = "An error occured while registering commands"; - private CommandCaller() { } private static CommandCaller instance; - public static void RegisterCommand(TBMCCommandBase cmd) { + public static void RegisterCommand(TBMCCommandBase cmd) throws Exception { if (instance == null) instance = new CommandCaller(); - if (cmd.GetCommandPath() == null) { - TBMCCoreAPI.SendException(REGISTER_ERROR_MSG, - new Exception("Command " + cmd.getClass().getSimpleName() + " has no command path!")); - return; - } - if (cmd.getPlugin() == null) { - TBMCCoreAPI.SendException(REGISTER_ERROR_MSG, - new Exception("Command " + cmd.GetCommandPath() + " has no plugin!")); - return; - } + String topcmd = cmd.GetCommandPath(); + if (topcmd == null) + throw new Exception("Command " + cmd.getClass().getSimpleName() + " has no command path!"); + if (cmd.getPlugin() == null) + throw new Exception("Command " + cmd.GetCommandPath() + " has no plugin!"); int i; - String topcmd; - if ((i = (topcmd = cmd.GetCommandPath()).indexOf(' ')) != -1) // Get top-level command - topcmd = cmd.GetCommandPath().substring(0, i); + if ((i = topcmd.indexOf(' ')) != -1) // Get top-level command + topcmd = topcmd.substring(0, i); { PluginCommand pc = ((JavaPlugin) cmd.getPlugin()).getCommand(topcmd); if (pc == null) - TBMCCoreAPI.SendException(REGISTER_ERROR_MSG, new Exception("Top level command " + topcmd - + " not registered in plugin.yml for plugin: " + cmd.getPlugin().getName())); + throw new Exception("Top level command " + topcmd + " not registered in plugin.yml for plugin: " + + cmd.getPlugin().getName()); else pc.setExecutor(instance); } @@ -69,14 +61,11 @@ public class CommandCaller implements CommandExecutor { } return true; } - if (cmd.GetModOnly() && !MainPlugin.permission.has(sender, "tbmc.admin")) { + if (cmd.getClass().getAnnotation(CommandClass.class).modOnly() + && !MainPlugin.permission.has(sender, "tbmc.admin")) { sender.sendMessage("§cYou need to be a mod to use this command."); return true; } - if (cmd.GetPlayerOnly() && !(sender instanceof Player)) { - sender.sendMessage("§cOnly ingame players can use this command."); - return true; - } final String[] cmdargs = args.length > 0 ? Arrays.copyOfRange(args, args.length - argc, args.length) : args; try { if (!cmd.OnCommand(sender, alias, cmdargs)) { diff --git a/src/main/java/buttondevteam/core/MainPlugin.java b/src/main/java/buttondevteam/core/MainPlugin.java index 9d48e81..b8ae2da 100644 --- a/src/main/java/buttondevteam/core/MainPlugin.java +++ b/src/main/java/buttondevteam/core/MainPlugin.java @@ -1,22 +1,16 @@ package buttondevteam.core; -import java.io.File; -import java.io.IOException; -import java.util.Arrays; -import java.util.List; import java.util.logging.Logger; -import org.bukkit.Bukkit; -import org.bukkit.configuration.ConfigurationSection; -import org.bukkit.configuration.InvalidConfigurationException; -import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.entity.Player; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; - import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.chat.Channel; +import buttondevteam.lib.chat.Color; import buttondevteam.lib.chat.TBMCChatAPI; -import buttondevteam.lib.player.ChromaGamerBase; +import buttondevteam.lib.chat.Channel.RecipientTestResult; import buttondevteam.lib.player.TBMCPlayerBase; import net.milkbowl.vault.permission.Permission; @@ -27,7 +21,6 @@ public class MainPlugin extends JavaPlugin { private PluginDescriptionFile pdfFile; private Logger logger; - private int C = 0, keep = 0; @Override public void onEnable() { @@ -42,46 +35,15 @@ public class MainPlugin extends JavaPlugin { TBMCChatAPI.AddCommand(this, ScheduledRestartCommand.class); TBMCCoreAPI.RegisterEventsForExceptions(new PlayerListener(), this); TBMCCoreAPI.RegisterUserClass(TBMCPlayerBase.class); - logger.info(pdfFile.getName() + " has been Enabled (V." + pdfFile.getVersion() + ")."); - Arrays.stream(new File(ChromaGamerBase.TBMC_PLAYERS_DIR).listFiles(f -> !f.isDirectory())).map(f -> { - YamlConfiguration yc = new YamlConfiguration(); - try { - yc.load(f); - } catch (IOException | InvalidConfigurationException e) { - TBMCCoreAPI.SendException("Error while converting player data!", e); - } - f.delete(); - return yc; - }).forEach(yc -> { - try { - int flairtime = yc.getInt("flairtime"), fcount = yc.getInt("fcount"), fdeaths = yc.getInt("fdeaths"); - String flairstate = yc.getString("flairstate"); - List usernames = yc.getStringList("usernames"); - boolean flaircheater = yc.getBoolean("flaircheater"); - final String uuid = yc.getString("uuid"); - C++; - if ((fcount == 0 || fdeaths == 0) - && (flairstate == null || "NoComment".equals(flairstate) || flairtime <= 0)) - return; // Those who received no Fs yet will also get their stats reset if no flair - final File file = new File(ChromaGamerBase.TBMC_PLAYERS_DIR + "minecraft", uuid + ".yml"); - YamlConfiguration targetyc = YamlConfiguration.loadConfiguration(file); - targetyc.set("PlayerName", yc.getString("playername")); - targetyc.set("minecraft_id", uuid); - ConfigurationSection bc = targetyc.createSection("ButtonChat"); - bc.set("FlairTime", "NoComment".equals(flairstate) ? -3 : flairtime); // FlairTimeNone: -3 - bc.set("FCount", fcount); - bc.set("FDeaths", fdeaths); - bc.set("FlairState", flairstate); - bc.set("UserNames", usernames); - bc.set("FlairCheater", flaircheater); - targetyc.save(file); - keep++; - } catch (Exception e) { - TBMCCoreAPI.SendException("Error while converting player data!", e); - } - }); - Bukkit.getScheduler().runTask(this, () -> logger.info("Converted " + keep + " player data from " + C)); - //TODO: Remove once ran it at least once + TBMCChatAPI.RegisterChatChannel(Channel.GlobalChat = new Channel("§fg§f", Color.White, "g", null)); + TBMCChatAPI.RegisterChatChannel( + Channel.AdminChat = new Channel("§cADMIN§f", Color.Red, "a", s -> s.isOp() ? new RecipientTestResult(0) + : new RecipientTestResult("You need to be an admin to use this channel."))); + TBMCChatAPI.RegisterChatChannel(Channel.ModChat = new Channel("§9MOD§f", Color.Blue, "mod", + s -> s.isOp() || (s instanceof Player && MainPlugin.permission.playerInGroup((Player) s, "mod")) + ? new RecipientTestResult(0) // + : new RecipientTestResult("You need to be a mod to use this channel."))); + logger.info(pdfFile.getName() + " has been Enabled (V." + pdfFile.getVersion() + ") Test: " + Test + "."); } @Override diff --git a/src/main/java/buttondevteam/core/ScheduledRestartCommand.java b/src/main/java/buttondevteam/core/ScheduledRestartCommand.java index 77bc185..b80c2de 100644 --- a/src/main/java/buttondevteam/core/ScheduledRestartCommand.java +++ b/src/main/java/buttondevteam/core/ScheduledRestartCommand.java @@ -9,8 +9,10 @@ import org.bukkit.command.CommandSender; import org.bukkit.scheduler.BukkitTask; import buttondevteam.lib.ScheduledServerRestartEvent; +import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.TBMCCommandBase; +@CommandClass(modOnly = true, path = "schrestart") public class ScheduledRestartCommand extends TBMCCommandBase { private static volatile int restartcounter; private static volatile BukkitTask restarttask; @@ -32,11 +34,7 @@ public class ScheduledRestartCommand extends TBMCCommandBase { restartbar = Bukkit.createBossBar("Server restart in " + ticks / 20f, BarColor.RED, BarStyle.SOLID, BarFlag.DARKEN_SKY); restartbar.setProgress(1); - // System.out.println("Progress: " + restartbar.getProgress()); Bukkit.getOnlinePlayers().stream().forEach(p -> restartbar.addPlayer(p)); - /* - * System.out.println( "Players: " + restartbar.getPlayers().stream().map(p -> p.getName()).collect(Collectors.joining(", "))); - */ sender.sendMessage("Scheduled restart in " + ticks / 20f); ScheduledServerRestartEvent e = new ScheduledServerRestartEvent(ticks); Bukkit.getPluginManager().callEvent(e); @@ -50,9 +48,6 @@ public class ScheduledRestartCommand extends TBMCCommandBase { Bukkit.broadcastMessage("§c-- The server is restarting in " + restartcounter / 20 + " seconds!"); restartbar.setProgress(restartcounter / (double) restarttime); restartbar.setTitle(String.format("Server restart in %f.2", restartcounter / 20f)); - /* - * if (restartcounter % 20 == 0) System.out.println("Progress: " + restartbar.getProgress()); - */ restartcounter--; }, 1, 1); return true; @@ -66,19 +61,4 @@ public class ScheduledRestartCommand extends TBMCCommandBase { "You can optionally set the amount of ticks to wait before the restart." // }; } - - @Override - public boolean GetPlayerOnly() { - return false; - } - - @Override - public boolean GetModOnly() { - return true; - } - - @Override - public String GetCommandPath() { - return "schrestart"; - } } diff --git a/src/main/java/buttondevteam/core/TestPrepare.java b/src/main/java/buttondevteam/core/TestPrepare.java index e135a6a..3b6c837 100644 --- a/src/main/java/buttondevteam/core/TestPrepare.java +++ b/src/main/java/buttondevteam/core/TestPrepare.java @@ -11,14 +11,16 @@ import org.mockito.Mockito; import org.mockito.invocation.InvocationOnMock; import org.mockito.stubbing.Answer; +import buttondevteam.lib.chat.Channel; +import buttondevteam.lib.chat.Color; +import buttondevteam.lib.chat.TBMCChatAPI; + public class TestPrepare { public static void PrepareServer() { Bukkit.setServer(Mockito.mock(Server.class, new Answer() { @Override public Object answer(InvocationOnMock invocation) throws Throwable { - // System.out.println("Return type: " + invocation.getMethod().getReturnType()); - // System.out.println(String.class.isAssignableFrom(invocation.getMethod().getReturnType())); if (returns(invocation, String.class)) return "test"; if (returns(invocation, Logger.class)) @@ -34,5 +36,6 @@ public class TestPrepare { return cl.isAssignableFrom(invocation.getMethod().getReturnType()); } })); + TBMCChatAPI.RegisterChatChannel(Channel.GlobalChat = new Channel("§fg§f", Color.White, "g", null)); } } diff --git a/src/main/java/buttondevteam/core/UpdatePluginCommand.java b/src/main/java/buttondevteam/core/UpdatePluginCommand.java index dab077e..06f5787 100644 --- a/src/main/java/buttondevteam/core/UpdatePluginCommand.java +++ b/src/main/java/buttondevteam/core/UpdatePluginCommand.java @@ -4,8 +4,10 @@ import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.chat.CommandClass; import buttondevteam.lib.chat.TBMCCommandBase; +@CommandClass(modOnly = true) public class UpdatePluginCommand extends TBMCCommandBase { @Override public boolean OnCommand(CommandSender sender, String alias, String[] args) { @@ -37,14 +39,4 @@ public class UpdatePluginCommand extends TBMCCommandBase { "To list the plugin names: /" + alias // }; } - - @Override - public boolean GetPlayerOnly() { - return false; - } - - @Override - public boolean GetModOnly() { - return true; - } } diff --git a/src/main/java/buttondevteam/lib/AnnotationProcessor.java b/src/main/java/buttondevteam/lib/AnnotationProcessor.java new file mode 100644 index 0000000..8e763ec --- /dev/null +++ b/src/main/java/buttondevteam/lib/AnnotationProcessor.java @@ -0,0 +1,33 @@ +package buttondevteam.lib; + +import java.util.List; +import java.util.Set; +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.RoundEnvironment; +import javax.annotation.processing.SupportedSourceVersion; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.AnnotationMirror; +import javax.lang.model.element.Element; +import javax.lang.model.element.TypeElement; + +import buttondevteam.lib.player.ChromaGamerEnforcer; + +/** * A simple session bean type annotation processor. The implementation * is based on the standard annotation processing API in Java 6. */ +@SupportedSourceVersion(SourceVersion.RELEASE_8) +public class AnnotationProcessor extends AbstractProcessor { + /** * Check if both @Stateful and @Stateless are present in an * session bean. If so, emits a warning message. */ + @Override + public boolean process(Set typeElements, RoundEnvironment roundEnv) { // TODO: SEparate JAR + Set elements = roundEnv.getElementsAnnotatedWith(ChromaGamerEnforcer.class); + for (Element element : elements) { + System.out.println("Processing " + element); + List annotationMirrors = element.getAnnotationMirrors(); + System.out.println("Annotations: " + annotationMirrors); + for (AnnotationMirror annotation : annotationMirrors) { + String type = annotation.getAnnotationType().toString(); + System.out.println("Type: " + type); + } + } + return true; // claim the annotations + } +} diff --git a/src/main/java/buttondevteam/lib/TBMCChatEvent.java b/src/main/java/buttondevteam/lib/TBMCChatEvent.java index 21c84b3..192b7c1 100644 --- a/src/main/java/buttondevteam/lib/TBMCChatEvent.java +++ b/src/main/java/buttondevteam/lib/TBMCChatEvent.java @@ -6,7 +6,14 @@ import org.bukkit.event.Event; import org.bukkit.event.HandlerList; import buttondevteam.lib.chat.Channel; +import buttondevteam.lib.chat.Channel.RecipientTestResult; +/** + * Make sure to only send the message to users who {@link #shouldSendTo(CommandSender)} returns true. + * + * @author NorbiPeti + * + */ public class TBMCChatEvent extends Event implements Cancellable { private static final HandlerList handlers = new HandlerList(); @@ -14,11 +21,13 @@ public class TBMCChatEvent extends Event implements Cancellable { private CommandSender sender; private String message; private boolean cancelled; + private int score; - public TBMCChatEvent(CommandSender sender, Channel channel, String message) { + public TBMCChatEvent(CommandSender sender, Channel channel, String message, int score) { this.sender = sender; this.channel = channel; this.message = message; // TODO: Message object with data? + this.score = score; } /* @@ -56,4 +65,23 @@ public class TBMCChatEvent extends Event implements Cancellable { this.cancelled = cancelled; } + /** + * Note: Errors are sent to the sender automatically + */ + public boolean shouldSendTo(CommandSender sender) { + if (channel.filteranderrormsg == null) + return true; + RecipientTestResult result = channel.filteranderrormsg.apply(sender); + return result.errormessage == null && score == result.score; + } + + /** + * Note: Errors are sent to the sender automatically + */ + public int getMCScore(CommandSender sender) { + if (channel.filteranderrormsg == null) + return 0; + RecipientTestResult result = channel.filteranderrormsg.apply(sender); + return result.errormessage == null ? result.score : -1; + } } diff --git a/src/main/java/buttondevteam/lib/TBMCChatPreprocessEvent.java b/src/main/java/buttondevteam/lib/TBMCChatPreprocessEvent.java new file mode 100644 index 0000000..8f8f9d2 --- /dev/null +++ b/src/main/java/buttondevteam/lib/TBMCChatPreprocessEvent.java @@ -0,0 +1,68 @@ +package buttondevteam.lib; + +import org.bukkit.command.CommandSender; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +import buttondevteam.lib.chat.Channel; + +/** + * Can be used to change messages before it's sent. + * + * @author NorbiPeti + * + */ +public class TBMCChatPreprocessEvent extends Event implements Cancellable { + private static final HandlerList handlers = new HandlerList(); + + private Channel channel; + private CommandSender sender; + private String message; + private boolean cancelled; + + public TBMCChatPreprocessEvent(CommandSender sender, Channel channel, String message) { + this.sender = sender; + this.channel = channel; + this.message = message; // TODO: Message object with data? + } + + /* + * public TBMCPlayer getPlayer() { return TBMCPlayer.getPlayer(sender); // TODO: Get Chroma user } + */ + + public Channel getChannel() { + return channel; + } + + public CommandSender getSender() { + return sender; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancelled) { + this.cancelled = cancelled; + } +} diff --git a/src/main/java/buttondevteam/lib/TBMCCoreAPI.java b/src/main/java/buttondevteam/lib/TBMCCoreAPI.java index 469095e..a80923c 100644 --- a/src/main/java/buttondevteam/lib/TBMCCoreAPI.java +++ b/src/main/java/buttondevteam/lib/TBMCCoreAPI.java @@ -201,8 +201,10 @@ public class TBMCCoreAPI { SendUnsentExceptions(); TBMCExceptionEvent event = new TBMCExceptionEvent(sourcemsg, e); Bukkit.getPluginManager().callEvent(event); - if (!event.isHandled()) - exceptionsToSend.put(sourcemsg, e); + synchronized (exceptionsToSend) { + if (!event.isHandled()) + exceptionsToSend.put(sourcemsg, e); + } Bukkit.getLogger().warning(sourcemsg); e.printStackTrace(); if (debugPotato) { @@ -212,7 +214,6 @@ public class TBMCCoreAPI { devsOnline.add(player); } } - ; if (!devsOnline.isEmpty()) { DebugPotato potato = new DebugPotato() .setMessage(new String[] { // @@ -234,8 +235,10 @@ public class TBMCCoreAPI { SendUnsentDebugMessages(); TBMCDebugMessageEvent event = new TBMCDebugMessageEvent(debugMessage); Bukkit.getPluginManager().callEvent(event); - if (!event.isSent()) - debugMessagesToSend.add(debugMessage); + synchronized (debugMessagesToSend) { + if (!event.isSent()) + debugMessagesToSend.add(debugMessage); + } } /** @@ -258,28 +261,32 @@ public class TBMCCoreAPI { * Send exceptions that haven't been sent (their events didn't get handled). This method is used by the DiscordPlugin's ready event */ public static void SendUnsentExceptions() { - if (exceptionsToSend.size() > 20) { - exceptionsToSend.clear(); // Don't call more and more events if all the handler plugins are unloaded - Bukkit.getLogger().warning("Unhandled exception list is over 20! Clearing!"); - } - for (Entry entry : exceptionsToSend.entrySet()) { - TBMCExceptionEvent event = new TBMCExceptionEvent(entry.getKey(), entry.getValue()); - Bukkit.getPluginManager().callEvent(event); - if (event.isHandled()) - exceptionsToSend.remove(entry.getKey()); + synchronized (exceptionsToSend) { + if (exceptionsToSend.size() > 20) { + exceptionsToSend.clear(); // Don't call more and more events if all the handler plugins are unloaded + Bukkit.getLogger().warning("Unhandled exception list is over 20! Clearing!"); + } + for (Entry entry : exceptionsToSend.entrySet()) { + TBMCExceptionEvent event = new TBMCExceptionEvent(entry.getKey(), entry.getValue()); + Bukkit.getPluginManager().callEvent(event); + if (event.isHandled()) + exceptionsToSend.remove(entry.getKey()); + } } } public static void SendUnsentDebugMessages() { - if (debugMessagesToSend.size() > 20) { - debugMessagesToSend.clear(); // Don't call more and more DebugMessages if all the handler plugins are unloaded - Bukkit.getLogger().warning("Unhandled Debug Message list is over 20! Clearing!"); - } - for (String message : debugMessagesToSend) { - TBMCDebugMessageEvent event = new TBMCDebugMessageEvent(message); - Bukkit.getPluginManager().callEvent(event); - if (event.isSent()) - debugMessagesToSend.remove(message); + synchronized (debugMessagesToSend) { + if (debugMessagesToSend.size() > 20) { + debugMessagesToSend.clear(); // Don't call more and more DebugMessages if all the handler plugins are unloaded + Bukkit.getLogger().warning("Unhandled Debug Message list is over 20! Clearing!"); + } + for (String message : debugMessagesToSend) { + TBMCDebugMessageEvent event = new TBMCDebugMessageEvent(message); + Bukkit.getPluginManager().callEvent(event); + if (event.isSent()) + debugMessagesToSend.remove(message); + } } } diff --git a/src/main/java/buttondevteam/lib/chat/Channel.java b/src/main/java/buttondevteam/lib/chat/Channel.java index 5a257a4..cfb86a7 100644 --- a/src/main/java/buttondevteam/lib/chat/Channel.java +++ b/src/main/java/buttondevteam/lib/chat/Channel.java @@ -2,26 +2,41 @@ package buttondevteam.lib.chat; import java.util.ArrayList; import java.util.List; +import java.util.function.Function; + +import org.bukkit.Bukkit; +import org.bukkit.command.CommandSender; public class Channel { public final String DisplayName; public final Color color; - public final String Command; + public final String ID; + /** + * Filters both the sender and the targets + */ + public final Function filteranderrormsg; private static List channels = new ArrayList<>(); - public Channel(String displayname, Color color, String command) { + /** + * Creates a channel. + * + * @param displayname + * The name that should appear at the start of the message + * @param color + * The default color of the messages sent in the channel + * @param command + * The command to be used for the channel without /. For example "mod". It's also used for scoreboard objective names. + * @param filteranderrormsg + * Checks all senders against the criteria provided here and sends the message if the index matches the sender's - if no score at all, displays the error.
+ * May be null to send to everyone. + */ + public Channel(String displayname, Color color, String command, + Function filteranderrormsg) { DisplayName = displayname; this.color = color; - Command = command; - } - - static { - channels.add(GlobalChat = new Channel("§fg§f", Color.White, "g")); - channels.add(TownChat = new Channel("§3TC§f", Color.DarkAqua, "tc")); - channels.add(NationChat = new Channel("§6NC§f", Color.Gold, "nc")); - channels.add(AdminChat = new Channel("§cADMIN§f", Color.Red, "a")); - channels.add(ModChat = new Channel("§9MOD§f", Color.Blue, "mod")); + ID = command; + this.filteranderrormsg = filteranderrormsg; } public static List getChannels() { @@ -29,8 +44,36 @@ public class Channel { } public static Channel GlobalChat; - public static Channel TownChat; - public static Channel NationChat; public static Channel AdminChat; public static Channel ModChat; + + static void RegisterChannel(Channel channel) { + channels.add(channel); + Bukkit.getPluginManager().callEvent(new ChatChannelRegisterEvent(channel)); + } + + public static class RecipientTestResult { + public String errormessage; + public int score; + + /** + * Creates a result that indicates an error + * + * @param errormessage + * The error message to show the sender if they don't meet the criteria. + */ + public RecipientTestResult(String errormessage) { + this.errormessage = errormessage; + } + + /** + * Creates a result that indicates a success + * + * @param score + * The score that identifies the target group. For example, the index of the town or nation to send to. + */ + public RecipientTestResult(int score) { + this.score = score; + } + } } diff --git a/src/main/java/buttondevteam/lib/chat/ChatChannelRegisterEvent.java b/src/main/java/buttondevteam/lib/chat/ChatChannelRegisterEvent.java new file mode 100644 index 0000000..d10c54b --- /dev/null +++ b/src/main/java/buttondevteam/lib/chat/ChatChannelRegisterEvent.java @@ -0,0 +1,27 @@ +package buttondevteam.lib.chat; + +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; + +public class ChatChannelRegisterEvent extends Event { + private static final HandlerList handlers = new HandlerList(); + + private Channel channel; + + public ChatChannelRegisterEvent(Channel channel) { + this.channel = channel; + } + + public Channel getChannel() { + return channel; + } + + @Override + public HandlerList getHandlers() { + return handlers; + } + + public static HandlerList getHandlerList() { + return handlers; + } +} diff --git a/src/main/java/buttondevteam/lib/chat/CommandClass.java b/src/main/java/buttondevteam/lib/chat/CommandClass.java new file mode 100644 index 0000000..1a49d87 --- /dev/null +++ b/src/main/java/buttondevteam/lib/chat/CommandClass.java @@ -0,0 +1,41 @@ +package buttondevteam.lib.chat; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Abstract classes with no {@link CommandClass} annotations will be ignored. Classes that are not abstract or have the annotation will be included in the command path unless + * {@link #excludeFromPath()} is true. + * + * @author NorbiPeti + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface CommandClass { + /** + * Determines whether the command can only be used by mods and above or regular players can use it as well. + * + * @return If the command is mod only + */ + public boolean modOnly(); + + /** + * The command's path, or name if top-level command.
+ * For example:
+ * "u admin updateplugin" or "u" for the top level one
+ * The path must be lowercase!
+ * + * @return The command path, which is the command class name by default (removing any "command" from it) + */ + public String path() default ""; + + /** + * Exclude this class from the path. Useful if more commands share some property but aren't subcommands of a common command. See {@link CommandClass} for more details. + */ + public boolean excludeFromPath() default false; +} diff --git a/src/main/java/buttondevteam/lib/chat/Format.java b/src/main/java/buttondevteam/lib/chat/Format.java deleted file mode 100644 index b13d45b..0000000 --- a/src/main/java/buttondevteam/lib/chat/Format.java +++ /dev/null @@ -1,24 +0,0 @@ -package buttondevteam.lib.chat; - -public enum Format implements TellrawSerializableEnum { - Bold("bold"), Underlined("underlined"), Italic("italic"), Strikethrough("strikethrough"), Obfuscated( - "obfuscated"); - // TODO: Add format codes to /u c - private String name; - - Format(String name) { - this.name = name; - this.flag = 1 << this.ordinal(); - } - - @Override - public String getName() { - return name; - } - - private final int flag; - - public int getFlag() { - return flag; - } -} \ No newline at end of file diff --git a/src/main/java/buttondevteam/lib/chat/OptionallyPlayerCommandBase.java b/src/main/java/buttondevteam/lib/chat/OptionallyPlayerCommandBase.java new file mode 100644 index 0000000..889cf76 --- /dev/null +++ b/src/main/java/buttondevteam/lib/chat/OptionallyPlayerCommandBase.java @@ -0,0 +1,30 @@ +package buttondevteam.lib.chat; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import buttondevteam.lib.TBMCCoreAPI; + +public abstract class OptionallyPlayerCommandBase extends TBMCCommandBase { + public boolean OnCommand(Player player, String alias, String[] args) { + if (getClass().isAnnotationPresent(OptionallyPlayerCommandClass.class) + && getClass().getAnnotation(OptionallyPlayerCommandClass.class).playerOnly()) + TBMCCoreAPI.SendException("Error while executing command " + getClass().getSimpleName() + "!", + new Exception( + "The PlayerCommand annotation is present and set to playerOnly but the Player overload isn't overriden!")); + return true; + } + + @Override + public boolean OnCommand(CommandSender sender, String alias, String[] args) { + if (sender instanceof Player) + return OnCommand((Player) sender, alias, args); + if (!getClass().isAnnotationPresent(OptionallyPlayerCommandClass.class) + || !getClass().getAnnotation(OptionallyPlayerCommandClass.class).playerOnly()) + TBMCCoreAPI.SendException("Error while executing command " + getClass().getSimpleName() + "!", + new Exception( + "Command class doesn't override the CommandSender overload and no PlayerCommandClass annotation is present or playerOnly is false!")); + sender.sendMessage("§cYou need to be a player to use this command."); + return true; + } +} diff --git a/src/main/java/buttondevteam/lib/chat/OptionallyPlayerCommandClass.java b/src/main/java/buttondevteam/lib/chat/OptionallyPlayerCommandClass.java new file mode 100644 index 0000000..6cfd977 --- /dev/null +++ b/src/main/java/buttondevteam/lib/chat/OptionallyPlayerCommandClass.java @@ -0,0 +1,20 @@ +package buttondevteam.lib.chat; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Only needed to use with {@link OptionallyPlayerCommandBase} command classes + * + * @author NorbiPeti + * + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.TYPE) +@Inherited +public @interface OptionallyPlayerCommandClass { + public boolean playerOnly(); +} diff --git a/src/main/java/buttondevteam/lib/chat/PlayerCommandBase.java b/src/main/java/buttondevteam/lib/chat/PlayerCommandBase.java new file mode 100644 index 0000000..3b30ff5 --- /dev/null +++ b/src/main/java/buttondevteam/lib/chat/PlayerCommandBase.java @@ -0,0 +1,16 @@ +package buttondevteam.lib.chat; + +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public abstract class PlayerCommandBase extends TBMCCommandBase { + public abstract boolean OnCommand(Player player, String alias, String[] args); + + @Override + public final boolean OnCommand(CommandSender sender, String alias, String[] args) { + if (sender instanceof Player) + return OnCommand((Player) sender, alias, args); + sender.sendMessage("§cYou need to be a player to use this command."); + return true; + } +} diff --git a/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java b/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java index b6317ee..943de8d 100644 --- a/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java +++ b/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java @@ -4,6 +4,7 @@ import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; +import java.util.Map.Entry; import java.util.Set; import java.util.function.Consumer; @@ -19,7 +20,9 @@ import org.reflections.util.ConfigurationBuilder; import buttondevteam.core.CommandCaller; import buttondevteam.core.MainPlugin; import buttondevteam.lib.TBMCChatEvent; +import buttondevteam.lib.TBMCChatPreprocessEvent; import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.chat.Channel.RecipientTestResult; public class TBMCChatAPI { @@ -59,19 +62,20 @@ public class TBMCChatAPI { cmds.add("§6---- Subcommands ----"); cmds.add(cmd); }; - for (TBMCCommandBase cmd : TBMCChatAPI.GetCommands().values()) { - if (cmd.GetCommandPath().startsWith(command + " ")) { - if (cmd.GetPlayerOnly() && !(sender instanceof Player)) + for (Entry cmd : TBMCChatAPI.GetCommands().entrySet()) { + if (cmd.getKey().startsWith(command + " ")) { + if (cmd.getValue().isPlayerOnly() && !(sender instanceof Player)) continue; - if (cmd.GetModOnly() && !MainPlugin.permission.has(sender, "tbmc.admin")) + if (cmd.getClass().getAnnotation(CommandClass.class).modOnly() + && !MainPlugin.permission.has(sender, "tbmc.admin")) continue; - int ind = cmd.GetCommandPath().indexOf(' ', command.length() + 2); + int ind = cmd.getKey().indexOf(' ', command.length() + 2); if (ind >= 0) { - String newcmd = cmd.GetCommandPath().substring(0, ind); + String newcmd = cmd.getKey().substring(0, ind); if (!cmds.contains("/" + newcmd)) addToCmds.accept("/" + newcmd); } else - addToCmds.accept("/" + cmd.GetCommandPath()); + addToCmds.accept("/" + cmd.getKey()); } } return cmds.toArray(new String[cmds.size()]); @@ -96,15 +100,21 @@ public class TBMCChatAPI { * @param acmdclass * A command's class to get the package name for commands. The provided class's package and subpackages are scanned for commands. */ - public static void AddCommands(JavaPlugin plugin, Class acmdclass) { - plugin.getLogger().info("Registering commands for " + plugin.getName()); + public static synchronized void AddCommands(JavaPlugin plugin, Class acmdclass) { + plugin.getLogger().info("Registering commands from " + acmdclass.getPackage().getName()); Reflections rf = new Reflections(new ConfigurationBuilder() .setUrls(ClasspathHelper.forPackage(acmdclass.getPackage().getName(), plugin.getClass().getClassLoader())) + .addUrls( + ClasspathHelper.forClass(OptionallyPlayerCommandBase.class, + OptionallyPlayerCommandBase.class.getClassLoader()), + ClasspathHelper.forClass(PlayerCommandBase.class, PlayerCommandBase.class.getClassLoader())) // http://stackoverflow.com/questions/12917417/using-reflections-for-finding-the-transitive-subtypes-of-a-class-when-not-all .addClassLoader(plugin.getClass().getClassLoader()).addScanners(new SubTypesScanner())); Set> cmds = rf.getSubTypesOf(TBMCCommandBase.class); for (Class cmd : cmds) { try { + if (!cmd.getPackage().getName().startsWith(acmdclass.getPackage().getName())) + continue; // It keeps including the commands from here if (Modifier.isAbstract(cmd.getModifiers())) continue; TBMCCommandBase c = cmd.newInstance(); @@ -113,9 +123,7 @@ public class TBMCChatAPI { continue; commands.put(c.GetCommandPath(), c); CommandCaller.RegisterCommand(c); - } catch (InstantiationException e) { - TBMCCoreAPI.SendException("An error occured while registering command " + cmd.getName(), e); - } catch (IllegalAccessException e) { + } catch (Exception e) { TBMCCoreAPI.SendException("An error occured while registering command " + cmd.getName(), e); } } @@ -199,7 +207,7 @@ public class TBMCChatAPI { } /** - * Sends a chat message to Minecraft + * Sends a chat message to Minecraft. Make sure that the channel is registered with {@link #RegisterChatChannel(Channel)}. * * @param channel * The channel to send to @@ -210,8 +218,35 @@ public class TBMCChatAPI { * @return The event cancelled state */ public static boolean SendChatMessage(Channel channel, CommandSender sender, String message) { - TBMCChatEvent event = new TBMCChatEvent(sender, channel, message); + if (!Channel.getChannels().contains(channel)) + throw new RuntimeException("Channel " + channel.DisplayName + " not registered!"); + TBMCChatPreprocessEvent eventPre = new TBMCChatPreprocessEvent(sender, channel, message); + Bukkit.getPluginManager().callEvent(eventPre); + if (eventPre.isCancelled()) + return true; + int score; + if (channel.filteranderrormsg == null) + score = -1; + else { + RecipientTestResult result = channel.filteranderrormsg.apply(sender); + if (result.errormessage != null) { + sender.sendMessage("§c" + result.errormessage); + return true; + } + score = result.score; + } + TBMCChatEvent event = new TBMCChatEvent(sender, channel, eventPre.getMessage(), score); Bukkit.getPluginManager().callEvent(event); return event.isCancelled(); } + + /** + * Register a chat channel. See {@link Channel#Channel(String, Color, String, java.util.function.Function)} for details. + * + * @param channel + * A new {@link Channel} to register + */ + public static void RegisterChatChannel(Channel channel) { + Channel.RegisterChannel(channel); + } } diff --git a/src/main/java/buttondevteam/lib/chat/TBMCCommandBase.java b/src/main/java/buttondevteam/lib/chat/TBMCCommandBase.java index 111e057..086f15d 100644 --- a/src/main/java/buttondevteam/lib/chat/TBMCCommandBase.java +++ b/src/main/java/buttondevteam/lib/chat/TBMCCommandBase.java @@ -1,11 +1,15 @@ package buttondevteam.lib.chat; +import java.util.function.Function; + import org.bukkit.command.CommandSender; import org.bukkit.plugin.Plugin; +import javassist.Modifier; + /** * Extend this class to create new TBMCCommand and use {@link TBMCChatAPI#AddCommand(org.bukkit.plugin.java.JavaPlugin, TBMCCommandBase)} to add it. Note: The command path (command name - * and subcommand arguments) will be the class name by default, removing any "command" from it. To change it (especially for subcommands), override {@link #GetCommandPath()}. + * and subcommand arguments) will be the class name by default, removing any "command" from it. To change it (especially for subcommands), use the path field in the {@link CommandClass} annotation. * * @author Norbi * @@ -13,41 +17,64 @@ import org.bukkit.plugin.Plugin; public abstract class TBMCCommandBase { public TBMCCommandBase() { + path = getcmdpath(); } public abstract boolean OnCommand(CommandSender sender, String alias, String[] args); public abstract String[] GetHelpText(String alias); + private String path = null; + /** * The command's path, or name if top-level command.
* For example:
* "u admin updateplugin" or "u" for the top level one
* The path must be lowercase!
+ * Abstract classes with no {@link CommandClass} annotations will be ignored. * - * @return The command path, which is the command class name by default (removing any "command" from it) + * @return The command path, which is the command class name by default (removing any "command" from it) - Change via the {@link CommandClass} annotation */ - public String GetCommandPath() { - return getClass().getSimpleName().toLowerCase().replace("command", ""); + public final String GetCommandPath() { + return path; } - /** - * Determines whether the command can only be used as a player, or command blocks or the console can use it as well. - * - * @return If the command is player only - */ - public abstract boolean GetPlayerOnly(); - - /** - * Determines whether the command can only be used by mods or regular players can use it as well. - * - * @return If the command is mod only - */ - public abstract boolean GetModOnly(); + private final String getcmdpath() { + if (!getClass().isAnnotationPresent(CommandClass.class)) + throw new RuntimeException( + "No @CommandClass annotation on command class " + getClass().getSimpleName() + "!"); + Function, String> getFromClass = cl -> cl.getSimpleName().toLowerCase().replace("commandbase", "") // <-- ... + .replace("command", ""); + String path = getClass().getAnnotation(CommandClass.class).path(), + prevpath = path = path.length() == 0 ? getFromClass.apply(getClass()) : path; + for (Class cl = getClass().getSuperclass(); cl != null + && !cl.getPackage().getName().equals(TBMCCommandBase.class.getPackage().getName()); cl = cl + .getSuperclass()) { // + String newpath; + if (!cl.isAnnotationPresent(CommandClass.class) + || (newpath = cl.getAnnotation(CommandClass.class).path()).length() == 0 + || newpath.equals(prevpath)) { + if (Modifier.isAbstract(cl.getModifiers()) && (!cl.isAnnotationPresent(CommandClass.class)) + || cl.getAnnotation(CommandClass.class).excludeFromPath()) // <-- + continue; + newpath = getFromClass.apply(cl); + } + path = (prevpath = newpath) + " " + path; + } + return path; + } Plugin plugin; // Used By TBMCChatAPI public final Plugin getPlugin() { // Used by CommandCaller (ButtonChat) return plugin; } + + public final boolean isPlayerOnly() { + return this instanceof PlayerCommandBase ? true + : this instanceof OptionallyPlayerCommandBase + ? getClass().isAnnotationPresent(OptionallyPlayerCommandClass.class) + ? getClass().getAnnotation(OptionallyPlayerCommandClass.class).playerOnly() : true + : false; + } } diff --git a/src/main/java/buttondevteam/lib/player/ChromaGamerBase.java b/src/main/java/buttondevteam/lib/player/ChromaGamerBase.java index 944b676..d22d120 100644 --- a/src/main/java/buttondevteam/lib/player/ChromaGamerBase.java +++ b/src/main/java/buttondevteam/lib/player/ChromaGamerBase.java @@ -9,6 +9,7 @@ import org.bukkit.Bukkit; import org.bukkit.configuration.file.YamlConfiguration; import buttondevteam.lib.TBMCCoreAPI; +@ChromaGamerEnforcer public abstract class ChromaGamerBase implements AutoCloseable { public static final String TBMC_PLAYERS_DIR = "TBMC/players/"; diff --git a/src/main/java/buttondevteam/lib/player/ChromaGamerEnforcer.java b/src/main/java/buttondevteam/lib/player/ChromaGamerEnforcer.java new file mode 100644 index 0000000..35ffdf4 --- /dev/null +++ b/src/main/java/buttondevteam/lib/player/ChromaGamerEnforcer.java @@ -0,0 +1,14 @@ +package buttondevteam.lib.player; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.SOURCE) +@Target(ElementType.TYPE) +@Inherited +public @interface ChromaGamerEnforcer { + +} diff --git a/src/main/java/buttondevteam/lib/player/TBMCPlayerBase.java b/src/main/java/buttondevteam/lib/player/TBMCPlayerBase.java index e4f38c2..c1e3400 100644 --- a/src/main/java/buttondevteam/lib/player/TBMCPlayerBase.java +++ b/src/main/java/buttondevteam/lib/player/TBMCPlayerBase.java @@ -74,17 +74,14 @@ public abstract class TBMCPlayerBase extends ChromaGamerBase { public static T getPlayer(UUID uuid, Class cl) { if (playermap.containsKey(uuid + "-" + cl.getSimpleName())) return (T) playermap.get(uuid + "-" + cl.getSimpleName()); - // System.out.println("A"); try { T player; if (playermap.containsKey(uuid + "-" + TBMCPlayer.class.getSimpleName())) { - // System.out.println("B"); - Don't program when tired player = cl.newInstance(); player.plugindata = playermap.get(uuid + "-" + TBMCPlayer.class.getSimpleName()).plugindata; playermap.put(uuid + "-" + cl.getSimpleName(), player); // It will get removed on player quit } else player = ChromaGamerBase.getUser(uuid.toString(), cl); - // System.out.println("C"); player.uuid = uuid; return player; } catch (Exception e) {