diff --git a/ButtonCore/.gitignore b/.gitignore similarity index 99% rename from ButtonCore/.gitignore rename to .gitignore index 86472d4..8983379 100755 --- a/ButtonCore/.gitignore +++ b/.gitignore @@ -218,7 +218,7 @@ pip-log.txt .mr.developer.cfg .metadata/* TheButtonAutoFlair/out/artifacts/Autoflair/Autoflair.jar -*.iml +#*.iml *.name .idea/compiler.xml *.xml diff --git a/.idea/libraries/Maven__org_spigotmc_spigot_api_1_12_2_R0_1_SNAPSHOT.xml b/.idea/libraries/Maven__org_spigotmc_spigot_api_1_12_2_R0_1_SNAPSHOT.xml index b6f88ae..42f0d6f 100644 --- a/.idea/libraries/Maven__org_spigotmc_spigot_api_1_12_2_R0_1_SNAPSHOT.xml +++ b/.idea/libraries/Maven__org_spigotmc_spigot_api_1_12_2_R0_1_SNAPSHOT.xml @@ -1,13 +1,13 @@ - + - + - + \ No newline at end of file diff --git a/BuildConfigUpdater/BuildConfigUpdater.iml b/BuildConfigUpdater/BuildConfigUpdater.iml index f14440c..274b3de 100644 --- a/BuildConfigUpdater/BuildConfigUpdater.iml +++ b/BuildConfigUpdater/BuildConfigUpdater.iml @@ -12,7 +12,6 @@ - diff --git a/ButtonCore/ButtonCore (1) (com.github.TBMCPlugins.ButtonCore).iml b/ButtonCore/ButtonCore (1) (com.github.TBMCPlugins.ButtonCore).iml new file mode 100644 index 0000000..7c2fa83 --- /dev/null +++ b/ButtonCore/ButtonCore (1) (com.github.TBMCPlugins.ButtonCore).iml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/ButtonCore/pom.xml b/ButtonCore/pom.xml index e044b9a..ed35499 100755 --- a/ButtonCore/pom.xml +++ b/ButtonCore/pom.xml @@ -75,6 +75,7 @@ org.apache.maven.plugins maven-surefire-plugin + 3.0.0-M3 false @@ -83,6 +84,7 @@ org.apache.maven.plugins maven-source-plugin + 3.0.1 attach-sources @@ -103,14 +105,18 @@ jitpack.io https://jitpack.io/ - - vault-repo - http://nexus.hc.to/content/repositories/pub_releases - + ess-repo http://repo.ess3.net/content/repositories/essrel/ + + Votifier + https://dl.bintray.com/nuvotifier/maven/ + @@ -175,6 +181,12 @@ 2.13.1 provided + + com.vexsoftware + nuvotifier-universal + 2.3.4 + provided + TBMCPlugins diff --git a/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java b/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java index 4aaceac..10b4b16 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java +++ b/ButtonCore/src/main/java/buttondevteam/core/ComponentCommand.java @@ -1,9 +1,12 @@ package buttondevteam.core; import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.Component; -import buttondevteam.lib.chat.Command2MC; +import buttondevteam.lib.chat.Command2; +import buttondevteam.lib.chat.Command2.Subcommand; import buttondevteam.lib.chat.CommandClass; +import buttondevteam.lib.chat.ICommand2MC; import lombok.val; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; @@ -12,30 +15,30 @@ import org.bukkit.plugin.Plugin; import java.util.Optional; @CommandClass(modOnly = true, helpText = { - "§6---- Component command ----", + "Component command", "Can be used to enable/disable/list components" }) -public class ComponentCommand extends Command2MC { +public class ComponentCommand extends ICommand2MC { public ComponentCommand() { - addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg)); - + getManager().addParamConverter(Plugin.class, arg -> Bukkit.getPluginManager().getPlugin(arg), "Plugin not found!"); } @Subcommand public boolean enable(CommandSender sender, Plugin plugin, String component) { - if (plugin == null) return respond(sender, "§cPlugin not found!"); - plugin.reloadConfig(); //Reload config so the new config values are read - All changes are saved to disk on disable + if (plugin instanceof ButtonPlugin) + ((ButtonPlugin) plugin).justReload(); + else + plugin.reloadConfig(); //Reload config so the new config values are read - All changes are saved to disk on disable return enable_disable(sender, plugin, component, true); } @Subcommand public boolean disable(CommandSender sender, Plugin plugin, String component) { - if (plugin == null) return respond(sender, "§cPlugin not found!"); return enable_disable(sender, plugin, component, false); } @Subcommand - public boolean list(CommandSender sender, String plugin) { + public boolean list(CommandSender sender, @Command2.OptionalArg String plugin) { sender.sendMessage("§6List of components:"); Component.getComponents().values().stream().filter(c -> plugin == null || c.getPlugin().getName().equalsIgnoreCase(plugin)) //If plugin is null, don't check .map(c -> c.getPlugin().getName() + " - " + c.getClass().getSimpleName() + " - " + (c.isEnabled() ? "en" : "dis") + "abled").forEach(sender::sendMessage); @@ -55,7 +58,7 @@ public class ComponentCommand extends Command2MC { return true; } - private Optional getComponentOrError(Plugin plugin, String arg, CommandSender sender) { + private Optional> getComponentOrError(Plugin plugin, String arg, CommandSender sender) { val oc = Component.getComponents().values().stream() .filter(c -> plugin.getName().equals(c.getPlugin().getName())) .filter(c -> c.getClass().getSimpleName().equalsIgnoreCase(arg)).findAny(); diff --git a/ButtonCore/src/main/java/buttondevteam/core/ComponentManager.java b/ButtonCore/src/main/java/buttondevteam/core/ComponentManager.java index 9a84ddd..db9c2e3 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/ComponentManager.java +++ b/ButtonCore/src/main/java/buttondevteam/core/ComponentManager.java @@ -34,9 +34,10 @@ public final class ComponentManager { /** * Unregister all components of a plugin that are enabled - called on {@link ButtonPlugin} disable */ - public static void unregComponents(ButtonPlugin plugin) { + @SuppressWarnings("unchecked") + public static void unregComponents(T plugin) { while (!plugin.getComponentStack().empty()) //Unregister in reverse order - Component.unregisterComponent(plugin, plugin.getComponentStack().pop()); //Components are pushed on register + Component.unregisterComponent(plugin, (Component) plugin.getComponentStack().pop()); //Components are pushed on register componentsEnabled = false; } diff --git a/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java b/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java index b2200a4..e810c67 100755 --- a/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java +++ b/ButtonCore/src/main/java/buttondevteam/core/MainPlugin.java @@ -9,17 +9,20 @@ import buttondevteam.core.component.restart.RestartComponent; import buttondevteam.core.component.towny.TownyComponent; import buttondevteam.core.component.updater.PluginUpdater; import buttondevteam.core.component.updater.PluginUpdaterComponent; +import buttondevteam.core.component.votifier.VotifierComponent; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.ButtonPlugin; import buttondevteam.lib.architecture.Component; import buttondevteam.lib.architecture.ConfigData; import buttondevteam.lib.chat.Color; -import buttondevteam.lib.chat.Command2MC; import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.player.ChromaGamerBase; import buttondevteam.lib.player.TBMCPlayer; import buttondevteam.lib.player.TBMCPlayerBase; import com.earth2me.essentials.Essentials; +import lombok.Getter; +import lombok.Setter; +import net.milkbowl.vault.economy.Economy; import net.milkbowl.vault.permission.Permission; import org.bukkit.Bukkit; import org.bukkit.command.BlockCommandSender; @@ -42,41 +45,62 @@ import java.util.logging.Logger; public class MainPlugin extends ButtonPlugin { public static MainPlugin Instance; - @Nullable public static Permission permission; public static boolean Test; public static Essentials ess; private Logger logger; + @Nullable + private Economy economy; + /** + * Whether the Core's chat handler should be enabled. + * Other chat plugins handling messages from other platforms should set this to false. + */ + @Getter + @Setter + private boolean chatHandlerEnabled = true; private ConfigData writePluginList() { return getIConfig().getData("writePluginList", false); } + ConfigData chatFormat() { + return getIConfig().getData("chatFormat", "[{origin}|" + + "{channel}] <{name}> {message}"); + } + @Override public void pluginEnable() { // Logs "Plugin Enabled", registers commands Instance = this; PluginDescriptionFile pdf = getDescription(); logger = getLogger(); - setupPermissions(); + if (!setupPermissions()) + throw new NullPointerException("No permission plugin found!"); + if (!setupEconomy()) //Though Essentials always provides economy so this shouldn't happen + getLogger().warning("No economy plugin found! Components using economy will not be registered."); Test = getConfig().getBoolean("test", true); saveConfig(); Component.registerComponent(this, new PluginUpdaterComponent()); Component.registerComponent(this, new RestartComponent()); + //noinspection unchecked - needed for testing Component.registerComponent(this, new ChannelComponent()); Component.registerComponent(this, new RandomTPComponent()); Component.registerComponent(this, new MemberComponent()); - Component.registerComponent(this, new TownyComponent()); + if (Bukkit.getPluginManager().isPluginEnabled("Towny")) //It fails to load the component class otherwise + Component.registerComponent(this, new TownyComponent()); + if (Bukkit.getPluginManager().isPluginEnabled("Votifier") && economy != null) + Component.registerComponent(this, new VotifierComponent(economy)); ComponentManager.enableComponents(); - Command2MC.registerCommand(new ComponentCommand()); + getCommand2MC().registerCommand(new ComponentCommand()); + getCommand2MC().registerCommand(new ThorpeCommand()); TBMCCoreAPI.RegisterEventsForExceptions(new PlayerListener(), this); ChromaGamerBase.addConverter(commandSender -> Optional.ofNullable(commandSender instanceof ConsoleCommandSender || commandSender instanceof BlockCommandSender ? TBMCPlayer.getPlayer(new UUID(0, 0), TBMCPlayer.class) : null)); //Console & cmdblocks ChromaGamerBase.addConverter(sender -> Optional.ofNullable(sender instanceof Player ? TBMCPlayer.getPlayer(((Player) sender).getUniqueId(), TBMCPlayer.class) : null)); //Players, has higher priority TBMCCoreAPI.RegisterUserClass(TBMCPlayerBase.class); - TBMCChatAPI.RegisterChatChannel(Channel.GlobalChat = new Channel("§fOOC§f", Color.White, "g", null)); //The /ooc ID has moved to the config + TBMCChatAPI.RegisterChatChannel(Channel.GlobalChat = new Channel("§fg§f", Color.White, "g", null)); //The /ooc ID has moved to the config TBMCChatAPI.RegisterChatChannel( Channel.AdminChat = new Channel("§cADMIN§f", Color.Red, "a", Channel.inGroupFilter(null))); TBMCChatAPI.RegisterChatChannel( @@ -122,14 +146,23 @@ public class MainPlugin extends ButtonPlugin { } private boolean setupPermissions() { - RegisteredServiceProvider permissionProvider = getServer().getServicesManager() - .getRegistration(Permission.class); - if (permissionProvider != null) { - permission = permissionProvider.getProvider(); - } + permission = setupProvider(Permission.class); return (permission != null); } + private boolean setupEconomy() { + economy = setupProvider(Economy.class); + return (economy != null); + } + + private T setupProvider(Class cl) { + RegisteredServiceProvider provider = getServer().getServicesManager() + .getRegistration(cl); + if (provider != null) + return provider.getProvider(); + return null; + } + @Override public boolean onCommand(CommandSender sender, Command command, String label, String[] args) { if (command.getName().equals("dontrunthiscmd")) return true; //Used in chat preprocess for console diff --git a/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java b/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java index c5146cd..097b90a 100755 --- a/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java +++ b/ButtonCore/src/main/java/buttondevteam/core/PlayerListener.java @@ -1,17 +1,21 @@ package buttondevteam.core; -import buttondevteam.lib.TBMCCommandPreprocessEvent; -import buttondevteam.lib.TBMCCoreAPI; -import buttondevteam.lib.TBMCSystemChatEvent; -import buttondevteam.lib.chat.Command2MC; +import buttondevteam.lib.*; +import buttondevteam.lib.architecture.ButtonPlugin; +import buttondevteam.lib.chat.ChatMessage; +import buttondevteam.lib.chat.Command2MCSender; +import buttondevteam.lib.chat.TBMCChatAPI; +import buttondevteam.lib.player.TBMCPlayer; import buttondevteam.lib.player.TBMCPlayerBase; import lombok.val; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; import org.bukkit.event.Cancellable; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; import org.bukkit.event.Listener; +import org.bukkit.event.player.AsyncPlayerChatEvent; import org.bukkit.event.player.PlayerCommandPreprocessEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; @@ -64,9 +68,36 @@ public class PlayerListener implements Listener { public void onTBMCPreprocess(TBMCCommandPreprocessEvent event) { if (event.isCancelled()) return; try { - event.setCancelled(Command2MC.handleCommand(event.getSender(), event.getMessage())); + event.setCancelled(ButtonPlugin.getCommand2MC().handleCommand(new Command2MCSender(event.getSender()), event.getMessage())); } catch (Exception e) { TBMCCoreAPI.SendException("Command processing failed for sender '" + event.getSender() + "' and message '" + event.getMessage() + "'", e); } } + + @EventHandler(priority = EventPriority.HIGH) //The one in the chat plugin is set to highest + public void onPlayerChat(AsyncPlayerChatEvent event) { + if (event.isCancelled()) + return; //The chat plugin should cancel it after this handler + val cp = TBMCPlayer.getPlayer(event.getPlayer().getUniqueId(), TBMCPlayer.class); + TBMCChatAPI.SendChatMessage(ChatMessage.builder(event.getPlayer(), cp, event.getMessage()).build()); + //Not cancelling the original event here, it's cancelled in the chat plugin + //This way other plugins can deal with the MC formatting if the chat plugin isn't present, but other platforms still get the message + } + + @EventHandler(priority = EventPriority.HIGH) //The one in the chat plugin is set to highest + public void onPlayerChat(TBMCChatEvent event) { + if (event.isCancelled()) + return; + if (!MainPlugin.Instance.isChatHandlerEnabled()) return; + if (event.getOrigin().equals("Minecraft")) return; //Let other plugins handle MC messages + String msg = MainPlugin.Instance.chatFormat().get() + .replace("{channel}", event.getChannel().DisplayName().get()) + .replace("{origin}", event.getOrigin().substring(0, 1)) + .replace("{name}", ThorpeUtils.getDisplayName(event.getSender())) + .replace("{message}", event.getMessage()); + for (Player player : Bukkit.getOnlinePlayers()) + if (event.shouldSendTo(player)) + player.sendMessage(msg); + Bukkit.getConsoleSender().sendMessage(msg); + } } \ No newline at end of file diff --git a/ButtonCore/src/main/java/buttondevteam/core/TestPrepare.java b/ButtonCore/src/main/java/buttondevteam/core/TestPrepare.java index 67b0a33..13513b4 100755 --- a/ButtonCore/src/main/java/buttondevteam/core/TestPrepare.java +++ b/ButtonCore/src/main/java/buttondevteam/core/TestPrepare.java @@ -41,6 +41,7 @@ public class TestPrepare { return cl.isAssignableFrom(invocation.getMethod().getReturnType()); } })); + //noinspection unchecked Component.registerComponent(Mockito.mock(JavaPlugin.class), new ChannelComponent()); TBMCChatAPI.RegisterChatChannel(Channel.GlobalChat = new Channel("§fg§f", Color.White, "g", null)); } diff --git a/ButtonCore/src/main/java/buttondevteam/core/ThorpeCommand.java b/ButtonCore/src/main/java/buttondevteam/core/ThorpeCommand.java new file mode 100644 index 0000000..91f2d5c --- /dev/null +++ b/ButtonCore/src/main/java/buttondevteam/core/ThorpeCommand.java @@ -0,0 +1,17 @@ +package buttondevteam.core; + +import buttondevteam.lib.chat.Command2; +import buttondevteam.lib.chat.CommandClass; +import buttondevteam.lib.chat.ICommand2MC; +import org.bukkit.command.CommandSender; + +@CommandClass +public class ThorpeCommand extends ICommand2MC { + @Command2.Subcommand //TODO: Main permissions (groups) like 'mod' + public void reload(CommandSender sender) { + if (MainPlugin.Instance.tryReloadConfig()) + sender.sendMessage("§bConfig reloaded."); + else + sender.sendMessage("§cFailed to reload config. Check console."); + } +} diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/channel/ChannelComponent.java b/ButtonCore/src/main/java/buttondevteam/core/component/channel/ChannelComponent.java index 083633d..5e19ea0 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/component/channel/ChannelComponent.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/channel/ChannelComponent.java @@ -1,22 +1,30 @@ package buttondevteam.core.component.channel; +import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.architecture.Component; import org.bukkit.plugin.java.JavaPlugin; +/** + * Manages chat channels. If disabled, only global channels will be registered. + */ public class ChannelComponent extends Component { + static TBMCSystemChatEvent.BroadcastTarget roomJoinLeave; + @Override protected void register(JavaPlugin plugin) { super.register(plugin); + roomJoinLeave = TBMCSystemChatEvent.BroadcastTarget.add("roomJoinLeave"); //Even if it's disabled, global channels continue to work } @Override protected void unregister(JavaPlugin plugin) { super.unregister(plugin); + TBMCSystemChatEvent.BroadcastTarget.remove(roomJoinLeave); + roomJoinLeave = null; } @Override protected void enable() { - } @Override diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/channel/ChatRoom.java b/ButtonCore/src/main/java/buttondevteam/core/component/channel/ChatRoom.java index a086a42..428007a 100755 --- a/ButtonCore/src/main/java/buttondevteam/core/component/channel/ChatRoom.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/channel/ChatRoom.java @@ -1,5 +1,6 @@ package buttondevteam.core.component.channel; +import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.chat.Color; import buttondevteam.lib.chat.TBMCChatAPI; import org.bukkit.command.CommandSender; @@ -17,11 +18,11 @@ public class ChatRoom extends Channel { public void joinRoom(CommandSender sender) { usersInRoom.add(sender); - TBMCChatAPI.SendSystemMessage(this, RecipientTestResult.ALL, sender.getName() + " joined the room"); + TBMCChatAPI.SendSystemMessage(this, RecipientTestResult.ALL, sender.getName() + " joined the room", TBMCSystemChatEvent.BroadcastTarget.ALL); //Always show message in the same kind of channel } public void leaveRoom(CommandSender sender) { usersInRoom.remove(sender); - TBMCChatAPI.SendSystemMessage(this, RecipientTestResult.ALL, sender.getName() + " left the room"); + TBMCChatAPI.SendSystemMessage(this, RecipientTestResult.ALL, sender.getName() + " left the room", ChannelComponent.roomJoinLeave); } } diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberCommand.java b/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberCommand.java index d395d99..1059b26 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberCommand.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberCommand.java @@ -1,56 +1,47 @@ package buttondevteam.core.component.members; import buttondevteam.core.MainPlugin; +import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.CommandClass; -import buttondevteam.lib.chat.TBMCCommandBase; -import lombok.val; +import buttondevteam.lib.chat.ICommand2MC; import org.bukkit.Bukkit; +import org.bukkit.OfflinePlayer; import org.bukkit.command.CommandSender; -@CommandClass(modOnly = true, path = "member") -public class MemberCommand extends TBMCCommandBase { - @Override - public boolean OnCommand(CommandSender sender, String alias, String[] args) { - if (args.length < 2) - return false; - final boolean add; - if (args[0].equalsIgnoreCase("add")) - add = true; - else if (args[0].equalsIgnoreCase("remove")) - add = false; - else - return false; +@CommandClass(modOnly = true, path = "member", helpText = { // + "Member command", // + "Add or remove server members.", // +}) +public class MemberCommand extends ICommand2MC { + private final MemberComponent component; + + public MemberCommand(MemberComponent component) { + getManager().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!"); + this.component = component; + } + + @Command2.Subcommand + public boolean add(CommandSender sender, OfflinePlayer player) { + return addRemove(sender, player, true); + } + + @Command2.Subcommand + public boolean remove(CommandSender sender, OfflinePlayer player) { + return addRemove(sender, player, false); + } + + public boolean addRemove(CommandSender sender, OfflinePlayer op, boolean add) { Bukkit.getScheduler().runTaskAsynchronously(MainPlugin.Instance, () -> { - if (MainPlugin.permission == null) { - sender.sendMessage("§cError: No permission plugin found!"); - return; - } - val op = Bukkit.getOfflinePlayer(args[1]); if (!op.hasPlayedBefore()) { sender.sendMessage("§cCannot find player or haven't played before."); return; } - if (add) { - if (MainPlugin.permission.playerAddGroup(null, op, "member")) - sender.sendMessage("§b" + op.getName() + " added as a member!"); - else - sender.sendMessage("§cFailed to add " + op.getName() + " as a member!"); - } else { - if (MainPlugin.permission.playerRemoveGroup(null, op, "member")) - sender.sendMessage("§b" + op.getName() + " removed as a member!"); - else - sender.sendMessage("§bFailed to remove " + op.getName() + " as a member!"); - } + if (add ? MainPlugin.permission.playerAddGroup(null, op, component.memberGroup().get()) + : MainPlugin.permission.playerRemoveGroup(null, op, component.memberGroup().get())) + sender.sendMessage("§b" + op.getName() + " " + (add ? "added" : "removed") + " as a member!"); + else + sender.sendMessage("§cFailed to " + (add ? "add" : "remove") + " " + op.getName() + " as a member!"); }); return true; } - - @Override - public String[] GetHelpText(String alias) { - return new String[]{ // - "06---- Member ----", // - "Add or remove server members.", // - "Usage: /member " // - }; - } } diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberComponent.java b/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberComponent.java index 0caeca7..b5bab6e 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberComponent.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/members/MemberComponent.java @@ -14,15 +14,35 @@ import java.util.Date; import static buttondevteam.core.MainPlugin.permission; -public class MemberComponent extends Component implements Listener { - private ConfigData memberGroup() { +/** + * Allows giving a 'member' group over some time elapsed OR played. + */ +public class MemberComponent extends Component implements Listener { + /** + * The permission group to give to the player + */ + ConfigData memberGroup() { return getConfig().getData("memberGroup", "member"); } + /** + * The amount of hours needed to play before promotion + */ + private ConfigData playedHours() { + return getConfig().getData("playedHours", 12); + } + + /** + * The amount of days passed since first login + */ + private ConfigData registeredForDays() { + return getConfig().getData("registeredForDays", 7); + } + @Override protected void enable() { registerListener(this); - registerCommand(new MemberCommand()); + registerCommand(new MemberCommand(this)); } @Override @@ -32,8 +52,8 @@ public class MemberComponent extends Component implements Listener { @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { if (permission != null && !permission.playerInGroup(event.getPlayer(), memberGroup().get()) - && (new Date(event.getPlayer().getFirstPlayed()).toInstant().plus(7, ChronoUnit.DAYS).isBefore(Instant.now()) - || event.getPlayer().getStatistic(Statistic.PLAY_ONE_TICK) > 20 * 3600 * 12)) { + && (new Date(event.getPlayer().getFirstPlayed()).toInstant().plus(registeredForDays().get(), ChronoUnit.DAYS).isBefore(Instant.now()) + || event.getPlayer().getStatistic(Statistic.PLAY_ONE_TICK) > 20 * 3600 * playedHours().get())) { permission.playerAddGroup(null, event.getPlayer(), memberGroup().get()); event.getPlayer().sendMessage("§bYou are a member now. YEEHAW"); MainPlugin.Instance.getLogger().info("Added " + event.getPlayer().getName() + " as a member."); diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/randomtp/RandomTPComponent.java b/ButtonCore/src/main/java/buttondevteam/core/component/randomtp/RandomTPComponent.java index e324b3a..0f1fb5e 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/component/randomtp/RandomTPComponent.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/randomtp/RandomTPComponent.java @@ -1,8 +1,13 @@ package buttondevteam.core.component.randomtp; +import buttondevteam.core.MainPlugin; import buttondevteam.lib.architecture.Component; -public class RandomTPComponent extends Component { +/** + * Teleport player to random location within world border. + * Every five players teleport to the same general area, and then a new general area is randomly selected for the next five players. + */ +public class RandomTPComponent extends Component { @Override protected void enable() { new RandomTP().onEnable(this); //It registers it's command diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/restart/PrimeRestartCommand.java b/ButtonCore/src/main/java/buttondevteam/core/component/restart/PrimeRestartCommand.java index 32773ab..389da82 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/component/restart/PrimeRestartCommand.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/restart/PrimeRestartCommand.java @@ -1,26 +1,31 @@ package buttondevteam.core.component.restart; +import buttondevteam.core.component.channel.Channel; import buttondevteam.lib.chat.CommandClass; +import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.chat.TBMCCommandBase; import lombok.Getter; +import lombok.RequiredArgsConstructor; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.command.CommandSender; @CommandClass(path = "primerestart", modOnly = true) +@RequiredArgsConstructor public class PrimeRestartCommand extends TBMCCommandBase { + private final RestartComponent component; @Override public boolean OnCommand(CommandSender sender, String alias, String[] args) { loud = args.length > 0; if (Bukkit.getOnlinePlayers().size() > 0) { sender.sendMessage("§bPlayers online, restart delayed."); if (loud) - Bukkit.broadcastMessage(ChatColor.DARK_RED + "The server will restart as soon as nobody is online."); + TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, ChatColor.DARK_RED + "The server will restart as soon as nobody is online.", component.getRestartBroadcast()); plsrestart = true; } else { sender.sendMessage("§bNobody is online. Restarting now."); if (loud) - Bukkit.broadcastMessage("§cNobody is online. Restarting server."); + TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§cNobody is online. Restarting server.", component.getRestartBroadcast()); Bukkit.spigot().restart(); } return true; diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/restart/RestartComponent.java b/ButtonCore/src/main/java/buttondevteam/core/component/restart/RestartComponent.java index e49e3b2..6b431bb 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/component/restart/RestartComponent.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/restart/RestartComponent.java @@ -1,29 +1,39 @@ package buttondevteam.core.component.restart; +import buttondevteam.core.MainPlugin; +import buttondevteam.core.component.channel.Channel; +import buttondevteam.lib.TBMCSystemChatEvent; import buttondevteam.lib.architecture.Component; import buttondevteam.lib.chat.IFakePlayer; import buttondevteam.lib.chat.TBMCChatAPI; +import lombok.Getter; import org.bukkit.Bukkit; import org.bukkit.ChatColor; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; import org.bukkit.event.player.PlayerQuitEvent; -public class RestartComponent extends Component implements Listener { +/** + * Provides commands such as /schrestart (restart after a countdown) and /primerestart (restart when nobody is online) + */ +public class RestartComponent extends Component implements Listener { @Override public void enable() { //TODO: Permissions for the commands - TBMCChatAPI.AddCommand(this, new ScheduledRestartCommand()); - TBMCChatAPI.AddCommand(this, new PrimeRestartCommand()); + registerCommand(new ScheduledRestartCommand(this)); + TBMCChatAPI.AddCommand(this, new PrimeRestartCommand(this)); registerListener(this); + restartBroadcast = TBMCSystemChatEvent.BroadcastTarget.add("restartCountdown"); } @Override public void disable() { - + TBMCSystemChatEvent.BroadcastTarget.remove(restartBroadcast); } private long lasttime = 0; + @Getter + private TBMCSystemChatEvent.BroadcastTarget restartBroadcast; @EventHandler public void onPlayerLeave(PlayerQuitEvent event) { @@ -32,12 +42,12 @@ public class RestartComponent extends Component implements Listener { && !event.getQuitMessage().equalsIgnoreCase("Server is restarting")) { if (Bukkit.getOnlinePlayers().size() <= 1) { if (PrimeRestartCommand.isLoud()) - Bukkit.broadcastMessage("§cNobody is online anymore. Restarting."); + TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§cNobody is online anymore. Restarting.", restartBroadcast); Bukkit.spigot().restart(); } else if (!(event.getPlayer() instanceof IFakePlayer) && System.nanoTime() - 10 * 1000000000L - lasttime > 0) { //Ten seconds passed since last reminder lasttime = System.nanoTime(); if (PrimeRestartCommand.isLoud()) - Bukkit.broadcastMessage(ChatColor.DARK_RED + "The server will restart as soon as nobody is online."); + TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, ChatColor.DARK_RED + "The server will restart as soon as nobody is online.", restartBroadcast); } } } diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/restart/ScheduledRestartCommand.java b/ButtonCore/src/main/java/buttondevteam/core/component/restart/ScheduledRestartCommand.java index fea469e..dabd48a 100755 --- a/ButtonCore/src/main/java/buttondevteam/core/component/restart/ScheduledRestartCommand.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/restart/ScheduledRestartCommand.java @@ -1,10 +1,14 @@ package buttondevteam.core.component.restart; import buttondevteam.core.MainPlugin; +import buttondevteam.core.component.channel.Channel; import buttondevteam.lib.ScheduledServerRestartEvent; +import buttondevteam.lib.chat.Command2; import buttondevteam.lib.chat.CommandClass; -import buttondevteam.lib.chat.TBMCCommandBase; +import buttondevteam.lib.chat.ICommand2MC; +import buttondevteam.lib.chat.TBMCChatAPI; import lombok.Getter; +import lombok.RequiredArgsConstructor; import lombok.Setter; import org.bukkit.Bukkit; import org.bukkit.boss.BarColor; @@ -14,16 +18,23 @@ import org.bukkit.boss.BossBar; import org.bukkit.command.CommandSender; import org.bukkit.scheduler.BukkitTask; -@CommandClass(modOnly = true, path = "schrestart") -public class ScheduledRestartCommand extends TBMCCommandBase { +@CommandClass(modOnly = true, path = "schrestart", helpText = { + "Scheduled restart", // + "This command restarts the server 1 minute after it's executed, warning players every 10 seconds.", // + "You can optionally set the amount of seconds to wait before the restart." // +}) +@RequiredArgsConstructor +public class ScheduledRestartCommand extends ICommand2MC { @Getter @Setter private int restartCounter; private BukkitTask restarttask; private volatile BossBar restartbar; + @Getter + private final RestartComponent component; - @Override - public boolean OnCommand(CommandSender sender, String alias, String[] args) { + @Command2.Subcommand + public boolean def(CommandSender sender, String alias, String[] args) { int secs = 60; try { if (args.length > 0) @@ -51,20 +62,11 @@ public class ScheduledRestartCommand extends TBMCCommandBase { Bukkit.spigot().restart(); } if (restartCounter % 200 == 0) - Bukkit.broadcastMessage("§c-- The server is restarting in " + restartCounter / 20 + " seconds! (/press)"); + TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§c-- The server is restarting in " + restartCounter / 20 + " seconds! (/press)", component.getRestartBroadcast()); restartbar.setProgress(restartCounter / (double) restarttime); restartbar.setTitle(String.format("Server restart in %.2f", restartCounter / 20f)); restartCounter--; }, 1, 1); return true; } - - @Override - public String[] GetHelpText(String alias) { - return new String[] { // - "§6---- Scheduled restart ----", // - "This command restarts the server 1 minute after it's executed, warning players every 10 seconds.", // - "You can optionally set the amount of ticks to wait before the restart." // - }; - } } diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/towny/TownyComponent.java b/ButtonCore/src/main/java/buttondevteam/core/component/towny/TownyComponent.java index 1b2bf9d..42c857d 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/component/towny/TownyComponent.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/towny/TownyComponent.java @@ -1,6 +1,7 @@ package buttondevteam.core.component.towny; import buttondevteam.core.ComponentManager; +import buttondevteam.core.MainPlugin; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.Component; import com.palmergames.bukkit.towny.Towny; @@ -10,7 +11,10 @@ import com.palmergames.bukkit.towny.object.Resident; import com.palmergames.bukkit.towny.object.TownyUniverse; import org.bukkit.Bukkit; -public class TownyComponent extends Component { +/** + * Automatically renames Towny players if they changed their Minecraft name + */ +public class TownyComponent extends Component { @Override protected void enable() { } diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/updater/PluginUpdaterComponent.java b/ButtonCore/src/main/java/buttondevteam/core/component/updater/PluginUpdaterComponent.java index 2ead741..3ea44c8 100644 --- a/ButtonCore/src/main/java/buttondevteam/core/component/updater/PluginUpdaterComponent.java +++ b/ButtonCore/src/main/java/buttondevteam/core/component/updater/PluginUpdaterComponent.java @@ -1,9 +1,13 @@ package buttondevteam.core.component.updater; +import buttondevteam.core.MainPlugin; import buttondevteam.lib.architecture.Component; import buttondevteam.lib.chat.TBMCChatAPI; -public class PluginUpdaterComponent extends Component { +/** + * Downloads plugin updates built from their source using JitPack - older code + */ +public class PluginUpdaterComponent extends Component { //TODO: Config @Override public void enable() { TBMCChatAPI.AddCommand(this, new UpdatePluginCommand()); diff --git a/ButtonCore/src/main/java/buttondevteam/core/component/votifier/VotifierComponent.java b/ButtonCore/src/main/java/buttondevteam/core/component/votifier/VotifierComponent.java new file mode 100644 index 0000000..94ffe55 --- /dev/null +++ b/ButtonCore/src/main/java/buttondevteam/core/component/votifier/VotifierComponent.java @@ -0,0 +1,49 @@ +package buttondevteam.core.component.votifier; + +import buttondevteam.core.MainPlugin; +import buttondevteam.lib.architecture.Component; +import buttondevteam.lib.architecture.ConfigData; +import com.vexsoftware.votifier.model.Vote; +import com.vexsoftware.votifier.model.VotifierEvent; +import lombok.RequiredArgsConstructor; +import net.milkbowl.vault.economy.Economy; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; + +/** + * Do not use (EULA) + */ +@RequiredArgsConstructor +public class VotifierComponent extends Component { + private final Economy economy; + + private ConfigData rewardAmount() { + return getConfig().getData("rewardAmount", 0.0); + } + + @Override + protected void enable() { + + } + + @Override + protected void disable() { + + } + + @EventHandler + @SuppressWarnings("deprecation") + public void onVotifierEvent(VotifierEvent event) { + Vote vote = event.getVote(); + getPlugin().getLogger().info("Vote: " + vote); + org.bukkit.OfflinePlayer op = Bukkit.getOfflinePlayer(vote.getUsername()); + Player p = Bukkit.getPlayer(vote.getUsername()); + if (op != null) { + economy.depositPlayer(op, rewardAmount().get()); + } + if (p != null) { + p.sendMessage("§bThanks for voting! $50 was added to your account."); + } + } +} diff --git a/ButtonCore/src/main/java/buttondevteam/lib/TBMCCommandPreprocessEvent.java b/ButtonCore/src/main/java/buttondevteam/lib/TBMCCommandPreprocessEvent.java index a72c0b7..d7cd103 100755 --- a/ButtonCore/src/main/java/buttondevteam/lib/TBMCCommandPreprocessEvent.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/TBMCCommandPreprocessEvent.java @@ -25,7 +25,7 @@ public class TBMCCommandPreprocessEvent extends Event implements Cancellable { public TBMCCommandPreprocessEvent(CommandSender sender, String message) { this.sender = sender; - this.message = message; //TODO: Actually call from Discord as well + this.message = message; } @Override diff --git a/ButtonCore/src/main/java/buttondevteam/lib/TBMCSystemChatEvent.java b/ButtonCore/src/main/java/buttondevteam/lib/TBMCSystemChatEvent.java index 80cd634..f5d5a0f 100755 --- a/ButtonCore/src/main/java/buttondevteam/lib/TBMCSystemChatEvent.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/TBMCSystemChatEvent.java @@ -1,10 +1,18 @@ package buttondevteam.lib; import buttondevteam.core.component.channel.Channel; +import lombok.AccessLevel; import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.val; import org.bukkit.command.CommandSender; import org.bukkit.event.HandlerList; +import javax.annotation.Nullable; +import java.util.HashSet; +import java.util.Objects; +import java.util.stream.Stream; + /** * Make sure to only send the message to users who {@link #shouldSendTo(CommandSender)} returns true. * @@ -14,15 +22,17 @@ import org.bukkit.event.HandlerList; @Getter public class TBMCSystemChatEvent extends TBMCChatEventBase { private final String[] exceptions; + private final BroadcastTarget target; private boolean handled; public void setHandled() { handled = true; } - public TBMCSystemChatEvent(Channel channel, String message, int score, String groupid, String[] exceptions) { // TODO: Rich message + public TBMCSystemChatEvent(Channel channel, String message, int score, String groupid, String[] exceptions, BroadcastTarget target) { // TODO: Rich message super(channel, message, score, groupid); this.exceptions = exceptions; + this.target = target; } private static final HandlerList handlers = new HandlerList(); @@ -35,4 +45,30 @@ public class TBMCSystemChatEvent extends TBMCChatEventBase { public static HandlerList getHandlerList() { return handlers; } + + @RequiredArgsConstructor(access = AccessLevel.PRIVATE) + public static class BroadcastTarget { + private final @Getter String name; + private static final HashSet targets = new HashSet<>(); + public static final BroadcastTarget ALL = new BroadcastTarget("ALL"); + + public static BroadcastTarget add(String name) { + val bt = new BroadcastTarget(Objects.requireNonNull(name)); + targets.add(bt); + return bt; + } + + public static void remove(BroadcastTarget target) { + targets.remove(target); + } + + @Nullable + public static BroadcastTarget get(String name) { + return targets.stream().filter(bt -> bt.name.equalsIgnoreCase(name)).findAny().orElse(null); + } + + public static Stream stream() { + return targets.stream(); + } + } } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/ThorpeUtils.java b/ButtonCore/src/main/java/buttondevteam/lib/ThorpeUtils.java index 4f8560c..d69983e 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/ThorpeUtils.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/ThorpeUtils.java @@ -35,4 +35,20 @@ public final class ThorpeUtils { */ String getFancyFullName(); } + + public static Number convertNumber(Number number, Class targetcl) { + if (targetcl == long.class || Long.class.isAssignableFrom(targetcl)) + return number.longValue(); + else if (targetcl == int.class || Integer.class.isAssignableFrom(targetcl)) + return number.intValue(); //Needed because the parser can get longs + else if (targetcl == short.class || Short.class.isAssignableFrom(targetcl)) + return number.shortValue(); + else if (targetcl == byte.class || Byte.class.isAssignableFrom(targetcl)) + return number.byteValue(); + else if (targetcl == float.class || Float.class.isAssignableFrom(targetcl)) + return number.floatValue(); + else if (targetcl == double.class || Double.class.isAssignableFrom(targetcl)) + return number.doubleValue(); + return number; + } } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.java b/ButtonCore/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.java index 5b26d9d..25df8af 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/architecture/ButtonPlugin.java @@ -2,6 +2,7 @@ package buttondevteam.lib.architecture; import buttondevteam.core.ComponentManager; import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.chat.Command2MC; import buttondevteam.lib.chat.TBMCChatAPI; import lombok.AccessLevel; import lombok.Getter; @@ -10,14 +11,20 @@ import org.bukkit.plugin.java.JavaPlugin; import java.util.Stack; +@HasConfig public abstract class ButtonPlugin extends JavaPlugin { + @Getter + private static Command2MC command2MC = new Command2MC(); @Getter(AccessLevel.PROTECTED) private IHaveConfig iConfig; + @Getter(AccessLevel.PROTECTED) + private IHaveConfig data; //TODO + private boolean loaded = false; /** - * Used to unregister components in the right order + * Used to unregister components in the right order - and to reload configs */ @Getter - private Stack componentStack = new Stack<>(); + private Stack> componentStack = new Stack<>(); protected abstract void pluginEnable(); @@ -34,9 +41,7 @@ public abstract class ButtonPlugin extends JavaPlugin { @Override public final void onEnable() { - var section = super.getConfig().getConfigurationSection("global"); - if (section == null) section = super.getConfig().createSection("global"); - iConfig = new IHaveConfig(section); + loadConfig(); try { pluginEnable(); } catch (Exception e) { @@ -44,6 +49,12 @@ public abstract class ButtonPlugin extends JavaPlugin { } } + private void loadConfig() { + var section = super.getConfig().getConfigurationSection("global"); + if (section == null) section = super.getConfig().createSection("global"); + iConfig = new IHaveConfig(section, this::saveConfig); + } + @Override public final void onDisable() { try { @@ -57,4 +68,26 @@ public abstract class ButtonPlugin extends JavaPlugin { TBMCCoreAPI.SendException("Error while disabling plugin " + getName() + "!", e); } } + + @Override + public void reloadConfig() { + tryReloadConfig(); + } + + public boolean tryReloadConfig() { + if (!justReload()) return false; + loadConfig(); + componentStack.forEach(c -> Component.updateConfig(this, c)); + return true; + } + + public boolean justReload() { + if (loaded && ConfigData.saveNow(getConfig())) { + getLogger().warning("Saved pending configuration changes to the file, didn't reload (try again)."); + return false; + } + super.reloadConfig(); + loaded = true; //Needed because for the first time it uses reloadConfig() to load it + return true; + } } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java b/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java index e052555..4d2c9cf 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/architecture/Component.java @@ -3,34 +3,39 @@ package buttondevteam.lib.architecture; import buttondevteam.core.ComponentManager; import buttondevteam.lib.TBMCCoreAPI; import buttondevteam.lib.architecture.exceptions.UnregisteredComponentException; -import buttondevteam.lib.chat.Command2MC; +import buttondevteam.lib.chat.ICommand2MC; import buttondevteam.lib.chat.TBMCChatAPI; import buttondevteam.lib.chat.TBMCCommandBase; import lombok.Getter; import lombok.NonNull; import lombok.experimental.var; import lombok.val; +import org.bukkit.configuration.ConfigurationSection; import org.bukkit.event.Listener; import org.bukkit.plugin.java.JavaPlugin; import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.function.Consumer; +import java.util.stream.Collectors; /** * Configuration is based on class name */ -public abstract class Component { - private static HashMap, Component> components = new HashMap<>(); +@HasConfig //Used for obtaining javadoc +public abstract class Component { + private static HashMap, Component> components = new HashMap<>(); @Getter private boolean enabled = false; @Getter @NonNull - private JavaPlugin plugin; + private TP plugin; @NonNull private @Getter IHaveConfig config; + private @Getter IHaveConfig data; //TODO public final ConfigData shouldBeEnabled() { return config.getData("enabled", true); @@ -45,7 +50,7 @@ public abstract class Component { * @param component The component to register * @return Whether the component is registered successfully (it may have failed to enable) */ - public static boolean registerComponent(JavaPlugin plugin, Component component) { + public static boolean registerComponent(T plugin, Component component) { return registerUnregisterComponent(plugin, component, true); } @@ -57,11 +62,11 @@ public abstract class Component { * @param component The component to unregister * @return Whether the component is unregistered successfully (it also got disabled) */ - public static boolean unregisterComponent(JavaPlugin plugin, Component component) { + public static boolean unregisterComponent(T plugin, Component component) { return registerUnregisterComponent(plugin, component, false); } - public static boolean registerUnregisterComponent(JavaPlugin plugin, Component component, boolean register) { + public static boolean registerUnregisterComponent(T plugin, Component component, boolean register) { try { val metaAnn = component.getClass().getAnnotation(ComponentMetadata.class); if (metaAnn != null) { @@ -135,16 +140,16 @@ public abstract class Component { } } - private static void updateConfig(JavaPlugin plugin, Component component) { + public static void updateConfig(JavaPlugin plugin, Component component) { if (plugin.getConfig() != null) { //Production var compconf = plugin.getConfig().getConfigurationSection("components"); if (compconf == null) compconf = plugin.getConfig().createSection("components"); var configSect = compconf.getConfigurationSection(component.getClassName()); if (configSect == null) configSect = compconf.createSection(component.getClassName()); - component.config = new IHaveConfig(configSect); + component.config = new IHaveConfig(configSect, plugin::saveConfig); } else //Testing - component.config = new IHaveConfig(null); + component.config = new IHaveConfig(null, plugin::saveConfig); } /** @@ -152,7 +157,7 @@ public abstract class Component { * * @return The currently registered components */ - public static Map, Component> getComponents() { + public static Map, Component> getComponents() { return Collections.unmodifiableMap(components); } @@ -197,8 +202,8 @@ public abstract class Component { * * @param commandBase Custom coded command class */ - protected final void registerCommand(Command2MC commandBase) { - Command2MC.registerCommand(commandBase); + protected final void registerCommand(ICommand2MC commandBase) { + ButtonPlugin.getCommand2MC().registerCommand(commandBase); } /** @@ -221,6 +226,28 @@ public abstract class Component { return listener; } + /** + * Returns a map of configs that are under the given key. + * @param key The key to use + * @param defaultProvider A mapping between config paths and config generators + * @return A map containing configs + */ + protected Map getConfigMap(String key, Map> defaultProvider) { + val c=getConfig().getConfig(); + var cs=c.getConfigurationSection(key); + if(cs==null) cs=c.createSection(key); + val res = cs.getValues(false).entrySet().stream().filter(e -> e.getValue() instanceof ConfigurationSection) + .collect(Collectors.toMap(Map.Entry::getKey, kv -> new IHaveConfig((ConfigurationSection) kv.getValue(), getPlugin()::saveConfig))); + if (res.size() == 0) { + for (val entry : defaultProvider.entrySet()) { + val conf = new IHaveConfig(cs.createSection(entry.getKey()), getPlugin()::saveConfig); + entry.getValue().accept(conf); + res.put(entry.getKey(), conf); + } + } + return res; + } + private String getClassName() { return getClass().getSimpleName(); } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/architecture/ComponentMetadata.java b/ButtonCore/src/main/java/buttondevteam/lib/architecture/ComponentMetadata.java index 6519fca..a182df8 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/architecture/ComponentMetadata.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/architecture/ComponentMetadata.java @@ -8,5 +8,5 @@ import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) public @interface ComponentMetadata { - Class[] depends(); + Class[] depends() default {}; } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/architecture/ConfigData.java b/ButtonCore/src/main/java/buttondevteam/lib/architecture/ConfigData.java index 1a3a11f..6374db0 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/architecture/ConfigData.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/architecture/ConfigData.java @@ -1,10 +1,19 @@ package buttondevteam.lib.architecture; +import buttondevteam.core.MainPlugin; +import buttondevteam.lib.ThorpeUtils; import lombok.AccessLevel; +import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; +import org.bukkit.Bukkit; +import org.bukkit.configuration.Configuration; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.scheduler.BukkitTask; +import java.lang.reflect.Array; +import java.util.HashMap; +import java.util.List; import java.util.Objects; import java.util.function.Function; @@ -14,7 +23,8 @@ import java.util.function.Function; */ @RequiredArgsConstructor(access = AccessLevel.PACKAGE) //@AllArgsConstructor(access = AccessLevel.PACKAGE) -public class ConfigData { //TODO: Save after a while +public class ConfigData { + private static final HashMap saveTasks = new HashMap<>(); /** * May be null for testing */ @@ -22,6 +32,7 @@ public class ConfigData { //TODO: Save after a while private final String path; private final T def; private final Object primitiveDef; + private final Runnable saveAction; /** * The parameter is of a primitive type as returned by {@link YamlConfiguration#get(String)} */ @@ -35,15 +46,20 @@ public class ConfigData { //TODO: Save after a while * The config value should not change outside this instance */ private T value; + /** + * Whether the default value is saved in the yaml + */ private boolean saved = false; - public ConfigData(ConfigurationSection config, String path, T def, Object primitiveDef, Function getter, Function setter) { + //This constructor is needed because it sets the getter and setter + public ConfigData(ConfigurationSection config, String path, T def, Object primitiveDef, Function getter, Function setter, Runnable saveAction) { this.config = config; this.path = path; this.def = def; this.primitiveDef = primitiveDef; this.getter = getter; this.setter = setter; + this.saveAction=saveAction; } @SuppressWarnings("unchecked") @@ -54,7 +70,10 @@ public class ConfigData { //TODO: Save after a while val = primitiveDef; } if (!saved && Objects.equals(val, primitiveDef)) { //String needs .equals() - set(def); //Save default value - def is always set + if (def == null && config != null) //In Discord's case def may be null + config.set(path, primitiveDef); + else + set(def); //Save default value - def is always set saved = true; } if (getter != null) { @@ -62,28 +81,49 @@ public class ConfigData { //TODO: Save after a while if (hmm == null) hmm = def; //Set if the getter returned null return hmm; } - if (val instanceof Number) { - if (def instanceof Long) - val = ((Number) val).longValue(); - else if (def instanceof Short) - val = ((Number) val).shortValue(); - else if (def instanceof Byte) - val = ((Number) val).byteValue(); - else if (def instanceof Float) - val = ((Number) val).floatValue(); - else if (def instanceof Double) - val = ((Number) val).doubleValue(); - } - return (T) val; + if (val instanceof Number && def != null) + val = ThorpeUtils.convertNumber((Number) val, + (Class) def.getClass()); + if (val instanceof List && def != null && def.getClass().isArray()) + val = ((List) val).toArray((T[]) Array.newInstance(def.getClass().getComponentType(), 0)); + return value = (T) val; //Always cache, if not cached yet } public void set(T value) { Object val; - if (setter != null) + if (setter != null && value != null) val = setter.apply(value); else val = value; - if (config != null) + if (config != null) { config.set(path, val); + if(!saveTasks.containsKey(config.getRoot())) { + synchronized (saveTasks) { + saveTasks.put(config.getRoot(), new SaveTask(Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, () -> { + synchronized (saveTasks) { + saveTasks.remove(config.getRoot()); + saveAction.run(); + } + }, 100), saveAction)); + } + } + } this.value = value; } + + @AllArgsConstructor + private static class SaveTask { + BukkitTask task; + Runnable saveAction; + } + + public static boolean saveNow(Configuration config) { + SaveTask st = saveTasks.get(config); + if (st != null) { + st.task.cancel(); + saveTasks.remove(config); + st.saveAction.run(); + return true; + } + return false; + } } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/architecture/HasConfig.java b/ButtonCore/src/main/java/buttondevteam/lib/architecture/HasConfig.java new file mode 100644 index 0000000..8e1e63a --- /dev/null +++ b/ButtonCore/src/main/java/buttondevteam/lib/architecture/HasConfig.java @@ -0,0 +1,13 @@ +package buttondevteam.lib.architecture; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Target; + +/** + * Used to generate documentation for the config + */ +@Target(ElementType.TYPE) +@Inherited +public @interface HasConfig { +} diff --git a/ButtonCore/src/main/java/buttondevteam/lib/architecture/IHaveConfig.java b/ButtonCore/src/main/java/buttondevteam/lib/architecture/IHaveConfig.java index 98fa241..241e4f5 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/architecture/IHaveConfig.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/architecture/IHaveConfig.java @@ -3,6 +3,8 @@ package buttondevteam.lib.architecture; import lombok.Getter; import lombok.val; import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitTask; import java.util.HashMap; import java.util.function.Function; @@ -15,14 +17,16 @@ public final class IHaveConfig { private final HashMap> datamap = new HashMap<>(); @Getter private ConfigurationSection config; + private final Runnable saveAction; /** * May be used in testing. * * @param section May be null for testing */ - IHaveConfig(ConfigurationSection section) { + IHaveConfig(ConfigurationSection section, Runnable saveAction) { config = section; + this.saveAction=saveAction; } /** @@ -36,7 +40,7 @@ public final class IHaveConfig { @SuppressWarnings("unchecked") public ConfigData getData(String path, T def) { ConfigData data = datamap.get(path); - if (data == null) datamap.put(path, data = new ConfigData<>(config, path, def, def)); + if (data == null) datamap.put(path, data = new ConfigData<>(config, path, def, def, saveAction)); return (ConfigData) data; } @@ -54,7 +58,7 @@ public final class IHaveConfig { public ConfigData getData(String path, T def, Function getter, Function setter) { ConfigData data = datamap.get(path); if (data == null) - datamap.put(path, data = new ConfigData<>(config, path, def, setter.apply(def), getter, setter)); + datamap.put(path, data = new ConfigData<>(config, path, def, setter.apply(def), getter, setter, saveAction)); return (ConfigData) data; } @@ -72,7 +76,7 @@ public final class IHaveConfig { public ConfigData getDataPrimDef(String path, Object primitiveDef, Function getter, Function setter) { ConfigData data = datamap.get(path); if (data == null) - datamap.put(path, data = new ConfigData<>(config, path, getter.apply(primitiveDef), primitiveDef, getter, setter)); + datamap.put(path, data = new ConfigData<>(config, path, getter.apply(primitiveDef), primitiveDef, getter, setter, saveAction)); return (ConfigData) data; } @@ -89,7 +93,7 @@ public final class IHaveConfig { ConfigData data = datamap.get(path); if (data == null) { val defval = def.get(); - datamap.put(path, data = new ConfigData<>(config, path, defval, defval)); + datamap.put(path, data = new ConfigData<>(config, path, defval, defval, saveAction)); } return (ConfigData) data; } @@ -109,7 +113,7 @@ public final class IHaveConfig { ConfigData data = datamap.get(path); if (data == null) { val defval = def.get(); - datamap.put(path, data = new ConfigData<>(config, path, defval, setter.apply(defval), getter, setter)); + datamap.put(path, data = new ConfigData<>(config, path, defval, setter.apply(defval), getter, setter, saveAction)); } return (ConfigData) data; } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java index c16d318..bc10fc7 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2.java @@ -1,11 +1,12 @@ package buttondevteam.lib.chat; import buttondevteam.lib.TBMCCoreAPI; +import buttondevteam.lib.ThorpeUtils; import buttondevteam.lib.player.ChromaGamerBase; +import lombok.AllArgsConstructor; import lombok.RequiredArgsConstructor; import lombok.experimental.var; import lombok.val; -import org.bukkit.command.CommandSender; import org.bukkit.configuration.file.YamlConfiguration; import java.io.InputStreamReader; @@ -13,43 +14,25 @@ import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +import java.text.NumberFormat; +import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.function.Function; -import java.util.stream.Collectors; /** * The method name is the subcommand, use underlines (_) to add further subcommands. - * The args may be null if the conversion failed. + * The args may be null if the conversion failed and it's optional. */ -public abstract class Command2 { - /** - * Default handler for commands, can be used to copy the args too. - * - * @param sender The sender which ran the command - * @param args All of the arguments passed as is - * @return The success of the command - */ - public boolean def(CommandSender sender, @TextArg String args) { - return false; +public abstract class Command2 { + protected Command2() { + commandHelp.add("§6---- Commands ----"); } /** - * Convenience method. Return with this. - * - * @param sender The sender of the command - * @param message The message to send to the sender - * @return Always true so that the usage isn't shown - */ - protected boolean respond(CommandSender sender, String message) { - sender.sendMessage(message); - return true; - } - - /** - * TODO: @CommandClass(helpText=...) * Parameters annotated with this receive all of the remaining arguments */ @Target(ElementType.PARAMETER) @@ -69,17 +52,29 @@ public abstract class Command2 { String[] helpText() default {}; } - @RequiredArgsConstructor - protected static class SubcommandData { - public final Method method; - public final T command; - public final String[] helpText; + @Target(ElementType.PARAMETER) + @Retention(RetentionPolicy.RUNTIME) + public @interface OptionalArg { } - public Command2() { - path = getcmdpath(); + @AllArgsConstructor + protected static class SubcommandData { + public final Method method; + public final T command; + public String[] helpText; } + @RequiredArgsConstructor + protected static class ParamConverter { + public final Function converter; + public final String errormsg; + } + + private HashMap> subcommands = new HashMap<>(); + private HashMap, ParamConverter> paramConverters = new HashMap<>(); + + private ArrayList commandHelp = new ArrayList<>(); //Mainly needed by Discord + /** * Adds a param converter that obtains a specific object from a string parameter. * The converter may return null. @@ -88,16 +83,23 @@ public abstract class Command2 { * @param converter The converter to use * @param The type of the result */ - protected static void addParamConverter(Class cl, Function converter, HashMap, Function> map) { - map.put(cl, converter); + public void addParamConverter(Class cl, Function converter, String errormsg) { + paramConverters.put(cl, new ParamConverter<>(converter, errormsg)); } - protected static boolean handleCommand(CommandSender sender, String commandline, - HashMap> subcommands, HashMap, Function> paramConverters) throws Exception { + public boolean handleCommand(TP sender, String commandline) throws Exception { for (int i = commandline.length(); i != -1; i = commandline.lastIndexOf(' ', i - 1)) { String subcommand = commandline.substring(0, i).toLowerCase(); - SubcommandData sd = subcommands.get(subcommand); //O(1) - if (sd == null) continue; //TODO: This will run each time someone runs any command + SubcommandData sd = subcommands.get(subcommand); //O(1) + if (sd == null) continue; + if (sd.method == null || sd.command == null) { //Main command not registered, but we have subcommands + sender.sendMessage(sd.helpText); + return true; + } + if (!hasPermission(sender, sd.command)) { + sender.sendMessage("§cYou don't have permission to use this command"); + return true; + } val params = new ArrayList(sd.method.getParameterCount()); int j = subcommand.length(), pj; Class[] parameterTypes = sd.method.getParameterTypes(); @@ -107,77 +109,142 @@ public abstract class Command2 { final ChromaGamerBase cg; if (sendertype.isAssignableFrom(sender.getClass())) params.add(sender); //The command either expects a CommandSender or it is a Player, or some other expected type + else if (sender instanceof Command2MCSender + && sendertype.isAssignableFrom(((Command2MCSender) sender).getSender().getClass())) + params.add(((Command2MCSender) sender).getSender()); else if (ChromaGamerBase.class.isAssignableFrom(sendertype) - && (cg = ChromaGamerBase.getFromSender(sender)) != null + && sender instanceof Command2MCSender + && (cg = ChromaGamerBase.getFromSender(((Command2MCSender) sender).getSender())) != null && cg.getClass() == sendertype) //The command expects a user of our system params.add(cg); else { sender.sendMessage("§cYou need to be a " + sendertype.getSimpleName() + " to use this command."); return true; } + val paramArr = sd.method.getParameters(); for (int i1 = 1; i1 < parameterTypes.length; i1++) { Class cl = parameterTypes[i1]; pj = j + 1; //Start index if (pj == commandline.length() + 1) { //No param given - params.add(null); - continue; //Fill the remaining params with nulls + if (paramArr[i1].isAnnotationPresent(OptionalArg.class)) { + params.add(null); + continue; //Fill the remaining params with nulls + } else { + sender.sendMessage(sd.helpText); //Required param missing + return true; + } + } + if (paramArr[i1].isVarArgs()) { + params.add(commandline.substring(j + 1).split(" +")); + continue; } j = commandline.indexOf(' ', j + 1); //End index - if (j == -1) //Last parameter + if (j == -1 || paramArr[i1].isAnnotationPresent(TextArg.class)) //Last parameter j = commandline.length(); String param = commandline.substring(pj, j); if (cl == String.class) { params.add(param); continue; + } else if (Number.class.isAssignableFrom(cl) || cl.isPrimitive()) { + try { + //System.out.println("Converting "+param+" param to "+cl.getSimpleName()); + //noinspection unchecked + Number n = ThorpeUtils.convertNumber(NumberFormat.getInstance().parse(param), (Class) cl); + //System.out.println(n.getClass().getSimpleName()+" with value "+n); + params.add(n); + } catch (ParseException e) { + sender.sendMessage("§c'" + param + "' is not a number."); + return true; + } + continue; } val conv = paramConverters.get(cl); if (conv == null) throw new Exception("No suitable converter found for parameter type '" + cl.getCanonicalName() + "' for command '" + sd.method.toString() + "'"); - params.add(conv.apply(param)); + val cparam = conv.converter.apply(param); + if (cparam == null) { + sender.sendMessage(conv.errormsg); //Param conversion failed - ex. plugin not found + return true; + } + params.add(cparam); } //System.out.println("Our params: "+params); - val ret = sd.method.invoke(sd.command, params.toArray()); //I FORGOT TO TURN IT INTO AN ARRAY (for a long time) - if (ret instanceof Boolean) { - if (!(boolean) ret) //Show usage - sender.sendMessage(sd.helpText); - } else if (ret != null) - throw new Exception("Wrong return type! Must return a boolean or void. Return value: "+ret); - return true; //We found a method + try { + val ret = sd.method.invoke(sd.command, params.toArray()); //I FORGOT TO TURN IT INTO AN ARRAY (for a long time) + if (ret instanceof Boolean) { + if (!(boolean) ret) //Show usage + sender.sendMessage(sd.helpText); + } else if (ret != null) + throw new Exception("Wrong return type! Must return a boolean or void. Return value: " + ret); + return true; //We found a method + } catch (InvocationTargetException e) { + TBMCCoreAPI.SendException("An error occurred in a command handler!", e.getCause()); + } } return false; //Didn't handle } //TODO: Add to the help - protected static void registerCommand(T command, HashMap> subcommands, char commandChar) { + public abstract void registerCommand(TC command); + + protected void registerCommand(TC command, char commandChar) { val path = command.getCommandPath(); + int x = path.indexOf(' '); + val mainPath = commandChar + path.substring(0, x == -1 ? path.length() : x); + //var scmdmap = subcommandStrings.computeIfAbsent(mainPath, k -> new HashSet<>()); //Used to display subcommands + val scmdHelpList = new ArrayList(); + Method mainMethod = null; + boolean nosubs = true; + boolean isSubcommand = x != -1; try { //Register the default handler first so it can be reliably overwritten - val method = command.getClass().getMethod("def", CommandSender.class, String.class); + mainMethod = command.getClass().getMethod("def", Command2Sender.class, String.class); val cc = command.getClass().getAnnotation(CommandClass.class); - var ht = cc == null ? new String[0] : cc.helpText(); - String[] both = Arrays.copyOf(ht, ht.length + 1); - both[ht.length] = "Usage: " + commandChar + path; //TODO: Print subcommands - ht = both; - subcommands.put(commandChar + path, new SubcommandData<>(method, command, ht)); //TODO: Disable components when the plugin is disabled + var ht = cc == null || isSubcommand ? new String[0] : cc.helpText(); //If it's not the main command, don't add it + if (ht.length > 0) + ht[0] = "§6---- " + ht[0] + " ----"; + scmdHelpList.addAll(Arrays.asList(ht)); + if (!isSubcommand) + scmdHelpList.add("§6Subcommands:"); + if (!commandHelp.contains(mainPath)) + commandHelp.add(mainPath); } catch (Exception e) { TBMCCoreAPI.SendException("Could not register default handler for command /" + path, e); - } //Continue on + } for (val method : command.getClass().getMethods()) { val ann = method.getAnnotation(Subcommand.class); - if (ann != null) { - val cc = command.getClass().getAnnotation(CommandClass.class); - var ht = ann.helpText().length != 0 || cc == null ? ann.helpText() : cc.helpText(); //If cc is null then it's empty array + if (ann == null) continue; //Don't call the method on non-subcommands because they're not in the yaml + var ht = command.getHelpText(method, ann); + if (ht != null) { val subcommand = commandChar + path + //Add command path (class name by default) (method.getName().equals("def") ? "" : " " + method.getName().replace('_', ' ').toLowerCase()); //Add method name, unless it's 'def' ht = getHelpText(method, ht, subcommand); subcommands.put(subcommand, new SubcommandData<>(method, command, ht)); //Result of the above (def) is that it will show the help text + scmdHelpList.add(subcommand); + nosubs = false; } } + if (nosubs && scmdHelpList.size() > 0) + scmdHelpList.remove(scmdHelpList.size() - 1); //Remove Subcommands header + if (mainMethod != null && !subcommands.containsKey(commandChar + path)) //Command specified by the class + subcommands.put(commandChar + path, new SubcommandData<>(mainMethod, command, scmdHelpList.toArray(new String[0]))); + if (mainMethod != null && !mainPath.equals(commandChar + path)) { //Main command, typically the same as the above + if (isSubcommand) { //The class itself is a subcommand + val scmd = subcommands.computeIfAbsent(mainPath, p -> new SubcommandData<>(null, null, new String[]{"§6---- Subcommands ----"})); + val scmdHelp = Arrays.copyOf(scmd.helpText, scmd.helpText.length + scmdHelpList.size()); + for (int i = 0; i < scmdHelpList.size(); i++) + scmdHelp[scmd.helpText.length + i] = scmdHelpList.get(i); + scmd.helpText = scmdHelp; + } else if (!subcommands.containsKey(mainPath)) + subcommands.put(mainPath, new SubcommandData<>(null, null, scmdHelpList.toArray(new String[0]))); + } } - private static String[] getHelpText(Method method, String[] ht, String subcommand) { //TODO: helpText[0]="§6---- "+helpText[0]+" ----"; + private String[] getHelpText(Method method, String[] ht, String subcommand) { val str = method.getDeclaringClass().getResourceAsStream("/commands.yml"); if (str == null) TBMCCoreAPI.SendException("Error while getting command data!", new Exception("Resource not found!")); else { + if (ht.length > 0) + ht[0] = "§6---- " + ht[0] + " ----"; YamlConfiguration yc = YamlConfiguration.loadConfiguration(new InputStreamReader(str)); //Generated by ButtonProcessor val ccs = yc.getConfigurationSection(method.getDeclaringClass().getCanonicalName()); if (ccs != null) { @@ -185,10 +252,11 @@ public abstract class Command2 { if (cs != null) { val mname = cs.getString("method"); val params = cs.getString("params"); - val goodname = method.getName() + "(" + Arrays.stream(method.getParameterTypes()).map(cl -> cl.getCanonicalName()).collect(Collectors.joining(",")) + ")"; - if (goodname.equals(mname) && params != null) { + //val goodname = method.getName() + "(" + Arrays.stream(method.getGenericParameterTypes()).map(cl -> cl.getTypeName()).collect(Collectors.joining(",")) + ")"; + int i = mname.indexOf('('); //Check only the name - the whole method is still stored for backwards compatibility and in case it may be useful + if (i != -1 && method.getName().equals(mname.substring(0, i)) && params != null) { String[] both = Arrays.copyOf(ht, ht.length + 1); - both[ht.length] = "Usage: " + subcommand + " " + params; + both[ht.length] = "§6Usage:§r " + subcommand + " " + params; ht = both; } else TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method.toString() + "' != " + mname + " or params is " + params)); @@ -200,28 +268,15 @@ public abstract class Command2 { return ht; } - private final String path; + public abstract boolean hasPermission(TP sender, TC command); - /** - * 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) - Change via the {@link CommandClass} annotation - */ - public final String getCommandPath() { - return path; + public String[] getCommandsText() { + return commandHelp.toArray(new String[0]); } - private 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(); - path = path.length() == 0 ? getFromClass.apply(getClass()) : path; - return path; + public String[] getHelpText(String path) { + val scmd = subcommands.get(path); + if (scmd == null) return null; + return scmd.helpText; } -} //TODO: Test support of Player instead of CommandSender +} diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java index 715d078..9761ea5 100644 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MC.java @@ -1,24 +1,43 @@ package buttondevteam.lib.chat; -import org.bukkit.command.CommandSender; +import buttondevteam.core.MainPlugin; +import lombok.val; -import java.util.HashMap; import java.util.function.Function; -public class Command2MC extends Command2 { - - private static HashMap> subcommands = new HashMap<>(); - private static HashMap, Function> paramConverters = new HashMap<>(); - - public static boolean handleCommand(CommandSender sender, String commandLine) throws Exception { - return handleCommand(sender, commandLine, subcommands, paramConverters); +public class Command2MC extends Command2 { + @Override + public void registerCommand(ICommand2MC command) { + super.registerCommand(command, '/'); } - public static void registerCommand(Command2MC command) { - registerCommand(command, subcommands, '/'); + @Override + public boolean hasPermission(Command2MCSender sender, ICommand2MC command) { + return modOnly(command) + ? MainPlugin.permission.has(sender.getSender(), "tbmc.admin") //TODO: Change when groups are implemented + : MainPlugin.permission.has(sender.getSender(), "thorpe.command." + command.getCommandPath().replace(' ', '.')); } - public static void addParamConverter(Class cl, Function converter) { - addParamConverter(cl, converter, paramConverters); + /** + * Returns true if this class or any of the superclasses are mod only. + * + * @param command The command to check + * @return Whether the command is mod only + */ + private boolean modOnly(ICommand2MC command) { + for (Class cl = command.getClass(); cl != null; cl = cl.getSuperclass()) { + val cc = command.getClass().getAnnotation(CommandClass.class); + if (cc != null && cc.modOnly()) return true; + } + return false; + } + + /** + * Automatically colors the message red. + * {@see super#addParamConverter} + */ + @Override + public void addParamConverter(Class cl, Function converter, String errormsg) { + super.addParamConverter(cl, converter, "§c" + errormsg); } } diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MCSender.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MCSender.java new file mode 100644 index 0000000..059d953 --- /dev/null +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2MCSender.java @@ -0,0 +1,20 @@ +package buttondevteam.lib.chat; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.bukkit.command.CommandSender; + +@RequiredArgsConstructor +public class Command2MCSender implements Command2Sender { + private @Getter final CommandSender sender; + + @Override + public void sendMessage(String message) { + sender.sendMessage(message); + } + + @Override + public void sendMessage(String[] message) { + sender.sendMessage(message); + } +} diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2Sender.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2Sender.java new file mode 100644 index 0000000..ebb2b66 --- /dev/null +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/Command2Sender.java @@ -0,0 +1,7 @@ +package buttondevteam.lib.chat; + +public interface Command2Sender { //We don't need the 'extras' of CommandSender on Discord + void sendMessage(String message); + + void sendMessage(String[] message); +} diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/CommandClass.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/CommandClass.java index ddfff22..aa497fd 100755 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/CommandClass.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/CommandClass.java @@ -38,7 +38,8 @@ public @interface CommandClass { boolean excludeFromPath() default false; /** - * The help text to show for the players. A usage message will be also shown below it. + * The help text to show for the players. A usage message will be also shown below it.
+ * The fist line will be converted to a header. * * @return The help text */ diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/ICommand2.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/ICommand2.java new file mode 100644 index 0000000..b55a85a --- /dev/null +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/ICommand2.java @@ -0,0 +1,91 @@ +package buttondevteam.lib.chat; + +import lombok.Getter; +import lombok.val; + +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.function.Function; + +public abstract class ICommand2 { + /** + * Default handler for commands, can be used to copy the args too. + * + * @param sender The sender which ran the command + * @param args All of the arguments passed as is + * @return The success of the command + */ + public boolean def(TP sender, @Command2.TextArg String args) { + return false; + } + + /** + * Convenience method. Return with this. + * + * @param sender The sender of the command + * @param message The message to send to the sender + * @return Always true so that the usage isn't shown + */ + protected boolean respond(TP sender, String message) { + sender.sendMessage(message); + return true; + } + + /** + * Return null to not add any help text, return an empty array to only print subcommands.
+ * By default, returns null if the Subcommand annotation is not present and returns an empty array if no help text can be found. + * + * @param method The method of the subcommand + * @return The help text, empty array or null + */ + public String[] getHelpText(Method method, Command2.Subcommand ann) { + val cc = getClass().getAnnotation(CommandClass.class); + return ann.helpText().length != 0 || cc == null ? ann.helpText() : cc.helpText(); //If cc is null then it's empty array + } + + private final String path; + @Getter + private final Command2 manager; //TIL that if I use a raw type on a variable then none of the type args will work (including what's defined on a method, not on the type) + + public ICommand2(Command2 manager) { + path = getcmdpath(); + this.manager = manager; + } + + /** + * 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) - Change via the {@link CommandClass} annotation + */ + public String getCommandPath() { + return path; + } + + private 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; + } +} diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/ICommand2MC.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/ICommand2MC.java new file mode 100644 index 0000000..4ad997b --- /dev/null +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/ICommand2MC.java @@ -0,0 +1,9 @@ +package buttondevteam.lib.chat; + +import buttondevteam.lib.architecture.ButtonPlugin; + +public class ICommand2MC extends ICommand2 { + public ICommand2MC() { + super(ButtonPlugin.getCommand2MC()); + } +} diff --git a/ButtonCore/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java b/ButtonCore/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java index d143d8b..21f772e 100755 --- a/ButtonCore/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java +++ b/ButtonCore/src/main/java/buttondevteam/lib/chat/TBMCChatAPI.java @@ -311,12 +311,12 @@ public class TBMCChatAPI { * @param exceptions Platforms where this message shouldn't be sent (same as {@link ChatMessage#getOrigin()} * @return The event cancelled state */ - public static boolean SendSystemMessage(Channel channel, RecipientTestResult rtr, String message, String... exceptions) { + public static boolean SendSystemMessage(Channel channel, RecipientTestResult rtr, String message, TBMCSystemChatEvent.BroadcastTarget target, String... exceptions) { if (!Channel.getChannelList().contains(channel)) throw new RuntimeException("Channel " + channel.DisplayName().get() + " not registered!"); if (!channel.Enabled().get()) return true; //Cancel sending - TBMCSystemChatEvent event = new TBMCSystemChatEvent(channel, message, rtr.score, rtr.groupID, exceptions); + TBMCSystemChatEvent event = new TBMCSystemChatEvent(channel, message, rtr.score, rtr.groupID, exceptions, target); Bukkit.getPluginManager().callEvent(event); return event.isCancelled(); } diff --git a/ButtonCore/src/main/resources/plugin.yml b/ButtonCore/src/main/resources/plugin.yml index 43c609f..29bfbb1 100755 --- a/ButtonCore/src/main/resources/plugin.yml +++ b/ButtonCore/src/main/resources/plugin.yml @@ -15,4 +15,9 @@ commands: description: Add or remove a member component: description: Enable or disable or list components - dontrunthiscmd: \ No newline at end of file + dontrunthiscmd: +depend: + - Vault +softdepend: + - Towny + - Votifier \ No newline at end of file diff --git a/ButtonProcessor/pom.xml b/ButtonProcessor/pom.xml index 975ead3..401e62c 100755 --- a/ButtonProcessor/pom.xml +++ b/ButtonProcessor/pom.xml @@ -46,6 +46,7 @@ org.apache.maven.plugins maven-surefire-plugin + 3.0.0-M3 false diff --git a/ButtonProcessor/src/main/java/buttondevteam/buttonproc/ButtonProcessor.java b/ButtonProcessor/src/main/java/buttondevteam/buttonproc/ButtonProcessor.java index f7bb7fe..50e1939 100755 --- a/ButtonProcessor/src/main/java/buttondevteam/buttonproc/ButtonProcessor.java +++ b/ButtonProcessor/src/main/java/buttondevteam/buttonproc/ButtonProcessor.java @@ -8,14 +8,11 @@ import javax.annotation.processing.RoundEnvironment; import javax.annotation.processing.SupportedAnnotationTypes; import javax.lang.model.SourceVersion; import javax.lang.model.element.*; -import javax.tools.Diagnostic; import javax.tools.Diagnostic.Kind; import javax.tools.FileObject; -import javax.tools.JavaFileObject; import javax.tools.StandardLocation; import java.io.File; import java.io.IOException; -import java.io.Writer; import java.util.List; import java.util.Set; import java.util.function.Function; @@ -25,6 +22,8 @@ import java.util.stream.Collectors; public class ButtonProcessor extends AbstractProcessor { @Override public boolean process(Set annotations, RoundEnvironment roundEnv) { + if (configProcessor == null) + configProcessor = new ConfigProcessor(processingEnv); for (TypeElement te : annotations) { Set classes = roundEnv.getElementsAnnotatedWith(te); for (Element targetcl : classes) { @@ -47,6 +46,8 @@ public class ButtonProcessor extends AbstractProcessor { //System.out.println("Type: " + type); } processSubcommands(targetcl, annotationMirrors); + if (hasAnnotation.apply("HasConfig")) + configProcessor.process(targetcl); } } try { @@ -63,6 +64,7 @@ public class ButtonProcessor extends AbstractProcessor { private YamlConfiguration yc = new YamlConfiguration(); private boolean found = false; + private ConfigProcessor configProcessor; private void processSubcommands(Element targetcl, List annotationMirrors) { if (!(targetcl instanceof ExecutableElement)) @@ -78,7 +80,7 @@ public class ButtonProcessor extends AbstractProcessor { cs.set("params", ((ExecutableElement) targetcl).getParameters().stream().skip(1).map(p -> { //String tn=p.asType().toString(); //return tn.substring(tn.lastIndexOf('.')+1)+" "+p.getSimpleName(); - boolean optional = p.getAnnotationMirrors().stream().anyMatch(am -> am.getAnnotationType().toString().endsWith("Optional")); + boolean optional = p.getAnnotationMirrors().stream().anyMatch(am -> am.getAnnotationType().toString().endsWith("OptionalArg")); if (optional) return "[" + p.getSimpleName() + "]"; return "<" + p.getSimpleName() + ">"; @@ -91,20 +93,4 @@ public class ButtonProcessor extends AbstractProcessor { public SourceVersion getSupportedSourceVersion() { return SourceVersion.latestSupported(); } - - private String fetchSourcePath() { - try { - JavaFileObject generationForPath = processingEnv.getFiler().createSourceFile("PathFor" + getClass().getSimpleName()); - Writer writer = generationForPath.openWriter(); - String sourcePath = generationForPath.toUri().getPath(); - writer.close(); - generationForPath.delete(); - - return sourcePath; - } catch (IOException e) { - processingEnv.getMessager().printMessage(Diagnostic.Kind.WARNING, "Unable to determine source file path!"); - } - - return ""; - } } diff --git a/ButtonProcessor/src/main/java/buttondevteam/buttonproc/ConfigProcessor.java b/ButtonProcessor/src/main/java/buttondevteam/buttonproc/ConfigProcessor.java new file mode 100644 index 0000000..f244553 --- /dev/null +++ b/ButtonProcessor/src/main/java/buttondevteam/buttonproc/ConfigProcessor.java @@ -0,0 +1,43 @@ +package buttondevteam.buttonproc; + +import org.bukkit.configuration.file.YamlConfiguration; + +import javax.annotation.processing.ProcessingEnvironment; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.tools.FileObject; +import javax.tools.StandardLocation; +import java.io.File; +import java.io.IOException; + +public class ConfigProcessor { + private final ProcessingEnvironment procEnv; + private final YamlConfiguration yaml; + private final File file; + + public ConfigProcessor(ProcessingEnvironment procEnv) { + this.procEnv = procEnv; + FileObject file = null; + try { + file = procEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", "config.yml"); + } catch (IOException e) { + e.printStackTrace(); + } + yaml = new YamlConfiguration(); + this.file = new File(file.toUri()); + } + + public void process(Element targetcl) { + if (targetcl.getModifiers().contains(Modifier.ABSTRACT)) return; + String javadoc = procEnv.getElementUtils().getDocComment(targetcl); + if (javadoc == null) return; + System.out.println("JAVADOC"); //TODO: Config methods + System.out.println(javadoc); + yaml.set("components." + targetcl.getSimpleName() + "._doc", javadoc); + try { + yaml.save(file); + } catch (IOException e) { + e.printStackTrace(); + } + } +}