New command system improvements, broadcast toggles, config fixes #62

Merged
NorbiPeti merged 23 commits from dev into master 2019-03-17 01:27:43 +00:00
43 changed files with 920 additions and 280 deletions

View file

@ -218,7 +218,7 @@ pip-log.txt
.mr.developer.cfg
.metadata/*
TheButtonAutoFlair/out/artifacts/Autoflair/Autoflair.jar
*.iml
#*.iml
*.name
.idea/compiler.xml
*.xml

View file

@ -1,13 +1,13 @@
<component name="libraryTable">
<library name="Maven: org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT">
<CLASSES>
<root url="jar://$MAVEN_REPOSITORY$/org/spigotmc/spigot-api/1.12.2-R0.1-SNAPSHOT/spigot-api-1.12.2-R0.1-SNAPSHOT.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/spigotmc/spigot-api/1.12.2-R0.1-SNAPSHOT/spigot-api-1.12.2-R0.1-20180712.012057-156.jar!/" />
</CLASSES>
<JAVADOC>
<root url="jar://$MAVEN_REPOSITORY$/org/spigotmc/spigot-api/1.12.2-R0.1-SNAPSHOT/spigot-api-1.12.2-R0.1-SNAPSHOT-javadoc.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/spigotmc/spigot-api/1.12.2-R0.1-SNAPSHOT/spigot-api-1.12.2-R0.1-20180712.012057-156-javadoc.jar!/" />
</JAVADOC>
<SOURCES>
<root url="jar://$MAVEN_REPOSITORY$/org/spigotmc/spigot-api/1.12.2-R0.1-SNAPSHOT/spigot-api-1.12.2-R0.1-SNAPSHOT-sources.jar!/" />
<root url="jar://$MAVEN_REPOSITORY$/org/spigotmc/spigot-api/1.12.2-R0.1-SNAPSHOT/spigot-api-1.12.2-R0.1-20180712.012057-156-sources.jar!/" />
</SOURCES>
</library>
</component>

View file

@ -12,7 +12,6 @@
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="ButtonCore (1) (com.github.TBMCPlugins.ButtonCore)" />
<orderEntry type="module" module-name="ButtonCore (1) (com.github.TBMCPlugins.ButtonCore)" />
<orderEntry type="library" name="Maven: org.reflections:reflections:0.9.10" level="project" />
<orderEntry type="library" name="Maven: com.google.code.findbugs:annotations:2.0.1" level="project" />
<orderEntry type="library" name="Maven: org.javassist:javassist:3.20.0-GA" level="project" />

View file

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/src/main/resources" type="java-resource" />
<sourceFolder url="file://$MODULE_DIR$/src/test/java" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: org.reflections:reflections:0.9.10" level="project" />
<orderEntry type="library" name="Maven: com.google.guava:guava:15.0" level="project" />
<orderEntry type="library" name="Maven: com.google.code.findbugs:annotations:2.0.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.spigotmc:spigot-api:1.12.2-R0.1-SNAPSHOT" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: commons-lang:commons-lang:2.6" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.googlecode.json-simple:json-simple:1.1.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.gson:gson:2.8.0" level="project" />
<orderEntry type="library" name="Maven: org.yaml:snakeyaml:1.19" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.md-5:bungeecord-chat:1.12-SNAPSHOT" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: commons-io:commons-io:1.3.2" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.github.TBMCPlugins.ButtonCore:Towny:master-98b73aaac3-1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.github.milkbowl:VaultAPI:master-68f14eca20-1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.bukkit:bukkit:1.13.1-R0.1-SNAPSHOT" level="project" />
<orderEntry type="library" name="Maven: org.javassist:javassist:3.20.0-GA" level="project" />
<orderEntry type="library" name="Maven: org.mockito:mockito-core:2.7.20" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: net.bytebuddy:byte-buddy:1.6.11" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: net.bytebuddy:byte-buddy-agent:1.6.11" level="project" />
<orderEntry type="library" scope="RUNTIME" name="Maven: org.objenesis:objenesis:2.5" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.projectlombok:lombok:1.16.16" level="project" />
<orderEntry type="module" module-name="ButtonProcessor" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.ess3:Essentials:2.13.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.vexsoftware:nuvotifier-universal:2.3.4" level="project" />
<orderEntry type="library" scope="TEST" name="Maven: junit:junit:3.8.1" level="project" />
</component>
</module>

View file

@ -75,6 +75,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
<useSystemClassLoader>false
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
@ -83,6 +84,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-source-plugin</artifactId>
<version>3.0.1</version>
<executions>
<execution>
<id>attach-sources</id>
@ -103,14 +105,18 @@
<id>jitpack.io</id>
<url>https://jitpack.io/</url>
</repository>
<repository>
<id>vault-repo</id>
<url>http://nexus.hc.to/content/repositories/pub_releases</url>
</repository>
<!-- <repository>
<id>vault-repo</id>
<url>http://nexus.hc.to/content/repositories/pub_releases</url>
</repository> -->
<repository>
<id>ess-repo</id>
<url>http://repo.ess3.net/content/repositories/essrel/</url>
</repository>
<repository>
<id>Votifier</id>
<url>https://dl.bintray.com/nuvotifier/maven/</url>
</repository>
</repositories>
<dependencies>
<dependency>
@ -175,6 +181,12 @@
<version>2.13.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.vexsoftware</groupId>
<artifactId>nuvotifier-universal</artifactId>
<version>2.3.4</version>
<scope>provided</scope>
</dependency>
</dependencies>
<organization>
<name>TBMCPlugins</name>

View file

@ -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<Component> getComponentOrError(Plugin plugin, String arg, CommandSender sender) {
private Optional<Component<?>> 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();

View file

@ -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 <T extends ButtonPlugin> 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<T>) plugin.getComponentStack().pop()); //Components are pushed on register
componentsEnabled = false;
}

View file

@ -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<Boolean> writePluginList() {
return getIConfig().getData("writePluginList", false);
}
ConfigData<String> 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<Permission> 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> T setupProvider(Class<T> cl) {
RegisteredServiceProvider<T> 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

View file

@ -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);
}
}

View file

@ -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));
}

View file

@ -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.");
}
}

View file

@ -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

View file

@ -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);
}
}

View file

@ -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 <add|remove> <player>" //
};
}
}

View file

@ -14,15 +14,35 @@ import java.util.Date;
import static buttondevteam.core.MainPlugin.permission;
public class MemberComponent extends Component implements Listener {
private ConfigData<String> memberGroup() {
/**
* Allows giving a 'member' group over some time elapsed OR played.
*/
public class MemberComponent extends Component<MainPlugin> implements Listener {
/**
* The permission group to give to the player
*/
ConfigData<String> memberGroup() {
return getConfig().getData("memberGroup", "member");
}
/**
* The amount of hours needed to play before promotion
*/
private ConfigData<Integer> playedHours() {
return getConfig().getData("playedHours", 12);
}
/**
* The amount of days passed since first login
*/
private ConfigData<Integer> 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.");

View file

@ -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<MainPlugin> {
@Override
protected void enable() {
new RandomTP().onEnable(this); //It registers it's command

View file

@ -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;

View file

@ -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<MainPlugin> 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);
}
}
}

View file

@ -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." //
};
}
}

View file

@ -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<MainPlugin> {
@Override
protected void enable() {
}

View file

@ -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<MainPlugin> { //TODO: Config
@Override
public void enable() {
TBMCChatAPI.AddCommand(this, new UpdatePluginCommand());

View file

@ -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<MainPlugin> {
private final Economy economy;
private ConfigData<Double> 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.");
}
}
}

View file

@ -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

View file

@ -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<BroadcastTarget> 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<BroadcastTarget> stream() {
return targets.stream();
}
}
}

View file

@ -35,4 +35,20 @@ public final class ThorpeUtils {
*/
String getFancyFullName();
}
public static Number convertNumber(Number number, Class<? extends Number> 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;
}
}

View file

@ -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<Component> componentStack = new Stack<>();
private Stack<Component<?>> 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;
}
}

View file

@ -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<Class<? extends Component>, Component> components = new HashMap<>();
@HasConfig //Used for obtaining javadoc
public abstract class Component<TP extends JavaPlugin> {
private static HashMap<Class<? extends Component>, Component<? extends JavaPlugin>> 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<Boolean> 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 <T extends JavaPlugin> boolean registerComponent(T plugin, Component<T> 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 <T extends JavaPlugin> boolean unregisterComponent(T plugin, Component<T> component) {
return registerUnregisterComponent(plugin, component, false);
}
public static boolean registerUnregisterComponent(JavaPlugin plugin, Component component, boolean register) {
public static <T extends JavaPlugin> boolean registerUnregisterComponent(T plugin, Component<T> 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<Class<? extends Component>, Component> getComponents() {
public static Map<Class<? extends Component>, Component<? extends JavaPlugin>> 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<String, IHaveConfig> getConfigMap(String key, Map<String, Consumer<IHaveConfig>> 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();
}

View file

@ -8,5 +8,5 @@ import java.lang.annotation.Target;
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface ComponentMetadata {
Class<? extends Component>[] depends();
Class<? extends Component>[] depends() default {};
}

View file

@ -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<T> { //TODO: Save after a while
public class ConfigData<T> {
private static final HashMap<Configuration, SaveTask> saveTasks = new HashMap<>();
/**
* May be null for testing
*/
@ -22,6 +32,7 @@ public class ConfigData<T> { //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<T> { //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<Object, T> getter, Function<T, Object> setter) {
//This constructor is needed because it sets the getter and setter
public ConfigData(ConfigurationSection config, String path, T def, Object primitiveDef, Function<Object, T> getter, Function<T, Object> 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<T> { //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<T> { //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<? extends Number>) def.getClass());
if (val instanceof List && def != null && def.getClass().isArray())
val = ((List<T>) 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;
}
}

View file

@ -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 {
}

View file

@ -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<String, ConfigData<?>> 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 <T> ConfigData<T> 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<T>) data;
}
@ -54,7 +58,7 @@ public final class IHaveConfig {
public <T> ConfigData<T> getData(String path, T def, Function<Object, T> getter, Function<T, Object> 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<T>) data;
}
@ -72,7 +76,7 @@ public final class IHaveConfig {
public <T> ConfigData<T> getDataPrimDef(String path, Object primitiveDef, Function<Object, T> getter, Function<T, Object> 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<T>) 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<T>) 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<T>) data;
}

View file

@ -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<TC extends ICommand2, TP extends Command2Sender> {
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<T extends Command2> {
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<T extends ICommand2> {
public final Method method;
public final T command;
public String[] helpText;
}
@RequiredArgsConstructor
protected static class ParamConverter<T> {
public final Function<String, T> converter;
public final String errormsg;
}
private HashMap<String, SubcommandData<TC>> subcommands = new HashMap<>();
private HashMap<Class<?>, ParamConverter<?>> paramConverters = new HashMap<>();
private ArrayList<String> 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 <T> The type of the result
*/
protected static <T> void addParamConverter(Class<T> cl, Function<String, T> converter, HashMap<Class<?>, Function<String, ?>> map) {
map.put(cl, converter);
public <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg) {
paramConverters.put(cl, new ParamConverter<>(converter, errormsg));
}
protected static <T extends Command2> boolean handleCommand(CommandSender sender, String commandline,
HashMap<String, SubcommandData<T>> subcommands, HashMap<Class<?>, Function<String, ?>> 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<TC> 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<Object>(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<? extends Number>) 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 <T extends Command2> void registerCommand(T command, HashMap<String, SubcommandData<T>> 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<String>();
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.<br>
* For example:<br>
* "u admin updateplugin" or "u" for the top level one<br>
* <u>The path must be lowercase!</u><br>
*
* @return The command path, <i>which is the command class name by default</i> (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<Class<?>, 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
}

View file

@ -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<String, SubcommandData<Command2MC>> subcommands = new HashMap<>();
private static HashMap<Class<?>, Function<String, ?>> paramConverters = new HashMap<>();
public static boolean handleCommand(CommandSender sender, String commandLine) throws Exception {
return handleCommand(sender, commandLine, subcommands, paramConverters);
public class Command2MC extends Command2<ICommand2MC, Command2MCSender> {
@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 <T> void addParamConverter(Class<T> cl, Function<String, T> converter) {
addParamConverter(cl, converter, paramConverters);
/**
* Returns true if this class or <u>any</u> 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 <T> void addParamConverter(Class<T> cl, Function<String, T> converter, String errormsg) {
super.addParamConverter(cl, converter, "§c" + errormsg);
}
}

View file

@ -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);
}
}

View file

@ -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);
}

View file

@ -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.<br>
* <b>The fist line will be converted to a header.</b>
*
* @return The help text
*/

View file

@ -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<TP extends Command2Sender> {
/**
* 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.<br>
* 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<?, TP> 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 <T extends ICommand2> ICommand2(Command2<T, TP> manager) {
path = getcmdpath();
this.manager = manager;
}
/**
* The command's path, or name if top-level command.<br>
* For example:<br>
* "u admin updateplugin" or "u" for the top level one<br>
* <u>The path must be lowercase!</u><br>
*
* @return The command path, <i>which is the command class name by default</i> (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<Class<?>, 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;
}
}

View file

@ -0,0 +1,9 @@
package buttondevteam.lib.chat;
import buttondevteam.lib.architecture.ButtonPlugin;
public class ICommand2MC extends ICommand2<Command2MCSender> {
public ICommand2MC() {
super(ButtonPlugin.getCommand2MC());
}
}

View file

@ -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();
}

View file

@ -15,4 +15,9 @@ commands:
description: Add or remove a member
component:
description: Enable or disable or list components
dontrunthiscmd:
dontrunthiscmd:
depend:
- Vault
softdepend:
- Towny
- Votifier

View file

@ -46,6 +46,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M3</version>
<configuration>
<useSystemClassLoader>false
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->

View file

@ -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<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (configProcessor == null)
configProcessor = new ConfigProcessor(processingEnv);
for (TypeElement te : annotations) {
Set<? extends Element> 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<? extends AnnotationMirror> 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 "";
}
}

View file

@ -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();
}
}
}