refactor(layout): clean packaging

This commit is contained in:
aro 2023-01-31 15:26:09 +01:00
parent 273ffb28ec
commit 575224a69d
80 changed files with 48 additions and 18 deletions

View file

@ -0,0 +1,175 @@
<?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/maven-v4_0_0.xsd">
<parent>
<artifactId>nicko-parent</artifactId>
<groupId>net.artelnatif</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>nicko-core</artifactId>
<version>1.0-SNAPSHOT</version>
<build>
<resources>
<resource>
<filtering>true</filtering>
<directory>${basedir}/src/main/resources/</directory>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.1-SNAPSHOT</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<includes>
<include>net.wesjd:anvilgui</include>
<include>de.studiocode.invui:*</include>
<include>com.github.jsixface:*</include>
<include>com.fasterxml.jackson.dataformat</include>
<include>com.fasterxml.jackson.core</include>
<include>org.mariadb.jdbc</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>net.wesjd.anvilgui</pattern>
<shadedPattern>net.artelnatif.libs.anvilgui</shadedPattern>
</relocation>
<relocation>
<pattern>de.studiocode.invui</pattern>
<shadedPattern>net.artelnatif.libs.invui</shadedPattern>
</relocation>
<relocation>
<pattern>com.github.jsixface</pattern>
<shadedPattern>net.artelnatif.libs.yaml</shadedPattern>
</relocation>
<relocation>
<pattern>com.fasterxml.jackson.dataformat</pattern>
<shadedPattern>net.artelnatif.libs.jackson.yaml</shadedPattern>
</relocation>
<relocation>
<pattern>com.fasterxml.jackson.core</pattern>
<shadedPattern>net.artelnatif.libs.jackson.core</shadedPattern>
</relocation>
<relocation>
<pattern>org.mariadb.jdbc</pattern>
<shadedPattern>net.artelnatif.libs.mariadb</shadedPattern>
</relocation>
</relocations>
<minimizeJar>false</minimizeJar>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>papermc</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>xenondevs</id>
<url>https://repo.xenondevs.xyz/releases</url>
</repository>
<repository>
<id>bungeecord-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
<repository>
<id>codemc-snapshots</id>
<url>https://repo.codemc.io/repository/maven-snapshots/</url>
</repository>
<repository>
<id>placeholderapi</id>
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
</repository>
</repositories>
<dependencies>
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>2.11.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.19.3-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.18-R0.1-SNAPSHOT</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.github.seeseemelk</groupId>
<artifactId>MockBukkit-v1.19</artifactId>
<version>2.29.0</version>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>paper-api</artifactId>
<groupId>io.papermc.paper</groupId>
</exclusion>
<exclusion>
<artifactId>junit-jupiter</artifactId>
<groupId>org.junit.jupiter</groupId>
</exclusion>
<exclusion>
<artifactId>hamcrest-library</artifactId>
<groupId>org.hamcrest</groupId>
</exclusion>
<exclusion>
<artifactId>adventure-platform-bungeecord</artifactId>
<groupId>net.kyori</groupId>
</exclusion>
<exclusion>
<artifactId>maven-resolver-provider</artifactId>
<groupId>org.apache.maven</groupId>
</exclusion>
<exclusion>
<artifactId>maven-resolver-connector-basic</artifactId>
<groupId>org.apache.maven.resolver</groupId>
</exclusion>
<exclusion>
<artifactId>maven-resolver-transport-http</artifactId>
<groupId>org.apache.maven.resolver</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<properties>
<maven.compiler.target>17</maven.compiler.target>
<maven.compiler.source>17</maven.compiler.source>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
</project>

196
core/pom.xml Normal file
View file

@ -0,0 +1,196 @@
<?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>
<artifactId>core</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>net.artelnatif</groupId>
<artifactId>nicko-parent</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<repositories>
<repository>
<id>papermc</id>
<url>https://repo.papermc.io/repository/maven-public/</url>
</repository>
<repository>
<id>xenondevs</id>
<url>https://repo.xenondevs.xyz/releases</url>
</repository>
<repository>
<id>bungeecord-repo</id>
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
</repository>
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/groups/public/</url>
</repository>
<repository>
<id>codemc-snapshots</id>
<url>https://repo.codemc.io/repository/maven-snapshots/</url>
</repository>
<repository>
<id>placeholderapi</id>
<url>https://repo.extendedclip.com/content/repositories/placeholderapi/</url>
</repository>
</repositories>
<dependencies>
<!-- PlaceHolder API -->
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>2.11.2</version>
<scope>provided</scope>
</dependency>
<!-- Spigot API -->
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.19.3-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!-- BungeeCord API -->
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-api</artifactId>
<version>1.18-R0.1-SNAPSHOT</version>
</dependency>
<!-- Inventory Lib -->
<dependency>
<groupId>de.studiocode.invui</groupId>
<artifactId>InvUI</artifactId>
<version>0.10.2</version>
</dependency>
<!-- AnvilGUI -->
<dependency>
<groupId>net.wesjd</groupId>
<artifactId>anvilgui</artifactId>
<version>1.6.3-SNAPSHOT</version>
</dependency>
<!-- Apache Lang3 (LocaleUtils) -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.12.0</version>
</dependency>
<!-- Google Guava (GSON) -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
<scope>provided</scope>
</dependency>
<!-- MockBukkit 1.19 (Bukkit Unit Tests) -->
<dependency>
<groupId>com.github.seeseemelk</groupId>
<artifactId>MockBukkit-v1.19</artifactId>
<version>2.29.0</version>
<scope>test</scope>
</dependency>
<!-- MariaDB JDBC Driver -->
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.1.0</version>
</dependency>
<!-- YAML Reader -->
<dependency>
<groupId>com.github.jsixface</groupId>
<artifactId>yamlconfig</artifactId>
<version>1.1.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.core/jackson-core -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.14.0-rc1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-yaml</artifactId>
<version>2.14.0-rc1</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>3.0.0-M7</version>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.3.1-SNAPSHOT</version>
<executions>
<execution>
<phase>package</phase>
<goals>
<goal>shade</goal>
</goals>
<configuration>
<artifactSet>
<includes>
<include>net.wesjd:anvilgui</include>
<include>de.studiocode.invui:*</include>
<include>com.github.jsixface:*</include>
<include>com.fasterxml.jackson.dataformat</include>
<include>com.fasterxml.jackson.core</include>
<include>org.mariadb.jdbc</include>
</includes>
</artifactSet>
<relocations>
<relocation>
<pattern>net.wesjd.anvilgui</pattern>
<shadedPattern>net.artelnatif.libs.anvilgui</shadedPattern>
</relocation>
<relocation>
<pattern>de.studiocode.invui</pattern>
<shadedPattern>net.artelnatif.libs.invui</shadedPattern>
</relocation>
<relocation>
<pattern>com.github.jsixface</pattern>
<shadedPattern>net.artelnatif.libs.yaml</shadedPattern>
</relocation>
<relocation>
<pattern>com.fasterxml.jackson.dataformat</pattern>
<shadedPattern>net.artelnatif.libs.jackson.yaml</shadedPattern>
</relocation>
<relocation>
<pattern>com.fasterxml.jackson.core</pattern>
<shadedPattern>net.artelnatif.libs.jackson.core</shadedPattern>
</relocation>
<relocation>
<pattern>org.mariadb.jdbc</pattern>
<shadedPattern>net.artelnatif.libs.mariadb</shadedPattern>
</relocation>
</relocations>
<!-- Prevents breaking AnvilGUI's VersionWrapper. -->
<minimizeJar>false</minimizeJar>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<filtering>true</filtering>
<directory>${basedir}/src/main/resources/</directory>
</resource>
</resources>
</build>
</project>

View file

@ -0,0 +1,86 @@
package net.artelnatif.nicko;
import net.artelnatif.nicko.config.Configuration;
import net.artelnatif.nicko.config.ConfigurationManager;
import net.artelnatif.nicko.mojang.MojangAPI;
import net.artelnatif.nicko.storage.PlayerDataStore;
import net.md_5.bungee.api.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
import java.io.File;
import java.io.IOException;
import java.util.logging.Logger;
public class Nicko {
private boolean bungeecord = false;
private ConfigurationManager configManager;
private Logger logger;
private File dataFolder;
private MojangAPI mojangAPI;
private Configuration config;
private PlayerDataStore dataStore;
public void initBungeecord(Plugin bungee) {
logger = bungee.getLogger();
dataFolder = bungee.getDataFolder();
bungeecord = true;
initNicko();
}
public void initBukkit(JavaPlugin bukkit) {
logger = bukkit.getLogger();
dataFolder = bukkit.getDataFolder();
initNicko();
}
private void initNicko() {
configManager = new ConfigurationManager(this);
configManager.saveDefaultConfig();
mojangAPI = new MojangAPI(this);
dataStore = new PlayerDataStore(this);
}
public Logger getLogger() {
return logger;
}
public PlayerDataStore getDataStore() {
return dataStore;
}
public File getDataFolder() {
return dataFolder;
}
public MojangAPI getMojangAPI() {
return mojangAPI;
}
public ConfigurationManager getConfigManager() {
return configManager;
}
public Configuration getConfig() {
try {
if (config == null) { return config = configManager.load(); }
return config;
} catch (IOException e) {
logger.severe("Failed to load configuration file: " + e.getMessage());
return null;
}
}
public void setConfig(Configuration config) {
this.config = config;
}
public boolean isBungeecord() {
return bungeecord;
}
public void setBungeecord(boolean bungeecord) {
this.bungeecord = bungeecord;
}
}

View file

@ -0,0 +1,37 @@
package net.artelnatif.nicko.bukkit;
import org.bukkit.Server;
import org.bukkit.configuration.file.YamlConfiguration;
public class BungeeCordSupport {
private final NickoBukkit instance;
public BungeeCordSupport(NickoBukkit instance) {
this.instance = instance;
}
public void warnNickoNotHookedToBungeeCord() {
final Server server = instance.getServer();
final YamlConfiguration config = server.spigot().getConfig();
if (config.getConfigurationSection("settings").getBoolean("bungeecord") && !instance.getNicko().getConfig().bungeecord()) {
instance.getLogger().warning("Hummm. Your server is hooked to BungeeCord, but it seems");
instance.getLogger().warning("that BungeeCord support is not enabled inside Nicko.");
instance.getLogger().warning("If this is intentional, you can safely ignore this message.");
instance.getLogger().warning("Otherwise, you can enable BungeeCord support inside Nicko's configuration file.");
}
}
public boolean stopIfBungeeCordIsNotEnabled() {
final Server server = instance.getServer();
final YamlConfiguration config = server.spigot().getConfig();
if (!config.getConfigurationSection("settings").getBoolean("bungeecord") && instance.getNicko().getConfig().bungeecord()) {
instance.getLogger().severe("Hummm. You have enabled BungeeCord support inside Nicko,");
instance.getLogger().severe("but it seems that your server is not hooked to your BungeeCord instance.");
instance.getLogger().severe("Please enable BungeeCord support inside your spigot.yml as well.");
instance.getLogger().severe("The plugin will not continue.");
instance.getServer().getPluginManager().disablePlugin(instance);
return false;
}
return true;
}
}

View file

@ -0,0 +1,163 @@
package net.artelnatif.nicko.bukkit;
import de.studiocode.invui.gui.structure.Structure;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.SimpleItem;
import net.artelnatif.nicko.Nicko;
import net.artelnatif.nicko.bukkit.command.NickoCommand;
import net.artelnatif.nicko.bukkit.gui.items.main.ExitGUI;
import net.artelnatif.nicko.bukkit.pluginchannel.PluginMessageHandler;
import net.artelnatif.nicko.bungee.NickoBungee;
import net.artelnatif.nicko.config.Configuration;
import net.artelnatif.nicko.bukkit.event.PlayerJoinListener;
import net.artelnatif.nicko.bukkit.event.PlayerQuitListener;
import net.artelnatif.nicko.bukkit.i18n.Locale;
import net.artelnatif.nicko.bukkit.i18n.LocaleFileManager;
import net.artelnatif.nicko.impl.Internals;
import net.artelnatif.nicko.impl.InternalsProvider;
import net.artelnatif.nicko.mojang.MojangAPI;
import net.artelnatif.nicko.bukkit.placeholder.PlaceHolderHook;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.command.PluginCommand;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.java.JavaPluginLoader;
import java.io.File;
public class NickoBukkit extends JavaPlugin {
private static NickoBukkit plugin;
private final Nicko nicko = new Nicko();
private final boolean unitTesting;
private MojangAPI mojangAPI;
private LocaleFileManager localeFileManager;
public NickoBukkit() { this.unitTesting = false; }
/**
* Used by MockBukkit
*/
protected NickoBukkit(JavaPluginLoader loader, PluginDescriptionFile description, File dataFolder, File file, Configuration config) {
super(loader, description, dataFolder, file);
unitTesting = true;
nicko.setConfig(config);
getLogger().info("Unit Testing Mode enabled.");
}
@Override
public void onEnable() {
plugin = this;
if (isUnitTesting()) {
onUnitTestingStartup();
} else {
onPluginStartup();
}
}
public void onUnitTestingStartup() {
nicko.initBukkit(this);
if (!nicko.getDataStore().getStorage().getProvider().init()) {
nicko.getDataStore().getStorage().setError(true);
getLogger().severe("Failed to open persistence, data will NOT be saved!");
}
}
public void onPluginStartup() {
nicko.initBukkit(this);
getLogger().info("Loading internals...");
if (getInternals() == null) {
getLogger().severe("Nicko could not find a valid implementation for this server version. Is your server supported?");
nicko.getDataStore().getStorage().setError(true);
getServer().getPluginManager().disablePlugin(this);
}
if (getServer().getPluginManager().isPluginEnabled(this) && !nicko.getDataStore().getStorage().isError()) {
getLogger().info("Loading persistence...");
if (!nicko.getDataStore().getStorage().getProvider().init()) {
nicko.getDataStore().getStorage().setError(true);
getLogger().severe("Failed to open persistence, data will NOT be saved!");
}
mojangAPI = new MojangAPI(nicko);
localeFileManager = new LocaleFileManager();
if (nicko.getConfig().customLocale()) {
if (localeFileManager.dumpFromLocale(Locale.ENGLISH)) {
getLogger().info("Successfully loaded custom language file.");
} else {
getLogger().severe("Failed to load custom language file!");
}
}
final PluginCommand command = getCommand("nicko");
if (command != null) {
command.setExecutor(new NickoCommand());
}
Structure.addGlobalIngredient('#', new SimpleItem(new ItemBuilder(Material.BLACK_STAINED_GLASS_PANE).setDisplayName(" ")));
Structure.addGlobalIngredient('%', new SimpleItem(new ItemBuilder(Material.ORANGE_STAINED_GLASS_PANE).setDisplayName(" ")));
Structure.addGlobalIngredient('E', new ExitGUI());
new PlaceHolderHook(this).hook();
getServer().getPluginManager().registerEvents(new PlayerJoinListener(), this);
getServer().getPluginManager().registerEvents(new PlayerQuitListener(), this);
final BungeeCordSupport support = new BungeeCordSupport(this);
support.warnNickoNotHookedToBungeeCord();
if (nicko.getConfig().bungeecord()) {
if (support.stopIfBungeeCordIsNotEnabled()) {
getLogger().info("Enabling BungeeCord support...");
getServer().getMessenger().registerIncomingPluginChannel(this, NickoBungee.PROXY_UPDATE, new PluginMessageHandler());
}
}
getLogger().info("Nicko (Bukkit) has been enabled.");
}
}
@Override
public void onDisable() {
if (!nicko.getDataStore().getStorage().isError()) {
getLogger().info("Closing persistence...");
nicko.getDataStore().removeAllNames();
Bukkit.getOnlinePlayers().forEach(player -> nicko.getDataStore().saveData(player));
if (!nicko.getDataStore().getStorage().getProvider().close()) {
getLogger().severe("Failed to close persistence!");
}
}
if (nicko.getConfig().bungeecord()) {
getServer().getMessenger().unregisterIncomingPluginChannel(this);
getServer().getMessenger().unregisterOutgoingPluginChannel(this);
}
getLogger().info("Nicko (Bukkit) has been disabled.");
}
public static NickoBukkit getInstance() {
return plugin;
}
public Nicko getNicko() {
return nicko;
}
public LocaleFileManager getLocaleFileManager() {
return localeFileManager;
}
public boolean isUnitTesting() {
return unitTesting;
}
public Internals getInternals() {
return InternalsProvider.getInternals();
}
}

View file

@ -0,0 +1,106 @@
package net.artelnatif.nicko.bukkit.anvil;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.bukkit.appearance.AppearanceManager;
import net.artelnatif.nicko.disguise.ActionResult;
import net.artelnatif.nicko.bukkit.i18n.I18N;
import net.artelnatif.nicko.bukkit.i18n.I18NDict;
import net.artelnatif.nicko.mojang.MojangUtils;
import net.wesjd.anvilgui.AnvilGUI;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import java.util.Collections;
import java.util.List;
public class AnvilManager {
private final Player player;
private final AppearanceManager appearanceManager;
public AnvilManager(Player player) {
this.player = player;
this.appearanceManager = AppearanceManager.get(player);
}
public void openNameThenSkinAnvil() {
getNameThenSkinAnvil().open(player);
}
public void openSkinAnvil() {
getSkinAnvil().open(player);
}
public void openNameAnvil() {
getNameAnvil().open(player);
}
public AnvilGUI.Builder getNameThenSkinAnvil() {
return new AnvilGUI.Builder()
.plugin(NickoBukkit.getInstance())
.itemLeft(getLeftItem(false))
.interactableSlots(AnvilGUI.Slot.OUTPUT)
.onComplete((completion) -> {
if (MojangUtils.isUsernameInvalid(completion.getText())) {
return Collections.singletonList(AnvilGUI.ResponseAction.replaceInputText("Invalid username!"));
} else {
appearanceManager.setName(completion.getText());
openSkinAnvil();
return Collections.singletonList(AnvilGUI.ResponseAction.close());
}
})
.text("New name...");
}
public AnvilGUI.Builder getNameAnvil() {
return new AnvilGUI.Builder()
.plugin(NickoBukkit.getInstance())
.itemLeft(getLeftItem(false))
.interactableSlots(AnvilGUI.Slot.OUTPUT)
.onComplete((completion) -> {
if (MojangUtils.isUsernameInvalid(completion.getText())) {
return Collections.singletonList(AnvilGUI.ResponseAction.replaceInputText("Invalid username!"));
} else {
appearanceManager.setName(completion.getText());
final ActionResult<Void> actionResult = appearanceManager.updatePlayer(false);
return sendResultAndClose(actionResult);
}
})
.text("New name...");
}
private AnvilGUI.Builder getSkinAnvil() {
return new AnvilGUI.Builder()
.plugin(NickoBukkit.getInstance())
.itemLeft(getLeftItem(true))
.interactableSlots(AnvilGUI.Slot.OUTPUT)
.onComplete((completion) -> {
if (MojangUtils.isUsernameInvalid(completion.getText())) {
return Collections.singletonList(AnvilGUI.ResponseAction.replaceInputText("Invalid username!"));
} else {
appearanceManager.setSkin(completion.getText());
final ActionResult<Void> actionResult = appearanceManager.updatePlayer(true);
return sendResultAndClose(actionResult);
}
})
.text("New skin...");
}
private List<AnvilGUI.ResponseAction> sendResultAndClose(ActionResult<Void> actionResult) {
if (!actionResult.isError()) {
player.sendMessage(I18N.translate(player, I18NDict.Event.Disguise.SUCCESS));
} else {
player.sendMessage(I18N.translate(player, I18NDict.Event.Disguise.FAIL, I18N.translateWithoutPrefix(player, actionResult.getErrorMessage())));
}
return Collections.singletonList(AnvilGUI.ResponseAction.close());
}
private ItemStack getLeftItem(boolean skin) {
final ItemStack item = new ItemStack(Material.PAPER);
final ItemMeta meta = item.getItemMeta();
meta.setDisplayName("§0New " + (skin ? "skin" : "name") + "...");
item.setItemMeta(meta);
return item;
}
}

View file

@ -0,0 +1,87 @@
package net.artelnatif.nicko.bukkit.appearance;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.disguise.ActionResult;
import net.artelnatif.nicko.disguise.NickoProfile;
import net.artelnatif.nicko.storage.PlayerDataStore;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.UUID;
public class AppearanceManager {
private final NickoProfile profile;
private final Player player;
private final NickoBukkit instance = NickoBukkit.getInstance();
private final PlayerDataStore dataStore = instance.getNicko().getDataStore();
private AppearanceManager(UUID uuid) {
this.player = Bukkit.getPlayer(uuid);
this.profile = dataStore.getData(uuid).orElse(NickoProfile.EMPTY_PROFILE.clone());
}
private AppearanceManager(String name) {
this.player = null;
this.profile = dataStore.getOfflineData(name).orElse(NickoProfile.EMPTY_PROFILE.clone());
}
public static AppearanceManager get(Player player) {
return new AppearanceManager(player.getUniqueId());
}
public static AppearanceManager get(String name) {
return new AppearanceManager(name);
}
public boolean hasData() {
return !profile.isEmpty();
}
public void setSkin(String skin) {
profile.setSkin(skin);
}
public String getSkin() {
return profile.getSkin();
}
public boolean needsASkinChange() {
return profile.getSkin() != null && !profile.getSkin().equals(player.getName());
}
public void setName(String name) {
profile.setName(name);
}
public String getName() {
return profile.getName();
}
public NickoProfile getProfile() {
return profile;
}
public void setNameAndSkin(String name, String skin) {
this.profile.setName(name);
this.profile.setSkin(skin);
updatePlayer(true);
}
public ActionResult<Void> reset() {
final String defaultName = instance.getNicko().getDataStore().getStoredName(player);
this.profile.setName(defaultName);
this.profile.setSkin(defaultName);
final ActionResult<Void> actionResult = resetPlayer();
this.profile.setSkin(null);
this.profile.setName(null);
return actionResult;
}
public ActionResult<Void> resetPlayer() {
return NickoBukkit.getInstance().getInternals().updateProfile(player, profile, true, true);
}
public ActionResult<Void> updatePlayer(boolean skinChange) {
return NickoBukkit.getInstance().getInternals().updateProfile(player, profile, skinChange, false);
}
}

View file

@ -0,0 +1,43 @@
package net.artelnatif.nicko.bukkit.command;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.bukkit.command.sub.NickoCheckSubCmd;
import net.artelnatif.nicko.bukkit.command.sub.NickoDebugSubCmd;
import net.artelnatif.nicko.bukkit.gui.MainGUI;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class NickoCommand implements CommandExecutor {
private String helpMessage = """
§cNicko §8§o[{version}] §f- §2Help:
§6/nicko §f- §7Open the GUI.
§6/nicko help §f- §7Print this help message.
""";
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (sender instanceof Player player) {
if (args.length >= 1) {
switch (args[0]) {
case "debug" -> new NickoDebugSubCmd().execute(sender, args);
case "check" -> new NickoCheckSubCmd().execute(player, args);
default -> sendHelpMessage(sender);
}
return false;
}
new MainGUI(player).open();
return false;
}
sender.sendMessage("This plugin can only be used in-game. Sorry!");
return false;
}
public void sendHelpMessage(CommandSender sender) {
helpMessage = helpMessage.replace("{version}", NickoBukkit.getInstance().getDescription().getVersion());
sender.sendMessage(helpMessage);
}
}

View file

@ -0,0 +1,42 @@
package net.artelnatif.nicko.bukkit.command.sub;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.bukkit.appearance.AppearanceManager;
import net.artelnatif.nicko.bukkit.i18n.I18N;
import net.artelnatif.nicko.bukkit.i18n.I18NDict;
import net.artelnatif.nicko.mojang.MojangUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.util.StringJoiner;
public class NickoCheckSubCmd {
public void execute(Player player, String[] args) {
final String targetName = args[1];
final Player target = Bukkit.getPlayerExact(targetName);
AppearanceManager appearanceManager;
if (MojangUtils.isUsernameInvalid(targetName)) {
player.sendMessage(I18N.translate(player, I18NDict.Error.INVALID_USERNAME));
return;
}
if (target == null) {
appearanceManager = AppearanceManager.get(targetName);
} else {
appearanceManager = AppearanceManager.get(target);
}
final StringJoiner builder = new StringJoiner("\n");
builder.add("§c" + NickoBukkit.getInstance().getNicko().getConfig().prefix() + "§6Check for: §f§o" + targetName);
if (!appearanceManager.hasData()) {
builder.add("§cThis player has not data.");
} else {
builder.add("§7- §fNicked: §a✔");
builder.add("§7- §fName: §6" + appearanceManager.getName());
builder.add("§7- §fSkin: §6" + appearanceManager.getSkin());
}
player.sendMessage(builder.toString());
}
}

View file

@ -0,0 +1,48 @@
package net.artelnatif.nicko.bukkit.command.sub;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.bukkit.appearance.AppearanceManager;
import net.artelnatif.nicko.mojang.MojangUtils;
import org.bukkit.Bukkit;
import org.bukkit.Sound;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class NickoDebugSubCmd {
public void execute(CommandSender sender, String[] args) {
final String prefix = NickoBukkit.getInstance().getNicko().getConfig().prefix();
Player target;
String name, skin;
if (args.length == 3) {
target = (Player) sender;
name = args[1];
skin = args[2];
} else {
if (args.length < 3) {
sender.sendMessage(prefix + "§cMissing argument.");
return;
}
final String playerName = args[1];
name = args[2];
skin = args[3];
target = Bukkit.getPlayer(playerName);
if (target == null) {
sender.sendMessage(prefix + "§cSpecified player is offline.");
return;
}
}
final AppearanceManager appearanceManager = AppearanceManager.get(target.getPlayer());
if (MojangUtils.isUsernameInvalid(name) || MojangUtils.isUsernameInvalid(skin)) {
sender.sendMessage(prefix + "§cSpecified username is invalid.");
}
appearanceManager.setNameAndSkin(name, skin);
target.sendMessage(prefix + "§aWhoosh!");
target.playSound(target.getLocation(), Sound.ENTITY_ITEM_FRAME_PLACE, 1, 1);
}
}

View file

@ -0,0 +1,34 @@
package net.artelnatif.nicko.bukkit.event;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.bukkit.appearance.AppearanceManager;
import net.artelnatif.nicko.disguise.ActionResult;
import net.artelnatif.nicko.bukkit.i18n.I18N;
import net.artelnatif.nicko.bukkit.i18n.I18NDict;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
public class PlayerJoinListener implements Listener {
@EventHandler
public void onPlayerJoin(PlayerJoinEvent event) {
final Player player = event.getPlayer();
NickoBukkit.getInstance().getNicko().getDataStore().storeName(player);
Bukkit.getScheduler().runTaskLater(NickoBukkit.getInstance(), () -> {
final AppearanceManager appearanceManager = AppearanceManager.get(player);
// TODO: 12/5/22 Update from BungeeCord
if (appearanceManager.hasData()) {
final ActionResult<Void> actionResult = appearanceManager.updatePlayer(appearanceManager.needsASkinChange());
if (!actionResult.isError()) {
player.sendMessage(I18N.translate(player, I18NDict.Event.PreviousSkin.SUCCESS));
} else {
player.sendMessage(I18N.translate(player, I18NDict.Event.PreviousSkin.FAIL, I18N.translate(player, actionResult.getErrorMessage())));
}
}
}, 20L);
}
}

View file

@ -0,0 +1,15 @@
package net.artelnatif.nicko.bukkit.event;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerQuitEvent;
public class PlayerQuitListener implements Listener {
@EventHandler
public void onPlayerQuit(PlayerQuitEvent event) {
final Player player = event.getPlayer();
NickoBukkit.getInstance().getNicko().getDataStore().saveData(player);
}
}

View file

@ -0,0 +1,35 @@
package net.artelnatif.nicko.bukkit.gui;
import de.studiocode.invui.gui.GUI;
import de.studiocode.invui.gui.builder.GUIBuilder;
import de.studiocode.invui.gui.builder.guitype.GUIType;
import de.studiocode.invui.window.impl.single.SimpleWindow;
import net.artelnatif.nicko.bukkit.gui.items.common.GoBack;
import net.artelnatif.nicko.bukkit.gui.items.admin.ManageCache;
import org.bukkit.entity.Player;
public class AdminGUI {
private final Player player;
private final GUI gui;
public AdminGUI(Player player) {
this.gui = new GUIBuilder<>(GUIType.NORMAL)
.setStructure(
"# # # # # # # # #",
"# % % X X S % % #",
"B # # # # # # # #"
)
.addIngredient('S', new ManageCache())
.addIngredient('B', new GoBack(new MainGUI(player).getGUI()))
.build();
this.player = player;
}
public GUI getGUI() {
return gui;
}
public void open() {
new SimpleWindow(player, "Nicko", gui).show();
}
}

View file

@ -0,0 +1,37 @@
package net.artelnatif.nicko.bukkit.gui;
import de.studiocode.invui.gui.GUI;
import de.studiocode.invui.gui.builder.GUIBuilder;
import de.studiocode.invui.gui.builder.guitype.GUIType;
import de.studiocode.invui.window.impl.single.SimpleWindow;
import net.artelnatif.nicko.bukkit.gui.items.common.GoBack;
import net.artelnatif.nicko.bukkit.gui.items.skin.ChangeSkin;
import net.artelnatif.nicko.bukkit.gui.items.skin.ChangeName;
import net.artelnatif.nicko.bukkit.gui.items.skin.ChangeNameAndSkin;
import org.bukkit.entity.Player;
public class AppearanceManagerGUI {
private final Player player;
private final GUI gui;
public AppearanceManagerGUI(Player player) {
this.gui = new GUIBuilder<>(GUIType.NORMAL)
.setStructure(
"# # # # # # # # #",
"# % % % % % % % #",
"# % # N A S # % #",
"# % % % % % % % #",
"B # # # # # # # #"
)
.addIngredient('N', new ChangeName())
.addIngredient('A', new ChangeNameAndSkin())
.addIngredient('S', new ChangeSkin())
.addIngredient('B', new GoBack(new MainGUI(player).getGUI()))
.build();
this.player = player;
}
public void open() {
new SimpleWindow(player, "Nicko", gui).show();
}
}

View file

@ -0,0 +1,46 @@
package net.artelnatif.nicko.bukkit.gui;
import de.studiocode.invui.gui.GUI;
import de.studiocode.invui.gui.builder.GUIBuilder;
import de.studiocode.invui.gui.builder.guitype.GUIType;
import de.studiocode.invui.window.impl.single.SimpleWindow;
import net.artelnatif.nicko.bukkit.gui.items.main.AdminSubGUI;
import net.artelnatif.nicko.bukkit.gui.items.main.AppearanceManagerSubGUI;
import net.artelnatif.nicko.bukkit.gui.items.main.ResetAppearance;
import net.artelnatif.nicko.bukkit.gui.items.main.SettingsSubGUI;
import org.bukkit.entity.Player;
public class MainGUI {
private final Player player;
private final GUI gui;
public MainGUI(Player player) {
final String[] dynamicStructure = new String[]{
"# # # # # # # # #",
"# % % % A % % % #",
"# % # R S P # % #",
"# % % % % % % % #",
"E # # # # # # # #"};
if (!player.hasPermission("nicko.admin") || !player.isOp()) {
dynamicStructure[3] = dynamicStructure[3].replace("A", "#");
}
this.gui = new GUIBuilder<>(GUIType.NORMAL)
.setStructure(dynamicStructure)
.addIngredient('R', new ResetAppearance())
.addIngredient('S', new AppearanceManagerSubGUI())
.addIngredient('P', new SettingsSubGUI())
.addIngredient('A', new AdminSubGUI())
.build();
this.player = player;
}
public GUI getGUI() {
return gui;
}
public void open() {
new SimpleWindow(player, "Nicko", gui).show();
}
}

View file

@ -0,0 +1,44 @@
package net.artelnatif.nicko.bukkit.gui;
import de.studiocode.invui.gui.GUI;
import de.studiocode.invui.gui.builder.GUIBuilder;
import de.studiocode.invui.gui.builder.guitype.GUIType;
import de.studiocode.invui.window.impl.single.SimpleWindow;
import net.artelnatif.nicko.Nicko;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.bukkit.gui.items.settings.LanguageCycling;
import net.artelnatif.nicko.bukkit.gui.items.common.GoBack;
import net.artelnatif.nicko.bukkit.gui.items.settings.BungeeCordCycling;
import net.artelnatif.nicko.bukkit.gui.items.settings.OptionUnavailable;
import org.bukkit.entity.Player;
public class SettingsGUI {
private final Player player;
private final GUI gui;
public SettingsGUI(Player player) {
final String[] dynamicStructure = new String[]{
"# # # # # # # # #",
"# % % L U T % % #",
"B # # # # # # # #"
};
final Nicko nicko = NickoBukkit.getInstance().getNicko();
if (!nicko.getConfig().bungeecord() && nicko.isBungeecord()) {
dynamicStructure[1] = dynamicStructure[1].replace("T", "U");
}
this.gui = new GUIBuilder<>(GUIType.NORMAL)
.setStructure(dynamicStructure)
.addIngredient('B', new GoBack(new MainGUI(player).getGUI()))
.addIngredient('L', new LanguageCycling().get(player))
.addIngredient('T', new BungeeCordCycling().get(player))
.addIngredient('U', new OptionUnavailable())
.build();
this.player = player;
}
public void open() {
new SimpleWindow(player, "Nicko", gui).show();
}
}

View file

@ -0,0 +1,36 @@
package net.artelnatif.nicko.bukkit.gui.admin;
import de.studiocode.invui.gui.GUI;
import de.studiocode.invui.gui.builder.GUIBuilder;
import de.studiocode.invui.gui.builder.guitype.GUIType;
import de.studiocode.invui.window.impl.single.SimpleWindow;
import net.artelnatif.nicko.bukkit.gui.AdminGUI;
import net.artelnatif.nicko.bukkit.gui.items.admin.cache.CacheDetailed;
import net.artelnatif.nicko.bukkit.gui.items.admin.cache.CacheInvalidate;
import net.artelnatif.nicko.bukkit.gui.items.admin.cache.CacheOverview;
import net.artelnatif.nicko.bukkit.gui.items.common.GoBack;
import org.bukkit.entity.Player;
public class CacheManagementGUI {
private final Player player;
private final GUI gui;
public CacheManagementGUI(Player player) {
this.gui = new GUIBuilder<>(GUIType.NORMAL)
.setStructure("B # S A D")
.addIngredient('B', new GoBack(new AdminGUI(player).getGUI()))
.addIngredient('S', new CacheOverview())
.addIngredient('A', new CacheInvalidate())
.addIngredient('D', new CacheDetailed())
.build();
this.player = player;
}
public GUI getGUI() {
return gui;
}
public void open() {
new SimpleWindow(player, "Nicko", gui).show();
}
}

View file

@ -0,0 +1,64 @@
package net.artelnatif.nicko.bukkit.gui.admin.cache;
import de.studiocode.invui.gui.GUI;
import de.studiocode.invui.gui.builder.GUIBuilder;
import de.studiocode.invui.gui.builder.guitype.GUIType;
import de.studiocode.invui.gui.structure.Markers;
import de.studiocode.invui.item.Item;
import de.studiocode.invui.window.impl.single.SimpleWindow;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.bukkit.gui.items.admin.cache.SkinPlaceholder;
import net.artelnatif.nicko.bukkit.gui.admin.CacheManagementGUI;
import net.artelnatif.nicko.bukkit.gui.items.common.GoBack;
import net.artelnatif.nicko.bukkit.gui.items.common.ScrollDown;
import net.artelnatif.nicko.bukkit.gui.items.common.ScrollUp;
import net.artelnatif.nicko.mojang.MojangSkin;
import org.bukkit.entity.Player;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentMap;
import java.util.stream.Collectors;
public class CacheDetailledGUI {
private final Player player;
private final GUI gui;
public CacheDetailledGUI(Player player) {
final ConcurrentMap<String, Optional<MojangSkin>> skins = NickoBukkit.getInstance().getNicko().getMojangAPI().getCache().asMap();
final List<String> loadedSkins = skins.entrySet().stream()
.filter(entry -> entry.getValue().isPresent())
.map(Map.Entry::getKey)
.toList();
final List<Item> items = loadedSkins.stream()
.map(SkinPlaceholder::new)
.collect(Collectors.toList());
this.gui = new GUIBuilder<>(GUIType.SCROLL_ITEMS)
.setStructure(
"% # # # # # # # %",
"# x x x x x x U #",
"# x x x x x x # #",
"# x x x x x x # #",
"# x x x x x x D #",
"B # # # # # # # %"
)
.addIngredient('x', Markers.ITEM_LIST_SLOT_HORIZONTAL)
.addIngredient('U', new ScrollUp())
.addIngredient('D', new ScrollDown())
.addIngredient('B', new GoBack(new CacheManagementGUI(player).getGUI()))
.setItems(items)
.build();
this.player = player;
}
public GUI getGUI() {
return gui;
}
public void open() {
new SimpleWindow(player, "Nicko", gui).show();
}
}

View file

@ -0,0 +1,33 @@
package net.artelnatif.nicko.bukkit.gui.items.admin;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.builder.SkullBuilder;
import de.studiocode.invui.item.impl.AsyncItem;
import net.artelnatif.nicko.bukkit.gui.admin.CacheManagementGUI;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.jetbrains.annotations.NotNull;
public class ManageCache extends AsyncItem {
public ManageCache() {
super(new ItemBuilder(Material.PAINTING)
.setDisplayName("§fManage §6skin §fcache...")
.addLoreLines("§7Access the skin cache management panel."),
() -> {
final SkullBuilder builder = new SkullBuilder("Notch");
builder.setDisplayName("§fManage §6skin §fcache...");
builder.addLoreLines("§7Access the skin cache management panel.");
return builder;
});
}
@Override
public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) {
if (clickType.isLeftClick() || clickType.isRightClick()) {
event.getView().close();
new CacheManagementGUI(player).open();
}
}
}

View file

@ -0,0 +1,29 @@
package net.artelnatif.nicko.bukkit.gui.items.admin.cache;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.BaseItem;
import net.artelnatif.nicko.bukkit.gui.admin.cache.CacheDetailledGUI;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.jetbrains.annotations.NotNull;
public class CacheDetailed extends BaseItem {
@Override
public ItemProvider getItemProvider() {
final ItemBuilder builder = new ItemBuilder(Material.PAPER);
builder.setDisplayName("§6Invalidate specific skin...");
builder.addLoreLines("§7Select a specific skin to invalidate.");
return builder;
}
@Override
public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) {
if (clickType.isLeftClick() || clickType.isRightClick()) {
event.getView().close();
new CacheDetailledGUI(player).open();
}
}
}

View file

@ -0,0 +1,37 @@
package net.artelnatif.nicko.bukkit.gui.items.admin.cache;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.BaseItem;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.bukkit.i18n.I18N;
import net.artelnatif.nicko.bukkit.i18n.I18NDict;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.jetbrains.annotations.NotNull;
public class CacheInvalidate extends BaseItem {
@Override
public ItemProvider getItemProvider() {
final ItemBuilder builder = new ItemBuilder(Material.TNT);
builder.setDisplayName("§fInvalidate §6skin cache");
builder.addLoreLines(
"§c§oNOT RECOMMENDED",
"§7Invalidates every skin entry present in the cache.",
"§7Does not reset player disguises.",
"§7Could be useful if a skin has been updated",
"§7recently and the cache is now outdated.");
return builder;
}
@Override
public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) {
if (clickType.isLeftClick() || clickType.isRightClick()) {
event.getView().close();
player.sendMessage(I18N.translate(player, I18NDict.Event.Admin.CACHE_CLEAN));
NickoBukkit.getInstance().getNicko().getMojangAPI().getCache().invalidateAll();
}
}
}

View file

@ -0,0 +1,39 @@
package net.artelnatif.nicko.bukkit.gui.items.admin.cache;
import com.google.common.cache.CacheStats;
import com.google.common.cache.LoadingCache;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.BaseItem;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.mojang.MojangSkin;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.jetbrains.annotations.NotNull;
import java.util.Optional;
public class CacheOverview extends BaseItem {
@Override
public ItemProvider getItemProvider() {
final ItemBuilder builder = new ItemBuilder(Material.OAK_SIGN);
final LoadingCache<String, Optional<MojangSkin>> cache = NickoBukkit.getInstance().getNicko().getMojangAPI().getCache();
final CacheStats stats = cache.stats();
builder.setDisplayName("§6Skin cache §foverview:");
builder.addLoreLines(
"Request Count: §2" + stats.requestCount(),
"Skin Cached: §2" + Math.round(cache.size()),
"§7§oCache is cleared every 24 hours.",
"§7§o(Click to refresh)");
return builder;
}
@Override
public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) {
if (clickType.isLeftClick() || clickType.isRightClick()) {
notifyWindows();
}
}
}

View file

@ -0,0 +1,21 @@
package net.artelnatif.nicko.bukkit.gui.items.admin.cache;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.builder.SkullBuilder;
import de.studiocode.invui.item.impl.AsyncItem;
import org.bukkit.Material;
import java.util.UUID;
public class SkinPlaceholder extends AsyncItem {
public SkinPlaceholder(String name) {
super(new ItemBuilder(Material.PAINTING).setDisplayName("§7§oLoading..."), () -> {
final String stringUUID = name.replaceAll("(.{8})(.{4})(.{4})(.{4})(.+)", "$1-$2-$3-$4-$5");
final UUID uuid = UUID.fromString(stringUUID);
final SkullBuilder skull = new SkullBuilder(uuid);
skull.setDisplayName("§6Skin Entry");
skull.addLoreLines("§7Click to invalidate skin");
return skull;
});
}
}

View file

@ -0,0 +1,34 @@
package net.artelnatif.nicko.bukkit.gui.items.common;
import de.studiocode.invui.gui.GUI;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.BaseItem;
import de.studiocode.invui.window.impl.single.SimpleWindow;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.jetbrains.annotations.NotNull;
public class GoBack extends BaseItem {
private final GUI gui;
public GoBack(GUI gui) {
this.gui = gui;
}
@Override
public ItemProvider getItemProvider() {
final ItemBuilder builder = new ItemBuilder(Material.ARROW);
builder.setDisplayName("Go back");
builder.addLoreLines("§7Return to the previous window.");
return builder;
}
@Override
public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) {
event.getView().close();
new SimpleWindow(player, "Nicko", gui).show();
}
}

View file

@ -0,0 +1,26 @@
package net.artelnatif.nicko.bukkit.gui.items.common;
import de.studiocode.invui.gui.impl.ScrollGUI;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.controlitem.ScrollItem;
import org.bukkit.Material;
public class ScrollDown extends ScrollItem {
public ScrollDown() {
super(1);
}
@Override
public ItemProvider getItemProvider(ScrollGUI gui) {
ItemBuilder builder = new ItemBuilder(Material.GREEN_STAINED_GLASS_PANE);
builder.setDisplayName("§7Scroll down");
if (!gui.canScroll(1))
builder.addLoreLines("§cYou can't scroll further down");
return builder;
}
}

View file

@ -0,0 +1,26 @@
package net.artelnatif.nicko.bukkit.gui.items.common;
import de.studiocode.invui.gui.impl.ScrollGUI;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.controlitem.ScrollItem;
import org.bukkit.Material;
public class ScrollUp extends ScrollItem {
public ScrollUp() {
super(-1);
}
@Override
public ItemProvider getItemProvider(ScrollGUI gui) {
ItemBuilder builder = new ItemBuilder(Material.RED_STAINED_GLASS_PANE);
builder.setDisplayName("§7Scroll up");
if (!gui.canScroll(-1))
builder.addLoreLines("§cYou've reached the top");
return builder;
}
}

View file

@ -0,0 +1,33 @@
package net.artelnatif.nicko.bukkit.gui.items.main;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.BaseItem;
import net.artelnatif.nicko.bukkit.gui.AdminGUI;
import org.bukkit.Material;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemFlag;
import org.jetbrains.annotations.NotNull;
public class AdminSubGUI extends BaseItem {
@Override
public ItemProvider getItemProvider() {
final ItemBuilder builder = new ItemBuilder(Material.COMMAND_BLOCK);
builder.addEnchantment(Enchantment.DAMAGE_ALL, 1, false);
builder.addItemFlags(ItemFlag.HIDE_ENCHANTS);
builder.setDisplayName("§cAdministration panel...");
builder.addLoreLines("§7Access the administration panel.");
return builder;
}
@Override
public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) {
if (clickType.isLeftClick() || clickType.isRightClick()) {
event.getView().close();
new AdminGUI(player).open();
}
}
}

View file

@ -0,0 +1,29 @@
package net.artelnatif.nicko.bukkit.gui.items.main;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.BaseItem;
import net.artelnatif.nicko.bukkit.gui.AppearanceManagerGUI;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.jetbrains.annotations.NotNull;
public class AppearanceManagerSubGUI extends BaseItem {
@Override
public ItemProvider getItemProvider() {
final ItemBuilder builder = new ItemBuilder(Material.ENDER_EYE);
builder.setDisplayName("§fManage §6appearance§f...");
builder.addLoreLines("§7Access the appearance manager.");
return builder;
}
@Override
public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) {
if (clickType.isLeftClick() || clickType.isRightClick()) {
event.getView().close();
new AppearanceManagerGUI(player).open();
}
}
}

View file

@ -0,0 +1,24 @@
package net.artelnatif.nicko.bukkit.gui.items.main;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.BaseItem;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.jetbrains.annotations.NotNull;
public class ExitGUI extends BaseItem {
@Override
public ItemProvider getItemProvider() {
return new ItemBuilder(Material.OAK_DOOR).setDisplayName("§fExit");
}
@Override
public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) {
if (clickType.isLeftClick() || clickType.isRightClick()) {
event.getView().close();
}
}
}

View file

@ -0,0 +1,42 @@
package net.artelnatif.nicko.bukkit.gui.items.main;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.BaseItem;
import net.artelnatif.nicko.bukkit.appearance.AppearanceManager;
import net.artelnatif.nicko.bukkit.i18n.I18N;
import net.artelnatif.nicko.bukkit.i18n.I18NDict;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.jetbrains.annotations.NotNull;
public class ResetAppearance extends BaseItem {
@Override
public ItemProvider getItemProvider() {
final ItemBuilder builder = new ItemBuilder(Material.TNT);
builder.setDisplayName("§fReset");
builder.addLoreLines("§7Get rid of your disguise.");
return builder;
}
@Override
public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) {
if (clickType.isLeftClick() || clickType.isRightClick()) {
final AppearanceManager appearanceManager = AppearanceManager.get(player);
if (!appearanceManager.hasData()) {
player.sendMessage(I18N.translate(player, I18NDict.Event.Undisguise.NONE));
event.getView().close();
return;
}
if (!appearanceManager.reset().isError()) {
player.sendMessage(I18N.translate(player, I18NDict.Event.Undisguise.SUCCESS));
} else {
player.sendMessage(I18N.translate(player, I18NDict.Event.Undisguise.FAIL));
}
}
}
}

View file

@ -0,0 +1,29 @@
package net.artelnatif.nicko.bukkit.gui.items.main;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.BaseItem;
import net.artelnatif.nicko.bukkit.gui.SettingsGUI;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.jetbrains.annotations.NotNull;
public class SettingsSubGUI extends BaseItem {
@Override
public ItemProvider getItemProvider() {
final ItemBuilder builder = new ItemBuilder(Material.COMPARATOR);
builder.setDisplayName("§fSettings...");
builder.addLoreLines("§7Adjust your preferences.");
return builder;
}
@Override
public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) {
if (clickType.isLeftClick() || clickType.isRightClick()) {
event.getView().close();
new SettingsGUI(player).open();
}
}
}

View file

@ -0,0 +1,49 @@
package net.artelnatif.nicko.bukkit.gui.items.settings;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.BaseItem;
import de.studiocode.invui.item.impl.CycleItem;
import de.studiocode.invui.item.impl.SimpleItem;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.disguise.NickoProfile;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import java.util.Optional;
public class BungeeCordCycling {
private final ItemProvider[] providers = new ItemProvider[]{
getItemProviderForValue(true),
getItemProviderForValue(false)
};
public BaseItem get(Player player) {
Optional<NickoProfile> profile = NickoBukkit.getInstance().getNicko().getDataStore().getData(player.getUniqueId());
if (profile.isPresent()) {
final NickoProfile nickoProfile = profile.get();
int startingState = nickoProfile.isBungeecordTransfer() ? 0 : 1;
return CycleItem.withStateChangeHandler((observer, integer) -> {
nickoProfile.setBungeecordTransfer(integer != 1);
observer.playSound(player, Sound.UI_BUTTON_CLICK, 1f, 0.707107f); // 0.707107 ~= C
}, startingState, providers);
}
return new SimpleItem(ItemProvider.EMPTY);
}
private ItemProvider getItemProviderForValue(boolean enabled) {
final ItemBuilder builder = new ItemBuilder(Material.COMPASS);
builder.setDisplayName("§6BungeeCord transfer:");
if (enabled) {
builder.addLoreLines("§7> §cDisabled");
builder.addLoreLines("§6§l> §a§lEnabled");
} else {
builder.addLoreLines("§6§l> §c§lDisabled");
builder.addLoreLines("§7> §aEnabled");
}
builder.addLoreLines("§7§oCycle through the values by", "§7§oleft and right clicking.");
return builder;
}
}

View file

@ -0,0 +1,61 @@
package net.artelnatif.nicko.bukkit.gui.items.settings;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.BaseItem;
import de.studiocode.invui.item.impl.CycleItem;
import de.studiocode.invui.item.impl.SimpleItem;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.disguise.NickoProfile;
import net.artelnatif.nicko.bukkit.i18n.Locale;
import org.bukkit.Material;
import org.bukkit.Sound;
import org.bukkit.entity.Player;
import java.util.*;
public class LanguageCycling {
private final ItemProvider[] providers = getItems();
public BaseItem get(Player player) {
final NickoBukkit instance = NickoBukkit.getInstance();
Optional<NickoProfile> profile = instance.getNicko().getDataStore().getData(player.getUniqueId());
if (profile.isPresent()) {
final NickoProfile nickoProfile = profile.get();
int localeOrdinal = nickoProfile.getLocale().ordinal();
return CycleItem.withStateChangeHandler((observer, integer) -> {
nickoProfile.setLocale(Locale.values()[integer]);
observer.playSound(player, Sound.UI_BUTTON_CLICK, 1f, 0.707107f); // 0.707107 ~= C
}, localeOrdinal, providers);
}
return new SimpleItem(ItemProvider.EMPTY);
}
private ItemProvider generateItem(Locale locale, List<Locale> locales) {
final ItemBuilder builder = new ItemBuilder(Material.OAK_SIGN);
builder.setDisplayName("§6Select your language:");
for (Locale value : locales) {
if (locale != value) {
builder.addLoreLines("§7> " + value.getName());
} else {
builder.addLoreLines("§6§l> §f" + value.getName());
}
}
builder.addLoreLines("§7§oCycle through the values by", "§7§oleft and right clicking.");
return builder;
}
private ItemProvider[] getItems() {
final NickoBukkit instance = NickoBukkit.getInstance();
final ArrayList<ItemProvider> items = new ArrayList<>();
final ArrayList<Locale> localesToGenerate = new ArrayList<>();
Collections.addAll(localesToGenerate, Locale.values());
if (!instance.getNicko().getConfig().customLocale()) {
localesToGenerate.remove(Locale.CUSTOM);
}
localesToGenerate.forEach(locale -> items.add(generateItem(locale, localesToGenerate)));
return items.toArray(new ItemProvider[]{});
}
}

View file

@ -0,0 +1,24 @@
package net.artelnatif.nicko.bukkit.gui.items.settings;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.BaseItem;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.jetbrains.annotations.NotNull;
public class OptionUnavailable extends BaseItem {
@Override
public ItemProvider getItemProvider() {
final ItemBuilder builder = new ItemBuilder(Material.RED_TERRACOTTA);
builder.setDisplayName("§cOption unavailable :(");
builder.addLoreLines("§7This option is disabled due to the",
"§7feature it controls being disabled.");
return builder;
}
@Override
public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent inventoryClickEvent) { }
}

View file

@ -0,0 +1,30 @@
package net.artelnatif.nicko.bukkit.gui.items.skin;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.BaseItem;
import net.artelnatif.nicko.bukkit.anvil.AnvilManager;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.jetbrains.annotations.NotNull;
public class ChangeName extends BaseItem {
@Override
public ItemProvider getItemProvider() {
final ItemBuilder builder = new ItemBuilder(Material.NAME_TAG);
builder.setDisplayName("§6Name §fchange");
builder.addLoreLines("§7Only change your name.");
return builder;
}
@Override
public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) {
if (clickType.isLeftClick() || clickType.isRightClick()) {
event.getView().close();
final AnvilManager manager = new AnvilManager(player);
manager.openNameAnvil();
}
}
}

View file

@ -0,0 +1,30 @@
package net.artelnatif.nicko.bukkit.gui.items.skin;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.BaseItem;
import net.artelnatif.nicko.bukkit.anvil.AnvilManager;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.jetbrains.annotations.NotNull;
public class ChangeNameAndSkin extends BaseItem {
@Override
public ItemProvider getItemProvider() {
final ItemBuilder builder = new ItemBuilder(Material.ENDER_PEARL);
builder.setDisplayName("§6Skin §fand §6name §fchange");
builder.addLoreLines("§7Change both your skin and name.");
return builder;
}
@Override
public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) {
if (clickType.isLeftClick() || clickType.isRightClick()) {
event.getView().close();
final AnvilManager manager = new AnvilManager(player);
manager.openNameThenSkinAnvil();
}
}
}

View file

@ -0,0 +1,30 @@
package net.artelnatif.nicko.bukkit.gui.items.skin;
import de.studiocode.invui.item.ItemProvider;
import de.studiocode.invui.item.builder.ItemBuilder;
import de.studiocode.invui.item.impl.BaseItem;
import net.artelnatif.nicko.bukkit.anvil.AnvilManager;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.jetbrains.annotations.NotNull;
public class ChangeSkin extends BaseItem {
@Override
public ItemProvider getItemProvider() {
final ItemBuilder builder = new ItemBuilder(Material.PAINTING);
builder.setDisplayName("§6Skin §fchange");
builder.addLoreLines("§7Only change your skin.");
return builder;
}
@Override
public void handleClick(@NotNull ClickType clickType, @NotNull Player player, @NotNull InventoryClickEvent event) {
if(clickType.isLeftClick() || clickType.isRightClick()) {
event.getView().close();
final AnvilManager manager = new AnvilManager(player);
manager.openSkinAnvil();
}
}
}

View file

@ -0,0 +1,62 @@
package net.artelnatif.nicko.bukkit.i18n;
import com.github.jsixface.YamlConfig;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.disguise.NickoProfile;
import org.bukkit.entity.Player;
import java.io.InputStream;
import java.text.MessageFormat;
import java.util.Optional;
public class I18N {
private final static MessageFormat formatter = new MessageFormat("");
private static Locale getLocale(Player player) {
final NickoBukkit instance = NickoBukkit.getInstance();
try {
final Optional<NickoProfile> profile = instance.getNicko().getDataStore().getData(player.getUniqueId());
return profile.isEmpty() ? Locale.FALLBACK_LOCALE : profile.get().getLocale();
} catch (IllegalArgumentException exception) {
instance.getLogger().severe("Invalid locale provided by " + player.getName() + ", defaulting to " + Locale.FALLBACK_LOCALE.getCode() + ".");
return Locale.FALLBACK_LOCALE;
}
}
public static String translate(Player player, I18NDict key, Object... arguments) {
final NickoBukkit instance = NickoBukkit.getInstance();
final String translation = findTranslation(player, key);
try {
formatter.applyPattern(translation);
return instance.getNicko().getConfig().prefix() + formatter.format(arguments);
} catch (Exception e) {
return instance.getNicko().getConfig().prefix() + key.key();
}
}
public static String translateWithoutPrefix(Player player, I18NDict key, Object... arguments) {
final String translation = findTranslation(player, key);
try {
formatter.applyPattern(translation);
return formatter.format(arguments);
} catch (Exception e) {
return key.key();
}
}
private static String findTranslation(Player player, I18NDict key) {
final NickoBukkit instance = NickoBukkit.getInstance();
final Locale locale = getLocale(player);
String translation;
if (locale == Locale.CUSTOM) {
translation = instance.getLocaleFileManager().get(key.key());
} else {
final InputStream resource = instance.getResource(locale.getCode() + ".yml");
final YamlConfig yamlConfig = YamlConfig.load(resource);
translation = yamlConfig.getString(key.key());
}
return translation;
}
}

View file

@ -0,0 +1,36 @@
package net.artelnatif.nicko.bukkit.i18n;
public record I18NDict(String key) {
public static class Event {
public static class Admin {
public static final I18NDict CACHE_CLEAN = new I18NDict("event.admin.cache_clear");
}
public static class Disguise {
public static final I18NDict SUCCESS = new I18NDict("event.disguise.success");
public static final I18NDict FAIL = new I18NDict("event.disguise.fail");
}
public static class Undisguise {
public static final I18NDict SUCCESS = new I18NDict("event.undisguise.success");
public static final I18NDict FAIL = new I18NDict("event.undisguise.fail");
public static final I18NDict NONE = new I18NDict("event.undisguise.none");
}
public static class PreviousSkin {
public static final I18NDict SUCCESS = new I18NDict("event.previous_skin_applied.success");
public static final I18NDict FAIL = new I18NDict("event.previous_skin_applied.fail");
}
}
public static class Error {
public static final I18NDict PLAYER_OFFLINE = new I18NDict("error.player_offline");
public static final I18NDict SKIN_FAIL_MOJANG = new I18NDict("error.couldnt_get_skin_from_mojang");
public static final I18NDict SKIN_FAIL_CACHE = new I18NDict("error.couldnt_get_skin_from_cache");
public static final I18NDict NAME_FAIL_MOJANG = new I18NDict("error.couldnt_get_name_from_mojang");
public static final I18NDict INVALID_USERNAME = new I18NDict("error.invalid_username");
public static final I18NDict UNEXPECTED_ERROR = new I18NDict("error.generic");
public static final I18NDict SQL_ERROR = new I18NDict("error.sql");
public static final I18NDict JSON_ERROR = new I18NDict("error.json");
}
}

View file

@ -0,0 +1,27 @@
package net.artelnatif.nicko.bukkit.i18n;
import java.io.Serializable;
public enum Locale implements Serializable {
ENGLISH("en", "English"),
FRENCH("fr", "Français"),
CUSTOM("custom", "Server Custom");
public static final Locale FALLBACK_LOCALE = ENGLISH;
private final String code;
private transient final String name;
Locale(String code, String name) {
this.code = code;
this.name = name;
}
public String getCode() {
return code;
}
public String getName() {
return name;
}
}

View file

@ -0,0 +1,41 @@
package net.artelnatif.nicko.bukkit.i18n;
import com.github.jsixface.YamlConfig;
import de.studiocode.invui.util.IOUtils;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import java.io.*;
public class LocaleFileManager {
private final File folder = new File(NickoBukkit.getInstance().getDataFolder() + "/lang/");
private final File file = new File(folder, "lang.yml");
public String get(String key) {
if (!file.exists()) return key;
try (BufferedInputStream inputStream = new BufferedInputStream(new FileInputStream(file))) {
final YamlConfig yamlConfig = YamlConfig.load(inputStream);
return yamlConfig.getString(key);
} catch (IOException e) {
return key;
}
}
public boolean dumpFromLocale(Locale locale) {
if (locale == Locale.CUSTOM) return true;
if (file.exists()) return true;
final InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(locale.getCode() + ".yml");
try {
if (folder.mkdirs()) {
if (file.createNewFile()) {
try (FileOutputStream outputStream = new FileOutputStream(file)) {
IOUtils.copy(inputStream, outputStream, 8192);
}
}
}
return true;
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
}

View file

@ -0,0 +1,70 @@
package net.artelnatif.nicko.bukkit.placeholder;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.disguise.NickoProfile;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Optional;
public class NickoExpansion extends PlaceholderExpansion {
private final NickoBukkit instance;
public NickoExpansion(NickoBukkit instance) {
this.instance = instance;
}
@Override
public @NotNull String getIdentifier() {
return "nicko";
}
@Override
public @NotNull String getAuthor() {
return "Aro";
}
@Override
public @NotNull String getVersion() {
return "1.0.0";
}
@Override
public boolean persist() {
return true;
}
@Override
public @Nullable String onPlaceholderRequest(Player player, @NotNull String params) {
if (player == null) return null;
String name, skin, locale;
boolean bungeecord;
name = skin = player.getName();
locale = "N/A";
bungeecord = true;
final Optional<NickoProfile> optionalProfile = instance.getNicko().getDataStore().getData(player.getUniqueId());
if (optionalProfile.isPresent()) {
final NickoProfile profile = optionalProfile.get();
if (!profile.isEmpty()) {
name = profile.getName();
skin = profile.getSkin();
}
locale = profile.getLocale().getName();
bungeecord = profile.isBungeecordTransfer();
}
return switch (params) {
case "name" -> name;
case "skin" -> skin;
case "locale" -> locale;
case "bungeecord" -> String.valueOf(bungeecord);
default -> null;
};
}
}

View file

@ -0,0 +1,19 @@
package net.artelnatif.nicko.bukkit.placeholder;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import org.bukkit.Bukkit;
public class PlaceHolderHook {
private final NickoBukkit instance;
public PlaceHolderHook(NickoBukkit instance) {
this.instance = instance;
}
public void hook() {
if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) {
instance.getLogger().info("Enabling PlaceHolderAPI support...");
new NickoExpansion(instance).register();
}
}
}

View file

@ -0,0 +1,35 @@
package net.artelnatif.nicko.bukkit.pluginchannel;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.bungee.NickoBungee;
import org.bukkit.entity.Player;
import org.bukkit.plugin.messaging.PluginMessageListener;
import java.util.ArrayList;
public class PluginMessageHandler implements PluginMessageListener {
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!channel.equals(NickoBungee.SERVER_DATA)) {
return;
}
final ByteArrayDataInput in = ByteStreams.newDataInput(message);
final int payloadSize = in.readInt();
if (payloadSize == 0 || payloadSize > 4) {
NickoBukkit.getInstance().getLogger().severe("Prevented error by skipping malformed payload of size " + payloadSize + "!");
NickoBukkit.getInstance().getLogger().severe("This should not have happened, open an issue at https://atnrch.xyz/git/aro/Nicko !");
return;
}
final ArrayList<String> decodedPayload = new ArrayList<>(payloadSize);
for (int i = 0; i < payloadSize; i++) {
decodedPayload.add(in.readUTF());
}
System.out.println("decodedPayload = " + decodedPayload);
}
}

View file

@ -0,0 +1,57 @@
package net.artelnatif.nicko.bungee;
import net.artelnatif.nicko.Nicko;
import net.artelnatif.nicko.bungee.event.UpdateMessageListener;
import net.md_5.bungee.api.plugin.Plugin;
public class NickoBungee extends Plugin {
public static final String NICKO_PLUGIN_CHANNEL_BASE = "nicko:";
public static final String PROXY_UPDATE = NICKO_PLUGIN_CHANNEL_BASE + "update";
public static final String PROXY_FETCH = NICKO_PLUGIN_CHANNEL_BASE + "fetch";
public static final String SERVER_DATA = NICKO_PLUGIN_CHANNEL_BASE + "data";
private final Nicko nicko = new Nicko();
private static NickoBungee plugin;
@Override
public void onEnable() {
plugin = this;
nicko.initBungeecord(this);
getLogger().info("Loading persistence...");
if (!nicko.getDataStore().getStorage().isError()) {
if (!nicko.getDataStore().getStorage().getProvider().init()) {
getLogger().severe("Failed to load persistence!");
getLogger().severe("Nicko can't enable BungeeCord support without SQL storage.");
getLogger().severe("The plugin will not continue.");
nicko.getDataStore().getStorage().setError(true);
nicko.setBungeecord(false);
onDisable();
return;
}
getLogger().info("Registering channel...");
getProxy().registerChannel(SERVER_DATA);
getLogger().info("Registering listener...");
getProxy().getPluginManager().registerListener(this, new UpdateMessageListener());
getLogger().info("Nicko (Bungee) has been enabled.");
}
}
@Override
public void onDisable() {
if (!nicko.getDataStore().getStorage().isError()) {
getLogger().info("Unregistering channels...");
getProxy().unregisterChannel(PROXY_UPDATE);
getLogger().info("Nicko (Bungee) has been disabled.");
}
}
public static NickoBungee getInstance() {
return plugin;
}
}

View file

@ -0,0 +1,39 @@
package net.artelnatif.nicko.bungee.event;
import net.artelnatif.nicko.bungee.NickoBungee;
import net.artelnatif.nicko.bungee.message.PluginMessageSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.config.ServerInfo;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.UUID;
public class FetchMessageListener implements Listener {
@EventHandler
public void onMessage(PluginMessageEvent event) {
if (!event.getTag().equals(NickoBungee.PROXY_FETCH)) { return; }
try (DataInputStream input = new DataInputStream(new ByteArrayInputStream(event.getData()))) {
final ProxyServer proxy = NickoBungee.getInstance().getProxy();
final String uuid = input.readUTF();
final ProxiedPlayer player = proxy.getPlayer(UUID.fromString(uuid));
final ServerInfo serverInfo = player.getServer().getInfo();
// TODO: 1/28/23 FETCH PROFILE
final ArrayList<String> payload = new ArrayList<>();
payload.add(player.getUniqueId().toString());
PluginMessageSender.send(serverInfo, NickoBungee.SERVER_DATA, payload);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,27 @@
package net.artelnatif.nicko.bungee.event;
import net.artelnatif.nicko.bungee.NickoBungee;
import net.artelnatif.nicko.bungee.message.MessageDecoder;
import net.artelnatif.nicko.disguise.NickoProfile;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.IOException;
public class UpdateMessageListener implements Listener {
@EventHandler
public void onMessage(PluginMessageEvent event) {
if (!event.getTag().equals(NickoBungee.PROXY_UPDATE)) { return; }
try (DataInputStream input = new DataInputStream(new ByteArrayInputStream(event.getData()))) {
final NickoProfile profile = MessageDecoder.decode(input);
// TODO: 1/28/23 STORE PROFILE
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -0,0 +1,17 @@
package net.artelnatif.nicko.bungee.message;
import net.artelnatif.nicko.disguise.NickoProfile;
import net.artelnatif.nicko.bukkit.i18n.Locale;
import java.io.DataInputStream;
import java.io.IOException;
public class MessageDecoder {
public static final int STRING_SIZE = 3;
public static NickoProfile decode(DataInputStream input) throws IOException {
final String[] stringValues = new String[3];
for (int i = 1; i < STRING_SIZE; i++) { stringValues[i] = input.readUTF(); }
return new NickoProfile(stringValues[0], stringValues[1], Locale.valueOf(stringValues[2]), true);
}
}

View file

@ -0,0 +1,20 @@
package net.artelnatif.nicko.bungee.message;
import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import net.md_5.bungee.api.config.ServerInfo;
import java.util.ArrayList;
public class PluginMessageSender {
public static void send(final ServerInfo info, final String channel, final ArrayList<String> payload) {
if (info == null) { return; }
final ByteArrayDataOutput output = ByteStreams.newDataOutput();
output.writeInt(payload.size());
for (String elt : payload) {
output.writeUTF(elt);
}
info.sendData(channel, output.toByteArray(), true);
}
}

View file

@ -0,0 +1,14 @@
package net.artelnatif.nicko.config;
public record Configuration(
String address,
String username,
String password,
String prefix,
Boolean local,
Boolean bungeecord,
Boolean customLocale) {
public Configuration() {
this("", "", "", "", false, false, false);
}
}

View file

@ -0,0 +1,51 @@
package net.artelnatif.nicko.config;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
import net.artelnatif.nicko.Nicko;
import java.io.*;
import java.nio.file.Files;
import java.nio.file.StandardCopyOption;
public class ConfigurationManager {
private final Nicko nicko;
private final ObjectMapper mapper = new ObjectMapper(new YAMLFactory());
private final File directory;
private final File file;
public ConfigurationManager(Nicko nicko) {
this.nicko = nicko;
this.directory = nicko.getDataFolder();
this.file = new File(directory, "config.yml");
}
public void save(Configuration configuration) throws IOException {
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
mapper.writeValue(writer, configuration);
writer.flush();
}
}
public void saveDefaultConfig() {
if (!file.exists()) {
try {
final InputStream input = getClass().getResourceAsStream("/config.yml");
if (input != null) {
nicko.getLogger().info("Saved default configuration as config.yml");
Files.createDirectories(file.getParentFile().toPath());
Files.createFile(file.toPath());
Files.copy(input, file.toPath(), StandardCopyOption.REPLACE_EXISTING);
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public Configuration load() throws IOException {
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
return mapper.readValue(reader, Configuration.class);
}
}
}

View file

@ -0,0 +1,34 @@
package net.artelnatif.nicko.disguise;
import net.artelnatif.nicko.bukkit.i18n.I18NDict;
public class ActionResult<R> {
private final I18NDict errorMessage;
private boolean error = false;
private R result;
public ActionResult(I18NDict errorMessage) {
this.error = true;
this.errorMessage = errorMessage;
}
public ActionResult() {
this.errorMessage = null;
}
public void setResult(R result) {
this.result = result;
}
public R getResult() {
return result;
}
public boolean isError() {
return error;
}
public I18NDict getErrorMessage() {
return errorMessage;
}
}

View file

@ -0,0 +1,72 @@
package net.artelnatif.nicko.disguise;
import net.artelnatif.nicko.bukkit.i18n.Locale;
public class NickoProfile implements Cloneable {
public static final NickoProfile EMPTY_PROFILE = new NickoProfile(null, null, Locale.ENGLISH, true);
private String name;
private String skin;
private Locale locale;
private boolean bungeecordTransfer;
public NickoProfile(String name, String skin, Locale locale, boolean bungeecordTransfer) {
this.name = name;
this.skin = skin;
this.locale = locale;
this.bungeecordTransfer = bungeecordTransfer;
}
public boolean isEmpty() {
return name == null && skin == null;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSkin() {
return skin;
}
public void setSkin(String skin) {
this.skin = skin;
}
public Locale getLocale() { return locale; }
public void setLocale(Locale locale) { this.locale = locale; }
public boolean isBungeecordTransfer() {
return bungeecordTransfer;
}
public void setBungeecordTransfer(boolean bungeecordTransfer) {
this.bungeecordTransfer = bungeecordTransfer;
}
@Override
public String toString() {
return "NickoProfile{" +
"name='" + name + '\'' +
", skin='" + skin + '\'' +
", locale=" + locale +
", bungeecordTransfer=" + bungeecordTransfer +
'}';
}
@Override
public NickoProfile clone() {
Object o;
try {
o = super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException(e);
}
return (NickoProfile) o;
}
}

View file

@ -0,0 +1,44 @@
package net.artelnatif.nicko.impl;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.disguise.NickoProfile;
import net.artelnatif.nicko.disguise.ActionResult;
import net.artelnatif.nicko.bukkit.i18n.I18NDict;
import net.artelnatif.nicko.mojang.MojangAPI;
import net.artelnatif.nicko.mojang.MojangSkin;
import org.bukkit.entity.Player;
import java.io.IOException;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
public interface Internals {
void updateSelf(Player player);
void updateOthers(Player player);
ActionResult<Void> updateProfile(Player player, NickoProfile profile, boolean skinChange, boolean reset);
default ActionResult<MojangSkin> fetchSkinTextures(NickoProfile profile, boolean reset) {
Optional<MojangSkin> skin;
try {
final MojangAPI mojang = NickoBukkit.getInstance().getNicko().getMojangAPI();
final Optional<String> uuid = mojang.getUUID(profile.getSkin());
if (uuid.isPresent()) {
skin = (reset ? mojang.getSkinWithoutCaching(uuid.get()) : mojang.getSkin(uuid.get()));
if (skin.isEmpty()) {
return new ActionResult<>(I18NDict.Error.SKIN_FAIL_MOJANG);
}
final ActionResult<MojangSkin> actionResult = new ActionResult<>();
actionResult.setResult(skin.get());
return actionResult;
}
return new ActionResult<>(I18NDict.Error.NAME_FAIL_MOJANG);
} catch (ExecutionException e) {
return new ActionResult<>(I18NDict.Error.SKIN_FAIL_CACHE);
} catch (IOException e) {
return new ActionResult<>(I18NDict.Error.NAME_FAIL_MOJANG);
}
}
}

View file

@ -0,0 +1,25 @@
package net.artelnatif.nicko.impl;
import org.bukkit.Bukkit;
import java.lang.reflect.InvocationTargetException;
public class InternalsProvider {
private static Internals internals;
static {
try {
final String packageName = Internals.class.getPackage().getName();
final String bukkitVersion = Bukkit.getServer().getClass().getPackage().getName().split("\\.")[3];
final String fullClassName = packageName + "." + bukkitVersion;
internals = (Internals) Class.forName(fullClassName).getConstructors()[0].newInstance();
} catch (InvocationTargetException | ClassNotFoundException | InstantiationException | IllegalAccessException |
ClassCastException exception) {
internals = null;
}
}
public static Internals getInternals() {
return internals;
}
}

View file

@ -0,0 +1,124 @@
package net.artelnatif.nicko.mojang;
import com.google.common.cache.CacheBuilder;
import com.google.common.cache.CacheLoader;
import com.google.common.cache.LoadingCache;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonParseException;
import com.google.gson.JsonParser;
import net.artelnatif.nicko.Nicko;
import javax.annotation.Nonnull;
import javax.net.ssl.HttpsURLConnection;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class MojangAPI {
public static final String URL_NAME = "https://api.mojang.com/users/profiles/minecraft/{name}";
public static final String URL_SKIN = "https://sessionserver.mojang.com/session/minecraft/profile/{uuid}?unsigned=false";
private final CacheLoader<String, Optional<MojangSkin>> loader = new CacheLoader<>() {
@Nonnull
public Optional<MojangSkin> load(@Nonnull String uuid) throws Exception {
return getSkinFromMojang(uuid);
}
};
private final LoadingCache<String, Optional<MojangSkin>> cache = CacheBuilder
.newBuilder()
.recordStats()
.expireAfterWrite(24, TimeUnit.HOURS)
.build(loader);
private final Nicko nicko;
public MojangAPI(Nicko nicko) {
this.nicko = nicko;
}
public Optional<MojangSkin> getSkin(String uuid) throws IOException, ExecutionException {
return cache.get(uuid);
}
public Optional<MojangSkin> getSkinWithoutCaching(String uuid) throws IOException {
return getSkinFromMojang(uuid);
}
public Optional<String> getUUID(String name) throws IOException {
final String parametrizedUrl = URL_NAME.replace("{name}", name);
final JsonObject object = getRequestToUrl(parametrizedUrl);
if (hasNoError(object)) {
return Optional.of(object.get("id").getAsString());
}
return Optional.empty();
}
private Optional<MojangSkin> getSkinFromMojang(String uuid) throws IOException {
final String parametrizedUrl = URL_SKIN.replace("{uuid}", uuid);
final JsonObject object = getRequestToUrl(parametrizedUrl);
if (hasNoError(object)) {
final MojangSkin skin = MojangSkin.buildFromJson(object);
return Optional.of(skin);
}
return Optional.empty();
}
private JsonObject getRequestToUrl(String parametrizedUrl) throws IOException {
final URL url = new URL(parametrizedUrl);
final HttpsURLConnection con = (HttpsURLConnection) url.openConnection();
con.setDoInput(true);
con.setRequestMethod("GET");
switch (con.getResponseCode()) {
case 400 -> {
nicko.getLogger().warning("Failed to parse request: Invalid Name");
return getErrorObject();
}
case 429 -> {
nicko.getLogger().warning("Failed to parse request: The connection is throttled.");
return getErrorObject();
}
case 200 -> {
final BufferedReader input = new BufferedReader(new InputStreamReader(con.getInputStream()));
final StringBuilder builder = new StringBuilder();
String line;
while ((line = input.readLine()) != null) {
builder.append(line);
}
try {
final JsonElement jsonElt = JsonParser.parseString(builder.toString());
return jsonElt.getAsJsonObject();
} catch (JsonParseException | IllegalStateException exception) {
nicko.getLogger().warning("Failed to parse request (" + parametrizedUrl + ")!");
return getErrorObject();
}
}
default -> {
nicko.getLogger().warning("Unhandled response code from Mojang: " + con.getResponseCode());
return getErrorObject();
}
}
}
private JsonObject getErrorObject() {
final JsonObject errorObject = new JsonObject();
errorObject.addProperty("error", "An error occurred.");
return errorObject;
}
private boolean hasNoError(JsonObject object) {
return object.get("error") == null;
}
public LoadingCache<String, Optional<MojangSkin>> getCache() {
return cache;
}
}

View file

@ -0,0 +1,13 @@
package net.artelnatif.nicko.mojang;
import com.google.gson.JsonObject;
public record MojangSkin(String name, String value, String signature) {
public static MojangSkin buildFromJson(JsonObject object) {
final String name = object.get("name").getAsString();
final JsonObject properties = object.get("properties").getAsJsonArray().get(0).getAsJsonObject();
final String value = properties.get("value").getAsString();
final String signature = properties.get("signature").getAsString();
return new MojangSkin(name, value, signature);
}
}

View file

@ -0,0 +1,26 @@
package net.artelnatif.nicko.mojang;
import java.util.UUID;
import java.util.regex.Pattern;
public class MojangUtils {
public static boolean isUsernameInvalid(String username) {
return !Pattern.matches("^\\w{3,16}$", username);
}
public static UUID fromTrimmed(String trimmedUUID) throws IllegalArgumentException {
if (trimmedUUID == null) throw new IllegalArgumentException();
StringBuilder builder = new StringBuilder(trimmedUUID.trim());
/* Backwards adding to avoid index adjustments */
try {
builder.insert(20, "-");
builder.insert(16, "-");
builder.insert(12, "-");
builder.insert(8, "-");
} catch (StringIndexOutOfBoundsException e) {
throw new IllegalArgumentException();
}
return UUID.fromString(builder.toString());
}
}

View file

@ -0,0 +1,90 @@
package net.artelnatif.nicko.storage;
import net.artelnatif.nicko.Nicko;
import net.artelnatif.nicko.disguise.NickoProfile;
import net.artelnatif.nicko.mojang.MojangUtils;
import net.artelnatif.nicko.storage.json.JSONStorage;
import net.artelnatif.nicko.storage.sql.SQLStorage;
import org.bukkit.entity.Player;
import java.io.IOException;
import java.util.HashMap;
import java.util.Optional;
import java.util.UUID;
public class PlayerDataStore {
private final Storage storage;
private final Nicko nicko;
private final HashMap<UUID, NickoProfile> profiles = new HashMap<>();
private final HashMap<UUID, String> names = new HashMap<>();
public PlayerDataStore(Nicko nicko) {
this.nicko = nicko;
this.storage = nicko.getConfig().local() && !nicko.isBungeecord() ? new JSONStorage(nicko) : new SQLStorage(nicko);
}
public void storeName(Player player) {
if (!isNameStored(player)) {
names.put(player.getUniqueId(), player.getName());
}
}
public String getStoredName(Player player) {
return names.get(player.getUniqueId());
}
private boolean isNameStored(Player player) {
return names.containsKey(player.getUniqueId());
}
public void removeAllNames() {
names.clear();
}
public Optional<NickoProfile> getData(UUID uuid) {
if (storage.isError()) {
return Optional.empty();
}
if (profiles.containsKey(uuid)) {
return Optional.of(profiles.get(uuid));
} else if (storage.isStored(uuid)) {
final Optional<NickoProfile> retrievedProfile = storage.retrieve(uuid);
retrievedProfile.ifPresent(profile -> profiles.put(uuid, profile));
return retrievedProfile;
} else {
final NickoProfile newProfile = NickoProfile.EMPTY_PROFILE.clone();
profiles.put(uuid, newProfile);
return Optional.of(newProfile);
}
}
public Optional<NickoProfile> getOfflineData(String name) {
if (storage.isError()) {
return Optional.empty();
}
try {
final Optional<String> uuidTrimmed = nicko.getMojangAPI().getUUID(name);
if (uuidTrimmed.isPresent()) {
final UUID uuid = MojangUtils.fromTrimmed(uuidTrimmed.get());
return getData(uuid);
}
return Optional.empty();
} catch (IOException e) {
return Optional.empty();
}
}
public void saveData(Player player) {
if (storage.isError()) { return; }
if (!profiles.containsKey(player.getUniqueId())) { return; }
storage.store(player.getUniqueId(), profiles.get(player.getUniqueId()));
profiles.remove(player.getUniqueId());
}
public Storage getStorage() {
return storage;
}
}

View file

@ -0,0 +1,27 @@
package net.artelnatif.nicko.storage;
import net.artelnatif.nicko.disguise.ActionResult;
import net.artelnatif.nicko.disguise.NickoProfile;
import java.util.Optional;
import java.util.UUID;
public abstract class Storage {
private boolean error = false;
public abstract StorageProvider getProvider();
public abstract ActionResult<Void> store(UUID uuid, NickoProfile profile);
public abstract boolean isStored(UUID uuid);
public abstract Optional<NickoProfile> retrieve(UUID uuid);
public boolean isError() {
return error;
}
public void setError(boolean error) {
this.error = error;
}
}

View file

@ -0,0 +1,7 @@
package net.artelnatif.nicko.storage;
public interface StorageProvider {
boolean init();
boolean close();
}

View file

@ -0,0 +1,85 @@
package net.artelnatif.nicko.storage.json;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import net.artelnatif.nicko.Nicko;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.disguise.ActionResult;
import net.artelnatif.nicko.disguise.NickoProfile;
import net.artelnatif.nicko.bukkit.i18n.I18NDict;
import net.artelnatif.nicko.storage.Storage;
import net.artelnatif.nicko.storage.StorageProvider;
import java.io.*;
import java.util.Optional;
import java.util.UUID;
public class JSONStorage extends Storage {
private final Nicko nicko;
private final Gson gson = new GsonBuilder().serializeNulls().setPrettyPrinting().create();
private final File directory = new File(NickoBukkit.getInstance().getDataFolder() + "/players/");
private JSONStorageProvider provider;
public JSONStorage(Nicko nicko) { this.nicko = nicko; }
@Override
public StorageProvider getProvider() {
if (provider == null) {
provider = new JSONStorageProvider(directory);
}
return provider;
}
@Override
public ActionResult<Void> store(UUID uuid, NickoProfile profile) {
final String profileToJson = gson.toJson(profile);
final File file = new File(directory, uuid.toString() + ".json");
try {
if (checkFileExists(file)) {
try (FileWriter fileWriter = new FileWriter(file)) {
try (BufferedWriter writer = new BufferedWriter(fileWriter)) {
writer.write(profileToJson);
}
} catch (IOException e) {
nicko.getLogger().warning("Could not write to file.");
return new ActionResult<>(I18NDict.Error.JSON_ERROR);
}
}
} catch (IOException e) {
nicko.getLogger().warning("Could not create file.");
return new ActionResult<>(I18NDict.Error.JSON_ERROR);
}
return new ActionResult<>();
}
@Override
public boolean isStored(UUID uuid) {
final File directory = new File(NickoBukkit.getInstance().getDataFolder() + "/players/");
final File file = new File(directory, uuid.toString() + ".json");
return file.exists();
}
@Override
public Optional<NickoProfile> retrieve(UUID uuid) {
final File directory = new File(NickoBukkit.getInstance().getDataFolder() + "/players/");
final File file = new File(directory, uuid.toString() + ".json");
try (FileReader fileReader = new FileReader(file)) {
try (BufferedReader reader = new BufferedReader(fileReader)) {
final NickoProfile value = gson.fromJson(reader, NickoProfile.class);
return Optional.of(value);
}
} catch (IOException e) {
return Optional.empty();
}
}
private boolean checkFileExists(File file) throws IOException {
if (!file.exists()) {
return file.createNewFile();
}
return true;
}
}

View file

@ -0,0 +1,22 @@
package net.artelnatif.nicko.storage.json;
import net.artelnatif.nicko.storage.StorageProvider;
import java.io.File;
public class JSONStorageProvider implements StorageProvider {
private final File directory;
public JSONStorageProvider(File directory) {
this.directory = directory;
}
@Override
public boolean init()
{
return directory.exists() || directory.mkdirs();
}
@Override
public boolean close() { return true; }
}

View file

@ -0,0 +1,83 @@
package net.artelnatif.nicko.storage.sql;
import net.artelnatif.nicko.Nicko;
import net.artelnatif.nicko.disguise.ActionResult;
import net.artelnatif.nicko.disguise.NickoProfile;
import net.artelnatif.nicko.bukkit.i18n.I18NDict;
import net.artelnatif.nicko.storage.Storage;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Optional;
import java.util.UUID;
public class SQLStorage extends Storage {
private final Nicko nicko;
private SQLStorageProvider provider;
public SQLStorage(Nicko nicko) {
this.nicko = nicko;
}
@Override
public SQLStorageProvider getProvider() {
if (provider == null) {
provider = new SQLStorageProvider(nicko);
}
return provider;
}
@Override
public ActionResult<Void> store(UUID uuid, NickoProfile profile) {
final Connection connection = getProvider().getConnection();
if (connection == null) return new ActionResult<>(I18NDict.Error.SQL_ERROR);
try {
final String sql = """
INSERT IGNORE INTO nicko.DATA
(`uuid`, `name`, `skin`, `bungeecord`)
VALUES
(?, ?, ?, ?)
""";
final PreparedStatement statement = connection.prepareStatement(sql);
statement.setObject(1, uuidToBin(uuid));
statement.setString(2, profile.getName());
statement.setString(3, profile.getSkin());
statement.setBoolean(4, profile.isBungeecordTransfer());
statement.executeUpdate();
return new ActionResult<>();
} catch (SQLException e) {
nicko.getLogger().warning("Couldn't send SQL Request: " + e.getMessage());
return new ActionResult<>(I18NDict.Error.SQL_ERROR);
}
}
@Override
public boolean isStored(UUID uuid) {
return false;
}
@Override
public Optional<NickoProfile> retrieve(UUID uuid) {
return Optional.empty();
}
private byte[] uuidToBin(UUID uuid) {
final byte[] uuidBytes = new byte[16];
final ByteBuffer buffer = ByteBuffer.wrap(uuidBytes)
.order(ByteOrder.BIG_ENDIAN)
.putLong(uuid.getMostSignificantBits())
.putLong(uuid.getLeastSignificantBits());
return buffer.array();
}
private UUID binToUUID(byte[] array) {
final ByteBuffer buffer = ByteBuffer.wrap(array);
return new UUID(buffer.getLong(), buffer.getLong());
}
}

View file

@ -0,0 +1,102 @@
package net.artelnatif.nicko.storage.sql;
import net.artelnatif.nicko.Nicko;
import net.artelnatif.nicko.config.Configuration;
import net.artelnatif.nicko.storage.StorageProvider;
import org.mariadb.jdbc.MariaDbDataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
public class SQLStorageProvider implements StorageProvider {
private final Nicko nicko;
private Connection connection;
private MariaDbDataSource dataSource;
private final String schemaName = "nicko";
public SQLStorageProvider(Nicko nicko) {
this.nicko = nicko;
}
@Override
public boolean init() {
try {
final Configuration config = nicko.getConfig();
dataSource = new MariaDbDataSource();
dataSource.setUrl("jdbc:mariadb://" + config.address());
dataSource.setUser(config.username());
dataSource.setPassword(config.password());
connection = dataSource.getConnection();
final boolean initialized = connection != null && !connection.isClosed();
if (!initialized) return false;
nicko.getLogger().info("Creating SQL database...");
createDatabase();
nicko.getLogger().info("Creating SQL table...");
createTable();
return true;
} catch (SQLException e) {
nicko.getLogger().severe("Couldn't establish a connection to the MySQL database: " + e.getMessage());
return false;
}
}
@Override
public boolean close() {
if (connection == null) { return true; }
try {
connection.close();
return connection.isClosed();
} catch (SQLException e) {
return false;
}
}
private void createTable() {
final Connection connection = getConnection();
final String query = """
CREATE TABLE IF NOT EXISTS %s.DATA (
uuid binary(16) NOT NULL,
name varchar(16) NOT NULL,
skin varchar(16) NOT NULL,
bungeecord boolean NOT NULL,
PRIMARY KEY (UUID)
)
""".formatted(schemaName);
try {
final PreparedStatement statement = connection.prepareStatement(query);
statement.executeUpdate();
statement.close();
} catch (SQLException e) {
// TODO: 12/10/22 Handle error
throw new RuntimeException(e);
}
}
private void createDatabase() {
final Connection connection = getConnection();
final String query = """
CREATE DATABASE IF NOT EXISTS %s
""".formatted(schemaName);
try {
final PreparedStatement statement = connection.prepareStatement(query);
statement.executeUpdate();
statement.close();
} catch (SQLException e) {
// TODO: 12/10/22 Handle error
throw new RuntimeException(e);
}
}
public Connection getConnection() {
return connection;
}
}

View file

@ -0,0 +1,4 @@
name: ${project.parent.name}
main: net.artelnatif.nicko.bungee.NickoBungee
version: ${project.version}
author: Aro

View file

@ -0,0 +1,48 @@
# Nicko ${project.version} - Config:
# This is the Nicko configuration file.
# This file contains all the configuration for
# the Bukkit AND Bungeecord versions of Nicko.
# Nicko is smart and will only read its configuration
# section based on what it's enabled as.
# For example, if Nicko is enabled as a Bukkit plugin,
# it will only read the "Bukkit" section of the config file.
# As a result, modifying any other section is useless.
###########
# GENERAL #
###########
# SQL database's address
# Accepted values: valid IP address (e.g. localhost, 127.0.0.1)
address: "localhost"
# SQL database's username.
# Accepted values: any string
username: "username"
# SQL database's password.
# Accepted values: any string
password: "password"
#################
# BUKKIT/SPIGOT #
#################
# Nicko's messages prefix.
# Accepted values: any string
prefix: "§8[§6Nicko§8] "
# Indicates wherever the data will be stored
# locally through a .json file or a (My)SQL database.
# Accepted values: false (Disabled), true (Enabled)
local: true
# Enables Bungeecord support, switching
# servers will transfer player's disguise.
# Accepted values: false (Disabled), true (Enabled)
bungeecord: false
# Nicko will copy the English locale as "lang.yml"
# and will use the translations in that file when "Server Custom"
# is selected as the player's locale.
# Accepted values: false (Disabled), true (Enabled)
customLocale: false

View file

@ -0,0 +1,22 @@
error:
couldnt_get_name_from_mojang: "Failed to get username from Mojang"
couldnt_get_skin_from_cache: "Failed to get skin from cache"
couldnt_get_skin_from_mojang: "Failed to get skin from Mojang"
generic: "Unknown error"
invalid_username: "§cThe specified username is not a valid Minecraft username."
player_offline: "§c{0} §fis offline, please try again."
sql: "SQL Error"
json: "JSON Error"
event:
admin:
cache_clear: "§aSkin cache cleaned."
disguise:
fail: "§cUnable to apply your disguise. §7§o({0})"
success: "§aDisguise applied!"
previous_skin_applied:
fail: "§cFailed to apply your previous disguise back. §7§o({0})"
success: "§aYour previous active disguise has been applied back."
undisguise:
fail: "§cUnable to remove your disguise. It will be set back to default on your next login. Sorry!"
none: "§cYou do not have an active disguise."
success: "§aDisguise removed."

View file

@ -0,0 +1,22 @@
error:
couldnt_get_name_from_mojang: "Impossible de récupérer le nom d''utilisateur depuis Mojang"
couldnt_get_skin_from_cache: "Impossible de récupérer le skin depuis le cache"
couldnt_get_skin_from_mojang: "Impossible de récupérer le skin depuis Mojang"
generic: "Erreur inconnue"
invalid_username: "§cLe pseudo spécifié n''est pas un pseudo Minecraft valide."
player_offline: "§c{0} §fest hors-ligne, veuillez réessayer."
sql: "Erreur SQL"
json: "Erreur JSON"
event:
admin:
cache_clear: "§aCache des skins nettoyé."
disguise:
fail: "§cImpossible d''appliquer votre déguisement. §7§o({0})"
success: "§aDéguisement appliqué !"
previous_skin_applied:
fail: "§cImpossible d''appliquer votre déguisement précédent. §7§o({0})"
success: "§aVotre précédent déguisement a été réappliqué."
undisguise:
fail: "§cImpossible de retier votre déguisement. Il sera remis par défaut à votre prochaine reconnexion. Désolé !"
none: "§cVous n''avez pas de déguisement."
success: "§aDéguisement retiré."

View file

@ -0,0 +1,17 @@
name: Nicko
main: net.artelnatif.nicko.bukkit.NickoBukkit
version: 1.0-SNAPSHOT
author: Aro
api-version: 1.19
softdepend: [ PlaceholderAPI ]
commands:
nicko:
description: "Opens Nicko's GUI."
permission: nicko.admin
permissions:
nicko.*:
default: op
children:
- nicko.use
nicko.use:
default: op

View file

@ -0,0 +1,36 @@
package net.artelnatif.nicko.test;
import be.seeseemelk.mockbukkit.MockBukkit;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.config.Configuration;
import org.junit.jupiter.api.*;
public class NickoPluginTest {
private static NickoBukkit plugin;
@BeforeAll
public static void setup() {
final Configuration config = new Configuration(
"",
"",
"",
"",
true,
false,
false);
MockBukkit.mock();
plugin = MockBukkit.load(NickoBukkit.class, config);
}
@Test
@DisplayName("Plugin Initialization")
public void testPluginInitialization() {
Assertions.assertNotNull(plugin.getNicko().getDataStore().getStorage().getProvider());
Assertions.assertNotNull(plugin.getNicko().getConfig());
}
@AfterAll
public static void shutdown() {
MockBukkit.unmock();
}
}

View file

@ -0,0 +1,50 @@
package net.artelnatif.nicko.test.storage;
import be.seeseemelk.mockbukkit.MockBukkit;
import be.seeseemelk.mockbukkit.ServerMock;
import be.seeseemelk.mockbukkit.entity.PlayerMock;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.config.Configuration;
import net.artelnatif.nicko.disguise.ActionResult;
import net.artelnatif.nicko.disguise.NickoProfile;
import net.artelnatif.nicko.bukkit.i18n.Locale;
import org.junit.jupiter.api.*;
public class BrokenSQLTest {
private static ServerMock server;
private static NickoBukkit plugin;
@BeforeAll
public static void setup() {
final Configuration config = new Configuration(
"127.0.0.1",
"root",
"INVALID_PASSWORD",
"",
false,
false,
false);
server = MockBukkit.mock();
plugin = MockBukkit.load(NickoBukkit.class, config);
}
@Test
@DisplayName("Fail to create Tables")
public void createSQLTables() {
Assertions.assertTrue(plugin.getNicko().getDataStore().getStorage().isError());
}
@Test
@DisplayName("Fail to Store Player Via SQL")
public void storePlayer() {
final PlayerMock playerMock = server.addPlayer();
final NickoProfile profile = new NickoProfile("Notch", "Notch", Locale.ENGLISH, true);
final ActionResult<Void> storeAction = plugin.getNicko().getDataStore().getStorage().store(playerMock.getUniqueId(), profile);
Assertions.assertTrue(storeAction.isError());
}
@AfterAll
public static void shutdown() {
MockBukkit.unmock();
}
}

View file

@ -0,0 +1,50 @@
package net.artelnatif.nicko.test.storage;
import be.seeseemelk.mockbukkit.MockBukkit;
import be.seeseemelk.mockbukkit.ServerMock;
import be.seeseemelk.mockbukkit.entity.PlayerMock;
import net.artelnatif.nicko.bukkit.NickoBukkit;
import net.artelnatif.nicko.config.Configuration;
import net.artelnatif.nicko.disguise.ActionResult;
import net.artelnatif.nicko.disguise.NickoProfile;
import net.artelnatif.nicko.bukkit.i18n.Locale;
import org.junit.jupiter.api.*;
public class SQLStorageTest {
private static ServerMock server;
private static NickoBukkit plugin;
@BeforeAll
public static void setup() {
final Configuration config = new Configuration(
"127.0.0.1",
"root",
"12345",
"",
false,
false,
false);
server = MockBukkit.mock();
plugin = MockBukkit.load(NickoBukkit.class, config);
}
@Test
@DisplayName("Create SQL Tables")
public void createSQLTables() {
Assertions.assertFalse(plugin.getNicko().getDataStore().getStorage().isError());
}
@Test
@DisplayName("Store Player Via SQL")
public void storePlayer() {
final PlayerMock playerMock = server.addPlayer();
final NickoProfile profile = new NickoProfile("Notch", "Notch", Locale.ENGLISH, true);
final ActionResult<Void> storeAction = plugin.getNicko().getDataStore().getStorage().store(playerMock.getUniqueId(), profile);
Assertions.assertFalse(storeAction.isError());
}
@AfterAll
public static void shutdown() {
MockBukkit.unmock();
}
}