Compare commits

..

No commits in common. "master" and "v1.0-pre7" have entirely different histories.

48 changed files with 1102 additions and 837 deletions

View file

@ -1,75 +0,0 @@
# For most projects, this workflow file will not need changing; you simply need
# to commit it to your repository.
#
# You may wish to alter this file to override the set of languages analyzed,
# or to provide custom queries or build logic.
name: "CodeQL"
on:
push:
branches: [ master ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master ]
schedule:
- cron: '0 10 * * 1'
jobs:
analyze:
name: Analyze
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
# Override automatic language detection by changing the below list
# Supported options are ['csharp', 'cpp', 'go', 'java', 'javascript', 'python']
language: [ 'java' ]
# Learn more...
# https://docs.github.com/en/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#overriding-automatic-language-detection
steps:
- name: Setup Java JDK
uses: actions/setup-java@v1.3.0
with:
java-version: 11
- name: Checkout repository
uses: actions/checkout@v2
with:
# We must fetch at least the immediate parents so that if this is
# a pull request then we can checkout the head.
fetch-depth: 2
# If this run was triggered by a pull request event, then checkout
# the head of the pull request instead of the merge commit.
- run: git checkout HEAD^2
if: ${{ github.event_name == 'pull_request' }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
with:
languages: ${{ matrix.language }}
# If you wish to specify custom queries, you can do so here or in a config file.
# By default, queries listed here will override any specified in a config file.
# Prefix the list here with "+" to use these queries and those in the config file.
# queries: ./path/to/local/query, your-org/your-repo/queries@main
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v1
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
# and modify them (or add more) to build your code if your project
# uses a compiled language
#- run: |
# make bootstrap
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1

2
ButtonProcessor/pom.xml Normal file → Executable file
View file

@ -11,7 +11,7 @@
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.32</version>
<version>1.21</version>
<scope>compile</scope>
</dependency>
<dependency>

View file

@ -21,29 +21,35 @@ import java.util.stream.Collectors;
@SupportedAnnotationTypes("buttondevteam.*")
public class ButtonProcessor extends AbstractProcessor {
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
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) {
List<? extends AnnotationMirror> annotationMirrors = processingEnv.getElementUtils()
.getAllAnnotationMirrors(targetcl);
Function<String, Boolean> hasAnnotation = ann -> annotationMirrors.stream()
.anyMatch(am -> am.getAnnotationType().toString().contains(ann));
if (hasAnnotation.apply("ChromaGamerEnforcer") && !hasAnnotation.apply("UserClass")
&& !targetcl.getModifiers().contains(Modifier.ABSTRACT))
processingEnv.getMessager().printMessage(Kind.ERROR,
"No UserClass annotation found for " + targetcl.getSimpleName(), targetcl);
if (hasAnnotation.apply("TBMCPlayerEnforcer") && !hasAnnotation.apply("PlayerClass")
&& !targetcl.getModifiers().contains(Modifier.ABSTRACT))
processingEnv.getMessager().printMessage(Kind.ERROR,
"No PlayerClass annotation found for " + targetcl.getSimpleName(), targetcl);
processSubcommands(targetcl, annotationMirrors);
if (hasAnnotation.apply("HasConfig"))
configProcessor.process(targetcl);
}
}
for (TypeElement te : annotations) {
Set<? extends Element> classes = roundEnv.getElementsAnnotatedWith(te);
for (Element targetcl : classes) {
System.out.println("Processing " + targetcl);
List<? extends AnnotationMirror> annotationMirrors = processingEnv.getElementUtils()
.getAllAnnotationMirrors(targetcl);
//System.out.println("Annotations: " + annotationMirrors);
Function<String, Boolean> hasAnnotation = ann -> annotationMirrors.stream()
.anyMatch(am -> am.getAnnotationType().toString().contains(ann));
if (hasAnnotation.apply("ChromaGamerEnforcer") && !hasAnnotation.apply("UserClass")
&& !targetcl.getModifiers().contains(Modifier.ABSTRACT))
processingEnv.getMessager().printMessage(Kind.ERROR,
"No UserClass annotation found for " + targetcl.getSimpleName(), targetcl);
if (hasAnnotation.apply("TBMCPlayerEnforcer") && !hasAnnotation.apply("PlayerClass")
&& !targetcl.getModifiers().contains(Modifier.ABSTRACT))
processingEnv.getMessager().printMessage(Kind.ERROR,
"No PlayerClass annotation found for " + targetcl.getSimpleName(), targetcl);
for (AnnotationMirror annotation : annotationMirrors) {
String type = annotation.getAnnotationType().toString();
//System.out.println("Type: " + type);
}
processSubcommands(targetcl, annotationMirrors);
if (hasAnnotation.apply("HasConfig"))
configProcessor.process(targetcl);
}
}
try {
if (found) {
FileObject fo = processingEnv.getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", "commands.yml");
@ -53,33 +59,38 @@ public class ButtonProcessor extends AbstractProcessor {
} catch (IOException e) {
e.printStackTrace();
}
return true; // claim the annotations
}
return true; // claim the annotations
}
private final YamlConfiguration yc = new YamlConfiguration();
private YamlConfiguration yc = new YamlConfiguration();
private boolean found = false;
private ConfigProcessor configProcessor;
private void processSubcommands(Element method, List<? extends AnnotationMirror> annotationMirrors) {
if (!(method instanceof ExecutableElement))
private void processSubcommands(Element targetcl, List<? extends AnnotationMirror> annotationMirrors) {
if (!(targetcl instanceof ExecutableElement))
return;
//System.out.println("Annotations: "+annotationMirrors);
if (annotationMirrors.stream().noneMatch(an -> an.getAnnotationType().toString().endsWith("Subcommand")))
return;
ConfigurationSection cs = yc.createSection(method.getEnclosingElement().toString()
+ "." + method.getSimpleName().toString()); //Need to do the 2 config sections at once so it doesn't overwrite the class section
System.out.println("Found subcommand: " + method);
cs.set("method", method.toString());
cs.set("params", ((ExecutableElement) method).getParameters().stream().skip(1).map(p -> {
//System.out.print("Processing method: " + targetcl.getEnclosingElement()+" "+targetcl);
ConfigurationSection cs = yc.createSection(targetcl.getEnclosingElement().toString()
+ "." + targetcl.getSimpleName().toString()); //Need to do the 2 config sections at once so it doesn't overwrite the class section
System.out.println(targetcl);
cs.set("method", targetcl.toString());
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("OptionalArg"));
if (optional)
return "[" + p.getSimpleName() + "]";
return "<" + p.getSimpleName() + ">";
}).collect(Collectors.joining(" ")));
//System.out.println();
found = true;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
}

View file

@ -48,6 +48,10 @@ public class ConfigProcessor {
e.printStackTrace();
}
for (Element e : targetcl.getEnclosedElements()) {
/*System.out.println("Element: "+e);
System.out.println("Type: "+e.getClass()+" - "+e.getKind());
if(e instanceof ExecutableElement)
System.out.println("METHOD!");*/
TypeMirror tm;
if (e instanceof ExecutableElement)
tm = ((ExecutableElement) e).getReturnType();
@ -59,16 +63,18 @@ public class ConfigProcessor {
DeclaredType dt = (DeclaredType) tm;
if (!dt.asElement().getSimpleName().toString().contains("ConfigData"))
continue; //Ahhha! There was a return here! (MinecraftChatModule getListener())
System.out.println("Config: " + e.getSimpleName());
String doc = procEnv.getElementUtils().getDocComment(e);
if (doc == null) continue;
System.out.println("Adding docs for config: " + e.getSimpleName());
System.out.println("DOC: " + doc);
yc.set(path + "." + e.getSimpleName(), doc.trim());
}
String javadoc = procEnv.getElementUtils().getDocComment(targetcl);
if (javadoc != null) {
System.out.println("Adding docs for class: " + targetcl.getSimpleName());
yc.set(path + ".generalDescriptionInsteadOfAConfig", javadoc.trim());
System.out.println("JAVADOC");
System.out.println(javadoc.trim());
yc.set(path, javadoc.trim());
}
try {
yc.save(file);

View file

@ -1,16 +1,15 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>CorePOM</artifactId>
<version>master-SNAPSHOT</version>
<relativePath>../CorePOM</relativePath>
</parent>
<parent>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>CorePOM</artifactId>
<version>master-SNAPSHOT</version>
<relativePath>../CorePOM</relativePath>
</parent>
<artifactId>Chroma-Core</artifactId>
<name>Chroma-Core</name>
<description>Chroma-Core</description>
<version>v${noprefix.version}-SNAPSHOT</version>
<build>
<sourceDirectory>src/main/java</sourceDirectory>
<resources>
@ -119,9 +118,13 @@
<id>ess-repo</id>
<url>https://ci.ender.zone/plugin/repository/everything/</url>
</repository>
<repository>
<id>Votifier</id>
<url>https://dl.bintray.com/nuvotifier/maven/</url>
</repository>
<repository>
<id>Multiverse-Core</id>
<url>https://repo.onarandombox.com/content/groups/public/</url>
<url>http://repo.onarandombox.com/content/repositories/multiverse/</url>
</repository>
<repository>
<id>minecraft-repo</id>
@ -132,7 +135,7 @@
<dependency>
<groupId>org.reflections</groupId>
<artifactId>reflections</artifactId>
<version>0.10.2</version>
<version>0.9.10</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -157,12 +160,12 @@
<dependency>
<groupId>org.javassist</groupId>
<artifactId>javassist</artifactId>
<version>3.28.0-GA</version>
<version>3.20.0-GA</version>
</dependency>
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-core</artifactId>
<version>4.2.0</version>
<version>3.0.0</version>
</dependency>
<dependency>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
@ -176,23 +179,22 @@
<version>2.17.1</version>
<scope>provided</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.NuVotifier.NuVotifier/nuvotifier-bukkit -->
<dependency>
<groupId>com.github.NuVotifier.NuVotifier</groupId>
<artifactId>nuvotifier-bukkit</artifactId>
<version>v2.7.1</version>
<groupId>com.vexsoftware</groupId>
<artifactId>nuvotifier-universal</artifactId>
<version>2.3.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.onarandombox.multiversecore</groupId>
<artifactId>Multiverse-Core</artifactId>
<version>4.3.1</version>
<version>4.0.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>me.lucko</groupId>
<artifactId>commodore</artifactId>
<version>1.11</version>
<version>1.8</version>
<scope>compile</scope>
</dependency>
<dependency>
@ -216,7 +218,6 @@
<!-- github server corresponds to entry in ~/.m2/settings.xml -->
<github.global.server>github</github.global.server>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<noprefix.version>1.0.1</noprefix.version>
</properties>
<scm>
<url>https://github.com/TBMCPlugins/mvn-repo</url>

View file

@ -80,7 +80,7 @@ public class ComponentCommand extends ICommand2MC {
return true;
Component.setComponentEnabled(oc.get(), enable);
if (permanent)
oc.get().shouldBeEnabled.set(enable);
oc.get().shouldBeEnabled().set(enable);
sender.sendMessage(oc.get().getClass().getSimpleName() + " " + (enable ? "en" : "dis") + "abled " + (permanent ? "permanently" : "temporarily") + ".");
} catch (Exception e) {
TBMCCoreAPI.SendException("Couldn't " + (enable ? "en" : "dis") + "able component " + component + "!", e, (JavaPlugin) plugin);

View file

@ -21,7 +21,7 @@ public final class ComponentManager {
*/
public static void enableComponents() {
//Component.getComponents().values().stream().filter(c->cs.getConfigurationSection(c.getClass().getSimpleName()).getBoolean("enabled")).forEach(c-> {
Component.getComponents().values().stream().filter(c -> c.shouldBeEnabled.get()).forEach(c -> {
Component.getComponents().values().stream().filter(c -> c.shouldBeEnabled().get()).forEach(c -> {
try {
Component.setComponentEnabled(c, true);
} catch (Exception | NoClassDefFoundError e) {

View file

@ -8,6 +8,7 @@ import buttondevteam.core.component.randomtp.RandomTPComponent;
import buttondevteam.core.component.restart.RestartComponent;
import buttondevteam.core.component.spawn.SpawnComponent;
import buttondevteam.core.component.towny.TownyComponent;
import buttondevteam.core.component.votifier.VotifierComponent;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ButtonPlugin;
import buttondevteam.lib.architecture.Component;
@ -64,26 +65,35 @@ public class MainPlugin extends ButtonPlugin {
* Sets whether the plugin should write a list of installed plugins in a txt file.
* It can be useful if some other software needs to know the plugins.
*/
private final ConfigData<Boolean> writePluginList = getIConfig().getData("writePluginList", false);
private ConfigData<Boolean> writePluginList() {
return getIConfig().getData("writePluginList", false);
}
/**
* The chat format to use for messages from other platforms if Chroma-Chat is not installed.
*/
ConfigData<String> chatFormat = getIConfig().getData("chatFormat", "[{origin}|" +
"{channel}] <{name}> {message}");
ConfigData<String> chatFormat() {
return getIConfig().getData("chatFormat", "[{origin}|" +
"{channel}] <{name}> {message}");
}
/**
* Print some debug information.
*/
public final ConfigData<Boolean> test = getIConfig().getData("test", false);
public ConfigData<Boolean> test() {
return getIConfig().getData("test", false);
}
/**
* If a Chroma command clashes with another plugin's command, this setting determines whether the Chroma command should be executed or the other plugin's.
*/
public final ConfigData<Boolean> prioritizeCustomCommands = getIConfig().getData("prioritizeCustomCommands", false);
public ConfigData<Boolean> prioritizeCustomCommands() {
return getIConfig().getData("prioritizeCustomCommands", false);
}
@Override
public void pluginEnable() {
// Logs "Plugin Enabled", registers commands
Instance = this;
PluginDescriptionFile pdf = getDescription();
logger = getLogger();
@ -93,6 +103,7 @@ public class MainPlugin extends ButtonPlugin {
getLogger().warning("No economy plugin found! Components using economy will not be registered.");
saveConfig();
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());
@ -100,8 +111,8 @@ public class MainPlugin extends ButtonPlugin {
Component.registerComponent(this, new SpawnComponent());
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));*/
if (Bukkit.getPluginManager().isPluginEnabled("Votifier") && economy != null)
Component.registerComponent(this, new VotifierComponent(economy));
ComponentManager.enableComponents();
registerCommand(new ComponentCommand());
registerCommand(new ChromaCommand());
@ -111,7 +122,7 @@ public class MainPlugin extends ButtonPlugin {
? 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, TBMCPlayer::new);
TBMCCoreAPI.RegisterUserClass(TBMCPlayerBase.class);
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)));
@ -127,7 +138,7 @@ public class MainPlugin extends ButtonPlugin {
Supplier<Iterable<String>> playerSupplier = () -> Bukkit.getOnlinePlayers().stream().map(HumanEntity::getName)::iterator;
getCommand2MC().addParamConverter(OfflinePlayer.class, Bukkit::getOfflinePlayer, "Player not found!", playerSupplier);
getCommand2MC().addParamConverter(Player.class, Bukkit::getPlayer, "Online player not found!", playerSupplier);
if (writePluginList.get()) {
if (writePluginList().get()) {
try {
Files.write(new File("plugins", "plugins.txt").toPath(), Arrays.stream(Bukkit.getPluginManager().getPlugins()).map(p -> (CharSequence) p.getDataFolder().getName())::iterator);
} catch (IOException e) {
@ -136,13 +147,13 @@ public class MainPlugin extends ButtonPlugin {
}
if (getServer().getPluginManager().isPluginEnabled("Essentials"))
ess = Essentials.getPlugin(Essentials.class);
logger.info(pdf.getName() + " has been Enabled (V." + pdf.getVersion() + ") Test: " + test.get() + ".");
logger.info(pdf.getName() + " has been Enabled (V." + pdf.getVersion() + ") Test: " + test().get() + ".");
}
@Override
public void pluginDisable() {
logger.info("Saving player data...");
ChromaGamerBase.saveUsers();
TBMCPlayerBase.savePlayers();
logger.info("Player data saved.");
}

View file

@ -28,21 +28,12 @@ public class PlayerListener implements Listener {
@EventHandler(priority = EventPriority.NORMAL)
public void OnPlayerJoin(PlayerJoinEvent event) {
var p = event.getPlayer();
TBMCPlayer player = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class);
String pname = player.PlayerName.get();
if (pname.length() == 0) {
player.PlayerName.set(p.getName());
MainPlugin.Instance.getLogger().info("Player name saved: " + player.PlayerName.get());
} else if (!p.getName().equals(pname)) {
MainPlugin.Instance.getLogger().info(pname + " renamed to " + p.getName());
player.PlayerName.set(p.getName());
}
TBMCPlayerBase.joinPlayer(event.getPlayer());
}
@EventHandler(priority = EventPriority.NORMAL)
public void OnPlayerLeave(PlayerQuitEvent event) {
TBMCPlayerBase.getPlayer(event.getPlayer().getUniqueId(), TBMCPlayer.class).uncache();
TBMCPlayerBase.quitPlayer(event.getPlayer());
}
@EventHandler(priority = EventPriority.HIGHEST)
@ -52,7 +43,7 @@ public class PlayerListener implements Listener {
if (Arrays.stream(event.getExceptions()).anyMatch("Minecraft"::equalsIgnoreCase))
return;
Bukkit.getOnlinePlayers().stream().filter(event::shouldSendTo)
.forEach(p -> p.sendMessage(event.getChannel().DisplayName.get().substring(0, 2) + event.getMessage()));
.forEach(p -> p.sendMessage(event.getChannel().DisplayName().get().substring(0, 2) + event.getMessage()));
}
@EventHandler
@ -68,9 +59,13 @@ public class PlayerListener implements Listener {
private void handlePreprocess(CommandSender sender, String message, Cancellable event) {
if (event.isCancelled()) return;
/*val cg = Optional.ofNullable(ChromaGamerBase.getFromSender(sender));
val ch = cg.map(ChromaGamerBase::channel).map(ChannelPlayerData::get);
val rtr = ch.map(c -> c.getRTR(sender)).orElseGet(() -> new Channel.RecipientTestResult("Failed to get user"));
val ev = new TBMCCommandPreprocessEvent(sender, ch.orElse(Channel.GlobalChat), message, rtr.score, rtr.groupID);*/
val cg = ChromaGamerBase.getFromSender(sender);
if (cg == null) throw new RuntimeException("Couldn't get user from sender for " + sender.getName() + "!");
val ev = new TBMCCommandPreprocessEvent(sender, cg.channel.get(), message, sender);
val ev = new TBMCCommandPreprocessEvent(sender, cg.channel().get(), message, sender);
Bukkit.getPluginManager().callEvent(ev);
if (ev.isCancelled())
event.setCancelled(true); //Cancel the original event
@ -103,11 +98,11 @@ public class PlayerListener implements Listener {
if (!MainPlugin.Instance.isChatHandlerEnabled()) return;
if (event.getOrigin().equals("Minecraft")) return; //Let other plugins handle MC messages
var channel = event.getChannel();
String msg = MainPlugin.Instance.chatFormat.get()
.replace("{channel}", channel.DisplayName.get())
String msg = MainPlugin.Instance.chatFormat().get()
.replace("{channel}", channel.DisplayName().get())
.replace("{origin}", event.getOrigin().substring(0, 1))
.replace("{name}", ChromaUtils.getDisplayName(event.getSender()))
.replace("{message}", String.format("§%x%s", channel.Color.get().ordinal(), event.getMessage()));
.replace("{message}", String.format("§%x%s", channel.Color().get().ordinal(), event.getMessage()));
for (Player player : Bukkit.getOnlinePlayers())
if (event.shouldSendTo(player))
player.sendMessage(msg);

View file

@ -44,6 +44,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

@ -4,7 +4,6 @@ import buttondevteam.core.ComponentManager;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.IHaveConfig;
import buttondevteam.lib.chat.Color;
import com.google.common.collect.Lists;
import org.bukkit.Bukkit;
@ -21,135 +20,127 @@ import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Stream;
/**
* Represents a chat channel. May only be instantiated after the channel component is registered.
*/
public class Channel {
/**
* Specifies a score that means it's OK to send - but it does not define any groups, only send or not send. See {@link #GROUP_EVERYONE}
*/
public static final int SCORE_SEND_OK = 0;
/**
* Specifies a score that means the user doesn't have permission to see or send the message. Any negative value has the same effect.
*/
public static final int SCORE_SEND_NOPE = -1;
/**
* Send the message to everyone <i>who has access to the channel</i> - this does not necessarily mean all players
*/
public static final String GROUP_EVERYONE = "everyone";
/**
* Specifies a score that means it's OK to send - but it does not define any groups, only send or not send. See {@link #GROUP_EVERYONE}
*/
public static final int SCORE_SEND_OK = 0;
/**
* Specifies a score that means the user doesn't have permission to see or send the message. Any negative value has the same effect.
*/
public static final int SCORE_SEND_NOPE = -1;
/**
* Send the message to everyone <i>who has access to the channel</i> - this does not necessarily mean all players
*/
public static final String GROUP_EVERYONE = "everyone";
private static ChannelComponent component;
private String defDisplayName;
private Color defColor;
private IHaveConfig config;
private void throwGame() {
if (component == null) throw new RuntimeException("Cannot access channel properties until registered!");
}
public final ConfigData<Boolean> Enabled;
public final ConfigData<Boolean> Enabled() {
throwGame();
return component.getConfig().getData(ID + ".enabled", true);
}
/**
* Must start with a color code
*/
public final ConfigData<String> DisplayName;
public final ConfigData<Color> Color;
public final String ID;
public ConfigData<String[]> IDs;
/**
* Filters both the sender and the targets
*/
private final Function<CommandSender, RecipientTestResult> filteranderrormsg;
private static final List<Channel> channels = new ArrayList<>();
/**
* Creates a channel.
*
* @param displayname The name that should appear at the start of the message. <b>A chat color is expected at the beginning (§9).</b>
* @param color The default color of the messages sent in the channel
* @param command The command to be used for the channel <i>without /</i>. For example "mod". It's also used for scoreboard objective names.
* @param filteranderrormsg Checks all senders against the criteria provided here and sends the message if the index matches the sender's - if no score at all, displays the error.<br>
* May be null to send to everyone.
*/
public Channel(String displayname, Color color, String command,
Function<CommandSender, RecipientTestResult> filteranderrormsg) {
defDisplayName = displayname;
defColor = color;
ID = command;
this.filteranderrormsg = filteranderrormsg;
init();
Enabled = component.getConfig().getData(ID + ".enabled", true);
DisplayName = component.getConfig().getData(ID + ".displayName", defDisplayName);
Color = component.getConfig().getData(ID + ".color", defColor, c -> buttondevteam.lib.chat.Color.valueOf((String) c), Enum::toString);
//noinspection unchecked
IDs = component.getConfig().getData(ID + ".IDs", new String[0], l -> ((List<String>) l).toArray(new String[0]), Lists::newArrayList);
public final ConfigData<String> DisplayName() {
throwGame();
return component.getConfig().getData(ID + ".displayName", defDisplayName); //TODO: Use config map
}
/**
* Must be only called from a subclass - otherwise it'll throw an exception.
*
* @see Channel#Channel(String, Color, String, Function)
*/
public final ConfigData<Color> Color() {
throwGame();
return component.getConfig().getData(ID + ".color", defColor, c -> Color.valueOf((String) c), Enum::toString);
}
public final String ID;
@SuppressWarnings("unchecked")
protected <T extends Channel> Channel(String displayname, Color color, String command,
BiFunction<T, CommandSender, RecipientTestResult> filteranderrormsg) {
defDisplayName = displayname;
defColor = color;
ID = command;
this.filteranderrormsg = s -> filteranderrormsg.apply((T) this, s);
init();
Enabled = component.getConfig().getData(ID + ".enabled", true);
DisplayName = component.getConfig().getData(ID + ".displayName", defDisplayName);
Color = component.getConfig().getData(ID + ".color", defColor, c -> buttondevteam.lib.chat.Color.valueOf((String) c), Enum::toString);
//noinspection unchecked
IDs = component.getConfig().getData(ID + ".IDs", new String[0], l -> ((List<String>) l).toArray(new String[0]), Lists::newArrayList);
public ConfigData<String[]> IDs() {
throwGame();
return component.getConfig().getData(ID + ".IDs", new String[0], l -> ((List<String>) l).toArray(new String[0]), Lists::newArrayList);
}
/**
* Filters both the sender and the targets
*/
private final Function<CommandSender, RecipientTestResult> filteranderrormsg;
private static void init() {
if (component == null)
component = (ChannelComponent) Component.getComponents().get(ChannelComponent.class);
if (component == null)
throw new RuntimeException("Attempting to create a channel before the component is registered!");
}
private static final List<Channel> channels = new ArrayList<>();
public boolean isGlobal() {
return filteranderrormsg == null;
}
/**
* Creates a channel.
*
* @param displayname The name that should appear at the start of the message. <b>A chat color is expected at the beginning (§9).</b>
* @param color The default color of the messages sent in the channel
* @param command The command to be used for the channel <i>without /</i>. For example "mod". It's also used for scoreboard objective names.
* @param filteranderrormsg Checks all senders against the criteria provided here and sends the message if the index matches the sender's - if no score at all, displays the error.<br>
* May be null to send to everyone.
*/
public Channel(String displayname, Color color, String command,
Function<CommandSender, RecipientTestResult> filteranderrormsg) {
defDisplayName = displayname;
defColor = color;
ID = command;
this.filteranderrormsg = filteranderrormsg;
}
/**
* Note: Errors are sent to the sender automatically
*
* @param sender The user we're sending to
* @param score The (source) score to compare with the user's
*/
public boolean shouldSendTo(CommandSender sender, int score) {
return score == getMCScore(sender); //If there's any error, the score won't be equal
}
/**
* Must be only called from a subclass - otherwise it'll throw an exception.
*
* @see Channel#Channel(String, Color, String, Function)
*/
@SuppressWarnings("unchecked")
protected <T extends Channel> Channel(String displayname, Color color, String command,
BiFunction<T, CommandSender, RecipientTestResult> filteranderrormsg) {
defDisplayName = displayname;
defColor = color;
ID = command;
this.filteranderrormsg = s -> filteranderrormsg.apply((T) this, s);
}
/**
* Note: Errors are sent to the sender automatically
*/
public int getMCScore(CommandSender sender) {
return getRTR(sender).score; //No need to check if there was an error
}
public boolean isGlobal() {
return filteranderrormsg == null;
}
/**
* Note: Errors are sent to the sender automatically<br>
* <p>
* Null means don't send
*/
@Nullable
public String getGroupID(CommandSender sender) {
return getRTR(sender).groupID; //No need to check if there was an error
}
/**
* Note: Errors are sent to the sender automatically
*
* @param sender The user we're sending to
* @param score The (source) score to compare with the user's
*/
public boolean shouldSendTo(CommandSender sender, int score) {
return score == getMCScore(sender); //If there's any error, the score won't be equal
}
public RecipientTestResult getRTR(CommandSender sender) {
if (filteranderrormsg == null)
return new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE);
return filteranderrormsg.apply(sender);
}
/**
* Note: Errors are sent to the sender automatically
*/
public int getMCScore(CommandSender sender) {
return getRTR(sender).score; //No need to check if there was an error
}
/**
* Note: Errors are sent to the sender automatically<br>
* <p>
* Null means don't send
*/
@Nullable
public String getGroupID(CommandSender sender) {
return getRTR(sender).groupID; //No need to check if there was an error
}
public RecipientTestResult getRTR(CommandSender sender) {
if (filteranderrormsg == null)
return new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE);
return filteranderrormsg.apply(sender);
}
/**
* Get a stream of the enabled channels
@ -157,7 +148,7 @@ public class Channel {
* @return Only the enabled channels
*/
public static Stream<Channel> getChannels() {
return channels.stream().filter(ch -> ch.Enabled.get());
return channels.stream().filter(ch -> ch.Enabled().get());
}
/**
@ -169,69 +160,73 @@ public class Channel {
return Collections.unmodifiableList(channels);
}
/**
* Convenience method for the function parameter of {@link #Channel(String, Color, String, Function)}. It checks if the sender is OP or optionally has the specified group. The error message is
* generated automatically.
*
* @param permgroup The group that can access the channel or <b>null</b> to only allow OPs.
* @return If has access
*/
public static Function<CommandSender, RecipientTestResult> inGroupFilter(String permgroup) {
return noScoreResult(
s -> s.isOp() || (permgroup != null && (s instanceof Player && MainPlugin.permission != null && MainPlugin.permission.playerInGroup((Player) s, permgroup))),
"You need to be a(n) " + (permgroup != null ? permgroup : "OP") + " to use this channel.");
}
/**
* Convenience method for the function parameter of {@link #Channel(String, Color, String, Function)}. It checks if the sender is OP or optionally has the specified group. The error message is
* generated automatically.
*
* @param permgroup The group that can access the channel or <b>null</b> to only allow OPs.
* @return If has access
*/
public static Function<CommandSender, RecipientTestResult> inGroupFilter(String permgroup) {
return noScoreResult(
s -> s.isOp() || (permgroup != null && (s instanceof Player && MainPlugin.permission != null && MainPlugin.permission.playerInGroup((Player) s, permgroup))),
"You need to be a(n) " + (permgroup != null ? permgroup : "OP") + " to use this channel.");
}
public static Function<CommandSender, RecipientTestResult> noScoreResult(Predicate<CommandSender> filter,
String errormsg) {
return s -> filter.test(s) ? new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE) : new RecipientTestResult(errormsg);
}
public static Function<CommandSender, RecipientTestResult> noScoreResult(Predicate<CommandSender> filter,
String errormsg) {
return s -> filter.test(s) ? new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE) : new RecipientTestResult(errormsg);
}
public static <T extends Channel> BiFunction<T, CommandSender, RecipientTestResult> noScoreResult(
BiPredicate<T, CommandSender> filter, String errormsg) {
return (this_, s) -> filter.test(this_, s) ? new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE) : new RecipientTestResult(errormsg);
}
public static <T extends Channel> BiFunction<T, CommandSender, RecipientTestResult> noScoreResult(
BiPredicate<T, CommandSender> filter, String errormsg) {
return (this_, s) -> filter.test(this_, s) ? new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE) : new RecipientTestResult(errormsg);
}
public static Channel GlobalChat;
public static Channel AdminChat;
public static Channel ModChat;
public static Channel GlobalChat;
public static Channel AdminChat;
public static Channel ModChat;
public static void RegisterChannel(Channel channel) {
if (!channel.isGlobal() && !ComponentManager.isEnabled(ChannelComponent.class))
return; //Allow registering the global chat (and I guess other chats like the RP chat)
if (component == null)
component = (ChannelComponent) Component.getComponents().get(ChannelComponent.class);
if (component == null)
throw new RuntimeException("Attempting to register a channel before the component is registered!");
channels.add(channel);
component.registerChannelCommand(channel);
Bukkit.getScheduler().runTask(MainPlugin.Instance, () -> Bukkit.getPluginManager().callEvent(new ChatChannelRegisterEvent(channel))); // Wait for server start
}
}
public static class RecipientTestResult {
public final String errormessage;
public final int score; // Anything below 0 is "never send"
public final String groupID;
public static final RecipientTestResult ALL = new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE);
public static class RecipientTestResult {
public final String errormessage;
public final int score; // Anything below 0 is "never send"
public final String groupID;
public static final RecipientTestResult ALL = new RecipientTestResult(SCORE_SEND_OK, GROUP_EVERYONE);
/**
* Creates a result that indicates an <b>error</b>
*
* @param errormessage The error message to show the sender if they don't meet the criteria.
*/
public RecipientTestResult(String errormessage) {
this.errormessage = errormessage;
this.score = SCORE_SEND_NOPE;
this.groupID = null;
}
/**
* Creates a result that indicates an <b>error</b>
*
* @param errormessage The error message to show the sender if they don't meet the criteria.
*/
public RecipientTestResult(String errormessage) {
this.errormessage = errormessage;
this.score = SCORE_SEND_NOPE;
this.groupID = null;
}
/**
* Creates a result that indicates a <b>success</b>
*
* @param score The score that identifies the target group. <b>Must be non-negative.</b> For example, the index of the town or nation to send to.
* @param groupID The ID of the target group.
*/
public RecipientTestResult(int score, String groupID) {
if (score < 0) throw new IllegalArgumentException("Score must be non-negative!");
this.score = score;
this.groupID = groupID;
this.errormessage = null;
}
}
/**
* Creates a result that indicates a <b>success</b>
*
* @param score The score that identifies the target group. <b>Must be non-negative.</b> For example, the index of the town or nation to send to.
* @param groupID The ID of the target group.
*/
public RecipientTestResult(int score, String groupID) {
if (score < 0) throw new IllegalArgumentException("Score must be non-negative!");
this.score = score;
this.groupID = groupID;
this.errormessage = null;
}
}
}

View file

@ -11,7 +11,7 @@ import org.bukkit.plugin.java.JavaPlugin;
/**
* Manages chat channels. If disabled, only global channels will be registered.
*/
public class ChannelComponent extends Component<JavaPlugin> {
public class ChannelComponent extends Component {
static TBMCSystemChatEvent.BroadcastTarget roomJoinLeave;
@Override
@ -47,12 +47,7 @@ public class ChannelComponent extends Component<JavaPlugin> {
@Override
public String getCommandPath() {
return channel.ID;
}
@Override
public String[] getCommandPaths() {
return channel.IDs.get();
return channel.ID; //TODO: IDs
}
@Command2.Subcommand
@ -64,17 +59,17 @@ public class ChannelComponent extends Component<JavaPlugin> {
return;
}
if (message == null) {
Channel oldch = user.channel.get();
Channel oldch = user.channel().get();
if (oldch instanceof ChatRoom)
((ChatRoom) oldch).leaveRoom(sender);
if (oldch.equals(channel))
user.channel.set(Channel.GlobalChat);
user.channel().set(Channel.GlobalChat);
else {
user.channel.set(channel);
user.channel().set(channel);
if (channel instanceof ChatRoom)
((ChatRoom) channel).joinRoom(sender);
}
sender.sendMessage("§6You are now talking in: §b" + user.channel.get().DisplayName.get());
sender.sendMessage("§6You are now talking in: §b" + user.channel().get().DisplayName().get());
} else
TBMCChatAPI.SendChatMessage(ChatMessage.builder(sender, user, message).fromCommand(true)
.permCheck(senderMC.getPermCheck()).build(), channel);

View file

@ -38,8 +38,8 @@ public class MemberCommand extends ICommand2MC {
sender.sendMessage("§cCannot find player or haven't played before.");
return;
}
if (add ? MainPlugin.permission.playerAddGroup(null, op, component.memberGroup.get())
: MainPlugin.permission.playerRemoveGroup(null, op, component.memberGroup.get()))
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!");

View file

@ -25,17 +25,23 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
/**
* The permission group to give to the player
*/
final ConfigData<String> memberGroup = getConfig().getData("memberGroup", "member");
ConfigData<String> memberGroup() {
return getConfig().getData("memberGroup", "member");
}
/**
* The amount of hours needed to play before promotion
*/
private final ConfigData<Integer> playedHours = getConfig().getData("playedHours", 12);
private ConfigData<Integer> playedHours() {
return getConfig().getData("playedHours", 12);
}
/**
* The amount of days passed since first login
*/
private final ConfigData<Integer> registeredForDays = getConfig().getData("registeredForDays", 7);
private ConfigData<Integer> registeredForDays() {
return getConfig().getData("registeredForDays", 7);
}
private AbstractMap.SimpleEntry<Statistic, Integer> playtime;
@ -63,7 +69,7 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
public Boolean addPlayerAsMember(Player player) {
try {
if (permission.playerAddGroup(null, player, memberGroup.get())) {
if (permission.playerAddGroup(null, player, memberGroup().get())) {
player.sendMessage("§bYou are a member now!");
log("Added " + player.getName() + " as a member.");
return true;
@ -78,7 +84,7 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
}
public boolean checkNotMember(Player player) {
return permission != null && !permission.playerInGroup(player, memberGroup.get());
return permission != null && !permission.playerInGroup(player, memberGroup().get());
}
public boolean checkRegTime(Player player) {
@ -86,14 +92,14 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
}
public boolean checkPlayTime(Player player) {
return getPlayTime(player) > playtime.getValue() * playedHours.get();
return getPlayTime(player) > playtime.getValue() * playedHours().get();
}
/**
* Returns milliseconds
*/
public long getRegTime(Player player) {
Instant date = new Date(player.getFirstPlayed()).toInstant().plus(registeredForDays.get(), ChronoUnit.DAYS);
Instant date = new Date(player.getFirstPlayed()).toInstant().plus(registeredForDays().get(), ChronoUnit.DAYS);
if (date.isAfter(Instant.now()))
return date.toEpochMilli() - Instant.now().toEpochMilli();
return -1;
@ -107,7 +113,7 @@ public class MemberComponent extends Component<MainPlugin> implements Listener {
* Returns hours
*/
public double getPlayTime(Player player) {
double pt = playedHours.get() - (double) getPlayTimeTotal(player) / playtime.getValue();
double pt = playedHours().get() - (double) getPlayTimeTotal(player) / playtime.getValue();
if (pt < 0) return -1;
return pt;
}

View file

@ -33,7 +33,7 @@ public class RestartComponent extends Component<MainPlugin> implements Listener
registerListener(this);
restartBroadcast = TBMCSystemChatEvent.BroadcastTarget.add("restartCountdown");
int restartAt = this.restartAt.get();
int restartAt = restartAt().get();
if (restartAt < 0) return;
int restart = syncStart(restartAt);
log("Scheduled restart " + (restart / 3600. / 20.) + " hours from now");
@ -48,7 +48,9 @@ public class RestartComponent extends Component<MainPlugin> implements Listener
/**
* Specifies the hour of day when the server should be restarted. Set to -1 to disable.
*/
private final ConfigData<Integer> restartAt = getConfig().getData("restartAt", 12);
private ConfigData<Integer> restartAt() {
return getConfig().getData("restartAt", 12);
}
private long lasttime = 0;
@Getter
@ -57,13 +59,19 @@ public class RestartComponent extends Component<MainPlugin> implements Listener
private int syncStart(int hour) {
var now = ZonedDateTime.now(ZoneId.ofOffset("", ZoneOffset.UTC));
int secs = now.getHour() * 3600 + now.getMinute() * 60 + now.getSecond();
//System.out.println("now: " + secs / 3600.);
int diff = secs - hour * 3600;
//System.out.println("diff: " + diff / 3600.);
if (diff < 0) {
diff += 24 * 3600;
}
//System.out.println("diff: " + diff / 3600.);
int count = diff / (24 * 3600);
//System.out.println("count: " + count);
int intervalPart = diff - count * 24 * 3600;
//System.out.println("intervalPart: " + intervalPart / 3600.);
int remaining = 24 * 3600 - intervalPart;
//System.out.println("remaining: " + remaining / 3600.);
return remaining * 20;
}

View file

@ -45,20 +45,20 @@ public class ScheduledRestartCommand extends ICommand2MC {
}
final int restarttime = restartCounter = seconds * 20;
restartbar = Bukkit.createBossBar("Server restart in " + seconds + "s", BarColor.RED, BarStyle.SOLID,
BarFlag.DARKEN_SKY);
BarFlag.DARKEN_SKY);
restartbar.setProgress(1);
Bukkit.getOnlinePlayers().forEach(p -> restartbar.addPlayer(p));
Bukkit.getOnlinePlayers().forEach(p -> restartbar.addPlayer(p));
sender.sendMessage("Scheduled restart in " + seconds);
ScheduledServerRestartEvent e = new ScheduledServerRestartEvent(restarttime, this);
Bukkit.getPluginManager().callEvent(e);
restarttask = Bukkit.getScheduler().runTaskTimer(MainPlugin.Instance, () -> {
if (restartCounter < 0) {
restarttask.cancel();
restartbar.getPlayers().forEach(p -> restartbar.removePlayer(p));
restartbar.getPlayers().forEach(p -> restartbar.removePlayer(p));
Bukkit.spigot().restart();
}
if (restartCounter % 200 == 0 && Bukkit.getOnlinePlayers().size() > 0)
TBMCChatAPI.SendSystemMessage(Channel.GlobalChat, Channel.RecipientTestResult.ALL, "§c-- The server is restarting in " + restartCounter / 20 + " seconds!", component.getRestartBroadcast());
if (restartCounter % 200 == 0 && Bukkit.getOnlinePlayers().size()>0)
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--;

View file

@ -29,7 +29,7 @@ public class SpawnComponent extends Component<MainPlugin> implements PluginMessa
@Override
protected void enable() {
registerCommand(new SpawnCommand());
if (targetServer.get().length() == 0) {
if (targetServer().get().length() == 0) {
spawnloc = MultiverseCore.getPlugin(MultiverseCore.class).getMVWorldManager().getFirstSpawnWorld()
.getSpawnLocation();
}
@ -49,7 +49,7 @@ public class SpawnComponent extends Component<MainPlugin> implements PluginMessa
if (!channel.equals("BungeeCord")) {
return;
}
if (targetServer.get().length() != 0)
if (targetServer().get().length() != 0)
return;
ByteArrayDataInput in = ByteStreams.newDataInput(message);
String subchannel = in.readUTF();
@ -78,7 +78,9 @@ public class SpawnComponent extends Component<MainPlugin> implements PluginMessa
/**
* The BungeeCord server that has the spawn. Set to empty if this server is the target.
*/
private final ConfigData<String> targetServer = getConfig().getData("targetServer", "");
private ConfigData<String> targetServer() {
return getConfig().getData("targetServer", "");
}
private Location spawnloc;
@ -90,7 +92,7 @@ public class SpawnComponent extends Component<MainPlugin> implements PluginMessa
@SuppressWarnings("UnstableApiUsage")
@Command2.Subcommand
public void def(Player player) {
if (targetServer.get().length() == 0) {
if (targetServer().get().length() == 0) {
player.sendMessage("§bTeleporting to spawn...");
try {
if (MainPlugin.ess != null)
@ -105,7 +107,7 @@ public class SpawnComponent extends Component<MainPlugin> implements PluginMessa
}
ByteArrayDataOutput out = ByteStreams.newDataOutput();
out.writeUTF("Connect");
out.writeUTF(targetServer.get());
out.writeUTF(targetServer().get());
player.sendPluginMessage(getPlugin(), "BungeeCord", out.toByteArray());

View file

@ -1,10 +1,16 @@
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.TownyUniverse;
import com.palmergames.bukkit.towny.exceptions.AlreadyRegisteredException;
import com.palmergames.bukkit.towny.exceptions.NotRegisteredException;
import com.palmergames.bukkit.towny.object.Resident;
/**
* Provides a command to remove invalid Towny residents.
* Automatically renames Towny players if they changed their Minecraft name
*/
public class TownyComponent extends Component<MainPlugin> {
@Override
@ -15,4 +21,35 @@ public class TownyComponent extends Component<MainPlugin> {
@Override
protected void disable() {
}
/**
* Only renames the resident if this component is enabled. Used to handle name changes.
*
* @param oldName The player's old name as known by us
* @param newName The player's new name
*/
public static void renameInTowny(String oldName, String newName) {
var component = ComponentManager.getIfEnabled(TownyComponent.class);
if (component == null)
return;
component.log("Renaming " + oldName + " in Towny to " + newName);
TownyUniverse tu = TownyUniverse.getInstance();
try {
Resident resident = tu.getDataSource().getResident(oldName);
if (resident == null) {
component.logWarn("Resident not found - couldn't rename in Towny.");
TBMCCoreAPI.sendDebugMessage("Resident not found - couldn't rename in Towny.");
} else if (tu.getDataSource().hasResident(newName)) {
component.logWarn("Target resident name is already in use.");
TBMCCoreAPI.sendDebugMessage("Target resident name is already in use. (" + oldName + " -> " + newName + ")");
} else {
tu.getDataSource().renamePlayer(resident, newName); //Fixed in Towny 0.91.1.2
component.log("Renaming done.");
}
} catch (AlreadyRegisteredException e) {
TBMCCoreAPI.SendException("Failed to rename resident, there's already one with this name.", e, component);
} catch (NotRegisteredException e) {
TBMCCoreAPI.SendException("Failed to rename resident, the resident isn't registered.", e, component);
}
}
}

View file

@ -0,0 +1,51 @@
package buttondevteam.core.component.votifier;
import buttondevteam.core.MainPlugin;
import buttondevteam.lib.architecture.Component;
import buttondevteam.lib.architecture.ComponentMetadata;
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
@ComponentMetadata(enabledByDefault = false)
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();
log("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

@ -30,6 +30,7 @@ public class TBMCChatEvent extends TBMCChatEventBase {
private boolean isIgnoreSenderPermissions() {
return cm.getPermCheck() != cm.getSender();
}
// TODO: Message object with data?
/**
* This will allow the sender of the message if {@link #isIgnoreSenderPermissions()} is true.

View file

@ -54,6 +54,6 @@ public abstract class TBMCChatEventBase extends Event implements Cancellable {
*/
@Nullable
public String getGroupID(CommandSender sender) {
return channel.getGroupID(sender);
return channel.getGroupID(sender); //TODO: Performance-wise it'd be much better to use serialization for player data - it's only converted once
}
}

View file

@ -30,9 +30,13 @@ public class TBMCChatPreprocessEvent extends Event implements Cancellable {
super(true);
this.sender = sender;
this.channel = channel;
this.message = message;
this.message = message; // TODO: Message object with data?
}
/*
* public TBMCPlayer getPlayer() { return TBMCPlayer.getPlayer(sender); // TODO: Get Chroma user }
*/
@Override
public HandlerList getHandlers() {
return handlers;

View file

@ -17,7 +17,6 @@ import java.net.URLConnection;
import java.util.*;
import java.util.Map.Entry;
import java.util.function.Consumer;
import java.util.function.Supplier;
public class TBMCCoreAPI {
static final List<String> coders = new ArrayList<String>() {
@ -130,8 +129,8 @@ public class TBMCCoreAPI {
EventExceptionHandler.registerEvents(listener, plugin, eventExceptionCoreHandler);
}
public static <T extends ChromaGamerBase> void RegisterUserClass(Class<T> userclass, Supplier<T> constructor) {
ChromaGamerBase.RegisterPluginUserClass(userclass, constructor);
public static <T extends ChromaGamerBase> void RegisterUserClass(Class<T> userclass) {
ChromaGamerBase.RegisterPluginUserClass(userclass);
}
/**
@ -171,6 +170,6 @@ public class TBMCCoreAPI {
public static boolean IsTestServer() {
if (MainPlugin.Instance == null) return true;
return MainPlugin.Instance.test.get();
return MainPlugin.Instance.test().get();
}
}

View file

@ -124,17 +124,15 @@ public abstract class ButtonPlugin extends JavaPlugin {
var yc = YamlConfiguration.loadConfiguration(res);
for (var kv : yc.getValues(true).entrySet())
if (kv.getValue() instanceof String)
yaml.addComment(kv.getKey().replace(".generalDescriptionInsteadOfAConfig", ""),
Arrays.stream(((String) kv.getValue()).split("\n"))
.map(str -> "# " + str.trim()).toArray(String[]::new));
yaml.addComment(kv.getKey(), Arrays.stream(((String) kv.getValue()).split("\n"))
.map(str -> "# " + str.trim()).toArray(String[]::new));
return true;
}
@Override
public FileConfiguration getConfig() {
if (yaml == null)
justReload();
if (yaml == null) return new YamlConfiguration(); //Return a temporary instance
justReload(); //TODO: If it fails to load, it'll probably throw an NPE
return yaml;
}

View file

@ -10,7 +10,6 @@ import org.yaml.snakeyaml.representer.Representer;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.util.HashMap;
@ -175,7 +174,7 @@ public class CommentedConfiguration extends YamlConfiguration {
while (newContents.toString().startsWith(" " + System.getProperty("line.separator"))) {
newContents = new StringBuilder(newContents.toString().replaceFirst(" " + System.getProperty("line.separator"), ""));
}
Files.write(file.toPath(), newContents.toString().getBytes(StandardCharsets.UTF_8));
Files.write(file.toPath(), newContents.toString().getBytes());
}
}

View file

@ -34,8 +34,9 @@ public abstract class Component<TP extends JavaPlugin> {
private @Getter final IHaveConfig config = new IHaveConfig(null);
private @Getter IHaveConfig data; //TODO
public final ConfigData<Boolean> shouldBeEnabled = config.getData("enabled",
Optional.ofNullable(getClass().getAnnotation(ComponentMetadata.class)).map(ComponentMetadata::enabledByDefault).orElse(true));
public final ConfigData<Boolean> shouldBeEnabled() {
return config.getData("enabled", Optional.ofNullable(getClass().getAnnotation(ComponentMetadata.class)).map(ComponentMetadata::enabledByDefault).orElse(true));
}
/**
* Registers a component checking it's dependencies and calling {@link #register(JavaPlugin)}.<br>
@ -58,7 +59,7 @@ public abstract class Component<TP extends JavaPlugin> {
* @param component The component to unregister
* @return Whether the component is unregistered successfully (it also got disabled)
*/
public static <T extends JavaPlugin> boolean unregisterComponent(T plugin, Component<T> component) {
public static <T extends ButtonPlugin> boolean unregisterComponent(T plugin, Component<T> component) {
return registerUnregisterComponent(plugin, component, false);
}
@ -86,7 +87,7 @@ public abstract class Component<TP extends JavaPlugin> {
components.put(component.getClass(), component);
if (plugin instanceof ButtonPlugin)
((ButtonPlugin) plugin).getComponentStack().push(component);
if (ComponentManager.areComponentsEnabled() && component.shouldBeEnabled.get()) {
if (ComponentManager.areComponentsEnabled() && component.shouldBeEnabled().get()) {
try { //Enable components registered after the previous ones getting enabled
setComponentEnabled(component, true);
return true;

View file

@ -63,7 +63,6 @@ public class ConfigData<T> {
this.primitiveDef = primitiveDef;
this.getter = getter;
this.setter = setter;
get(); //Generate config automatically
}
@Override

View file

@ -35,7 +35,7 @@ public final class IHaveConfig {
*
* @param saveAction What to do to save the config to disk. Don't use get methods until it's non-null.
*/
public IHaveConfig(Runnable saveAction) {
IHaveConfig(Runnable saveAction) {
this.saveAction = saveAction;
}
@ -201,13 +201,6 @@ public final class IHaveConfig {
val ms = obj.getClass().getDeclaredMethods();
for (val m : ms) {
if (!m.getReturnType().getName().equals(ConfigData.class.getName())) continue;
final String mName;
{
var name = m.getName();
var ind = name.lastIndexOf('$');
if (ind == -1) mName = name;
else mName = name.substring(ind + 1);
}
try {
m.setAccessible(true);
List<ConfigData<?>> configList;
@ -220,7 +213,7 @@ public final class IHaveConfig {
try {
return (ConfigData<?>) m.invoke(obj, kv.getValue());
} catch (IllegalAccessException | InvocationTargetException e) {
String msg = "Failed to pregenerate " + mName + " for " + obj + " using config " + kv.getKey() + "!";
String msg = "Failed to pregenerate " + m.getName() + " for " + obj + " using config " + kv.getKey() + "!";
if (obj instanceof Component<?>)
TBMCCoreAPI.SendException(msg, e, (Component<?>) obj);
else if (obj instanceof JavaPlugin)
@ -232,18 +225,18 @@ public final class IHaveConfig {
}).filter(Objects::nonNull).collect(Collectors.toList());
} else {
if (TBMCCoreAPI.IsTestServer())
MainPlugin.Instance.getLogger().warning("Method " + mName + " returns a config but its parameters are unknown: " + Arrays.toString(m.getParameterTypes()));
MainPlugin.Instance.getLogger().warning("Method " + m.getName() + " returns a config but its parameters are unknown: " + Arrays.toString(m.getParameterTypes()));
continue;
}
for (val c : configList) {
if (c.getPath().length() == 0)
c.setPath(mName);
else if (!c.getPath().equals(mName))
MainPlugin.Instance.getLogger().warning("Config name does not match: " + c.getPath() + " instead of " + mName);
c.setPath(m.getName());
else if (!c.getPath().equals(m.getName()))
MainPlugin.Instance.getLogger().warning("Config name does not match: " + c.getPath() + " instead of " + m.getName());
c.get(); //Saves the default value if needed - also checks validity
}
} catch (Exception e) {
String msg = "Failed to pregenerate " + mName + " for " + obj + "!";
String msg = "Failed to pregenerate " + m.getName() + " for " + obj + "!";
if (obj instanceof Component<?>)
TBMCCoreAPI.SendException(msg, e, (Component<?>) obj);
else if (obj instanceof JavaPlugin)

View file

@ -6,25 +6,15 @@ import lombok.RequiredArgsConstructor;
@RequiredArgsConstructor
@Getter
public enum Color implements TellrawSerializableEnum {
Black("black", 0, 0, 0),
DarkBlue("dark_blue", 0, 0, 170),
DarkGreen("dark_green", 0, 170, 0),
DarkAqua("dark_aqua", 0, 170, 170),
DarkRed("dark_red", 170, 0, 0),
DarkPurple("dark_purple", 0, 170, 0),
Gold("gold", 255, 170,0),
Gray("gray", 170, 170, 170),
DarkGray("dark_gray", 85, 85, 85),
Blue("blue", 85, 85, 255),
Green("green", 85, 255, 85),
Aqua("aqua", 85, 255, 255),
Red("red", 255, 85,85),
LightPurple("light_purple", 255, 85, 255),
Yellow("yellow", 255, 255, 85),
White("white", 255, 255, 255);
Black("black", 0, 0, 0), DarkBlue("dark_blue", 0, 0, 170), DarkGreen("dark_green", 0, 170, 0), DarkAqua("dark_aqua",
0, 170, 170), DarkRed("dark_red", 170, 0, 0), DarkPurple("dark_purple", 0, 170, 0), Gold("gold", 255, 170,
0), Gray("gray", 170, 170, 170), DarkGray("dark_gray", 85, 85, 85), Blue("blue", 85, 85,
255), Green("green", 85, 255, 85), Aqua("aqua", 85, 255, 255), Red("red", 255, 85,
85), LightPurple("light_purple", 255, 85,
255), Yellow("yellow", 255, 255, 85), White("white", 255, 255, 255);
private final String name;
private final int red;
private final int green;
private final int blue;
}
}

View file

@ -119,7 +119,7 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
protected final HashMap<String, SubcommandData<TC>> subcommands = new HashMap<>();
protected final HashMap<Class<?>, ParamConverter<?>> paramConverters = new HashMap<>();
private final ArrayList<String> commandHelp = new ArrayList<>(); //Mainly needed by Discord
private ArrayList<String> commandHelp = new ArrayList<>(); //Mainly needed by Discord
private char commandChar;
@ -253,7 +253,7 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
}
val conv = paramConverters.get(cl);
if (conv == null)
throw new Exception("No suitable converter found for parameter type '" + cl.getCanonicalName() + "' for command '" + sd.method + "'");
throw new Exception("No suitable converter found for parameter type '" + cl.getCanonicalName() + "' for command '" + sd.method.toString() + "'");
val cparam = conv.converter.apply(param);
if (cparam == null) {
sender.sendMessage(conv.errormsg); //Param conversion failed - ex. plugin not found
@ -284,12 +284,9 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
public abstract void registerCommand(TC command);
protected List<SubcommandData<TC>> registerCommand(TC command, char commandChar) {
return registerCommand(command, command.getCommandPath(), commandChar);
}
protected List<SubcommandData<TC>> registerCommand(TC command, String path, @SuppressWarnings("SameParameterValue") char commandChar) {
protected List<SubcommandData<TC>> registerCommand(TC command, @SuppressWarnings("SameParameterValue") char commandChar) {
this.commandChar = 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
@ -322,9 +319,9 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
var params = new String[method.getParameterCount() - 1];
ht = getParameterHelp(method, ht, subcommand, params);
var sd = new SubcommandData<>(method, command, params, ht);
registerCommand(path, method.getName(), ann, sd);
for (String p : command.getCommandPaths())
registerCommand(p, method.getName(), ann, sd);
subcommands.put(subcommand, sd); //Result of the above (def) is that it will show the help text
for (String alias : ann.aliases())
subcommands.put(commandChar + path + alias, sd);
addedSubcommands.add(sd);
scmdHelpList.add(subcommand);
nosubs = false;
@ -355,7 +352,7 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
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().replace('$', '.'));
val ccs = yc.getConfigurationSection(method.getDeclaringClass().getCanonicalName());
if (ccs != null) {
val cs = ccs.getConfigurationSection(method.getName());
if (cs != null) {
@ -371,22 +368,15 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
for (int j = 0; j < paramArray.length && j < parameters.length; j++)
parameters[j] = paramArray[j];
} else
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method + "' != " + mname + " or params is " + params), MainPlugin.Instance);
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("Method '" + method.toString() + "' != " + mname + " or params is " + params), MainPlugin.Instance);
} else
MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + " (cs is null)! Make sure to use 'clean install' when building the project.");
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("cs is " + cs), MainPlugin.Instance);
} else
MainPlugin.Instance.getLogger().warning("Failed to get command data for " + method + " (ccs is null)! Make sure to use 'clean install' when building the project.");
TBMCCoreAPI.SendException("Error while getting command data for " + method + "!", new Exception("ccs is " + ccs + " - class: " + method.getDeclaringClass().getCanonicalName()), MainPlugin.Instance);
}
return ht;
}
private void registerCommand(String path, String methodName, Subcommand ann, SubcommandData<TC> sd) {
val subcommand = commandChar + path + getCommandPath(methodName, ' ');
subcommands.put(subcommand, sd);
for (String alias : ann.aliases())
subcommands.put(commandChar + path + alias, sd);
}
public abstract boolean hasPermission(TP sender, TC command, Method subcommand);
public String[] getCommandsText() {
@ -413,19 +403,13 @@ public abstract class Command2<TC extends ICommand2<TP>, TP extends Command2Send
for (val method : command.getClass().getMethods()) {
val ann = method.getAnnotation(Subcommand.class);
if (ann == null) continue;
unregisterCommand(path, method.getName(), ann);
for (String p : command.getCommandPaths())
unregisterCommand(p, method.getName(), ann);
val subcommand = commandChar + path + getCommandPath(method.getName(), ' ');
subcommands.remove(subcommand);
for (String alias : ann.aliases())
subcommands.remove(alias);
}
}
private void unregisterCommand(String path, String methodName, Subcommand ann) {
val subcommand = commandChar + path + getCommandPath(methodName, ' ');
subcommands.remove(subcommand);
for (String alias : ann.aliases())
subcommands.remove(commandChar + path + alias);
}
/**
* It will start with the given replace char.
*

View file

@ -33,7 +33,6 @@ import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.regex.Matcher;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@ -53,10 +52,6 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
mainpath = cpath.substring(0, i == -1 ? cpath.length() : i);
}*/
var subcmds = super.registerCommand(command, '/');
var bcmd = registerOfficially(command, subcmds);
if (bcmd != null)
for (String alias : bcmd.getAliases())
super.registerCommand(command, command.getCommandPath().replaceFirst("^" + bcmd.getName(), Matcher.quoteReplacement(alias)), '/');
var perm = "chroma.command." + command.getCommandPath().replace(' ', '.');
if (Bukkit.getPluginManager().getPermission(perm) == null) //Check needed for plugin reset
@ -78,6 +73,8 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
Bukkit.getPluginManager().addPermission(new Permission(permGroup,
PermissionDefault.OP)); //Do not allow any commands that belong to a group
}
registerOfficially(command, subcmds);
}
@Override
@ -193,8 +190,13 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
int i = commandline.indexOf(' ');
String mainpath = commandline.substring(1, i == -1 ? commandline.length() : i); //Without the slash
PluginCommand pcmd;
/*System.out.println("Command line: " + commandline);
System.out.println("Prioritize: " + MainPlugin.Instance.prioritizeCustomCommands().get());
System.out.println("PCMD: " + (pcmd = Bukkit.getPluginCommand(mainpath)));
if (pcmd != null)
System.out.println("ButtonPlugin: " + (pcmd.getPlugin() instanceof ButtonPlugin));*/
if (!checkPlugin
|| MainPlugin.Instance.prioritizeCustomCommands.get()
|| MainPlugin.Instance.prioritizeCustomCommands().get()
|| (pcmd = Bukkit.getPluginCommand(mainpath)) == null //Our commands aren't PluginCommands
|| pcmd.getPlugin() instanceof ButtonPlugin) //Unless it's specified in the plugin.yml
return super.handleCommand(sender, commandline);
@ -204,15 +206,15 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
private boolean shouldRegisterOfficially = true;
private Command registerOfficially(ICommand2MC command, List<SubcommandData<ICommand2MC>> subcmds) {
if (!shouldRegisterOfficially || command.getPlugin() == null) return null;
private void registerOfficially(ICommand2MC command, List<SubcommandData<ICommand2MC>> subcmds) {
if (!shouldRegisterOfficially || command.getPlugin() == null) return;
try {
var cmdmap = (SimpleCommandMap) Bukkit.getServer().getClass().getMethod("getCommandMap").invoke(Bukkit.getServer());
var path = command.getCommandPath();
int x = path.indexOf(' ');
var mainPath = path.substring(0, x == -1 ? path.length() : x);
Command bukkitCommand;
{ //Commands conflicting with Essentials have to be registered in plugin.yml
{ //TODO: Commands conflicting with Essentials have to be registered in plugin.yml
var oldcmd = cmdmap.getCommand(command.getPlugin().getName() + ":" + mainPath); //The label with the fallback prefix is always registered
if (oldcmd == null) {
bukkitCommand = new BukkitCommand(mainPath);
@ -223,17 +225,18 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
((PluginCommand) bukkitCommand).setExecutor(this::executeCommand);
}
bukkitCommand = oldcmd == null ? new BukkitCommand(mainPath) : oldcmd;
/*System.out.println("oldcmd: " + oldcmd);
System.out.println("bukkitCommand: " + bukkitCommand);*/
}
//System.out.println("Registering to " + command.getPlugin().getName());
if (CommodoreProvider.isSupported())
TabcompleteHelper.registerTabcomplete(command, subcmds, bukkitCommand);
return bukkitCommand;
} catch (Exception e) {
if (command.getComponent() == null)
TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.getPlugin());
else
TBMCCoreAPI.SendException("Failed to register command in command map!", e, command.getComponent());
shouldRegisterOfficially = false;
return null;
}
}
@ -244,7 +247,8 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
sender.sendMessage("§cAn internal error occurred.");
return true;
}
handleCommand(new Command2MCSender(sender, user.channel.get(), sender),
//System.out.println("Executing " + label + " which is actually " + command.getName());
handleCommand(new Command2MCSender(sender, user.channel().get(), sender),
("/" + command.getName() + " " + String.join(" ", args)).trim(), false); ///trim(): remove space if there are no args
return true;
}
@ -261,11 +265,13 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException {
//System.out.println("Correct tabcomplete queried");
return Collections.emptyList();
}
@Override
public List<String> tabComplete(CommandSender sender, String alias, String[] args, Location location) throws IllegalArgumentException {
//System.out.println("Correct tabcomplete queried");
return Collections.emptyList();
}
}
@ -454,14 +460,10 @@ public class Command2MC extends Command2<ICommand2MC, Command2MCSender> implemen
if (shouldRegister.get()) {
commodore.register(maincmd);
//MinecraftArgumentTypes.getByKey(NamespacedKey.minecraft(""))
String pluginName = command2MC.getPlugin().getName().toLowerCase();
var prefixedcmd = LiteralArgumentBuilder.literal(pluginName + ":" + path[0])
.redirect(maincmd).build();
var prefixedcmd = new LiteralCommandNode<>(command2MC.getPlugin().getName().toLowerCase() + ":" + path[0], maincmd.getCommand(), maincmd.getRequirement(), maincmd.getRedirect(), maincmd.getRedirectModifier(), maincmd.isFork());
for (var child : maincmd.getChildren())
prefixedcmd.addChild(child);
commodore.register(prefixedcmd);
for (String alias : bukkitCommand.getAliases()) {
commodore.register(LiteralArgumentBuilder.literal(alias).redirect(maincmd).build());
commodore.register(LiteralArgumentBuilder.literal(pluginName + ":" + alias).redirect(maincmd).build());
}
}
}
}

View file

@ -63,18 +63,6 @@ public abstract class ICommand2<TP extends Command2Sender> {
return path;
}
private static final String[] EMPTY_PATHS = new String[0];
/**
* All of the command's paths it will be invoked on. Does not include aliases or the default path.
* Must be lowercase and must include the full path.
*
* @return The full command paths that this command should be registered under in addition to the default one.
*/
public String[] getCommandPaths() {
return EMPTY_PATHS;
}
private String getcmdpath() {
if (!getClass().isAnnotationPresent(CommandClass.class))
throw new RuntimeException(

View file

@ -22,7 +22,7 @@ public class TBMCChatAPI {
* @return The event cancelled state
*/
public static boolean SendChatMessage(ChatMessage cm) {
return SendChatMessage(cm, cm.getUser().channel.get());
return SendChatMessage(cm, cm.getUser().channel().get());
}
/**
@ -35,9 +35,9 @@ public class TBMCChatAPI {
*/
public static boolean SendChatMessage(ChatMessage cm, Channel channel) {
if (!Channel.getChannelList().contains(channel))
throw new RuntimeException("Channel " + channel.DisplayName.get() + " not registered!");
if (!channel.Enabled.get()) {
cm.getSender().sendMessage("§cThe channel '" + channel.DisplayName.get() + "' is disabled!");
throw new RuntimeException("Channel " + channel.DisplayName().get() + " not registered!");
if (!channel.Enabled().get()) {
cm.getSender().sendMessage("§cThe channel '" + channel.DisplayName().get() + "' is disabled!");
return true; //Cancel sending if channel is disabled
}
Supplier<Boolean> task = () -> {
@ -70,11 +70,11 @@ public class TBMCChatAPI {
*/
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())
throw new RuntimeException("Channel " + channel.DisplayName().get() + " not registered!");
if (!channel.Enabled().get())
return true; //Cancel sending
if (!Arrays.asList(exceptions).contains("Minecraft"))
Bukkit.getConsoleSender().sendMessage("[" + channel.DisplayName.get() + "] " + message);
Bukkit.getConsoleSender().sendMessage("[" + channel.DisplayName().get() + "] " + message);
TBMCSystemChatEvent event = new TBMCSystemChatEvent(channel, message, rtr.score, rtr.groupID, exceptions, target);
return ChromaUtils.callEventAsync(event);
}

View file

@ -0,0 +1,25 @@
package buttondevteam.lib.player;
import buttondevteam.core.component.channel.Channel;
import org.bukkit.configuration.file.YamlConfiguration;
public class ChannelPlayerData { //I just want this to work
private final PlayerData<String> data;
private final Channel def;
public ChannelPlayerData(String name, YamlConfiguration yaml, Channel def) {
data = new PlayerData<>(name, yaml, "");
this.def = def;
}
public Channel get() {
String str = data.get();
if (str.isEmpty())
return def;
return Channel.getChannels().filter(c -> str.equals(c.ID)).findAny().orElse(def);
}
public void set(Channel value) {
data.set(value.ID);
}
}

View file

@ -3,9 +3,7 @@ package buttondevteam.lib.player;
import buttondevteam.core.MainPlugin;
import buttondevteam.core.component.channel.Channel;
import buttondevteam.lib.TBMCCoreAPI;
import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.IHaveConfig;
import lombok.Getter;
import com.google.common.collect.HashBiMap;
import lombok.val;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
@ -15,52 +13,27 @@ import javax.annotation.Nullable;
import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@ChromaGamerEnforcer
public abstract class ChromaGamerBase {
private static final String TBMC_PLAYERS_DIR = "TBMC/players/";
private static final ArrayList<Function<CommandSender, ? extends Optional<? extends ChromaGamerBase>>> senderConverters = new ArrayList<>();
/**
* Holds data per user class
*/
private static final HashMap<Class<? extends ChromaGamerBase>, StaticUserData<?>> staticDataMap = new HashMap<>();
public abstract class ChromaGamerBase implements AutoCloseable {
public static final String TBMC_PLAYERS_DIR = "TBMC/players/";
private static final HashBiMap<Class<? extends ChromaGamerBase>, String> playerTypes = HashBiMap.create();
/**
* Use {@link #getConfig()} where possible; the 'id' must be always set
* Used for connecting with every type of user ({@link #connectWith(ChromaGamerBase)})
*/
//protected YamlConfiguration plugindata;
@Getter
protected final IHaveConfig config = new IHaveConfig(this::save);
protected CommonUserData<?> commonUserData;
/**
* Used for connecting with every type of user ({@link #connectWith(ChromaGamerBase)}) and to init the configs.
*/
public static <T extends ChromaGamerBase> void RegisterPluginUserClass(Class<T> userclass, Supplier<T> constructor) {
Class<? extends T> cl;
String folderName;
if (userclass.isAnnotationPresent(UserClass.class)) {
cl = userclass;
folderName = userclass.getAnnotation(UserClass.class).foldername();
} else if (userclass.isAnnotationPresent(AbstractUserClass.class)) {
var ucl = userclass.getAnnotation(AbstractUserClass.class).prototype();
if (!userclass.isAssignableFrom(ucl))
throw new RuntimeException("The prototype class (" + ucl.getSimpleName() + ") must be a subclass of the userclass parameter (" + userclass.getSimpleName() + ")!");
//noinspection unchecked
cl = (Class<? extends T>) ucl;
folderName = userclass.getAnnotation(AbstractUserClass.class).foldername();
} else // <-- Really important
public static void RegisterPluginUserClass(Class<? extends ChromaGamerBase> userclass) {
if (userclass.isAnnotationPresent(UserClass.class))
playerTypes.put(userclass, userclass.getAnnotation(UserClass.class).foldername());
else if (userclass.isAnnotationPresent(AbstractUserClass.class))
playerTypes.put(userclass.getAnnotation(AbstractUserClass.class).prototype(),
userclass.getAnnotation(AbstractUserClass.class).foldername());
else // <-- Really important
throw new RuntimeException("Class not registered as a user class! Use @UserClass or TBMCPlayerBase");
var sud = new StaticUserData<T>(folderName);
sud.getConstructors().put(cl, constructor);
sud.getConstructors().put(userclass, constructor); // Alawys register abstract and prototype class (TBMCPlayerBase and TBMCPlayer)
staticDataMap.put(userclass, sud);
}
/**
@ -85,58 +58,46 @@ public abstract class ChromaGamerBase {
* @return The type for the given folder name or null if not found
*/
public static Class<? extends ChromaGamerBase> getTypeForFolder(String foldername) {
synchronized (staticDataMap) {
return staticDataMap.entrySet().stream().filter(e -> e.getValue().getFolder().equalsIgnoreCase(foldername))
.map(Map.Entry::getKey).findAny().orElse(null);
}
return playerTypes.inverse().get(foldername);
}
/**
* This method returns the filename for this player data. For example, for Minecraft-related data, MC UUIDs, for Discord data, use Discord IDs, etc.<br>
* <b>Does not include .yml</b>
*/
public final String getFileName() {
return plugindata.getString(getFolder() + "_id");
}
/**
* Use {@link #data(Object)} or {@link #data(String, Object)} where possible; the 'id' must be always set
*/
protected YamlConfiguration plugindata;
/***
* Retrieves a user from cache or loads it from disk.
* Loads a user from disk and returns the user object. Make sure to use the subclasses' methods, where possible, like {@link TBMCPlayerBase#getPlayer(java.util.UUID, Class)}
*
* @param fname Filename without .yml, the user's identifier for that type
* @param fname Filename without .yml, usually UUID
* @param cl User class
* @return The user object
*/
public static synchronized <T extends ChromaGamerBase> T getUser(String fname, Class<T> cl) {
StaticUserData<?> staticUserData = null;
for (var sud : staticDataMap.entrySet()) {
if (sud.getKey().isAssignableFrom(cl)) {
staticUserData = sud.getValue();
break;
}
}
if (staticUserData == null)
throw new RuntimeException("User class not registered! Use @UserClass or @AbstractUserClass");
var commonUserData = staticUserData.getUserDataMap().get(fname);
if (commonUserData == null) {
final String folder = staticUserData.getFolder();
public static <T extends ChromaGamerBase> T getUser(String fname, Class<T> cl) {
try {
T obj = cl.newInstance();
final String folder = getFolderForType(cl);
final File file = new File(TBMC_PLAYERS_DIR + folder, fname + ".yml");
file.getParentFile().mkdirs();
var playerData = YamlConfiguration.loadConfiguration(file);
commonUserData = new CommonUserData<>(playerData);
playerData.set(staticUserData.getFolder() + "_id", fname);
staticUserData.getUserDataMap().put(fname, commonUserData);
obj.plugindata = YamlConfiguration.loadConfiguration(file);
obj.plugindata.set(folder + "_id", fname);
return obj;
} catch (Exception e) {
TBMCCoreAPI.SendException("An error occured while loading a " + cl.getSimpleName() + "!", e, MainPlugin.Instance);
}
if (commonUserData.getUserCache().containsKey(cl))
return (T) commonUserData.getUserCache().get(cl);
T obj;
if (staticUserData.getConstructors().containsKey(cl))
//noinspection unchecked
obj = (T) staticUserData.getConstructors().get(cl).get();
else {
try {
obj = cl.getConstructor().newInstance();
} catch (Exception e) {
throw new RuntimeException("Failed to create new instance of user of type " + cl.getSimpleName() + "!", e);
}
}
obj.commonUserData = commonUserData;
obj.init();
obj.scheduleUncache();
return obj;
return null;
}
private static ArrayList<Function<CommandSender, ? extends Optional<? extends ChromaGamerBase>>> senderConverters = new ArrayList<>();
/**
* Adds a converter to the start of the list.
*
@ -161,110 +122,85 @@ public abstract class ChromaGamerBase {
return null;
}
public static void saveUsers() {
synchronized (staticDataMap) {
for (var sud : staticDataMap.values())
for (var cud : sud.getUserDataMap().values())
ConfigData.saveNow(cud.getPlayerData()); //Calls save()
}
}
protected void init() {
config.reset(commonUserData.getPlayerData());
/**
* Saves the player. It'll pass all exceptions to the caller. To automatically handle the exception, use {@link #save()} instead.
*/
@Override
public void close() throws Exception {
if (plugindata.getKeys(false).size() > 0)
plugindata.save(new File(TBMC_PLAYERS_DIR + getFolder(), getFileName() + ".yml"));
}
/**
* Saves the player. It'll handle all exceptions that may happen. Called automatically.
* Saves the player. It'll handle all exceptions that may happen. To catch the exception, use {@link #close()} instead.
*/
protected void save() {
public void save() {
try {
if (commonUserData.getPlayerData().getKeys(false).size() > 0)
commonUserData.getPlayerData().save(new File(TBMC_PLAYERS_DIR + getFolder(), getFileName() + ".yml"));
close();
} catch (Exception e) {
TBMCCoreAPI.SendException("Error while saving player to " + getFolder() + "/" + getFileName() + ".yml!", e, MainPlugin.Instance);
}
}
/**
* Removes the user from the cache. This will be called automatically after some time by default.
*/
public void uncache() {
final var userCache = commonUserData.getUserCache();
//noinspection SynchronizationOnLocalVariableOrMethodParameter
synchronized (userCache) {
if (userCache.containsKey(getClass()))
if (userCache.remove(getClass()) != this)
throw new IllegalStateException("A different player instance was cached!");
}
}
protected void scheduleUncache() {
Bukkit.getScheduler().runTaskLaterAsynchronously(MainPlugin.Instance, this::uncache, 2 * 60 * 60 * 20); //2 hours
}
/**
* Connect two accounts. Do not use for connecting two Minecraft accounts or similar. Also make sure you have the "id" tag set.
* Connect two accounts. Do not use for connecting two Minecraft accounts or similar. Also make sure you have the "id" tag set
*
* @param user The account to connect with
*/
public final <T extends ChromaGamerBase> void connectWith(T user) {
public <T extends ChromaGamerBase> void connectWith(T user) {
// Set the ID, go through all linked files and connect them as well
if (!playerTypes.containsKey(getClass()))
throw new RuntimeException("Class not registered as a user class! Use TBMCCoreAPI.RegisterUserClass");
final String ownFolder = getFolder();
final String userFolder = user.getFolder();
if (ownFolder.equalsIgnoreCase(userFolder))
throw new RuntimeException("Do not connect two accounts of the same type! Type: " + ownFolder);
var ownData = commonUserData.getPlayerData();
var userData = user.commonUserData.getPlayerData();
userData.set(ownFolder + "_id", ownData.getString(ownFolder + "_id"));
ownData.set(userFolder + "_id", userData.getString(userFolder + "_id"));
config.signalChange();
user.config.signalChange();
user.plugindata.set(ownFolder + "_id", plugindata.getString(ownFolder + "_id"));
plugindata.set(userFolder + "_id", user.plugindata.getString(userFolder + "_id"));
Consumer<YamlConfiguration> sync = sourcedata -> {
final String sourcefolder = sourcedata == ownData ? ownFolder : userFolder;
final String sourcefolder = sourcedata == plugindata ? ownFolder : userFolder;
final String id = sourcedata.getString(sourcefolder + "_id");
for (val entry : staticDataMap.entrySet()) { // Set our ID in all files we can find, both from our connections and the new ones
for (val entry : playerTypes.entrySet()) { // Set our ID in all files we can find, both from our connections and the new ones
if (entry.getKey() == getClass() || entry.getKey() == user.getClass())
continue;
var entryFolder = entry.getValue().getFolder();
final String otherid = sourcedata.getString(entryFolder + "_id");
final String otherid = sourcedata.getString(entry.getValue() + "_id");
if (otherid == null)
continue;
ChromaGamerBase cg = getUser(otherid, entry.getKey());
var cgData = cg.commonUserData.getPlayerData();
cgData.set(sourcefolder + "_id", id); // Set new IDs
for (val item : staticDataMap.entrySet()) {
var itemFolder = item.getValue().getFolder();
if (sourcedata.contains(itemFolder + "_id")) {
cgData.set(itemFolder + "_id", sourcedata.getString(itemFolder + "_id")); // Set all existing IDs
}
try (ChromaGamerBase cg = getUser(otherid, entry.getKey())) {
cg.plugindata.set(sourcefolder + "_id", id); // Set new IDs
for (val item : playerTypes.entrySet())
if (sourcedata.contains(item.getValue() + "_id"))
cg.plugindata.set(item.getValue() + "_id", sourcedata.getString(item.getValue() + "_id")); // Set all existing IDs
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to update " + sourcefolder + " ID in player files for " + id
+ " in folder with " + entry.getValue() + " id " + otherid + "!", e, MainPlugin.Instance);
}
cg.config.signalChange();
}
};
sync.accept(ownData);
sync.accept(userData);
sync.accept(plugindata);
sync.accept(user.plugindata);
}
/**
* Returns the ID for the T typed player object connected with this one or null if no connection found.
* Retunrs the ID for the T typed player object connected with this one or null if no connection found.
*
* @param cl The player class to get the ID from
* @return The ID or null if not found
*/
public final <T extends ChromaGamerBase> String getConnectedID(Class<T> cl) {
return commonUserData.getPlayerData().getString(getFolderForType(cl) + "_id");
public <T extends ChromaGamerBase> String getConnectedID(Class<T> cl) {
return plugindata.getString(getFolderForType(cl) + "_id");
}
/**
* Returns a player instance of the given type that represents the same player. This will return a new instance unless the player is cached.<br>
* If the class is a subclass of the current class then the same ID is used, otherwise, a connected ID is used, if found.
* Returns this player as a plugin player. This will return a new instance unless the player is online.<br>
* Make sure to close both the returned and this object. A try-with-resources block or two can help.<br>
*
* @param cl The target player class
* @return The player as a {@link T} object or null if the user doesn't have an account there
* @return The player as a {@link T} object or null if not having an account there
*/
@SuppressWarnings("unchecked")
@Nullable
public final <T extends ChromaGamerBase> T getAs(Class<T> cl) {
public <T extends ChromaGamerBase> T getAs(Class<T> cl) { // TODO: Provide a way to use TBMCPlayerBase's loaded players
if (cl.getSimpleName().equals(getClass().getSimpleName()))
return (T) this;
String newfolder = getFolderForType(cl);
@ -272,34 +208,104 @@ public abstract class ChromaGamerBase {
throw new RuntimeException("The specified class " + cl.getSimpleName() + " isn't registered!");
if (newfolder.equals(getFolder())) // If in the same folder, the same filename is used
return getUser(getFileName(), cl);
var playerData = commonUserData.getPlayerData();
if (!playerData.contains(newfolder + "_id"))
if (!plugindata.contains(newfolder + "_id"))
return null;
return getUser(playerData.getString(newfolder + "_id"), cl);
return getUser(plugindata.getString(newfolder + "_id"), cl);
}
/**
* This method returns the filename for this player data. For example, for Minecraft-related data, MC UUIDs, for Discord data, Discord IDs, etc.<br>
* <b>Does not include .yml</b>
*/
public final String getFileName() {
return commonUserData.getPlayerData().getString(getFolder() + "_id");
}
/**
* This method returns the folder that this player data is stored in. For example: "minecraft".
*/
public final String getFolder() {
public String getFolder() {
return getFolderForType(getClass());
}
private void ThrowIfNoUser() {
if (!getClass().isAnnotationPresent(UserClass.class)
&& !getClass().isAnnotationPresent(AbstractUserClass.class))
throw new RuntimeException("Class not registered as a user class! Use @UserClass");
}
@SuppressWarnings("rawtypes")
private final HashMap<String, PlayerData> datamap = new HashMap<>();
/**
* Use from a data() method, which is in a method with the name of the key. For example, use flair() for the enclosing method of the outer data() to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T> PlayerData<T> data(String sectionname, T def) {
ThrowIfNoUser();
String mname = sectionname + "." + new Exception().getStackTrace()[2].getMethodName();
if (!datamap.containsKey(mname))
datamap.put(mname, new PlayerData<T>(mname, plugindata, def));
return datamap.get(mname);
}
/**
* Use from a method with the name of the key. For example, use flair() for the enclosing method to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T> PlayerData<T> data(T def) {
ThrowIfNoUser();
String mname = new Exception().getStackTrace()[1].getMethodName();
if (!datamap.containsKey(mname))
datamap.put(mname, new PlayerData<T>(mname, plugindata, def));
return datamap.get(mname);
}
@SuppressWarnings("rawtypes")
private final HashMap<String, EnumPlayerData> dataenummap = new HashMap<>();
private ChannelPlayerData datachannel;
/**
* Use from a data() method, which is in a method with the name of the key. For example, use flair() for the enclosing method of the outer data() to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T extends Enum<T>> EnumPlayerData<T> dataEnum(String sectionname, Class<T> cl, T def) {
ThrowIfNoUser();
String mname = sectionname + "." + new Exception().getStackTrace()[2].getMethodName();
if (!dataenummap.containsKey(mname))
dataenummap.put(mname, new EnumPlayerData<T>(mname, plugindata, cl, def));
return dataenummap.get(mname);
}
/**
* Use from a method with the name of the key. For example, use flair() for the enclosing method to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected <T extends Enum<T>> EnumPlayerData<T> dataEnum(Class<T> cl, T def) {
ThrowIfNoUser();
String mname = new Exception().getStackTrace()[1].getMethodName();
if (!dataenummap.containsKey(mname))
dataenummap.put(mname, new EnumPlayerData<T>(mname, plugindata, cl, def));
return dataenummap.get(mname);
}
/**
* Channel
*
* @return A data object with methods to get and set
*/
@SuppressWarnings("unchecked")
protected ChannelPlayerData dataChannel(Channel def) { //TODO: Make interface with fromString() method and require use of that for player data types
ThrowIfNoUser();
if (datachannel == null)
datachannel = new ChannelPlayerData("channel", plugindata, def);
return datachannel;
}
/**
* Get player information. This method calls the {@link TBMCPlayerGetInfoEvent} to get all the player information across the TBMC plugins.
*
* @param target The {@link InfoTarget} to return the info for.
* @return The player information.
*/
public final String getInfo(InfoTarget target) {
public String getInfo(InfoTarget target) {
TBMCPlayerGetInfoEvent event = new TBMCPlayerGetInfoEvent(this, target);
Bukkit.getServer().getPluginManager().callEvent(event);
return event.getResult();
@ -311,6 +317,7 @@ public abstract class ChromaGamerBase {
//-----------------------------------------------------------------
public final ConfigData<Channel> channel = config.getData("channel", Channel.GlobalChat,
id -> Channel.getChannels().filter(ch -> ch.ID.equalsIgnoreCase((String) id)).findAny().orElse(null), ch -> ch.ID);
public ChannelPlayerData channel() {
return dataChannel(Channel.GlobalChat);
}
}

View file

@ -1,19 +0,0 @@
package buttondevteam.lib.player;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.bukkit.configuration.file.YamlConfiguration;
import java.util.HashMap;
/**
* Per user, regardless of actual type
*
* @param <T> The user class, may be abstract
*/
@Getter
@RequiredArgsConstructor
public class CommonUserData<T extends ChromaGamerBase> {
private final HashMap<Class<? extends T>, ? extends T> userCache = new HashMap<>();
private final YamlConfiguration playerData;
}

View file

@ -0,0 +1,26 @@
package buttondevteam.lib.player;
import org.bukkit.configuration.file.YamlConfiguration;
public class EnumPlayerData<T extends Enum<T>> {
private final PlayerData<String> data;
private final Class<T> cl;
private final T def;
public EnumPlayerData(String name, YamlConfiguration yaml, Class<T> cl, T def) {
data = new PlayerData<String>(name, yaml, "");
this.cl = cl;
this.def = def;
}
public T get() {
String str = data.get();
if (str.isEmpty())
return def;
return Enum.valueOf(cl, str);
}
public void set(T value) {
data.set(value.toString());
}
}

View file

@ -0,0 +1,37 @@
package buttondevteam.lib.player;
import org.bukkit.configuration.file.YamlConfiguration;
public class PlayerData<T> {
private final String name;
private final YamlConfiguration yaml;
private final T def;
public PlayerData(String name, YamlConfiguration yaml, T def) {
this.name = name;
this.yaml = yaml;
this.def = def;
}
@SuppressWarnings("unchecked")
// @Deprecated - What was once enforced (2 days ago from now) vanished now
public T get() {
Object value = yaml.get(name, def);
if (value instanceof Integer) {
if (def instanceof Short) // If the default is Short the value must be as well because both are T
return (T) (Short) ((Integer) value).shortValue();
if (def instanceof Long)
return (T) (Long) ((Integer) value).longValue();
}
return (T) value;
}
public void set(T value) {
yaml.set(name, value);
}
@Override
public String toString() {
return get().toString();
}
}

View file

@ -1,23 +0,0 @@
package buttondevteam.lib.player;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import java.util.HashMap;
import java.util.function.Supplier;
/**
* Per user class
*
* @param <T> The user class type, may be abstract
*/
@Getter
@RequiredArgsConstructor
public class StaticUserData<T extends ChromaGamerBase> {
private final HashMap<Class<? extends T>, Supplier<T>> constructors = new HashMap<>();
/**
* Key: User ID
*/
private final HashMap<String, CommonUserData<?>> userDataMap = new HashMap<>();
private final String folder;
}

View file

@ -1,6 +1,6 @@
package buttondevteam.lib.player;
@PlayerClass(pluginname = "Chroma-Core")
@PlayerClass(pluginname = "ButtonCore") //TODO: Migrate
public final class TBMCPlayer extends TBMCPlayerBase {
}

View file

@ -1,21 +1,29 @@
package buttondevteam.lib.player;
import buttondevteam.lib.architecture.ConfigData;
import buttondevteam.lib.architecture.IHaveConfig;
import lombok.Getter;
import buttondevteam.core.MainPlugin;
import buttondevteam.core.component.towny.TownyComponent;
import buttondevteam.lib.TBMCCoreAPI;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.entity.Player;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
@AbstractUserClass(foldername = "minecraft", prototype = TBMCPlayer.class)
@TBMCPlayerEnforcer
public abstract class TBMCPlayerBase extends ChromaGamerBase {
protected UUID uuid;
@Getter
private final IHaveConfig config = new IHaveConfig(this::save);
private final String pluginname;
protected TBMCPlayerBase() {
if (getClass().isAnnotationPresent(PlayerClass.class))
pluginname = getClass().getAnnotation(PlayerClass.class).pluginname();
else
throw new RuntimeException("Class not defined as player class! Use @PlayerClass");
}
public UUID getUUID() {
if (uuid == null)
@ -23,61 +31,154 @@ public abstract class TBMCPlayerBase extends ChromaGamerBase {
return uuid;
}
public final ConfigData<String> PlayerName = super.config.getData("PlayerName", "");
public PlayerData<String> PlayerName() {
return super.data(null);
}
/**
* Get player as a plugin player.
* Use from a method with the name of the key. For example, use flair() for the enclosing method to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@Override
protected <T> PlayerData<T> data(T def) {
return super.data(pluginname, def);
}
/**
* Use from a method with the name of the key. For example, use flair() for the enclosing method to save to and load from "flair"
*
* @return A data object with methods to get and set
*/
@Override
protected <T extends Enum<T>> EnumPlayerData<T> dataEnum(Class<T> cl, T def) {
return super.dataEnum(pluginname, cl, def);
}
/**
* Get player as a plugin player
*
* @param uuid The UUID of the player to get
* @param cl The type of the player
* @return The requested player object
*/
@SuppressWarnings("unchecked")
public static <T extends TBMCPlayerBase> T getPlayer(UUID uuid, Class<T> cl) {
var player = ChromaGamerBase.getUser(uuid.toString(), cl);
if (!player.getUUID().equals(uuid)) //It will be set from the filename because we check it for scheduling the uncache.
throw new IllegalStateException("Player UUID differs after converting from and to string...");
return player;
}
@Override
public void init() {
super.init();
String pluginname;
if (getClass().isAnnotationPresent(PlayerClass.class))
pluginname = getClass().getAnnotation(PlayerClass.class).pluginname();
else
throw new RuntimeException("Class not defined as player class! Use @PlayerClass");
var playerData = commonUserData.getPlayerData();
var section = playerData.getConfigurationSection(pluginname);
if (section == null) section = playerData.createSection(pluginname);
config.reset(section);
}
@Override
protected void scheduleUncache() { //Don't schedule it, it will happen on quit - if the player is online
var p = Bukkit.getPlayer(getUUID());
if (p == null || !p.isOnline())
super.scheduleUncache();
if (playermap.containsKey(uuid + "-" + cl.getSimpleName()))
return (T) playermap.get(uuid + "-" + cl.getSimpleName());
try {
T player;
if (playermap.containsKey(uuid + "-" + TBMCPlayer.class.getSimpleName())) {
player = cl.newInstance();
player.plugindata = playermap.get(uuid + "-" + TBMCPlayer.class.getSimpleName()).plugindata;
playermap.put(uuid + "-" + cl.getSimpleName(), player); // It will get removed on player quit
} else
player = ChromaGamerBase.getUser(uuid.toString(), cl);
player.uuid = uuid;
return player;
} catch (Exception e) {
TBMCCoreAPI.SendException("Failed to get player with UUID " + uuid + " and class " + cl.getSimpleName() + "!", e, MainPlugin.Instance);
return null;
}
}
/**
* This method returns a TBMC player from their name. See {@link Bukkit#getOfflinePlayer(String)}.
* Key: UUID-Class
*/
private static final ConcurrentHashMap<String, TBMCPlayerBase> playermap = new ConcurrentHashMap<>();
/**
* Gets the TBMCPlayer object as a specific plugin player, keeping it's data<br>
* Make sure to use try-with-resources with this to save the data, as it may need to load the file
*
* @param cl The TBMCPlayer subclass
*/
public <T extends TBMCPlayerBase> T asPluginPlayer(Class<T> cl) {
return getPlayer(uuid, cl);
}
/**
* Only intended to use from ButtonCore
*/
public static void savePlayer(TBMCPlayerBase player) {
Bukkit.getServer().getPluginManager().callEvent(new TBMCPlayerSaveEvent(player));
try {
player.close();
} catch (Exception e) {
new Exception("Failed to save player data for " + player.PlayerName().get(), e).printStackTrace();
}
}
/**
* Only intended to use from ButtonCore
*/
public static void joinPlayer(Player p) {
TBMCPlayer player = TBMCPlayerBase.getPlayer(p.getUniqueId(), TBMCPlayer.class);
if (player.PlayerName().get() == null) {
player.PlayerName().set(p.getName());
MainPlugin.Instance.getLogger().info("Player name saved: " + player.PlayerName().get());
} else if (!p.getName().equals(player.PlayerName().get())) {
TownyComponent.renameInTowny(player.PlayerName().get(), p.getName());
MainPlugin.Instance.getLogger().info(player.PlayerName().get() + " renamed to " + p.getName());
player.PlayerName().set(p.getName());
}
playermap.put(p.getUniqueId() + "-" + TBMCPlayer.class.getSimpleName(), player);
// Load in other plugins
Bukkit.getServer().getPluginManager().callEvent(new TBMCPlayerLoadEvent(player));
Bukkit.getServer().getPluginManager().callEvent(new TBMCPlayerJoinEvent(player, p));
player.save();
}
/**
* Only intended to use from ButtonCore
*/
public static void quitPlayer(Player p) {
final TBMCPlayerBase player = playermap.get(p.getUniqueId() + "-" + TBMCPlayer.class.getSimpleName());
player.save();
Bukkit.getServer().getPluginManager().callEvent(new TBMCPlayerQuitEvent(player, p));
playermap.entrySet().removeIf(entry -> entry.getKey().startsWith(p.getUniqueId().toString()));
}
public static void savePlayers() {
playermap.values().forEach(p -> {
try {
p.close();
} catch (Exception e) {
TBMCCoreAPI.SendException("Error while saving player " + p.PlayerName().get() + " (" + p.getFolder()
+ "/" + p.getFileName() + ")!", e, MainPlugin.Instance);
}
});
}
/**
* This method returns a TBMC player from their name. Calling this method may return an offline player which will load it, therefore it's highly recommended to use {@link #close()} to unload the
* player data. Using try-with-resources may be the easiest way to achieve this. Example:
*
* <pre>
* {@code
* try(TBMCPlayer player = getFromName(p))
* {
* ...
* }
* </pre>
*
* @param name The player's name
* @return The {@link TBMCPlayer} object for the player
*/
@SuppressWarnings("deprecation")
public static <T extends TBMCPlayerBase> T getFromName(String name, Class<T> cl) {
@SuppressWarnings("deprecation")
OfflinePlayer p = Bukkit.getOfflinePlayer(name);
return getPlayer(p.getUniqueId(), cl);
if (p != null)
return getPlayer(p.getUniqueId(), cl);
else
return null;
}
@Override
protected void save() {
Set<String> keys = commonUserData.getPlayerData().getKeys(false);
public void close() throws Exception {
Set<String> keys = plugindata.getKeys(false);
if (keys.size() > 1) // PlayerName is always saved, but we don't need a file for just that
super.save();
super.close();
}
}

View file

@ -0,0 +1,34 @@
package buttondevteam.lib.player;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class TBMCPlayerJoinEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final TBMCPlayerBase player;
private final Player player_;
public TBMCPlayerJoinEvent(TBMCPlayerBase player, Player player_) {
this.player = player;
this.player_ = player_;
}
public TBMCPlayerBase GetPlayer() {
return player;
}
public Player getPlayer() { // :P
return player_;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View file

@ -0,0 +1,27 @@
package buttondevteam.lib.player;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class TBMCPlayerLoadEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final TBMCPlayerBase player;
public TBMCPlayerLoadEvent(TBMCPlayerBase player) {
this.player = player;
}
public TBMCPlayerBase GetPlayer() {
return player;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View file

@ -0,0 +1,34 @@
package buttondevteam.lib.player;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class TBMCPlayerQuitEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final TBMCPlayerBase player;
private final Player player_;
public TBMCPlayerQuitEvent(TBMCPlayerBase player, Player player_) {
this.player = player;
this.player_ = player_;
}
public TBMCPlayerBase GetPlayer() {
return player;
}
public Player getPlayer() {
return player_;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View file

@ -0,0 +1,27 @@
package buttondevteam.lib.player;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class TBMCPlayerSaveEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final TBMCPlayerBase player;
public TBMCPlayerSaveEvent(TBMCPlayerBase player) {
this.player = player;
}
public TBMCPlayerBase GetPlayer() {
return player;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View file

@ -1,6 +1,6 @@
name: Chroma-Core
name: ChromaCore
main: buttondevteam.core.MainPlugin
version: '${noprefix.version}'
version: '1.0'
author: NorbiPeti
commands:
updateplugin:
@ -9,6 +9,10 @@ commands:
description: Schedules a restart for a given time.
primerestart:
description: Restarts the server as soon as nobody is online.
randomtp:
description: 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.
member:
description: Add or remove a member
component:
description: Enable or disable or list components
dontrunthiscmd:

221
CorePOM/pom.xml Normal file → Executable file
View file

@ -1,127 +1,110 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<modelVersion>4.0.0</modelVersion>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>CorePOM</artifactId>
<packaging>pom</packaging>
<version>master-SNAPSHOT</version>
<properties>
<lombok.version>1.18.10</lombok.version>
</properties>
<name>Core POM for Chroma</name>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>CorePOM</artifactId>
<packaging>pom</packaging>
<version>master-SNAPSHOT</version>
<properties>
<lombok.version>1.18.10</lombok.version>
</properties>
<name>Core POM for Chroma</name>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>8</release>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>com.github.bsideup.jabel</groupId>
<artifactId>jabel-javac-plugin</artifactId>
<version>0.2.0</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>ButtonProcessor</artifactId>
<version>master-SNAPSHOT</version>
</annotationProcessorPath>
</annotationProcessorPaths>
<annotationProcessors> <!-- Order is important, so these lines are needed -->
<annotationProcessor>com.github.bsideup.jabel.JabelJavacProcessor</annotationProcessor>
<annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor
</annotationProcessor>
<annotationProcessor>buttondevteam.buttonproc.ButtonProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<useSystemClassLoader>false
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
</configuration>
</plugin>
<!-- <plugin>
<groupId>io.github.1tchy</groupId>
<artifactId>variable-search-replace-plugin</artifactId>
<version>1.1</version>
<executions>
<execution>
<goals>
<goal>replace</goal>
</goals>
<configuration>
<text>${project.version}</text>
<search>^v</search>
<variableName>noprefix.version</variableName>
</configuration>
</execution>
</executions>
</plugin> -->
</plugins>
</pluginManagement>
</build>
<build>
<pluginManagement>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<release>8</release>
<annotationProcessorPaths>
<annotationProcessorPath>
<groupId>com.github.bsideup.jabel</groupId>
<artifactId>jabel-javac-plugin</artifactId>
<version>0.2.0</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
</annotationProcessorPath>
<annotationProcessorPath>
<groupId>com.github.TBMCPlugins.ChromaCore</groupId>
<artifactId>ButtonProcessor</artifactId>
<version>master-SNAPSHOT</version>
</annotationProcessorPath>
</annotationProcessorPaths>
<annotationProcessors> <!-- Order is important, so these lines are needed -->
<annotationProcessor>com.github.bsideup.jabel.JabelJavacProcessor</annotationProcessor>
<annotationProcessor>lombok.launch.AnnotationProcessorHider$AnnotationProcessor
</annotationProcessor>
<annotationProcessor>buttondevteam.buttonproc.ButtonProcessor</annotationProcessor>
</annotationProcessors>
</configuration>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<useSystemClassLoader>false
</useSystemClassLoader> <!-- https://stackoverflow.com/a/53012553/2703239 -->
</configuration>
</plugin>
</plugins>
</pluginManagement>
</build>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io/</url>
</repository>
</repositories>
<repositories>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<scope>provided</scope>
</dependency>
</dependencies>
<profiles>
<profile>
<id>intellij-idea-only</id>
<activation>
<property>
<name>idea.maven.embedder.version</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>11</release>
<!--
<compilerArgs>
<arg>HYPHENHYPHENenable-preview</arg>
</compilerArgs>
-->
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
<profiles>
<profile>
<id>intellij-idea-only</id>
<activation>
<property>
<name>idea.maven.embedder.version</name>
</property>
</activation>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<release>11</release>
<!--
<compilerArgs>
<arg>HYPHENHYPHENenable-preview</arg>
</compilerArgs>
-->
</configuration>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

2
pom.xml Normal file → Executable file
View file

@ -44,7 +44,7 @@
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->