Initial commit

This commit is contained in:
Michael Ziluck 2019-05-28 19:59:48 -05:00
commit d22f155fb3
14 changed files with 911 additions and 0 deletions

3
.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
target/
.idea/
*.iml

48
pom.xml Normal file
View File

@ -0,0 +1,48 @@
<?xml version="1.0" encoding="UTF-8"?>
<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>
<groupId>com.alttd</groupId>
<artifactId>AltitudeTag</artifactId>
<version>0.0.1</version>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>com.destroystokyo.paper</groupId>
<artifactId>paper</artifactId>
<version>1.14.1-R0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alttd</groupId>
<artifactId>AltitudeAPI</artifactId>
<version>LATEST</version>
</dependency>
<!-- MariaDB -->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>2.4.1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@ -0,0 +1,70 @@
package com.alttd.altitudetag;
import java.util.UUID;
import java.util.function.Consumer;
import org.bukkit.plugin.java.JavaPlugin;
public class AltitudeTag extends JavaPlugin
{
private static AltitudeTag instance;
private UUID tagger;
/**
* Enable the plugin
*/
public void onEnable()
{
instance = this;
}
/**
* Set the current tagger.
*
* @param tagger the new tagger.
*
* @return the previous tagger.
*/
public static UUID setTagger(UUID tagger)
{
UUID prev = instance.tagger;
instance.tagger = tagger;
return prev;
}
/**
* Returns the current tagger.
*
* @return the current tagger.
*/
public static UUID getTagger()
{
return instance.tagger;
}
/**
* Adds a tag for the given player.
*
* @param uuid the player to add a tag for.
*/
public static void addTag(UUID uuid, Runnable runnable)
{
Leaderboard.addTag(uuid, runnable);
}
public static void getTags(UUID uuid, Consumer<Integer> consumer)
{
Leaderboard.getTags(uuid, consumer);
}
/**
* Returns the singleton instance of this plugin.
*
* @return the singleton instance of this plugin.
*/
public static AltitudeTag getInstance()
{
return instance;
}
}

View File

@ -0,0 +1,131 @@
package com.alttd.altitudetag;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.UUID;
import java.util.function.Consumer;
import org.bukkit.Bukkit;
public class Leaderboard
{
public static void initialize()
{
//language=SQL
String sql = "CREATE TABLE IF NOT EXISTS Players ("
+ " PlayerId INT NOT NULL AUTO_INCREMENT,"
+ " PlayerUuidMost BIGINT NOT NULL,"
+ " PlayerUuidLeast BIGINT NOT NULL,"
+ " PlayerTags INT NOT NULL DEFAULT(1),"
+ " PRIMARY KEY (PlayerId)" +
");";
try
{
Statement statement = TagConnection.getConnection().createStatement();
statement.execute(sql);
}
catch (SQLException ex)
{
ex.printStackTrace();
}
}
/**
* Adds a tag for the given player.
*
* @param uuid the player to add a tag for.
*/
public static void addTag(UUID uuid, Runnable runnable)
{
// call the database action asynchronously
Bukkit.getScheduler().runTaskAsynchronously(AltitudeTag.getInstance(), () ->
{
String sql;
// if they've tagged before we want to update rather than insert
if (hasTagged(uuid))
{
sql = "UPDATE Players SET PlayerTags = PlayerTags + 1 WHERE PlayerUuidMost = ? AND PlayerUuidLeast = ?;";
}
else
{
sql = "INSERT INTO Players (PlayerUuidMost, PlayerUuidLeast) VALUES (?, ?);";
}
// prepare the statement
try (PreparedStatement ps = TagConnection.getConnection().prepareStatement(sql))
{
// set the parameters
ps.setLong(1, uuid.getMostSignificantBits());
ps.setLong(2, uuid.getLeastSignificantBits());
// execute the code
ps.execute();
// run the runnable if it's not null
if (runnable != null)
{
runnable.run();
}
}
catch (SQLException ex)
{
ex.printStackTrace();
}
});
}
/**
* Returns the number of tags the given player has.
*
* @param uuid the player's uuid.
*/
public static void getTags(UUID uuid, Consumer<Integer> consumer)
{
Bukkit.getScheduler().runTaskAsynchronously(AltitudeTag.getInstance(), () ->
{
String sql = "SELECT PlayerTags FROM Players WHERE PlayerUuidMost = ? AND PlayerUuidLeast = ?;";
try (PreparedStatement ps = TagConnection.getConnection().prepareStatement(sql))
{
ps.setLong(1, uuid.getMostSignificantBits());
ps.setLong(2, uuid.getLeastSignificantBits());
ResultSet rs = ps.getResultSet();
if (rs.next())
{
// call the consumer when the query returns back
consumer.accept(rs.getInt(1));
}
}
catch (SQLException ex)
{
ex.printStackTrace();
}
});
}
public static boolean hasTagged(UUID uuid)
{
String sql = "SELECT COUNT(*) FROM Players WHERE PlayerUuidMost = ? AND PlayerUuidLeast = ?;";
try (PreparedStatement ps = TagConnection.getConnection().prepareStatement(sql))
{
ps.setLong(1, uuid.getMostSignificantBits());
ps.setLong(2, uuid.getLeastSignificantBits());
ResultSet rs = ps.getResultSet();
return rs.next() && rs.getInt(1) > 0;
}
catch (SQLException ex)
{
ex.printStackTrace();
}
return false;
}
}

View File

@ -0,0 +1,21 @@
package com.alttd.altitudetag;
public enum Permission
{
PRIORITY_QUEUE("altiqueue.priority-queue"),
SKIP_QUEUE("altiqueue.skip-queue"),
QUEUE_COMMAND("altiqueue.queue-command");
private String permission;
private Permission(String permission)
{
this.permission = permission;
}
public String getPermission()
{
return permission;
}
}

View File

@ -0,0 +1,75 @@
package com.alttd.altitudetag;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import com.alttd.altitudetag.configuration.Config;
public class TagConnection
{
private static TagConnection instance;
private Connection connection;
private String host;
private String database;
private String username;
private String password;
private int port;
private TagConnection()
{
this.host = Config.DATABASE_HOSTNAME.getValue();
this.database = Config.DATABASE_DATABASE.getValue();
this.username = Config.DATABASE_USERNAME.getValue();
this.password = Config.DATABASE_PASSWORD.getValue();
this.port = Config.DATABASE_PORT.getValue();
try
{
instance.openConnection();
}
catch (SQLException | ClassNotFoundException ex)
{
ex.printStackTrace();
}
}
private void openConnection() throws SQLException, ClassNotFoundException
{
if (connection != null && !connection.isClosed())
{
return;
}
synchronized (this)
{
if (connection != null && !connection.isClosed())
{
return;
}
Class.forName("org.mariadb.jdbc.Driver");
connection = DriverManager.getConnection("jdbc:mariadb://" + this.host + ":" + this.port + "/" + this.database, this.username, this.password);
}
}
public static Connection getConnection()
{
try
{
instance.openConnection();
}
catch (SQLException | ClassNotFoundException ex)
{
ex.printStackTrace();
}
return instance.connection;
}
public static void initialize()
{
instance = new TagConnection();
}
}

View File

@ -0,0 +1,167 @@
package com.alttd.altitudetag.configuration;
import java.util.Objects;
import com.alttd.altitudeapi.utils.MutableValue;
import com.alttd.altitudetag.AltitudeTag;
import org.bukkit.configuration.file.FileConfiguration;
public final class Config
{
/**
* The plugin's version
*/
public static final MutableValue<String> VERSION = new MutableValue<>("1.1.2");
/**
* The hostname of the database if one is used.
*/
public static final MutableValue<String> DATABASE_HOSTNAME = new MutableValue<>("localhost");
/**
* The port of the database if one is used.
*/
public static final MutableValue<Integer> DATABASE_PORT = new MutableValue<>(27017);
/**
* The username of the user for the database if one is used.
*/
public static final MutableValue<String> DATABASE_USERNAME = new MutableValue<>("root");
/**
* The password of the database user if one is used.
*/
public static final MutableValue<String> DATABASE_PASSWORD = new MutableValue<>("password");
/**
* The database to use in the database if one is used.
*/
public static final MutableValue<String> DATABASE_DATABASE = new MutableValue<>("factions");
/**
* How long the system should wait when trying to connect to a database.
*/
public static final MutableValue<Integer> DATABASE_TIMEOUT = new MutableValue<>(100);
/**
* The description given to a connection if the DBMS supports that feature.
*/
public static final MutableValue<String> DATABASE_CONNECTION_DESCRIPTION = new MutableValue<>("Altitude Connection");
/**
* Update the values from the config file.
*/
public static void update()
{
FileConfiguration config = AltitudeTag.getInstance().getConfig();
MutableValue<Boolean> save = new MutableValue<>(false);
// how to optimize operations
updateValue(config, save, "version", VERSION);
// database connection information
updateValue(config, save, "database.hostname", DATABASE_HOSTNAME);
updateValue(config, save, "database.port", DATABASE_PORT);
updateValue(config, save, "database.username", DATABASE_USERNAME);
updateValue(config, save, "database.password", DATABASE_PASSWORD);
updateValue(config, save, "database.database", DATABASE_DATABASE);
updateValue(config, save, "database.timeout", DATABASE_TIMEOUT);
updateValue(config, save, "database.description", DATABASE_CONNECTION_DESCRIPTION);
if (save.getValue())
{
AltitudeTag.getInstance().saveConfig();
}
}
/**
* Updates the configuration with the given information. If the value fails to load from the config because it does
* not exist or it is in an invalid format, the system will notify the console.
*
* @param config the config file to load/update.
* @param location the location in the config.
* @param mutable the mutable value to update.
*/
private static <T> void updateValue(FileConfiguration config, MutableValue<Boolean> save, String location, MutableValue<T> mutable)
{
if (!config.isSet(location) || !successful(() -> mutable.setValue(loadValue(config, mutable.getType(), location))))
{
error(location);
config.set(location, mutable.getValue().toString());
if (!save.getValue())
{
save.setValue(true);
}
}
}
/**
* Used to check if an operation throws an exception with ease.
*
* @param runnable the operation to run.
*
* @return {@code true} if the operation does NOT throw an exception.<br>
* {@code false} if the operation DOES throw an exception.
*/
protected static boolean successful(Runnable runnable)
{
try
{
runnable.run();
}
catch (Exception ex)
{
ex.printStackTrace();
return false;
}
return true;
}
/**
* Alerts the console that there was an error loading a config value.
*
* @param location the location that caused an error.
*/
private static void error(String location)
{
AltitudeTag.getInstance().getLogger().severe("Error loading the config value '" + location + "'. Reverted it to default.");
}
@SuppressWarnings("unchecked")
public static <T> T loadValue(FileConfiguration config, Class<? super T> clazz, String location)
{
if (config == null)
{
throw new IllegalArgumentException("Config parameter can't be null.");
}
if (clazz == null)
{
throw new IllegalArgumentException("Class parameter can't be null.");
}
if (clazz == Integer.class)
{
return (T) Integer.valueOf(config.getInt(location));
}
else if (clazz == String.class)
{
return (T) config.getString(location);
}
else if (clazz == Boolean.class)
{
return (T) Boolean.valueOf(config.getBoolean(location));
}
else if (clazz == Double.class)
{
return (T) Double.valueOf(config.getDouble(location));
}
else if (Enum.class.isAssignableFrom(clazz))
{
return (T) Enum.valueOf((Class<? extends Enum>) clazz, Objects.requireNonNull(config.getString(location)));
}
// TODO throw exception since the type is weird
return null;
}
}

View File

@ -0,0 +1,245 @@
package com.alttd.altitudetag.configuration;
import java.io.File;
import java.util.Arrays;
import com.alttd.altitudeapi.utils.CollectionUtils;
import com.alttd.altitudeapi.utils.StringUtils;
import com.alttd.altitudetag.AltitudeTag;
import org.apache.commons.lang.mutable.MutableBoolean;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
/**
* The system for processing and sending messages to players.
*
* @author Michael Ziluck
*/
public enum Lang
{
/**
* The prefix before most of the lang messages.
*/
PREFIX("prefix", "&3[&bAltitudeTag&3] &f{message}"),
/**
* When a player misuses a command.
*/
USAGE("usage_message", "&6&lUSAGE &e» &f{usage}"),
/**
* The prefix to an error message.
*/
ERROR("error_message", "&4&lERROR &c» &7{message}"),
/**
* The prefix to a success message.
*/
SUCCESS("success_message", "&2&lSUCCESS &a» &f{message}"),
/**
* When players do not have permission to do something.
*/
NO_PERMS("no_permissions", "You don't have permission to do that."),
/**
* When a player does not have access to any sub-commands.
*/
NO_SUBS("no_sub_access", "You don't have access to any sub-commands."),
/**
* When the configuration files were successfully reloaded.
*/
RELOAD("reload", "Reloaded the config and lang files."),
/**
* The header and footer for all commands.
*/
HEADER_FOOTER("header_footer", "&7&m-----------------------------------"),
/**
* When the console tries to run a player-only command.
*/
ONLY_PLAYERS("only_players", "Only players can run that command."),
YOURE_IT("youre-it", "You're it! Try to tag someone!"),
TAGGED("tagged", "Nice tag! You're up to {tags} tags!");
private String[] message;
private String path;
Lang(String path, String... message)
{
this.path = path;
this.message = message;
}
/**
* Retrieves the message for this Lang object. This can be changed by editing the language configuration files, so
* they should NOT be treated as constants. Additionally their Strings should NOT be stored to reference anything.
*
* @return the message for this Lang object.
*/
public String[] getRawMessage()
{
return message;
}
/**
* Sets the message for this Lang object. This should not be done after startup to ensure data security.
*
* @param message the new message.
*/
public void setRawMessage(String... message)
{
this.message = message;
}
/**
* Retrieves the message for this Lang object. This can be changed by editing the language configuration files, so
* they should NOT be treated as constants. Additionally, their Strings should NOT be stored to reference anything.
* Lastly, this returns the combined version of the message in the case that there are multiple.
*
* @return the message for this Lang object.
*/
public String getRawMessageCompiled()
{
return StringUtils.compile(message);
}
/**
* @return the path of option in the lang.yml file.
*/
public String getPath()
{
return path;
}
/**
* Sends this Lang object to the CommandSender target. The parameters replace all placeholders that exist in the
* String as well.
*
* @param sender the CommandSender receiving the message.
* @param parameters all additional arguments to fill placeholders.
*/
public void send(CommandSender sender, Object... parameters)
{
sender.sendMessage(getMessage(parameters));
}
/**
* Sends this Lang object but prepended with the ERROR value as well.
*
* @param sender the CommandSender receiving the message.
* @param parameters all additional arguments to fill placeholders.
*/
public void sendError(CommandSender sender, Object... parameters)
{
for (String line : getMessage(parameters))
{
ERROR.send(sender, "{message}", line);
}
}
/**
* Sends this Lang object but prepended with the SUCCESS value as well.
*
* @param sender the CommandSender receiving the message.
* @param parameters all additional arguments to fill placeholders.
*/
public void sendSuccess(CommandSender sender, Object... parameters)
{
for (String line : getMessage(parameters))
{
SUCCESS.send(sender, "{message}", line);
}
}
/**
* Sends this Lang object but prepended with the PREFIX value as well.
*
* @param sender the CommandSender receiving the message.
* @param parameters all additional arguments to fill placeholders.
*/
public void sendInfo(CommandSender sender, Object... parameters)
{
for (String line : getMessage(parameters))
{
PREFIX.send(sender, "{message}", line);
}
}
/**
* Renders this message and returns it. Similar behavior to {@link #send(CommandSender, Object...)}, but instead of sending the message, it simply returns it.
*
* @param parameters all additional arguments to fill placeholders.
*
* @return the compiled message.
*/
public String[] getMessage(Object... parameters)
{
String[] args = Arrays.copyOf(message, message.length);
for (int i = 0; i < args.length; i++)
{
args[i] = renderString(args[i], parameters);
}
return args;
}
/**
* Render a string with the proper parameters.
*
* @param string the rendered string.
* @param args the placeholders and proper content.
*
* @return the rendered string.
*/
protected String renderString(String string, Object... args)
{
if (args.length % 2 != 0)
{
throw new IllegalArgumentException("Message rendering requires arguments of an even number. " + Arrays.toString(args) + " given.");
}
for (int i = 0; i < args.length; i += 2)
{
string = string.replace(args[i].toString(), CollectionUtils.firstNonNull(args[i + 1], "").toString());
}
return string;
}
public static void update()
{
File langFile = new File(AltitudeTag.getInstance().getDataFolder(), "lang.yml");
FileConfiguration config = YamlConfiguration.loadConfiguration(langFile);
final MutableBoolean save = new MutableBoolean(false);
for (Lang lang : values())
{
if (!config.isSet(lang.getPath()) || !Config.successful(() -> lang.setRawMessage(config.getString(lang.getPath()))))
{
config.set(lang.getPath(), lang.getRawMessage());
error(lang.getPath());
if (!save.booleanValue())
{
save.setValue(true);
}
}
}
}
/**
* Alerts the console that there was an error loading a config value.
*
* @param location the location that caused an error.
*/
private static void error(String location)
{
AltitudeTag.getInstance().getLogger().severe("Error loading the lang value '" + location + "'. Reverted it to default.");
}
public static void sendUsageMessage(CommandSender sender, String[] label, String[] parameters)
{
StringBuilder args = new StringBuilder("/" + StringUtils.compile(label));
for (String str : parameters)
{
args.append(" [").append(str).append("]");
}
USAGE.send(sender, "{usage}", args.toString());
}
}

View File

@ -0,0 +1,35 @@
package com.alttd.altitudetag.listeners;
import com.alttd.altitudeapi.utils.CollectionUtils;
import com.alttd.altitudetag.AltitudeTag;
import com.alttd.altitudetag.configuration.Lang;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerQuitEvent;
public class ConnectionListener implements Listener
{
@EventHandler
public void onJoin(PlayerJoinEvent event)
{
if (AltitudeTag.getTagger() == null)
{
AltitudeTag.setTagger(event.getPlayer().getUniqueId());
Lang.YOURE_IT.send(event.getPlayer());
}
}
@EventHandler
public void onLeave(PlayerQuitEvent event)
{
if (event.getPlayer().getUniqueId().equals(AltitudeTag.getTagger()))
{
AltitudeTag.setTagger(CollectionUtils.randomValue(Bukkit.getOnlinePlayers()).getUniqueId());
Lang.YOURE_IT.send(event.getPlayer());
}
}
}

View File

@ -0,0 +1,44 @@
package com.alttd.altitudetag.listeners;
import com.alttd.altitudetag.AltitudeTag;
import com.alttd.altitudetag.configuration.Lang;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.entity.EntityDamageByEntityEvent;
public class InteractListener implements Listener
{
@EventHandler
public void onHit(EntityDamageByEntityEvent event)
{
if (event.getDamager() instanceof Player && event.getEntity() instanceof Player)
{
Player tagger = (Player) event.getDamager();
Player tagged = (Player) event.getEntity();
// add the new tag
AltitudeTag.addTag(tagger.getUniqueId(), () ->
{
// if they're still online...
if (tagger.isOnline())
{
// get their tags...
AltitudeTag.getTags(tagger.getUniqueId(), (tags) ->
{
// if they're still online...
if (tagger.isOnline())
{
// let em know how they're doing!
Lang.TAGGED.send(tagger, "{tags}", tags);
}
});
}
});
AltitudeTag.setTagger(tagged.getUniqueId());
Lang.YOURE_IT.send(tagged);
}
}
}

View File

@ -0,0 +1,8 @@
database:
hostname: localhost
port: 3306
username: root
password: password
database: AltitudeTag
timeout: 100
description: 'Altitude Connection'

View File

@ -0,0 +1,9 @@
prefix: "§3[§bFortuneBlocks§3] §f{message}"
usage_message: "§6§lUSAGE §e» §f{usage}"
error_message: "§4§lERROR §c» §7{message}"
success_message: "§2§lSUCCESS §a» §f{message}"
no_permissions: "You don't have permission to do that."
no_sub_access: "You don't have access to any sub-commands."
reload: "Reloaded the config and lang files."
header_footer: "&7&m-----------------------------------"
only_players: "Only players can run that command."

View File

@ -0,0 +1,4 @@
main: com.alttd.altitudetag.AltitudeTag
name: AltitudeTag
version: ${project.version}
author: Michael Ziluck

View File

@ -0,0 +1,51 @@
package com.alttd.altiqueue.configuration;
import java.io.File;
import com.alttd.altitudetag.configuration.Lang;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.configuration.file.YamlConfiguration;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
public class LangTest
{
private FileConfiguration config;
@Before
public void setup()
{
File file = new File("src/main/resources/lang.yml");
config = YamlConfiguration.loadConfiguration(file);
}
@Test
public void test_file_contains_options()
{
for (Lang lang : Lang.values())
{
System.out.println(lang.getPath() + ": \"" + lang.getRawMessageCompiled() + "\"");
}
for (Lang lang : Lang.values())
{
if (!config.contains(lang.getPath()))
{
Assert.fail("Value missing from lang.yml: " + lang.name());
}
}
}
@Test
public void test_defaults_match()
{
for (Lang lang : Lang.values())
{
if (!config.getString(lang.getPath()).equals(lang.getRawMessageCompiled()))
{
Assert.fail("Lang values don't match: " + lang.name());
}
}
}
}