Merge branch 'Testing' into 'master'

Full Core Library

See merge request Songoda/songodaupdater!1
This commit is contained in:
Jacob Scott 2019-09-02 12:00:40 +00:00
commit af2e1b8cab
105 changed files with 15944 additions and 1018 deletions

21
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,21 @@
stages:
- build
variables:
name: "SongodaCore"
path: "/builds/$CI_PROJECT_PATH"
version: "2.0"
build:
stage: build
image: maven:3.5.3-jdk-8
script:
- find $path/ -type f -name "*.xml" -print0 | xargs -0 sed -i -e s/maven-version-number/$version/g
- find $path/ -type f -name "*.yml" -print0 | xargs -0 sed -i -e s/maven-version-number/$version/g
- mvn clean package
- find $path/ -depth -path '*original*' -delete
- mv $path/target/*.jar $path/
artifacts:
name: $name-$version
paths:
- "$path/*.jar"

44
SongodaCore.iml Normal file
View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="UTF-8"?>
<module org.jetbrains.idea.maven.project.MavenProjectsManager.isMavenModule="true" type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" LANGUAGE_LEVEL="JDK_1_8">
<output url="file://$MODULE_DIR$/target/classes" />
<output-test url="file://$MODULE_DIR$/target/test-classes" />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src/main/java" isTestSource="false" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="library" name="Maven: org.spigotmc:spigot:1.14.4" level="project" />
<orderEntry type="library" name="Maven: com.gmail.filoghost.holographicdisplays:holographicdisplays-api:2.3.2" level="project" />
<orderEntry type="library" name="Maven: com.sainttx.holograms:Holograms:2.9.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: net.tnemc:Reserve:0.1.3.0" level="project" />
<orderEntry type="library" name="Maven: org.black_ixx:playerpoints:2.1.4" level="project" />
<orderEntry type="library" name="Maven: net.milkbowl:vault:1.7.1" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-api:1.7.25" level="project" />
<orderEntry type="library" name="Maven: org.slf4j:slf4j-nop:1.7.25" level="project" />
<orderEntry type="library" name="Maven: com.zaxxer:HikariCP:3.2.0" level="project" />
<orderEntry type="library" name="Maven: org.xerial:sqlite-jdbc:3.23.1" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.sk89q.worldguard:worldguard-bukkit:7.0.1-SNAPSHOT" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.bukkit:bukkit:1.14.4-R0.1-SNAPSHOT" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: commons-lang:commons-lang:2.6" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.guava:guava:21.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.gson:gson:2.8.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.yaml:snakeyaml:1.23" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.sk89q.worldedit:worldedit-bukkit:7.0.1-SNAPSHOT" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.sk89q.worldguard:worldguard-core:7.0.1-SNAPSHOT" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.sk89q.worldguard.worldguard-libs:core:7.0.1-SNAPSHOT" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.sk89q.worldedit:worldedit-core:7.0.1-SNAPSHOT" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.sk89q.worldedit.worldedit-libs:core:7.0.1-SNAPSHOT" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: de.schlichtherle:truezip:6.8.3" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.mozilla:rhino:1.7.11" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.google.code.findbugs:jsr305:1.3.9" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.flywaydb:flyway-core:3.0" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: io.papermc:paperlib:1.0.2" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: com.sk89q:commandbook:2.3" level="project" />
<orderEntry type="library" scope="PROVIDED" name="Maven: org.bstats:bstats-bukkit:1.5" level="project" />
<orderEntry type="library" name="Maven: com.songoda:UltimateStacker:1.2.8" level="project" />
<orderEntry type="library" name="Maven: com.bgsoftware:WildStacker:2-9-0" level="project" />
<orderEntry type="library" name="Maven: uk.antiperson:stackmob:4-0-2" level="project" />
</component>
</module>

120
pom.xml
View File

@ -1,11 +1,11 @@
<project xmlns="http://maven.apache.org/POM/4.0.0"> <project xmlns="http://maven.apache.org/POM/4.0.0">
<groupId>com.songoda</groupId> <groupId>com.songoda</groupId>
<artifactId>SongodaUpdater</artifactId> <artifactId>SongodaCore</artifactId>
<modelVersion>4.0.0</modelVersion> <modelVersion>4.0.0</modelVersion>
<version>1</version> <version>maven-version-number</version>
<build> <build>
<defaultGoal>clean install</defaultGoal> <defaultGoal>clean install</defaultGoal>
<finalName>SongodaUpdater-${project.version}</finalName> <finalName>SongodaCore-${project.version}</finalName>
<plugins> <plugins>
<plugin> <plugin>
<groupId>org.apache.maven.plugins</groupId> <groupId>org.apache.maven.plugins</groupId>
@ -21,14 +21,124 @@
<repositories> <repositories>
<repository> <repository>
<id>private</id> <id>private</id>
<url>http://repo.songoda.com/artifactory/private/</url> <url>https://repo.songoda.com/artifactory/private/</url>
</repository> </repository>
</repositories> </repositories>
<dependencies> <dependencies>
<dependency> <dependency>
<groupId>com.destroystokyo.papermc</groupId>
<artifactId>paper</artifactId>
<version>1.14.4</version>
<scope>provided</scope>
</dependency>
<!--dependency>
<groupId>org.spigotmc</groupId> <groupId>org.spigotmc</groupId>
<artifactId>spigot</artifactId> <artifactId>spigot</artifactId>
<version>1.14</version> <version>1.14.4</version>
</dependency-->
<dependency>
<groupId>com.gmail.filoghost.holographicdisplays</groupId>
<artifactId>holographicdisplays-api</artifactId>
<version>2.3.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.sainttx.holograms</groupId>
<artifactId>Holograms</artifactId>
<version>2.9.1</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.tnemc</groupId>
<artifactId>Reserve</artifactId>
<version>0.1.3.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.black_ixx</groupId>
<artifactId>PlayerPoints</artifactId>
<version>2.1.4</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>net.milkbowl</groupId>
<artifactId>VaultAPI</artifactId>
<version>1.7.2</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>us.myles.viaversion-bukkit</groupId>
<artifactId>ViaVersion</artifactId>
<version>2.1.3</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>protocolsupport</groupId>
<artifactId>ProtocolSupport</artifactId>
<version>4.29</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.googlecode.json-simple</groupId>
<artifactId>json-simple</artifactId>
<version>1.1</version>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>13.0</version>
</dependency>
<dependency>
<groupId>org.yaml</groupId>
<artifactId>snakeyaml</artifactId>
<version>1.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-nop</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>3.2.0</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.23.1</version>
</dependency>
<dependency>
<groupId>com.sk89q.worldguard</groupId>
<artifactId>worldguard-bukkit</artifactId>
<version>7.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.sk89q.worldedit</groupId>
<artifactId>worldedit-bukkit</artifactId>
<version>7.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.songoda</groupId>
<artifactId>UltimateStacker</artifactId>
<version>1.9.6</version>
</dependency>
<dependency>
<groupId>com.bgsoftware</groupId>
<artifactId>WildStacker</artifactId>
<version>2-9-0</version>
</dependency>
<dependency>
<groupId>uk.antiperson</groupId>
<artifactId>stackmob</artifactId>
<version>4-0-2</version>
</dependency> </dependency>
</dependencies> </dependencies>
</project> </project>

View File

@ -0,0 +1,393 @@
package com.songoda.core;
import com.songoda.core.core.PluginInfo;
import com.songoda.core.core.LocaleModule;
import com.songoda.core.core.PluginInfoModule;
import com.songoda.core.core.SongodaCoreCommand;
import com.songoda.core.core.SongodaCoreDiagCommand;
import com.songoda.core.commands.CommandManager;
import com.songoda.core.compatibility.ClientVersion;
import com.songoda.core.compatibility.LegacyMaterials;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.event.server.PluginEnableEvent;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.ServicePriority;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
public class SongodaCore {
private final static String prefix = "[SongodaCore]";
/**
* Whenever we make a major change to the core GUI, updater,
* or other function used by the core, increment this number
*/
private final static int coreRevision = 2;
private final static int updaterVersion = 1;
private final static Set<PluginInfo> registeredPlugins = new HashSet<>();
private static SongodaCore INSTANCE = null;
private JavaPlugin piggybackedPlugin;
private CommandManager commandManager;
private EventListener loginListener;
private ShadedEventListener shadingListener;
public static boolean hasShading() {
// sneaky hack to check the package name since maven tries to re-shade all references to the package string
return !SongodaCore.class.getPackage().getName().equals(new String(new char[]{'c','o','m','.','s','o','n','g','o','d','a','.','c','o','r','e'}));
}
public static void registerPlugin(JavaPlugin plugin, int pluginID, LegacyMaterials icon) {
registerPlugin(plugin, pluginID, icon == null ? "STONE" : icon.name());
}
public static void registerPlugin(JavaPlugin plugin, int pluginID, String icon) {
if(INSTANCE == null) {
// First: are there any other instances of SongodaCore active?
for (Class<?> clazz : Bukkit.getServicesManager().getKnownServices()) {
if(clazz.getSimpleName().equals("SongodaCore")) {
try {
// test to see if we're up to date
int otherVersion = (int) clazz.getMethod("getCoreVersion").invoke(null);
if(otherVersion >= getCoreVersion()) {
// use the active service
clazz.getMethod("registerPlugin", JavaPlugin.class, int.class, String.class).invoke(null, plugin, pluginID, icon);
if(hasShading()) {
(INSTANCE = new SongodaCore()).piggybackedPlugin = plugin;
INSTANCE.shadingListener = new ShadedEventListener();
Bukkit.getPluginManager().registerEvents(INSTANCE.shadingListener, plugin);
}
} else {
// we are newer than the registered service: steal all of its registrations
// grab the old core's registrations
List otherPlugins = (List) clazz.getMethod("getPlugins").invoke(null);
// destroy the old core
Object oldCore = clazz.getMethod("getInstance").invoke(null);
Method destruct = clazz.getDeclaredMethod("destroy");
destruct.setAccessible(true);
destruct.invoke(oldCore);
// register ourselves as the SongodaCore service!
INSTANCE = new SongodaCore(plugin);
INSTANCE.init();
INSTANCE.register(plugin, pluginID, icon);
Bukkit.getServicesManager().register(SongodaCore.class, INSTANCE, plugin, ServicePriority.Normal);
// we need (JavaPlugin plugin, int pluginID, String icon) for our object
if(!otherPlugins.isEmpty()) {
Object testSubject = otherPlugins.get(0);
Class otherPluginInfo = testSubject.getClass();
Method otherPluginInfo_getJavaPlugin = otherPluginInfo.getMethod("getJavaPlugin");
Method otherPluginInfo_getSongodaId = otherPluginInfo.getMethod("getSongodaId");
Method otherPluginInfo_getCoreIcon = otherPluginInfo.getMethod("getCoreIcon");
for(Object other : otherPlugins) {
INSTANCE.register(
(JavaPlugin) otherPluginInfo_getJavaPlugin.invoke(other),
(int) otherPluginInfo_getSongodaId.invoke(other),
(String) otherPluginInfo_getCoreIcon.invoke(other));
}
}
}
return;
} catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException ignored) {
}
}
}
// register ourselves as the SongodaCore service!
INSTANCE = new SongodaCore(plugin);
INSTANCE.init();
INSTANCE.register(plugin, pluginID, icon);
Bukkit.getServicesManager().register(SongodaCore.class, INSTANCE, plugin, ServicePriority.Normal);
}
}
SongodaCore() {
commandManager = null;
}
SongodaCore(JavaPlugin javaPlugin) {
piggybackedPlugin = javaPlugin;
commandManager = new CommandManager(piggybackedPlugin);
loginListener = new EventListener();
}
private void init() {
shadingListener = new ShadedEventListener();
commandManager.registerCommandDynamically(new SongodaCoreCommand())
.addSubCommand(new SongodaCoreDiagCommand());
Bukkit.getPluginManager().registerEvents(loginListener, piggybackedPlugin);
Bukkit.getPluginManager().registerEvents(shadingListener, piggybackedPlugin);
// we aggressevely want to own this command
tasks.add(Bukkit.getScheduler().runTaskLaterAsynchronously(piggybackedPlugin, ()->{CommandManager.registerCommandDynamically(piggybackedPlugin, "songoda", commandManager, commandManager);}, 10 * 60 * 1));
tasks.add(Bukkit.getScheduler().runTaskLaterAsynchronously(piggybackedPlugin, ()->{CommandManager.registerCommandDynamically(piggybackedPlugin, "songoda", commandManager, commandManager);}, 20 * 60 * 1));
tasks.add(Bukkit.getScheduler().runTaskLaterAsynchronously(piggybackedPlugin, ()->{CommandManager.registerCommandDynamically(piggybackedPlugin, "songoda", commandManager, commandManager);}, 20 * 60 * 2));
tasks.add(Bukkit.getScheduler().runTaskLaterAsynchronously(piggybackedPlugin, ()->registerAllPlugins(), 20 * 60 * 2));
}
/**
* Used to yield this core to a newer core
*/
private void destroy() {
Bukkit.getServicesManager().unregister(SongodaCore.class, INSTANCE);
tasks.stream().filter(task -> task != null && !task.isCancelled())
.forEach(task -> Bukkit.getScheduler().cancelTask(task.getTaskId()));
HandlerList.unregisterAll(loginListener);
if(!hasShading()) {
HandlerList.unregisterAll(shadingListener);
}
registeredPlugins.clear();
commandManager = null;
loginListener = null;
}
private ArrayList<BukkitTask> tasks = new ArrayList();
/**
* Register plugins that may not have been updated yet
*/
private void registerAllPlugins() {
PluginManager pm = Bukkit.getPluginManager();
String p;
if (!isRegistered(p = "EpicAnchors") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 31, LegacyMaterials.END_PORTAL_FRAME.name());
}
if (!isRegistered(p = "EpicBosses") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 19, LegacyMaterials.ZOMBIE_SPAWN_EGG.name());
}
if (!isRegistered(p = "EpicEnchants") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 67, LegacyMaterials.DIAMOND_SWORD.name());
}
if (!isRegistered(p = "EpicFarming") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 21, LegacyMaterials.WHEAT.name());
}
if (!isRegistered(p = "EpicFurnaces") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 22, LegacyMaterials.FURNACE.name());
}
if (!isRegistered(p = "EpicHeads") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 26, LegacyMaterials.PLAYER_HEAD.name());
}
if (!isRegistered(p = "EpicHoppers") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 15, LegacyMaterials.HOPPER.name());
}
if (!isRegistered(p = "EpicLevels") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 44, LegacyMaterials.NETHER_STAR.name());
}
if (!isRegistered(p = "EpicSpawners") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 13, LegacyMaterials.SPAWNER.name());
}
if (!isRegistered(p = "EpicVouchers") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 25, LegacyMaterials.EMERALD.name());
}
if (!isRegistered(p = "FabledSkyBlock") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 17, LegacyMaterials.GRASS_BLOCK.name());
}
if (!isRegistered(p = "UltimateCatcher") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 51, LegacyMaterials.EGG.name());
}
if (!isRegistered(p = "UltimateClaims") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 65, LegacyMaterials.CHEST.name());
}
if (!isRegistered(p = "UltimateFishing") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 59, LegacyMaterials.COD.name());
}
if (!isRegistered(p = "UltimateKits") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 14, LegacyMaterials.BEACON.name());
}
if (!isRegistered(p = "UltimateModeration") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 29, LegacyMaterials.DIAMOND_CHESTPLATE.name());
}
if (!isRegistered(p = "UltimateRepairing") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 20, LegacyMaterials.ANVIL.name());
}
if (!isRegistered(p = "UltimateStacker") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 16, LegacyMaterials.IRON_INGOT.name());
}
if (!isRegistered(p = "UltimateTimber") && pm.isPluginEnabled(p)) {
register((JavaPlugin) pm.getPlugin(p), 18, LegacyMaterials.IRON_AXE.name());
}
}
private void register(JavaPlugin plugin, int pluginID, String icon) {
System.out.println(getPrefix() + "Hooked " + plugin.getName() + ".");
PluginInfo info = new PluginInfo(plugin, pluginID, icon);
// don't forget to check for language pack updates ;)
info.addModule(new LocaleModule());
registeredPlugins.add(info);
tasks.add(Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> update(info), 60L));
}
private void update(PluginInfo plugin) {
try {
URL url = new URL("https://update.songoda.com/index.php?plugin=" + plugin.getSongodaId()
+ "&version=" + plugin.getJavaPlugin().getDescription().getVersion()
+ "&updaterVersion=" + updaterVersion);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11");
urlConnection.setRequestProperty("Accept", "*/*");
urlConnection.setConnectTimeout(5000);
InputStream is = urlConnection.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
int numCharsRead;
char[] charArray = new char[1024];
StringBuilder sb = new StringBuilder();
while ((numCharsRead = isr.read(charArray)) > 0) {
sb.append(charArray, 0, numCharsRead);
}
urlConnection.disconnect();
String jsonString = sb.toString();
JSONObject json = (JSONObject) new JSONParser().parse(jsonString);
plugin.setLatestVersion((String) json.get("latestVersion"));
plugin.setMarketplaceLink((String) json.get("link"));
plugin.setNotification((String) json.get("notification"));
plugin.setChangeLog((String) json.get("changeLog"));
plugin.setJson(json);
for (PluginInfoModule module : plugin.getModules()) {
module.run(plugin);
}
} catch (IOException e) {
final String er = e.getMessage();
System.out.println("Connection with Songoda servers failed: " + (er.contains("URL") ? er.substring(0, er.indexOf("URL") + 3) : er));
} catch (ParseException e) {
System.out.println("Failed to parse json.");
}
}
public static List<PluginInfo> getPlugins() {
return new ArrayList<>(registeredPlugins);
}
public static int getCoreVersion() {
return coreRevision;
}
public static int getUpdaterVersion() {
return updaterVersion;
}
public static String getPrefix() {
return prefix + " ";
}
public static boolean isRegistered(String plugin) {
return registeredPlugins.stream().anyMatch(p -> p.getJavaPlugin().getName().equalsIgnoreCase(plugin));
}
public static JavaPlugin getHijackedPlugin() {
return INSTANCE == null ? null : INSTANCE.piggybackedPlugin;
}
public static SongodaCore getInstance() {
return INSTANCE;
}
private static class ShadedEventListener implements Listener {
boolean via = false;
boolean proto = false;
ShadedEventListener() {
if ((via = Bukkit.getPluginManager().isPluginEnabled("ViaVersion"))) {
Bukkit.getOnlinePlayers().forEach(p -> ClientVersion.onLoginVia(p));
} else if ((proto = Bukkit.getPluginManager().isPluginEnabled("ProtocolSupport"))) {
Bukkit.getOnlinePlayers().forEach(p -> ClientVersion.onLoginProtocol(p));
}
}
@EventHandler
void onLogin(PlayerLoginEvent event) {
if (via) {
ClientVersion.onLoginVia(event.getPlayer());
} else if (proto) {
ClientVersion.onLoginProtocol(event.getPlayer());
}
}
@EventHandler
void onLogout(PlayerQuitEvent event) {
if (via) {
ClientVersion.onLogout(event.getPlayer());
}
}
@EventHandler
void onEnable(PluginEnableEvent event) {
// technically shouldn't have online players here, but idk
if (!via && (via = event.getPlugin().getName().equals("ViaVersion"))) {
Bukkit.getOnlinePlayers().forEach(p -> ClientVersion.onLoginVia(p));
} else if (!proto && (proto = event.getPlugin().getName().equals("ProtocolSupport"))) {
Bukkit.getOnlinePlayers().forEach(p -> ClientVersion.onLoginProtocol(p));
}
}
}
private class EventListener implements Listener {
final HashMap<UUID, Long> lastCheck = new HashMap();
@EventHandler
void onLogin(PlayerLoginEvent event) {
final Player player = event.getPlayer();
// don't spam players with update checks
long now = System.currentTimeMillis();
Long last = lastCheck.get(player.getUniqueId());
if(last != null && now - 10000 < last) return;
lastCheck.put(player.getUniqueId(), now);
// is this player good to revieve update notices?
if (!event.getPlayer().isOp() && !player.hasPermission("songoda.updatecheck")) return;
// check for updates! ;)
for (PluginInfo plugin : getPlugins()) {
if (plugin.getNotification() != null && plugin.getJavaPlugin().isEnabled())
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin.getJavaPlugin(), () ->
player.sendMessage("[" + plugin.getJavaPlugin().getName() + "] " + plugin.getNotification()), 10L);
}
}
@EventHandler
void onDisable(PluginDisableEvent event) {
// don't track disabled plugins
PluginInfo pi = registeredPlugins.stream().filter(p -> event.getPlugin() == p.getJavaPlugin()).findFirst().orElse(null);
if (pi != null) {
registeredPlugins.remove(pi);
}
if (event.getPlugin() == piggybackedPlugin) {
// uh-oh! Abandon ship!!
Bukkit.getServicesManager().unregisterAll(piggybackedPlugin);
// can we move somewhere else?
if ((pi = registeredPlugins.stream().findFirst().orElse(null)) != null) {
// move ourselves to this plugin
piggybackedPlugin = pi.getJavaPlugin();
Bukkit.getServicesManager().register(SongodaCore.class, INSTANCE, piggybackedPlugin, ServicePriority.Normal);
Bukkit.getPluginManager().registerEvents(loginListener, piggybackedPlugin);
Bukkit.getPluginManager().registerEvents(shadingListener, piggybackedPlugin);
CommandManager.registerCommandDynamically(piggybackedPlugin, "songoda", commandManager, commandManager);
}
}
}
}
}

View File

@ -0,0 +1,145 @@
package com.songoda.core;
import com.songoda.core.configuration.Config;
import com.songoda.core.configuration.ConfigFileConfigurationAdapter;
import com.songoda.core.locale.Locale;
import com.songoda.core.utils.Metrics;
import java.util.List;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.command.ConsoleCommandSender;
import org.bukkit.plugin.java.JavaPlugin;
/**
* REMINDER: When converting plugins to use this, REMOVE METRICS <br>
* Must not have two instances of Metrics enabled!
*
* @author jascotty2
*/
public abstract class SongodaPlugin extends JavaPlugin {
protected Locale locale;
protected Config config = new Config(this);
protected ConsoleCommandSender console = Bukkit.getConsoleSender();
private boolean emergencyStop = false;
public abstract void onPluginLoad();
public abstract void onPluginEnable();
public abstract void onPluginDisable();
/**
* Called after reloadConfig() is called
*/
public abstract void onConfigReload();
/**
* Any other plugin configuration files used by the plugin.
*
* @return a list of Configs that are used in addition to the main config.
*/
public abstract List<Config> getExtraConfig();
@Override
public ConfigFileConfigurationAdapter getConfig() {
return config.getFileConfig();
}
@Override
public void reloadConfig() {
config.load();
onConfigReload();
}
@Override
public void saveConfig() {
config.save();
}
@Override
public final void onLoad() {
try {
onPluginLoad();
} catch (Throwable t) {
getLogger().log(Level.SEVERE, "Unexpected error while loading " + getDescription().getName() + ": Disabling plugin!", t);
emergencyStop = true;
}
}
@Override
public final void onEnable() {
if(emergencyStop) {
setEnabled(false);
return;
}
console.sendMessage(ChatColor.GREEN + "=============================");
console.sendMessage(String.format("%s%s %s by %sSongoda <3!", ChatColor.GRAY.toString(),
getDescription().getName(), getDescription().getVersion(), ChatColor.DARK_PURPLE.toString()));
console.sendMessage(String.format("%sAction: %s%s%s...", ChatColor.GRAY.toString(),
ChatColor.GREEN.toString(), "Enabling", ChatColor.GRAY.toString()));
try {
locale = Locale.loadDefaultLocale(this, "en_US");
// plugin setup
onPluginEnable();
// Start Metrics
Metrics.start(this);
} catch (Throwable t) {
getLogger().log(Level.SEVERE, "Unexpected error while loading " + getDescription().getName() + ": Disabling plugin!", t);
emergencyStop = true;
setEnabled(false);
console.sendMessage(ChatColor.RED + "~~~~~~~~~~~~~~~~~~~~~~~~~~~~~");
return;
}
console.sendMessage(ChatColor.GREEN + "=============================");
}
@Override
public final void onDisable() {
if (emergencyStop) {
return;
}
console.sendMessage(ChatColor.GREEN + "=============================");
console.sendMessage(String.format("%s%s %s by %sSongoda <3!", ChatColor.GRAY.toString(),
getDescription().getName(), getDescription().getVersion(), ChatColor.DARK_PURPLE.toString()));
console.sendMessage(String.format("%sAction: %s%s%s...", ChatColor.GRAY.toString(),
ChatColor.RED.toString(), "Disabling", ChatColor.GRAY.toString()));
onPluginDisable();
console.sendMessage(ChatColor.GREEN + "=============================");
}
public ConsoleCommandSender getConsole() {
return console;
}
public Locale getLocale() {
return locale;
}
/**
* Set the plugin's locale to a specific language
*
* @param localeName locale to use, eg "en_US"
* @param reload optionally reload the loaded locale if the locale didn't
* change
* @return true if the locale exists and was loaded successfully
*/
public boolean setLocale(String localeName, boolean reload) {
if (locale != null && locale.getName().equals(localeName)) {
return !reload || locale.reloadMessages();
} else {
Locale l = Locale.loadLocale(this, localeName);
if (l != null) {
locale = l;
return true;
} else {
return false;
}
}
}
}

View File

@ -0,0 +1,57 @@
package com.songoda.core.commands;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
public abstract class AbstractCommand {
private final boolean noConsole;
private boolean hasArgs = false;
private final List<String> subCommand = new ArrayList<>();
protected AbstractCommand(boolean noConsole, String... command) {
this.subCommand.addAll(Arrays.asList(command));
this.noConsole = noConsole;
}
protected AbstractCommand(boolean noConsole, boolean hasArgs, String... command) {
this.subCommand.addAll(Arrays.asList(command));
this.hasArgs = hasArgs;
this.noConsole = noConsole;
}
public final List<String> getCommands() {
return Collections.unmodifiableList(subCommand);
}
public final void addSubCommand(String command) {
subCommand.add(command);
}
protected abstract ReturnType runCommand(CommandSender sender, String... args);
protected abstract List<String> onTab(CommandSender sender, String... args);
public abstract String getPermissionNode();
public abstract String getSyntax();
public abstract String getDescription();
public boolean hasArgs() {
return hasArgs;
}
public boolean isNoConsole() {
return noConsole;
}
public static enum ReturnType {SUCCESS, FAILURE, SYNTAX_ERROR}
}

View File

@ -0,0 +1,275 @@
package com.songoda.core.commands;
import com.songoda.core.compatibility.ServerProject;
import com.songoda.core.compatibility.ServerVersion;
import com.songoda.core.utils.TextUtils;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import org.bukkit.command.Command;
import org.bukkit.command.CommandExecutor;
import org.bukkit.command.CommandSender;
import org.bukkit.command.PluginCommand;
import org.bukkit.command.TabCompleter;
import org.bukkit.entity.Player;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.command.SimpleCommandMap;
import org.bukkit.plugin.Plugin;
public class CommandManager implements CommandExecutor, TabCompleter {
private final JavaPlugin plugin;
private final HashMap<String, SimpleNestedCommand> commands = new HashMap<>();
private boolean allowLooseCommands = false;
public CommandManager(JavaPlugin plugin) {
this.plugin = plugin;
}
public Set<String> getCommands() {
return Collections.unmodifiableSet(commands.keySet());
}
public List<String> getSubCommands(String command) {
SimpleNestedCommand nested = command == null ? null : commands.get(command.toLowerCase());
return nested == null ? Collections.EMPTY_LIST : nested.children.keySet().stream().collect(Collectors.toList());
}
public Set<AbstractCommand> getAllCommands() {
HashSet<AbstractCommand> all = new HashSet();
commands.values().stream()
.filter(c -> c.parent != null && !all.contains(c.parent))
.forEach(c -> {
all.add(c.parent);
c.children.values().stream()
.filter(s -> !all.contains(s))
.forEach(s -> all.add(s));
});
return all;
}
public CommandManager registerCommandDynamically(String command) {
CommandManager.registerCommandDynamically(plugin, command, this, this);
return this;
}
/**
* TODO: Test compatibility. Seems to fail in 1.8
*/
public SimpleNestedCommand registerCommandDynamically(AbstractCommand abstractCommand) {
SimpleNestedCommand nested = new SimpleNestedCommand(abstractCommand);
abstractCommand.getCommands().stream().forEach(cmd -> {
CommandManager.registerCommandDynamically(plugin, cmd, this, this);
commands.put(cmd.toLowerCase(), nested);
PluginCommand pcmd = plugin.getCommand(cmd);
if(pcmd != null) {
pcmd.setExecutor(this);
pcmd.setTabCompleter(this);
} else {
plugin.getLogger().warning("Failed to register command: /" + cmd);
}
});
return nested;
}
public SimpleNestedCommand addCommand(AbstractCommand abstractCommand) {
SimpleNestedCommand nested = new SimpleNestedCommand(abstractCommand);
abstractCommand.getCommands().stream().forEach(cmd -> {
commands.put(cmd.toLowerCase(), nested);
PluginCommand pcmd = plugin.getCommand(cmd);
if(pcmd != null) {
pcmd.setExecutor(this);
pcmd.setTabCompleter(this);
} else {
plugin.getLogger().warning("Failed to register command: /" + cmd);
}
});
return nested;
}
public CommandManager addCommands(AbstractCommand... abstractCommands) {
for (AbstractCommand abstractCommand : abstractCommands)
addCommand(abstractCommand);
return this;
}
public CommandManager setExecutor(String command) {
plugin.getCommand(command).setExecutor(this);
return this;
}
public CommandManager setUseClosestCommand(boolean bool) {
allowLooseCommands = bool;
return this;
}
@Override
public boolean onCommand(CommandSender commandSender, Command command, String label, String[] args) {
// grab the specific command that's being called
SimpleNestedCommand nested = commands.get(command.getName().toLowerCase());
if(nested != null) {
// check to see if we're trying to call a sub-command
if(args.length != 0 && !nested.children.isEmpty()) {
String subCmd = getSubCommand(nested, args);
if(subCmd != null) {
// we have a subcommand to use!
AbstractCommand sub = nested.children.get(subCmd);
// adjust the arguments to match - BREAKING!!
int i = subCmd.indexOf(' ') == -1 ? 1 : 2;
String[] newArgs = new String[args.length - i];
System.arraycopy(args, i, newArgs, 0, newArgs.length);
// now process the command
processRequirements(sub, commandSender, newArgs);
return true;
}
}
// if we've gotten this far, then just use the command we have
if(nested.parent != null) {
processRequirements(nested.parent, commandSender, args);
return true;
}
}
commandSender.sendMessage(TextUtils.formatText("&7The command you entered does not exist or is spelt incorrectly."));
return true;
}
private String getSubCommand(SimpleNestedCommand nested, String[] args) {
String cmd = args[0].toLowerCase();
if(nested.children.containsKey(cmd))
return cmd;
String match = null;
// support for two-argument subcommands
if(args.length >= 2 && nested.children.keySet().stream().anyMatch(k -> k.indexOf(' ') != -1)) {
cmd = String.join(" ", args[0], args[1]);
if(nested.children.containsKey(cmd))
return cmd;
}
// if we don't have a subcommand, should we search for one?
if (allowLooseCommands) {
// do a "closest match"
int count = 0;
for (String c : nested.children.keySet()) {
if (c.startsWith(cmd)) {
match = c;
if (++count > 1) {
// there can only be one!
match = null;
break;
}
}
}
}
return match;
}
private void processRequirements(AbstractCommand command, CommandSender sender, String[] args) {
if (!(sender instanceof Player) && command.isNoConsole()) {
sender.sendMessage("&cYou must be a player to use this command...");
return;
}
if (command.getPermissionNode() == null || sender.hasPermission(command.getPermissionNode())) {
AbstractCommand.ReturnType returnType = command.runCommand(sender, args);
if (returnType == AbstractCommand.ReturnType.SYNTAX_ERROR) {
sender.sendMessage(TextUtils.formatText("&cInvalid Syntax!"));
sender.sendMessage(TextUtils.formatText("&7The valid syntax is: &6" + command.getSyntax() + "&7."));
}
return;
}
sender.sendMessage(TextUtils.formatText("&cYou do not have permission to do that."));
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] args) {
// grab the specific command that's being called
SimpleNestedCommand nested = commands.get(command.getName().toLowerCase());
if(nested != null) {
if(args.length == 0) {
return nested.parent != null ? nested.parent.onTab(sender, args) : null;
}
// check for each sub-command that they have access to
final boolean op = sender.isOp();
final boolean console = !(sender instanceof Player);
if(args.length == 1) {
// suggest sub-commands that this user has access to
final String arg = args[0].toLowerCase();
return nested.children.entrySet().stream()
.filter(e -> !console || !e.getValue().isNoConsole())
.filter(e -> e.getKey().startsWith(arg))
.filter(e -> op || e.getValue().getPermissionNode() == null || sender.hasPermission(e.getValue().getPermissionNode()))
.map(e -> e.getKey())
.collect(Collectors.toList());
} else {
// more than one arg, let's check to see if we have a command here
String subCmd = getSubCommand(nested, args);
AbstractCommand sub;
if(subCmd != null && (sub = nested.children.get(subCmd)) != null
&& (!console || !sub.isNoConsole())
&& (op || sub.getPermissionNode() == null || sender.hasPermission(sub.getPermissionNode()))) {
// adjust the arguments to match - BREAKING!!
int i = subCmd.indexOf(' ') == -1 ? 1 : 2;
String[] newArgs = new String[args.length - i];
System.arraycopy(args, i, newArgs, 0, newArgs.length);
// we're good to go!
return fetchList(sub, newArgs, sender);
}
}
}
return Collections.EMPTY_LIST;
}
private List<String> fetchList(AbstractCommand abstractCommand, String[] args, CommandSender sender) {
List<String> list = abstractCommand.onTab(sender, args);
String str = args[args.length - 1];
if (list != null && str != null && str.length() >= 1) {
try {
list.removeIf(s -> !s.toLowerCase().startsWith(str.toLowerCase()));
} catch (UnsupportedOperationException ignored) {
}
}
return list;
}
public static void registerCommandDynamically(Plugin plugin, String command, CommandExecutor executor, TabCompleter tabManager) {
try {
// Retrieve the SimpleCommandMap from the server
Class<?> classCraftServer = Bukkit.getServer().getClass();
Field fieldCommandMap = classCraftServer.getDeclaredField("commandMap");
fieldCommandMap.setAccessible(true);
SimpleCommandMap commandMap = (SimpleCommandMap) fieldCommandMap.get(Bukkit.getServer());
// Construct a new Command object
Constructor<PluginCommand> constructorPluginCommand = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
constructorPluginCommand.setAccessible(true);
PluginCommand commandObject = constructorPluginCommand.newInstance(command, plugin);
// If we're on Paper 1.8, we need to register timings (spigot creates timings on init, paper creates it on register)
// later versions of paper create timings if needed when the command is executed
if(ServerProject.isServer(ServerProject.PAPER) && ServerVersion.isServerVersionBelow(ServerVersion.V1_9)) {
commandObject.timings = co.aikar.timings.TimingsManager.getCommandTiming(plugin.getName().toLowerCase(), commandObject);
}
// Set command action
commandObject.setExecutor(executor);
// Set tab complete
commandObject.setTabCompleter(tabManager);
// Register the command
Field fieldKnownCommands = SimpleCommandMap.class.getDeclaredField("knownCommands");
fieldKnownCommands.setAccessible(true);
Map<String, Command> knownCommands = (Map<String, Command>) fieldKnownCommands.get(commandMap);
knownCommands.put(command, commandObject);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,25 @@
package com.songoda.core.commands;
import java.util.HashMap;
import java.util.stream.Stream;
public class SimpleNestedCommand {
final AbstractCommand parent;
final HashMap<String, AbstractCommand> children = new HashMap();
protected SimpleNestedCommand(AbstractCommand parent) {
this.parent = parent;
}
public SimpleNestedCommand addSubCommand(AbstractCommand command) {
command.getCommands().stream().forEach(cmd -> children.put(cmd.toLowerCase(), command));
return this;
}
public SimpleNestedCommand addSubCommands(AbstractCommand... commands) {
Stream.of(commands).forEach(command -> command.getCommands().stream().forEach(cmd -> children.put(cmd.toLowerCase(), command)));
return this;
}
}

View File

@ -0,0 +1,101 @@
package com.songoda.core.compatibility;
import com.songoda.core.SongodaCore;
import java.util.HashMap;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
/**
* Handy reference for checking a connected client's Minecraft version<br>
* NOTE: this is automatically initialized through SongodaCore
*/
public class ClientVersion {
static HashMap<UUID, ServerVersion> players = new HashMap();
/**
* Check to see what client version this player is connected to the server
* with. Note that if a player is connecting with a newer client than the server,
* this value will simply be the server version.
*
* @param player Player to check
* @return ServerVersion that matches this player's Minecraft version
*/
public static ServerVersion getClientVersion(Player player) {
if (player == null || !players.containsKey(player.getUniqueId())) {
return ServerVersion.getServerVersion();
}
return players.get(player.getUniqueId());
}
/**
* Do Not Use: This is handled by SongodaCore.
*/
@Deprecated
public static void onLoginProtocol(Player p) {
Bukkit.getScheduler().runTaskLater(SongodaCore.getHijackedPlugin(), ()-> {
if(p.isOnline()) {
final int version = protocolsupport.api.ProtocolSupportAPI.getProtocolVersion(p).getId();
players.put(p.getUniqueId(), protocolToVersion(version));
}
}, 20);
}
/**
* Do Not Use: This is handled by SongodaCore.
*/
@Deprecated
public static void onLoginVia(Player p) {
Bukkit.getScheduler().runTaskLater(SongodaCore.getHijackedPlugin(), ()-> {
if(p.isOnline()) {
final int version = us.myles.ViaVersion.api.Via.getAPI().getPlayerVersion(p.getUniqueId());
players.put(p.getUniqueId(), protocolToVersion(version));
}
}, 20);
}
private static ServerVersion protocolToVersion(int version) {
// https://github.com/ViaVersion/ViaVersion/blob/master/common/src/main/java/us/myles/ViaVersion/api/protocol/ProtocolVersion.java
// https://github.com/ProtocolSupport/ProtocolSupport/blob/master/src/protocolsupport/api/ProtocolVersion.java
switch (version) {
case 4:
case 5:
return ServerVersion.V1_7;
case 47:
return ServerVersion.V1_8;
case 107:
case 108:
case 109:
case 110:
return ServerVersion.V1_9;
case 210:
return ServerVersion.V1_10;
case 315:
case 316:
return ServerVersion.V1_11;
case 335:
case 338:
case 340:
return ServerVersion.V1_12;
case 393:
case 401:
case 404:
return ServerVersion.V1_13;
case 477:
case 485:
case 490:
case 498:
return ServerVersion.V1_14;
}
return version > 498 ? ServerVersion.getServerVersion() : ServerVersion.UNKNOWN;
}
/**
* Do Not Use: This is handled by SongodaCore.
*/
@Deprecated
public static void onLogout(Player p) {
players.remove(p.getUniqueId());
}
}

View File

@ -0,0 +1,956 @@
package com.songoda.core.compatibility;
import org.bukkit.Sound;
/**
* Sounds that are compatible with server versions 1.7+ <br>
* TODO: This needs work.
* Finished 1.8, finished 1.9 blocks, resume with 1.9 entities<br>
* Between 1.8 and 1.9, all sounds were renamed, and between 1.12 and 1.13, some
* sounds were renamed. New sounds have been added by different versions, as
* well. The intent of this class is to provide either the correct sound or a
* near equivalent for the current server.
*
* @since 2019-08-25
* @author jascotty2
*/
public enum CompatibleSounds {
// some of these values are missing an API value..
// would using the raw strings be better?
// 1.8 list:
// https://www.minecraftforum.net/forums/mapping-and-modding-java-edition/mapping-and-modding-tutorials/2213619-1-8-all-playsound-sound-arguments
AMBIENT_CAVE(ServerVersion.V1_9, v("AMBIENCE_CAVE")),
AMBIENT_UNDERWATER_ENTER,
AMBIENT_UNDERWATER_EXIT,
AMBIENT_UNDERWATER_LOOP,
AMBIENT_UNDERWATER_LOOP_ADDITIONS,
AMBIENT_UNDERWATER_LOOP_ADDITIONS_RARE,
AMBIENT_UNDERWATER_LOOP_ADDITIONS_ULTRA_RARE,
BLOCK_ANVIL_BREAK(ServerVersion.V1_9, v("ANVIL_BREAK")),
BLOCK_ANVIL_DESTROY(ServerVersion.V1_9, v("ANVIL_BREAK", true)),
BLOCK_ANVIL_FALL(ServerVersion.V1_9, v("ANVIL_LAND", true)),
BLOCK_ANVIL_HIT(ServerVersion.V1_9, v("ANVIL_LAND", true)),
BLOCK_ANVIL_LAND(ServerVersion.V1_9, v("ANVIL_LAND")),
BLOCK_ANVIL_PLACE(ServerVersion.V1_9, v("ANVIL_LAND", true)),
BLOCK_ANVIL_STEP(ServerVersion.V1_9, v("ANVIL_LAND", true)),
BLOCK_ANVIL_USE("ANVIL_USE"),
BLOCK_BAMBOO_BREAK(ServerVersion.V1_14, v(ServerVersion.V1_9, "BLOCK_WOOD_BREAK"), v(ServerVersion.V1_8, "DIG_WOOD")),
BLOCK_BAMBOO_FALL,
BLOCK_BAMBOO_HIT,
BLOCK_BAMBOO_PLACE,
BLOCK_BAMBOO_SAPLING_BREAK,
BLOCK_BAMBOO_SAPLING_HIT,
BLOCK_BAMBOO_SAPLING_PLACE,
BLOCK_BAMBOO_STEP,
BLOCK_BARREL_CLOSE,
BLOCK_BARREL_OPEN,
BLOCK_BEACON_ACTIVATE,
BLOCK_BEACON_AMBIENT,
BLOCK_BEACON_DEACTIVATE,
BLOCK_BEACON_POWER_SELECT,
BLOCK_BELL_RESONATE,
BLOCK_BELL_USE,
BLOCK_BLASTFURNACE_FIRE_CRACKLE,
BLOCK_BREWING_STAND_BREW(ServerVersion.V1_9, v("LAVA_POP", true)),
BLOCK_BUBBLE_COLUMN_BUBBLE_POP,
BLOCK_BUBBLE_COLUMN_UPWARDS_AMBIENT,
BLOCK_BUBBLE_COLUMN_UPWARDS_INSIDE,
BLOCK_BUBBLE_COLUMN_WHIRLPOOL_AMBIENT,
BLOCK_BUBBLE_COLUMN_WHIRLPOOL_INSIDE,
BLOCK_CAMPFIRE_CRACKLE,
BLOCK_CHEST_CLOSE(ServerVersion.V1_9, v("CHEST_CLOSE")),
BLOCK_CHEST_LOCKED(ServerVersion.V1_9, v("CHEST_CLOSE", true)),
BLOCK_CHEST_OPEN(ServerVersion.V1_9, v("CHEST_OPEN")),
BLOCK_CHORUS_FLOWER_DEATH(ServerVersion.V1_9, v(null, true)), // I have no idea..
BLOCK_CHORUS_FLOWER_GROW(ServerVersion.V1_9, v(null, true)), // I have no idea..
BLOCK_COMPARATOR_CLICK(ServerVersion.V1_9, v("WOOD_CLICK", true)),
BLOCK_COMPOSTER_EMPTY,
BLOCK_COMPOSTER_FILL,
BLOCK_COMPOSTER_FILL_SUCCESS,
BLOCK_COMPOSTER_READY,
BLOCK_CONDUIT_ACTIVATE,
BLOCK_CONDUIT_AMBIENT,
BLOCK_CONDUIT_AMBIENT_SHORT,
BLOCK_CONDUIT_ATTACK_TARGET,
BLOCK_CONDUIT_DEACTIVATE,
BLOCK_CORAL_BLOCK_BREAK,
BLOCK_CORAL_BLOCK_FALL,
BLOCK_CORAL_BLOCK_HIT,
BLOCK_CORAL_BLOCK_PLACE,
BLOCK_CORAL_BLOCK_STEP,
BLOCK_CROP_BREAK,
BLOCK_DISPENSER_DISPENSE(ServerVersion.V1_9, v("SHOOT_ARROW", true)),
BLOCK_DISPENSER_FAIL(ServerVersion.V1_9, v("WOOD_CLICK", true)),
BLOCK_DISPENSER_LAUNCH(ServerVersion.V1_9, v("GHAST_FIREBALL", true)),
BLOCK_ENCHANTMENT_TABLE_USE,
BLOCK_ENDER_CHEST_CLOSE(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_ENDERCHEST_CLOSE"), v("CHEST_CLOSE", true)),
BLOCK_ENDER_CHEST_OPEN(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_ENDERCHEST_OPEN"), v("CHEST_OPEN", true)),
BLOCK_END_GATEWAY_SPAWN(ServerVersion.V1_9, v("PORTAL_TRIGGER", true)),
BLOCK_END_PORTAL_FRAME_FILL,
BLOCK_END_PORTAL_SPAWN,
BLOCK_FENCE_GATE_CLOSE("DOOR_CLOSE"),
BLOCK_FENCE_GATE_OPEN("DOOR_OPEN"),
BLOCK_FIRE_AMBIENT("FIRE"),
BLOCK_FIRE_EXTINGUISH("FIZZ"),
BLOCK_FURNACE_FIRE_CRACKLE(ServerVersion.V1_9, v("FIRE", true)),
BLOCK_GLASS_BREAK("GLASS"),
BLOCK_GLASS_FALL(ServerVersion.V1_9, v("STEP_GRASS", true)),
BLOCK_GLASS_HIT(ServerVersion.V1_9, v("STEP_GRASS", true)),
BLOCK_GLASS_PLACE(ServerVersion.V1_9, v("STEP_GRASS", true)),
BLOCK_GLASS_STEP(ServerVersion.V1_9, v("STEP_STONE", true)),
BLOCK_GRASS_BREAK("DIG_GRASS"),
BLOCK_GRASS_FALL(ServerVersion.V1_9, v("STEP_GRASS", true)),
BLOCK_GRASS_HIT(ServerVersion.V1_9, v("STEP_GRASS", true)),
BLOCK_GRASS_PLACE(ServerVersion.V1_9, v("STEP_GRASS", true)),
BLOCK_GRASS_STEP("STEP_GRASS"),
BLOCK_GRAVEL_BREAK("DIG_GRAVEL"),
BLOCK_GRAVEL_FALL(ServerVersion.V1_9, v("STEP_GRAVEL", true)),
BLOCK_GRAVEL_HIT(ServerVersion.V1_9, v("STEP_GRAVEL", true)),
BLOCK_GRAVEL_PLACE(ServerVersion.V1_9, v("STEP_GRAVEL", true)),
BLOCK_GRAVEL_STEP("STEP_GRAVEL"),
BLOCK_GRINDSTONE_USE,
BLOCK_IRON_DOOR_CLOSE("DOOR_CLOSE"),
BLOCK_IRON_DOOR_OPEN("DOOR_OPEN"),
BLOCK_IRON_TRAPDOOR_CLOSE("DOOR_CLOSE"),
BLOCK_IRON_TRAPDOOR_OPEN("DOOR_OPEN"),
BLOCK_LADDER_BREAK(ServerVersion.V1_9, v(null, true)),
BLOCK_LADDER_FALL(ServerVersion.V1_9, v("STEP_LADDER", true)),
BLOCK_LADDER_HIT(ServerVersion.V1_9, v(null, true)),
BLOCK_LADDER_PLACE(ServerVersion.V1_9, v(null, true)),
BLOCK_LADDER_STEP("STEP_LADDER"),
BLOCK_LANTERN_BREAK,
BLOCK_LANTERN_FALL,
BLOCK_LANTERN_HIT,
BLOCK_LANTERN_PLACE,
BLOCK_LANTERN_STEP,
BLOCK_LAVA_AMBIENT("LAVA"),
BLOCK_LAVA_EXTINGUISH("FIZZ"),
BLOCK_LAVA_POP("LAVA_POP"),
BLOCK_LEVER_CLICK("WOOD_CLICK"),
BLOCK_LILY_PAD_PLACE(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_WATERLILY_PLACE"), v(null, true)),
BLOCK_METAL_BREAK(ServerVersion.V1_9, v(null, true)),
BLOCK_METAL_FALL(ServerVersion.V1_9, v(null, true)),
BLOCK_METAL_HIT(ServerVersion.V1_9, v(null, true)),
BLOCK_METAL_PLACE(ServerVersion.V1_9, v(null, true)),
BLOCK_METAL_PRESSURE_PLATE_CLICK_OFF(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_METAL_PRESSUREPLATE_CLICK_OFF"), v("WOOD_CLICK", true)),
BLOCK_METAL_PRESSURE_PLATE_CLICK_ON(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_METAL_PRESSUREPLATE_CLICK_ON"), v("WOOD_CLICK", true)),
BLOCK_METAL_STEP(ServerVersion.V1_9, v("STEP_STONE", true)),
BLOCK_NETHER_WART_BREAK,
BLOCK_NOTE_BLOCK_BANJO(ServerVersion.V1_14, v(ServerVersion.V1_13, "BLOCK_NOTE_BLOCK_GUITAR", true), v(ServerVersion.V1_9, "BLOCK_NOTE_GUITAR", true), v("NOTE_BASS_GUITAR", true)),
BLOCK_NOTE_BLOCK_BASEDRUM(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_NOTE_BASEDRUM"), v("NOTE_BASS_DRUM")),
BLOCK_NOTE_BLOCK_BASS(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_NOTE_BASS"), v("NOTE_BASS")),
BLOCK_NOTE_BLOCK_BELL(ServerVersion.V1_13, v(ServerVersion.V1_12, "BLOCK_NOTE_BELL"), v(ServerVersion.V1_9, "BLOCK_NOTE_HARP", true), v("NOTE_PLING", true)),
BLOCK_NOTE_BLOCK_BIT(ServerVersion.V1_14, v(ServerVersion.V1_13, "BLOCK_NOTE_BLOCK_BELL", true), v(ServerVersion.V1_12, "BLOCK_NOTE_BELL", true), v(ServerVersion.V1_9, "BLOCK_NOTE_HARP", true), v("NOTE_PLING", true)),
BLOCK_NOTE_BLOCK_CHIME(ServerVersion.V1_13, v(ServerVersion.V1_12, "BLOCK_NOTE_CHIME"), v(ServerVersion.V1_9, "BLOCK_NOTE_PLING", true), v("NOTE_PLING", true)),
BLOCK_NOTE_BLOCK_COW_BELL(ServerVersion.V1_14, v(ServerVersion.V1_13, "BLOCK_NOTE_BLOCK_BELL", true), v(ServerVersion.V1_12, "BLOCK_NOTE_BELL"), v(ServerVersion.V1_9, "BLOCK_NOTE_HARP", true), v("NOTE_PLING", true)),
BLOCK_NOTE_BLOCK_DIDGERIDOO(ServerVersion.V1_14, v(ServerVersion.V1_13, "BLOCK_NOTE_BLOCK_FLUTE", true), v(ServerVersion.V1_12, "BLOCK_NOTE_FLUTE", true), v(ServerVersion.V1_9, "BLOCK_NOTE_HARP", true), v("NOTE_BASS_GUITAR", true)),
BLOCK_NOTE_BLOCK_FLUTE(ServerVersion.V1_13, v(ServerVersion.V1_12, "BLOCK_NOTE_FLUTE"), v(ServerVersion.V1_9, "BLOCK_NOTE_HARP", true), v("NOTE_BASS_GUITAR", true)),
BLOCK_NOTE_BLOCK_GUITAR(ServerVersion.V1_13, v(ServerVersion.V1_12, "BLOCK_NOTE_GUITAR"), v(ServerVersion.V1_9, "BLOCK_NOTE_HARP", true), v("NOTE_BASS_GUITAR")), // This value disappeared from the API from 1.9-1.11 (returned in 12)
BLOCK_NOTE_BLOCK_HARP(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_NOTE_HARP"), v("NOTE_PIANO")),
BLOCK_NOTE_BLOCK_HAT(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_NOTE_HAT"), v("NOTE_STICKS")),
BLOCK_NOTE_BLOCK_IRON_XYLOPHONE(ServerVersion.V1_14, v(ServerVersion.V1_13, "BLOCK_NOTE_BLOCK_XYLOPHONE", true), v(ServerVersion.V1_9, "BLOCK_NOTE_XYLOPHONE", true), v("NOTE_PLING", true)),
BLOCK_NOTE_BLOCK_PLING(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_NOTE_PLING"), v("NOTE_PLING")),
BLOCK_NOTE_BLOCK_SNARE(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_NOTE_SNARE"), v("NOTE_SNARE_DRUM")),
BLOCK_NOTE_BLOCK_XYLOPHONE(ServerVersion.V1_13, v(ServerVersion.V1_12, "BLOCK_NOTE_XYLOPHONE"), v(ServerVersion.V1_9, "BLOCK_NOTE_PLING", true), v("NOTE_PLING", true)),
BLOCK_PISTON_CONTRACT("PISTON_RETRACT"),
BLOCK_PISTON_EXTEND("PISTON_EXTEND"),
BLOCK_PORTAL_AMBIENT("PORTAL"),
BLOCK_PORTAL_TRAVEL("PORTAL_TRAVEL"),
BLOCK_PORTAL_TRIGGER("PORTAL_TRIGGER"),
BLOCK_PUMPKIN_CARVE,
BLOCK_REDSTONE_TORCH_BURNOUT("FIZZ"),
BLOCK_SAND_BREAK("DIG_SAND"),
BLOCK_SAND_FALL(ServerVersion.V1_9, v("STEP_SAND", true)),
BLOCK_SAND_HIT(ServerVersion.V1_9, v("STEP_SAND", true)),
BLOCK_SAND_PLACE(ServerVersion.V1_9, v("STEP_SAND", true)),
BLOCK_SAND_STEP("STEP_SAND"),
BLOCK_SCAFFOLDING_BREAK,
BLOCK_SCAFFOLDING_FALL,
BLOCK_SCAFFOLDING_HIT,
BLOCK_SCAFFOLDING_PLACE,
BLOCK_SCAFFOLDING_STEP,
BLOCK_SHULKER_BOX_CLOSE("CHEST_CLOSE"),
BLOCK_SHULKER_BOX_OPEN("CHEST_OPEN"),
BLOCK_SLIME_BLOCK_BREAK(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_SLIME_BREAK"), v(null, true)),
BLOCK_SLIME_BLOCK_FALL(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_SLIME_FALL"), v(null, true)),
BLOCK_SLIME_BLOCK_HIT(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_SLIME_HIT"), v(null, true)),
BLOCK_SLIME_BLOCK_PLACE(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_SLIME_PLACE"), v(null, true)),
BLOCK_SLIME_BLOCK_STEP(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_SLIME_STEP"), v(null, true)),
BLOCK_SMOKER_SMOKE,
BLOCK_SNOW_BREAK("DIG_SNOW"),
BLOCK_SNOW_FALL(ServerVersion.V1_9, v("STEP_SNOW", true)),
BLOCK_SNOW_HIT(ServerVersion.V1_9, v("STEP_SNOW", true)),
BLOCK_SNOW_PLACE(ServerVersion.V1_9, v("STEP_SNOW", true)),
BLOCK_SNOW_STEP("STEP_SNOW"),
BLOCK_STONE_BREAK("DIG_STONE"),
BLOCK_STONE_BUTTON_CLICK_OFF(ServerVersion.V1_9, v("WOOD_CLICK", true)),
BLOCK_STONE_BUTTON_CLICK_ON(ServerVersion.V1_9, v("WOOD_CLICK", true)),
BLOCK_STONE_FALL,
BLOCK_STONE_HIT,
BLOCK_STONE_PLACE,
BLOCK_STONE_PRESSURE_PLATE_CLICK_OFF,
BLOCK_STONE_PRESSURE_PLATE_CLICK_ON,
BLOCK_STONE_STEP("STEP_STONE"),
BLOCK_SWEET_BERRY_BUSH_BREAK,
BLOCK_SWEET_BERRY_BUSH_PLACE,
BLOCK_TRIPWIRE_ATTACH(ServerVersion.V1_9, v("WOOD_CLICK", true)),
BLOCK_TRIPWIRE_CLICK_OFF(ServerVersion.V1_9, v("WOOD_CLICK", true)),
BLOCK_TRIPWIRE_CLICK_ON(ServerVersion.V1_9, v("WOOD_CLICK", true)),
BLOCK_TRIPWIRE_DETACH(ServerVersion.V1_9, v("WOOD_CLICK", true)),
BLOCK_WATER_AMBIENT("WATER"),
BLOCK_WET_GRASS_BREAK,
BLOCK_WET_GRASS_FALL,
BLOCK_WET_GRASS_HIT,
BLOCK_WET_GRASS_PLACE,
BLOCK_WET_GRASS_STEP,
BLOCK_WOODEN_BUTTON_CLICK_OFF(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_WOOD_BUTTON_CLICK_ON"), v("WOOD_CLICK")),
BLOCK_WOODEN_BUTTON_CLICK_ON(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_WOOD_BUTTON_CLICK_ON"), v("WOOD_CLICK")),
BLOCK_WOODEN_DOOR_CLOSE("DOOR_CLOSE"),
BLOCK_WOODEN_DOOR_OPEN("DOOR_OPEN"),
BLOCK_WOODEN_PRESSURE_PLATE_CLICK_OFF(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_WOOD_PRESSUREPLATE_CLICK_OFF"), v("WOOD_CLICK", true)),
BLOCK_WOODEN_PRESSURE_PLATE_CLICK_ON(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_WOOD_PRESSUREPLATE_CLICK_ON"), v("WOOD_CLICK", true)),
BLOCK_WOODEN_TRAPDOOR_CLOSE("DOOR_OPEN"),
BLOCK_WOODEN_TRAPDOOR_OPEN("DOOR_OPEN"),
BLOCK_WOOD_BREAK("DIG_WOOD"),
BLOCK_WOOD_FALL(ServerVersion.V1_9, v("STEP_WOOD", true)),
BLOCK_WOOD_HIT(ServerVersion.V1_9, v("STEP_WOOD", true)),
BLOCK_WOOD_PLACE(ServerVersion.V1_9, v("STEP_WOOD", true)),
BLOCK_WOOD_STEP("STEP_WOOD"),
BLOCK_WOOL_BREAK(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_CLOTH_BREAK"), v("DIG_WOOL")),
BLOCK_WOOL_FALL(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_CLOTH_STEP", true), v("STEP_WOOL", true)),
BLOCK_WOOL_HIT(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_CLOTH_STEP", true), v("STEP_WOOL", true)),
BLOCK_WOOL_PLACE(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_CLOTH_STEP", true), v("STEP_WOOL", true)),
BLOCK_WOOL_STEP(ServerVersion.V1_13, v(ServerVersion.V1_9, "BLOCK_CLOTH_STEP"), v("STEP_WOOL")),
ENCHANT_THORNS_HIT,
ENTITY_ARMOR_STAND_BREAK,
ENTITY_ARMOR_STAND_FALL,
ENTITY_ARMOR_STAND_HIT,
ENTITY_ARMOR_STAND_PLACE,
ENTITY_ARROW_HIT("SUCCESSFUL_HIT"), // these may be reversed..
ENTITY_ARROW_HIT_PLAYER("ARROW_HIT"),
ENTITY_ARROW_SHOOT("SHOOT_ARROW"),
ENTITY_BAT_AMBIENT("BAT_IDLE"),
ENTITY_BAT_DEATH("BAT_DEATH"),
ENTITY_BAT_HURT("BAT_HURT"),
ENTITY_BAT_LOOP("BAT_LOOP"),
ENTITY_BAT_TAKEOFF("BAT_TAKEOFF"),
ENTITY_BLAZE_AMBIENT("BLAZE_BREATH"),
ENTITY_BLAZE_BURN("BLAZE_HIT"),
ENTITY_BLAZE_DEATH("BLAZE_DEATH"),
ENTITY_BLAZE_HURT("BLAZE_HIT"),
ENTITY_BLAZE_SHOOT,
ENTITY_BOAT_PADDLE_LAND,
ENTITY_BOAT_PADDLE_WATER,
ENTITY_CAT_AMBIENT(ServerVersion.V1_9, v("CAT_MEOW")),
ENTITY_CAT_BEG_FOR_FOOD(ServerVersion.V1_14, v(ServerVersion.V1_9, "ENTITY_CAT_AMBIENT", true), v("CAT_MEOW", true)),
ENTITY_CAT_DEATH,
ENTITY_CAT_EAT(ServerVersion.V1_14, v(ServerVersion.V1_9, "ENTITY_GENERIC_EAT"), v("EAT")),
ENTITY_CAT_HISS("CAT_HISS"),
ENTITY_CAT_HURT("CAT_HIT"),
ENTITY_CAT_PURR("CAT_PURR"),
ENTITY_CAT_PURREOW("CAT_PURREOW"),
ENTITY_CAT_STRAY_AMBIENT,
ENTITY_CHICKEN_AMBIENT("CHICKEN_IDLE"),
ENTITY_CHICKEN_DEATH,
ENTITY_CHICKEN_EGG("CHICKEN_EGG_POP"),
ENTITY_CHICKEN_HURT("CHICKEN_HURT"),
ENTITY_CHICKEN_STEP("CHICKEN_WALK"),
ENTITY_COD_AMBIENT,
ENTITY_COD_DEATH,
ENTITY_COD_FLOP,
ENTITY_COD_HURT,
ENTITY_COW_AMBIENT("COW_IDLE"),
ENTITY_COW_DEATH("COW_HURT"),
ENTITY_COW_HURT("COW_HURT"),
ENTITY_COW_MILK,
ENTITY_COW_STEP("COW_WALK"),
ENTITY_CREEPER_DEATH("CREEPER_DEATH"),
ENTITY_CREEPER_HURT,
ENTITY_CREEPER_PRIMED("CREEPER_HISS"),
ENTITY_DOLPHIN_AMBIENT,
ENTITY_DOLPHIN_AMBIENT_WATER,
ENTITY_DOLPHIN_ATTACK,
ENTITY_DOLPHIN_DEATH,
ENTITY_DOLPHIN_EAT(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_GENERIC_EAT", true), v("EAT", true)),
ENTITY_DOLPHIN_HURT,
ENTITY_DOLPHIN_JUMP,
ENTITY_DOLPHIN_PLAY,
ENTITY_DOLPHIN_SPLASH(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_GENERIC_SPLASH", true), v("SPLASH", true)),
ENTITY_DOLPHIN_SWIM,
ENTITY_DONKEY_AMBIENT("DONKEY_IDLE"),
ENTITY_DONKEY_ANGRY("DONKEY_ANGRY"),
ENTITY_DONKEY_CHEST,
ENTITY_DONKEY_DEATH("DONKEY_DEATH"),
ENTITY_DONKEY_HURT("DONKEY_HIT"),
ENTITY_DRAGON_FIREBALL_EXPLODE,
ENTITY_DROWNED_AMBIENT,
ENTITY_DROWNED_AMBIENT_WATER,
ENTITY_DROWNED_DEATH,
ENTITY_DROWNED_DEATH_WATER,
ENTITY_DROWNED_HURT,
ENTITY_DROWNED_HURT_WATER,
ENTITY_DROWNED_SHOOT,
ENTITY_DROWNED_STEP,
ENTITY_DROWNED_SWIM,
ENTITY_EGG_THROW,
ENTITY_ELDER_GUARDIAN_AMBIENT,
ENTITY_ELDER_GUARDIAN_AMBIENT_LAND,
ENTITY_ELDER_GUARDIAN_CURSE,
ENTITY_ELDER_GUARDIAN_DEATH,
ENTITY_ELDER_GUARDIAN_DEATH_LAND,
ENTITY_ELDER_GUARDIAN_FLOP,
ENTITY_ELDER_GUARDIAN_HURT,
ENTITY_ELDER_GUARDIAN_HURT_LAND,
ENTITY_ENDERMAN_AMBIENT(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ENDERMEN_AMBIENT"), v("ENDERMAN_IDLE")),
ENTITY_ENDERMAN_DEATH(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ENDERMEN_DEATH"), v("ENDERMAN_DEATH")),
ENTITY_ENDERMAN_HURT(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ENDERMEN_HURT"), v("ENDERMAN_HIT")),
ENTITY_ENDERMAN_SCREAM(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ENDERMEN_SCREAM"), v("ENDERMAN_SCREAM")),
ENTITY_ENDERMAN_STARE(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ENDERMEN_STARE"), v("ENDERMAN_STARE")),
ENTITY_ENDERMAN_TELEPORT(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ENDERMEN_TELEPORT"), v("ENDERMAN_TELEPORT")),
ENTITY_ENDERMITE_AMBIENT,
ENTITY_ENDERMITE_DEATH,
ENTITY_ENDERMITE_HURT,
ENTITY_ENDERMITE_STEP,
ENTITY_ENDER_DRAGON_AMBIENT(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ENDERDRAGON_AMBIENT"), v("ENDERDRAGON_GROWL", true)),
ENTITY_ENDER_DRAGON_DEATH(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ENDERDRAGON_DEATH"), v("ENDERDRAGON_DEATH")),
ENTITY_ENDER_DRAGON_FLAP(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ENDERDRAGON_FLAP"), v("ENDERDRAGON_WINGS")),
ENTITY_ENDER_DRAGON_GROWL(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ENDERDRAGON_GROWL"), v("ENDERDRAGON_GROWL")),
ENTITY_ENDER_DRAGON_HURT(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ENDERDRAGON_HURT"), v("ENDERDRAGON_HIT")),
ENTITY_ENDER_DRAGON_SHOOT(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ENDERDRAGON_SHOOT"), v("GHAST_FIREBALL", true)),
ENTITY_ENDER_EYE_DEATH(ServerVersion.V1_13, v(ServerVersion.V1_12, "ENTITY_ENDEREYE_DEATH"), v(ServerVersion.V1_9, "BLOCK_GLASS_BREAK", true), v("GLASS", true)),
ENTITY_ENDER_EYE_LAUNCH(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ENDEREYE_LAUNCH"), v(ServerVersion.V1_9, "BLOCK_PORTAL_TRIGGER", true), v("PORTAL_TRIGGER", true)),
ENTITY_ENDER_PEARL_THROW,
ENTITY_EVOKER_AMBIENT,
ENTITY_EVOKER_CAST_SPELL,
ENTITY_EVOKER_CELEBRATE,
ENTITY_EVOKER_DEATH,
ENTITY_EVOKER_FANGS_ATTACK,
ENTITY_EVOKER_HURT,
ENTITY_EVOKER_PREPARE_ATTACK,
ENTITY_EVOKER_PREPARE_SUMMON,
ENTITY_EVOKER_PREPARE_WOLOLO,
ENTITY_EXPERIENCE_BOTTLE_THROW,
ENTITY_EXPERIENCE_ORB_PICKUP("ORB_PICKUP"),
ENTITY_FIREWORK_ROCKET_BLAST(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_FIREWORK_BLAST"), v("FIREWORK_BLAST")),
ENTITY_FIREWORK_ROCKET_BLAST_FAR(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_FIREWORK_BLAST_FAR"), v("FIREWORK_BLAST2")),
ENTITY_FIREWORK_ROCKET_LARGE_BLAST(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_FIREWORK_LARGE_BLAST"), v("FIREWORK_LARGE_BLAST")),
ENTITY_FIREWORK_ROCKET_LARGE_BLAST_FAR(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_FIREWORK_LARGE_BLAST_FAR"), v("FIREWORK_LARGE_BLAST2")),
ENTITY_FIREWORK_ROCKET_LAUNCH(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_FIREWORK_LAUNCH"), v("FIREWORK_LAUNCH")),
ENTITY_FIREWORK_ROCKET_SHOOT(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_FIREWORK_SHOOT"), v("FIREWORK_LAUNCH", true)),
ENTITY_FIREWORK_ROCKET_TWINKLE(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_FIREWORK_TWINKLE"), v("FIREWORK_TWINKLE")),
ENTITY_FIREWORK_ROCKET_TWINKLE_FAR(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_FIREWORK_TWINKLE_FAR"), v("FIREWORK_TWINKLE2")),
ENTITY_FISHING_BOBBER_RETRIEVE,
ENTITY_FISHING_BOBBER_SPLASH,
ENTITY_FISHING_BOBBER_THROW,
ENTITY_FISH_SWIM,
ENTITY_FOX_AGGRO,
ENTITY_FOX_AMBIENT,
ENTITY_FOX_BITE,
ENTITY_FOX_DEATH,
ENTITY_FOX_EAT(ServerVersion.V1_14, v(ServerVersion.V1_9, "ENTITY_GENERIC_EAT", true), v("EAT", true)),
ENTITY_FOX_HURT,
ENTITY_FOX_SCREECH,
ENTITY_FOX_SLEEP,
ENTITY_FOX_SNIFF,
ENTITY_FOX_SPIT,
ENTITY_GENERIC_BIG_FALL("FALL_BIG"),
ENTITY_GENERIC_BURN,
ENTITY_GENERIC_DEATH,
ENTITY_GENERIC_DRINK("DRINK"),
ENTITY_GENERIC_EAT("EAT"),
ENTITY_GENERIC_EXPLODE("EXPLODE"),
ENTITY_GENERIC_EXTINGUISH_FIRE("FIZZ"),
ENTITY_GENERIC_HURT("HURT_FLESH"),
ENTITY_GENERIC_SMALL_FALL("FALL_SMALL"),
ENTITY_GENERIC_SPLASH(ServerVersion.V1_9, v("SPLASH", true)),
ENTITY_GENERIC_SWIM,
ENTITY_GHAST_AMBIENT("GHAST_MOAN"),
ENTITY_GHAST_DEATH("GHAST_DEATH"),
ENTITY_GHAST_HURT("GHAST_SCREAM2"),
ENTITY_GHAST_SCREAM("GHAST_SCREAM"),
ENTITY_GHAST_SHOOT("GHAST_FIREBALL"),
ENTITY_GHAST_WARN("GHAST_CHARGE"),
ENTITY_GUARDIAN_AMBIENT,
ENTITY_GUARDIAN_AMBIENT_LAND,
ENTITY_GUARDIAN_ATTACK,
ENTITY_GUARDIAN_DEATH,
ENTITY_GUARDIAN_DEATH_LAND,
ENTITY_GUARDIAN_FLOP,
ENTITY_GUARDIAN_HURT,
ENTITY_GUARDIAN_HURT_LAND,
ENTITY_HORSE_AMBIENT("HORSE_IDLE"),
ENTITY_HORSE_ANGRY("HORSE_ANGRY"),
ENTITY_HORSE_ARMOR("HORSE_ARMOR"),
ENTITY_HORSE_BREATHE("HORSE_BREATHE"),
ENTITY_HORSE_DEATH("HORSE_DEATH"),
ENTITY_HORSE_EAT("EAT"),
ENTITY_HORSE_GALLOP("HORSE_GALLOP"),
ENTITY_HORSE_HURT("HORSE_HIT"),
ENTITY_HORSE_JUMP("HORSE_JUMP"),
ENTITY_HORSE_LAND("HORSE_LAND"),
ENTITY_HORSE_SADDLE("HORSE_SADDLE"),
ENTITY_HORSE_STEP("HORSE_SOFT"),
ENTITY_HORSE_STEP_WOOD("HORSE_WOOD"),
ENTITY_HOSTILE_BIG_FALL,
ENTITY_HOSTILE_DEATH,
ENTITY_HOSTILE_HURT,
ENTITY_HOSTILE_SMALL_FALL,
ENTITY_HOSTILE_SPLASH(ServerVersion.V1_9, v("SPLASH", true)),
ENTITY_HOSTILE_SWIM,
ENTITY_HUSK_AMBIENT,
ENTITY_HUSK_CONVERTED_TO_ZOMBIE,
ENTITY_HUSK_DEATH,
ENTITY_HUSK_HURT,
ENTITY_HUSK_STEP,
ENTITY_ILLUSIONER_AMBIENT,
ENTITY_ILLUSIONER_CAST_SPELL,
ENTITY_ILLUSIONER_DEATH,
ENTITY_ILLUSIONER_HURT,
ENTITY_ILLUSIONER_MIRROR_MOVE,
ENTITY_ILLUSIONER_PREPARE_BLINDNESS,
ENTITY_ILLUSIONER_PREPARE_MIRROR,
ENTITY_IRON_GOLEM_ATTACK(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_IRONGOLEM_ATTACK"), v("IRONGOLEM_THROW")),
ENTITY_IRON_GOLEM_DEATH(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_IRONGOLEM_DEATH"), v("IRONGOLEM_DEATH")),
ENTITY_IRON_GOLEM_HURT(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_IRONGOLEM_HURT"), v("IRONGOLEM_HIT")),
ENTITY_IRON_GOLEM_STEP(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_IRONGOLEM_STEP"), v("IRONGOLEM_WALK")),
ENTITY_ITEM_BREAK("ITEM_BREAK"),
ENTITY_ITEM_FRAME_ADD_ITEM,
ENTITY_ITEM_FRAME_BREAK,
ENTITY_ITEM_FRAME_PLACE,
ENTITY_ITEM_FRAME_REMOVE_ITEM,
ENTITY_ITEM_FRAME_ROTATE_ITEM,
ENTITY_ITEM_PICKUP("ITEM_PICKUP"),
ENTITY_LEASH_KNOT_BREAK,
ENTITY_LEASH_KNOT_PLACE,
ENTITY_LIGHTNING_BOLT_IMPACT(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_LIGHTNING_IMPACT"), v("AMBIENCE_THUNDER", true)),
ENTITY_LIGHTNING_BOLT_THUNDER(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_LIGHTNING_THUNDER"), v("AMBIENCE_THUNDER")),
ENTITY_LINGERING_POTION_THROW,
ENTITY_LLAMA_AMBIENT,
ENTITY_LLAMA_ANGRY,
ENTITY_LLAMA_CHEST,
ENTITY_LLAMA_DEATH,
ENTITY_LLAMA_EAT("EAT"),
ENTITY_LLAMA_HURT,
ENTITY_LLAMA_SPIT,
ENTITY_LLAMA_STEP,
ENTITY_LLAMA_SWAG,
ENTITY_MAGMA_CUBE_DEATH, // ENTITY_MAGMACUBE_DEATH
ENTITY_MAGMA_CUBE_DEATH_SMALL,
ENTITY_MAGMA_CUBE_HURT, // ENTITY_MAGMACUBE_HURT
ENTITY_MAGMA_CUBE_HURT_SMALL,
ENTITY_MAGMA_CUBE_JUMP(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_MAGMACUBE_JUMP"), v("MAGMACUBE_JUMP")),
ENTITY_MAGMA_CUBE_SQUISH(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_MAGMACUBE_SQUISH"), v("MAGMACUBE_WALK")),
ENTITY_MAGMA_CUBE_SQUISH_SMALL(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_MAGMACUBE_SQUISH", true), v("MAGMACUBE_WALK2")),
ENTITY_MINECART_INSIDE("MINECART_INSIDE"),
ENTITY_MINECART_RIDING("MINECART_BASE"),
ENTITY_MOOSHROOM_CONVERT,
ENTITY_MOOSHROOM_EAT(ServerVersion.V1_14, v(ServerVersion.V1_9, "ENTITY_GENERIC_EAT", true), v("EAT", true)),
ENTITY_MOOSHROOM_MILK,
ENTITY_MOOSHROOM_SHEAR,
ENTITY_MOOSHROOM_SUSPICIOUS_MILK,
ENTITY_MULE_AMBIENT,
ENTITY_MULE_CHEST,
ENTITY_MULE_DEATH,
ENTITY_MULE_HURT,
ENTITY_OCELOT_AMBIENT,
ENTITY_OCELOT_DEATH,
ENTITY_OCELOT_HURT,
ENTITY_PAINTING_BREAK,
ENTITY_PAINTING_PLACE,
ENTITY_PANDA_AGGRESSIVE_AMBIENT,
ENTITY_PANDA_AMBIENT,
ENTITY_PANDA_BITE,
ENTITY_PANDA_CANT_BREED,
ENTITY_PANDA_DEATH,
ENTITY_PANDA_EAT(ServerVersion.V1_14, v(ServerVersion.V1_9, "ENTITY_GENERIC_EAT", true), v("EAT", true)),
ENTITY_PANDA_HURT,
ENTITY_PANDA_PRE_SNEEZE,
ENTITY_PANDA_SNEEZE,
ENTITY_PANDA_STEP,
ENTITY_PANDA_WORRIED_AMBIENT,
ENTITY_PARROT_AMBIENT,
ENTITY_PARROT_DEATH,
ENTITY_PARROT_EAT(ServerVersion.V1_12, v(ServerVersion.V1_9, "ENTITY_GENERIC_EAT", true), v("EAT", true)),
ENTITY_PARROT_FLY,
ENTITY_PARROT_HURT,
ENTITY_PARROT_IMITATE_BLAZE,
ENTITY_PARROT_IMITATE_CREEPER,
ENTITY_PARROT_IMITATE_DROWNED,
ENTITY_PARROT_IMITATE_ELDER_GUARDIAN,
ENTITY_PARROT_IMITATE_ENDERMAN,
ENTITY_PARROT_IMITATE_ENDERMITE,
ENTITY_PARROT_IMITATE_ENDER_DRAGON,
ENTITY_PARROT_IMITATE_EVOKER,
ENTITY_PARROT_IMITATE_GHAST,
ENTITY_PARROT_IMITATE_GUARDIAN,
ENTITY_PARROT_IMITATE_HUSK,
ENTITY_PARROT_IMITATE_ILLUSIONER,
ENTITY_PARROT_IMITATE_MAGMA_CUBE,
ENTITY_PARROT_IMITATE_PANDA,
ENTITY_PARROT_IMITATE_PHANTOM,
ENTITY_PARROT_IMITATE_PILLAGER,
ENTITY_PARROT_IMITATE_POLAR_BEAR,
ENTITY_PARROT_IMITATE_RAVAGER,
ENTITY_PARROT_IMITATE_SHULKER,
ENTITY_PARROT_IMITATE_SILVERFISH,
ENTITY_PARROT_IMITATE_SKELETON,
ENTITY_PARROT_IMITATE_SLIME,
ENTITY_PARROT_IMITATE_SPIDER,
ENTITY_PARROT_IMITATE_STRAY,
ENTITY_PARROT_IMITATE_VEX,
ENTITY_PARROT_IMITATE_VINDICATOR,
ENTITY_PARROT_IMITATE_WITCH,
ENTITY_PARROT_IMITATE_WITHER,
ENTITY_PARROT_IMITATE_WITHER_SKELETON,
ENTITY_PARROT_IMITATE_WOLF,
ENTITY_PARROT_IMITATE_ZOMBIE,
ENTITY_PARROT_IMITATE_ZOMBIE_PIGMAN,
ENTITY_PARROT_IMITATE_ZOMBIE_VILLAGER,
ENTITY_PARROT_STEP,
ENTITY_PHANTOM_AMBIENT,
ENTITY_PHANTOM_BITE,
ENTITY_PHANTOM_DEATH,
ENTITY_PHANTOM_FLAP,
ENTITY_PHANTOM_HURT,
ENTITY_PHANTOM_SWOOP,
ENTITY_PIG_AMBIENT("PIG_IDLE"),
ENTITY_PIG_DEATH("PIG_DEATH"),
ENTITY_PIG_HURT,
ENTITY_PIG_SADDLE,
ENTITY_PIG_STEP("PIG_WALK"),
ENTITY_PILLAGER_AMBIENT,
ENTITY_PILLAGER_CELEBRATE,
ENTITY_PILLAGER_DEATH,
ENTITY_PILLAGER_HURT,
ENTITY_PLAYER_ATTACK_CRIT,
ENTITY_PLAYER_ATTACK_KNOCKBACK,
ENTITY_PLAYER_ATTACK_NODAMAGE,
ENTITY_PLAYER_ATTACK_STRONG,
ENTITY_PLAYER_ATTACK_SWEEP,
ENTITY_PLAYER_ATTACK_WEAK,
ENTITY_PLAYER_BIG_FALL,
ENTITY_PLAYER_BREATH,
ENTITY_PLAYER_BURP("BURP"),
ENTITY_PLAYER_DEATH,
ENTITY_PLAYER_HURT,
ENTITY_PLAYER_HURT_DROWN,
ENTITY_PLAYER_HURT_ON_FIRE,
ENTITY_PLAYER_HURT_SWEET_BERRY_BUSH,
ENTITY_PLAYER_LEVELUP("LEVEL_UP"),
ENTITY_PLAYER_SMALL_FALL,
ENTITY_PLAYER_SPLASH("SPLASH"),
ENTITY_PLAYER_SPLASH_HIGH_SPEED(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_GENERIC_SPLASH"), v("SPLASH2", true)),
ENTITY_PLAYER_SWIM("SWIM"),
ENTITY_POLAR_BEAR_AMBIENT,
ENTITY_POLAR_BEAR_AMBIENT_BABY,
ENTITY_POLAR_BEAR_DEATH,
ENTITY_POLAR_BEAR_HURT,
ENTITY_POLAR_BEAR_STEP,
ENTITY_POLAR_BEAR_WARNING,
ENTITY_PUFFER_FISH_AMBIENT,
ENTITY_PUFFER_FISH_BLOW_OUT,
ENTITY_PUFFER_FISH_BLOW_UP,
ENTITY_PUFFER_FISH_DEATH,
ENTITY_PUFFER_FISH_FLOP,
ENTITY_PUFFER_FISH_HURT,
ENTITY_PUFFER_FISH_STING,
ENTITY_RABBIT_AMBIENT,
ENTITY_RABBIT_ATTACK,
ENTITY_RABBIT_DEATH,
ENTITY_RABBIT_HURT,
ENTITY_RABBIT_JUMP,
ENTITY_RAVAGER_AMBIENT,
ENTITY_RAVAGER_ATTACK,
ENTITY_RAVAGER_CELEBRATE,
ENTITY_RAVAGER_DEATH,
ENTITY_RAVAGER_HURT,
ENTITY_RAVAGER_ROAR,
ENTITY_RAVAGER_STEP,
ENTITY_RAVAGER_STUNNED,
ENTITY_SALMON_AMBIENT,
ENTITY_SALMON_DEATH,
ENTITY_SALMON_FLOP,
ENTITY_SALMON_HURT,
ENTITY_SHEEP_AMBIENT("SHEEP_IDLE"),
ENTITY_SHEEP_DEATH,
ENTITY_SHEEP_HURT,
ENTITY_SHEEP_SHEAR("SHEEP_SHEAR"),
ENTITY_SHEEP_STEP("SHEEP_WALK"),
ENTITY_SHULKER_AMBIENT,
ENTITY_SHULKER_BULLET_HIT,
ENTITY_SHULKER_BULLET_HURT,
ENTITY_SHULKER_CLOSE,
ENTITY_SHULKER_DEATH,
ENTITY_SHULKER_HURT,
ENTITY_SHULKER_HURT_CLOSED,
ENTITY_SHULKER_OPEN,
ENTITY_SHULKER_SHOOT,
ENTITY_SHULKER_TELEPORT,
ENTITY_SILVERFISH_AMBIENT("SILVERFISH_IDLE"),
ENTITY_SILVERFISH_DEATH("SILVERFISH_KILL"),
ENTITY_SILVERFISH_HURT("SILVERFISH_HIT"),
ENTITY_SILVERFISH_STEP("SILVERFISH_WALK"),
ENTITY_SKELETON_AMBIENT("SKELETON_IDLE"),
ENTITY_SKELETON_DEATH("SKELETON_DEATH"),
ENTITY_SKELETON_HORSE_AMBIENT("HORSE_SKELETON_IDLE"),
ENTITY_SKELETON_HORSE_AMBIENT_WATER,
ENTITY_SKELETON_HORSE_DEATH("HORSE_SKELETON_DEATH"),
ENTITY_SKELETON_HORSE_GALLOP_WATER,
ENTITY_SKELETON_HORSE_HURT("HORSE_SKELETON_HIT"),
ENTITY_SKELETON_HORSE_JUMP_WATER,
ENTITY_SKELETON_HORSE_STEP_WATER,
ENTITY_SKELETON_HORSE_SWIM,
ENTITY_SKELETON_HURT("SKELETON_HURT"),
ENTITY_SKELETON_SHOOT(ServerVersion.V1_9, v("SHOOT_ARROW", true)),
ENTITY_SKELETON_STEP("SKELETON_WALK"),
ENTITY_SLIME_ATTACK("SLIME_ATTACK"),
ENTITY_SLIME_DEATH,
ENTITY_SLIME_DEATH_SMALL,
ENTITY_SLIME_HURT,
ENTITY_SLIME_HURT_SMALL,
ENTITY_SLIME_JUMP("SLIME_WALK"), // Not sure which is 1 or 2
ENTITY_SLIME_JUMP_SMALL(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_SLIME_JUMP", true), v("SLIME_WALK", true)),
ENTITY_SLIME_SQUISH("SLIME_WALK2"),
ENTITY_SLIME_SQUISH_SMALL(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_SLIME_JUMP", true), v("SLIME_WALK2", true)),
ENTITY_SNOWBALL_THROW,
ENTITY_SNOW_GOLEM_AMBIENT,
ENTITY_SNOW_GOLEM_DEATH,
ENTITY_SNOW_GOLEM_HURT,
ENTITY_SNOW_GOLEM_SHOOT(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_SNOWMAN_SHOOT"), v("SHOOT_ARROW", true)), // this is missing from 1.8 API for some reason
ENTITY_SPIDER_AMBIENT("SPIDER_IDLE"),
ENTITY_SPIDER_DEATH("SPIDER_DEATH"),
ENTITY_SPIDER_HURT,
ENTITY_SPIDER_STEP("SPIDER_WALK"),
ENTITY_SPLASH_POTION_BREAK,
ENTITY_SPLASH_POTION_THROW,
ENTITY_SQUID_AMBIENT,
ENTITY_SQUID_DEATH,
ENTITY_SQUID_HURT,
ENTITY_SQUID_SQUIRT,
ENTITY_STRAY_AMBIENT,
ENTITY_STRAY_DEATH,
ENTITY_STRAY_HURT,
ENTITY_STRAY_STEP,
ENTITY_TNT_PRIMED("FUSE"),
ENTITY_TROPICAL_FISH_AMBIENT,
ENTITY_TROPICAL_FISH_DEATH,
ENTITY_TROPICAL_FISH_FLOP,
ENTITY_TROPICAL_FISH_HURT,
ENTITY_TURTLE_AMBIENT_LAND,
ENTITY_TURTLE_DEATH,
ENTITY_TURTLE_DEATH_BABY,
ENTITY_TURTLE_EGG_BREAK,
ENTITY_TURTLE_EGG_CRACK,
ENTITY_TURTLE_EGG_HATCH,
ENTITY_TURTLE_HURT,
ENTITY_TURTLE_HURT_BABY,
ENTITY_TURTLE_LAY_EGG,
ENTITY_TURTLE_SHAMBLE,
ENTITY_TURTLE_SHAMBLE_BABY,
ENTITY_TURTLE_SWIM,
ENTITY_VEX_AMBIENT,
ENTITY_VEX_CHARGE,
ENTITY_VEX_DEATH,
ENTITY_VEX_HURT,
ENTITY_VILLAGER_AMBIENT("VILLAGER_IDLE"),
ENTITY_VILLAGER_CELEBRATE,
ENTITY_VILLAGER_DEATH("VILLAGER_DEATH"),
ENTITY_VILLAGER_HURT("VILLAGER_HIT"),
ENTITY_VILLAGER_NO("VILLAGER_NO"),
ENTITY_VILLAGER_TRADE(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_VILLAGER_TRADING"), v("VILLAGER_HAGGLE")),
ENTITY_VILLAGER_WORK_ARMORER,
ENTITY_VILLAGER_WORK_BUTCHER,
ENTITY_VILLAGER_WORK_CARTOGRAPHER,
ENTITY_VILLAGER_WORK_CLERIC,
ENTITY_VILLAGER_WORK_FARMER,
ENTITY_VILLAGER_WORK_FISHERMAN,
ENTITY_VILLAGER_WORK_FLETCHER,
ENTITY_VILLAGER_WORK_LEATHERWORKER,
ENTITY_VILLAGER_WORK_LIBRARIAN,
ENTITY_VILLAGER_WORK_MASON,
ENTITY_VILLAGER_WORK_SHEPHERD,
ENTITY_VILLAGER_WORK_TOOLSMITH,
ENTITY_VILLAGER_WORK_WEAPONSMITH,
ENTITY_VILLAGER_YES("VILLAGER_YES"),
ENTITY_VINDICATOR_AMBIENT,
ENTITY_VINDICATOR_CELEBRATE,
ENTITY_VINDICATOR_DEATH,
ENTITY_VINDICATOR_HURT,
ENTITY_WANDERING_TRADER_AMBIENT,
ENTITY_WANDERING_TRADER_DEATH,
ENTITY_WANDERING_TRADER_DISAPPEARED,
ENTITY_WANDERING_TRADER_DRINK_MILK(ServerVersion.V1_14, v(ServerVersion.V1_9, "ENTITY_GENERIC_DRINK", true), v("DRINK", true)),
ENTITY_WANDERING_TRADER_DRINK_POTION(ServerVersion.V1_14, v(ServerVersion.V1_9, "ENTITY_GENERIC_DRINK", true), v("DRINK", true)),
ENTITY_WANDERING_TRADER_HURT,
ENTITY_WANDERING_TRADER_NO,
ENTITY_WANDERING_TRADER_REAPPEARED,
ENTITY_WANDERING_TRADER_TRADE,
ENTITY_WANDERING_TRADER_YES,
ENTITY_WITCH_AMBIENT,
ENTITY_WITCH_CELEBRATE,
ENTITY_WITCH_DEATH,
ENTITY_WITCH_DRINK("DRINK"),
ENTITY_WITCH_HURT,
ENTITY_WITCH_THROW,
ENTITY_WITHER_AMBIENT("WITHER_IDLE"),
ENTITY_WITHER_BREAK_BLOCK("WITHER_SPAWN"),
ENTITY_WITHER_DEATH("WITHER_DEATH"),
ENTITY_WITHER_HURT("WITHER_HURT"),
ENTITY_WITHER_SHOOT("WITHER_SHOOT"),
ENTITY_WITHER_SKELETON_AMBIENT,
ENTITY_WITHER_SKELETON_DEATH,
ENTITY_WITHER_SKELETON_HURT,
ENTITY_WITHER_SKELETON_STEP,
ENTITY_WITHER_SPAWN,
ENTITY_WOLF_AMBIENT("WOLF_BARK"),
ENTITY_WOLF_DEATH("WOLF_DEATH"),
ENTITY_WOLF_GROWL("WOLF_GROWL"),
ENTITY_WOLF_HOWL("WOLF_HOWL"),
ENTITY_WOLF_HURT("WOLF_HURT"),
ENTITY_WOLF_PANT("WOLF_PANT"),
ENTITY_WOLF_SHAKE("WOLF_SHAKE"),
ENTITY_WOLF_STEP("WOLF_WALK"),
ENTITY_WOLF_WHINE("WOLF_WHINE"),
ENTITY_ZOMBIE_AMBIENT("ZOMBIE_IDLE"),
ENTITY_ZOMBIE_ATTACK_IRON_DOOR(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ZOMBIE_ATTACK_IRON_DOOR", true), v("ZOMBIE_METAL")),
ENTITY_ZOMBIE_ATTACK_WOODEN_DOOR(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ZOMBIE_ATTACK_DOOR_WOOD", true), v("ZOMBIE_WOOD")),
ENTITY_ZOMBIE_BREAK_WOODEN_DOOR(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ZOMBIE_BREAK_DOOR_WOOD", true), v("ZOMBIE_WOODBREAK")),
ENTITY_ZOMBIE_CONVERTED_TO_DROWNED,
ENTITY_ZOMBIE_DEATH("ZOMBIE_DEATH"),
ENTITY_ZOMBIE_DESTROY_EGG,
ENTITY_ZOMBIE_HORSE_AMBIENT("HORSE_ZOMBIE_IDLE"),
ENTITY_ZOMBIE_HORSE_DEATH("HORSE_ZOMBIE_DEATH"),
ENTITY_ZOMBIE_HORSE_HURT("HORSE_ZOMBIE_HIT"),
ENTITY_ZOMBIE_HURT("ZOMBIE_HURT"),
ENTITY_ZOMBIE_INFECT("ZOMBIE_INFECT"),
ENTITY_ZOMBIE_PIGMAN_AMBIENT(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ZOMBIE_PIG_AMBIENT", true), v("ZOMBIE_PIG_IDLE")),
ENTITY_ZOMBIE_PIGMAN_ANGRY(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ZOMBIE_PIG_ANGRY", true), v("ZOMBIE_PIG_ANGRY")),
ENTITY_ZOMBIE_PIGMAN_DEATH(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ZOMBIE_PIG_DEATH", true), v("ZOMBIE_PIG_DEATH")),
ENTITY_ZOMBIE_PIGMAN_HURT(ServerVersion.V1_13, v(ServerVersion.V1_9, "ENTITY_ZOMBIE_PIG_HURT", true), v("ZOMBIE_PIG_HURT")),
ENTITY_ZOMBIE_STEP("ZOMBIE_WALK"),
ENTITY_ZOMBIE_VILLAGER_AMBIENT,
ENTITY_ZOMBIE_VILLAGER_CONVERTED("ZOMBIE_REMEDY"),
ENTITY_ZOMBIE_VILLAGER_CURE("ZOMBIE_UNFECT"),
ENTITY_ZOMBIE_VILLAGER_DEATH,
ENTITY_ZOMBIE_VILLAGER_HURT,
ENTITY_ZOMBIE_VILLAGER_STEP,
EVENT_RAID_HORN,
ITEM_ARMOR_EQUIP_CHAIN(ServerVersion.V1_9, v("SUCCESSFUL_HIT", true)),
ITEM_ARMOR_EQUIP_DIAMOND(ServerVersion.V1_9, v("SUCCESSFUL_HIT", true)),
ITEM_ARMOR_EQUIP_ELYTRA(ServerVersion.V1_11, v(ServerVersion.V1_9, "ITEM_ARMOR_EQUIP_GENERIC", true), v("SUCCESSFUL_HIT", true)),
ITEM_ARMOR_EQUIP_GENERIC(ServerVersion.V1_9, v("SUCCESSFUL_HIT", true)),
ITEM_ARMOR_EQUIP_GOLD(ServerVersion.V1_9, v("SUCCESSFUL_HIT", true)),
ITEM_ARMOR_EQUIP_IRON(ServerVersion.V1_9, v("SUCCESSFUL_HIT", true)),
ITEM_ARMOR_EQUIP_LEATHER(ServerVersion.V1_9, v("SUCCESSFUL_HIT", true)),
ITEM_ARMOR_EQUIP_TURTLE(ServerVersion.V1_13, v(ServerVersion.V1_9, "ITEM_ARMOR_EQUIP_GENERIC", true), v("SUCCESSFUL_HIT", true)),
ITEM_AXE_STRIP,
ITEM_BOOK_PAGE_TURN,
ITEM_BOOK_PUT,
ITEM_BOTTLE_EMPTY,
ITEM_BOTTLE_FILL,
ITEM_BOTTLE_FILL_DRAGONBREATH,
ITEM_BUCKET_EMPTY,
ITEM_BUCKET_EMPTY_FISH,
ITEM_BUCKET_EMPTY_LAVA,
ITEM_BUCKET_FILL,
ITEM_BUCKET_FILL_FISH,
ITEM_BUCKET_FILL_LAVA,
ITEM_CHORUS_FRUIT_TELEPORT,
ITEM_CROP_PLANT,
ITEM_CROSSBOW_HIT,
ITEM_CROSSBOW_LOADING_END,
ITEM_CROSSBOW_LOADING_MIDDLE,
ITEM_CROSSBOW_LOADING_START,
ITEM_CROSSBOW_QUICK_CHARGE_1,
ITEM_CROSSBOW_QUICK_CHARGE_2,
ITEM_CROSSBOW_QUICK_CHARGE_3,
ITEM_CROSSBOW_SHOOT,
ITEM_ELYTRA_FLYING,
ITEM_FIRECHARGE_USE,
ITEM_FLINTANDSTEEL_USE("FIRE_IGNITE"),
ITEM_HOE_TILL,
ITEM_NETHER_WART_PLANT,
ITEM_SHIELD_BLOCK,
ITEM_SHIELD_BREAK,
ITEM_SHOVEL_FLATTEN,
ITEM_SWEET_BERRIES_PICK_FROM_BUSH,
ITEM_TOTEM_USE,
ITEM_TRIDENT_HIT,
ITEM_TRIDENT_HIT_GROUND,
ITEM_TRIDENT_RETURN,
ITEM_TRIDENT_RIPTIDE_1,
ITEM_TRIDENT_RIPTIDE_2,
ITEM_TRIDENT_RIPTIDE_3,
ITEM_TRIDENT_THROW,
ITEM_TRIDENT_THUNDER,
MUSIC_CREATIVE,
MUSIC_CREDITS,
MUSIC_DISC_11(ServerVersion.V1_13, v(ServerVersion.V1_9, "RECORD_11"), v("WOOD_CLICK", true)),
MUSIC_DISC_13(ServerVersion.V1_13, v(ServerVersion.V1_9, "RECORD_13"), v("WOOD_CLICK", true)),
MUSIC_DISC_BLOCKS(ServerVersion.V1_13, v(ServerVersion.V1_9, "RECORD_BLOCKS"), v("WOOD_CLICK", true)),
MUSIC_DISC_CAT(ServerVersion.V1_13, v(ServerVersion.V1_9, "RECORD_CAT"), v("WOOD_CLICK", true)),
MUSIC_DISC_CHIRP(ServerVersion.V1_13, v(ServerVersion.V1_9, "RECORD_CHIRP"), v("WOOD_CLICK", true)),
MUSIC_DISC_FAR(ServerVersion.V1_13, v(ServerVersion.V1_9, "RECORD_FAR"), v("WOOD_CLICK", true)),
MUSIC_DISC_MALL(ServerVersion.V1_13, v(ServerVersion.V1_9, "RECORD_MALL"), v("WOOD_CLICK", true)),
MUSIC_DISC_MELLOHI(ServerVersion.V1_13, v(ServerVersion.V1_9, "RECORD_MELLOHI"), v("WOOD_CLICK", true)),
MUSIC_DISC_STAL(ServerVersion.V1_13, v(ServerVersion.V1_9, "RECORD_STAL"), v("WOOD_CLICK", true)),
MUSIC_DISC_STRAD(ServerVersion.V1_13, v(ServerVersion.V1_9, "RECORD_STRAD"), v("WOOD_CLICK", true)),
MUSIC_DISC_WAIT(ServerVersion.V1_13, v(ServerVersion.V1_9, "RECORD_WAIT"), v("WOOD_CLICK", true)),
MUSIC_DISC_WARD(ServerVersion.V1_13, v(ServerVersion.V1_9, "RECORD_WARD"), v("WOOD_CLICK", true)), // records are missing from 1.8 API
MUSIC_DRAGON,
MUSIC_END,
MUSIC_GAME,
MUSIC_MENU,
MUSIC_NETHER,
MUSIC_UNDER_WATER,
UI_BUTTON_CLICK("CLICK"),
UI_CARTOGRAPHY_TABLE_TAKE_RESULT,
UI_LOOM_SELECT_PATTERN,
UI_LOOM_TAKE_RESULT,
UI_STONECUTTER_SELECT_RECIPE,
UI_STONECUTTER_TAKE_RESULT,
UI_TOAST_CHALLENGE_COMPLETE,
UI_TOAST_IN,
UI_TOAST_OUT,
WEATHER_RAIN("AMBIENCE_RAIN"),
WEATHER_RAIN_ABOVE;
protected /*final*/ Sound sound;
protected /*final*/ boolean compatibilityMode;
protected static final boolean DEBUG = false;
private CompatibleSounds() {
// This could get risky, since we haven't finished this
//sound = Sound.valueOf(name());
Sound find = null;
for (Sound s : Sound.values()) {
if (s.name().equals(name())) {
find = s;
break;
}
}
if(DEBUG && find == null) {
System.out.println("Sound for " + name() + " Not found!");
}
sound = find;
compatibilityMode = find == null;
}
// if the sound ony ever changed from 1.8 -> 1.9
private CompatibleSounds(String compatibility_18) {
try {
compatibilityMode = false;
if (ServerVersion.isServerVersionBelow(ServerVersion.V1_9)) {
sound = Sound.valueOf(compatibility_18);
} else {
sound = Sound.valueOf(name());
}
} catch (Exception e) {
System.out.println("ERROR loading " + name());
e.printStackTrace();
}
}
private CompatibleSounds(Version... versions) {
try {
for (Version v : versions) {
if (v.sound != null && ServerVersion.isServerVersionAtLeast(v.version)) {
sound = Sound.valueOf(v.sound);
compatibilityMode = v.compatibilityMode;
return;
}
}
} catch (Exception e) {
System.out.println("ERROR loading " + name());
for (Version v : versions) {
System.out.println(v.version + " - " + v.sound);
}
e.printStackTrace();
}
Sound find = null;
for (Sound s : Sound.values()) {
if (s.name().equals(name())) {
find = s;
break;
}
}
sound = find;
compatibilityMode = find == null;
}
private CompatibleSounds(ServerVersion minVersion, Version... versions) {
try {
if (ServerVersion.isServerVersionAtLeast(minVersion)) {
// should be good to use this sound
sound = Sound.valueOf(name());
compatibilityMode = false;
} else {
for (Version v : versions) {
if (v.sound != null && ServerVersion.isServerVersionAtLeast(v.version)) {
sound = Sound.valueOf(v.sound);
compatibilityMode = v.compatibilityMode;
return;
}
}
sound = null;
compatibilityMode = false;
}
} catch (Exception e) {
System.out.println("ERROR loading " + name() + " (" + minVersion);
for (Version v : versions) {
System.out.println(v.version + " - " + v.sound);
}
e.printStackTrace();
}
}
/**
* Get a Bukkit API sound that matches this sound
*
* @return Returns either the matching sound or a similar sound
*/
public Sound getSound() {
return sound != null ? sound : UI_BUTTON_CLICK.sound;
}
/**
* Check to see if this sound is available on this server.
*
* @return Returns false if we are using a different sound.
*/
public boolean isMatch() {
return !compatibilityMode;
}
private static Version v(String sound) {
return new Version(ServerVersion.UNKNOWN, sound, false);
}
private static Version v(ServerVersion version, String sound) {
return new Version(version, sound, false);
}
private static Version v(ServerVersion version, String sound, boolean compatibility) {
return new Version(version, sound, compatibility);
}
private static Version v(String sound, boolean compatibility) {
return new Version(ServerVersion.UNKNOWN, sound, compatibility);
}
private static class Version {
final ServerVersion version;
final String sound;
final boolean compatibilityMode;
public Version(ServerVersion version, String sound, boolean compatibility) {
this.version = version;
this.sound = sound;
this.compatibilityMode = compatibility;
}
}
}

View File

@ -0,0 +1,590 @@
package com.songoda.core.compatibility;
import java.util.HashMap;
import java.util.Map;
import org.bukkit.Material;
import org.bukkit.inventory.ItemStack;
/**
* Near-Materials for older servers 1.7+
* @since 2019-08-23
* @author jascotty2
*/
public enum LegacyAnalouges {
ACACIA_BOAT(ServerVersion.V1_9, "BOAT"),
ACACIA_BUTTON(ServerVersion.V1_13, "WOOD_BUTTON"),
ACACIA_DOOR(ServerVersion.V1_8, "WOOD_DOOR"), // TODO? ACACIA_DOOR & WOODEN_DOOR are the legacy block variants
ACACIA_FENCE(ServerVersion.V1_8, "FENCE"),
ACACIA_FENCE_GATE(ServerVersion.V1_8, "FENCE_GATE"),
ACACIA_PRESSURE_PLATE(ServerVersion.V1_13, "WOOD_PLATE"),
ACACIA_SIGN(ServerVersion.V1_14, "SIGN", "SIGN"),
ACACIA_TRAPDOOR(ServerVersion.V1_13, "TRAP_DOOR"),
ACACIA_WALL_SIGN(ServerVersion.V1_14, "WALL_SIGN"),
ANDESITE(ServerVersion.V1_8, "STONE"),
ANDESITE_SLAB(ServerVersion.V1_14, "STONE_SLAB", "STEP", (byte) 0),
ANDESITE_STAIRS(ServerVersion.V1_14, "STONE_BRICK_STAIRS", "SMOOTH_STAIRS"),
ANDESITE_WALL(ServerVersion.V1_14, "COBBLESTONE_WALL", "COBBLE_WALL"),
ARMOR_STAND(ServerVersion.V1_8, "STICK"), // idk, we just need *something*
BAMBOO(ServerVersion.V1_14, "SUGAR_CANE", "SUGAR_CANE_BLOCK"),
BAMBOO_SAPLING(ServerVersion.V1_14, "SUGAR_CANE"),
BARREL(ServerVersion.V1_14, "TRAPPED_CHEST"),
BARRIER(ServerVersion.V1_8, "STAINED_GLASS", (byte) 14), // plain glass would make more sense if this were to be a block..
BEETROOT(ServerVersion.V1_9, "RAW_BEEF"),
BEETROOT_SEEDS(ServerVersion.V1_9, "SEEDS"),
BEETROOT_SOUP(ServerVersion.V1_9, "MUSHROOM_SOUP"),
BEETROOTS(ServerVersion.V1_9, "CROPS"),
BELL(ServerVersion.V1_14, "GOLD_BLOCK", "GOLD_BLOCK"),
BIRCH_BOAT(ServerVersion.V1_9, "BOAT"),
BIRCH_BUTTON(ServerVersion.V1_13, "WOOD_BUTTON"),
BIRCH_DOOR(ServerVersion.V1_8, "WOOD_DOOR"),
BIRCH_FENCE(ServerVersion.V1_8, "FENCE"),
BIRCH_FENCE_GATE(ServerVersion.V1_8, "FENCE_GATE"),
BIRCH_PRESSURE_PLATE(ServerVersion.V1_13, "WOOD_PLATE"),
BIRCH_SIGN(ServerVersion.V1_14, "SIGN", "SIGN"),
BIRCH_STAIRS(ServerVersion.V1_13, "WOOD_STAIRS"),
BIRCH_TRAPDOOR(ServerVersion.V1_13, "TRAP_DOOR"),
BIRCH_WALL_SIGN(ServerVersion.V1_14, "WALL_SIGN"),
BLACK_BANNER(ServerVersion.V1_8, "SIGN"),
BLACK_BED(ServerVersion.V1_12, "BED"),
BLACK_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 15),
BLACK_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 15),
BLACK_DYE(ServerVersion.V1_14, "INK_SAC", "INK_SACK", (byte) 0),
BLACK_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 15),
BLACK_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
BLACK_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
BLAST_FURNACE(ServerVersion.V1_14, "FURNACE"),
BLUE_BANNER(ServerVersion.V1_8, "SIGN"),
BLUE_BED(ServerVersion.V1_12, "BED"),
BLUE_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 11),
BLUE_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 11),
BLUE_DYE(ServerVersion.V1_14, "LAPIS_LAZULI", "INK_SACK", (byte) 4),
BLUE_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 11),
BLUE_ICE(ServerVersion.V1_13, "PACKED_ICE"),
BLUE_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
BLUE_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
BONE_BLOCK(ServerVersion.V1_13, "QUARTZ_BLOCK"),
BRAIN_CORAL(ServerVersion.V1_13, "WOOL", (byte) 6),
BRAIN_CORAL_BLOCK(ServerVersion.V1_13, "WOOL", (byte) 6),
BRAIN_CORAL_FAN(ServerVersion.V1_13, "WOOL", (byte) 6),
BRAIN_CORAL_WALL_FAN(ServerVersion.V1_13, "WOOL", (byte) 6),
BRICK_WALL(ServerVersion.V1_14, "COBBLESTONE_WALL", "COBBLE_WALL"),
BROWN_BANNER(ServerVersion.V1_8, "SIGN"),
BROWN_BED(ServerVersion.V1_12, "BED"),
BROWN_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 12),
BROWN_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 12),
BROWN_DYE(ServerVersion.V1_14, "COCOA_BEANS", "INK_SACK", (byte) 3),
BROWN_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 12),
BROWN_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
BROWN_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
BUBBLE_COLUMN(ServerVersion.V1_13, "WATER"),
BUBBLE_CORAL(ServerVersion.V1_13, "WOOL", (byte) 2),
BUBBLE_CORAL_BLOCK(ServerVersion.V1_13, "WOOL", (byte) 2),
BUBBLE_CORAL_FAN(ServerVersion.V1_13, "WOOL", (byte) 2),
BUBBLE_CORAL_WALL_FAN(ServerVersion.V1_13, "WOOL", (byte) 2),
CAMPFIRE(ServerVersion.V1_14, "FURNACE", "BURNING_FURNACE"),
CARTOGRAPHY_TABLE(ServerVersion.V1_14, "BOOKSHELF"),
CAT_SPAWN_EGG(ServerVersion.V1_14, "OCELOT_SPAWN_EGG", "MONSTER_EGG", (byte) 98),
CAVE_AIR(ServerVersion.V1_13, "AIR"),
CHAIN_COMMAND_BLOCK(ServerVersion.V1_9, "COMMAND"),
CHISELED_RED_SANDSTONE(ServerVersion.V1_8, "SANDSTONE", (byte) 1),
CHORUS_FLOWER(ServerVersion.V1_9, "WOOL", (byte) 2),
CHORUS_FRUIT(ServerVersion.V1_9, "APPLE"),
CHORUS_PLANT(ServerVersion.V1_9, "WOOL", (byte) 10),
COARSE_DIRT(ServerVersion.V1_8, "DIRT"),
COD_BUCKET(ServerVersion.V1_13, "WATER_BUCKET"),
COD_SPAWN_EGG(ServerVersion.V1_13, "MONSTER_EGG", (byte) 60),
COMPOSTER(ServerVersion.V1_14, "FLOWER_POT", "FLOWER_POT_ITEM"),
CONDUIT(ServerVersion.V1_13, "MELON"),
COOKED_MUTTON(ServerVersion.V1_8, "COOKED_BEEF"),
COOKED_RABBIT(ServerVersion.V1_8, "COOKED_BEEF"),
CORNFLOWER(ServerVersion.V1_14, "BLUE_ORCHID", "RED_ROSE", (byte) 1),
CREEPER_BANNER_PATTERN(ServerVersion.V1_14, "PAPER"),
CROSSBOW(ServerVersion.V1_14, "BOW"),
CUT_RED_SANDSTONE(ServerVersion.V1_13, "RED_SANDSTONE", (byte) 2, ServerVersion.V1_8, "SANDSTONE", (byte) 0),
CUT_RED_SANDSTONE_SLAB(ServerVersion.V1_14, "STONE_SLAB", "STEP", (byte) 0),
CUT_SANDSTONE(ServerVersion.V1_13, "SANDSTONE", (byte) 2),
CUT_SANDSTONE_SLAB(ServerVersion.V1_14, "STONE_SLAB", "STEP", (byte) 0),
CYAN_BANNER(ServerVersion.V1_8, "SIGN"),
CYAN_BED(ServerVersion.V1_12, "BED"),
CYAN_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 9),
CYAN_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 9),
CYAN_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 9),
CYAN_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
CYAN_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
DARK_OAK_BOAT(ServerVersion.V1_9, "BOAT"),
DARK_OAK_BUTTON(ServerVersion.V1_13, "WOOD_BUTTON"),
DARK_OAK_DOOR(ServerVersion.V1_8, "WOOD_DOOR"),
DARK_OAK_FENCE(ServerVersion.V1_8, "FENCE"),
DARK_OAK_FENCE_GATE(ServerVersion.V1_8, "FENCE_GATE"),
DARK_OAK_PRESSURE_PLATE(ServerVersion.V1_13, "WOOD_PLATE"),
DARK_OAK_SIGN(ServerVersion.V1_14, "SIGN"),
DARK_OAK_TRAPDOOR(ServerVersion.V1_13, "TRAP_DOOR"),
DARK_OAK_WALL_SIGN(ServerVersion.V1_14, "WALL_SIGN"),
DARK_PRISMARINE(ServerVersion.V1_8, "WOOL", (byte) 7),
DARK_PRISMARINE_SLAB(ServerVersion.V1_13, "STEP", (byte) 0),
DARK_PRISMARINE_STAIRS(ServerVersion.V1_13, "NETHER_BRICK_STAIRS"),
DEAD_BRAIN_CORAL(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_BRAIN_CORAL_BLOCK(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_BRAIN_CORAL_FAN(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_BRAIN_CORAL_WALL_FAN(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_BUBBLE_CORAL(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_BUBBLE_CORAL_BLOCK(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_BUBBLE_CORAL_FAN(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_BUBBLE_CORAL_WALL_FAN(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_FIRE_CORAL(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_FIRE_CORAL_BLOCK(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_FIRE_CORAL_FAN(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_FIRE_CORAL_WALL_FAN(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_HORN_CORAL(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_HORN_CORAL_BLOCK(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_HORN_CORAL_FAN(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_HORN_CORAL_WALL_FAN(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_TUBE_CORAL(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_TUBE_CORAL_BLOCK(ServerVersion.V1_13, "COBBLESTONE"),
DEAD_TUBE_CORAL_FAN(ServerVersion.V1_13, "COBBLESTONE"), // these could also be a dead_bush ?
DEAD_TUBE_CORAL_WALL_FAN(ServerVersion.V1_13, "COBBLESTONE"),
DEBUG_STICK(ServerVersion.V1_13, "STICK"),
DIORITE(ServerVersion.V1_8, "STONE"),
DIORITE_SLAB(ServerVersion.V1_14, "STONE_SLAB", "STEP", (byte) 0),
DIORITE_STAIRS(ServerVersion.V1_14, "STONE_BRICK_STAIRS", "SMOOTH_STAIRS"),
DIORITE_WALL(ServerVersion.V1_14, "COBBLESTONE_WALL", "COBBLE_WALL"),
DOLPHIN_SPAWN_EGG(ServerVersion.V1_13, "MONSTER_EGG", (byte) 0),
DONKEY_SPAWN_EGG(ServerVersion.V1_11, "MONSTER_EGG", (byte) 100),
DRAGON_BREATH(ServerVersion.V1_9, "POTION", (byte) 0), // or maybe glowstone..
DRAGON_HEAD(ServerVersion.V1_9, "SKULL_ITEM", (byte) 4),
DRAGON_WALL_HEAD(ServerVersion.V1_9, "SKULL", (byte) 4),
DRIED_KELP(ServerVersion.V1_13, "POTATO_ITEM"),
DRIED_KELP_BLOCK(ServerVersion.V1_13, "HAY_BLOCK"),
DROWNED_SPAWN_EGG(ServerVersion.V1_13, "MONSTER_EGG", (byte) 54),
ELDER_GUARDIAN_SPAWN_EGG(ServerVersion.V1_11, "MONSTER_EGG", (byte) 51),
ELYTRA(ServerVersion.V1_9, "IRON_CHESTPLATE"),
END_CRYSTAL(ServerVersion.V1_9, "STAINED_GLASS", (byte) 0),
END_GATEWAY(ServerVersion.V1_9, "BEACON"),
END_PORTAL(ServerVersion.V1_9, "PORTAL"),
END_PORTAL_FRAME(ServerVersion.V1_9, "SANDSTONE", (byte) 1),
END_ROD(ServerVersion.V1_9, "STAINED_GLASS_PANE", (byte) 0),
END_STONE(ServerVersion.V1_9, "SANDSTONE", (byte) 0),
END_STONE_BRICK_SLAB(ServerVersion.V1_14, "STONE_SLAB", "STEP", (byte) 0),
END_STONE_BRICK_STAIRS(ServerVersion.V1_14, "SANDSTONE_STAIRS"),
END_STONE_BRICK_WALL(ServerVersion.V1_14, "COBBLESTONE_WALL", "COBBLE_WALL"),
END_STONE_BRICKS(ServerVersion.V1_9, "SANDSTONE", (byte) 2),
ENDERMITE_SPAWN_EGG(ServerVersion.V1_8, "MONSTER_EGG", (byte) 60),
EVOKER_SPAWN_EGG(ServerVersion.V1_11, "MONSTER_EGG", (byte) 51),
FILLED_MAP(ServerVersion.V1_13, "EMPTY_MAP"),
FIRE_CORAL(ServerVersion.V1_13, "WOOL", (byte) 14),
FIRE_CORAL_BLOCK(ServerVersion.V1_13, "WOOL", (byte) 14),
FIRE_CORAL_FAN(ServerVersion.V1_13, "WOOL", (byte) 14),
FIRE_CORAL_WALL_FAN(ServerVersion.V1_13, "WOOL", (byte) 14),
FLETCHING_TABLE(ServerVersion.V1_14, "CRAFTING_TABLE", "WORKBENCH"),
FLOWER_BANNER_PATTERN(ServerVersion.V1_14, "PAPER"),
FOX_SPAWN_EGG(ServerVersion.V1_14, "OCELOT_SPAWN_EGG", "MONSTER_EGG", (byte) 98),
FROSTED_ICE(ServerVersion.V1_13, "ICE"),
GLOBE_BANNER_PATTERN(ServerVersion.V1_14, "PAPER"),
GRANITE(ServerVersion.V1_8, "STONE"),
GRANITE_SLAB(ServerVersion.V1_14, "STONE_SLAB", "STEP", (byte) 0),
GRANITE_STAIRS(ServerVersion.V1_14, "STONE_BRICK_STAIRS", "SMOOTH_STAIRS"),
GRANITE_WALL(ServerVersion.V1_14, "COBBLESTONE_WALL", "COBBLE_WALL"),
GRASS_PATH(ServerVersion.V1_9, "DIRT", (byte) 1, ServerVersion.V1_8, "DIRT"),
GRAY_BANNER(ServerVersion.V1_8, "SIGN"),
GRAY_BED(ServerVersion.V1_12, "BED"),
GRAY_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 7),
GRAY_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 7),
GRAY_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 7),
GRAY_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
GRAY_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
GREEN_BANNER(ServerVersion.V1_8, "SIGN"),
GREEN_BED(ServerVersion.V1_12, "BED"),
GREEN_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 13),
GREEN_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 13),
GREEN_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 13),
GREEN_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
GREEN_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
GRINDSTONE(ServerVersion.V1_14, "ANVIL"),
GUARDIAN_SPAWN_EGG(ServerVersion.V1_8, "MONSTER_EGG", (byte) 51),
HEART_OF_THE_SEA(ServerVersion.V1_13, "DIAMOND"),
HORN_CORAL(ServerVersion.V1_13, "WOOL", (byte) 4),
HORN_CORAL_BLOCK(ServerVersion.V1_13, "WOOL", (byte) 4),
HORN_CORAL_FAN(ServerVersion.V1_13, "WOOL", (byte) 4),
HORN_CORAL_WALL_FAN(ServerVersion.V1_13, "WOOL", (byte) 4),
HUSK_SPAWN_EGG(ServerVersion.V1_11, "MONSTER_EGG", (byte) 54),
IRON_NUGGET(ServerVersion.V1_11, "IRON_INGOT"),
IRON_TRAPDOOR(ServerVersion.V1_8, "TRAP_DOOR"),
JIGSAW(ServerVersion.V1_14, "ANVIL"),
JUNGLE_BOAT(ServerVersion.V1_9, "BOAT"),
JUNGLE_BUTTON(ServerVersion.V1_13, "WOOD_BUTTON"),
JUNGLE_DOOR(ServerVersion.V1_8, "WOOD_DOOR"),
JUNGLE_FENCE(ServerVersion.V1_8, "FENCE"),
JUNGLE_FENCE_GATE(ServerVersion.V1_8, "FENCE_GATE"),
JUNGLE_PRESSURE_PLATE(ServerVersion.V1_13, "WOOD_PLATE"),
JUNGLE_SIGN(ServerVersion.V1_14, "SIGN"),
JUNGLE_TRAPDOOR(ServerVersion.V1_13, "TRAP_DOOR"),
JUNGLE_WALL_SIGN(ServerVersion.V1_14, "WALL_SIGN"),
KELP(ServerVersion.V1_13, "POTATO_ITEM"),
KELP_PLANT(ServerVersion.V1_13, "WATER"), // idk.
KNOWLEDGE_BOOK(ServerVersion.V1_12, "BOOK"),
LANTERN(ServerVersion.V1_14, "GLOWSTONE"),
LEATHER_HORSE_ARMOR(ServerVersion.V1_14, "IRON_BARDING"),
LECTERN(ServerVersion.V1_14, "BOOKSHELF"),
LIGHT_BLUE_BANNER(ServerVersion.V1_8, "SIGN"),
LIGHT_BLUE_BED(ServerVersion.V1_12, "BED"),
LIGHT_BLUE_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 3),
LIGHT_BLUE_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 3),
LIGHT_BLUE_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 3),
LIGHT_BLUE_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
LIGHT_BLUE_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
LIGHT_GRAY_BANNER(ServerVersion.V1_8, "SIGN"),
LIGHT_GRAY_BED(ServerVersion.V1_12, "BED"),
LIGHT_GRAY_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 8),
LIGHT_GRAY_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 8),
LIGHT_GRAY_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 8),
LIGHT_GRAY_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
LIGHT_GRAY_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
LILY_OF_THE_VALLEY(ServerVersion.V1_14, "AZURE_BLUET", "RED_ROSE", (byte) 3),
LIME_BANNER(ServerVersion.V1_8, "SIGN"),
LIME_BED(ServerVersion.V1_12, "BED"),
LIME_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 5),
LIME_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 5),
LIME_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 5),
LIME_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
LIME_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
LINGERING_POTION(ServerVersion.V1_9, "POTION", (byte) 0),
LLAMA_SPAWN_EGG(ServerVersion.V1_11, "MONSTER_EGG", (byte) 0),
LOOM(ServerVersion.V1_14, "CRAFTING_TABLE", "WORKBENCH"),
MAGENTA_BANNER(ServerVersion.V1_8, "SIGN"),
MAGENTA_BED(ServerVersion.V1_12, "BED"),
MAGENTA_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 2),
MAGENTA_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 2),
MAGENTA_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 2),
MAGENTA_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
MAGENTA_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
MAGMA_BLOCK(ServerVersion.V1_10, "NETHER_BRICK"),
MOJANG_BANNER_PATTERN(ServerVersion.V1_14, "PAPER"),
MOSSY_COBBLESTONE_SLAB(ServerVersion.V1_14, "COBBLESTONE_SLAB", "STEP", (byte) 3),
MOSSY_COBBLESTONE_STAIRS(ServerVersion.V1_14, "COBBLESTONE_STAIRS"),
MOSSY_COBBLESTONE_WALL(ServerVersion.V1_14, "COBBLESTONE_WALL", "COBBLE_WALL"),
MOSSY_STONE_BRICK_SLAB(ServerVersion.V1_14, "BRICK_SLAB", "STEP", (byte) 4),
MOSSY_STONE_BRICK_STAIRS(ServerVersion.V1_14, "STONE_BRICK_STAIRS", "SMOOTH_STAIRS"),
MOSSY_STONE_BRICK_WALL(ServerVersion.V1_14, "COBBLESTONE_WALL", "COBBLE_WALL"),
MULE_SPAWN_EGG(ServerVersion.V1_11, "MONSTER_EGG", (byte) 100),
MUTTON(ServerVersion.V1_8, "RAW_BEEF"),
NAUTILUS_SHELL(ServerVersion.V1_13, "SNOW_BALL"),
NETHER_BRICK_FENCE(ServerVersion.V1_13, "FENCE"),
NETHER_BRICK_WALL(ServerVersion.V1_14, "COBBLESTONE_WALL", "COBBLE_WALL"),
NETHER_WART_BLOCK(ServerVersion.V1_10, "NETHERRACK"),
OAK_BOAT(ServerVersion.V1_9, "BOAT"),
OBSERVER(ServerVersion.V1_11, "DISPENSER"),
ORANGE_BANNER(ServerVersion.V1_8, "SIGN"),
ORANGE_BED(ServerVersion.V1_12, "BED"),
ORANGE_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 1),
ORANGE_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 1),
ORANGE_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 1),
ORANGE_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
ORANGE_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
PANDA_SPAWN_EGG(ServerVersion.V1_14, "COW_SPAWN_EGG", "MONSTER_EGG", (byte) 92),
PARROT_SPAWN_EGG(ServerVersion.V1_12, "MONSTER_EGG", (byte) 65),
PHANTOM_MEMBRANE(ServerVersion.V1_13, "FEATHER"),
PHANTOM_SPAWN_EGG(ServerVersion.V1_13, "MONSTER_EGG", (byte) 65),
PILLAGER_SPAWN_EGG(ServerVersion.V1_14, "VILLAGER_SPAWN_EGG", "MONSTER_EGG", (byte) 120),
PINK_BANNER(ServerVersion.V1_8, "SIGN"),
PINK_BED(ServerVersion.V1_12, "BED"),
PINK_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 6),
PINK_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 6),
PINK_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 6),
PINK_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
PINK_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
PODZOL(ServerVersion.V1_8, "DIRT"),
POLAR_BEAR_SPAWN_EGG(ServerVersion.V1_10, "MONSTER_EGG", (byte) 0),
POLISHED_ANDESITE(ServerVersion.V1_8, "STONE"),
POLISHED_ANDESITE_SLAB(ServerVersion.V1_14, "STONE_SLAB", "STEP", (byte) 0),
POLISHED_ANDESITE_STAIRS(ServerVersion.V1_14, "STONE_BRICK_STAIRS", "SMOOTH_STAIRS"),
POLISHED_DIORITE(ServerVersion.V1_8, "STONE"),
POLISHED_DIORITE_SLAB(ServerVersion.V1_14, "STONE_SLAB", "STEP", (byte) 0),
POLISHED_DIORITE_STAIRS(ServerVersion.V1_14, "STONE_BRICK_STAIRS", "SMOOTH_STAIRS"),
POLISHED_GRANITE(ServerVersion.V1_8, "STONE"),
POLISHED_GRANITE_SLAB(ServerVersion.V1_14, "STONE_SLAB", "STEP", (byte) 0),
POLISHED_GRANITE_STAIRS(ServerVersion.V1_14, "STONE_BRICK_STAIRS", "SMOOTH_STAIRS"),
POPPED_CHORUS_FRUIT(ServerVersion.V1_9, "GOLDEN_APPLE"),
POTTED_ACACIA_SAPLING(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_ALLIUM(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_AZURE_BLUET(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_BAMBOO(ServerVersion.V1_14, "FLOWER_POT", "FLOWER_POT_ITEM"),
POTTED_BIRCH_SAPLING(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_BLUE_ORCHID(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_BROWN_MUSHROOM(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_CACTUS(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_CORNFLOWER(ServerVersion.V1_14, "FLOWER_POT", "FLOWER_POT_ITEM"),
POTTED_DANDELION(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_DARK_OAK_SAPLING(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_DEAD_BUSH(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_FERN(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_JUNGLE_SAPLING(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_LILY_OF_THE_VALLEY(ServerVersion.V1_14, "FLOWER_POT", "FLOWER_POT_ITEM"),
POTTED_OAK_SAPLING(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_ORANGE_TULIP(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_OXEYE_DAISY(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_PINK_TULIP(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_POPPY(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_RED_MUSHROOM(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_RED_TULIP(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_SPRUCE_SAPLING(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_WHITE_TULIP(ServerVersion.V1_13, "FLOWER_POT_ITEM"),
POTTED_WITHER_ROSE(ServerVersion.V1_14, "FLOWER_POT", "FLOWER_POT_ITEM"),
PRISMARINE(ServerVersion.V1_8, "WOOL", (byte) 9),
PRISMARINE_BRICK_SLAB(ServerVersion.V1_14, "STONE_SLAB", "STEP", (byte) 0),
PRISMARINE_BRICK_STAIRS(ServerVersion.V1_13, "SMOOTH_STAIRS"),
PRISMARINE_BRICKS(ServerVersion.V1_8, "WOOL", (byte) 9),
PRISMARINE_CRYSTALS(ServerVersion.V1_8, "CLAY_BALL"),
PRISMARINE_SHARD(ServerVersion.V1_8, "FLINT"),
PRISMARINE_SLAB(ServerVersion.V1_13, "STEP", (byte) 0),
PRISMARINE_STAIRS(ServerVersion.V1_13, "COBBLESTONE_STAIRS"),
PRISMARINE_WALL(ServerVersion.V1_14, "COBBLESTONE_WALL", "COBBLE_WALL"),
PUFFERFISH_BUCKET(ServerVersion.V1_13, "WATER_BUCKET"),
PUFFERFISH_SPAWN_EGG(ServerVersion.V1_13, "MONSTER_EGG", (byte) 60),
PURPLE_BANNER(ServerVersion.V1_8, "SIGN"),
PURPLE_BED(ServerVersion.V1_12, "BED"),
PURPLE_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 10),
PURPLE_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 10),
PURPLE_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 10),
PURPLE_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
PURPLE_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
PURPUR_BLOCK(ServerVersion.V1_9, "WOOL", (byte) 2),
PURPUR_PILLAR(ServerVersion.V1_9, "WOOL", (byte) 2),
PURPUR_SLAB(ServerVersion.V1_9, "STEP", (byte) 0),
PURPUR_STAIRS(ServerVersion.V1_9, "BRICK_STAIRS"),
RABBIT(ServerVersion.V1_8, "RAW_BEEF"),
RABBIT_FOOT(ServerVersion.V1_8, "ROTTEN_FLESH"),
RABBIT_HIDE(ServerVersion.V1_8, "ROTTEN_FLESH"),
RABBIT_SPAWN_EGG(ServerVersion.V1_8, "MONSTER_EGG", (byte) 0),
RABBIT_STEW(ServerVersion.V1_8, "MUSHROOM_SOUP"),
RAVAGER_SPAWN_EGG(ServerVersion.V1_14, "COW_SPAWN_EGG", "MONSTER_EGG", (byte) 92),
RED_BANNER(ServerVersion.V1_8, "SIGN"),
RED_BED(ServerVersion.V1_12, "BED"),
RED_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 14),
RED_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 14),
RED_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 14),
RED_NETHER_BRICK_SLAB(ServerVersion.V1_14, "STONE_SLAB", "STEP", (byte) 0),
RED_NETHER_BRICK_STAIRS(ServerVersion.V1_14, "NETHER_BRICK_STAIRS"),
RED_NETHER_BRICK_WALL(ServerVersion.V1_14, "COBBLESTONE_WALL", "COBBLE_WALL"),
RED_NETHER_BRICKS(ServerVersion.V1_10, "NETHER_BRICK"),
RED_SANDSTONE(ServerVersion.V1_8, "SANDSTONE", (byte) 0),
RED_SANDSTONE_SLAB(ServerVersion.V1_8, "STEP", (byte) 0),
RED_SANDSTONE_STAIRS(ServerVersion.V1_8, "SANDSTONE_STAIRS"),
RED_SANDSTONE_WALL(ServerVersion.V1_14, "COBBLESTONE_WALL", "COBBLE_WALL"),
RED_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
RED_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
REDSTONE_WALL_TORCH(ServerVersion.V1_13, "REDSTONE_TORCH_ON"),
REPEATING_COMMAND_BLOCK(ServerVersion.V1_9, "COMMAND"),
SALMON_BUCKET(ServerVersion.V1_13, "WATER_BUCKET"),
SALMON_SPAWN_EGG(ServerVersion.V1_13, "MONSTER_EGG", (byte) 60),
SANDSTONE_WALL(ServerVersion.V1_14, "COBBLESTONE_WALL", "COBBLE_WALL"),
SCAFFOLDING(ServerVersion.V1_14, "LADDER"),
SCUTE(ServerVersion.V1_13, "SLIME_BALL"),
SEA_LANTERN(ServerVersion.V1_8, "GLOWSTONE"),
SEA_PICKLE(ServerVersion.V1_13, "CACTUS"),
SEAGRASS(ServerVersion.V1_13, "LONG_GRASS", (byte) 1),
SHIELD(ServerVersion.V1_9, "BANNER", (byte) 3, ServerVersion.V1_8, "WOOD_DOOR"),
SHULKER_BOX(ServerVersion.V1_13, "ENDER_CHEST"),
SHULKER_SHELL(ServerVersion.V1_11, "SUGAR"),
SHULKER_SPAWN_EGG(ServerVersion.V1_9, "MONSTER_EGG", (byte) 0),
SKELETON_HORSE_SPAWN_EGG(ServerVersion.V1_11, "MONSTER_EGG", (byte) 0),
SKULL_BANNER_PATTERN(ServerVersion.V1_14, "PAPER"),
SLIME_BLOCK(ServerVersion.V1_8, "WOOL", (byte) 5),
SMITHING_TABLE(ServerVersion.V1_14, "CRAFTING_TABLE", "WORKBENCH"),
SMOKER(ServerVersion.V1_14, "FURNACE"),
SMOOTH_QUARTZ(ServerVersion.V1_13, "QUARTZ_BLOCK", (byte) 0),
SMOOTH_QUARTZ_SLAB(ServerVersion.V1_14, "QUARTZ_SLAB", "STEP", (byte) 7),
SMOOTH_QUARTZ_STAIRS(ServerVersion.V1_14, "QUARTZ_STAIRS"),
SMOOTH_RED_SANDSTONE(ServerVersion.V1_13, "RED_SANDSTONE", (byte) 0, ServerVersion.V1_8, "SANDSTONE", (byte) 0),
SMOOTH_RED_SANDSTONE_SLAB(ServerVersion.V1_14, "SANDSTONE_SLAB", "STEP", (byte) 1),
SMOOTH_RED_SANDSTONE_STAIRS(ServerVersion.V1_14, "RED_SANDSTONE_STAIRS", ServerVersion.V1_8, "SANDSTONE_STAIRS"),
SMOOTH_SANDSTONE_SLAB(ServerVersion.V1_14, "SANDSTONE_SLAB", "STEP", (byte) 1),
SMOOTH_SANDSTONE_STAIRS(ServerVersion.V1_14, "SANDSTONE_STAIRS"),
SMOOTH_STONE(ServerVersion.V1_13, "STONE"), // DOUBLE_STEP is a closer texture match
SMOOTH_STONE_SLAB(ServerVersion.V1_14, "STONE_SLAB", "STEP", (byte) 0),
SPECTRAL_ARROW(ServerVersion.V1_9, "ARROW"),
SPRUCE_BOAT(ServerVersion.V1_9, "BOAT"),
SPRUCE_BUTTON(ServerVersion.V1_13, "WOOD_BUTTON"),
SPRUCE_DOOR(ServerVersion.V1_8, "WOOD_DOOR"),
SPRUCE_FENCE(ServerVersion.V1_8, "FENCE"),
SPRUCE_FENCE_GATE(ServerVersion.V1_8, "FENCE_GATE"),
SPRUCE_PRESSURE_PLATE(ServerVersion.V1_13, "WOOD_PLATE"),
SPRUCE_SIGN(ServerVersion.V1_14, "SIGN"),
SPRUCE_TRAPDOOR(ServerVersion.V1_13, "TRAP_DOOR"),
SPRUCE_WALL_SIGN(ServerVersion.V1_14, "WALL_SIGN"),
//STONE(ServerVersion.V1_8, "STONE"), // funny how that happened, heh. Non-data to data to non-data again
STONE_BRICK_WALL(ServerVersion.V1_14, "COBBLESTONE_WALL", "COBBLE_WALL"),
STONE_SLAB(ServerVersion.V1_13, "STEP", (byte) 0),
STONE_STAIRS(ServerVersion.V1_14, "STONE_BRICK_STAIRS", "SMOOTH_STAIRS"),
STONECUTTER(ServerVersion.V1_14, "ANVIL"),
STRAY_SPAWN_EGG(ServerVersion.V1_11, "MONSTER_EGG", (byte) 0),
STRIPPED_ACACIA_LOG(ServerVersion.V1_13, "LOG_2", (byte) 0),
STRIPPED_ACACIA_WOOD(ServerVersion.V1_13, "LOG_2", (byte) 12),
STRIPPED_BIRCH_LOG(ServerVersion.V1_13, "LOG", (byte) 2),
STRIPPED_BIRCH_WOOD(ServerVersion.V1_13, "LOG", (byte) 14),
STRIPPED_DARK_OAK_LOG(ServerVersion.V1_13, "LOG_2", (byte) 1),
STRIPPED_DARK_OAK_WOOD(ServerVersion.V1_13, "LOG_2", (byte) 13),
STRIPPED_JUNGLE_LOG(ServerVersion.V1_13, "LOG", (byte) 3),
STRIPPED_JUNGLE_WOOD(ServerVersion.V1_13, "LOG", (byte) 15),
STRIPPED_OAK_LOG(ServerVersion.V1_13, "LOG", (byte) 0),
STRIPPED_OAK_WOOD(ServerVersion.V1_13, "LOG", (byte) 12),
STRIPPED_SPRUCE_LOG(ServerVersion.V1_13, "LOG", (byte) 1),
STRIPPED_SPRUCE_WOOD(ServerVersion.V1_13, "LOG", (byte) 13),
STRUCTURE_BLOCK(ServerVersion.V1_9, "COMMAND"),
STRUCTURE_VOID(ServerVersion.V1_10, "BARRIER", ServerVersion.V1_8, "STAINED_GLASS", (byte) 14), // Block would be air, but these make more sense as an item
SUSPICIOUS_STEW(ServerVersion.V1_14, "MUSHROOM_SOUP"),
SWEET_BERRIES(ServerVersion.V1_14, "POTATO", "POTATO_ITEM"),
SWEET_BERRY_BUSH(ServerVersion.V1_14, "OAK_LEAVES", "LEAVES", (byte) 3),
TALL_SEAGRASS(ServerVersion.V1_13, "LONG_GRASS", (byte) 1),
TIPPED_ARROW(ServerVersion.V1_9, "ARROW"),
TOTEM_OF_UNDYING(ServerVersion.V1_11, "CHAINMAIL_CHESTPLATE"),
TRADER_LLAMA_SPAWN_EGG(ServerVersion.V1_14, "LLAMA_SPAWN_EGG", "MONSTER_EGG", (byte) 103), // todo? should we change the item here? llamas are v1.11+
TRIDENT(ServerVersion.V1_13, "ARROW"),
TROPICAL_FISH_BUCKET(ServerVersion.V1_13, "WATER_BUCKET"),
TROPICAL_FISH_SPAWN_EGG(ServerVersion.V1_13, "MONSTER_EGG", (byte) 60),
TUBE_CORAL(ServerVersion.V1_13, "WOOL", (byte) 11),
TUBE_CORAL_BLOCK(ServerVersion.V1_13, "WOOL", (byte) 11),
TUBE_CORAL_FAN(ServerVersion.V1_13, "WOOL", (byte) 11),
TUBE_CORAL_WALL_FAN(ServerVersion.V1_13, "WOOL", (byte) 11),
TURTLE_EGG(ServerVersion.V1_13, "DRAGON_EGG"),
TURTLE_HELMET(ServerVersion.V1_13, "LEATHER_HELMET"), // would be cool to color it green..
TURTLE_SPAWN_EGG(ServerVersion.V1_13, "MONSTER_EGG", (byte) 60),
VEX_SPAWN_EGG(ServerVersion.V1_11, "MONSTER_EGG", (byte) 0),
VINDICATOR_SPAWN_EGG(ServerVersion.V1_11, "MONSTER_EGG", (byte) 0),
VOID_AIR(ServerVersion.V1_13, "AIR"),
WALL_TORCH(ServerVersion.V1_13, "TORCH"),
WANDERING_TRADER_SPAWN_EGG(ServerVersion.V1_14, "VILLAGER_SPAWN_EGG", "MONSTER_EGG", (byte) 120),
WHITE_BANNER(ServerVersion.V1_8, "SIGN"),
WHITE_BED(ServerVersion.V1_12, "BED"),
WHITE_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 0),
WHITE_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 0),
WHITE_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 0),
WHITE_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
WHITE_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
WITHER_ROSE(ServerVersion.V1_14, "POPPY", "RED_ROSE", (byte) 0),
WITHER_SKELETON_SPAWN_EGG(ServerVersion.V1_11, "MONSTER_EGG", (byte) 0),
YELLOW_BANNER(ServerVersion.V1_8, "SIGN"),
YELLOW_BED(ServerVersion.V1_12, "BED"),
YELLOW_CONCRETE(ServerVersion.V1_12, "STAINED_CLAY", (byte) 4),
YELLOW_CONCRETE_POWDER(ServerVersion.V1_12, "STAINED_CLAY", (byte) 4),
YELLOW_GLAZED_TERRACOTTA(ServerVersion.V1_12, "STAINED_CLAY", (byte) 4),
YELLOW_SHULKER_BOX(ServerVersion.V1_11, "ENDER_CHEST"),
YELLOW_WALL_BANNER(ServerVersion.V1_8, "WALL_SIGN"),
ZOMBIE_HORSE_SPAWN_EGG(ServerVersion.V1_11, "MONSTER_EGG", (byte) 0),
ZOMBIE_VILLAGER_SPAWN_EGG(ServerVersion.V1_11, "MONSTER_EGG", (byte) 0),
;
final ServerVersion versionLessThan;
final String modernMaterial;
final String legacyMaterial;
final Byte legacyData;
final ServerVersion legacyMinimumVersion;
final String compatibleMaterial;
final Byte compatibleData;
final Material material;
final Byte data;
// map to speed up name->material lookups
private static final Map<String, LegacyAnalouges> lookupMap = new HashMap();
static {
for (LegacyAnalouges m : values()) {
lookupMap.put(m.name(), m);
}
}
public static LegacyAnalouges lookupAnalouge(String material) {
return lookupMap.get(material);
}
private LegacyAnalouges(ServerVersion versionLessThan, String legacyMaterial, byte legacyData) {
this(versionLessThan, null, legacyMaterial, legacyData, null, null, null);
}
private LegacyAnalouges(ServerVersion versionLessThan, String legacyMaterial) {
this(versionLessThan, null, legacyMaterial, null, null, null, null);
}
private LegacyAnalouges(ServerVersion versionLessThan, String modernAnalouge, String legacyMaterial) {
this(versionLessThan, modernAnalouge, legacyMaterial, null, null, null, null);
}
private LegacyAnalouges(ServerVersion versionLessThan, String modernAnalouge, String legacyMaterial, byte legacyData) {
this(versionLessThan, modernAnalouge, legacyMaterial, legacyData, null, null, null);
}
private LegacyAnalouges(ServerVersion versionLessThan, String legacyMaterial, byte legacyData, ServerVersion legacyMinimum, String compatMaterial, byte compatData) {
this(versionLessThan, null, legacyMaterial, legacyData, legacyMinimum, compatMaterial, compatData);
}
private LegacyAnalouges(ServerVersion versionLessThan, String legacyMaterial, ServerVersion legacyMinimum, String compatMaterial, byte compatData) {
this(versionLessThan, null, legacyMaterial, null, legacyMinimum, compatMaterial, compatData);
}
private LegacyAnalouges(ServerVersion versionLessThan, String legacyMaterial, byte legacyData, ServerVersion legacyMinimum, String compatMaterial) {
this(versionLessThan, null, legacyMaterial, legacyData, legacyMinimum, compatMaterial, null);
}
private LegacyAnalouges(ServerVersion versionLessThan, String legacyMaterial, ServerVersion legacyMinimum, String compatMaterial) {
this(versionLessThan, null, legacyMaterial, null, legacyMinimum, compatMaterial, null);
}
/**
*
* @param versionLessThan AKA, what server version was this material added to minecraft?
* @param modernAnalouge post-1.13 material name, if applicable
* @param legacyMaterial pre-1.13 material name
* @param legacyData data for defining specific legacy items
*/
private LegacyAnalouges(ServerVersion versionLessThan, String modernAnalouge, String legacyMaterial, Byte legacyData, ServerVersion legacyMinimum, String compatMaterial, Byte compatData) {
this.versionLessThan = versionLessThan;
this.modernMaterial = modernAnalouge;
this.legacyMaterial = legacyMaterial;
this.legacyData = legacyData;
this.legacyMinimumVersion = legacyMinimum;
this.compatibleMaterial = compatMaterial;
this.compatibleData = compatData;
if (ServerVersion.isServerVersionBelow(versionLessThan)) {
if(legacyMinimumVersion != null && ServerVersion.isServerVersionBelow(legacyMinimumVersion)) {
// fallback material not available, so use its fallback
material = Material.getMaterial(compatibleMaterial);
data = compatibleData;
} else if (modernMaterial == null || ServerVersion.isServerVersionBelow(ServerVersion.V1_13)) {
// use legacy material if on legacy
material = Material.getMaterial(legacyMaterial);
data = legacyData;
} else if (modernMaterial != null) {
material = Material.getMaterial(modernMaterial);
data = null;
} else {
material = null;
data = null;
}
} else {
material = null;
data = null;
}
}
public Material getMaterial() {
return material;
}
public boolean usesData() {
return data != null;
}
public byte getData() {
return data == null ? 0 : data;
}
public ItemStack getItem() {
if (material == null) {
return null;
}
return data != null ? new ItemStack(material, 1, data) : new ItemStack(material);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,254 @@
package com.songoda.core.compatibility;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.entity.Player;
/**
* Particle effects for servers 1.8 and below
* @since 2019-08-23
* @author jascotty2
*/
public class LegacyParticleEffects {
public static enum Type {
EXPLOSION_NORMAL("explode"),
EXPLOSION_LARGE("largeexplode"),
EXPLOSION_HUGE("hugeexplosion"),
FIREWORKS_SPARK("fireworksSpark"),
WATER_BUBBLE("bubble"),
WATER_SPLASH("splash"),
WATER_WAKE("wake", ServerVersion.V1_8),
SUSPENDED("suspended"),
SUSPENDED_DEPTH("depthsuspend"),
CRIT("crit"),
CRIT_MAGIC("magicCrit"),
SMOKE_NORMAL("smoke"),
SMOKE_LARGE("largesmoke"),
SPELL("spell"),
SPELL_INSTANT("instantSpell"),
SPELL_MOB("mobSpell"),
SPELL_MOB_AMBIENT("mobSpellAmbient"),
SPELL_WITCH("witchMagic"),
DRIP_WATER("dripWater"),
DRIP_LAVA("dripLava"),
VILLAGER_ANGRY("angryVillager"),
VILLAGER_HAPPY("happyVillager"),
TOWN_AURA("townaura"),
NOTE("note"),
PORTAL("portal"),
ENCHANTMENT_TABLE("enchantmenttable"),
FLAME("flame"),
LAVA("lava"),
FOOTSTEP("footstep"),
CLOUD("cloud"),
REDSTONE("reddust"),
SNOWBALL("snowballpoof"),
SNOW_SHOVEL("snowshovel"),
SLIME("slime"),
HEART("heart"),
BARRIER("barrier", ServerVersion.V1_8),
/**
* Used when a block is broken
*/
ITEM_CRACK("iconcrack_"),
BLOCK_CRACK("blockcrack_", ServerVersion.V1_8),
BLOCK_DUST("blockdust_", ServerVersion.V1_8),
WATER_DROP("droplet", ServerVersion.V1_8),
ITEM_TAKE("take", ServerVersion.V1_8),
MOB_APPEARANCE("mobappearance", ServerVersion.V1_8),
TOOL_BREAK("tilecrack_", ServerVersion.UNKNOWN, ServerVersion.V1_7);
public final String name;
public final ServerVersion minVersion;
public final ServerVersion maxVersion;
private Type(String name) {
this.name = name;
this.minVersion = ServerVersion.UNKNOWN;
this.maxVersion = null;
}
private Type(String name, ServerVersion minVersion) {
this.name = name;
this.minVersion = minVersion;
this.maxVersion = null;
}
private Type(String name, ServerVersion minVersion, ServerVersion maxVersion) {
this.name = name;
this.minVersion = minVersion;
this.maxVersion = maxVersion;
}
public static Type getById(String id) {
for (Type t : Type.values()) {
if (t.name.equalsIgnoreCase(id) || t.name().equalsIgnoreCase(id)) {
return t;
}
}
return null;
}
}
private static final String version = getNMSVersion();
private static boolean enabled = true;
private static Class mc_packetPlayOutWorldParticlesClazz;
private static Class cb_craftPlayerClazz;
private static Method cb_craftPlayer_getHandle;
private static Class mc_entityPlayerClazz;
private static Class mc_playerConnectionClazz;
private static Field mc_entityPlayer_playerConnection;
private static Class mc_PacketInterface;
private static Method mc_playerConnection_sendPacket;
private static Class mc_EnumParticle;
private static Method mc_EnumParticle_valueOf;
static {
try {
// lower versions use "Packet63WorldParticles"
if (!version.startsWith("v1_7") && !version.startsWith("v1_8")) {
mc_packetPlayOutWorldParticlesClazz = Class.forName("net.minecraft.server." + version + ".Packet63WorldParticles");
} else {
mc_packetPlayOutWorldParticlesClazz = Class.forName("net.minecraft.server." + version + ".PacketPlayOutWorldParticles");
}
cb_craftPlayerClazz = Class.forName("org.bukkit.craftbukkit." + version + ".entity.CraftPlayer");
cb_craftPlayer_getHandle = cb_craftPlayerClazz.getDeclaredMethod("getHandle");
mc_entityPlayerClazz = Class.forName("net.minecraft.server." + version + ".EntityPlayer");
mc_entityPlayer_playerConnection = mc_entityPlayerClazz.getDeclaredField("playerConnection");
mc_playerConnectionClazz = Class.forName("net.minecraft.server." + version + ".PlayerConnection");
mc_PacketInterface = Class.forName("net.minecraft.server." + version + ".Packet");
mc_playerConnection_sendPacket = mc_playerConnectionClazz.getDeclaredMethod("sendPacket", mc_PacketInterface);
if (version.startsWith("v1_8")) {
// Aren't worrying about anything after 1.8 in this class here
mc_EnumParticle = Class.forName("net.minecraft.server." + version + ".EnumParticle");
mc_EnumParticle_valueOf = mc_EnumParticle.getDeclaredMethod("valueOf", String.class);
}
} catch (Throwable ex) {
Logger.getAnonymousLogger().log(Level.WARNING, "Problem preparing particle packets (disabling further packets)", ex);
enabled = false;
}
}
private static String getNMSVersion() {
String ver = Bukkit.getServer().getClass().getPackage().getName();
return ver.substring(ver.lastIndexOf('.') + 1);
}
public static void createParticle(Location l, Type e) {
createParticle(l, e, 0F, 0F, 0F, 1, 3, null);
}
public static void createParticle(Location l, Type e, List<Player> localOnly) {
createParticle(l, e, 0F, 0F, 0F, 1, 3, localOnly);
}
public static void createParticle(Location l, Type e, float effectSpeed, int amountOfParticles) {
createParticle(l, e, 0F, 0F, 0F, effectSpeed, amountOfParticles, null);
}
/**
*
* @param l exact location to spawn the particle
* @param e particle effect type
* @param xx for notes, this is a value 0-1 for the color ([0-24]/24), for
* redstone this is the red value 0-1 ([0-255]/255).
* Otherwise this is the distance for particles to fly on the x-axis.
* @param yy for redstone this is the green value 0-1 ([0-255]/255)
* Otherwise this is the distance for particles to fly on the y-axis.
* @param zz for redstone this is the blue value 0-1 ([0-255]/255)
* Otherwise this is the distance for particles to fly on the z-axis.
* @param effectSpeed particle effect speed
* @param amountOfParticles extra particles to spawn (client-side)
* @param localOnly list of players to send the packets to, or null for all players
*/
public static void createParticle(Location l, Type e, float xx, float yy, float zz, float effectSpeed, int amountOfParticles, List<Player> localOnly) {
if (!enabled || e == null || effectSpeed < 0 || amountOfParticles < 0
|| !ServerVersion.isServerVersionAtLeast(e.minVersion)
|| (e.maxVersion != null && !ServerVersion.isServerVersionBelow(e.maxVersion))) {
return;
}
final int rangeSquared = 256; // apparently there is no way to override this (unless to make smaller, of course)
// collect a list of players to send this packet to
List<Player> sendTo = new ArrayList();
if (localOnly == null) {
for (Player p : l.getWorld().getPlayers()) {
if (p.getLocation().distanceSquared(l) <= rangeSquared) {
sendTo.add(p);
}
}
} else {
final World w = l.getWorld();
for (Player p : localOnly) {
if (p.getWorld() == w && p.getLocation().distanceSquared(l) <= rangeSquared) {
sendTo.add(p);
}
}
}
if (sendTo.isEmpty()) {
return;
}
try {
// Make an instance of the packet!
Object sPacket = mc_packetPlayOutWorldParticlesClazz.newInstance();
for (Field field : sPacket.getClass().getDeclaredFields()) {
// Set those fields we need to be accessible!
field.setAccessible(true);
final String fieldName = field.getName();
// Set them to what we want!
if (fieldName.equals("a")) {
// we're just going to assume it's either 1.7 or 1.8
if (version.startsWith("v1_8")) {
// 1.8 uses an Enum
field.set(sPacket, mc_EnumParticle_valueOf.invoke(null, e.name()));
} else {
field.set(sPacket, e.name);
}
} else if (fieldName.equals("b")) {
field.setFloat(sPacket, (float) l.getX()); // x
} else if (fieldName.equals("c")) {
field.setFloat(sPacket, (float) l.getY()); // y
} else if (fieldName.equals("d")) {
field.setFloat(sPacket, (float) l.getZ()); // z
} else if (fieldName.equals("e")) {
field.setFloat(sPacket, xx); // xOffset
} else if (fieldName.equals("f")) {
field.setFloat(sPacket, yy); // yOffset
} else if (fieldName.equals("g")) {
field.setFloat(sPacket, zz); // zOffset
} else if (fieldName.equals("h")) {
field.setFloat(sPacket, effectSpeed);
} else if (fieldName.equals("i")) {
field.setInt(sPacket, amountOfParticles);
}
/*
1.8 also includes other data:
j = boolean, set if view distance is increased from 256 to 65536
k = int[] for packet data (like block type for ITEM_CRACK)
*/
}
// send it on its way!
for (Player p : sendTo) {
sendPacket(sPacket, p);
}
} catch (Throwable ex) {
Logger.getAnonymousLogger().log(Level.WARNING, "Problem preparing a particle packet (disabling further packets)", ex);
enabled = false;
}
}
private static void sendPacket(Object packet, Player to) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException {
Object cbPlayer = cb_craftPlayer_getHandle.invoke(to);
Object mcConnection = mc_entityPlayer_playerConnection.get(cbPlayer);
mc_playerConnection_sendPacket.invoke(mcConnection, packet);
}
}

View File

@ -0,0 +1,49 @@
package com.songoda.core.compatibility;
import java.util.HashMap;
import java.util.Random;
import org.bukkit.potion.PotionEffectType;
public class LegacyPotionEffects {
private LegacyPotionEffects() {
}
protected final static Random rand = new Random();
private final static HashMap<Integer, String> potionEffectNames = new HashMap() {
{
put(PotionEffectType.SPEED.getId(), "Speed");
put(PotionEffectType.SLOW.getId(), "Slowness");
put(PotionEffectType.FAST_DIGGING.getId(), "Haste");
put(PotionEffectType.SLOW_DIGGING.getId(), "Mining Fatigue");
put(PotionEffectType.INCREASE_DAMAGE.getId(), "Strength");
put(PotionEffectType.WEAKNESS.getId(), "Weakness");
put(PotionEffectType.HEAL.getId(), "Instant Health");
put(PotionEffectType.HARM.getId(), "Instant Damage");
put(PotionEffectType.JUMP.getId(), "Jump Boost");
put(PotionEffectType.CONFUSION.getId(), "Nausea");
put(PotionEffectType.REGENERATION.getId(), "Regeneration");
put(PotionEffectType.DAMAGE_RESISTANCE.getId(), "Resistance");
put(PotionEffectType.FIRE_RESISTANCE.getId(), "Fire Resistance");
put(PotionEffectType.WATER_BREATHING.getId(), "Water Breathing");
put(PotionEffectType.INVISIBILITY.getId(), "Invisibility");
put(PotionEffectType.BLINDNESS.getId(), "Blindness");
put(PotionEffectType.NIGHT_VISION.getId(), "Night Vision");
put(PotionEffectType.HUNGER.getId(), "Hunger");
put(PotionEffectType.POISON.getId(), "Poison");
put(PotionEffectType.WITHER.getId(), "Wither");
put(PotionEffectType.HEALTH_BOOST.getId(), "Health Boost");
put(PotionEffectType.ABSORPTION.getId(), "Absorption");
put(PotionEffectType.SATURATION.getId(), "Saturation");
}
};
public static String getEffectName(PotionEffectType e) {
if (e == null) {
return "null";
}
final String n = potionEffectNames.get(e.getId());
return n == null ? e.getName() : n;
}
}

View File

@ -0,0 +1,66 @@
package com.songoda.core.compatibility;
import org.bukkit.Color;
import org.bukkit.Effect;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.block.BlockFace;
public class ParticleHandler {
public static void redstoneParticles(Location location, int red, int green, int blue) {
redstoneParticles(location, red, green, blue, 1F, 1, 0);
}
/**
* Spawn colored redstone particles
*
* @param location area to spawn the particle in
* @param red red value 0-255
* @param green green value 0-255
* @param blue blue value 0-255
* @param size (1.13+) size of the particles
* @param count how many particles to spawn
* @param radius how far to spread out the particles from location
*/
public static void redstoneParticles(Location location, int red, int green, int blue, float size, int count, float radius) {
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)) {
float xx = (float) (0 + (Math.random() * 1));
float yy = (float) (0 + (Math.random() * 1));
float zz = (float) (0 + (Math.random() * 1));
location.getWorld().spawnParticle(Particle.REDSTONE, location, count, xx, yy, zz, 1, new Particle.DustOptions(Color.fromBGR(blue, green, red), size));
} else if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_9)) {
for (int i = 0; i < count; i++) {
float xx = (float) (radius * (Math.random() - Math.random()));
float yy = (float) (radius * (Math.random() - Math.random()));
float zz = (float) (radius * (Math.random() - Math.random()));
Location at = location.clone().add(xx, yy, zz);
location.getWorld().spawnParticle(Particle.REDSTONE, at, 0, red / 255F, green / 255F, blue / 255F, size); // particle, location, count, red, green, blue, extra data
}
} else {
// WE NEED MAGIC!
for (int i = 0; i < count; i++) {
float xx = (float) (radius * (Math.random() - Math.random()));
float yy = (float) (radius * (Math.random() - Math.random()));
float zz = (float) (radius * (Math.random() - Math.random()));
Location at = location.clone().add(xx, yy, zz);
LegacyParticleEffects.createParticle(at, LegacyParticleEffects.Type.REDSTONE,
red / 255F, green / 255F, blue / 255F, 1F, 0, null);
}
}
}
public static void bonemealSmoke(Location l) {
final org.bukkit.World w = l.getWorld();
w.playEffect(l, Effect.SMOKE, BlockFace.SOUTH_EAST);
w.playEffect(l, Effect.SMOKE, BlockFace.SOUTH);
w.playEffect(l, Effect.SMOKE, BlockFace.SOUTH_WEST);
w.playEffect(l, Effect.SMOKE, BlockFace.EAST);
w.playEffect(l, Effect.SMOKE, BlockFace.SELF);
w.playEffect(l, Effect.SMOKE, BlockFace.WEST);
w.playEffect(l, Effect.SMOKE, BlockFace.NORTH_EAST);
w.playEffect(l, Effect.SMOKE, BlockFace.NORTH);
w.playEffect(l, Effect.SMOKE, BlockFace.NORTH_WEST);
}
}

View File

@ -0,0 +1,47 @@
package com.songoda.core.compatibility;
import org.apache.commons.lang.ArrayUtils;
import org.bukkit.Bukkit;
public enum ServerProject {
UNKNOWN, CRAFTBUKKIT, SPIGOT, PAPER, GLOWSTONE;
private static ServerProject serverProject = checkProject();
private static ServerProject checkProject() {
String serverPath = Bukkit.getServer().getClass().getName();
if (serverPath.contains("glowstone")) {
return GLOWSTONE;
}
// paper used to be called "paperclip"
try {
Class.forName("com.destroystokyo.paperclip.Paperclip");
return PAPER;
} catch (ClassNotFoundException ex) {
}
try {
Class.forName("com.destroystokyo.paper.PaperConfig");
return PAPER;
} catch (ClassNotFoundException ex) {
}
try {
Class.forName("org.spigotmc.SpigotConfig");
return SPIGOT;
} catch (ClassNotFoundException ex) {
}
return serverPath.contains("craftbukkit") ? CRAFTBUKKIT : UNKNOWN;
}
public static ServerProject getServerVersion() {
return serverProject;
}
public static boolean isServer(ServerProject version) {
return serverProject == version;
}
public static boolean isServer(ServerProject... versions) {
return ArrayUtils.contains(versions, serverProject);
}
}

View File

@ -0,0 +1,59 @@
package com.songoda.core.compatibility;
import org.apache.commons.lang.ArrayUtils;
import org.bukkit.Bukkit;
public enum ServerVersion {
UNKNOWN, V1_7, V1_8, V1_9, V1_10, V1_11, V1_12, V1_13, V1_14, V1_15, V1_16, V1_17, V1_18, V1_19, V1_20;
private final static String serverPackagePath = Bukkit.getServer().getClass().getPackage().getName();
private final static String serverPackageVersion = serverPackagePath.substring(serverPackagePath.lastIndexOf('.') + 1);
private static ServerVersion serverVersion = UNKNOWN;
private static String serverReleaseVersion;
static {
for (ServerVersion version : values()) {
if (serverPackageVersion.toUpperCase().startsWith(version.name())) {
serverVersion = version;
serverReleaseVersion = serverPackageVersion.substring(version.name().length() + 2);
}
}
}
public boolean isLessThan(ServerVersion other) {
return this.ordinal() < other.ordinal();
}
public boolean isGreaterThan(ServerVersion other) {
return this.ordinal() > other.ordinal();
}
public static String getServerVersionString() {
return serverPackageVersion;
}
public static String getVersionReleaseNumber() {
return serverReleaseVersion;
}
public static ServerVersion getServerVersion() {
return serverVersion;
}
public static boolean isServerVersion(ServerVersion version) {
return serverVersion == version;
}
public static boolean isServerVersion(ServerVersion... versions) {
return ArrayUtils.contains(versions, serverVersion);
}
public static boolean isServerVersionAtLeast(ServerVersion version) {
return serverVersion.ordinal() >= version.ordinal();
}
public static boolean isServerVersionBelow(ServerVersion version) {
return serverVersion.ordinal() < version.ordinal();
}
}

View File

@ -0,0 +1,102 @@
package com.songoda.core.configuration;
import com.songoda.core.configuration.ConfigFormattingRules.CommentStyle;
import java.io.IOException;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* A comment for a configuration key
*
* @since 2019-08-28
* @author jascotty2
*/
public class Comment {
final List<String> lines = new ArrayList();
CommentStyle commentStyle = null;
public Comment() {
}
public Comment(String... lines) {
this.lines.addAll(Arrays.asList(lines));
}
public Comment(List<String> lines) {
if (lines != null) {
this.lines.addAll(lines);
}
}
public Comment(CommentStyle commentStyle, String... lines) {
this.commentStyle = commentStyle;
this.lines.addAll(Arrays.asList(lines));
}
public Comment(CommentStyle commentStyle, List<String> lines) {
this.commentStyle = commentStyle;
if (lines != null) {
this.lines.addAll(lines);
}
}
public CommentStyle getCommentStyle() {
return commentStyle;
}
public void setCommentStyle(CommentStyle commentStyle) {
this.commentStyle = commentStyle;
}
public List<String> getLines() {
return lines;
}
@Override
public String toString() {
return lines.isEmpty() ? "" : lines.stream().collect(Collectors.joining("\n"));
}
public void writeComment(Writer output, int offset, CommentStyle defaultStyle) throws IOException {
CommentStyle style = commentStyle != null ? commentStyle : defaultStyle;
int minSpacing = 0, borderSpacing = 0;
// first draw the top of the comment
if (style.drawBorder) {
// grab the longest line in the list of lines
minSpacing = lines.stream().max((s1, s2) -> s1.length() - s2.length()).get().length();
borderSpacing = minSpacing + style.commentPrefix.length() + style.commentSuffix.length();
// draw the first line
output.write((new String(new char[offset])).replace('\0', ' ') + (new String(new char[borderSpacing + 2])).replace('\0', '#') + "\n");
if (style.drawSpace) {
output.write((new String(new char[offset])).replace('\0', ' ')
+ "#" + style.spacePrefixTop
+ (new String(new char[borderSpacing - style.spacePrefixTop.length() - style.spaceSuffixTop.length()])).replace('\0', style.spaceCharTop)
+ style.spaceSuffixTop + "#\n");
}
} else if (style.drawSpace) {
output.write((new String(new char[offset])).replace('\0', ' ') + "#\n");
}
// then the actual comment lines
for (String line : lines) {
// todo? should we auto-wrap comment lines that are longer than 80 characters?
output.write((new String(new char[offset])).replace('\0', ' ') + "#" + style.commentPrefix
+ (minSpacing == 0 ? line : line + (new String(new char[minSpacing - line.length()])).replace('\0', ' ')) + style.commentSuffix + (style.drawBorder ? "#\n" : "\n"));
}
// now draw the bottom of the comment border
if (style.drawBorder) {
if (style.drawSpace) {
output.write((new String(new char[offset])).replace('\0', ' ')
+ "#" + style.spacePrefixBottom
+ (new String(new char[borderSpacing - style.spacePrefixBottom.length() - style.spaceSuffixBottom.length()])).replace('\0', style.spaceCharBottom)
+ style.spaceSuffixBottom + "#\n");
}
output.write((new String(new char[offset])).replace('\0', ' ') + (new String(new char[borderSpacing + 2])).replace('\0', '#') + "\n");
} else if (style.drawSpace) {
output.write((new String(new char[offset])).replace('\0', ' ') + "#\n");
}
}
}

View File

@ -0,0 +1,587 @@
package com.songoda.core.configuration;
import com.songoda.core.utils.TextUtils;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringReader;
import java.io.StringWriter;
import java.io.Writer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Timer;
import java.util.TimerTask;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.lang.Validate;
import org.bukkit.Bukkit;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.YamlConstructor;
import org.bukkit.configuration.file.YamlRepresenter;
import org.bukkit.plugin.Plugin;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.yaml.snakeyaml.DumperOptions;
import org.yaml.snakeyaml.Yaml;
import org.yaml.snakeyaml.error.YAMLException;
import org.yaml.snakeyaml.representer.Representer;
/**
* Configuration settings for a plugin
*
* @since 2019-08-28
* @author jascotty2
*/
public class Config extends ConfigSection {
/*
Serialization notes:
// implements ConfigurationSerializable:
//public Map<String, Object> serialize();
// Class must contain one of:
// public static Object deserialize(@NotNull Map<String, ?> args);
// public static valueOf(Map<String, ?> args);
// public new (Map<String, ?> args)
*/
protected static final String COMMENT_PREFIX = "# ";
protected static final String BLANK_CONFIG = "{}\n";
protected File file;
protected final ConfigFileConfigurationAdapter config = new ConfigFileConfigurationAdapter(this);
final String dirName, fileName;
final Plugin plugin;
final DumperOptions yamlOptions = new DumperOptions();
final Representer yamlRepresenter = new YamlRepresenter();
final Yaml yaml = new Yaml(new YamlConstructor(), yamlRepresenter, yamlOptions);
SaveTask saveTask;
Timer autosaveTimer;
////////////// Config settings ////////////////
/**
* save file whenever a change is made
*/
boolean autosave = false;
/**
* time in seconds to start a save after a change is made
*/
int autosaveInterval = 60;
/**
* remove nodes not defined in defaults
*/
boolean autoremove = false;
/**
* load comments when loading the file
* TODO
*/
boolean loadComments = false;
/**
* Default comment applied to config nodes
*/
ConfigFormattingRules.CommentStyle defaultNodeCommentFormat = ConfigFormattingRules.CommentStyle.SIMPLE;
/**
* Default comment applied to section nodes
*/
ConfigFormattingRules.CommentStyle defaultSectionCommentFormat = ConfigFormattingRules.CommentStyle.SPACED;
/**
* Extra lines to put between root nodes
*/
int rootNodeSpacing = 1;
/**
* Extra lines to put in front of comments. <br>
* This is separate from rootNodeSpacing, if applicable.
*/
int commentSpacing = 1;
public Config(@NotNull File file) {
this.plugin = null;
this.file = file.getAbsoluteFile();
dirName = null;
fileName = null;
}
public Config(@NotNull Plugin plugin) {
this.plugin = plugin;
dirName = null;
fileName = null;
}
public Config(@NotNull Plugin plugin, @NotNull String file) {
this.plugin = plugin;
dirName = null;
fileName = file;
}
public Config(@NotNull Plugin plugin, @NotNull String directory, @NotNull String file) {
this.plugin = plugin;
dirName = directory;
fileName = file;
}
public ConfigFileConfigurationAdapter getFileConfig() {
return config;
}
public File getFile() {
if (file == null) {
if (dirName != null) {
this.file = new File(plugin.getDataFolder() + dirName, fileName != null ? fileName : "config.yml");
} else {
this.file = new File(plugin.getDataFolder(), fileName != null ? fileName : "config.yml");
}
}
return file;
}
public boolean getAutosave() {
return autosave;
}
/**
* Should the configuration automatically save whenever it's been changed? <br>
* All saves are done asynchronously, so this should not impact server performance.
*
* @param autosave set to true if autosaving is enabled.
* @return this class
*/
@NotNull
public Config setAutosave(boolean autosave) {
this.autosave = autosave;
return this;
}
public int getAutosaveInterval() {
return autosaveInterval;
}
/**
* If autosave is enabled, this is the delay between a change and when the save is started. <br>
* If the configuration is changed within this period, the timer is not reset.
*
* @param autosaveInterval time in seconds
* @return this class
*/
public Config setAutosaveInterval(int autosaveInterval) {
this.autosaveInterval = autosaveInterval;
return this;
}
public boolean getAutoremove() {
return autoremove;
}
/**
* This setting is used to prevent users to from adding extraneous settings
* to the config and to remove deprecated settings. <br>
* If this is enabled, the config will delete any nodes that are not defined
* as a default setting.
*
* @param autoremove Remove settings that don't exist as defaults
* @return this class
*/
@NotNull
public Config setAutoremove(boolean autoremove) {
this.autoremove = autoremove;
return this;
}
/**
* Default comment applied to config nodes
*/
public ConfigFormattingRules.CommentStyle getDefaultNodeCommentFormat() {
return defaultNodeCommentFormat;
}
/**
* Default comment applied to config nodes
*
* @return this config
*/
public Config setDefaultNodeCommentFormat(ConfigFormattingRules.CommentStyle defaultNodeCommentFormat) {
this.defaultNodeCommentFormat = defaultNodeCommentFormat;
return this;
}
/**
* Default comment applied to section nodes
*/
public ConfigFormattingRules.CommentStyle getDefaultSectionCommentFormat() {
return defaultSectionCommentFormat;
}
/**
* Default comment applied to section nodes
*
* @return this config
*/
public Config setDefaultSectionCommentFormat(ConfigFormattingRules.CommentStyle defaultSectionCommentFormat) {
this.defaultSectionCommentFormat = defaultSectionCommentFormat;
return this;
}
/**
* Extra lines to put between root nodes
*/
public int getRootNodeSpacing() {
return rootNodeSpacing;
}
/**
* Extra lines to put between root nodes
*
* @return this config
*/
public Config setRootNodeSpacing(int rootNodeSpacing) {
this.rootNodeSpacing = rootNodeSpacing;
return this;
}
/**
* Extra lines to put in front of comments. <br>
* This is separate from rootNodeSpacing, if applicable.
*/
public int getCommentSpacing() {
return commentSpacing;
}
/**
* Extra lines to put in front of comments. <br>
* This is separate from rootNodeSpacing, if applicable.
*
* @return this config
*/
public Config setCommentSpacing(int commentSpacing) {
this.commentSpacing = commentSpacing;
return this;
}
@NotNull
public Config setHeader(@NotNull String... description) {
if (description.length == 0) {
configComments.remove(null);
} else {
configComments.put(null, new Comment(description));
}
return this;
}
@NotNull
public Config setHeader(@Nullable ConfigFormattingRules.CommentStyle commentStyle, @NotNull String... description) {
if (description.length == 0) {
configComments.remove(null);
} else {
configComments.put(null, new Comment(commentStyle, description));
}
return this;
}
@NotNull
public Config setHeader(@Nullable List<String> description) {
if (description == null || description.isEmpty()) {
configComments.remove(null);
} else {
configComments.put(null, new Comment(description));
}
return this;
}
@NotNull
public Config setHeader(@Nullable ConfigFormattingRules.CommentStyle commentStyle, @Nullable List<String> description) {
if (description == null || description.isEmpty()) {
configComments.remove(null);
} else {
configComments.put(null, new Comment(commentStyle, description));
}
return this;
}
@NotNull
public List<String> getHeader() {
if (configComments.containsKey(null)) {
return configComments.get(null).getLines();
} else {
return Collections.EMPTY_LIST;
}
}
public boolean load() {
return load(getFile());
}
public boolean load(@NotNull File file) {
Validate.notNull(file, "File cannot be null");
if (file.exists()) {
try (BufferedInputStream stream = new BufferedInputStream(new FileInputStream(file))) {
Charset charset = TextUtils.detectCharset(stream, StandardCharsets.UTF_8);
this.load(new InputStreamReader(stream, charset));
return true;
} catch (IOException | InvalidConfigurationException ex) {
(plugin != null ? plugin.getLogger() : Bukkit.getLogger()).log(Level.SEVERE, "Failed to load config file: " + file.getName(), ex);
}
return false;
}
return true;
}
public void load(@NotNull Reader reader) throws IOException, InvalidConfigurationException {
StringBuilder builder = new StringBuilder();
try (BufferedReader input = reader instanceof BufferedReader ? (BufferedReader) reader : new BufferedReader(reader)) {
String line;
boolean firstLine = true;
while ((line = input.readLine()) != null) {
if(firstLine) {
line = line.replaceAll("[\uFEFF\uFFFE\u200B]", ""); // clear BOM markers
firstLine = false;
}
builder.append(line).append('\n');
}
}
this.loadFromString(builder.toString());
}
public void loadFromString(@NotNull String contents) throws InvalidConfigurationException {
Map input;
try {
input = (Map) this.yaml.load(contents);
} catch (YAMLException e2) {
throw new InvalidConfigurationException(e2);
} catch (ClassCastException e3) {
throw new InvalidConfigurationException("Top level is not a Map.");
}
if (input != null) {
if(loadComments) {
this.parseComments(contents, input);
}
this.convertMapsToSections(input, this);
}
}
protected void convertMapsToSections(@NotNull Map<?, ?> input, @NotNull ConfigSection section) {
for (Map.Entry<?, ?> entry : input.entrySet()) {
String key = entry.getKey().toString();
Object value = entry.getValue();
if (value instanceof Map) {
this.convertMapsToSections((Map) value, section.createSection(key));
continue;
}
section.set(key, value);
}
}
protected void parseComments(@NotNull String contents, @NotNull Map<?, ?> input) {
// TODO?
// if starts with a comment, load all nonbreaking comments as a header
// then load all comments and assign to the next valid node loaded
// (Only load comments that are on their own line)
}
public void deleteNonDefaultSettings() {
// Delete old config values (thread-safe)
List<String> defaultKeys = Arrays.asList(defaults.keySet().toArray(new String[0]));
for(String key : values.keySet().toArray(new String[0])) {
if(!defaultKeys.contains(key)) {
values.remove(key);
}
}
}
@Override
protected void onChange() {
if (autosave) {
delaySave();
}
}
public void delaySave() {
// save async even if no plugin or if plugin disabled
if (changed && saveTask == null) {
autosaveTimer = new Timer((plugin != null ? plugin.getName() + "-ConfigSave-" : "ConfigSave-") + getFile().getName());
autosaveTimer.schedule(saveTask = new SaveTask(), autosaveInterval * 1000L);
}
}
public boolean saveChanges() {
boolean saved = true;
if (changed) {
saved = save();
}
if(saveTask != null) {
//Close Threads
saveTask.cancel();
autosaveTimer.cancel();
saveTask = null;
autosaveTimer = null;
}
return saved;
}
public boolean save() {
if(saveTask != null) {
//Close Threads
saveTask.cancel();
autosaveTimer.cancel();
saveTask = null;
autosaveTimer = null;
}
return save(getFile());
}
public boolean save(@NotNull String file) {
Validate.notNull(file, "File cannot be null");
return this.save(new File(file));
}
public boolean save(@NotNull File file) {
Validate.notNull(file, "File cannot be null");
if (!file.getParentFile().exists()) {
file.getParentFile().mkdirs();
}
String data = this.saveToString();
try (OutputStreamWriter writer = new OutputStreamWriter((OutputStream) new FileOutputStream(file), StandardCharsets.UTF_16);) {
writer.write(data);
} catch (IOException e) {
return false;
}
return true;
}
@NotNull
public String saveToString() {
try {
if(autoremove) {
deleteNonDefaultSettings();
}
yamlOptions.setIndent(indentation);
yamlOptions.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
yamlRepresenter.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK);
StringWriter str = new StringWriter();
Comment header = configComments.get(null);
if (header != null) {
header.writeComment(str, 0, ConfigFormattingRules.CommentStyle.SPACED);
str.write("\n"); // add one space after the header
}
String dump = yaml.dump(this.getValues(false));
if (!dump.equals(BLANK_CONFIG)) {
writeComments(dump, str);
}
return str.toString();
} catch (Throwable ex) {
Logger.getLogger(Config.class.getName()).log(Level.SEVERE, "Error saving config", ex);
delaySave();
}
return "";
}
protected final Pattern yamlNode = Pattern.compile("^( *)([^:\\{\\}\\[\\],&\\*#\\?\\|\\-<>=!%@`]+):(.*)$");
protected void writeComments(String data, Writer out) throws IOException {
// line-by-line apply line spacing formatting and comments per-node
BufferedReader in = new BufferedReader(new StringReader(data));
String line;
boolean insideScalar = false;
boolean firstNode = true;
int index = 0;
LinkedList<String> currentPath = new LinkedList();
while ((line = in.readLine()) != null) {
// ignore comments and empty lines (there shouldn't be any, but just in case)
if (line.trim().startsWith("#") || line.isEmpty()) {
continue;
}
// check to see if this is a line that we can process
int lineOffset = getOffset(line);
insideScalar &= lineOffset <= index;
Matcher m;
if (!insideScalar && (m = yamlNode.matcher(line)).find()) {
// we found a config node! ^.^
// check to see what the full path is
int depth = (m.group(1).length() / indentation);
while (depth < currentPath.size()) {
currentPath.removeLast();
}
currentPath.add(m.group(2));
String path = currentPath.stream().collect(Collectors.joining(String.valueOf(pathChar)));
// if this is a root-level node, apply extra spacing if we aren't the first node
if (!firstNode && depth == 0 && rootNodeSpacing > 0) {
out.write((new String(new char[rootNodeSpacing])).replace("\0", "\n")); // yes it's silly, but it works :>
}
firstNode = false; // we're no longer on the first node
// insert the relavant comment
Comment comment = getComment(path);
if (comment != null) {
// add spacing between previous nodes and comments
if (depth != 0) {
out.write((new String(new char[commentSpacing])).replace("\0", "\n"));
}
// formatting style for this node
ConfigFormattingRules.CommentStyle style = comment.getCommentStyle();
if (style == null) {
// check to see what type of node this is
if (!m.group(3).trim().isEmpty()) {
// setting node
style = defaultNodeCommentFormat;
} else {
// probably a section? (need to peek ahead to check if this is a list)
in.mark(1000);
String nextLine = in.readLine().trim();
in.reset();
if (nextLine.startsWith("-")) {
// not a section :P
style = defaultNodeCommentFormat;
} else {
style = defaultSectionCommentFormat;
}
}
}
// write it down!
comment.writeComment(out, lineOffset, style);
}
// ignore scalars
index = lineOffset;
if (m.group(3).trim().equals("|") || m.group(3).trim().equals(">")) {
insideScalar = true;
}
}
out.write(line);
out.write("\n");
}
}
protected static int getOffset(String s) {
char[] chars = s.toCharArray();
for (int i = 0; i < chars.length; ++i) {
if (chars[i] != ' ') {
return i;
}
}
return -1;
}
class SaveTask extends TimerTask {
@Override
public void run() {
saveChanges();
}
}
}

View File

@ -0,0 +1,119 @@
package com.songoda.core.configuration;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.InvalidConfigurationException;
import org.bukkit.configuration.file.FileConfiguration;
public class ConfigFileConfigurationAdapter extends FileConfiguration {
final Config config;
public ConfigFileConfigurationAdapter(Config config) {
super(config);
this.config = config;
}
public Config getCoreConfig() {
return config;
}
@Override
public String saveToString() {
return config.saveToString();
}
@Override
public void loadFromString(String string) throws InvalidConfigurationException {
config.loadFromString(string);
}
@Override
protected String buildHeader() {
return "#" + config.getHeader().stream().collect(Collectors.joining("\n#"));
}
@Override
public ConfigOptionsAdapter options() {
return new ConfigOptionsAdapter(config);
}
@Override
public Set<String> getKeys(boolean deep) {
return config.getKeys(deep);
}
@Override
public Map<String, Object> getValues(boolean deep) {
return config.getValues(deep);
}
@Override
public boolean contains(String path) {
return config.contains(path);
}
@Override
public boolean isSet(String path) {
return config.isSet(path);
}
@Override
public String getCurrentPath() {
return config.getCurrentPath();
}
@Override
public String getName() {
return config.getName();
}
@Override
public Configuration getRoot() {
return config;
}
@Override
public ConfigurationSection getParent() {
return null;
}
@Override
public void addDefault(String path, Object value) {
config.addDefault(path, value);
}
@Override
public ConfigurationSection getDefaultSection() {
return config.getDefaultSection();
}
@Override
public void set(String path, Object value) {
config.set(path, value);
}
@Override
public Object get(String path) {
return config.get(path);
}
@Override
public Object get(String path, Object def) {
return config.get(path, def);
}
@Override
public ConfigurationSection createSection(String path) {
return config.createSection(path);
}
@Override
public ConfigurationSection createSection(String path, Map<?, ?> map) {
return config.createSection(path, map);
}
}

View File

@ -0,0 +1,69 @@
package com.songoda.core.configuration;
public class ConfigFormattingRules {
int spacesBetweenMainCategories;
int spacesBetweenValues;
CommentStyle rootCommentStyle = CommentStyle.BLOCKSPACED;
CommentStyle mainCategoryCommentStyle = CommentStyle.SPACED;
public static enum CommentStyle {
/**
* # Comment
*/
SIMPLE(false, false, " ", ""),
/**
* # <br />
* # Comment <br />
* # <br />
*/
SPACED(false, true, " ", ""),
/**
* ########### <br />
* # Comment # <br />
* ########### <br />
*/
BLOCKED(true, false, " ", " "),
/**
* ############# <br />
* #|¯¯¯¯¯¯¯¯¯|# <br />
* #| Comment |# <br />
* #|_________|# <br />
* ############# <br />
*/
BLOCKSPACED(true, true, "|\u00AF", '\u00AF', "\u00AF|", "| ", " |", "|_", '_', "_|");
final boolean drawBorder, drawSpace;
final String commentPrefix, spacePrefixTop, spacePrefixBottom;
final String commentSuffix, spaceSuffixTop, spaceSuffixBottom;
final char spaceCharTop, spaceCharBottom;
private CommentStyle(boolean drawBorder, boolean drawSpace,
String spacePrefixTop, char spaceCharTop, String spaceSuffixTop,
String commentPrefix, String commentSuffix,
String spacePrefixBottom, char spaceCharBottom, String spaceSuffixBottom) {
this.drawBorder = drawBorder;
this.drawSpace = drawSpace;
this.commentPrefix = commentPrefix;
this.spacePrefixTop = spacePrefixTop;
this.spacePrefixBottom = spacePrefixBottom;
this.commentSuffix = commentSuffix;
this.spaceSuffixTop = spaceSuffixTop;
this.spaceSuffixBottom = spaceSuffixBottom;
this.spaceCharTop = spaceCharTop;
this.spaceCharBottom = spaceCharBottom;
}
private CommentStyle(boolean drawBorder, boolean drawSpace, String commentPrefix, String commentSuffix) {
this.drawBorder = drawBorder;
this.drawSpace = drawSpace;
this.commentPrefix = commentPrefix;
this.commentSuffix = commentSuffix;
this.spacePrefixTop = this.spacePrefixBottom = "";
this.spaceCharTop = this.spaceCharBottom = ' ';
this.spaceSuffixTop = this.spaceSuffixBottom = "";
}
}
}

View File

@ -0,0 +1,70 @@
package com.songoda.core.configuration;
import java.util.List;
import org.bukkit.configuration.file.FileConfigurationOptions;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ConfigOptionsAdapter extends FileConfigurationOptions {
final ConfigSection config;
public ConfigOptionsAdapter(ConfigSection config) {
super(config);
this.config = config;
}
public Config getConfig() {
return (Config) config.root;
}
@NotNull
@Override
public ConfigFileConfigurationAdapter configuration() {
return new ConfigFileConfigurationAdapter((Config) config.root);
}
@NotNull
@Override
public ConfigOptionsAdapter copyDefaults(boolean value) {
// we always copy new values
return this;
}
@NotNull
@Override
public ConfigOptionsAdapter pathSeparator(char value) {
((Config) config.root).setPathSeparator(value);
return this;
}
@NotNull
@Override
public ConfigOptionsAdapter header(@Nullable String value) {
if (value == null) {
((Config) config.root).setHeader((List) null);
} else {
((Config) config.root).setHeader(value.split("\n"));
}
return this;
}
@NotNull
@Override
public ConfigOptionsAdapter copyHeader(boolean value) {
if (!value) {
((Config) config.root).setHeader((List) null);
}
return this;
}
public int indent() {
return ((Config) config.root).getIndent();
}
@NotNull
public ConfigOptionsAdapter indent(int value) {
((Config) config.root).setIndent(value);
return this;
}
}

View File

@ -0,0 +1,636 @@
package com.songoda.core.configuration;
import com.songoda.core.compatibility.LegacyMaterials;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.bukkit.configuration.Configuration;
import org.bukkit.configuration.MemoryConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Configuration for a specific node
*
* @since 2019-08-28
* @author jascotty2
*/
public class ConfigSection extends MemoryConfiguration {
final String fullPath, nodeKey;
final ConfigSection root;
final ConfigSection parent;
protected int indentation = 2; // between 2 and 9 (inclusive)
protected char pathChar = '.';
final HashMap<String, Comment> configComments;
final HashMap<String, Comment> defaultComments;
final LinkedHashMap<String, Object> defaults;
final LinkedHashMap<String, Object> values;
/**
* Internal root state: if any configuration value has changed from file state
*/
boolean changed = false;
final boolean isDefault;
final Object lock = new Object();
ConfigSection() {
this.root = this;
this.parent = null;
isDefault = false;
nodeKey = fullPath = "";
configComments = new HashMap();
defaultComments = new HashMap();
defaults = new LinkedHashMap();
values = new LinkedHashMap();
}
ConfigSection(ConfigSection root, ConfigSection parent, String nodeKey, boolean isDefault) {
this.root = root;
this.parent = parent;
this.nodeKey = nodeKey;
this.fullPath = nodeKey != null ? parent.fullPath + nodeKey + root.pathChar : parent.fullPath;
this.isDefault = isDefault;
configComments = defaultComments = null;
defaults = null;
values = null;
}
public int getIndent() {
return root.indentation;
}
public void setIndent(int indentation) {
root.indentation = indentation;
}
protected void onChange() {
if (parent != null) {
root.onChange();
}
}
/**
* Sets the character used to separate configuration nodes. <br>
* IMPORTANT: Do not change this after loading or adding ConfigurationSections!
*
* @param pathChar character to use
*/
public void setPathSeparator(char pathChar) {
if (!root.values.isEmpty() || !root.defaults.isEmpty())
throw new RuntimeException("Path change after config initialization");
root.pathChar = pathChar;
}
public char getPathSeparator() {
return root.pathChar;
}
/**
* @return The full key for this section node
*/
public String getKey() {
return !fullPath.endsWith(String.valueOf(root.pathChar)) ? fullPath : fullPath.substring(0, fullPath.length() - 1);
}
/**
* @return The specific key that was used from the last node to get to this node
*/
public String getNodeKey() {
return nodeKey;
}
@NotNull
public ConfigSection createDefaultSection(@NotNull String path) {
ConfigSection section = new ConfigSection(root, this, path, true);
synchronized (root.lock) {
root.defaults.put(fullPath + path, section);
}
return section;
}
@NotNull
public ConfigSection createDefaultSection(@NotNull String path, String... comment) {
ConfigSection section = new ConfigSection(root, this, path, true);
synchronized (root.lock) {
root.defaults.put(fullPath + path, section);
root.defaultComments.put(fullPath + path, new Comment(comment));
}
return section;
}
@NotNull
public ConfigSection createDefaultSection(@NotNull String path, ConfigFormattingRules.CommentStyle commentStyle, String... comment) {
ConfigSection section = new ConfigSection(root, this, path, true);
synchronized (root.lock) {
root.defaults.put(fullPath + path, section);
root.defaultComments.put(fullPath + path, new Comment(commentStyle, comment));
}
return section;
}
@NotNull
public ConfigSection setComment(@NotNull String path, @Nullable ConfigFormattingRules.CommentStyle commentStyle, String... lines) {
return setComment(path, commentStyle, lines.length == 0 ? (List) null : Arrays.asList(lines));
}
@NotNull
public ConfigSection setComment(@NotNull String path, @Nullable ConfigFormattingRules.CommentStyle commentStyle, @Nullable List<String> lines) {
synchronized (root.lock) {
if (isDefault) {
root.defaultComments.put(fullPath + path, lines != null ? new Comment(commentStyle, lines) : null);
} else {
root.configComments.put(fullPath + path, lines != null ? new Comment(commentStyle, lines) : null);
}
}
return this;
}
@NotNull
public ConfigSection setDefaultComment(@NotNull String path, String... lines) {
return setDefaultComment(path, lines.length == 0 ? (List) null : Arrays.asList(lines));
}
@NotNull
public ConfigSection setDefaultComment(@NotNull String path, @Nullable List<String> lines) {
synchronized (root.lock) {
root.defaultComments.put(fullPath + path, new Comment(lines));
}
return this;
}
@NotNull
public ConfigSection setDefaultComment(@NotNull String path, ConfigFormattingRules.CommentStyle commentStyle, String... lines) {
return setDefaultComment(path, commentStyle, lines.length == 0 ? (List) null : Arrays.asList(lines));
}
@NotNull
public ConfigSection setDefaultComment(@NotNull String path, ConfigFormattingRules.CommentStyle commentStyle, @Nullable List<String> lines) {
synchronized (root.lock) {
root.defaultComments.put(fullPath + path, new Comment(commentStyle, lines));
}
return this;
}
@Nullable
public Comment getComment(@NotNull String path) {
Comment result = root.configComments.get(fullPath + path);
if (result == null) {
result = root.defaultComments.get(fullPath + path);
}
return result;
}
@Nullable
public String getCommentString(@NotNull String path) {
Comment result = root.configComments.get(fullPath + path);
if (result == null) {
result = root.defaultComments.get(fullPath + path);
}
return result != null ? result.toString() : null;
}
@Override
public void addDefault(@NotNull String path, @Nullable Object value) {
synchronized (root.lock) {
// if any intermediate nodes don't exist, create them
String[] pathParts = path.split(Pattern.quote(String.valueOf(root.pathChar)));
String nodePath = "";
for (int i = 0; i < pathParts.length - 1; ++i) {
nodePath += (nodePath.isEmpty() ? pathParts[i] : root.pathChar + pathParts[i]);
if (!(root.defaults.get(nodePath) instanceof ConfigSection)) {
root.defaults.put(nodePath, new ConfigSection(root, this, nodePath, true));
}
}
root.defaults.put(fullPath + path, value);
}
}
@Override
public void addDefaults(@NotNull Map<String, Object> defaults) {
//defaults.entrySet().stream().forEach(m -> root.defaults.put(fullPath + m.getKey(), m.getValue()));
defaults.entrySet().stream().forEach(m -> addDefault(m.getKey(), m.getValue()));
}
@Override
public void setDefaults(Configuration c) {
if (fullPath.isEmpty()) {
root.defaults.clear();
} else {
root.defaults.keySet().stream()
.filter(k -> k.startsWith(fullPath))
.forEach(k -> root.defaults.remove(k));
}
addDefaults(c);
}
@Override
public ConfigSection getDefaults() {
return new ConfigSection(root, this, null, true);
}
@Override
public ConfigSection getDefaultSection() {
return new ConfigSection(root, this, null, true);
}
@Override
public ConfigOptionsAdapter options() {
return new ConfigOptionsAdapter(root);
}
@NotNull
@Override
public Set<String> getKeys(boolean deep) {
LinkedHashSet<String> result = new LinkedHashSet();
int pathIndex = fullPath.lastIndexOf(root.pathChar);
if (deep) {
result.addAll(root.defaults.keySet().stream()
.filter(k -> k.startsWith(fullPath))
.map(k -> !k.endsWith(String.valueOf(root.pathChar)) ? k.substring(pathIndex + 1) : k.substring(pathIndex + 1, k.length() - 1))
.collect(Collectors.toCollection(LinkedHashSet::new)));
result.addAll(root.values.keySet().stream()
.filter(k -> k.startsWith(fullPath))
.map(k -> !k.endsWith(String.valueOf(root.pathChar)) ? k.substring(pathIndex + 1) : k.substring(pathIndex + 1, k.length() - 1))
.collect(Collectors.toCollection(LinkedHashSet::new)));
} else {
result.addAll(root.defaults.keySet().stream()
.filter(k -> k.startsWith(fullPath) && k.lastIndexOf(root.pathChar) == pathIndex + 1)
.map(k -> !k.endsWith(String.valueOf(root.pathChar)) ? k.substring(pathIndex + 1) : k.substring(pathIndex + 1, k.length() - 1))
.collect(Collectors.toCollection(LinkedHashSet::new)));
result.addAll(root.values.keySet().stream()
.filter(k -> k.startsWith(fullPath) && k.lastIndexOf(root.pathChar) == pathIndex)
.map(k -> !k.endsWith(String.valueOf(root.pathChar)) ? k.substring(pathIndex + 1) : k.substring(pathIndex + 1, k.length() - 1))
.collect(Collectors.toCollection(LinkedHashSet::new)));
}
return result;
}
@NotNull
@Override
public Map<String, Object> getValues(boolean deep) {
LinkedHashMap<String, Object> result = new LinkedHashMap();
int pathIndex = fullPath.lastIndexOf(root.pathChar);
if (deep) {
result.putAll((Map<String, Object>) root.defaults.entrySet().stream()
.filter(k -> k.getKey().startsWith(fullPath))
.collect(Collectors.toMap(
e -> !e.getKey().endsWith(String.valueOf(root.pathChar)) ? e.getKey().substring(pathIndex + 1) : e.getKey().substring(pathIndex + 1, e.getKey().length() - 1),
e -> e.getValue(),
(v1, v2) -> { throw new IllegalStateException(); }, // never going to be merging keys
LinkedHashMap::new)));
result.putAll((Map<String, Object>) root.values.entrySet().stream()
.filter(k -> k.getKey().startsWith(fullPath))
.collect(Collectors.toMap(
e -> !e.getKey().endsWith(String.valueOf(root.pathChar)) ? e.getKey().substring(pathIndex + 1) : e.getKey().substring(pathIndex + 1, e.getKey().length() - 1),
e -> e.getValue(),
(v1, v2) -> { throw new IllegalStateException(); }, // never going to be merging keys
LinkedHashMap::new)));
} else {
result.putAll((Map<String, Object>) root.defaults.entrySet().stream()
.filter(k -> k.getKey().startsWith(fullPath) && k.getKey().lastIndexOf(root.pathChar) == pathIndex)
.collect(Collectors.toMap(
e -> !e.getKey().endsWith(String.valueOf(root.pathChar)) ? e.getKey().substring(pathIndex + 1) : e.getKey().substring(pathIndex + 1, e.getKey().length() - 1),
e -> e.getValue(),
(v1, v2) -> { throw new IllegalStateException(); }, // never going to be merging keys
LinkedHashMap::new)));
result.putAll((Map<String, Object>) root.values.entrySet().stream()
.filter(k -> k.getKey().startsWith(fullPath) && k.getKey().lastIndexOf(root.pathChar) == pathIndex)
.collect(Collectors.toMap(
e -> !e.getKey().endsWith(String.valueOf(root.pathChar)) ? e.getKey().substring(pathIndex + 1) : e.getKey().substring(pathIndex + 1, e.getKey().length() - 1),
e -> e.getValue(),
(v1, v2) -> { throw new IllegalStateException(); }, // never going to be merging keys
LinkedHashMap::new)));
}
return result;
}
@NotNull
public List<ConfigSection> getSections(String path) {
ConfigSection rootSection = getConfigurationSection(path);
if (rootSection == null) {
return Collections.EMPTY_LIST;
}
ArrayList<ConfigSection> result = new ArrayList();
rootSection.getKeys(false).stream()
.map(key -> rootSection.get(key))
.filter(object -> object != null && object instanceof ConfigSection)
.forEachOrdered(object -> result.add((ConfigSection) object));
return result;
}
@Override
public boolean contains(@NotNull String path) {
return root.defaults.containsKey(fullPath + path) || root.values.containsKey(fullPath + path);
}
@Override
public boolean contains(@NotNull String path, boolean ignoreDefault) {
return (!ignoreDefault && root.defaults.containsKey(fullPath + path)) || root.values.containsKey(fullPath + path);
}
@Override
public boolean isSet(@NotNull String path) {
return root.defaults.get(fullPath + path) != null || root.values.get(fullPath + path) != null;
}
@Override
public String getCurrentPath() {
return fullPath.isEmpty() ? "" : fullPath.substring(0, fullPath.length() - 1);
}
@Override
public String getName() {
if (fullPath.isEmpty())
return "";
String[] parts = fullPath.split(Pattern.quote(String.valueOf(root.pathChar)));
return parts[parts.length - 1];
}
@Override
public ConfigSection getRoot() {
return root;
}
@Override
public ConfigSection getParent() {
return parent;
}
@Nullable
@Override
public Object get(@NotNull String path) {
Object result = root.values.get(fullPath + path);
if (result == null) {
result = root.defaults.get(fullPath + path);
}
return result;
}
@Nullable
@Override
public Object get(@NotNull String path, @Nullable Object def) {
Object result = root.values.get(fullPath + path);
return result != null ? result : def;
}
@Override
public void set(@NotNull String path, @Nullable Object value) {
if (isDefault) {
root.defaults.put(fullPath + path, value);
} else {
synchronized (root.lock) {
if (value != null) {
root.changed |= root.values.put(fullPath + path, value) != value;
} else {
root.changed |= root.values.remove(fullPath + path) != null;
}
}
onChange();
}
}
@NotNull
public ConfigSection set(@NotNull String path, @Nullable Object value, String ... comment) {
set(path, value);
return setComment(path, null, comment);
}
@NotNull
public ConfigSection set(@NotNull String path, @Nullable Object value, List<String> comment) {
set(path, value);
return setComment(path, null, comment);
}
@NotNull
public ConfigSection set(@NotNull String path, @Nullable Object value, @Nullable ConfigFormattingRules.CommentStyle commentStyle, String ... comment) {
set(path, value);
return setComment(path, commentStyle, comment);
}
@NotNull
public ConfigSection set(@NotNull String path, @Nullable Object value, @Nullable ConfigFormattingRules.CommentStyle commentStyle, List<String> comment) {
set(path, value);
return setComment(path, commentStyle, comment);
}
@NotNull
public ConfigSection setDefault(@NotNull String path, @Nullable Object value) {
addDefault(path, value);
return this;
}
@NotNull
public ConfigSection setDefault(@NotNull String path, @Nullable Object value, String ... comment) {
addDefault(path, value);
return setDefaultComment(path, comment);
}
@NotNull
public ConfigSection setDefault(@NotNull String path, @Nullable Object value, List<String> comment) {
addDefault(path, value);
return setDefaultComment(path, comment);
}
@NotNull
public ConfigSection setDefault(@NotNull String path, @Nullable Object value, ConfigFormattingRules.CommentStyle commentStyle, String ... comment) {
addDefault(path, value);
return setDefaultComment(path, commentStyle, comment);
}
@NotNull
public ConfigSection setDefault(@NotNull String path, @Nullable Object value, ConfigFormattingRules.CommentStyle commentStyle, List<String> comment) {
addDefault(path, value);
return setDefaultComment(path, commentStyle, comment);
}
@NotNull
@Override
public ConfigSection createSection(@NotNull String path) {
ConfigSection section = new ConfigSection(root, this, path, false);
synchronized(root.lock) {
root.values.put(fullPath + path, section);
}
root.changed = true;
onChange();
return section;
}
@NotNull
public ConfigSection createSection(@NotNull String path, String... comment) {
return createSection(path, null, comment.length == 0 ? (List) null : Arrays.asList(comment));
}
@NotNull
public ConfigSection createSection(@NotNull String path, @Nullable List<String> comment) {
return createSection(path, null, comment);
}
@NotNull
public ConfigSection createSection(@NotNull String path, @Nullable ConfigFormattingRules.CommentStyle commentStyle, String... comment) {
return createSection(path, commentStyle, comment.length == 0 ? (List) null : Arrays.asList(comment));
}
@NotNull
public ConfigSection createSection(@NotNull String path, @Nullable ConfigFormattingRules.CommentStyle commentStyle, @Nullable List<String> comment) {
ConfigSection section = new ConfigSection(root, this, path, false);
synchronized (root.lock) {
root.values.put(fullPath + path, section);
}
setComment(path, commentStyle, comment);
root.changed = true;
onChange();
return section;
}
@NotNull
@Override
public ConfigSection createSection(@NotNull String path, Map<?, ?> map) {
ConfigSection section = new ConfigSection(root, this, path, false);
synchronized (root.lock) {
root.values.put(fullPath + path, section);
}
for (Map.Entry<?, ?> entry : map.entrySet()) {
if (entry.getValue() instanceof Map) {
section.createSection(entry.getKey().toString(), (Map) entry.getValue());
continue;
}
section.set(entry.getKey().toString(), entry.getValue());
}
root.changed = true;
onChange();
return section;
}
@Nullable
@Override
public String getString(@NotNull String path) {
Object result = get(path);
return result != null ? result.toString() : null;
}
@Nullable
@Override
public String getString(@NotNull String path, @Nullable String def) {
Object result = get(path);
return result != null ? result.toString() : def;
}
public char getChar(@NotNull String path) {
Object result = get(path);
return result != null && !result.toString().isEmpty() ? result.toString().charAt(0) : '\0';
}
public char getChar(@NotNull String path, char def) {
Object result = get(path);
return result != null && !result.toString().isEmpty() ? result.toString().charAt(0) : def;
}
@Override
public int getInt(@NotNull String path) {
Object result = get(path);
return result instanceof Number ? ((Number) result).intValue() : 0;
}
@Override
public int getInt(@NotNull String path, int def) {
Object result = get(path);
return result instanceof Number ? ((Number) result).intValue() : def;
}
@Override
public boolean getBoolean(@NotNull String path) {
Object result = get(path);
return result instanceof Boolean ? (Boolean) result : false;
}
@Override
public boolean getBoolean(@NotNull String path, boolean def) {
Object result = get(path);
return result instanceof Boolean ? (Boolean) result : def;
}
@Override
public double getDouble(@NotNull String path) {
Object result = get(path);
return result instanceof Number ? ((Number) result).doubleValue() : 0;
}
@Override
public double getDouble(@NotNull String path, double def) {
Object result = get(path);
return result instanceof Number ? ((Number) result).doubleValue() : def;
}
@Override
public long getLong(@NotNull String path) {
Object result = get(path);
return result instanceof Number ? ((Number) result).longValue(): 0;
}
@Override
public long getLong(@NotNull String path, long def) {
Object result = get(path);
return result instanceof Number ? ((Number) result).longValue() : def;
}
@Nullable
@Override
public List<?> getList(@NotNull String path) {
Object result = get(path);
return result instanceof List ? (List) result : null;
}
@Nullable
@Override
public List<?> getList(@NotNull String path, @Nullable List<?> def) {
Object result = get(path);
return result instanceof List ? (List) result : def;
}
@Nullable
public LegacyMaterials getMaterial(@NotNull String path) {
String val = getString(path);
LegacyMaterials mat = val != null ? LegacyMaterials.getMaterial(val) : null;
return mat;
}
@Nullable
public LegacyMaterials getMaterial(@NotNull String path, @Nullable LegacyMaterials def) {
String val = getString(path);
LegacyMaterials mat = val != null ? LegacyMaterials.getMaterial(val) : null;
return mat != null ? mat : def;
}
@Nullable
@Override
public <T> T getObject(@NotNull String path, @NotNull Class<T> clazz) {
Object result = get(path);
return result != null && clazz.isInstance(result) ? clazz.cast(result) : null;
}
@Nullable
@Override
public <T> T getObject(@NotNull String path, @NotNull Class<T> clazz, @Nullable T def) {
Object result = get(path);
return result != null && clazz.isInstance(result) ? clazz.cast(result) : def;
}
@Override
public ConfigSection getConfigurationSection(@NotNull String path) {
Object result = get(path);
return result instanceof ConfigSection ? (ConfigSection) result : null;
}
public ConfigSection getOrCreateConfigurationSection(@NotNull String path) {
Object result = get(path);
return result instanceof ConfigSection ? (ConfigSection) result : createSection(path);
}
}

View File

@ -0,0 +1,126 @@
package com.songoda.core.configuration;
import com.songoda.core.compatibility.LegacyMaterials;
import java.util.List;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
public class ConfigSetting {
final Config config;
final String key;
public ConfigSetting(@NotNull Config config, @NotNull String key) {
this.config = config;
this.key = key;
}
public ConfigSetting(@NotNull Config config, @NotNull String key, @NotNull Object defaultValue, String ... comment) {
this.config = config;
this.key = key;
config.setDefault(key, defaultValue, comment);
}
public ConfigSetting(@NotNull Config config, @NotNull String key, @NotNull Object defaultValue, ConfigFormattingRules.CommentStyle commentStyle, String ... comment) {
this.config = config;
this.key = key;
config.setDefault(key, defaultValue, commentStyle, comment);
}
@NotNull
public String getKey() {
return key;
}
public List<Integer> getIntegerList() {
return config.getIntegerList(key);
}
public List<String> getStringList() {
return config.getStringList(key);
}
public boolean getBoolean() {
return config.getBoolean(key);
}
public boolean getBoolean(boolean def) {
return config.getBoolean(key, def);
}
public int getInt() {
return config.getInt(key);
}
public int getInt(int def) {
return config.getInt(key, def);
}
public long getLong() {
return config.getLong(key);
}
public long getLong(long def) {
return config.getLong(key, def);
}
public double getDouble() {
return config.getDouble(key);
}
public double getDouble(double def) {
return config.getDouble(key, def);
}
public String getString() {
return config.getString(key);
}
public String getString(String def) {
return config.getString(key, def);
}
public Object getObject() {
return config.get(key);
}
public Object getObject(Object def) {
return config.get(key, def);
}
public <T> T getObject(@NotNull Class<T> clazz) {
return config.getObject(key, clazz);
}
public <T> T getObject(@NotNull Class<T> clazz, @Nullable T def) {
return config.getObject(key, clazz, def);
}
public char getChar() {
return config.getChar(key);
}
public char getChar(char def) {
return config.getChar(key, def);
}
@NotNull
public LegacyMaterials getMaterial() {
LegacyMaterials m = getMaterial(null);
return m != null ? m : LegacyMaterials.STONE;
}
@Nullable
public LegacyMaterials getMaterial(@Nullable LegacyMaterials def) {
//return config.getMaterial(key, def);
String val = config.getString(key);
LegacyMaterials mat = val != null ? LegacyMaterials.getMaterial(val) : null;
if (mat == null) {
System.out.println(String.format("Config value \"%s\" has an invalid material name: \"%s\"", key, val));
}
return mat != null ? mat : def;
}
}

View File

@ -0,0 +1,280 @@
package com.songoda.core.configuration.editor;
import com.songoda.core.compatibility.LegacyMaterials;
import com.songoda.core.gui.Gui;
import com.songoda.core.gui.GuiUtils;
import com.songoda.core.gui.SimplePagedGui;
import com.songoda.core.input.ChatPrompt;
import com.songoda.core.utils.ItemUtils;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import org.bukkit.ChatColor;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.configuration.MemoryConfiguration;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.plugin.java.JavaPlugin;
/**
* Edit a configuration file for a specific plugin
*
* @since 2019-08-31
* @author jascotty2
*/
public class ConfigEditorGui extends SimplePagedGui {
final JavaPlugin plugin;
final String file;
final MemoryConfiguration config;
final ConfigurationSection node;
Method configSection_getCommentString = null;
List<String> sections = new ArrayList();
List<String> settings = new ArrayList();
public ConfigEditorGui(JavaPlugin plugin, Gui parent, String file, MemoryConfiguration config) {
this(plugin, parent, file, config, config);
setOnClose((gui) -> save());
}
public ConfigEditorGui(JavaPlugin plugin, Gui parent, String file, MemoryConfiguration config, ConfigurationSection node) {
super(parent);
this.plugin = plugin;
this.file = file;
this.config = config;
this.node = node;
this.blankItem = GuiUtils.getBorderItem(LegacyMaterials.LIGHT_GRAY_STAINED_GLASS_PANE);
// if we have a ConfigSection, we can also grab comments
try {
configSection_getCommentString = node.getClass().getDeclaredMethod("getCommentString", String.class);
} catch (Exception ex) {
}
// decorate header
this.setTitle(ChatColor.DARK_BLUE + file);
this.setUseHeader(true);
headerBackItem = footerBackItem = GuiUtils.getBorderItem(LegacyMaterials.GRAY_STAINED_GLASS_PANE.getItem());
final String path = node.getCurrentPath();
this.setItem(4, configItem(LegacyMaterials.FILLED_MAP, !path.isEmpty() ? path : file , config, !path.isEmpty() ? path : null, ChatColor.BLACK.toString()));
this.setButton(8, GuiUtils.createButtonItem(LegacyMaterials.OAK_DOOR, "Exit"), (event) -> event.player.closeInventory());
// compile list of settings
for (String key : node.getKeys(false)) {
if (node.isConfigurationSection(key)) {
sections.add(key);
} else {
settings.add(key);
}
}
// next we need to display the config settings
int index = 9;
for (final String sectionKey : sections) {
setButton(index++, configItem(LegacyMaterials.WRITABLE_BOOK, ChatColor.YELLOW + sectionKey, node, sectionKey, "Click to open this section"),
(event) -> event.manager.showGUI(event.player, new ConfigEditorGui(plugin, this, file, config, node.getConfigurationSection(sectionKey))));
}
// now display individual settings
for (final String settingKey : settings) {
final Object val = node.get(settingKey);
if(val == null) continue;
else if (val instanceof Boolean) {
// toggle switch
setButton(index, configItem(LegacyMaterials.LEVER, ChatColor.YELLOW + settingKey, node, settingKey, String.valueOf((Boolean) val), "Click to toggle this setting"),
(event) -> this.toggle(event.slot, settingKey));
if ((Boolean) val) {
highlightItem(index);
}
} else if (isNumber(val)) {
// number dial
this.setButton(index, configItem(LegacyMaterials.CLOCK, ChatColor.YELLOW + settingKey, node, settingKey, String.valueOf((Number) val), "Click to edit this setting"),
(event) -> {
event.gui.exit();
ChatPrompt.showPrompt(plugin, event.player, "Enter a new number value for " + settingKey + ":", response -> {
if (!setNumber(event.slot, settingKey, response.getMessage().trim())) {
event.player.sendMessage(ChatColor.RED + "Error: \"" + response.getMessage().trim() + "\" is not a number!");
}
}).setOnClose(() -> event.manager.showGUI(event.player, this))
.setOnCancel(() -> {event.player.sendMessage(ChatColor.RED + "Edit canceled"); event.manager.showGUI(event.player, this);});
});
} else if (isMaterial(val)) {
// changing a block
// isMaterial is more of a guess, to be honest.
setButton(index, configItem(LegacyMaterials.STONE, ChatColor.YELLOW + settingKey, node, settingKey, val.toString(), "Click to edit this setting"),
(event) -> {
SimplePagedGui paged = new SimplePagedGui(this);
paged.setTitle(ChatColor.BLUE + settingKey);
paged.setHeaderBackItem(headerBackItem).setFooterBackItem(footerBackItem).setDefaultItem(blankItem);
paged.setItem(4, configItem(LegacyMaterials.FILLED_MAP, settingKey, node, settingKey, "Choose an item to change this value to"));
int i = 9;
for(LegacyMaterials mat : LegacyMaterials.getAllValidItemMaterials()) {
paged.setButton(i++, GuiUtils.createButtonItem(mat, mat.name()), ClickType.LEFT, (matEvent) -> {
setMaterial(event.slot, settingKey, matEvent.clickedItem);
matEvent.player.closeInventory();
});
}
event.manager.showGUI(event.player, paged);
});
} else if (val instanceof String) {
// changing a "string" value (or change to a feather for writing quill)
setButton(index, configItem(LegacyMaterials.STRING, ChatColor.YELLOW + settingKey, node, settingKey, val.toString(), "Click to edit this setting"),
(event) -> {
event.gui.exit();
ChatPrompt.showPrompt(plugin, event.player, "Enter a new value for " + settingKey + ":", response -> {
node.set(settingKey, response.getMessage().trim());
updateValue(event.slot, settingKey);
}).setOnClose(() -> event.manager.showGUI(event.player, this))
.setOnCancel(() -> {event.player.sendMessage(ChatColor.RED + "Edit canceled"); event.manager.showGUI(event.player, this);});
});
} else if (val instanceof List) {
setButton(index, configItem(LegacyMaterials.WRITABLE_BOOK, ChatColor.YELLOW + settingKey, node, settingKey, String.format("(%d values)", ((List) val).size()), "Click to edit this setting"),
(event) -> {
event.manager.showGUI(event.player, (new ConfigEditorListEditorGui(this, settingKey, (List) val)).setOnClose((gui) -> {
if(((ConfigEditorListEditorGui) gui.gui).saveChanges) {
setList(event.slot, settingKey, ((ConfigEditorListEditorGui) gui.gui).values);
}
}));
});
} else {
// idk. should we display uneditable values?
}
++index;
}
}
public ConfigurationSection getCurrentNode() {
return node;
}
protected void updateValue(int clickCell, String path) {
ItemStack item = inventory.getItem(clickCell);
if(item == null || item == AIR) return;
ItemMeta meta = item.getItemMeta();
Object val = node.get(path);
if (meta != null && val != null) {
String valStr;
if (val instanceof List) {
valStr = String.format("(%d values)", ((List) val).size());
} else {
valStr = val.toString();
}
List<String> lore = meta.getLore();
if (lore == null || lore.isEmpty()) {
meta.setLore(Arrays.asList(valStr));
} else {
lore.set(0, valStr);
meta.setLore(lore);
}
item.setItemMeta(meta);
setItem(clickCell, item);
}
}
void toggle(int clickCell, String path) {
boolean val = !node.getBoolean(path);
node.set(path, val);
if(val) {
setItem(clickCell, ItemUtils.addGlow(inventory.getItem(clickCell)));
} else {
setItem(clickCell, ItemUtils.removeGlow(inventory.getItem(clickCell)));
}
updateValue(clickCell, path);
}
boolean setNumber(int clickCell, String path, String input) {
try {
if (node.isInt(path)) {
node.set(path, Integer.parseInt(input));
} else if (node.isDouble(path)) {
node.set(path, Double.parseDouble(input));
} else if (node.isLong(path)) {
node.set(path, Long.parseLong(input));
}
updateValue(clickCell, path);
} catch (NumberFormatException e) {
return false;
}
return true;
}
void setMaterial(int clickCell, String path, ItemStack item) {
LegacyMaterials mat = LegacyMaterials.getMaterial(item);
if (mat == null) {
node.set(path, LegacyMaterials.STONE.name());
} else {
node.set(path, mat.name());
}
updateValue(clickCell, path);
}
void setList(int clickCell, String path, List<String> list) {
node.set(path, list);
updateValue(clickCell, path);
}
void save() {
// could also check and call saveChanges()
if (config instanceof FileConfiguration) {
try {
((FileConfiguration) config).save(new File(plugin.getDataFolder(), file));
} catch (IOException ex) {
plugin.getLogger().log(Level.SEVERE, "Failed to save config changes to " + file, ex);
return;
}
}
plugin.reloadConfig();
}
private boolean isNumber(Object value) {
return value != null && (
value instanceof Long
|| value instanceof Integer
|| value instanceof Float
|| value instanceof Double);
}
private boolean isMaterial(Object value) {
LegacyMaterials m;
return value instanceof String && value.toString().equals(value.toString().toUpperCase())
&& (m = LegacyMaterials.getMaterial(value.toString())) != null && m.isValidItem();
}
protected ItemStack configItem(LegacyMaterials type, String name, ConfigurationSection node, String path, String def) {
String[] info = null;
if (configSection_getCommentString != null) {
try {
Object comment = configSection_getCommentString.invoke(node, path);
if (comment != null) {
info = comment.toString().split("\n");
}
} catch (Exception ex) {
}
}
return GuiUtils.createButtonItem(type, name, info != null ? info : (def != null ? def.split("\n") : null));
}
protected ItemStack configItem(LegacyMaterials type, String name, ConfigurationSection node, String path, String value, String def) {
if(value == null) value = "";
String[] info = null;
if (configSection_getCommentString != null) {
try {
Object comment = configSection_getCommentString.invoke(node, path);
if (comment != null) {
info = (value + "\n" + comment.toString()).split("\n");
}
} catch (Exception ex) {
}
}
return GuiUtils.createButtonItem(type, name, info != null ? info : (def != null ? (value + "\n" + def).split("\n") : null));
}
}

View File

@ -0,0 +1,80 @@
package com.songoda.core.configuration.editor;
import com.songoda.core.compatibility.LegacyMaterials;
import com.songoda.core.gui.GuiUtils;
import com.songoda.core.gui.SimplePagedGui;
import com.songoda.core.input.ChatPrompt;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
import org.bukkit.ChatColor;
import org.bukkit.event.inventory.ClickType;
/**
* Edit a string list
*
* @since 2019-08-31
* @author jascotty2
*/
public class ConfigEditorListEditorGui extends SimplePagedGui {
final ConfigEditorGui current;
public boolean saveChanges = false;
public List<String> values;
public ConfigEditorListEditorGui(ConfigEditorGui current, String key, List<String> val) {
super(current);
this.current = current;
this.blankItem = current.getDefaultItem();
headerBackItem = footerBackItem = current.getHeaderBackItem();
setTitle(ChatColor.DARK_BLUE + "String List Editor");
this.setUseHeader(true);
this.setItem(4, current.configItem(LegacyMaterials.FILLED_MAP, key, current.getCurrentNode(), key, null));
this.setButton(8, GuiUtils.createButtonItem(LegacyMaterials.OAK_DOOR, "Exit"), (event) -> event.player.closeInventory());
this.values = new ArrayList(val);
this.setButton(8, GuiUtils.createButtonItem(LegacyMaterials.LAVA_BUCKET, ChatColor.RED + "Discard Changes"), (event) -> event.player.closeInventory());
this.setButton(0, GuiUtils.createButtonItem(LegacyMaterials.REDSTONE, ChatColor.GREEN + "Save"), (event) -> {
saveChanges = true;
event.player.closeInventory();
});
this.setButton(1, GuiUtils.createButtonItem(LegacyMaterials.CHEST, ChatColor.BLUE + "Add Item"),
(event) -> {
event.gui.exit();
ChatPrompt.showPrompt(event.manager.getPlugin(), event.player, "Enter a new value to add:", response -> {
values.add(response.getMessage().trim());
redraw();
}).setOnClose(() -> {event.manager.showGUI(event.player, this); })
.setOnCancel(() -> {event.player.sendMessage(ChatColor.RED + "Edit canceled"); event.manager.showGUI(event.player, this);});
});
redraw();
}
void redraw() {
page = 1;
// clear old display
if(inventory != null) {
for(Integer i : cellItems.keySet().toArray(new Integer[0])) {
if(i > 8) {
cellItems.remove(i);
conditionalButtons.remove(i);
}
}
}
// update items
int i = 9;
for (String item : values) {
final int index = i - 9;
setButton(i++, GuiUtils.createButtonItem(LegacyMaterials.PAPER, item, "Right-click to remove"), ClickType.RIGHT, (event) -> {
values.remove(index);
redraw();
});
}
// update display
update();
}
}

View File

@ -0,0 +1,98 @@
package com.songoda.core.configuration.editor;
import com.songoda.core.SongodaPlugin;
import com.songoda.core.compatibility.LegacyMaterials;
import com.songoda.core.configuration.Config;
import com.songoda.core.gui.Gui;
import com.songoda.core.gui.GuiUtils;
import com.songoda.core.gui.SimplePagedGui;
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.bukkit.ChatColor;
import org.bukkit.configuration.MemoryConfiguration;
import org.bukkit.plugin.java.JavaPlugin;
/**
* Edit all configuration files for a specific plugin
*
* @since 2019-08-31
* @author jascotty2
*/
public class PluginConfigGui extends SimplePagedGui {
final JavaPlugin plugin;
LinkedHashMap<String, MemoryConfiguration> configs = new LinkedHashMap();
public PluginConfigGui(SongodaPlugin plugin) {
this(plugin, null);
}
public PluginConfigGui(SongodaPlugin plugin, Gui parent) {
super(parent);
this.plugin = plugin;
// collect list of plugins
configs.put(plugin.getConfig().getCoreConfig().getFile().getName(), plugin.getConfig().getCoreConfig());
List<Config> more = plugin.getExtraConfig();
if (more != null && !more.isEmpty()) {
for (Config cfg : more) {
configs.put(cfg.getFile().getName(), cfg);
}
}
init();
}
public PluginConfigGui(JavaPlugin plugin) {
this(plugin, null);
}
public PluginConfigGui(JavaPlugin plugin, Gui parent) {
super(parent);
this.plugin = plugin;
// collect list of plugins
configs.put("config.yml", plugin.getConfig());
try {
// can we also grab extra config from this mysterious plugin?
Object more = plugin.getClass().getDeclaredMethod("getExtraConfig").invoke(plugin);
if (more instanceof List && !((List) more).isEmpty()) {
try {
// if we have the getExtraConfig function, we should also be able to get the file
Method method_Config_getFile = ((List) more).get(0).getClass().getDeclaredMethod("getFile");
for (Object cfg : ((List) more)) {
configs.put(((File) method_Config_getFile.invoke(cfg)).getName(), (MemoryConfiguration) cfg);
}
} catch (Exception ex) {
// include a failsafe, I guess
((List) more).forEach(cfg -> configs.put("(File " + configs.size() + ")", (MemoryConfiguration) cfg));
}
}
} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
// I guess not!
}
init();
}
private void init() {
this.blankItem = GuiUtils.getBorderItem(LegacyMaterials.LIGHT_GRAY_STAINED_GLASS_PANE);
// decorate header
this.setTitle(ChatColor.DARK_BLUE + plugin.getName() + " Plugin Config");
this.setUseHeader(true);
headerBackItem = footerBackItem = GuiUtils.getBorderItem(LegacyMaterials.GRAY_STAINED_GLASS_PANE.getItem());
this.setButton(8, GuiUtils.createButtonItem(LegacyMaterials.OAK_DOOR, "Exit"), (event) -> event.player.closeInventory());
// List out all config files that this plugin has
int i = 9;
for (Map.Entry<String, MemoryConfiguration> config : configs.entrySet()) {
this.setButton(i++, GuiUtils.createButtonItem(LegacyMaterials.BOOK, ChatColor.YELLOW + config.getKey(), "Click to edit this config"),
(event) -> event.manager.showGUI(event.player, new ConfigEditorGui(plugin, this, config.getKey(), config.getValue())));
}
}
}

View File

@ -0,0 +1,44 @@
package com.songoda.core.core;
import com.songoda.core.core.PluginInfoModule;
import com.songoda.core.locale.Locale;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
public class LocaleModule implements PluginInfoModule {
@Override
public void run(PluginInfo plugin) {
if(plugin.getJavaPlugin() == null || plugin.getSongodaId() <= 0) return;
JSONObject json = plugin.getJson();
try {
JSONArray files = (JSONArray) json.get("neededFiles");
for (Object o : files) {
JSONObject file = (JSONObject) o;
if (file.get("type").equals("locale")) {
downloadLocale(plugin, (String) file.get("link"), (String) file.get("name"));
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
void downloadLocale(PluginInfo plugin, String link, String fileName) throws MalformedURLException, IOException {
URL url = new URL(link);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.11 (KHTML, like Gecko) Chrome/23.0.1271.95 Safari/537.11");
urlConnection.setRequestProperty("Accept", "*/*");
urlConnection.setConnectTimeout(5000);
Locale.saveLocale(plugin.getJavaPlugin(), urlConnection.getInputStream(), fileName);
urlConnection.disconnect();
}
}

View File

@ -1,25 +1,32 @@
package com.songoda.update; package com.songoda.core.core;
import com.songoda.core.compatibility.LegacyMaterials;
import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.plugin.java.JavaPlugin;
import org.json.simple.JSONObject; import org.json.simple.JSONObject;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
public class Plugin { public final class PluginInfo {
private JavaPlugin javaPlugin; protected final JavaPlugin javaPlugin;
private int songodaId; protected final int songodaId;
private List<Module> modules = new ArrayList<>(); protected final String coreIcon;
protected final LegacyMaterials icon;
private final List<PluginInfoModule> modules = new ArrayList<>();
private boolean hasUpdate = false;
private String latestVersion; private String latestVersion;
private String notification; private String notification;
private String changeLog; private String changeLog;
private String marketplaceLink; private String marketplaceLink;
private JSONObject json; private JSONObject json;
public Plugin(JavaPlugin javaPlugin, int songodaId) { public PluginInfo(JavaPlugin javaPlugin, int songodaId, String icon) {
this.javaPlugin = javaPlugin; this.javaPlugin = javaPlugin;
this.songodaId = songodaId; this.songodaId = songodaId;
this.coreIcon = icon;
this.icon = LegacyMaterials.getMaterial(icon);
} }
public String getLatestVersion() { public String getLatestVersion() {
@ -28,6 +35,7 @@ public class Plugin {
public void setLatestVersion(String latestVersion) { public void setLatestVersion(String latestVersion) {
this.latestVersion = latestVersion; this.latestVersion = latestVersion;
hasUpdate = latestVersion != null && !latestVersion.isEmpty() && !javaPlugin.getDescription().getVersion().equalsIgnoreCase(latestVersion);
} }
public String getNotification() { public String getNotification() {
@ -38,6 +46,14 @@ public class Plugin {
this.notification = notification; this.notification = notification;
} }
public boolean hasUpdate() {
return hasUpdate;
}
public void setHasUpdate(boolean hasUpdate) {
this.hasUpdate = hasUpdate;
}
public String getChangeLog() { public String getChangeLog() {
return changeLog; return changeLog;
} }
@ -62,13 +78,13 @@ public class Plugin {
this.json = json; this.json = json;
} }
public Module addModule(Module module) { public PluginInfoModule addModule(PluginInfoModule module) {
modules.add(module); modules.add(module);
return module; return module;
} }
public List<Module> getModules() { public List<PluginInfoModule> getModules() {
return new ArrayList<>(modules); return Collections.unmodifiableList(modules);
} }
public JavaPlugin getJavaPlugin() { public JavaPlugin getJavaPlugin() {
@ -78,4 +94,8 @@ public class Plugin {
public int getSongodaId() { public int getSongodaId() {
return songodaId; return songodaId;
} }
public String getCoreIcon() {
return coreIcon;
}
} }

View File

@ -0,0 +1,7 @@
package com.songoda.core.core;
public interface PluginInfoModule {
void run(PluginInfo plugin);
}

View File

@ -0,0 +1,51 @@
package com.songoda.core.core;
import com.songoda.core.SongodaCore;
import com.songoda.core.commands.AbstractCommand;
import com.songoda.core.gui.GuiManager;
import java.util.List;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class SongodaCoreCommand extends AbstractCommand {
protected GuiManager guiManager;
public SongodaCoreCommand() {
super(false, "songoda");
}
@Override
protected ReturnType runCommand(CommandSender sender, String... args) {
if(sender instanceof Player) {
if(guiManager == null || guiManager.isClosed()) {
guiManager = new GuiManager(SongodaCore.getHijackedPlugin());
}
guiManager.showGUI((Player) sender, new SongodaCoreOverviewGUI());
} else {
sender.sendMessage("/songoda diag");
}
return ReturnType.SUCCESS;
}
@Override
public String getPermissionNode() {
return "songoda.admin";
}
@Override
public String getSyntax() {
return "/songoda";
}
@Override
public String getDescription() {
return "Displays this interface.";
}
@Override
protected List<String> onTab(CommandSender sender, String... args) {
return null;
}
}

View File

@ -1,8 +1,10 @@
package com.songoda.update.command.commands; package com.songoda.core.core;
import com.songoda.update.Plugin; import com.songoda.core.SongodaCore;
import com.songoda.update.SongodaUpdate; import com.songoda.core.commands.AbstractCommand;
import com.songoda.update.command.AbstractCommand; import com.songoda.core.compatibility.ServerProject;
import com.songoda.core.compatibility.ServerVersion;
import com.songoda.core.utils.NMSUtils;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender; import org.bukkit.command.CommandSender;
@ -11,46 +13,43 @@ import java.lang.reflect.InvocationTargetException;
import java.text.DecimalFormat; import java.text.DecimalFormat;
import java.util.List; import java.util.List;
public class CommandDiag extends AbstractCommand { public class SongodaCoreDiagCommand extends AbstractCommand {
private final String name = Bukkit.getServer().getClass().getPackage().getName();
private final String version = name.substring(name.lastIndexOf('.') + 1);
private final DecimalFormat format = new DecimalFormat("##.##"); private final DecimalFormat format = new DecimalFormat("##.##");
private Object serverInstance; private Object serverInstance;
private Field tpsField; private Field tpsField;
public SongodaCoreDiagCommand() {
public CommandDiag(AbstractCommand parent) { super(false, "diag");
super(parent, false, "diag");
try { try {
serverInstance = getNMSClass("MinecraftServer").getMethod("getServer").invoke(null); serverInstance = NMSUtils.getNMSClass("MinecraftServer").getMethod("getServer").invoke(null);
tpsField = serverInstance.getClass().getField("recentTps"); tpsField = serverInstance.getClass().getField("recentTps");
} catch (NoSuchFieldException | SecurityException | IllegalAccessException | IllegalArgumentException } catch (NoSuchFieldException | SecurityException | IllegalAccessException | IllegalArgumentException
| InvocationTargetException | NoSuchMethodException e) { | InvocationTargetException | NoSuchMethodException e) {
e.printStackTrace(); e.printStackTrace();
} }
} }
@Override @Override
protected ReturnType runCommand(SongodaUpdate instance, CommandSender sender, String... args) { protected ReturnType runCommand(CommandSender sender, String... args) {
sender.sendMessage(""); sender.sendMessage("");
sender.sendMessage("Songoda Diagnostics Information"); sender.sendMessage("Songoda Diagnostics Information");
sender.sendMessage(""); sender.sendMessage("");
sender.sendMessage("Plugins:"); sender.sendMessage("Plugins:");
for (Plugin plugin : instance.getPlugins()) { for (PluginInfo plugin : SongodaCore.getPlugins()) {
sender.sendMessage(plugin.getJavaPlugin().getName() sender.sendMessage(plugin.getJavaPlugin().getName()
+ " (" + plugin.getJavaPlugin().getDescription().getVersion() + ")"); + " (" + plugin.getJavaPlugin().getDescription().getVersion() + ")");
} }
sender.sendMessage(""); sender.sendMessage("");
sender.sendMessage("Server Version: " + Bukkit.getVersion()); sender.sendMessage("Server Version: " + Bukkit.getVersion());
sender.sendMessage("NMS: " + ServerProject.getServerVersion() + " " + ServerVersion.getServerVersionString());
sender.sendMessage("Operating System: " + System.getProperty("os.name")); sender.sendMessage("Operating System: " + System.getProperty("os.name"));
sender.sendMessage("Allocated Memory: " + format.format(Runtime.getRuntime().maxMemory() / (1024 * 1024)) + "Mb"); sender.sendMessage("Allocated Memory: " + format.format(Runtime.getRuntime().maxMemory() / (1024 * 1024)) + "Mb");
sender.sendMessage("Online Players: " + Bukkit.getOnlinePlayers().size()); sender.sendMessage("Online Players: " + Bukkit.getOnlinePlayers().size());
if(tpsField != null) {
try { try {
double[] tps = ((double[]) tpsField.get(serverInstance)); double[] tps = ((double[]) tpsField.get(serverInstance));
@ -59,12 +58,13 @@ public class CommandDiag extends AbstractCommand {
} catch (IllegalAccessException e) { } catch (IllegalAccessException e) {
e.printStackTrace(); e.printStackTrace();
} }
}
return ReturnType.SUCCESS; return ReturnType.SUCCESS;
} }
@Override @Override
protected List<String> onTab(SongodaUpdate instance, CommandSender sender, String... args) { protected List<String> onTab(CommandSender sender, String... args) {
return null; return null;
} }
@ -82,12 +82,4 @@ public class CommandDiag extends AbstractCommand {
public String getDescription() { public String getDescription() {
return "Display diagnostics information."; return "Display diagnostics information.";
} }
private Class<?> getNMSClass(String className) {
try {
return Class.forName("net.minecraft.server." + version + "." + className);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
} }

View File

@ -0,0 +1,53 @@
package com.songoda.core.core;
import com.songoda.core.SongodaCore;
import com.songoda.core.compatibility.LegacyMaterials;
import com.songoda.core.configuration.editor.PluginConfigGui;
import com.songoda.core.gui.Gui;
import com.songoda.core.gui.GuiUtils;
import java.util.List;
import org.bukkit.ChatColor;
import org.bukkit.event.inventory.ClickType;
final class SongodaCoreOverviewGUI extends Gui {
protected SongodaCoreOverviewGUI() {
List<PluginInfo> plugins = SongodaCore.getPlugins();
// could do pages, too, but don't think we'll have that many at a time for a while
int max = (int) Math.ceil(plugins.size() / 9.);
setRows(max);
setTitle("Songoda Plugins");
// TODO: this could use some decorating
for (int i = 0; i < plugins.size(); i++) {
final PluginInfo plugin = plugins.get(i);
if (plugin.hasUpdate()) {
setButton(i, GuiUtils.createButtonItem(plugin.icon != null ? plugin.icon : LegacyMaterials.STONE,
ChatColor.GOLD + plugin.getJavaPlugin().getName(),
ChatColor.GRAY + "Latest Version: " + plugin.getLatestVersion(),
ChatColor.GRAY + "Installed Version: " + plugin.getJavaPlugin().getDescription().getVersion(),
"",
"Change log:",
plugin.getChangeLog(),
"",
ChatColor.GOLD + "Click for the marketplace page link.",
ChatColor.GOLD + "Right Click to edit plugin settings."
),
ClickType.LEFT, (event) -> event.player.sendMessage(plugin.getMarketplaceLink()));
setAction(i, ClickType.RIGHT, (event) -> event.manager.showGUI(event.player, new PluginConfigGui(plugin.getJavaPlugin(), event.gui)));
highlightItem(i);
} else {
setButton(i, GuiUtils.createButtonItem(plugin.icon != null ? plugin.icon : LegacyMaterials.STONE,
ChatColor.GOLD + plugin.getJavaPlugin().getName(),
ChatColor.GRAY + "Installed Version: " + plugin.getJavaPlugin().getDescription().getVersion(),
"",
ChatColor.GOLD + "Click for the marketplace page link.",
ChatColor.GOLD + "Right Click to edit plugin settings."
),
ClickType.LEFT, (event) -> event.player.sendMessage(plugin.getMarketplaceLink()));
setAction(i, ClickType.RIGHT, (event) -> event.manager.showGUI(event.player, new PluginConfigGui(plugin.getJavaPlugin(), event.gui)));
}
}
}
}

View File

@ -0,0 +1,54 @@
package com.songoda.core.database;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
public class DataManagerAbstract {
protected final DatabaseConnector databaseConnector;
protected final Plugin plugin;
public DataManagerAbstract(DatabaseConnector databaseConnector, Plugin plugin) {
this.databaseConnector = databaseConnector;
this.plugin = plugin;
}
/**
* @return the prefix to be used by all table names
*/
public String getTablePrefix() {
return this.plugin.getDescription().getName().toLowerCase() + '_';
}
protected int lastInsertedId(Connection connection) {
String query;
if (this.databaseConnector instanceof SQLiteConnector) {
query = "SELECT last_insert_rowid()";
} else {
query = "SELECT LAST_INSERT_ID()";
}
try (Statement statement = connection.createStatement()) {
ResultSet result = statement.executeQuery(query);
result.next();
return result.getInt(1);
} catch (SQLException e) {
e.printStackTrace();
return -1;
}
}
public void async(Runnable runnable) {
Bukkit.getScheduler().runTaskAsynchronously(this.plugin, runnable);
}
public void sync(Runnable runnable) {
Bukkit.getScheduler().runTask(this.plugin, runnable);
}
}

View File

@ -0,0 +1,23 @@
package com.songoda.core.database;
import java.sql.Connection;
import java.sql.SQLException;
public abstract class DataMigration {
private final int revision;
public DataMigration(int revision) {
this.revision = revision;
}
public abstract void migrate(Connection connection, String tablePrefix) throws SQLException;
/**
* @return the revision number of this migration
*/
public int getRevision() {
return this.revision;
}
}

View File

@ -0,0 +1,104 @@
package com.songoda.core.database;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
public class DataMigrationManager {
private List<DataMigration> migrations;
private DatabaseConnector databaseConnector;
private DataManagerAbstract dataManagerAbstract;
public DataMigrationManager(DatabaseConnector databaseConnector, DataManagerAbstract dataManagerAbstract, DataMigration... migrations) {
this.databaseConnector = databaseConnector;
this.dataManagerAbstract = dataManagerAbstract;
this.migrations = Arrays.asList(migrations);
}
/**
* Runs any needed data migrations
*/
public void runMigrations() {
this.databaseConnector.connect((connection -> {
int currentMigration = -1;
boolean migrationsExist;
String query;
if (this.databaseConnector instanceof SQLiteConnector) {
query = "SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = ?";
} else {
query = "SHOW TABLES LIKE ?";
}
try (PreparedStatement statement = connection.prepareStatement(query)) {
statement.setString(1, this.getMigrationsTableName());
migrationsExist = statement.executeQuery().next();
}
if (!migrationsExist) {
// No migration table exists, create one
String createTable = "CREATE TABLE " + this.getMigrationsTableName() + " (migration_version INT NOT NULL)";
try (PreparedStatement statement = connection.prepareStatement(createTable)) {
statement.execute();
}
// Insert primary row into migration table
String insertRow = "INSERT INTO " + this.getMigrationsTableName() + " VALUES (?)";
try (PreparedStatement statement = connection.prepareStatement(insertRow)) {
statement.setInt(1, -1);
statement.execute();
}
} else {
// Grab the current migration version
String selectVersion = "SELECT migration_version FROM " + this.getMigrationsTableName();
try (PreparedStatement statement = connection.prepareStatement(selectVersion)) {
ResultSet result = statement.executeQuery();
result.next();
currentMigration = result.getInt("migration_version");
}
}
// Grab required migrations
int finalCurrentMigration = currentMigration;
List<DataMigration> requiredMigrations = this.migrations
.stream()
.filter(x -> x.getRevision() > finalCurrentMigration)
.sorted(Comparator.comparingInt(DataMigration::getRevision))
.collect(Collectors.toList());
// Nothing to migrate, abort
if (requiredMigrations.isEmpty())
return;
// Migrate the data
for (DataMigration dataMigration : requiredMigrations)
dataMigration.migrate(connection, this.dataManagerAbstract.getTablePrefix());
// Set the new current migration to be the highest migrated to
currentMigration = requiredMigrations
.stream()
.map(DataMigration::getRevision)
.max(Integer::compareTo)
.orElse(-1);
String updateVersion = "UPDATE " + this.getMigrationsTableName() + " SET migration_version = ?";
try (PreparedStatement statement = connection.prepareStatement(updateVersion)) {
statement.setInt(1, currentMigration);
statement.execute();
}
}));
}
/**
* @return the name of the migrations table
*/
private String getMigrationsTableName() {
return this.dataManagerAbstract.getTablePrefix() + "migrations";
}
}

View File

@ -0,0 +1,34 @@
package com.songoda.core.database;
import java.sql.Connection;
import java.sql.SQLException;
public interface DatabaseConnector {
/**
* Checks if the connection to the database has been created
*
* @return true if the connection is created, otherwise false
*/
boolean isInitialized();
/**
* Closes all open connections to the database
*/
void closeConnection();
/**
* Executes a callback with a Connection passed and automatically closes it when finished
*
* @param callback The callback to execute once the connection is retrieved
*/
void connect(ConnectionCallback callback);
/**
* Wraps a connection in a callback which will automagically handle catching sql errors
*/
interface ConnectionCallback {
void accept(Connection connection) throws SQLException;
}
}

View File

@ -0,0 +1,50 @@
package com.songoda.core.database;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.bukkit.plugin.Plugin;
import java.sql.Connection;
import java.sql.SQLException;
public class MySQLConnector implements DatabaseConnector {
private final Plugin plugin;
private HikariDataSource hikari;
private boolean initializedSuccessfully;
public MySQLConnector(Plugin plugin, String hostname, int port, String database, String username, String password, boolean useSSL) {
this.plugin = plugin;
HikariConfig config = new HikariConfig();
config.setJdbcUrl("jdbc:mysql://" + hostname + ":" + port + "/" + database + "?useSSL=" + useSSL);
config.setUsername(username);
config.setPassword(password);
config.setMaximumPoolSize(3);
try {
this.hikari = new HikariDataSource(config);
this.initializedSuccessfully = true;
} catch (Exception ex) {
this.initializedSuccessfully = false;
}
}
public boolean isInitialized() {
return this.initializedSuccessfully;
}
public void closeConnection() {
this.hikari.close();
}
public void connect(ConnectionCallback callback) {
try (Connection connection = this.hikari.getConnection()) {
callback.accept(connection);
} catch (SQLException ex) {
this.plugin.getLogger().severe("An error occurred executing a MySQL query: " + ex.getMessage());
ex.printStackTrace();
}
}
}

View File

@ -0,0 +1,58 @@
package com.songoda.core.database;
import org.bukkit.plugin.Plugin;
import java.io.File;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
public class SQLiteConnector implements DatabaseConnector {
private final Plugin plugin;
private final String connectionString;
private Connection connection;
public SQLiteConnector(Plugin plugin) {
this.plugin = plugin;
this.connectionString = "jdbc:sqlite:" + plugin.getDataFolder() + File.separator + plugin.getDescription().getName().toLowerCase() + ".db";
try {
Class.forName("org.sqlite.JDBC"); // This is required to put here for Spigot 1.10 and below for some reason
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
public boolean isInitialized() {
return true; // Always available
}
public void closeConnection() {
try {
if (this.connection != null) {
this.connection.close();
}
} catch (SQLException ex) {
this.plugin.getLogger().severe("An error occurred closing the SQLite database connection: " + ex.getMessage());
}
}
public void connect(ConnectionCallback callback) {
if (this.connection == null) {
try {
this.connection = DriverManager.getConnection(this.connectionString);
} catch (SQLException ex) {
this.plugin.getLogger().severe("An error occurred retrieving the SQLite database connection: " + ex.getMessage());
}
}
try {
callback.accept(this.connection);
} catch (Exception ex) {
this.plugin.getLogger().severe("An error occurred executing an SQLite query: " + ex.getMessage());
ex.printStackTrace();
}
}
}

View File

@ -0,0 +1,14 @@
package com.songoda.core.gui;
/**
* Background images available for use in toast messages
*/
public enum BackgroundType {
ADVENTURE, END, HUSBANDRY, NETHER, STONE;
final String key;
private BackgroundType() {
this.key = "minecraft:textures/gui/advancements/backgrounds/" + name().toLowerCase() + ".png";
}
}

View File

@ -0,0 +1,509 @@
package com.songoda.core.gui;
import com.songoda.core.compatibility.LegacyMaterials;
import com.songoda.core.gui.events.GuiClickEvent;
import com.songoda.core.gui.events.GuiDropItemEvent;
import com.songoda.core.gui.methods.Clickable;
import com.songoda.core.gui.methods.Closable;
import com.songoda.core.gui.methods.Droppable;
import com.songoda.core.gui.methods.Openable;
import com.songoda.core.gui.methods.Pagable;
import com.songoda.core.utils.ItemUtils;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.bukkit.entity.HumanEntity;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
/**
* TODO: does not restore inventory if server crashes while player inventory is open
* Method to fix: save inv + ender slot to file, store paper in ender inv with name of cache file, check for paper item in slot when loading
* Or just manually manage all inventories in a file and remove when restored
*
* @since 2019-08-25
* @author jascotty2
*/
public class DoubleGui extends Gui {
protected int playerRows = 4;
protected Map<Player, ItemStack[]> stash = new HashMap();
public DoubleGui(GuiType type) {
super(type);
allowDropItems = false;
}
public DoubleGui(int rows) {
super(rows);
allowDropItems = false;
}
public DoubleGui(int rows, Gui parent) {
super(rows, parent);
allowDropItems = false;
}
public int getPlayerRows() {
return playerRows;
}
// 9 -> 0 -> 54
// 18 -> 9 -> 63
// 27 -> 18 -> 72
// 0 -> 27 -> 81
// offset required to make click translations
int clickOffset(int cell) {
return 54 + (cell < 9 ? cell + 27 : cell - 9);
}
// offset required to make inventory translations
int invOffset(int cell) {
return 54 + cell;
}
public DoubleGui setPlayerUnlocked(int cell) {
unlockedCells.put(invOffset(cell), true);
return this;
}
public DoubleGui setPlayerUnlocked(int row, int col) {
unlockedCells.put(invOffset(col + row * 9), true);
return this;
}
public DoubleGui setPlayerUnlocked(int cell, boolean open) {
unlockedCells.put(invOffset(cell), open);
return this;
}
public DoubleGui setPlayerUnlocked(int row, int col, boolean open) {
unlockedCells.put(invOffset(col + row * 9), open);
return this;
}
public DoubleGui setPlayerItem(int cell, ItemStack item) {
cellItems.put(invOffset(cell), item);
if (open && cell >= 0 && cell < 36) {
cell = cell >= 27 ? cell - 27 : cell + 9;
for (HumanEntity e : inventory.getViewers()) {
e.getInventory().setItem(cell, item);
}
}
return this;
}
public DoubleGui setPlayerItem(int row, int col, ItemStack item) {
int cell = col + row * 9;
cellItems.put(invOffset(cell), item);
if (open && cell >= 0 && cell < 36) {
cell = cell >= 27 ? cell - 27 : cell + 9;
for (HumanEntity e : inventory.getViewers()) {
e.getInventory().setItem(cell, item);
}
}
return this;
}
public DoubleGui highlightPlayerItem(int cell) {
final int invCell = invOffset(cell);
ItemStack item = cellItems.get(invCell);
if (item != null) {
setPlayerItem(cell, ItemUtils.addGlow(item));
}
return this;
}
public DoubleGui highlightPlayerItem(int row, int col) {
final int cell = col + row * 9;
final int invCell = invOffset(cell);
ItemStack item = cellItems.get(invCell);
if (item != null) {
setPlayerItem(cell, ItemUtils.addGlow(item));
}
return this;
}
public DoubleGui setPlayerAction(int cell, Clickable action) {
setConditional(invOffset(cell), null, action);
return this;
}
public DoubleGui setPlayerAction(int row, int col, Clickable action) {
setConditional(invOffset(col + row * 9), null, action);
return this;
}
public DoubleGui setPlayerAction(int cell, ClickType type, Clickable action) {
setConditional(invOffset(cell), type, action);
return this;
}
public DoubleGui setPlayerAction(int row, int col, ClickType type, Clickable action) {
setConditional(invOffset(col + row * 9), type, action);
return this;
}
public DoubleGui setPlayerActionForRange(int cellFirst, int cellLast, Clickable action) {
for (int cell = cellFirst; cell <= cellLast; ++cell) {
setConditional(invOffset(cell), null, action);
}
return this;
}
public DoubleGui setPlayerActionForRange(int cellRowFirst, int cellColFirst, int cellRowLast, int cellColLast, Clickable action) {
final int last = cellColLast + cellRowLast * 9;
for (int cell = cellColFirst + cellRowFirst * 9; cell <= last; ++cell) {
setConditional(invOffset(cell), null, action);
}
return this;
}
public DoubleGui setPlayerActionForRange(int cellFirst, int cellLast, ClickType type, Clickable action) {
for (int cell = cellFirst; cell <= cellLast; ++cell) {
setConditional(invOffset(cell), type, action);
}
return this;
}
public DoubleGui setPlayerActionForRange(int cellRowFirst, int cellColFirst, int cellRowLast, int cellColLast, ClickType type, Clickable action) {
final int last = cellColLast + cellRowLast * 9;
for (int cell = cellColFirst + cellRowFirst * 9; cell <= last; ++cell) {
setConditional(invOffset(cell), type, action);
}
return this;
}
public DoubleGui clearPlayerActions(int cell) {
conditionalButtons.remove(cell = invOffset(cell));
return this;
}
public DoubleGui clearPlayerActions(int row, int col) {
final int cell = invOffset(col + row * 9);
conditionalButtons.remove(cell);
return this;
}
public DoubleGui setPlayerButton(int cell, ItemStack item, Clickable action) {
setPlayerItem(cell, item);
setConditional(invOffset(cell), null, action);
return this;
}
public DoubleGui setPlayerButton(int row, int col, ItemStack item, Clickable action) {
final int cell = col + row * 9;
setPlayerItem(cell, item);
setConditional(invOffset(cell), null, action);
return this;
}
public DoubleGui setPlayerButton(int cell, ItemStack item, ClickType type, Clickable action) {
setItem(cell, item);
setConditional(invOffset(cell), type, action);
return this;
}
public DoubleGui setPlayerButton(int row, int col, ItemStack item, ClickType type, Clickable action) {
final int cell = col + row * 9;
setPlayerItem(cell, item);
setConditional(invOffset(cell), type, action);
return this;
}
@Override
protected boolean onClickPlayerInventory(GuiManager manager, Player player, Inventory openInv, InventoryClickEvent event) {
final int cell = event.getSlot(), offsetCell = clickOffset(cell);
Map<ClickType, Clickable> conditionals = conditionalButtons.get(offsetCell);
Clickable button;
if (conditionals != null
&& ((button = conditionals.get(event.getClick())) != null || (button = conditionals.get(null)) != null)) {
button.onClick(new GuiClickEvent(manager, this, player, event, cell, true));
} else {
// no event for this button
return false;
}
event.setCancelled(!unlockedCells.entrySet().stream().anyMatch(e -> offsetCell == e.getKey() && e.getValue()));
return true;
}
@Override
protected boolean onClickOutside(GuiManager manager, Player player, InventoryClickEvent event) {
if (dropper != null) {
return dropper.onDrop(new GuiDropItemEvent(manager, this, player, event));
}
// do not allow by default
return false;
}
@Override
public void onOpen(GuiManager manager, Player player) {
// replace the player's inventory
ItemStack[] oldInv = player.getInventory().getContents();
ItemStack[] newInv = new ItemStack[oldInv.length];
for (int i = 0; i < 36; ++i) {
final ItemStack item = cellItems.get(invOffset(i < 9 ? i + 27 : i - 9));
newInv[i] = item != null ? item : blankItem;
}
stash.put(player, oldInv);
player.getInventory().setContents(newInv);
// other opening functions
super.onOpen(manager, player);
}
@Override
public void onClose(GuiManager manager, Player player) {
// restore the player's inventory
if (stash.containsKey(player)) {
player.getInventory().setContents(stash.remove(player));
player.updateInventory();
}
// other closing functions
super.onClose(manager, player);
}
/*
*********************************************************
* Other functions from GUI that we don't actually override
*********************************************************
*/
@Override
public DoubleGui setAcceptsItems(boolean acceptsItems) {
return (DoubleGui) super.setAcceptsItems(acceptsItems);
}
@Override
public DoubleGui setUnlocked(int cell) {
return (DoubleGui) super.setUnlocked(cell);
}
@Override
public DoubleGui setUnlocked(int cell, boolean open) {
return (DoubleGui) super.setUnlocked(cell, open);
}
@Override
public DoubleGui setUnlocked(int row, int col) {
return (DoubleGui) super.setUnlocked(row, col);
}
@Override
public DoubleGui setUnlocked(int row, int col, boolean open) {
return (DoubleGui) super.setUnlocked(row, col, open);
}
@Override
public DoubleGui setUnlockedRange(int cellFirst, int cellLast) {
return (DoubleGui) super.setUnlockedRange(cellFirst, cellLast);
}
@Override
public DoubleGui setUnlockedRange(int cellRowFirst, int cellColFirst, int cellRowLast, int cellColLast) {
return (DoubleGui) super.setUnlockedRange(cellRowFirst, cellColFirst, cellRowLast, cellColLast);
}
@Override
public DoubleGui setAllowDrops(boolean allow) {
return (DoubleGui) super.setAllowDrops(allow);
}
@Override
public DoubleGui setAllowClose(boolean allow) {
return (DoubleGui) super.setAllowClose(allow);
}
@Override
public DoubleGui setTitle(String title) {
return (DoubleGui) super.setTitle(title);
}
@Override
public DoubleGui setRows(int rows) {
return (DoubleGui) super.setRows(rows);
}
@Override
public DoubleGui setDefaultItem(ItemStack item) {
return (DoubleGui) super.setDefaultItem(item);
}
@Override
public DoubleGui setItem(int cell, ItemStack item) {
return (DoubleGui) super.setItem(cell, item);
}
@Override
public DoubleGui setItem(int row, int col, ItemStack item) {
return (DoubleGui) super.setItem(row, col, item);
}
@Override
public DoubleGui highlightItem(int cell) {
return (DoubleGui) super.highlightItem(cell);
}
@Override
public DoubleGui highlightItem(int row, int col) {
return (DoubleGui) super.highlightItem(row, col);
}
@Override
public DoubleGui updateItem(int cell, String name, String... lore) {
return (DoubleGui) super.updateItem(cell, name, lore);
}
@Override
public DoubleGui updateItem(int row, int col, String name, List<String> lore) {
return (DoubleGui) super.updateItem(col + row * 9, name, lore);
}
@Override
public DoubleGui updateItem(int cell, String name, List<String> lore) {
return (DoubleGui) super.updateItem(cell, name, lore);
}
@Override
public DoubleGui updateItem(int row, int col, ItemStack itemTo, String title, String... lore) {
return (DoubleGui) super.updateItem(col + row * 9, itemTo, title, lore);
}
@Override
public DoubleGui updateItem(int cell, ItemStack itemTo, String title, String... lore) {
return (DoubleGui) super.updateItem(cell, itemTo, title, lore);
}
@Override
public DoubleGui updateItem(int row, int col, LegacyMaterials itemTo, String title, String... lore) {
return (DoubleGui) super.updateItem(col + row * 9, itemTo, title, lore);
}
@Override
public DoubleGui updateItem(int cell, LegacyMaterials itemTo, String title, String... lore) {
return (DoubleGui) super.updateItem(cell, itemTo, title, lore);
}
@Override
public DoubleGui updateItem(int row, int col, ItemStack itemTo, String title, List<String> lore) {
return (DoubleGui) super.updateItem(col + row * 9, itemTo, title, lore);
}
@Override
public DoubleGui updateItem(int cell, ItemStack itemTo, String title, List<String> lore) {
return (DoubleGui) super.updateItem(cell, itemTo, title, lore);
}
@Override
public DoubleGui updateItem(int row, int col, LegacyMaterials itemTo, String title, List<String> lore) {
return (DoubleGui) super.updateItem(col + row * 9, itemTo, title, lore);
}
@Override
public DoubleGui updateItem(int cell, LegacyMaterials itemTo, String title, List<String> lore) {
return (DoubleGui) super.updateItem(cell, itemTo, title, lore);
}
@Override
public DoubleGui setAction(int cell, Clickable action) {
return (DoubleGui) super.setAction(cell, action);
}
@Override
public DoubleGui setAction(int row, int col, Clickable action) {
return (DoubleGui) super.setAction(row, col, action);
}
@Override
public DoubleGui setAction(int cell, ClickType type, Clickable action) {
return (DoubleGui) super.setAction(cell, type, action);
}
@Override
public DoubleGui setAction(int row, int col, ClickType type, Clickable action) {
return (DoubleGui) super.setAction(row, col, type, action);
}
@Override
public DoubleGui setActionForRange(int cellFirst, int cellLast, Clickable action) {
return (DoubleGui) super.setActionForRange(cellFirst, cellLast, action);
}
@Override
public DoubleGui setActionForRange(int cellRowFirst, int cellColFirst, int cellRowLast, int cellColLast, Clickable action) {
return (DoubleGui) super.setActionForRange(cellRowFirst, cellColFirst, cellRowLast, cellColLast, action);
}
@Override
public DoubleGui setActionForRange(int cellFirst, int cellLast, ClickType type, Clickable action) {
return (DoubleGui) super.setActionForRange(cellFirst, cellLast, type, action);
}
@Override
public DoubleGui setActionForRange(int cellRowFirst, int cellColFirst, int cellRowLast, int cellColLast, ClickType type, Clickable action) {
return (DoubleGui) super.setActionForRange(cellRowFirst, cellColFirst, cellRowLast, cellColLast, type, action);
}
@Override
public DoubleGui clearActions(int cell) {
return (DoubleGui) super.clearActions(cell);
}
@Override
public DoubleGui clearActions(int row, int col) {
return (DoubleGui) super.clearActions(row, col);
}
@Override
public DoubleGui setButton(int cell, ItemStack item, Clickable action) {
return (DoubleGui) super.setButton(cell, item, action);
}
@Override
public DoubleGui setButton(int row, int col, ItemStack item, Clickable action) {
return (DoubleGui) super.setButton(row, col, item, action);
}
@Override
public DoubleGui setButton(int cell, ItemStack item, ClickType type, Clickable action) {
return (DoubleGui) super.setButton(cell, item, type, action);
}
@Override
public DoubleGui setButton(int row, int col, ItemStack item, ClickType type, Clickable action) {
return (DoubleGui) super.setButton(row, col, item, type, action);
}
@Override
public DoubleGui setOnOpen(Openable action) {
return (DoubleGui) super.setOnOpen(action);
}
@Override
public DoubleGui setOnClose(Closable action) {
return (DoubleGui) super.setOnClose(action);
}
@Override
public DoubleGui setOnDrop(Droppable action) {
return (DoubleGui) super.setOnDrop(action);
}
@Override
public DoubleGui setOnPage(Pagable action) {
return (DoubleGui) super.setOnPage(action);
}
@Override
public DoubleGui setNextPage(int row, int col, ItemStack item) {
return (DoubleGui) super.setNextPage(row, col, item);
}
@Override
public DoubleGui setPrevPage(int row, int col, ItemStack item) {
return (DoubleGui) super.setPrevPage(row, col, item);
}
}

View File

@ -0,0 +1,620 @@
package com.songoda.core.gui;
import com.songoda.core.compatibility.LegacyMaterials;
import com.songoda.core.gui.events.GuiClickEvent;
import com.songoda.core.gui.events.GuiCloseEvent;
import com.songoda.core.gui.events.GuiDropItemEvent;
import com.songoda.core.gui.events.GuiOpenEvent;
import com.songoda.core.gui.methods.Pagable;
import com.songoda.core.gui.methods.Clickable;
import com.songoda.core.gui.methods.Droppable;
import com.songoda.core.gui.methods.Closable;
import com.songoda.core.gui.methods.Openable;
import com.songoda.core.utils.ItemUtils;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryType;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
/**
* TODO: animated buttons
*
* @since 2019-08-25
* @author jascotty2
*/
public class Gui {
protected Inventory inventory;
protected String title;
protected GuiType inventoryType = GuiType.STANDARD;
protected int rows, page, pages;
protected boolean acceptsItems = false;
protected boolean allowDropItems = true;
protected boolean allowClose = true;
protected final Map<Integer, Boolean> unlockedCells = new HashMap<>();
protected final Map<Integer, ItemStack> cellItems = new HashMap<>();
protected final Map<Integer, Map<ClickType, Clickable>> conditionalButtons = new HashMap<>();
protected ItemStack blankItem = GuiUtils.getBorderGlassItem();
protected int nextPageIndex, prevPageIndex;
protected ItemStack nextPage, prevPage;
protected Gui parent = null;
protected static ItemStack AIR = new ItemStack(Material.AIR);
protected boolean open = false;
protected Openable opener = null;
protected Closable closer = null;
protected Droppable dropper = null;
protected Pagable pager = null;
public Gui() {
this.rows = 3;
}
public Gui(GuiType type) {
this.inventoryType = type;
switch (type) {
case HOPPER:
case DISPENSER:
this.rows = 1;
break;
default:
this.rows = 3;
}
}
public Gui(Gui parent) {
this.parent = parent;
}
public Gui(int rows) {
this.rows = Math.max(1, Math.min(6, rows));
}
public Gui(int rows, Gui parent) {
this.parent = parent;
this.rows = Math.max(1, Math.min(6, rows));
}
public List<Player> getPlayers() {
return inventory == null ? Collections.EMPTY_LIST
: inventory.getViewers().stream()
.filter(e -> e instanceof Player)
.map(e -> (Player) e)
.collect(Collectors.toList());
}
public boolean isOpen() {
// double check
if (inventory != null && inventory.getViewers().isEmpty()) {
open = false;
}
return open;
}
public boolean getAcceptsItems() {
return acceptsItems;
}
public Gui setAcceptsItems(boolean acceptsItems) {
this.acceptsItems = acceptsItems;
return this;
}
/**
* If this is true, then items in the player's cursor when the GUI is closed
* will be cleared
*/
public boolean getAllowDrops() {
return allowDropItems;
}
/**
* Set if items in the player's cursor will be cleared when the GUI is
* closed
*/
public Gui setAllowDrops(boolean allow) {
this.allowDropItems = allow;
return this;
}
public boolean getAllowClose() {
return allowClose;
}
public Gui setAllowClose(boolean allow) {
this.allowClose = allow;
return this;
}
/**
* Close the GUI without calling onClose() and without opening any parent GUIs
*/
public void exit() {
allowClose = true;
open = false;
inventory.getViewers().stream()
.filter(e -> e instanceof Player)
.map(e -> (Player) e)
.collect(Collectors.toList())
.forEach(Player::closeInventory);
}
public GuiType getType() {
return inventoryType;
}
public Gui setUnlocked(int cell) {
unlockedCells.put(cell, true);
return this;
}
public Gui setUnlocked(int row, int col) {
final int cell = col + row * 9;
unlockedCells.put(cell, true);
return this;
}
public Gui setUnlockedRange(int cellFirst, int cellLast) {
for (int cell = cellFirst; cell <= cellLast; ++cell) {
unlockedCells.put(cell, true);
}
return this;
}
public Gui setUnlockedRange(int cellRowFirst, int cellColFirst, int cellRowLast, int cellColLast) {
final int last = cellColLast + cellRowLast * 9;
for (int cell = cellColFirst + cellRowFirst * 9; cell <= last; ++cell) {
unlockedCells.put(cell, true);
}
return this;
}
public Gui setUnlocked(int cell, boolean open) {
unlockedCells.put(cell, open);
return this;
}
public Gui setUnlocked(int row, int col, boolean open) {
final int cell = col + row * 9;
unlockedCells.put(cell, open);
return this;
}
public Gui setTitle(String title) {
this.title = title;
return this;
}
public int getRows() {
return rows;
}
public Gui setRows(int rows) {
switch (inventoryType) {
case HOPPER:
case DISPENSER:
break;
default:
this.rows = Math.max(1, Math.min(6, rows));
}
return this;
}
public Gui setDefaultItem(ItemStack item) {
blankItem = item;
return this;
}
public ItemStack getDefaultItem() {
return blankItem;
}
public Gui setItem(int cell, ItemStack item) {
cellItems.put(cell, item);
if (open && cell >= 0 && cell < inventory.getSize()) {
inventory.setItem(cell, item);
}
return this;
}
public Gui setItem(int row, int col, ItemStack item) {
final int cell = col + row * 9;
cellItems.put(cell, item);
if (open && cell >= 0 && cell < inventory.getSize()) {
inventory.setItem(cell, item);
}
return this;
}
public Gui highlightItem(int cell) {
ItemStack item = cellItems.get(cell);
if (item != null && item.getType() != Material.AIR) {
setItem(cell, ItemUtils.addGlow(item));
}
return this;
}
public Gui highlightItem(int row, int col) {
final int cell = col + row * 9;
ItemStack item = cellItems.get(cell);
if (item != null && item.getType() != Material.AIR) {
setItem(cell, ItemUtils.addGlow(item));
}
return this;
}
public Gui removeHighlight(int cell) {
ItemStack item = cellItems.get(cell);
if (item != null && item.getType() != Material.AIR) {
setItem(cell, ItemUtils.removeGlow(item));
}
return this;
}
public Gui removeHighlight(int row, int col) {
final int cell = col + row * 9;
ItemStack item = cellItems.get(cell);
if (item != null && item.getType() != Material.AIR) {
setItem(cell, ItemUtils.removeGlow(item));
}
return this;
}
public Gui updateItem(int row, int col, String name, String... lore) {
return updateItem(col + row * 9, name, lore);
}
public Gui updateItem(int cell, String name, String... lore) {
ItemStack item = cellItems.get(cell);
if (item != null && item.getType() != Material.AIR) {
setItem(cell, GuiUtils.updateItem(item, title, lore));
}
return this;
}
public Gui updateItem(int row, int col, String name, List<String> lore) {
return updateItem(col + row * 9, name, lore);
}
public Gui updateItem(int cell, String name, List<String> lore) {
ItemStack item = cellItems.get(cell);
if (item != null && item.getType() != Material.AIR) {
setItem(cell, GuiUtils.updateItem(item, title, lore));
}
return this;
}
public Gui updateItem(int row, int col, ItemStack itemTo, String title, String... lore) {
return updateItem(col + row * 9, itemTo, title, lore);
}
public Gui updateItem(int cell, ItemStack itemTo, String title, String... lore) {
ItemStack item = cellItems.get(cell);
if (item != null && item.getType() != Material.AIR) {
setItem(cell, GuiUtils.updateItem(item, itemTo, title, lore));
}
return this;
}
public Gui updateItem(int row, int col, LegacyMaterials itemTo, String title, String... lore) {
return updateItem(col + row * 9, itemTo, title, lore);
}
public Gui updateItem(int cell, LegacyMaterials itemTo, String title, String... lore) {
ItemStack item = cellItems.get(cell);
if (item != null && item.getType() != Material.AIR) {
setItem(cell, GuiUtils.updateItem(item, itemTo, title, lore));
}
return this;
}
public Gui updateItem(int row, int col, ItemStack itemTo, String title, List<String> lore) {
return updateItem(col + row * 9, itemTo, title, lore);
}
public Gui updateItem(int cell, ItemStack itemTo, String title, List<String> lore) {
ItemStack item = cellItems.get(cell);
if (item != null && item.getType() != Material.AIR) {
setItem(cell, GuiUtils.updateItem(item, itemTo, title, lore));
}
return this;
}
public Gui updateItem(int row, int col, LegacyMaterials itemTo, String title, List<String> lore) {
return updateItem(col + row * 9, itemTo, title, lore);
}
public Gui updateItem(int cell, LegacyMaterials itemTo, String title, List<String> lore) {
ItemStack item = cellItems.get(cell);
if (item != null && item.getType() != Material.AIR) {
setItem(cell, GuiUtils.updateItem(item, itemTo, title, lore));
}
return this;
}
public Gui setAction(int cell, Clickable action) {
setConditional(cell, null, action);
return this;
}
public Gui setAction(int row, int col, Clickable action) {
setConditional(col + row * 9, null, action);
return this;
}
public Gui setAction(int cell, ClickType type, Clickable action) {
setConditional(cell, type, action);
return this;
}
public Gui setAction(int row, int col, ClickType type, Clickable action) {
setConditional(col + row * 9, type, action);
return this;
}
public Gui setActionForRange(int cellFirst, int cellLast, Clickable action) {
for (int cell = cellFirst; cell <= cellLast; ++cell) {
setConditional(cell, null, action);
}
return this;
}
public Gui setActionForRange(int cellRowFirst, int cellColFirst, int cellRowLast, int cellColLast, Clickable action) {
final int last = cellColLast + cellRowLast * 9;
for (int cell = cellColFirst + cellRowFirst * 9; cell <= last; ++cell) {
setConditional(cell, null, action);
}
return this;
}
public Gui setActionForRange(int cellFirst, int cellLast, ClickType type, Clickable action) {
for (int cell = cellFirst; cell <= cellLast; ++cell) {
setConditional(cell, type, action);
}
return this;
}
public Gui setActionForRange(int cellRowFirst, int cellColFirst, int cellRowLast, int cellColLast, ClickType type, Clickable action) {
final int last = cellColLast + cellRowLast * 9;
for (int cell = cellColFirst + cellRowFirst * 9; cell <= last; ++cell) {
setConditional(cell, type, action);
}
return this;
}
public Gui clearActions(int cell) {
conditionalButtons.remove(cell);
return this;
}
public Gui clearActions(int row, int col) {
final int cell = col + row * 9;
conditionalButtons.remove(cell);
return this;
}
public Gui setButton(int cell, ItemStack item, Clickable action) {
setItem(cell, item);
setConditional(cell, null, action);
return this;
}
public Gui setButton(int row, int col, ItemStack item, Clickable action) {
final int cell = col + row * 9;
setItem(cell, item);
setConditional(cell, null, action);
return this;
}
public Gui setButton(int cell, ItemStack item, ClickType type, Clickable action) {
setItem(cell, item);
setConditional(cell, type, action);
return this;
}
public Gui setButton(int row, int col, ItemStack item, ClickType type, Clickable action) {
final int cell = col + row * 9;
setItem(cell, item);
setConditional(cell, type, action);
return this;
}
protected void setConditional(int cell, ClickType type, Clickable action) {
Map<ClickType, Clickable> conditionals = conditionalButtons.get(cell);
if (action != null) {
if (conditionals == null) {
conditionalButtons.put(cell, conditionals = new HashMap());
}
conditionals.put(type, action);
}
}
public Gui setOnOpen(Openable action) {
opener = action;
return this;
}
public Gui setOnClose(Closable action) {
closer = action;
return this;
}
public Gui setOnDrop(Droppable action) {
dropper = action;
return this;
}
public Gui setOnPage(Pagable action) {
pager = action;
return this;
}
public Gui setNextPage(int row, int col, ItemStack item) {
nextPageIndex = col + row * 9;
if (page < pages) {
setButton(nextPageIndex, item, ClickType.LEFT, (event) -> this.nextPage());
}
return this;
}
public Gui setPrevPage(int row, int col, ItemStack item) {
prevPageIndex = col + row * 9;
if (page > 1) {
setButton(prevPageIndex, item, ClickType.LEFT, (event) -> this.prevPage());
}
return this;
}
public void nextPage() {
if (page < pages) {
int lastPage = page;
++page;
// page switch events
if (pager != null) {
pager.onPageChange(this, lastPage, page);
// page markers
updatePageNavigation();
// push new inventory to the view inventory
// shouldn't be needed since adding inventory update to setItem
//update();
}
}
}
public void prevPage() {
if (page > 1) {
int lastPage = page;
--page;
if (pager != null) {
pager.onPageChange(this, lastPage, page);
// page markers
updatePageNavigation();
// push new inventory to the view inventory
// shouldn't be needed since adding inventory update to setItem
//update();
}
}
}
protected void updatePageNavigation() {
if (page > 1) {
this.setButton(prevPageIndex, prevPage, ClickType.LEFT, (event) -> this.prevPage());
} else {
this.setItem(prevPageIndex, null);
this.clearActions(prevPageIndex);
}
if (pages > 1 && page != pages) {
this.setButton(nextPageIndex, nextPage, ClickType.LEFT, (event) -> this.nextPage());
} else {
this.setItem(nextPageIndex, null);
this.clearActions(nextPageIndex);
}
}
protected Inventory getOrCreateInventory(GuiManager manager) {
return inventory != null ? inventory : generateInventory(manager);
}
protected Inventory generateInventory(GuiManager manager) {
final int cells = rows * 9;
InventoryType t = inventoryType == null ? InventoryType.CHEST : inventoryType.type;
switch (t) {
case DISPENSER:
case HOPPER:
inventory = Bukkit.getServer().createInventory(new GuiHolder(manager, this), t,
title == null ? "" : trimTitle(ChatColor.translateAlternateColorCodes('&', title)));
break;
default:
inventory = Bukkit.getServer().createInventory(new GuiHolder(manager, this), cells,
title == null ? "" : trimTitle(ChatColor.translateAlternateColorCodes('&', title)));
}
for (int i = 0; i < cells; ++i) {
final ItemStack item = cellItems.get(i);
inventory.setItem(i, item != null ? item : blankItem);
}
return inventory;
}
public Gui getParent() {
return parent;
}
public void update() {
if (inventory == null) {
return;
}
final int cells = rows * 9;
for (int i = 0; i < cells; ++i) {
final ItemStack item = cellItems.get(i);
inventory.setItem(i, item != null ? item : blankItem);
}
}
protected static String trimTitle(String title) {
if (title != null && title.length() > 32) {
return title.substring(0, 31);
}
return title;
}
protected boolean onClickOutside(GuiManager manager, Player player, InventoryClickEvent event) {
return dropper != null ? dropper.onDrop(new GuiDropItemEvent(manager, this, player, event)) : true;
}
protected boolean onClick(GuiManager manager, Player player, Inventory inventory, InventoryClickEvent event) {
final int cell = event.getSlot();
Map<ClickType, Clickable> conditionals = conditionalButtons.get(cell);
Clickable button;
if (conditionals != null
&& ((button = conditionals.get(event.getClick())) != null || (button = conditionals.get(null)) != null)) {
button.onClick(new GuiClickEvent(manager, this, player, event, cell, true));
} else {
// no event for this button
return false;
}
return true;
}
protected boolean onClickPlayerInventory(GuiManager manager, Player player, Inventory openInv, InventoryClickEvent event) {
// no events for this yet
return false;
}
public void onOpen(GuiManager manager, Player player) {
open = true;
if (opener != null) {
opener.onOpen(new GuiOpenEvent(manager, this, player));
}
}
public void onClose(GuiManager manager, Player player) {
if (!allowClose) {
manager.showGUI(player, this);
return;
}
if (open && closer != null) {
open = inventory.getViewers().isEmpty();
closer.onClose(new GuiCloseEvent(manager, this, player));
}
if (parent != null) {
manager.showGUI(player, parent);
}
}
}

View File

@ -0,0 +1,30 @@
package com.songoda.core.gui;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.InventoryHolder;
/**
* Internal class for marking an inventory as a GUI inventory
*
* @since 2019-08-25
* @author jascotty2
*/
class GuiHolder implements InventoryHolder {
final Gui gui;
final GuiManager manager;
public GuiHolder(GuiManager manager, Gui gui) {
this.gui = gui;
this.manager = manager;
}
@Override
public Inventory getInventory() {
return gui.inventory;
}
public Gui getGUI() {
return gui;
}
}

View File

@ -0,0 +1,197 @@
package com.songoda.core.gui;
import com.songoda.core.compatibility.ClientVersion;
import com.songoda.core.compatibility.CompatibleSounds;
import com.songoda.core.compatibility.LegacyMaterials;
import com.songoda.core.compatibility.ServerVersion;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.inventory.InventoryAction;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.event.inventory.InventoryCloseEvent;
import org.bukkit.event.inventory.InventoryType.SlotType;
import org.bukkit.event.server.PluginDisableEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.plugin.Plugin;
/**
* Manages events for GUI screens
*
* @since 2019-08-25
* @author jascotty2
*/
public class GuiManager {
final Plugin plugin;
final UUID uuid = UUID.randomUUID(); // manager tracking to fix weird bugs from lazy programming
final GuiListener listener = new GuiListener(this);
final Map<Player, Gui> openInventories = new HashMap();
private boolean initialized = false;
private boolean shutdown = false;
public GuiManager(Plugin plugin) {
this.plugin = plugin;
}
public Plugin getPlugin() {
return plugin;
}
/**
* Initialize the GUI handlers
*/
public void init() {
Bukkit.getPluginManager().registerEvents(listener, plugin);
initialized = true;
shutdown = false;
}
/**
* Check to see if this manager cannot open any more GUI screens
*
* @return true if the owning plugin has shutdown
*/
public boolean isClosed() {
return shutdown;
}
/**
* Create and display a GUI interface for a player
*
* @param player player to open the interface for
* @param gui GUI to use
*/
public void showGUI(Player player, Gui gui) {
if (shutdown) {
return;
} else if (!initialized) {
init();
}
Gui openInv = openInventories.get(player);
if(openInv != null) {
openInv.open = false;
}
Inventory inv = gui.generateInventory(this);
player.openInventory(inv);
gui.onOpen(this, player);
openInventories.put(player, gui);
}
public void showPopup(Player player, String message) {
showPopup(player, message, LegacyMaterials.NETHER_STAR, BackgroundType.ADVENTURE);
}
public void showPopup(Player player, String message, LegacyMaterials icon) {
showPopup(player, message, icon, BackgroundType.ADVENTURE);
}
public void showPopup(Player player, String message, LegacyMaterials icon, BackgroundType background) {
if (ClientVersion.getClientVersion(player).isServerVersionAtLeast(ServerVersion.V1_12)) {
PopupMessage popup = new PopupMessage(plugin, icon, message, background);
popup.add();
popup.grant(player);
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> {
popup.revoke(player);
popup.remove();
}, 70);
} else if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11)) {
player.sendTitle("", message, 10, 70, 10);
} else {
player.sendTitle("", message);
}
}
/**
* Close all active GUIs
*/
public void closeAll() {
openInventories.entrySet().stream()
.filter(e -> e.getKey().getOpenInventory().getTopInventory().getHolder() instanceof GuiHolder)
.collect(Collectors.toList()) // to prevent concurrency exceptions
.forEach(e -> e.getKey().closeInventory());
openInventories.clear();
}
protected static class GuiListener implements Listener {
final GuiManager manager;
public GuiListener(GuiManager manager) {
this.manager = manager;
}
@EventHandler(priority = EventPriority.LOW)
void onClickGUI(InventoryClickEvent event) {
if (!(event.getWhoClicked() instanceof Player)) {
return;
}
Inventory openInv = event.getInventory();
final Player player = (Player) event.getWhoClicked();
Gui gui;
if (openInv.getHolder() != null && openInv.getHolder() instanceof GuiHolder
&& ((GuiHolder) openInv.getHolder()).manager.uuid.equals(manager.uuid)) {
gui = ((GuiHolder) openInv.getHolder()).getGUI();
if (event.getSlotType() == SlotType.OUTSIDE) {
if (!gui.onClickOutside(manager, player, event)) {
event.setCancelled(true);
}
} // did we click the gui or in the user's inventory?
else if (event.getRawSlot() < gui.inventory.getSize()) {// or could use event.getClickedInventory() == gui.inventory
// allow event if this is not a GUI element
event.setCancelled(!gui.unlockedCells.entrySet().stream().anyMatch(e -> event.getSlot() == e.getKey() && e.getValue()));
// process button press
if (gui.onClick(manager, player, openInv, event)) {
player.playSound(player.getLocation(), CompatibleSounds.UI_BUTTON_CLICK.getSound(), 1F, 1F);
}
} else {
// Player clicked in the bottom inventory while GUI is open
if (gui.onClickPlayerInventory(manager, player, openInv, event)) {
player.playSound(player.getLocation(), CompatibleSounds.UI_BUTTON_CLICK.getSound(), 1F, 1F);
} else if (!gui.acceptsItems || event.getAction() == InventoryAction.MOVE_TO_OTHER_INVENTORY) {
event.setCancelled(true);
}
}
}
}
@EventHandler(priority = EventPriority.LOW)
void onCloseGUI(InventoryCloseEvent event) {
Inventory openInv = event.getInventory();
if (openInv.getHolder() != null && openInv.getHolder() instanceof GuiHolder
&& ((GuiHolder) openInv.getHolder()).manager.uuid.equals(manager.uuid)) {
Gui gui = ((GuiHolder) openInv.getHolder()).getGUI();
if(!gui.open) {
return;
}
final Player player = (Player) event.getPlayer();
if (!gui.allowDropItems) {
player.setItemOnCursor(null);
}
if (manager.shutdown) {
gui.onClose(manager, player);
} else {
Bukkit.getScheduler().runTaskLater(manager.plugin, () -> gui.onClose(manager, player), 1);
}
manager.openInventories.remove(player);
}
}
@EventHandler
void onDisable(PluginDisableEvent event) {
if (event.getPlugin() == manager.plugin) {
// uh-oh! Abandon ship!!
manager.shutdown = true;
manager.closeAll();
manager.initialized = false;
}
}
}
}

View File

@ -0,0 +1,17 @@
package com.songoda.core.gui;
import org.bukkit.event.inventory.InventoryType;
public enum GuiType {
STANDARD(InventoryType.CHEST),
DISPENSER(InventoryType.DISPENSER),
HOPPER(InventoryType.HOPPER);
protected final InventoryType type;
private GuiType(InventoryType type) {
this.type = type;
}
}

View File

@ -0,0 +1,259 @@
package com.songoda.core.gui;
import com.songoda.core.compatibility.LegacyMaterials;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import org.bukkit.ChatColor;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.ItemMeta;
/**
* @since 2019-08-25
* @author jascotty2
*/
public class GuiUtils {
public static ItemStack getBorderGlassItem() {
ItemStack glass = LegacyMaterials.LIGHT_BLUE_STAINED_GLASS_PANE.getItem();
ItemMeta glassmeta = glass.getItemMeta();
glassmeta.setDisplayName(ChatColor.BLACK.toString());
glass.setItemMeta(glassmeta);
return glass;
}
public static ItemStack getBorderItem(ItemStack item) {
ItemMeta glassmeta = item.getItemMeta();
glassmeta.setDisplayName(ChatColor.BLACK.toString());
item.setItemMeta(glassmeta);
return item;
}
public static ItemStack getBorderItem(LegacyMaterials mat) {
ItemStack item = mat.getItem();
ItemMeta glassmeta = item.getItemMeta();
glassmeta.setDisplayName(ChatColor.BLACK.toString());
item.setItemMeta(glassmeta);
return item;
}
public static List<String> getSafeLore(String... lines) {
return getSafeLore(Arrays.asList(lines));
}
/**
* Get a lore value that will display fine on clients using auto gui scaling
*
* @param lines lines to format
* @return newline and length-corrected item lore
*/
public static List<String> getSafeLore(List<String> lines) {
// fix newlines
ArrayList<String> newLore = new ArrayList();
for (String l : lines) {
for (String l2 : l.split("\n")) {
if (l2.length() < 54) {
newLore.add(l2);
} else {
// try to shorten the string
String shorterString = l2;
ChatColor lastColor = null; // todo? probably should also track formatting codes..
int line = 0;
while (shorterString.length() > 50) {
int breakingSpace = -1;
for (int i = 0; i < 50; ++i) {
if (shorterString.charAt(i) == ChatColor.COLOR_CHAR) {
lastColor = ChatColor.getByChar(shorterString.charAt(++i));
} else if (shorterString.charAt(i) == ' ' || shorterString.charAt(i) == '-') {
breakingSpace = i;
}
}
if (breakingSpace == -1) {
breakingSpace = Math.max(50, shorterString.length());
newLore.add((line != 0 && lastColor != null ? lastColor.toString() : "") + shorterString.substring(0, breakingSpace) + "-");
shorterString = breakingSpace == shorterString.length() ? "" : shorterString.substring(breakingSpace + 1);
} else {
newLore.add((line != 0 && lastColor != null ? lastColor.toString() : "") + shorterString.substring(0, breakingSpace));
shorterString = breakingSpace == shorterString.length() ? "" : shorterString.substring(breakingSpace + 1);
}
++line;
}
if (!shorterString.isEmpty()) {
newLore.add((line != 0 && lastColor != null ? lastColor.toString() : "") + " " + shorterString);
}
}
}
}
return newLore;
}
public static ItemStack createButtonItem(LegacyMaterials mat, String title, String... lore) {
ItemStack item = mat.getItem();
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(title);
if (lore != null) {
meta.setLore(getSafeLore(lore));
} else {
meta.setLore(Collections.EMPTY_LIST);
}
item.setItemMeta(meta);
}
return item;
}
public static ItemStack createButtonItem(ItemStack from, String title, String... lore) {
ItemStack item = from.clone();
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(title);
if (lore != null) {
meta.setLore(getSafeLore(lore));
} else {
meta.setLore(Collections.EMPTY_LIST);
}
}
item.setItemMeta(meta);
return item;
}
public static ItemStack createButtonItem(LegacyMaterials mat, String title, List<String> lore) {
ItemStack item = mat.getItem();
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(title);
if (lore != null) {
meta.setLore(getSafeLore(lore));
} else {
meta.setLore(Collections.EMPTY_LIST);
}
}
item.setItemMeta(meta);
return item;
}
public static ItemStack createButtonItem(ItemStack from, String title, List<String> lore) {
ItemStack item = from.clone();
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(title);
if (lore != null) {
meta.setLore(getSafeLore(lore));
} else {
meta.setLore(Collections.EMPTY_LIST);
}
}
item.setItemMeta(meta);
return item;
}
public static ItemStack updateItem(ItemStack item, String title, String... lore) {
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(title);
if (lore != null) {
meta.setLore(getSafeLore(lore));
} else {
meta.setLore(Collections.EMPTY_LIST);
}
}
item.setItemMeta(meta);
return item;
}
public static ItemStack updateItem(ItemStack item, LegacyMaterials matTo, String title, String... lore) {
if (!matTo.matches(item)) {
item = matTo.getItem();
}
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(title);
if (lore != null) {
meta.setLore(getSafeLore(lore));
} else {
meta.setLore(Collections.EMPTY_LIST);
}
}
item.setItemMeta(meta);
return item;
}
public static ItemStack updateItem(ItemStack item, ItemStack to, String title, String... lore) {
if (!LegacyMaterials.getMaterial(item).matches(to)) {
item = to.clone();
}
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(title);
if (lore != null) {
meta.setLore(getSafeLore(lore));
} else {
meta.setLore(Collections.EMPTY_LIST);
}
}
item.setItemMeta(meta);
return item;
}
public static ItemStack updateItem(ItemStack item, String title, List<String> lore) {
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(title);
if (lore != null) {
meta.setLore(getSafeLore(lore));
} else {
meta.setLore(Collections.EMPTY_LIST);
}
}
item.setItemMeta(meta);
return item;
}
public static ItemStack updateItem(ItemStack item, LegacyMaterials matTo, String title, List<String> lore) {
if (!matTo.matches(item)) {
item = matTo.getItem();
}
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(title);
if (lore != null) {
meta.setLore(getSafeLore(lore));
} else {
meta.setLore(Collections.EMPTY_LIST);
}
}
item.setItemMeta(meta);
return item;
}
public static ItemStack updateItem(ItemStack item, ItemStack to, String title, List<String> lore) {
if (!LegacyMaterials.getMaterial(item).matches(to)) {
item = to.clone();
}
ItemMeta meta = item.getItemMeta();
if (meta != null) {
meta.setDisplayName(title);
if (lore != null) {
meta.setLore(getSafeLore(lore));
} else {
meta.setLore(Collections.EMPTY_LIST);
}
}
item.setItemMeta(meta);
return item;
}
public static void mirrorFill(Gui gui, int row, int col, boolean mirrorRow, boolean mirrorCol, ItemStack item) {
gui.setItem(row, col, item);
if (mirrorRow) {
gui.setItem(gui.rows - row - 1, col, item);
}
if (mirrorCol) {
gui.setItem(row, 8 - col, item);
}
if (mirrorRow && mirrorCol) {
gui.setItem(gui.rows - row - 1, 8 - col, item);
}
}
}

View File

@ -0,0 +1,190 @@
package com.songoda.core.gui;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.songoda.core.compatibility.LegacyMaterials;
import com.songoda.core.compatibility.ServerVersion;
import java.util.HashSet;
import java.util.UUID;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.chat.ComponentSerializer;
import org.bukkit.Bukkit;
import org.bukkit.NamespacedKey;
import org.bukkit.advancement.Advancement;
import org.bukkit.advancement.AdvancementProgress;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
/**
* Instance of a popup message that can be sent to a player <br>
* Popup toast messages only work on Minecraft 1.12+ <br>
* Calling this class on anything below 1.12 will cause ClassLoader Exceptions!
*/
class PopupMessage {
private static final Gson gson = new GsonBuilder().setPrettyPrinting().create();
private static final HashSet<UUID> registeredMessages = new HashSet();
final UUID id = UUID.randomUUID();
private final NamespacedKey key;
private final TextComponent title;
LegacyMaterials icon;
int iconAmount = 1; // experimental, untested
TriggerType trigger = TriggerType.IMPOSSIBLE;
FrameType frame = FrameType.GOAL;
BackgroundType background = BackgroundType.ADVENTURE;
PopupMessage(Plugin source, LegacyMaterials icon, String title) {
this.key = new NamespacedKey(source, "popup/" + id);
this.title = new TextComponent(title.length() < 74 ? title : (title.substring(0, 72) + "..."));
this.icon = icon;
}
PopupMessage(Plugin source, LegacyMaterials icon, String title, BackgroundType background) {
this.key = new NamespacedKey(source, "popup/" + id);
this.title = new TextComponent(title.length() < 74 ? title : (title.substring(0, 72) + "..."));
this.icon = icon;
this.background = background;
}
private String getJSON() {
JsonObject json = new JsonObject();
JsonObject advDisplay = new JsonObject();
if (this.icon != null) {
JsonObject displayIcon = new JsonObject();
displayIcon.addProperty("item", "minecraft:" + this.icon.getMaterial().name().toLowerCase());
if (this.icon.usesData()) {
displayIcon.addProperty("data", this.icon.getData());
}
if (this.iconAmount > 1) {
displayIcon.addProperty("amount", this.iconAmount); // not entirely sure if this works
}
advDisplay.add("icon", displayIcon);
}
advDisplay.add("title", gson.fromJson(ComponentSerializer.toString(this.title), JsonElement.class));
advDisplay.addProperty("background", background.key);
advDisplay.addProperty("description", "");
advDisplay.addProperty("frame", this.frame.id);
advDisplay.addProperty("announce_to_chat", false);
advDisplay.addProperty("show_toast", true);
advDisplay.addProperty("hidden", true);
json.add("display", advDisplay);
JsonObject advCriteria = new JsonObject();
json.add("criteria", advCriteria);
JsonObject advTrigger = new JsonObject();
advTrigger.addProperty("trigger", this.trigger.getKey());
/*if() {
JsonObject advConditions = new JsonObject();
// can add items to this list with [item,amount,data]
advTrigger.add("conditions", advConditions);
}*/
advCriteria.add("mentioned", advTrigger);
return gson.toJson(json);
}
protected void grant(final Player pl) {
final Advancement adv = getAdvancement();
final AdvancementProgress progress = pl.getAdvancementProgress(adv);
if (!progress.isDone())
progress.getRemainingCriteria().forEach((crit) -> progress.awardCriteria(crit));
}
protected void revoke(final Player pl) {
final Advancement adv = getAdvancement();
final AdvancementProgress prog = pl.getAdvancementProgress(adv);
if (prog.isDone())
prog.getAwardedCriteria().forEach((crit) -> prog.revokeCriteria(crit));
}
protected void add() {
if (!registeredMessages.contains(id)) {
registeredMessages.add(id);
try {
Bukkit.getUnsafe().loadAdvancement(key, getJSON());
} catch (IllegalArgumentException e) {
Bukkit.getLogger().warning("Failed to create popup advancement!");
}
}
}
protected void remove() {
if (registeredMessages.contains(id)) {
registeredMessages.remove(id);
Bukkit.getUnsafe().removeAdvancement(key);
}
}
public Advancement getAdvancement() {
return Bukkit.getAdvancement(key);
}
public static enum FrameType {
TASK,
CHALLENGE,
GOAL;
final String id;
private FrameType() {
id = name().toLowerCase();
}
}
public static enum TriggerType {
ARBITRARY_PLAYER_TICK(ServerVersion.V1_13, "TICK"),
BRED_ANIMALS,
BREWED_POTION,
CHANGED_DIMENSION,
CONSTRUCT_BEACON,
CONSUME_ITEM,
CURED_ZOMBIE_VILLAGER,
EFFECTS_CHANGED,
ENCHANTED_ITEM,
ENTER_BLOCK,
ENTITY_HURT_PLAYER,
ENTITY_KILLED_PLAYER,
IMPOSSIBLE,
INVENTORY_CHANGED,
ITEM_DURABILITY_CHANGED,
LEVITATION,
LOCATION,
NETHER_TRAVEL,
PLACED_BLOCK,
PLAYER_HURT_ENTITY,
PLAYER_KILL_ENTITY,
RECIPE_UNLOCKED,
SLEPT_IN_BED,
SUMMONED_ENTITY,
TAME_ANIMAL,
TICK,
USED_ENDER_EYE,
USED_TOTEM,
VILLAGER_TRADE;
final ServerVersion minVersion;
final String compatible;
final String key;
private TriggerType() {
this.minVersion = ServerVersion.UNKNOWN;
this.compatible = "";
this.key = "minecraft:" + name().toLowerCase();
}
private TriggerType(ServerVersion minVersion, String compatible) {
this.minVersion = minVersion;
this.compatible = compatible;
this.key = "minecraft:" + (ServerVersion.isServerVersionAtLeast(minVersion) ? name() : compatible).toLowerCase();
}
public String getKey() {
return key;
}
}
}

View File

@ -0,0 +1,227 @@
package com.songoda.core.gui;
import com.songoda.core.compatibility.LegacyMaterials;
import static com.songoda.core.gui.Gui.trimTitle;
import com.songoda.core.gui.events.GuiClickEvent;
import com.songoda.core.gui.methods.Clickable;
import java.util.Map;
import org.bukkit.Bukkit;
import org.bukkit.ChatColor;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
/**
* Paged GUI for when you aren't going to be making too many pages
*
* @since 2019-08-31
* @author jascotty2
*/
public class SimplePagedGui extends Gui {
protected boolean useHeader;
private int rowsPerPage, maxCellSlot;
protected ItemStack headerBackItem;
protected ItemStack footerBackItem;
final int nextPageIndex = -4, prevPageIndex = -6;
public SimplePagedGui() {
this(null);
}
public SimplePagedGui(Gui parent) {
super(parent);
nextPage = GuiUtils.createButtonItem(LegacyMaterials.ARROW, "Next Page");
prevPage = GuiUtils.createButtonItem(LegacyMaterials.ARROW, "Previous Page");
}
public SimplePagedGui setUseHeader(boolean useHeader) {
this.useHeader = useHeader;
return this;
}
public ItemStack getHeaderBackItem() {
return headerBackItem;
}
public SimplePagedGui setHeaderBackItem(ItemStack headerBackItem) {
this.headerBackItem = headerBackItem;
return this;
}
public ItemStack getFooterBackItem() {
return footerBackItem;
}
public SimplePagedGui setFooterBackItem(ItemStack footerBackItem) {
this.footerBackItem = footerBackItem;
return this;
}
public SimplePagedGui setNextPage(ItemStack item) {
nextPage = item;
return this;
}
public SimplePagedGui setPrevPage(ItemStack item) {
prevPage = item;
return this;
}
@Override
public SimplePagedGui setItem(int row, int col, ItemStack item) {
return setItem(col + row * 9, item);
}
@Override
public SimplePagedGui setItem(int cell, ItemStack item) {
// set the cell relative to the current page
int cellIndex = page == 1 || (useHeader && cell < 9) ? cell : (cell + (page - 1) * (rowsPerPage * 9));
cellItems.put(cellIndex, item);
if (open && cell >= 0 && cell < inventory.getSize()) {
inventory.setItem(cell, item);
}
return this;
}
@Override
public void nextPage() {
if (page < pages) {
++page;
showPage();
}
}
@Override
public void prevPage() {
if (page > 1) {
--page;
showPage();
}
}
public void showPage() {
int startCell = useHeader ? 9 : 0;
int cellIndex = startCell + (page - 1) * (rowsPerPage * 9);
for (int i = startCell; i < (rows - 1) * 9; ++i) {
final ItemStack item = cellItems.get(cellIndex++);
inventory.setItem(i, item != null ? item : blankItem);
}
// page markers
updatePageNavigation();
}
@Override
protected void updatePageNavigation() {
if (page > 1) {
inventory.setItem((rows * 9) - 6, prevPage);
this.setButton(prevPageIndex, prevPage, ClickType.LEFT, (event) -> this.prevPage());
} else {
inventory.setItem((rows * 9) - 6, footerBackItem != null ? footerBackItem : blankItem);
this.setItem(prevPageIndex, null);
this.clearActions(prevPageIndex);
}
if (pages > 1 && page != pages) {
inventory.setItem((rows * 9) - 4, nextPage);
this.setButton(nextPageIndex, nextPage, ClickType.LEFT, (event) -> this.nextPage());
} else {
inventory.setItem((rows * 9) - 4, footerBackItem != null ? footerBackItem : blankItem);
this.setItem(nextPageIndex, null);
this.clearActions(nextPageIndex);
}
}
@Override
protected Inventory generateInventory(GuiManager manager) {
// calculate pages here
rowsPerPage = useHeader ? 4 : 5;
maxCellSlot = (this.cellItems.isEmpty() ? 0 : this.cellItems.keySet().stream().max(Integer::compare).get()) + 1;
int maxRows = (int) Math.ceil(maxCellSlot / 9.);
pages = (int) Math.ceil(maxRows / rowsPerPage);
this.setRows(maxRows + (useHeader ? 1 : 0));
// create inventory view
final int cells = rows * 9;
inventory = Bukkit.getServer().createInventory(new GuiHolder(manager, this), cells,
title == null ? "" : trimTitle(ChatColor.translateAlternateColorCodes('&', title)));
// populate and return the display inventory
page = 1;
update();
return inventory;
}
@Override
public void update() {
if (inventory == null) {
return;
}
// calculate pages here
rowsPerPage = useHeader ? 4 : 5;
maxCellSlot = (this.cellItems.isEmpty() ? 0 : this.cellItems.keySet().stream().max(Integer::compare).get()) + 1;
int maxRows = Math.max((useHeader ? 1 : 0), (int) Math.ceil(maxCellSlot / 9.));
pages = (int) Math.ceil(maxRows / rowsPerPage);
// create a new inventory if needed
final int cells = rows * 9;
boolean isNew = false;
if (cells != inventory.getSize()) {
this.setRows(maxRows + (useHeader ? 2 : 1));
inventory = Bukkit.getServer().createInventory(inventory.getHolder(), cells,
title == null ? "" : trimTitle(ChatColor.translateAlternateColorCodes('&', title)));
isNew = true;
}
// populate header
if (useHeader) {
for (int i = 0; i < 9; ++i) {
final ItemStack item = cellItems.get(i);
inventory.setItem(i, item != null ? item : (headerBackItem != null ? headerBackItem : blankItem));
}
}
// last row is dedicated to pagation
for (int i = cells - 9; i < cells; ++i) {
inventory.setItem(i, footerBackItem != null ? footerBackItem : blankItem);
}
// fill out the rest of the page
showPage();
if(isNew) {
// whoopsie!
exit();
getPlayers().forEach(player -> ((GuiHolder) inventory.getHolder()).manager.showGUI(player, this));
}
}
@Override
protected boolean onClick(GuiManager manager, Player player, Inventory inventory, InventoryClickEvent event) {
int cell = event.getSlot();
Map<ClickType, Clickable> conditionals;
if (useHeader && cell < 9) {
conditionals = conditionalButtons.get(cell);
} else if (cell >= (rows - 1) * 9) {
// footer row
conditionals = conditionalButtons.get(cell - (rows * 9));
} else {
int cellIndex = page == 1 || (useHeader && cell < 9) ? cell : (cell + (page - 1) * (rowsPerPage * 9));
conditionals = conditionalButtons.get(cellIndex);
}
Clickable button;
if (conditionals != null
&& ((button = conditionals.get(event.getClick())) != null || (button = conditionals.get(null)) != null)) {
button.onClick(new GuiClickEvent(manager, this, player, event, cell, true));
} else {
// no event for this button
return false;
}
return true;
}
}

View File

@ -0,0 +1,29 @@
package com.songoda.core.gui.events;
import com.songoda.core.gui.Gui;
import com.songoda.core.gui.GuiManager;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemStack;
public class GuiClickEvent extends GuiEvent {
public final int slot;
public final boolean guiClicked;
public final ItemStack cursor, clickedItem;
public final ClickType clickType;
public final InventoryClickEvent event;
public GuiClickEvent(GuiManager manager, Gui gui, Player player, InventoryClickEvent event, int slot, boolean guiClicked) {
super(manager, gui, player);
this.slot = slot;
this.guiClicked = guiClicked;
this.cursor = event.getCursor();
Inventory clicked = event.getClickedInventory();
this.clickedItem = clicked == null ? null : clicked.getItem(event.getSlot());
this.clickType = event.getClick();
this.event = event;
}
}

View File

@ -0,0 +1,13 @@
package com.songoda.core.gui.events;
import com.songoda.core.gui.Gui;
import com.songoda.core.gui.GuiManager;
import org.bukkit.entity.Player;
public class GuiCloseEvent extends GuiEvent {
public GuiCloseEvent(GuiManager manager, Gui gui, Player player) {
super(manager, gui, player);
}
}

View File

@ -0,0 +1,22 @@
package com.songoda.core.gui.events;
import com.songoda.core.gui.Gui;
import com.songoda.core.gui.GuiManager;
import org.bukkit.entity.Player;
import org.bukkit.event.inventory.ClickType;
import org.bukkit.event.inventory.InventoryClickEvent;
import org.bukkit.inventory.ItemStack;
public class GuiDropItemEvent extends GuiEvent {
public final ItemStack cursor;
public final ClickType clickType;
public final InventoryClickEvent event;
public GuiDropItemEvent(GuiManager manager, Gui gui, Player player, InventoryClickEvent event) {
super(manager, gui, player);
this.cursor = event.getCursor();
this.clickType = event.getClick();
this.event = event;
}
}

View File

@ -0,0 +1,19 @@
package com.songoda.core.gui.events;
import com.songoda.core.gui.Gui;
import com.songoda.core.gui.GuiManager;
import org.bukkit.entity.Player;
public abstract class GuiEvent {
public final GuiManager manager;
public final Gui gui;
public final Player player;
public GuiEvent(GuiManager manager, Gui gui, Player player) {
this.manager = manager;
this.gui = gui;
this.player = player;
}
}

View File

@ -0,0 +1,13 @@
package com.songoda.core.gui.events;
import com.songoda.core.gui.Gui;
import com.songoda.core.gui.GuiManager;
import org.bukkit.entity.Player;
public class GuiOpenEvent extends GuiEvent {
public GuiOpenEvent(GuiManager manager, Gui gui, Player player) {
super(manager, gui, player);
}
}

View File

@ -0,0 +1,8 @@
package com.songoda.core.gui.methods;
import com.songoda.core.gui.events.GuiClickEvent;
public interface Clickable {
void onClick(GuiClickEvent event);
}

View File

@ -0,0 +1,8 @@
package com.songoda.core.gui.methods;
import com.songoda.core.gui.events.GuiCloseEvent;
public interface Closable {
void onClose(GuiCloseEvent event);
}

View File

@ -0,0 +1,8 @@
package com.songoda.core.gui.methods;
import com.songoda.core.gui.events.GuiDropItemEvent;
public interface Droppable {
boolean onDrop(GuiDropItemEvent event);
}

View File

@ -0,0 +1,8 @@
package com.songoda.core.gui.methods;
import com.songoda.core.gui.events.GuiOpenEvent;
public interface Openable {
void onOpen(GuiOpenEvent event);
}

View File

@ -0,0 +1,8 @@
package com.songoda.core.gui.methods;
import com.songoda.core.gui.Gui;
public interface Pagable {
void onPageChange(Gui gui, int lastPage, int newPage);
}

View File

@ -0,0 +1,99 @@
package com.songoda.core.hooks;
import com.songoda.core.hooks.economies.Economy;
import org.bukkit.OfflinePlayer;
/**
* A convenience class for static access to an Economy HookManager
*/
public class EconomyManager {
private static final HookManager<Economy> manager = new HookManager(Economy.class);
/**
* Load all supported economy plugins. <br />
* Note: This method should be called in your plugin's onEnable() section
*/
public static void load() {
manager.load();
}
public static HookManager getManager() {
return manager;
}
/**
* Grab the default economy plugin. <br />
* NOTE: using a default economy assumes that this library is shaded
*
* @return returns null if no plugin enabled
*/
public static Economy getEconomy() {
return manager.getCurrentHook();
}
/**
* Check to see if there is a default economy loaded. <br />
* NOTE: using a default economy assumes that this library is shaded
*
* @return returns false if there are no supported economy plugins
*/
public static boolean isEnabled() {
return manager.isEnabled();
}
/**
* Get the name of the economy plugin being used. <br />
* NOTE: using a default economy assumes that this library is shaded
*
* @return
*/
public static String getName() {
return manager.getName();
}
/**
* Format the given amount to a human-readable string in this currency
* @param amt amount to display
* @return a currency string as formatted by the economy plugin
*/
public static String formatEconomy(double amt) {
return manager.isEnabled() ? manager.getCurrentHook().formatEconomy(amt) : String.valueOf(amt);
}
/**
* Check to see if a player has at least some balance available. <br />
* NOTE: using a default economy assumes that this library is shaded
*
* @param player player to check
* @param cost minimum amount this player should have
* @return true if this player can have this amount withdrawn
*/
public static boolean hasBalance(OfflinePlayer player, double cost) {
return manager.isEnabled() && manager.getCurrentHook().hasBalance(player, cost);
}
/**
* Try to withdraw an amount from a player's balance. <br />
* NOTE: using a default economy assumes that this library is shaded
*
* @param player player to check
* @param cost amount to remove from this player
* @return true if the total amount was withdrawn successfully
*/
public static boolean withdrawBalance(OfflinePlayer player, double cost) {
return manager.isEnabled() && manager.getCurrentHook().withdrawBalance(player, cost);
}
/**
* Try to add an amount to a player's balance. <br />
* NOTE: using a default economy assumes that this library is shaded
*
* @param player player to check
* @param amount amount to add to this player
* @return true if the total amount was added successfully
*/
public static boolean deposit(OfflinePlayer player, double amount) {
return manager.isEnabled() && manager.getCurrentHook().deposit(player, amount);
}
}

View File

@ -0,0 +1,60 @@
package com.songoda.core.hooks;
import com.songoda.core.hooks.stackers.Stacker;
import org.bukkit.entity.LivingEntity;
/**
* A convenience class for static access to a Stacker HookManager
*/
public class EntityStackerManager {
private static final HookManager<Stacker> manager = new HookManager(Stacker.class);
/**
* Load all supported economy plugins. <br />
* Note: This method should be called in your plugin's onEnable() section
*/
public static void load() {
manager.load();
}
public static HookManager getManager() {
return manager;
}
/**
* Grab the default hologram plugin. <br />
* NOTE: using a default hologram assumes that this library is shaded
*
* @return returns null if no plugin enabled
*/
public static Stacker getStacker() {
return manager.getCurrentHook();
}
public static boolean isStacked(LivingEntity entity) {
return manager.isEnabled() && manager.getCurrentHook().isStacked(entity);
}
public static int getSize(LivingEntity entity) {
return manager.isEnabled() ? manager.getCurrentHook().getSize(entity) : 1;
}
public static void removeOne(LivingEntity entity) {
remove(entity, 1);
}
public static void remove(LivingEntity entity, int amount) {
if (manager.isEnabled())
manager.getCurrentHook().remove(entity, amount);
}
public static void addOne(LivingEntity entity) {
add(entity, 1);
}
public static void add(LivingEntity entity, int amount) {
if (manager.isEnabled())
manager.getCurrentHook().add(entity, amount);
}
}

View File

@ -0,0 +1,68 @@
package com.songoda.core.hooks;
import com.songoda.core.hooks.holograms.Holograms;
import org.bukkit.Location;
import java.util.List;
import org.bukkit.plugin.Plugin;
/**
* A convenience class for static access to a Holograms HookManager
*/
public class HologramManager {
private static final HookManager<Holograms> manager = new HookManager(Holograms.class);
/**
* Load all supported economy plugins.<br/>
* Note: This method should be called in your plugin's onEnable() section
*
* @param plugin plugin that will be using the holograms
*/
public static void load(Plugin plugin) {
manager.load(plugin);
}
public static HookManager getManager() {
return manager;
}
/**
* Grab the default hologram plugin. <br />
* NOTE: using a default hologram assumes that this library is shaded
*
* @return returns null if no plugin enabled
*/
public static Holograms getHolograms() {
return manager.getCurrentHook();
}
public static void createHologram(Location location, String line) {
if (manager.isEnabled())
manager.getCurrentHook().createHologram(location, line);
}
public static void createHologram(Location location, List<String> lines) {
if (manager.isEnabled())
manager.getCurrentHook().createHologram(location, lines);
}
public static void removeHologram(Location location) {
if (manager.isEnabled())
manager.getCurrentHook().removeHologram(location);
}
public static void removeAllHolograms() {
if (manager.isEnabled())
manager.getCurrentHook().removeAllHolograms();
}
public static void updateHologram(Location location, String line) {
if (manager.isEnabled())
manager.getCurrentHook().updateHologram(location, line);
}
public static void updateHologram(Location location, List<String> lines) {
if (manager.isEnabled())
manager.getCurrentHook().updateHologram(location, lines);
}
}

View File

@ -0,0 +1,18 @@
package com.songoda.core.hooks;
public interface Hook {
/**
* Get the name of the plugin being used
*
* @return
*/
abstract String getName();
/**
* Check to see if the economy plugin being used is active
*
* @return true if the plugin is loaded and active
*/
abstract boolean isEnabled();
}

View File

@ -0,0 +1,189 @@
package com.songoda.core.hooks;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import org.bukkit.plugin.Plugin;
public class HookManager<T extends Hook> {
private final Class typeClass;
private T defaultHook = null;
private boolean loaded = false;
private final Map<PluginHook, T> registeredHooks = new HashMap<>();
public HookManager(Class typeClass) {
this.typeClass = typeClass;
}
/**
* Load all supported plugins.
*/
public void load() {
if (!loaded) {
registeredHooks.putAll(PluginHook.loadHooks(typeClass, null).entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey(), e -> (T) e.getValue())));
if (!registeredHooks.isEmpty()) {
defaultHook = (T) registeredHooks.values().iterator().next();
}
loaded = true;
}
}
/**
* Load all supported plugins.
* @param hookingPlugin plugin to pass to the hook handler
*/
public void load(Plugin hookingPlugin) {
if (!loaded) {
registeredHooks.putAll(PluginHook.loadHooks(typeClass, hookingPlugin).entrySet().stream()
.collect(Collectors.toMap(e -> e.getKey(), e -> (T) e.getValue())));
if (!registeredHooks.isEmpty()) {
defaultHook = (T) registeredHooks.values().iterator().next();
}
loaded = true;
}
}
/**
* Get the currently selected plugin hook. <br>
* If none were set, then the first one found is used.
*
* @return The instance of T that was created, or null if none available.
*/
public T getCurrentHook() {
return defaultHook;
}
/**
* Set the default hook to a different plugin, if that plugin exists. <br>
* If the plugin is not loaded or supported,
* the previously defined default will be used.
*
* @param name name of the plugin to use
* @return true if the default was set to this plugin
*/
public boolean setPreferredHook(String name) {
T hook = getHook(name);
if (hook != null) {
defaultHook = hook;
return true;
}
return false;
}
/**
* Set the default hook to a different plugin, if that plugin exists. <br />
* If the plugin is not loaded or supported,
* the previously defined default will be used.
*
* @param plugin plugin to use
* @return true if the default was set to this plugin
*/
public boolean setPreferredHook(PluginHook plugin) {
T hook = getHook(plugin);
if (hook != null) {
defaultHook = hook;
return true;
}
return false;
}
/**
* Try to grab the handler for this specific plugin hook.
*
* @param name plugin to use
* @return returns null if plugin is not enabled
*/
public T getHook(String name) {
if (name == null)
return null;
final String plugin = name.trim();
return (T) registeredHooks.get(registeredHooks.keySet().stream()
.filter(type -> type.plugin.equalsIgnoreCase(plugin))
.findFirst().orElse(null));
}
/**
* Try to grab the handler for this specific plugin hook.
*
* @param hook plugin to use
* @return returns null if plugin is not enabled
*/
public T getHook(PluginHook hook) {
return registeredHooks.get(hook);
}
/**
* Grab a list of all supported and loaded plugin hooks.
*
* @return an immutable collection of the loaded handler instances
*/
public Collection<T> getRegisteredHooks() {
return Collections.unmodifiableCollection(registeredHooks.values());
}
/**
* Grab a list of all supported and loaded plugin hooks.
*
* @return an immutable collection of plugin names that are loaded.
*/
public List<String> getRegisteredPlugins() {
return registeredHooks.keySet().stream()
.map(v -> v.plugin)
.collect(Collectors.toList());
}
/**
* Get a list of all supported plugins that we can hook into.
*
* @return an immutable collection of plugin names that can be used.
*/
public List<String> getPossiblePlugins() {
return PluginHook.getHooks(typeClass).stream()
.map(v -> v.plugin)
.collect(Collectors.toList());
}
/**
* Check to see if a specific plugin hook is enabled.
*
* @param name plugin to check
* @return true if this plugin is supported and loaded
*/
public boolean isEnabled(String name) {
return getHook(name) != null;
}
/**
* Check to see if a specific plugin hook is enabled.
*
* @param hook plugin to check
* @return true if this plugin is supported and loaded
*/
public boolean isEnabled(PluginHook hook) {
return registeredHooks.containsKey(hook);
}
/**
* Check to see if there is a default hook loaded.
*
* @return returns false if there are no supported plugins loaded
*/
public boolean isEnabled() {
return defaultHook != null;
}
/**
* Get the name of the default plugin being hooked into.
*
* @return plugin name, or null if none enabled.
*/
public String getName() {
return defaultHook != null ? defaultHook.getName() : null;
}
}

View File

@ -0,0 +1,132 @@
package com.songoda.core.hooks;
import com.songoda.core.hooks.economies.Economy;
import com.songoda.core.hooks.economies.PlayerPointsEconomy;
import com.songoda.core.hooks.economies.ReserveEconomy;
import com.songoda.core.hooks.economies.VaultEconomy;
import com.songoda.core.hooks.stackers.StackMob;
import com.songoda.core.hooks.stackers.Stacker;
import com.songoda.core.hooks.stackers.UltimateStacker;
import com.songoda.core.hooks.stackers.WildStacker;
import com.songoda.core.hooks.holograms.Holograms;
import com.songoda.core.hooks.holograms.HologramsHolograms;
import com.songoda.core.hooks.holograms.HolographicDisplaysHolograms;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.logging.Level;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.PluginManager;
public final class PluginHook <T extends Class> {
public static final PluginHook ECO_VAULT = new PluginHook(Economy.class, "Vault", VaultEconomy.class);
public static final PluginHook ECO_PLAYER_POINTS = new PluginHook(Economy.class, "PlayerPoints", PlayerPointsEconomy.class);
public static final PluginHook ECO_RESERVE = new PluginHook(Economy.class, "Reserve", ReserveEconomy.class);
public static final PluginHook STACKER_ULTIMATE = new PluginHook(Stacker.class, "UltimateStacker", UltimateStacker.class);
public static final PluginHook STACKER_WILD = new PluginHook(Stacker.class, "WildStacker", WildStacker.class);
public static final PluginHook STACKER_STACK_MOB = new PluginHook(Stacker.class, "StackMob", StackMob.class);
public static final PluginHook HOLO_DISPLAYS = new PluginHook(Holograms.class, "HolographicDisplays", HolographicDisplaysHolograms.class);
public static final PluginHook HOLO_HOLOGRAMS = new PluginHook(Holograms.class, "Holograms", HologramsHolograms.class);
/******* Start Manager stuff *******/
protected final T hookGeneric;
protected final String plugin;
protected final Class managerClass;
protected static Map<Class, PluginHook> hooks;
protected Constructor pluginConstructor;
private PluginHook(T type, String pluginName, Class handler) {
if (!Hook.class.isAssignableFrom(handler)) {
throw new RuntimeException("Tried to register a non-Hook plugin hook! " + pluginName + " -> " + handler.getName());
}
this.hookGeneric = type;
this.plugin = pluginName;
this.managerClass = handler;
if (hooks == null) {
hooks = new LinkedHashMap();
}
hooks.put(handler, this);
// Does this class have a plugin constructor?
try {
pluginConstructor = type.getDeclaredConstructor(Plugin.class);
} catch (NoSuchMethodException | SecurityException ex) {
// nope!
}
}
protected static Map<PluginHook, Hook> loadHooks(Class type, Plugin plugin) {
Map<PluginHook, Hook> loaded = new LinkedHashMap<>();
PluginManager pluginManager = Bukkit.getPluginManager();
for (PluginHook hook : getHooks(type)) {
if (pluginManager.isPluginEnabled(hook.plugin)) {
Hook handler = (Hook) (plugin != null ? hook.load(plugin) : hook.load());
if (handler != null && handler.isEnabled()) {
loaded.put(hook, handler);
}
}
}
return loaded;
}
protected static List<PluginHook> getHooks(Class type) {
return hooks.entrySet().parallelStream()
.filter(e -> e.getKey() == type || e.getValue().managerClass == type || type.isAssignableFrom(e.getKey()))
.map(Map.Entry::getValue)
.collect(Collectors.toList());
}
public String getPluginName() {
return plugin;
}
protected Object load() {
try {
return managerClass.cast(
pluginConstructor != null
? pluginConstructor.newInstance((Plugin) null)
: managerClass.getConstructor().newInstance());
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException ex) {
Bukkit.getLogger().log(Level.SEVERE, "Unexpected Error while creating a new Hook Manager for " + plugin, ex);
}
return null;
}
protected Object load(Plugin hookingPlugin) {
try {
return managerClass.cast(
pluginConstructor != null
? pluginConstructor.newInstance(hookingPlugin)
: managerClass.getConstructor().newInstance());
} catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException ex) {
Bukkit.getLogger().log(Level.SEVERE, "Unexpected Error while creating a new Hook Manager for " + plugin, ex);
}
return null;
}
@Override
public int hashCode() {
int hash = 3;
hash = 37 * hash + Objects.hashCode(this.plugin);
hash = 37 * hash + Objects.hashCode(this.managerClass);
return hash;
}
@Override
public boolean equals(Object obj) {
if (obj == null || getClass() != obj.getClass()) {
return false;
}
final PluginHook<?> other = (PluginHook<?>) obj;
return Objects.equals(this.plugin, other.plugin)
&& Objects.equals(this.managerClass, other.managerClass);
}
}

View File

@ -0,0 +1,72 @@
package com.songoda.core.hooks;
import com.songoda.core.hooks.worldguard.WorldGuardFlagHandler;
import org.bukkit.Chunk;
import org.bukkit.Location;
public class WorldGuardHook {
static boolean canHook, checkedCanHook = false;
private static void init() {
if(checkedCanHook) return;
try {
// if this class exists, we're good to use WG classes
Class.forName("com.sk89q.worldguard.protection.flags.Flag");
canHook = true;
} catch (ClassNotFoundException ex) {
}
checkedCanHook = true;
}
/**
* Attempt to register a worldGuard flag (ALLOW/DENY) <br />
* Note: This must be called before WorldGuard loads, or it will fail.
*
* @param flag name of the flag to set
* @param state default value of the flag
*/
public static void addHook(String flag, boolean state) {
init();
if(canHook) {
WorldGuardFlagHandler.addHook(flag, state);
}
}
/**
* Check to see if WorldGuard is installed and hooked
*
* @return true if and only if WorldGuard exists and addHook() has been
* called and added successfully
*/
public static boolean isEnabled() {
init();
return canHook && WorldGuardFlagHandler.isEnabled();
}
/**
* Checks this location to see what this flag is set to
*
* @param l location to check
* @param flag ALLOW/DENY flag to check
* @return flag state, or null if undefined
*/
public static Boolean getBooleanFlag(Location l, String flag) {
init();
return canHook ? WorldGuardFlagHandler.getBooleanFlag(l, flag) : null;
}
/**
* Query all regions that are in or intersect this chunk
*
* @param c chunk to check for regions in
* @param flag ALLOW/DENY flag to check
* @return flag state, or null if undefined
*/
public static Boolean getBooleanFlag(Chunk c, String flag) {
init();
return canHook ? WorldGuardFlagHandler.getBooleanFlag(c, flag) : null;
}
}

View File

@ -0,0 +1,45 @@
package com.songoda.core.hooks.economies;
import com.songoda.core.hooks.Hook;
import java.text.DecimalFormat;
import org.bukkit.OfflinePlayer;
public abstract class Economy implements Hook {
/**
* Check to see if a player has at least some balance available
*
* @param player player to check
* @param cost minimum amount this player should have
* @return true if this player can have this amount withdrawn
*/
public abstract boolean hasBalance(OfflinePlayer player, double cost);
/**
* Try to withdraw an amount from a player's balance
*
* @param player player to check
* @param cost amount to remove from this player
* @return true if the total amount was withdrawn successfully
*/
public abstract boolean withdrawBalance(OfflinePlayer player, double cost);
/**
* Try to add an amount to a player's balance
*
* @param player player to check
* @param amount amount to add to this player
* @return true if the total amount was added successfully
*/
public abstract boolean deposit(OfflinePlayer player, double amount);
/**
* Format the given amount to a human-readable string in this currency
* @param amt amount to display
* @return a currency string as formatted by the economy plugin
*/
public String formatEconomy(double amt) {
DecimalFormat formatter = new DecimalFormat(amt == Math.ceil(amt) ? "#,###" : "#,###.00");
return "$" + formatter.format(amt);
}
}

View File

@ -0,0 +1,48 @@
package com.songoda.core.hooks.economies;
import org.black_ixx.playerpoints.PlayerPoints;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
public class PlayerPointsEconomy extends Economy {
private final PlayerPoints playerPoints;
public PlayerPointsEconomy() {
this.playerPoints = (PlayerPoints) Bukkit.getServer().getPluginManager().getPlugin("PlayerPoints");
}
private int convertAmount(double amount) {
return (int) Math.ceil(amount);
}
@Override
public boolean isEnabled() {
return playerPoints.isEnabled();
}
@Override
public String getName() {
return "PlayerPoints";
}
@Override
public boolean hasBalance(OfflinePlayer player, double cost) {
int amount = convertAmount(cost);
return playerPoints.getAPI().look(player.getUniqueId()) >= amount;
}
@Override
public boolean withdrawBalance(OfflinePlayer player, double cost) {
int amount = convertAmount(cost);
return playerPoints.getAPI().take(player.getUniqueId(), amount);
}
@Override
public boolean deposit(OfflinePlayer player, double amount) {
int amt = convertAmount(amount);
return playerPoints.getAPI().give(player.getUniqueId(), amt);
}
}

View File

@ -0,0 +1,47 @@
package com.songoda.core.hooks.economies;
import net.tnemc.core.Reserve;
import net.tnemc.core.economy.EconomyAPI;
import org.bukkit.OfflinePlayer;
import java.math.BigDecimal;
public class ReserveEconomy extends Economy {
EconomyAPI economyAPI;
public ReserveEconomy() {
if (Reserve.instance().economyProvided())
economyAPI = Reserve.instance().economy();
}
@Override
public boolean isEnabled() {
return Reserve.instance().isEnabled();
}
@Override
public String getName() {
return "Reserve";
}
@Override
public String formatEconomy(double amt) {
return economyAPI.format(BigDecimal.valueOf(amt));
}
@Override
public boolean hasBalance(OfflinePlayer player, double cost) {
return economyAPI.hasHoldings(player.getUniqueId(), new BigDecimal(cost));
}
@Override
public boolean withdrawBalance(OfflinePlayer player, double cost) {
return economyAPI.removeHoldings(player.getUniqueId(), new BigDecimal(cost));
}
@Override
public boolean deposit(OfflinePlayer player, double amount) {
return economyAPI.addHoldings(player.getUniqueId(), new BigDecimal(amount));
}
}

View File

@ -0,0 +1,51 @@
package com.songoda.core.hooks.economies;
import org.bukkit.Bukkit;
import org.bukkit.OfflinePlayer;
import org.bukkit.plugin.RegisteredServiceProvider;
public class VaultEconomy extends Economy {
private final net.milkbowl.vault.economy.Economy vault;
public VaultEconomy() {
// this returns null if we have Vault with no compatibe eco plugin
RegisteredServiceProvider<net.milkbowl.vault.economy.Economy> v = Bukkit.getServicesManager().getRegistration(net.milkbowl.vault.economy.Economy.class);
if(v != null) {
this.vault = v.getProvider();
} else {
// whoopsie!
this.vault = null;
}
}
@Override
public boolean isEnabled() {
return vault != null;
}
@Override
public String getName() {
return "Vault";
}
@Override
public String formatEconomy(double amt) {
return vault != null ? vault.format(amt) : super.formatEconomy(amt);
}
@Override
public boolean hasBalance(OfflinePlayer player, double cost) {
return vault != null && vault.has(player, cost);
}
@Override
public boolean withdrawBalance(OfflinePlayer player, double cost) {
return vault != null && vault.withdrawPlayer(player, cost).transactionSuccess();
}
@Override
public boolean deposit(OfflinePlayer player, double amount) {
return vault != null && vault.depositPlayer(player, amount).transactionSuccess();
}
}

View File

@ -0,0 +1,59 @@
package com.songoda.core.hooks.holograms;
import com.songoda.core.hooks.Hook;
import org.bukkit.Location;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.Collections;
import java.util.List;
public abstract class Holograms implements Hook {
protected double xOffset = 0.5;
protected double yOffset = 0.5;
protected double zOffset = 0.5;
protected final JavaPlugin plugin;
public Holograms(JavaPlugin plugin) {
this.plugin = plugin;
}
public Holograms setPositionOffset(double x, double y, double z) {
this.xOffset = x;
this.yOffset = y;
this.zOffset = z;
return this;
}
/**
* Center and offset this location
*
* @param location location to offset
* @return copy-safe location with the applied offset.
*/
protected final Location fixLocation(Location location) {
double x = location.getX();
double y = location.getY();
double z = location.getZ();
return location.clone().add((x - (int) x) + xOffset, (y - (int) y) + yOffset + defaultHeightOffset(), (z - (int) z) + zOffset);
}
protected abstract double defaultHeightOffset();
public void createHologram(Location location, String line) {
createHologram(location, Collections.singletonList(line));
}
public abstract void createHologram(Location location, List<String> lines);
public abstract void removeHologram(Location location);
public void updateHologram(Location location, String line) {
updateHologram(location, Collections.singletonList(line));
}
public abstract void updateHologram(Location location, List<String> lines);
public abstract void removeAllHolograms();
}

View File

@ -0,0 +1,102 @@
package com.songoda.core.hooks.holograms;
import com.sainttx.holograms.api.Hologram;
import com.sainttx.holograms.api.HologramPlugin;
import com.sainttx.holograms.api.line.HologramLine;
import com.sainttx.holograms.api.line.TextLine;
import java.util.HashSet;
import org.bukkit.Location;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.List;
import org.bukkit.Bukkit;
public class HologramsHolograms extends Holograms {
HologramPlugin hologramPlugin;
HashSet<String> ourHolograms = new HashSet();
public HologramsHolograms(JavaPlugin plugin) {
super(plugin);
hologramPlugin = (HologramPlugin) Bukkit.getPluginManager().getPlugin("Holograms");
}
@Override
public String getName() {
return "Holograms";
}
@Override
public boolean isEnabled() {
return hologramPlugin.isEnabled();
}
@Override
protected double defaultHeightOffset() {
return 0.5;
}
@Override
public void createHologram(Location location, List<String> lines) {
createAt(fixLocation(location), lines);
}
@Override
public void removeHologram(Location location) {
location = fixLocation(location);
final String id = locStr(location);
Hologram hologram = hologramPlugin.getHologramManager().getHologram(id);
if (hologram != null) {
hologram.despawn();
hologramPlugin.getHologramManager().removeActiveHologram(hologram);
}
ourHolograms.remove(id);
}
@Override
public void removeAllHolograms() {
for(String id : ourHolograms) {
Hologram hologram = hologramPlugin.getHologramManager().getHologram(id);
if (hologram != null) {
hologram.despawn();
hologramPlugin.getHologramManager().removeActiveHologram(hologram);
}
}
ourHolograms.clear();
}
@Override
public void updateHologram(Location location, List<String> lines) {
location = fixLocation(location);
Hologram hologram = hologramPlugin.getHologramManager().getHologram(locStr(location));
if (hologram != null) {
for(HologramLine line : hologram.getLines().toArray(new HologramLine[0])) {
hologram.removeLine(line);
}
for (String line : lines) {
hologram.addLine(new TextLine(hologram, line));
}
return;
}
createAt(location, lines);
}
private String locStr(Location loc) {
return String.format("%s-%d-%d-%d", loc.getWorld().getName(), loc.getBlockX(), loc.getBlockY(), loc.getBlockZ());
}
private void createAt(Location location, List<String> lines) {
final String id = locStr(location);
Hologram hologram = new Hologram(id, location);
for (String line : lines) {
hologram.addLine(new TextLine(hologram, line));
}
hologramPlugin.getHologramManager().addActiveHologram(hologram);
if(!ourHolograms.contains(id))
ourHolograms.add(id);
}
}

View File

@ -0,0 +1,75 @@
package com.songoda.core.hooks.holograms;
import com.gmail.filoghost.holographicdisplays.api.Hologram;
import com.gmail.filoghost.holographicdisplays.api.HologramsAPI;
import org.bukkit.Location;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.List;
public class HolographicDisplaysHolograms extends Holograms {
public HolographicDisplaysHolograms(JavaPlugin plugin) {
super(plugin);
}
@Override
public String getName() {
return "HolographicDisplays";
}
@Override
public boolean isEnabled() {
return true;
}
@Override
protected double defaultHeightOffset() {
return 1;
}
@Override
public void createHologram(Location location, List<String> lines) {
createAt(fixLocation(location), lines);
}
@Override
public void removeHologram(Location location) {
location = fixLocation(location);
for (Hologram hologram : HologramsAPI.getHolograms(plugin)) {
if (hologram.getX() != location.getX()
|| hologram.getY() != location.getY()
|| hologram.getZ() != location.getZ()) continue;
hologram.delete();
}
}
@Override
public void updateHologram(Location location, List<String> lines) {
location = fixLocation(location);
for (Hologram hologram : HologramsAPI.getHolograms(plugin)) {
if (hologram.getX() != location.getX()
|| hologram.getY() != location.getY()
|| hologram.getZ() != location.getZ()) continue;
hologram.clearLines();
for (String line : lines) {
hologram.appendTextLine(line);
}
return;
}
createAt(location, lines);
}
private void createAt(Location location, List<String> lines) {
Hologram hologram = HologramsAPI.createHologram(plugin, location);
for (String line : lines) {
hologram.appendTextLine(line);
}
}
@Override
public void removeAllHolograms() {
HologramsAPI.getHolograms(plugin).stream().forEach(x -> x.delete());
}
}

View File

@ -0,0 +1,75 @@
package com.songoda.core.hooks.stackers;
import org.bukkit.Bukkit;
import org.bukkit.entity.Item;
import org.bukkit.entity.LivingEntity;
import uk.antiperson.stackmob.api.EntityManager;
import uk.antiperson.stackmob.api.StackedEntity;
public class StackMob extends Stacker {
private final EntityManager plugin;
public StackMob() {
this.plugin = new EntityManager((uk.antiperson.stackmob.StackMob) Bukkit.getPluginManager().getPlugin("StackMob"));
}
@Override
public String getName() {
return "StackMob";
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean supportsItemStacking() {
return false;
}
@Override
public boolean supportsEntityStacking() {
return true;
}
/**
* Don't do it.
*/
@Override
public void setItemAmount(Item item, int amount) {
// idk, if you ignored the warnings and still use this method, we can at least try to help out
item.getItemStack().setAmount(amount);
}
/**
* If you use this method, you're pretty lazy. Didn't you see supportsItemStacking()?
*/
@Override
public int getItemAmount(Item item) {
return item.getItemStack().getAmount();
}
@Override
public boolean isStacked(LivingEntity entity) {
return plugin.isStackedEntity(entity);
}
@Override
public int getSize(LivingEntity entity) {
return plugin.getStackedEntity(entity).getSize();
}
@Override
public void remove(LivingEntity entity, int amount) {
StackedEntity stackedEntity = plugin.getStackedEntity(entity);
stackedEntity.setSize(stackedEntity.getSize() - amount);
}
@Override
public void add(LivingEntity entity, int amount) {
StackedEntity stackedEntity = plugin.getStackedEntity(entity);
stackedEntity.setSize(stackedEntity.getSize() + amount);
}
}

View File

@ -0,0 +1,32 @@
package com.songoda.core.hooks.stackers;
import com.songoda.core.hooks.Hook;
import org.bukkit.entity.Item;
import org.bukkit.entity.LivingEntity;
public abstract class Stacker implements Hook {
public abstract boolean supportsItemStacking();
public abstract boolean supportsEntityStacking();
public abstract void setItemAmount(Item item, int amount);
public abstract int getItemAmount(Item item);
public abstract boolean isStacked(LivingEntity entity);
public abstract int getSize(LivingEntity entity);
public void removeOne(LivingEntity entity) {
remove(entity, 1);
}
public abstract void remove(LivingEntity entity, int amount);
public void addOne(LivingEntity entity) {
add(entity, 1);
}
public abstract void add(LivingEntity entity, int amount);
}

View File

@ -0,0 +1,83 @@
package com.songoda.core.hooks.stackers;
import com.songoda.ultimatestacker.entity.EntityStack;
import com.songoda.ultimatestacker.utils.Methods;
import java.lang.reflect.Method;
import org.bukkit.entity.Item;
import org.bukkit.entity.LivingEntity;
public class UltimateStacker extends Stacker {
private final com.songoda.ultimatestacker.UltimateStacker plugin;
private boolean oldItemMethods = false;
private Method oldUltimateStacker_updateItemAmount;
public UltimateStacker() {
this.plugin = com.songoda.ultimatestacker.UltimateStacker.getInstance();
try {
oldUltimateStacker_updateItemAmount = com.songoda.ultimatestacker.utils.Methods.class.getDeclaredMethod("updateItemAmount", Item.class, int.class);
oldItemMethods = true;
} catch (NoSuchMethodException | SecurityException ex) {
}
}
@Override
public String getName() {
return "UltimateStacker";
}
@Override
public boolean isEnabled() {
return plugin.isEnabled();
}
@Override
public boolean supportsItemStacking() {
return true;
}
@Override
public boolean supportsEntityStacking() {
return true;
}
@Override
public void setItemAmount(Item item, int amount) {
if (oldItemMethods) {
// TODO: direct reference when this is re-added to the API
try {
oldUltimateStacker_updateItemAmount.invoke(null, item, amount);
} catch (Exception ex) {
item.remove(); // not the best solution, but prevents duping
}
} else {
Methods.updateItemAmount(item, item.getItemStack(), amount);
}
}
@Override
public int getItemAmount(Item item) {
return Methods.getActualItemAmount(item);
}
@Override
public boolean isStacked(LivingEntity entity) {
return plugin.getEntityStackManager().isStacked(entity);
}
@Override
public int getSize(LivingEntity entity) {
return isStacked(entity) ? plugin.getEntityStackManager().getStack(entity).getAmount() : 0;
}
@Override
public void remove(LivingEntity entity, int amount) {
EntityStack stack = plugin.getEntityStackManager().getStack(entity);
stack.setAmount(stack.getAmount() - amount);
}
@Override
public void add(LivingEntity entity, int amount) {
plugin.getEntityStackManager().getStack(entity).addAmount(amount);
}
}

View File

@ -0,0 +1,61 @@
package com.songoda.core.hooks.stackers;
import com.bgsoftware.wildstacker.api.WildStackerAPI;
import com.bgsoftware.wildstacker.api.objects.StackedEntity;
import org.bukkit.entity.Item;
import org.bukkit.entity.LivingEntity;
public class WildStacker extends Stacker {
@Override
public String getName() {
return "WildStacker";
}
@Override
public boolean isEnabled() {
return true;
}
@Override
public boolean supportsItemStacking() {
return true;
}
@Override
public boolean supportsEntityStacking() {
return true;
}
@Override
public void setItemAmount(Item item, int amount) {
WildStackerAPI.getStackedItem(item).setStackAmount(amount, true);
}
@Override
public int getItemAmount(Item item) {
return WildStackerAPI.getItemAmount(item);
}
@Override
public boolean isStacked(LivingEntity entity) {
return WildStackerAPI.getEntityAmount(entity) != 0;
}
@Override
public int getSize(LivingEntity entity) {
return WildStackerAPI.getEntityAmount(entity);
}
@Override
public void remove(LivingEntity entity, int amount) {
StackedEntity stackedEntity = WildStackerAPI.getStackedEntity(entity);
stackedEntity.setStackAmount(stackedEntity.getStackAmount() - amount, true);
}
@Override
public void add(LivingEntity entity, int amount) {
StackedEntity stackedEntity = WildStackerAPI.getStackedEntity(entity);
stackedEntity.setStackAmount(stackedEntity.getStackAmount() + amount, true);
}
}

View File

@ -0,0 +1,326 @@
/**
* Hooks for adding a custom WorldGuard flag
*
* Note: Hooks must be added before WG loads!
*/
package com.songoda.core.hooks.worldguard;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.math.BlockVector3;
import com.sk89q.worldguard.WorldGuard;
import com.sk89q.worldguard.protection.ApplicableRegionSet;
import com.sk89q.worldguard.protection.association.RegionAssociable;
import com.sk89q.worldguard.protection.flags.Flag;
import com.sk89q.worldguard.protection.flags.StateFlag;
import com.sk89q.worldguard.protection.flags.StateFlag.State;
import com.sk89q.worldguard.protection.managers.RegionManager;
import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion;
import com.sk89q.worldguard.protection.regions.RegionContainer;
import com.sk89q.worldguard.protection.regions.RegionQuery;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Level;
import java.util.stream.Stream;
import org.bukkit.Bukkit;
import org.bukkit.Chunk;
import org.bukkit.Location;
public class WorldGuardFlagHandler {
static Boolean wgPlugin = null;
static Object worldGuardPlugin;
static boolean legacy_v6 = false;
static boolean legacy_v5 = false;
static boolean hooksInstalled = false;
static Map<String, Object> flags = new HashMap();
/**
* Attempt to register a worldGuard flag (ALLOW/DENY) <br />
* Note: This must be called before WorldGuard loads, or it will fail.
*
* @param flag name of the flag to set
* @param state default value of the flag
*/
public static void addHook(String flag, boolean state) {
if (wgPlugin == null && (wgPlugin = (worldGuardPlugin = Bukkit.getPluginManager().getPlugin("WorldGuard")) != null)) {
try {
// if this class exists, we're on 6.0
Class.forName("com.sk89q.worldguard.protection.flags.registry.SimpleFlagRegistry");
legacy_v6 = true;
} catch (ClassNotFoundException ex) {
}
if(!legacy_v6) {
try {
// if this class exists, we're on 5.x
Class.forName("com.sk89q.worldguard.protection.flags.DefaultFlag");
legacy_v5 = true;
} catch (ClassNotFoundException ex) {
}
}
}
if (!wgPlugin) return;
if (legacy_v6 || legacy_v5) {
addLegacyHook(flag, state);
return;
}
StateFlag addFlag = new StateFlag(flag, state);
try {
WorldGuard.getInstance().getFlagRegistry().register(addFlag);
flags.put(flag, addFlag);
} catch (Exception ex) {
Bukkit.getServer().getLogger().log(Level.WARNING, "Could not add flag {0} to WorldGuard", addFlag.getName());
Flag wgFlag = (StateFlag) WorldGuard.getInstance().getFlagRegistry().get(addFlag.getName());
if (wgFlag == null) {
wgPlugin = false;
Bukkit.getServer().getLogger().log(Level.WARNING, "Could not hook WorldGuard");
} else {
flags.put(flag, wgFlag);
Bukkit.getServer().getLogger().log(Level.WARNING, "Loaded existing {1} {0}", new Object[] {wgFlag.getName(), wgFlag.getClass().getSimpleName()});
}
}
}
// reflection to add hooks
private static void addLegacyHook(String flag, boolean state) {
try {
// 6.0 has the same classpath for StateFlag as the current version does
// does this flag exist already?
Class defaultFlagClazz = Class.forName("com.sk89q.worldguard.protection.flags.DefaultFlag");
Field flagField = defaultFlagClazz.getField("flagsList");
Flag<?>[] flagsOld = (Flag<?>[]) flagField.get(null);
Flag wgFlag = Stream.of(flagsOld)
.filter(f -> ((Flag<?>)f).getName().equalsIgnoreCase(flag))
.findFirst().orElse(null);
if (wgFlag != null) {
// we already have one
flags.put(flag, wgFlag);
Bukkit.getServer().getLogger().log(Level.WARNING, "Loaded existing {1} {0}", new Object[] {wgFlag.getName(), wgFlag.getClass().getSimpleName()});
return;
}
// if not, we need to add one
wgFlag = new StateFlag(flag, state);
// we need to sneak our flag into the array
// make a copy first
Flag<?>[] flagsNew = new Flag<?>[flagsOld.length + 1];
System.arraycopy(flagsOld, 0, flagsNew, 0, flagsOld.length);
// add ours
flagsNew[flagsNew.length - 1] = wgFlag;
// and put the new list into place
setStaticField(flagField, flagsNew);
if(legacy_v6) {
// register this flag in the registry
Object flagRegistry = getPrivateField(worldGuardPlugin.getClass(), worldGuardPlugin, "flagRegistry");
Class simpleFlagRegistryClazz = Class.forName("com.sk89q.worldguard.protection.flags.registry.SimpleFlagRegistry");
Method registerSimpleFlagRegistry = simpleFlagRegistryClazz.getDeclaredMethod("register", Flag.class);
registerSimpleFlagRegistry.invoke(flagRegistry, wgFlag);
}
// all good!
flags.put(flag, wgFlag);
} catch (Exception ex) {
//Bukkit.getServer().getLogger().log(Level.WARNING, "Failed to set legacy WorldGuard Flags", ex);
Bukkit.getServer().getLogger().log(Level.WARNING, "Could not add flag {0} to WorldGuard", flag);
Bukkit.getServer().getLogger().log(Level.WARNING, "Could not hook WorldGuard");
wgPlugin = false;
}
}
private static Object getPrivateField(Class<?> c, Object handle, String fieldName) throws Exception {
Field field = c.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(handle);
}
private static void setStaticField(Field field, Object value) throws Exception {
field.setAccessible(true);
Field modifier = Field.class.getDeclaredField("modifiers");
modifier.setAccessible(true);
modifier.setInt(field, field.getModifiers() & ~Modifier.FINAL);
field.set(null, value);
}
public static boolean isEnabled() {
return wgPlugin != null && wgPlugin;
}
/**
* Checks this location to see what this flag is set to
* @param l location to check
* @param flag ALLOW/DENY flag to check
* @return flag state, or null if undefined
*/
public static Boolean getBooleanFlag(Location l, String flag) {
if (wgPlugin == null || !wgPlugin) return null;
Object flagObj = flags.get(flag);
// There's a different way to get this in the old version
if (legacy_v6 || legacy_v5)
return flagObj == null ? null : getBooleanFlagLegacy(l, flagObj);
// for convinience, we can load a flag if we don't know it
if (flagObj == null && !legacy_v6)
flags.put(flag, flagObj = WorldGuard.getInstance().getFlagRegistry().get(flag));
// so, what's up?
if (flagObj instanceof StateFlag) {
RegionContainer container = WorldGuard.getInstance().getPlatform().getRegionContainer();
RegionQuery query = container.createQuery();
com.sk89q.worldedit.util.Location loc = BukkitAdapter.adapt(l);
return query.testState(loc, (RegionAssociable) null, (StateFlag) flagObj);
}
return null;
}
/**
* Query all regions that are in or intersect this chunk
* @param c chunk to check for regions in
* @param flag ALLOW/DENY flag to check
* @return flag state, or null if undefined
*/
public static Boolean getBooleanFlag(Chunk c, String flag) {
if (wgPlugin == null || !wgPlugin) return null;
Object flagObj = flags.get(flag);
// There's a different way to get this in the old version
if (legacy_v6 || legacy_v5)
return flagObj == null ? null : getBooleanFlagLegacy(c, flagObj);
// for convinience, we can load a flag if we don't know it
if (flagObj == null)
flags.put(flag, flagObj = WorldGuard.getInstance().getFlagRegistry().get(flag));
// so, what's up?
if (flagObj instanceof StateFlag) {
RegionManager worldManager = WorldGuard.getInstance().getPlatform().getRegionContainer().get(BukkitAdapter.adapt(c.getWorld()));
if (worldManager == null)
return null;
ProtectedCuboidRegion chunkRegion = new ProtectedCuboidRegion("__TEST__",
BlockVector3.at(c.getX() << 4, c.getWorld().getMaxHeight(), c.getZ() << 4),
BlockVector3.at((c.getX() << 4) + 15, 0, (c.getZ() << 4) + 15));
ApplicableRegionSet set = worldManager.getApplicableRegions(chunkRegion);
State result = set.queryState((RegionAssociable) null, (StateFlag) flagObj);
if (result == null && set.size() == 0)
return null;
return result == State.ALLOW;
}
return null;
}
static Method legacy_getRegionManager = null;
static Method legacy_getApplicableRegions_Region = null;
static Method legacy_getApplicableRegions_Location = null;
static Method legacy5_applicableRegionSet_getFlag = null;
static Constructor legacy_newProtectedCuboidRegion;
static Class legacy_blockVectorClazz;
static Constructor legacy_newblockVector;
private static Boolean getBooleanFlagLegacy(Location l, Object flag) {
try {
// cache reflection methods
if (legacy_getRegionManager == null) {
legacy_getRegionManager = worldGuardPlugin.getClass()
.getDeclaredMethod("getRegionManager", org.bukkit.World.class);
legacy_getApplicableRegions_Region = RegionManager.class.getDeclaredMethod("getApplicableRegions",
Class.forName("com.sk89q.worldguard.protection.regions.ProtectedRegion"));
legacy_getApplicableRegions_Location = RegionManager.class.getDeclaredMethod("getApplicableRegions",
Location.class);
legacy_blockVectorClazz = Class.forName("com.sk89q.worldedit.BlockVector");
legacy_newblockVector = legacy_blockVectorClazz.getConstructor(int.class, int.class, int.class);
legacy_newProtectedCuboidRegion = Class.forName("com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion")
.getConstructor(String.class, legacy_blockVectorClazz, legacy_blockVectorClazz);
}
// grab the applicable manager for this world
Object worldManager = (RegionManager) legacy_getRegionManager.invoke(worldGuardPlugin, l.getWorld());
if (worldManager == null)
return null;
// now look for any intersecting regions
Object set = legacy_getApplicableRegions_Region.invoke(worldManager, l);
// so what's the verdict?
State result = null;
if(legacy_v6) {
set = ((ApplicableRegionSet) set).queryState((RegionAssociable) null, (StateFlag) flag);
} else {
// v5 has a different class signature for ApplicableRegionSet
// also doesn't have a "queryState" function
//getFlag(T flag)
if(legacy5_applicableRegionSet_getFlag == null) {
legacy5_applicableRegionSet_getFlag = Class.forName("com.sk89q.worldguard.protection.ApplicableRegionSet").getMethod("getFlag", Object.class);
}
result = (State) legacy5_applicableRegionSet_getFlag.invoke(set, flag);
}
if (result == null && set != null && ((Iterable) set).iterator().hasNext())
return null;
return result == State.ALLOW;
} catch (Exception ex) {
Bukkit.getServer().getLogger().log(Level.WARNING, "Could not grab flags from WorldGuard", ex);
}
return null;
}
private static Boolean getBooleanFlagLegacy(Chunk c, Object flag) {
// ApplicableRegionSet and RegionManager have the same classpath as the current version
// ProtectedCuboidRegion uses a different constructor, though
try {
// cache reflection methods
if (legacy_getRegionManager == null) {
legacy_getRegionManager = worldGuardPlugin.getClass()
.getDeclaredMethod("getRegionManager", org.bukkit.World.class);
legacy_getApplicableRegions_Region = RegionManager.class.getDeclaredMethod("getApplicableRegions",
Class.forName("com.sk89q.worldguard.protection.regions.ProtectedRegion"));
legacy_getApplicableRegions_Location = RegionManager.class.getDeclaredMethod("getApplicableRegions",
Location.class);
legacy_blockVectorClazz = Class.forName("com.sk89q.worldedit.BlockVector");
legacy_newblockVector = legacy_blockVectorClazz.getConstructor(int.class, int.class, int.class);
legacy_newProtectedCuboidRegion = Class.forName("com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion")
.getConstructor(String.class, legacy_blockVectorClazz, legacy_blockVectorClazz);
}
// grab the applicable manager for this world
Object worldManager = (RegionManager) legacy_getRegionManager.invoke(worldGuardPlugin, c.getWorld());
if (worldManager == null)
return null;
// Create a legacy ProtectedCuboidRegion
Object chunkRegion = legacy_newProtectedCuboidRegion.newInstance("__TEST__",
legacy_newblockVector.newInstance(c.getX() << 4, c.getWorld().getMaxHeight(), c.getZ() << 4),
legacy_newblockVector.newInstance((c.getX() << 4) + 15, 0, (c.getZ() << 4) + 15));
// now look for any intersecting regions
Object set = legacy_getApplicableRegions_Region.invoke(worldManager, chunkRegion);
// so what's the verdict?
State result = null;
if(legacy_v6) {
set = ((ApplicableRegionSet) set).queryState((RegionAssociable) null, (StateFlag) flag);
} else {
// v5 has a different class signature for ApplicableRegionSet
// also doesn't have a "queryState" function
//getFlag(T flag)
if(legacy5_applicableRegionSet_getFlag == null) {
legacy5_applicableRegionSet_getFlag = Class.forName("com.sk89q.worldguard.protection.ApplicableRegionSet").getMethod("getFlag", Flag.class);
}
result = (State) legacy5_applicableRegionSet_getFlag.invoke(set, flag);
}
if (result == null && set != null && ((Iterable) set).iterator().hasNext())
return null;
return result == State.ALLOW;
} catch (Exception ex) {
Bukkit.getServer().getLogger().log(Level.WARNING, "Could not grab flags from WorldGuard", ex);
}
return null;
}
}

View File

@ -0,0 +1,145 @@
package com.songoda.core.input;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.event.player.PlayerCommandPreprocessEvent;
import org.bukkit.plugin.Plugin;
public class ChatPrompt implements Listener {
private static final List<UUID> registered = new ArrayList<>();
private final ChatConfirmHandler handler;
private OnClose onClose = null;
private OnCancel onCancel = null;
private Listener listener;
private ChatPrompt(Player player, ChatConfirmHandler hander) {
this.handler = hander;
registered.add(player.getUniqueId());
}
public static ChatPrompt showPrompt(Plugin plugin, Player player, ChatConfirmHandler hander) {
ChatPrompt prompt = new ChatPrompt(player, hander);
prompt.startListener(plugin);
player.closeInventory();
return prompt;
}
public static ChatPrompt showPrompt(Plugin plugin, Player player, String message, ChatConfirmHandler hander) {
ChatPrompt prompt = new ChatPrompt(player, hander);
prompt.startListener(plugin);
player.closeInventory();
if (message != null)
player.sendMessage(message);
return prompt;
}
public static boolean isRegistered(Player player) {
return registered.contains(player.getUniqueId());
}
public static boolean unregister(Player player) {
return registered.remove(player.getUniqueId());
}
public ChatPrompt setOnClose(OnClose onClose) {
this.onClose = onClose;
return this;
}
public ChatPrompt setOnCancel(OnCancel onCancel) {
this.onCancel = onCancel;
return this;
}
private void startListener(Plugin plugin) {
this.listener = new Listener() {
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
public void onChat(AsyncPlayerChatEvent event) {
Player player = event.getPlayer();
if (!ChatPrompt.isRegistered(player)) return;
ChatPrompt.unregister(player);
event.setCancelled(true);
ChatConfirmEvent chatConfirmEvent = new ChatConfirmEvent(player, event.getMessage());
player.sendMessage("\u00BB " + event.getMessage());
try {
handler.onChat(chatConfirmEvent);
} catch (Throwable t) {
plugin.getLogger().log(Level.SEVERE, "Failed to process chat prompt", t);
}
if (onClose != null) {
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () ->
onClose.onClose(), 0L);
}
HandlerList.unregisterAll(listener);
}
@EventHandler(priority = EventPriority.LOWEST, ignoreCancelled = false)
public void onCancel(PlayerCommandPreprocessEvent event) {
Player player = event.getPlayer();
if (!ChatPrompt.isRegistered(player)) return;
ChatPrompt.unregister(player);
if(event.getMessage().toLowerCase().startsWith("/cancel"))
event.setCancelled(true);
if (onCancel != null) {
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () ->
onCancel.onCancel(), 0L);
} else if (onClose != null) {
plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, () ->
onClose.onClose(), 0L);
}
HandlerList.unregisterAll(listener);
}
};
Bukkit.getPluginManager().registerEvents(listener, plugin);
}
public static interface ChatConfirmHandler {
void onChat(ChatConfirmEvent event);
}
public static interface OnClose {
void onClose();
}
public static interface OnCancel {
void onCancel();
}
public static class ChatConfirmEvent {
private final Player player;
private final String message;
public ChatConfirmEvent(Player player, String message) {
this.player = player;
this.message = message;
}
public Player getPlayer() {
return player;
}
public String getMessage() {
return message;
}
}
}

View File

@ -0,0 +1,147 @@
package com.songoda.core.input;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;
import com.songoda.core.compatibility.ServerVersion;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.logging.Level;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
/**
* Send chat packets with embedded links
*
* @since 2019-09-01
* @author jascotty2
*/
public class ClickableChat {
private static final Gson gson = new GsonBuilder().create();
List<JsonObject> textList = new ArrayList();
public void clear() {
textList.clear();
}
public ClickableChat addMessage(String s) {
JsonObject txt = new JsonObject();
txt.addProperty("text", s);
textList.add(txt);
return this;
}
public ClickableChat addRunCommand(String text, String hoverText, String cmd) {
JsonObject txt = new JsonObject();
txt.addProperty("text", text);
JsonObject hover = new JsonObject();
hover.addProperty("action", "show_text");
hover.addProperty("value", hoverText);
txt.add("hoverEvent", hover);
JsonObject click = new JsonObject();
click.addProperty("action", "run_command");
click.addProperty("value", cmd);
txt.add("clickEvent", click);
textList.add(txt);
return this;
}
public ClickableChat addPromptCommand(String text, String hoverText, String cmd) {
JsonObject txt = new JsonObject();
txt.addProperty("text", text);
JsonObject hover = new JsonObject();
hover.addProperty("action", "show_text");
hover.addProperty("value", hoverText);
txt.add("hoverEvent", hover);
JsonObject click = new JsonObject();
click.addProperty("action", "suggest_command");
click.addProperty("value", cmd);
txt.add("clickEvent", click);
textList.add(txt);
return this;
}
public ClickableChat addURL(String text, String hoverText, String url) {
JsonObject txt = new JsonObject();
txt.addProperty("text", text);
JsonObject hover = new JsonObject();
hover.addProperty("action", "show_text");
hover.addProperty("value", hoverText);
txt.add("hoverEvent", hover);
JsonObject click = new JsonObject();
click.addProperty("action", "open_url");
click.addProperty("value", url);
txt.add("clickEvent", hover);
textList.add(txt);
return this;
}
@Override
public String toString() {
return gson.toJson(textList);
}
public void sendTo(Player p) {
if (enabled) {
try {
Object packet = mc_PacketPlayOutChat_new.newInstance(mc_IChatBaseComponent_ChatSerializer_a.invoke(null, this.toString()));
Object cbPlayer = cb_craftPlayer_getHandle.invoke(p);
Object mcConnection = mc_entityPlayer_playerConnection.get(cbPlayer);
mc_playerConnection_sendPacket.invoke(mcConnection, packet);
} catch (InstantiationException | IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
Bukkit.getLogger().log(Level.WARNING, "Problem preparing raw chat packets (disabling further packets)", ex);
enabled = false;
}
}
}
private static boolean enabled = ServerVersion.isServerVersionAtLeast(ServerVersion.V1_8);
private static Method mc_IChatBaseComponent_ChatSerializer_a;
private static Constructor mc_PacketPlayOutChat_new;
private static Method cb_craftPlayer_getHandle;
private static Field mc_entityPlayer_playerConnection;
private static Method mc_playerConnection_sendPacket;
static {
init();
}
static void init() {
if (enabled) {
try {
final String version = ServerVersion.getServerVersionString();
Class cb_craftPlayerClazz;
Class mc_entityPlayerClazz;
Class mc_playerConnectionClazz;
Class mc_PacketInterface;
Class mc_IChatBaseComponent;
Class mc_IChatBaseComponent_ChatSerializer;
Class mc_PacketPlayOutChat;
cb_craftPlayerClazz = Class.forName("org.bukkit.craftbukkit." + version + ".entity.CraftPlayer");
cb_craftPlayer_getHandle = cb_craftPlayerClazz.getDeclaredMethod("getHandle");
mc_entityPlayerClazz = Class.forName("net.minecraft.server." + version + ".EntityPlayer");
mc_entityPlayer_playerConnection = mc_entityPlayerClazz.getDeclaredField("playerConnection");
mc_playerConnectionClazz = Class.forName("net.minecraft.server." + version + ".PlayerConnection");
mc_PacketInterface = Class.forName("net.minecraft.server." + version + ".Packet");
mc_playerConnection_sendPacket = mc_playerConnectionClazz.getDeclaredMethod("sendPacket", mc_PacketInterface);
mc_IChatBaseComponent = Class.forName("net.minecraft.server." + version + ".IChatBaseComponent");
mc_IChatBaseComponent_ChatSerializer = Class.forName("net.minecraft.server." + version + ".IChatBaseComponent$ChatSerializer");
mc_IChatBaseComponent_ChatSerializer_a = mc_IChatBaseComponent_ChatSerializer.getMethod("a", String.class);
mc_PacketPlayOutChat = Class.forName("net.minecraft.server." + version + ".PacketPlayOutChat");
mc_PacketPlayOutChat_new = mc_PacketPlayOutChat.getConstructor(mc_IChatBaseComponent);
} catch (Throwable ex) {
Bukkit.getLogger().log(Level.WARNING, "Problem preparing raw chat packets (disabling further packets)", ex);
enabled = false;
}
}
}
}

View File

@ -0,0 +1,356 @@
package com.songoda.core.locale;
import com.songoda.core.utils.TextUtils;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.java.JavaPlugin;
/**
* Assists in the utilization of localization files. <br>
* Created to be used by the Songoda Team. <br>
* NOTE: Using this class in multiple plugins requires shading! <br>
* Updated 2019-09-01 to support UTF encoded lang files - jascotty2
*
* @author Brianna O'Keefe - Songoda
*/
public class Locale {
private static final Pattern NODE_PATTERN = Pattern.compile("^([^ ]+)\\s*=\\s*\"?(.*?)\"?$");
private static final String FILE_EXTENSION = ".lang";
private final Map<String, String> nodes = new HashMap<>();
private final Plugin plugin;
private final File file;
private final String name;
/**
* Instantiate the Locale class for future use
*
* @param plugin Owning Plugin
* @param file Location of the locale file
* @param name The locale name for the language
*/
public Locale(Plugin plugin, File file, String name) {
this.plugin = plugin;
this.file = file;
this.name = name;
}
/**
* Load a default-included lang file from the plugin's jar file
*
* @param plugin plugin to load from
* @param name name of the default locale, eg "en_US"
* @return returns the loaded Locale, or null if there was an error
*/
public static Locale loadDefaultLocale(JavaPlugin plugin, String name) {
saveDefaultLocale(plugin, name, name);
return loadLocale(plugin, name);
}
/**
* Load a locale from this plugin's locale directory
*
* @param plugin plugin to load from
* @param name name of the locale, eg "en_US"
* @return returns the loaded Locale, or null if there was an error
*/
public static Locale loadLocale(JavaPlugin plugin, String name) {
File localeFolder = new File(plugin.getDataFolder(), "locales/");
if (!localeFolder.exists()) return null;
File localeFile = new File(localeFolder, name + FILE_EXTENSION);
if (!localeFolder.exists()) return null;
// found the lang file, now load it in!
Locale l = new Locale(plugin, localeFile, name);
if (!l.reloadMessages()) return null;
plugin.getLogger().info("Loaded locale \"" + name + "\"");
return l;
}
/**
* Load all locales from this plugin's locale directory
*
* @param plugin plugin to load from
* @return returns the loaded Locales
*/
public static List<Locale> loadAllLocales(JavaPlugin plugin) {
File localeFolder = new File(plugin.getDataFolder(), "locales/");
List<Locale> all = new ArrayList();
for (File localeFile : localeFolder.listFiles()) {
String fileName = localeFile.getName();
if (!fileName.endsWith(FILE_EXTENSION)) continue;
fileName = fileName.substring(0, fileName.lastIndexOf('.'));
if (fileName.split("_").length != 2) continue;
Locale l = new Locale(plugin, localeFile, fileName);
if (l.reloadMessages()) {
plugin.getLogger().info("Loaded locale \"" + fileName + "\"");
all.add(l);
}
}
return all;
}
/**
* Get a list of all locale files in this plugin's locale directory
*
* @param plugin Plugin to check for
*/
public static List<String> getLocales(Plugin plugin) {
File localeFolder = new File(plugin.getDataFolder(), "locales/");
List<String> all = new ArrayList();
for (File localeFile : localeFolder.listFiles()) {
String fileName = localeFile.getName();
if (!fileName.endsWith(FILE_EXTENSION)) continue;
fileName = fileName.substring(0, fileName.lastIndexOf('.'));
if (fileName.split("_").length != 2) {
continue;
}
all.add(fileName);
}
return all;
}
/**
* Save a locale file from the Plugin's Resources to the locale folder
*
* @param plugin plugin owning the locale file
* @param locale the specific locale file to save
* @param fileName where to save the file
* @return true if the operation was successful, false otherwise
*/
public static boolean saveDefaultLocale(JavaPlugin plugin, String locale, String fileName) {
return saveLocale(plugin, plugin.getResource(locale + FILE_EXTENSION), fileName, true);
}
/**
* Save a locale file from an InputStream to the locale folder
*
* @param plugin plugin owning the locale file
* @param in file to save
* @param fileName the name of the file to save
* @return true if the operation was successful, false otherwise
*/
public static boolean saveLocale(Plugin plugin, InputStream in, String fileName) {
return saveLocale(plugin, in, fileName, false);
}
private static boolean saveLocale(Plugin plugin, InputStream in, String fileName, boolean builtin) {
if(in == null) return false;
File localeFolder = new File(plugin.getDataFolder(), "locales/");
if (!localeFolder.exists()) localeFolder.mkdirs();
if (!fileName.endsWith(FILE_EXTENSION))
fileName = fileName + FILE_EXTENSION;
File destinationFile = new File(localeFolder, fileName);
if (destinationFile.exists())
return updateFiles(plugin, in, destinationFile, builtin);
try (OutputStream outputStream = new FileOutputStream(destinationFile)) {
copy(in, outputStream);
fileName = fileName.substring(0, fileName.lastIndexOf('.'));
if (fileName.split("_").length != 2) return false;
return true;
} catch (IOException e) {
return false;
}
}
// Write new changes to existing files, if any at all
private static boolean updateFiles(Plugin plugin, InputStream defaultFile, File existingFile, boolean builtin) {
boolean changed = false;
List<String> defaultLines, existingLines;
try (BufferedInputStream defaultIn = new BufferedInputStream(defaultFile);
BufferedInputStream existingIn = new BufferedInputStream(new FileInputStream(existingFile))) {
Charset defaultCharset = TextUtils.detectCharset(defaultIn, StandardCharsets.UTF_8);
Charset existingCharset = TextUtils.detectCharset(existingIn, StandardCharsets.UTF_8);
try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(existingFile, true), existingCharset);
BufferedReader defaultReader = new BufferedReader(new InputStreamReader(defaultIn, defaultCharset));
BufferedReader existingReader = new BufferedReader(new InputStreamReader(existingIn, existingCharset));) {
defaultLines = defaultReader.lines().map(s -> s.replaceAll("[\uFEFF\uFFFE\u200B]", "")).collect(Collectors.toList());
existingLines = existingReader.lines().map(s -> s.replaceAll("[\uFEFF\uFFFE\u200B]", "").split("\\s*=")[0]).collect(Collectors.toList());
for (String defaultValue : defaultLines) {
if (defaultValue.isEmpty() || defaultValue.startsWith("#")) {
continue;
}
String key = defaultValue.split("\\s*=")[0];
if (!existingLines.contains(key)) {
if (!changed) {
writer.write("\n\n");
// Leave a note alerting the user of the newly added messages.
writer.write("# New messages for " + plugin.getName() + " v" + plugin.getDescription().getVersion() + ".");
// If changes were found outside of the default file leave a note explaining that.
if (!builtin) {
writer.write("\n");
writer.write("# These translations were found untranslated, join\n");
writer.write("# our translation Discord https://discord.gg/f7fpZEf\n");
writer.write("# to request an official update!\n");
}
}
writer.write("\n");
// re-encode to target format? (transform down, not up)
// if (defaultCharset != existingCharset) {
// byte[] encoded = defaultValue.getBytes(defaultCharset);
// defaultValue = new String(encoded, 2, encoded.length - 2, existingCharset);
// }
writer.write(defaultValue);
changed = true;
}
}
}
} catch (IOException e) {
return false;
}
return changed;
}
/**
* Clear the previous message cache and load new messages directly from file
*
* @return reload messages from file
*/
public boolean reloadMessages() {
if (!this.file.exists()) {
plugin.getLogger().warning("Could not find file for locale \"" + this.name + "\"");
return false;
}
this.nodes.clear(); // Clear previous data (if any)
// guess what encoding this file is in
Charset charset = TextUtils.detectCharset(file, null);
if(charset == null) {
plugin.getLogger().warning("Could not determine charset for locale \"" + this.name + "\"");
charset = StandardCharsets.UTF_8;
}
// load in the file!
try (FileInputStream stream = new FileInputStream(file);
BufferedReader reader = new BufferedReader(new InputStreamReader((InputStream) stream, charset));) {
String line;
for (int lineNumber = 0; (line = reader.readLine()) != null; lineNumber++) {
if (lineNumber == 0){
// remove BOM markers, if any
line = line.replaceAll("[\uFEFF\uFFFE\u200B]", "");
}
// convert to UTF-8 ?
/*if (charset == StandardCharsets.UTF_16LE || charset == StandardCharsets.UTF_16BE) {
byte[] encoded = line.getBytes(charset);
line = new String(encoded, 0, encoded.length, StandardCharsets.UTF_8);
} */
if ((line = line.trim()).isEmpty() || line.startsWith("#") /* Comment */) continue;
Matcher matcher = NODE_PATTERN.matcher(line);
if (!matcher.find()) {
System.err.println("Invalid locale syntax at (line=" + lineNumber + "): " + line);
continue;
}
nodes.put(matcher.group(1), matcher.group(2));
}
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
/**
* Supply the Message object with the plugins prefix.
*
* @param message message to be applied
* @return applied message
*/
private Message supplyPrefix(Message message) {
return message.setPrefix(this.nodes.getOrDefault("general.nametag.prefix", "[" + plugin.getName() + "]"));
}
/**
* Create a new unsaved Message
*
* @param message the message to create
* @return the created message
*/
public Message newMessage(String message) {
return supplyPrefix(new Message(message));
}
/**
* Get a message set for a specific node.
*
* @param node the node to get
* @return the message for the specified node
*/
public Message getMessage(String node) {
return this.getMessageOrDefault(node, node);
}
/**
* Get a message set for a specific node
*
* @param node the node to get
* @param defaultValue the default value given that a value for the node was not found
* @return the message for the specified node. Default if none found
*/
public Message getMessageOrDefault(String node, String defaultValue) {
return supplyPrefix(new Message(this.nodes.getOrDefault(node, defaultValue)));
}
/**
* Return the locale name (i.e. "en_US")
*
* @return the locale name
*/
public String getName() {
return name;
}
private static void copy(InputStream input, OutputStream output) {
int n;
byte[] buffer = new byte[1024 * 4];
try {
while ((n = input.read(buffer)) != -1) {
output.write(buffer, 0, n);
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,130 @@
package com.songoda.core.locale;
import java.util.regex.Matcher;
import org.bukkit.ChatColor;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
/**
* The Message object. This holds the message to be sent
* as well as the plugins prefix so that they can both be
* easily manipulated then deployed
*/
public class Message {
private String prefix = null;
private String message;
/**
* create a new message
*
* @param message the message text
*/
public Message(String message) {
this.message = message;
}
/**
* Format and send the held message to a player
*
* @param player player to send the message to
*/
public void sendMessage(Player player) {
player.sendMessage(this.getMessage());
}
/**
* Format and send the held message with the
* appended plugin prefix to a player
*
* @param player player to send the message to
*/
public void sendPrefixedMessage(Player player) {
player.sendMessage(this.getPrefixedMessage());
}
/**
* Format and send the held message to a player
*
* @param sender command sender to send the message to
*/
public void sendMessage(CommandSender sender) {
sender.sendMessage(this.getMessage());
}
/**
* Format and send the held message to a player as a title message
*
* @param sender command sender to send the message to
*/
public void sendTitle(CommandSender sender) {
if(sender instanceof Player) {
((Player) sender).sendTitle("", this.getMessage(), 10, 20, 10);
} else {
sender.sendMessage(this.getMessage());
}
}
/**
* Format and send the held message with the
* appended plugin prefix to a command sender
*
* @param sender command sender to send the message to
*/
public void sendPrefixedMessage(CommandSender sender) {
sender.sendMessage(this.getPrefixedMessage());
}
/**
* Format the held message and append the plugins
* prefix
*
* @return the prefixed message
*/
public String getPrefixedMessage() {
return ChatColor.translateAlternateColorCodes('&',(prefix == null ? "" : this.prefix)
+ " " + this.message);
}
/**
* Get and format the held message
*
* @return the message
*/
public String getMessage() {
return ChatColor.translateAlternateColorCodes('&', this.message);
}
/**
* Get the held message
*
* @return the message
*/
public String getUnformattedMessage() {
return this.message;
}
/**
* Replace the provided placeholder with the provided object. <br />
* Interchangeably Supports {@code %value%} and {@code {value}}
*
* @param placeholder the placeholder to replace
* @param replacement the replacement object
* @return the modified Message
*/
public Message processPlaceholder(String placeholder, Object replacement) {
final String place = Matcher.quoteReplacement(placeholder);
this.message = message.replaceAll("%" + place + "%|\\{" + place +"\\}", Matcher.quoteReplacement(replacement.toString()));
return this;
}
Message setPrefix(String prefix) {
this.prefix = prefix;
return this;
}
@Override
public String toString() {
return this.message;
}
}

View File

@ -0,0 +1,851 @@
package com.songoda.core.utils;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.EnumSet;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.bukkit.Bukkit;
import org.bukkit.Effect;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
public class BlockUtils {
protected static final Set<Material> DOORS;
protected static final Set<Material> PRESSURE_PLATES;
protected static final Set<Material> FENCE_GATES;
protected static final Set<Material> TRAP_DOORS;
protected static final boolean useLegacy = Material.getMaterial("OAK_LOG") == null;
protected static Method legacySetBlockData = null;
protected static Method legacyUpdateBlockData = null;
static {
DOORS = EnumSet.noneOf(Material.class);
PRESSURE_PLATES = EnumSet.noneOf(Material.class);
FENCE_GATES = EnumSet.noneOf(Material.class);
TRAP_DOORS = EnumSet.noneOf(Material.class);
for (Material material : Material.values()) {
String name = material.name();
if (name.contains("DOOR") && !name.contains("ITEM")) {
if (name.contains("TRAP")) {
TRAP_DOORS.add(material);
} else {
DOORS.add(material);
}
} else if (name.contains("GATE") && !name.contains("END")) {
FENCE_GATES.add(material);
} else if (name.contains("_PLATE")) {
PRESSURE_PLATES.add(material);
}
}
if (useLegacy) {
try {
//legacyUpdateBlockData = Block.class.getDeclaredMethod("update");
legacySetBlockData = Block.class.getDeclaredMethod("setData", byte.class);
} catch (NoSuchMethodException ex) {
}
}
}
/**
* Interact with this block to either update redstone or open doors
*
* @param b block to update
* @return if this block's state was updated
*/
public static boolean tryInteract(Block b) {
final Material bType = b.getType();
if (isOpenable(bType)) {
toggleDoorStates(true, b);
return true;
} else if(bType == Material.LEVER) {
toggleLever(b);
return true;
} else if(bType.name().endsWith("_BUTTON")) {
pressButton(b);
return true;
}
return false;
}
/**
* Change a pressure plate's redstone state
* @param plate plate to update
* @param power power to set to 0-15 (wood plates are active if greater than 0)
*/
public static void updatePressurePlate(Block plate, int power) {
if (useLegacy && legacySetBlockData != null) {
_updatePressurePlateLegacy(plate, power);
} else {
BlockUtilsModern._updatePressurePlateModern(plate, power);
}
}
private static void _updatePressurePlateLegacy(Block plate, int power) {
final Material m = plate.getType();
try {
if (m.name().equals("GOLD_PLATE") || m.name().equals("IRON_PLATE")) {
legacySetBlockData.invoke(plate, (byte) (power & 0x15));
} else if (m.name().endsWith("_PLATE")) {
legacySetBlockData.invoke(plate, (byte) (power == 0 ? 0 : 1));
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(BlockUtils.class.getName()).log(Level.SEVERE, "Unexpected method error", ex);
}
}
public static void pressButton(Block button) {
if (useLegacy && legacySetBlockData != null) {
_pressButtonLegacy(button);
} else {
BlockUtilsModern._pressButtonModern(button);
}
}
public static void releaseButton(Block button) {
if (useLegacy && legacySetBlockData != null) {
_releaseButtonLegacy(button);
} else {
BlockUtilsModern._releaseButtonModern(button);
}
}
private static void _pressButtonLegacy(Block button) {
final Material m = button.getType();
if(!m.name().endsWith("_BUTTON")) return;
try {
legacySetBlockData.invoke(button, (byte) (button.getData() | (31 & 0x8)));
button.getState().update();
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(BlockUtils.class.getName()).log(Level.SEVERE, "Unexpected method error", ex);
}
}
private static void _releaseButtonLegacy(Block button) {
final Material m = button.getType();
if(!m.name().endsWith("_BUTTON")) return;
try {
legacySetBlockData.invoke(button, (byte) (button.getData() & ~0x8));
button.getState().update();
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(BlockUtils.class.getName()).log(Level.SEVERE, "Unexpected method error", ex);
}
}
public static void toggleLever(Block lever) {
if (useLegacy && legacySetBlockData != null) {
_toggleLeverLegacy(lever);
} else {
BlockUtilsModern._toggleLeverModern(lever);
}
}
private static void _toggleLeverLegacy(Block lever) {
final Material m = lever.getType();
if(m != Material.LEVER) return;
try {
legacySetBlockData.invoke(lever, (byte) (lever.getData() ^ 0x8));
lever.getState().update();
//lever.getWorld().playEffect(lever.getLocation(), Effect.CLICK1, 0);
// now we need to update the redstone around it..
// int data = lever.getData() & ~0x8;
// Block attached;
// switch(data) {
// case 0:
// attached = lever.getRelative(BlockFace.UP);
// break;
// case 1:
// attached = lever.getRelative(BlockFace.WEST);
// break;
// case 2:
// attached = lever.getRelative(BlockFace.EAST);
// break;
// case 3:
// attached = lever.getRelative(BlockFace.NORTH);
// break;
// case 4:
// attached = lever.getRelative(BlockFace.SOUTH);
// break;
// case 5:
// attached = lever.getRelative(BlockFace.DOWN);
// break;
// default:
// return;
// }
//
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(BlockUtils.class.getName()).log(Level.SEVERE, "Unexpected method error", ex);
}
}
/**
* Change all of the given door states to be inverse; that is, if a door is
* open, it will be closed afterwards. If the door is closed, it will become
* open.
* <p/>
* Note that the blocks given must be the bottom block of the door.
*
* @param allowDoorToOpen If FALSE, and the door is currently CLOSED, it
* will NOT be opened!
* @param doors Blocks given must be the bottom block of the door
*/
public static void toggleDoorStates(boolean allowDoorToOpen, Block... doors) {
if (useLegacy && legacySetBlockData != null) {
_toggleDoorStatesLegacy(allowDoorToOpen, doors);
} else {
BlockUtilsModern._toggleDoorStatesModern(allowDoorToOpen, doors);
}
}
private static void _toggleDoorStatesLegacy(boolean allowDoorToOpen, Block... doors) {
try {
for (Block door : doors) {
if (door == null) {
continue;
}
boolean isTop = (door.getData() & 0x8) != 0;
if (isTop) {
// The lower half of the door contains the direction & open/close state
door = door.getRelative(BlockFace.DOWN);
}
// If we aren't allowing the door to open, check if it's already closed
if (!allowDoorToOpen && (door.getData() & 0x4) == 0) {
// The door is already closed and we don't want to open it
// the bit 0x4 is set when the door is open
continue;
}
// Now xor both data values with 0x4, the flag that states if the door is open
legacySetBlockData.invoke(door, (byte) (door.getData() ^ 0x4));
// Play the door open/close sound
door.getWorld().playEffect(door.getLocation(), Effect.DOOR_TOGGLE, 0);
}
} catch (IllegalAccessException | IllegalArgumentException | InvocationTargetException ex) {
Logger.getLogger(BlockUtils.class.getName()).log(Level.SEVERE, "Unexpected method error", ex);
}
}
/**
* Get the double door for the given block
*
* @param block
* @return
*/
public static Block getDoubleDoor(Block block) {
// TODO? if legacy, just search N/S/E/W to see if there's another door nearby
if (!isOpenable(block.getType())) {
return null;
}
return BlockUtilsModern._getDoubleDoorModern(block);
}
public static boolean isOpenable(Material m) {
return DOORS.contains(m)
|| FENCE_GATES.contains(m)
|| TRAP_DOORS.contains(m);
}
public static BlockFace getDoorClosedDirection(Block door) {
return useLegacy ? _getDoorClosedDirectionLegacy(door) : BlockUtilsModern._getDoorClosedDirectionModern(door);
}
private static BlockFace _getDoorClosedDirectionLegacy(Block door) {
final Material type = door.getType();
if (DOORS.contains(type)) {
boolean isTop = (door.getData() & 0x8) != 0;
if (isTop) {
// The lower half of the door contains the direction & open/close state
door = door.getRelative(BlockFace.DOWN);
if (door.getType() != type) {
return null;
}
}
boolean isOpen = (door.getData() & 0x4) != 0;
//int facing = (door.getData() & 0x3);
// [east, south, west, north]
boolean facingNS = (door.getData() & 0x1) != 0;
if (facingNS) {
return isOpen ? BlockFace.EAST : BlockFace.SOUTH;
} else {
return isOpen ? BlockFace.SOUTH : BlockFace.EAST;
}
} else if (FENCE_GATES.contains(door.getType())) {
boolean isOpen = (door.getData() & 0x4) != 0;
//int facing = (door.getData() & 0x3);
// so fence gate orientations are [south, west, north, east]
boolean facingNS = (door.getData() & 0x1) == 0;
if (facingNS) {
return isOpen ? BlockFace.EAST : BlockFace.SOUTH;
} else {
return isOpen ? BlockFace.SOUTH : BlockFace.EAST;
}
} else if (TRAP_DOORS.contains(door.getType())) {
boolean isOpen = (door.getData() & 0x4) != 0;
// [south, north, east, west]
boolean facingNS = (door.getData() & 0x3) <= 1;
if (facingNS) {
return isOpen ? BlockFace.EAST : BlockFace.SOUTH;
} else {
return isOpen ? BlockFace.SOUTH : BlockFace.EAST;
}
}
return null;
}
private static Class<?> clazzCraftWorld, clazzCraftBlock, clazzBlockPosition;
private static Method getHandle, updateAdjacentComparators, getNMSBlock;
/**
* Manually trigger the updateAdjacentComparators method for containers
* @param containerLocation location of the container
*/
public static void updateAdjacentComparators(Location containerLocation) {
try {
// Cache reflection.
if (clazzCraftWorld == null) {
String ver = Bukkit.getServer().getClass().getPackage().getName().substring(23);
clazzCraftWorld = Class.forName("org.bukkit.craftbukkit." + ver + ".CraftWorld");
clazzCraftBlock = Class.forName("org.bukkit.craftbukkit." + ver + ".block.CraftBlock");
clazzBlockPosition = Class.forName("net.minecraft.server." + ver + ".BlockPosition");
Class<?> clazzWorld = Class.forName("net.minecraft.server." + ver + ".World");
Class<?> clazzBlock = Class.forName("net.minecraft.server." + ver + ".Block");
getHandle = clazzCraftWorld.getMethod("getHandle");
updateAdjacentComparators = clazzWorld.getMethod("updateAdjacentComparators", clazzBlockPosition, clazzBlock);
getNMSBlock = clazzCraftBlock.getDeclaredMethod("getNMSBlock");
getNMSBlock.setAccessible(true);
}
// invoke and cast objects.
Object craftWorld = clazzCraftWorld.cast(containerLocation.getWorld());
Object world = getHandle.invoke(craftWorld);
Object craftBlock = clazzCraftBlock.cast(containerLocation.getBlock());
// Invoke final method.
updateAdjacentComparators
.invoke(world, clazzBlockPosition.getConstructor(double.class, double.class, double.class)
.newInstance(containerLocation.getX(), containerLocation.getY(), containerLocation.getZ()),
getNMSBlock.invoke(craftBlock));
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
public static boolean canPassThrough(Material m) {
switch (m.name()) {
case "ACACIA_BUTTON":
case "ACACIA_PRESSURE_PLATE":
case "ACACIA_SAPLING":
case "ACACIA_SIGN":
case "ACACIA_WALL_SIGN":
case "ACTIVATOR_RAIL":
case "AIR":
case "ATTACHED_MELON_STEM":
case "ATTACHED_PUMPKIN_STEM":
case "AZURE_BLUET":
//case "BAMBOO_SAPLING":
//case "BARRIER": // could let robots pass through barriers
case "BEETROOTS":
case "BIRCH_BUTTON":
case "BIRCH_PRESSURE_PLATE":
case "BIRCH_SAPLING":
case "BIRCH_SIGN":
case "BIRCH_WALL_SIGN":
case "BLACK_WALL_BANNER":
case "BLUE_BANNER":
case "BLUE_ORCHID":
case "BLUE_WALL_BANNER":
case "BRAIN_CORAL_FAN":
case "BRAIN_CORAL_WALL_FAN":
case "BROWN_BANNER":
case "BROWN_MUSHROOM":
case "BROWN_WALL_BANNER":
case "BUBBLE_CORAL_FAN":
case "BUBBLE_CORAL_WALL_FAN":
case "CARROTS":
case "CAVE_AIR":
case "COBWEB":
case "CORNFLOWER":
case "CYAN_BANNER":
case "CYAN_WALL_BANNER":
case "DANDELION":
case "DARK_OAK_BUTTON":
case "DARK_OAK_PRESSURE_PLATE":
case "DARK_OAK_SAPLING":
case "DARK_OAK_SIGN":
case "DARK_OAK_WALL_SIGN":
case "DEAD_BRAIN_CORAL_FAN":
case "DEAD_BRAIN_CORAL_WALL_FAN":
case "DEAD_BUBBLE_CORAL_FAN":
case "DEAD_BUBBLE_CORAL_WALL_FAN":
case "DEAD_BUSH":
case "DEAD_FIRE_CORAL_FAN":
case "DEAD_FIRE_CORAL_WALL_FAN":
case "DEAD_HORN_CORAL_FAN":
case "DEAD_HORN_CORAL_WALL_FAN":
case "DEAD_TUBE_CORAL_FAN":
case "DEAD_TUBE_CORAL_WALL_FAN":
case "DETECTOR_RAIL":
case "END_PORTAL":
case "FERN":
case "FIRE": // probably should take damage
case "FIRE_CORAL_FAN":
case "FIRE_CORAL_WALL_FAN":
case "GRASS":
case "GRAY_BANNER":
case "GRAY_WALL_BANNER":
case "GREEN_BANNER":
case "GREEN_WALL_BANNER":
case "HEAVY_WEIGHTED_PRESSURE_PLATE":
case "HORN_CORAL_FAN":
case "HORN_CORAL_WALL_FAN":
case "JUNGLE_BUTTON":
case "JUNGLE_PRESSURE_PLATE":
case "JUNGLE_SAPLING":
case "JUNGLE_SIGN":
case "JUNGLE_WALL_SIGN":
case "KELP":
case "LADDER":
case "LARGE_FERN":
case "LAVA":
case "LEVER":
case "LIGHT_BLUE_BANNER":
case "LIGHT_BLUE_WALL_BANNER":
case "LIGHT_GRAY_BANNER":
case "LIGHT_GRAY_WALL_BANNER":
case "LIGHT_WEIGHTED_PRESSURE_PLATE":
case "LILAC":
case "LILY_OF_THE_VALLEY":
case "LIME_BANNER":
case "MAGENTA_BANNER":
case "MAGENTA_WALL_BANNER":
case "MELON_STEM":
case "NETHER_PORTAL":
case "NETHER_WART":
case "OAK_BUTTON":
case "OAK_PRESSURE_PLATE":
case "OAK_SAPLING":
case "OAK_SIGN":
case "OAK_WALL_SIGN":
case "ORANGE_BANNER":
case "ORANGE_TULIP":
case "ORANGE_WALL_BANNER":
case "OXEYE_DAISY":
case "PEONY":
case "PINK_BANNER":
case "PINK_TULIP":
case "PINK_WALL_BANNER":
case "POTATOES":
case "POWERED_RAIL":
case "PUMPKIN_STEM":
case "PURPLE_BANNER":
case "PURPLE_WALL_BANNER":
case "RAIL":
case "REDSTONE_TORCH":
case "REDSTONE_WALL_TORCH":
case "REDSTONE_WIRE":
case "RED_BANNER":
case "RED_MUSHROOM":
case "RED_TULIP":
case "RED_WALL_BANNER":
case "ROSE_BUSH":
case "SCAFFOLDING":
case "SEAGRASS":
case "SPRUCE_BUTTON":
case "SPRUCE_PRESSURE_PLATE":
case "SPRUCE_SAPLING":
case "SPRUCE_SIGN":
case "SPRUCE_WALL_SIGN":
case "STONE_BUTTON":
case "STONE_PRESSURE_PLATE":
case "STRUCTURE_VOID":
case "SUGAR_CANE":
case "SUNFLOWER":
case "SWEET_BERRY_BUSH":
case "TALL_GRASS":
case "TALL_SEAGRASS":
case "TORCH":
case "TRIPWIRE":
case "TRIPWIRE_HOOK":
case "TUBE_CORAL_FAN":
case "TUBE_CORAL_WALL_FAN":
case "VINE":
case "VOID_AIR":
case "WALL_TORCH":
case "WATER":
case "WHEAT":
case "WHITE_BANNER":
case "WHITE_TULIP":
case "WHITE_WALL_BANNER":
case "WITHER_ROSE":
case "YELLOW_BANNER":
case "YELLOW_WALL_BANNER":
// Legacy values:
case "WEB":
case "LONG_GRASS":
case "YELLOW_FLOWER":
case "RED_ROSE":
case "CROPS":
case "SIGN_POST":
case "RAILS":
case "WALL_SIGN":
case "STONE_PLATE":
case "WOOD_PLATE":
case "REDSTONE_TORCH_OFF":
case "REDSTONE_TORCH_ON":
case "SUGAR_CANE_BLOCK":
case "PORTAL":
case "ENDER_PORTAL":
case "CARROT":
case "POTATO":
case "WOOD_BUTTON":
case "GOLD_PLATE":
case "IRON_PLATE":
case "DOUBLE_PLANT":
case "STANDING_BANNER":
case "WALL_BANNER":
case "BEETROOT_BLOCK":
return true;
}
return false;
}
public static boolean canWalkTo(Material m) {
switch (m.name()) {
case "ACACIA_BUTTON":
case "ACACIA_PRESSURE_PLATE":
case "ACACIA_SAPLING":
case "ACACIA_SIGN":
case "ACACIA_SLAB":
case "ACACIA_STAIRS":
case "ACACIA_TRAPDOOR":
case "ACACIA_WALL_SIGN":
case "ACTIVATOR_RAIL":
case "AIR":
case "ANDESITE_SLAB":
case "ANDESITE_STAIRS":
case "ATTACHED_MELON_STEM":
case "ATTACHED_PUMPKIN_STEM":
case "AZURE_BLUET":
//case "BAMBOO_SAPLING":
//case "BARRIER": // could let robots pass through barriers
case "BEETROOTS":
case "BIRCH_BUTTON":
case "BIRCH_DOOR":
case "BIRCH_FENCE_GATE":
case "BIRCH_PRESSURE_PLATE":
case "BIRCH_SAPLING":
case "BIRCH_SIGN":
case "BIRCH_SLAB":
case "BIRCH_STAIRS":
case "BIRCH_TRAPDOOR":
case "BIRCH_WALL_SIGN":
case "BLACK_CARPET":
case "BLACK_WALL_BANNER":
case "BLUE_BANNER":
case "BLUE_CARPET":
case "BLUE_ORCHID":
case "BLUE_WALL_BANNER":
case "BRAIN_CORAL_FAN":
case "BRAIN_CORAL_WALL_FAN":
case "BRICK_SLAB":
case "BRICK_STAIRS":
case "BROWN_BANNER":
case "BROWN_CARPET":
case "BROWN_MUSHROOM":
case "BROWN_WALL_BANNER":
case "BUBBLE_CORAL_FAN":
case "BUBBLE_CORAL_WALL_FAN":
case "CAKE":
case "CAMPFIRE": // could take damage from walking over?
case "CARROTS":
case "CAVE_AIR":
case "COBBLESTONE_SLAB":
case "COBBLESTONE_STAIRS":
case "COBWEB":
case "COMPARATOR":
case "CORNFLOWER":
case "CUT_RED_SANDSTONE_SLAB":
case "CUT_SANDSTONE_SLAB":
case "CYAN_BANNER":
case "CYAN_CARPET":
case "CYAN_WALL_BANNER":
case "DANDELION":
case "DARK_OAK_BUTTON":
case "DARK_OAK_DOOR":
case "DARK_OAK_FENCE_GATE":
case "DARK_OAK_PRESSURE_PLATE":
case "DARK_OAK_SAPLING":
case "DARK_OAK_SIGN":
case "DARK_OAK_SLAB":
case "DARK_OAK_STAIRS":
case "DARK_OAK_TRAPDOOR":
case "DARK_OAK_WALL_SIGN":
case "DARK_PRISMARINE_SLAB":
case "DARK_PRISMARINE_STAIRS":
case "DAYLIGHT_DETECTOR":
case "DEAD_BRAIN_CORAL_FAN":
case "DEAD_BRAIN_CORAL_WALL_FAN":
case "DEAD_BUBBLE_CORAL_FAN":
case "DEAD_BUBBLE_CORAL_WALL_FAN":
case "DEAD_BUSH":
case "DEAD_FIRE_CORAL_FAN":
case "DEAD_FIRE_CORAL_WALL_FAN":
case "DEAD_HORN_CORAL_FAN":
case "DEAD_HORN_CORAL_WALL_FAN":
case "DEAD_TUBE_CORAL_FAN":
case "DEAD_TUBE_CORAL_WALL_FAN":
case "DETECTOR_RAIL":
case "DIORITE_SLAB":
case "DIORITE_STAIRS":
case "END_PORTAL":
case "END_STONE_BRICK_SLAB":
case "END_STONE_BRICK_STAIRS":
case "FERN":
case "FIRE": // probably should take damage
case "FIRE_CORAL_FAN":
case "FIRE_CORAL_WALL_FAN":
case "FLOWER_POT":
case "GRANITE_SLAB":
case "GRANITE_STAIRS":
case "GRASS":
case "GRAY_BANNER":
case "GRAY_CARPET":
case "GRAY_WALL_BANNER":
case "GREEN_BANNER":
case "GREEN_WALL_BANNER":
case "HEAVY_WEIGHTED_PRESSURE_PLATE":
case "HORN_CORAL_FAN":
case "HORN_CORAL_WALL_FAN":
case "IRON_DOOR":
case "JUNGLE_BUTTON":
case "JUNGLE_DOOR":
case "JUNGLE_FENCE_GATE":
case "JUNGLE_PRESSURE_PLATE":
case "JUNGLE_SAPLING":
case "JUNGLE_SIGN":
case "JUNGLE_SLAB":
case "JUNGLE_STAIRS":
case "JUNGLE_TRAPDOOR":
case "JUNGLE_WALL_SIGN":
case "KELP":
case "LADDER":
case "LARGE_FERN":
case "LAVA":
case "LEVER":
case "LIGHT_BLUE_BANNER":
case "LIGHT_BLUE_CARPET":
case "LIGHT_BLUE_WALL_BANNER":
case "LIGHT_GRAY_BANNER":
case "LIGHT_GRAY_CARPET":
case "LIGHT_GRAY_WALL_BANNER":
case "LIGHT_WEIGHTED_PRESSURE_PLATE":
case "LILAC":
case "LILY_OF_THE_VALLEY":
case "LILY_PAD":
case "LIME_BANNER":
case "LIME_CARPET":
case "MAGENTA_BANNER":
case "MAGENTA_CARPET":
case "MAGENTA_WALL_BANNER":
case "MELON_STEM":
case "MOSSY_COBBLESTONE_SLAB":
case "MOSSY_COBBLESTONE_STAIRS":
case "MOSSY_STONE_BRICK_SLAB":
case "MOSSY_STONE_BRICK_STAIRS":
case "NETHER_BRICK_SLAB":
case "NETHER_BRICK_STAIRS":
case "NETHER_PORTAL":
case "NETHER_WART":
case "OAK_BUTTON":
case "OAK_DOOR":
case "OAK_FENCE_GATE":
case "OAK_PRESSURE_PLATE":
case "OAK_SAPLING":
case "OAK_SIGN":
case "OAK_SLAB":
case "OAK_STAIRS":
case "OAK_TRAPDOOR":
case "OAK_WALL_SIGN":
case "ORANGE_BANNER":
case "ORANGE_CARPET":
case "ORANGE_TULIP":
case "ORANGE_WALL_BANNER":
case "OXEYE_DAISY":
case "PEONY":
case "PETRIFIED_OAK_SLAB":
case "PINK_BANNER":
case "PINK_CARPET":
case "PINK_TULIP":
case "PINK_WALL_BANNER":
case "POLISHED_ANDESITE_SLAB":
case "POLISHED_ANDESITE_STAIRS":
case "POLISHED_DIORITE_SLAB":
case "POLISHED_DIORITE_STAIRS":
case "POLISHED_GRANITE_SLAB":
case "POLISHED_GRANITE_STAIRS":
case "POTATOES":
case "POTTED_ACACIA_SAPLING":
case "POTTED_ALLIUM":
case "POTTED_AZURE_BLUET":
case "POTTED_BAMBOO":
case "POTTED_BIRCH_SAPLING":
case "POTTED_BLUE_ORCHID":
case "POTTED_BROWN_MUSHROOM":
case "POTTED_CACTUS":
case "POTTED_CORNFLOWER":
case "POTTED_DANDELION":
case "POTTED_DARK_OAK_SAPLING":
case "POTTED_DEAD_BUSH":
case "POTTED_FERN":
case "POTTED_JUNGLE_SAPLING":
case "POTTED_LILY_OF_THE_VALLEY":
case "POTTED_OAK_SAPLING":
case "POTTED_ORANGE_TULIP":
case "POTTED_OXEYE_DAISY":
case "POTTED_PINK_TULIP":
case "POTTED_POPPY":
case "POTTED_RED_MUSHROOM":
case "POTTED_RED_TULIP":
case "POTTED_SPRUCE_SAPLING":
case "POTTED_WHITE_TULIP":
case "POTTED_WITHER_ROSE":
case "POWERED_RAIL":
case "PRISMARINE_BRICK_SLAB":
case "PRISMARINE_BRICK_STAIRS":
case "PRISMARINE_SLAB":
case "PRISMARINE_STAIRS":
case "PUMPKIN_STEM":
case "PURPLE_BANNER":
case "PURPLE_CARPET":
case "PURPLE_WALL_BANNER":
case "PURPUR_SLAB":
case "PURPUR_STAIRS":
case "RAIL":
case "REDSTONE_TORCH":
case "REDSTONE_WALL_TORCH":
case "REDSTONE_WIRE":
case "RED_BANNER":
case "RED_CARPET":
case "RED_MUSHROOM":
case "RED_SANDSTONE_SLAB":
case "RED_SANDSTONE_STAIRS":
case "RED_TULIP":
case "RED_WALL_BANNER":
case "REPEATER":
case "ROSE_BUSH":
case "SANDSTONE_SLAB":
case "SANDSTONE_STAIRS":
case "SCAFFOLDING":
case "SEAGRASS":
case "SMOOTH_QUARTZ_SLAB":
case "SMOOTH_QUARTZ_STAIRS":
case "SMOOTH_RED_SANDSTONE_SLAB":
case "SMOOTH_RED_SANDSTONE_STAIRS":
case "SMOOTH_SANDSTONE_SLAB":
case "SMOOTH_SANDSTONE_STAIRS":
case "SMOOTH_STONE_SLAB":
case "SPRUCE_BUTTON":
case "SPRUCE_DOOR":
case "SPRUCE_FENCE_GATE":
case "SPRUCE_PRESSURE_PLATE":
case "SPRUCE_SAPLING":
case "SPRUCE_SIGN":
case "SPRUCE_SLAB":
case "SPRUCE_STAIRS":
case "SPRUCE_TRAPDOOR":
case "SPRUCE_WALL_SIGN":
case "STONECUTTER": // technically can step on, so sure
case "STONE_BRICK_SLAB":
case "STONE_BRICK_STAIRS":
case "STONE_BUTTON":
case "STONE_PRESSURE_PLATE":
case "STONE_SLAB":
case "STONE_STAIRS":
case "STRUCTURE_VOID":
case "SUGAR_CANE":
case "SUNFLOWER":
case "SWEET_BERRY_BUSH":
case "TALL_GRASS":
case "TALL_SEAGRASS":
case "TORCH":
case "TRIPWIRE":
case "TRIPWIRE_HOOK":
case "TUBE_CORAL_FAN":
case "TUBE_CORAL_WALL_FAN":
case "VINE":
case "VOID_AIR":
case "WALL_TORCH":
case "WATER":
case "WHEAT":
case "WHITE_BANNER":
case "WHITE_CARPET":
case "WHITE_TULIP":
case "WHITE_WALL_BANNER":
case "WITHER_ROSE":
case "YELLOW_BANNER":
case "YELLOW_CARPET":
case "YELLOW_WALL_BANNER":
// Legacy values:
case "WEB":
case "LONG_GRASS":
case "YELLOW_FLOWER":
case "RED_ROSE":
case "STEP":
case "WOOD_STAIRS":
case "CROPS":
case "SIGN_POST":
case "RAILS":
case "WOODEN_DOOR":
case "WALL_SIGN":
case "STONE_PLATE":
case "IRON_DOOR_BLOCK":
case "WOOD_PLATE":
case "REDSTONE_TORCH_OFF":
case "REDSTONE_TORCH_ON":
case "SNOW":
case "SUGAR_CANE_BLOCK":
case "PORTAL":
case "CAKE_BLOCK":
case "DIODE_BLOCK_OFF":
case "DIODE_BLOCK_ON":
case "TRAP_DOOR":
case "FENCE_GATE":
case "SMOOTH_STAIRS":
case "ENDER_PORTAL":
case "WOOD_STEP":
case "SPRUCE_WOOD_STAIRS":
case "BIRCH_WOOD_STAIRS":
case "JUNGLE_WOOD_STAIRS":
case "CARROT":
case "POTATO":
case "WOOD_BUTTON":
case "GOLD_PLATE":
case "IRON_PLATE":
case "REDSTONE_COMPARATOR_OFF":
case "REDSTONE_COMPARATOR_ON":
case "QUARTZ_STAIRS":
case "DOUBLE_PLANT":
case "STANDING_BANNER":
case "WALL_BANNER":
case "DAYLIGHT_DETECTOR_INVERTED":
case "DOUBLE_STONE_SLAB2":
case "STONE_SLAB2":
case "BEETROOT_BLOCK":
return true;
}
return false;
}
}

View File

@ -0,0 +1,161 @@
package com.songoda.core.utils;
import org.bukkit.Effect;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.data.Bisected;
import org.bukkit.block.data.BlockData;
import org.bukkit.block.data.AnaloguePowerable;
import org.bukkit.block.data.type.Door;
import org.bukkit.block.data.type.Gate;
import org.bukkit.block.data.type.Switch;
import org.bukkit.block.data.type.TrapDoor;
public class BlockUtilsModern {
protected static void _updatePressurePlateModern(Block plate, int power) {
BlockData blockData = plate.getBlockData();
if (blockData instanceof AnaloguePowerable) {
AnaloguePowerable a = (AnaloguePowerable) blockData;
a.setPower(Math.max(a.getMaximumPower(), power));
plate.setBlockData(a);
}
}
protected static void _toggleLeverModern(Block lever) {
BlockData blockData = lever.getBlockData();
if (blockData instanceof Switch) {
Switch s = (Switch) blockData;
s.setPowered(!s.isPowered());
lever.setBlockData(s);
//lever.getWorld().playEffect(lever.getLocation(), Effect.CLICK1, 0);
lever.getState().update();
}
}
protected static void _pressButtonModern(Block button) {
BlockData blockData = button.getBlockData();
if (blockData instanceof Switch) {
Switch s = (Switch) blockData;
s.setPowered(true);
button.setBlockData(s);
//lever.getWorld().playEffect(lever.getLocation(), Effect.CLICK1, 0);
button.getState().update();
}
}
static void _releaseButtonModern(Block button) {
BlockData blockData = button.getBlockData();
if (blockData instanceof Switch) {
Switch s = (Switch) blockData;
s.setPowered(false);
button.setBlockData(s);
//lever.getWorld().playEffect(lever.getLocation(), Effect.CLICK1, 0);
button.getState().update();
}
}
protected static void _toggleDoorStatesModern(boolean allowDoorToOpen, Block... doors) {
for (Block door : doors) {
BlockData blockData;
if (door == null || !((blockData = door.getBlockData()) instanceof Door)) {
continue;
}
Door data = (Door) blockData;
if (!allowDoorToOpen && !data.isOpen()) {
continue;
}
// The lower half of the door contains the open/close state
if (data.getHalf() == Bisected.Half.TOP) {
Block lowerHalf = door.getRelative(BlockFace.DOWN);
if (lowerHalf.getBlockData() instanceof Door) {
Door lowerData = (Door) lowerHalf.getBlockData();
lowerData.setOpen(!data.isOpen());
lowerHalf.setBlockData(lowerData);
}
} else {
data.setOpen(!data.isOpen());
door.setBlockData(data);
}
// Play the door open/close sound
door.getWorld().playEffect(door.getLocation(), Effect.DOOR_TOGGLE, 0);
}
}
protected static Block _getDoubleDoorModern(Block block) {
BlockData bd = block.getBlockData();
Block door = null;
if (bd instanceof Door) {
final Door d = (Door) bd;
final BlockFace face = d.getFacing();
if (face.getModX() == 0) {
if (d.getHinge() == Door.Hinge.RIGHT) {
door = block.getRelative(face.getModZ(), 0, 0);
} else {
door = block.getRelative(-face.getModZ(), 0, 0);
}
} else {
if (d.getHinge() == Door.Hinge.RIGHT) {
door = block.getRelative(0, 0, -face.getModX());
} else {
door = block.getRelative(0, 0, face.getModX());
}
}
}
return door != null && door.getBlockData() instanceof Door
&& ((Door) door.getBlockData()).getHinge() != ((Door) bd).getHinge() ? door : null;
}
protected static BlockFace _getDoorClosedDirectionModern(Block door) {
if (BlockUtils.DOORS.contains(door.getType())) {
BlockData bd = door.getBlockData();
if (bd instanceof Door) {
Door d = (Door) bd;
// The lower half of the door contains the open/close state
if (d.getHalf() == Bisected.Half.TOP) {
door = door.getRelative(BlockFace.DOWN);
if (door.getBlockData() instanceof Door) {
d = (Door) door.getBlockData();
} else {
return null;
}
}
final BlockFace face = d.getFacing();
// now we /could/ also correct for the hinge (top block), it's not needed information
if (face.getModX() == 0) {
return d.isOpen() ? BlockFace.EAST : BlockFace.SOUTH;
} else {
return d.isOpen() ? BlockFace.SOUTH : BlockFace.EAST;
}
}
} else if (BlockUtils.FENCE_GATES.contains(door.getType())) {
BlockData bd = door.getBlockData();
if (bd instanceof Gate) {
Gate g = (Gate) bd;
final BlockFace face = g.getFacing();
if (face.getModX() == 0) {
return g.isOpen() ? BlockFace.EAST : BlockFace.SOUTH;
} else {
return g.isOpen() ? BlockFace.SOUTH : BlockFace.EAST;
}
}
} else if (BlockUtils.TRAP_DOORS.contains(door.getType())) {
BlockData bd = door.getBlockData();
if (bd instanceof TrapDoor) {
TrapDoor t = (TrapDoor) bd;
if (!t.isOpen()) {
return BlockFace.UP;
} else {
return t.getFacing();
}
}
}
return null;
}
}

View File

@ -0,0 +1,893 @@
/**
* This class uses some Minecraft code and also Paper API
*/
package com.songoda.core.utils;
import com.mojang.authlib.GameProfile;
import com.mojang.authlib.properties.Property;
import com.songoda.core.compatibility.LegacyMaterials;
import com.songoda.core.compatibility.ServerVersion;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.Base64;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.stream.Stream;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.OfflinePlayer;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.Inventory;
import org.bukkit.inventory.ItemFlag;
import org.bukkit.inventory.ItemStack;
import org.bukkit.inventory.meta.Damageable;
import org.bukkit.inventory.meta.ItemMeta;
import org.bukkit.inventory.meta.SkullMeta;
public class ItemUtils {
static boolean check_compatibility = false;
static boolean can_getI18NDisplayName = true;
static void init() {
check_compatibility = true;
try {
ItemStack.class.getMethod("getI18NDisplayName");
} catch (NoSuchMethodException | SecurityException ex) {
can_getI18NDisplayName = false;
}
}
/**
* Clone of org.bukkit.inventory.ItemStack.asQuantity, since it is a paper-only function
*
* @param item item to copy
* @param qty amount the new ItemStack should have
* @return a copy of the original item
*/
public static ItemStack getAsCopy(ItemStack item, int qty) {
ItemStack clone = item.clone();
clone.setAmount(qty);
return clone;
}
public static ItemStack addDamage(ItemStack item, int damage) {
if (item == null) {
return null;
} else if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)) {
// ItemStack.setDurability(short) still works in 1.13-1.14, but use these methods now
ItemMeta meta = item.getItemMeta();
if (meta instanceof Damageable) {
((Damageable) meta).setDamage(((Damageable) meta).getDamage() + damage);
item.setItemMeta(meta);
}
} else {
item.setDurability((short) Math.max(0, item.getDurability() + damage));
}
return item;
}
static Class cb_ItemStack = NMSUtils.getCraftClass("inventory.CraftItemStack");
static Class mc_ItemStack = NMSUtils.getNMSClass("ItemStack");
static Class mc_NBTTagCompound = NMSUtils.getNMSClass("NBTTagCompound");
static Class mc_NBTTagList = NMSUtils.getNMSClass("NBTTagList");
static Class mc_NBTBase = NMSUtils.getNMSClass("NBTBase");
static Method mc_ItemStack_getTag;
static Method mc_ItemStack_setTag;
static Method mc_NBTTagCompound_set;
static Method mc_NBTTagCompound_remove;
static Method mc_NBTTagCompound_setShort;
static Method mc_NBTTagCompound_setString;
static Method mc_NBTTagList_add;
static Method cb_CraftItemStack_asNMSCopy;
static Method cb_CraftItemStack_asCraftMirror;
static {
if(cb_ItemStack != null) {
try {
mc_ItemStack_getTag = mc_ItemStack.getDeclaredMethod("getTag");
mc_ItemStack_setTag = mc_ItemStack.getDeclaredMethod("setTag", mc_NBTTagCompound);
mc_NBTTagCompound_set = mc_NBTTagCompound.getDeclaredMethod("set", String.class, mc_NBTBase);
mc_NBTTagCompound_remove = mc_NBTTagCompound.getDeclaredMethod("remove", String.class);
mc_NBTTagCompound_setShort = mc_NBTTagCompound.getDeclaredMethod("setShort", String.class, short.class);
mc_NBTTagCompound_setString = mc_NBTTagCompound.getDeclaredMethod("setString", String.class, String.class);
cb_CraftItemStack_asNMSCopy = cb_ItemStack.getDeclaredMethod("asNMSCopy", ItemStack.class);
cb_CraftItemStack_asCraftMirror = cb_ItemStack.getDeclaredMethod("asCraftMirror", mc_ItemStack);
mc_NBTTagList_add = ServerVersion.isServerVersionAtLeast(ServerVersion.V1_14)
? NMSUtils.getPrivateMethod(mc_NBTTagList, "a", mc_NBTBase)
: mc_NBTTagList.getDeclaredMethod("add", mc_NBTBase);
} catch (Exception ex) {
Logger.getLogger(ItemUtils.class.getName()).log(Level.SEVERE, null, ex);
}
}
}
/**
* Make an item glow as if it contained an enchantment. <br>
* Tested working 1.8-1.14
*
* @param item itemstack to create a glowing copy of
* @return copy of item with a blank enchantment nbt tag
*/
public static ItemStack addGlow(ItemStack item) {
// from 1.11 up, fake enchantments don't work without more steps
// creating a new Enchantment involves some very involved reflection,
// as the namespace is the same but until 1.12 requires an int, but versions after require a String
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11)) {
item.addUnsafeEnchantment(Enchantment.DURABILITY, 1);
// you can at least hide the enchantment, though
ItemMeta m = item.getItemMeta();
m.addItemFlags(ItemFlag.HIDE_ENCHANTS);
item.setItemMeta(m);
return item;
} else {
// hack a fake enchant onto the item
// Confirmed works on 1.8, 1.9, 1.10
// Does not work 1.11+ (minecraft ignores the glitched enchantment)
if (item != null && item.getType() != Material.AIR && cb_CraftItemStack_asCraftMirror != null) {
try {
Object nmsStack = cb_CraftItemStack_asNMSCopy.invoke(null, item);
Object tag = mc_ItemStack_getTag.invoke(nmsStack);
if (tag == null) {
tag = mc_NBTTagCompound.newInstance();
}
// set to have a fake enchantment
Object enchantmentList = mc_NBTTagList.newInstance();
/*
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)) {
// Servers from 1.13 and up change the id to a string
Object fakeEnchantment = mc_NBTTagCompound.newInstance();
mc_NBTTagCompound_setString.invoke(fakeEnchantment, "id", "glow:glow");
mc_NBTTagCompound_setShort.invoke(fakeEnchantment, "lvl", (short) 0);
mc_NBTTagList_add.invoke(enchantmentList, fakeEnchantment);
} else if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11)) {
// Servers from 1.11 and up require *something* in the enchantment field
Object fakeEnchantment = mc_NBTTagCompound.newInstance();
mc_NBTTagCompound_setShort.invoke(fakeEnchantment, "id", (short) 245);
mc_NBTTagCompound_setShort.invoke(fakeEnchantment, "lvl", (short) 1);
mc_NBTTagList_add.invoke(enchantmentList, fakeEnchantment);
}//*/
mc_NBTTagCompound_set.invoke(tag, "ench", enchantmentList);
mc_ItemStack_setTag.invoke(nmsStack, tag);
item = (ItemStack) cb_CraftItemStack_asCraftMirror.invoke(null, nmsStack);
} catch (Exception ex) {
Bukkit.getLogger().log(Level.SEVERE, "Failed to set glow enchantment on item: " + item, ex);
}
}
}
return item;
}
/**
* Remove all enchantments, including hidden enchantments
* @param item item to clear enchants from
* @return copy of the item without any enchantment tag
*/
public static ItemStack removeGlow(ItemStack item) {
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_11)) {
item.removeEnchantment(Enchantment.DURABILITY);
return item;
} else {
if (item != null && item.getType() != Material.AIR && cb_CraftItemStack_asCraftMirror != null) {
try {
Object nmsStack = cb_CraftItemStack_asNMSCopy.invoke(null, item);
Object tag = mc_ItemStack_getTag.invoke(nmsStack);
if (tag != null) {
// remove enchantment list
mc_NBTTagCompound_remove.invoke(tag, "ench");
mc_ItemStack_setTag.invoke(nmsStack, tag);
item = (ItemStack) cb_CraftItemStack_asCraftMirror.invoke(null, nmsStack);
}
} catch (Exception ex) {
Bukkit.getLogger().log(Level.SEVERE, "Failed to set glow enchantment on item: " + item, ex);
}
}
}
return item;
}
public static String getItemName(ItemStack it) {
if (!check_compatibility) {
init();
}
if (it == null) {
return null;
} else if (can_getI18NDisplayName) {
return it.getI18NDisplayName();
} else {
return itemName(it.getType());
}
}
static String itemName(Material mat) {
String matName = mat.name().replace("_", " ");
StringBuilder titleCase = new StringBuilder(matName.length());
Stream.of(matName.split(" ")).forEach(s -> {
s = s.toLowerCase();
if (s.equals("of")) {
titleCase.append(s).append(" ");
} else {
char[] str = s.toCharArray();
str[0] = Character.toUpperCase(str[0]);
titleCase.append(new String(str)).append(" ");
}
});
return titleCase.toString().trim();
}
public static ItemStack getPlayerSkull(OfflinePlayer player) {
ItemStack head = LegacyMaterials.PLAYER_HEAD.getItem();
if (ServerVersion.isServerVersionBelow(ServerVersion.V1_8)) {
return head;
}
SkullMeta meta = (SkullMeta) head.getItemMeta();
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)) {
meta.setOwningPlayer(player);
} else {
meta.setOwner(player.getName());
}
return head;
}
public static void setHeadOwner(ItemStack head, OfflinePlayer player) {
if (ServerVersion.isServerVersionBelow(ServerVersion.V1_8) || head == null || !LegacyMaterials.PLAYER_HEAD.matches(head)) {
return;
}
SkullMeta meta = (SkullMeta) head.getItemMeta();
if (ServerVersion.isServerVersionAtLeast(ServerVersion.V1_13)) {
meta.setOwningPlayer(player);
} else {
meta.setOwner(player.getName());
}
}
public static ItemStack getCustomHead(String texture) {
ItemStack skullItem = LegacyMaterials.PLAYER_HEAD.getItem();
if (ServerVersion.isServerVersionBelow(ServerVersion.V1_8)) {
return skullItem;
}
SkullMeta sm = (SkullMeta) skullItem.getItemMeta();
GameProfile gm;
if (texture.endsWith("=")) {
gm = new GameProfile(UUID.nameUUIDFromBytes(texture.getBytes()), "CustomHead");
gm.getProperties().put("textures", new Property("texture", texture.replaceAll("=", "")));
} else {
gm = new GameProfile(UUID.nameUUIDFromBytes(texture.getBytes()), "CustomHead");
byte[] encodedData = Base64.getEncoder().encode(String.format("{textures:{SKIN:{url:\"http://textures.minecraft.net/texture/%s\"}}}", texture).getBytes());
gm.getProperties().put("textures", new Property("textures", new String(encodedData)));
}
try {
Field profileField;
profileField = sm.getClass().getDeclaredField("profile");
profileField.setAccessible(true);
profileField.set(sm, gm);
skullItem.setItemMeta(sm);
return skullItem;
} catch (NoSuchFieldException | IllegalAccessException | SecurityException ex) {
throw new RuntimeException("Reflection error while setting head texture", ex);
}
}
public static boolean isSimilarMaterial(ItemStack is1, ItemStack is2) {
LegacyMaterials mat1 = LegacyMaterials.getMaterial(is1);
return mat1 != null && mat1 == LegacyMaterials.getMaterial(is2);
}
/**
* Check to see if this item can be moved into a single slot in this
* inventory. <br>
* This returns true if there is a free slot or a slot with a matching item
* where adding this item's amount to that item's amount will not violate
* the maximum stack size for that item.
*
* @param inventory inventory to check
* @param item item to check against
* @return true if a free slot or single receiver slot is available
*/
public static boolean canMove(Inventory inventory, ItemStack item) {
if (inventory.firstEmpty() != -1) {
return true;
}
final ItemMeta itemMeta = item.getItemMeta();
for (ItemStack stack : inventory) {
final ItemMeta stackMeta;
if (isSimilarMaterial(stack, item) && (stack.getAmount() + item.getAmount()) < stack.getMaxStackSize()
&& ((itemMeta == null) == ((stackMeta = stack.getItemMeta()) == null))
&& (itemMeta == null || Bukkit.getItemFactory().equals(itemMeta, stackMeta))) {
return true;
}
}
return false;
}
/**
* Check to see if this item can be moved into a single slot in this
* inventory. <br>
* This returns true if there is a free slot or a slot with a matching item
* where adding this item's amount to that item's amount will not violate
* the maximum stack size for that item.
*
* @param contents inventory to check
* @param item item to check against
* @return true if a free slot or single receiver slot is available
*/
public static boolean canMove(ItemStack[] contents, ItemStack item) {
final ItemMeta itemMeta = item.getItemMeta();
for (int i = 0; i < contents.length; i++) {
final ItemStack stack = contents[i];
if (stack == null || stack.getAmount() == 0) {
return true;
}
final ItemMeta stackMeta;
if (isSimilarMaterial(stack, item) && (stack.getAmount() + item.getAmount()) < stack.getMaxStackSize()
&& ((itemMeta == null) == ((stackMeta = stack.getItemMeta()) == null))
&& (itemMeta == null || Bukkit.getItemFactory().equals(itemMeta, stackMeta))) {
return true;
}
}
return false;
}
/**
* Check to see if this item can be moved into a single slot in this
* inventory while also reserving one of the slots.<br>
* This returns true if there is a free slot or a slot with a matching item
* where adding this item's amount to that item's amount will not violate
* the maximum stack size for that item.
*
* @param inventory inventory to check
* @param item item to check against
* @param reserved which slot should be reserved
* @return true if a free slot or single receiver slot is available
*/
public static boolean canMoveReserved(Inventory inventory, ItemStack item, int reserved) {
final ItemMeta itemMeta = item.getItemMeta();
final ItemStack[] contents = inventory.getContents();
for (int i = 0; i < contents.length; i++) {
if (i == reserved) {
continue;
}
final ItemStack stack = contents[i];
final ItemMeta stackMeta;
if (stack == null || stack.getAmount() == 0
|| (isSimilarMaterial(stack, item) && (stack.getAmount() + item.getAmount()) < stack.getMaxStackSize()
&& ((itemMeta == null) == ((stackMeta = stack.getItemMeta()) == null))
&& (itemMeta == null || Bukkit.getItemFactory().equals(itemMeta, stackMeta)))) {
return true;
}
}
return false;
}
/**
* Check to see if this item can be moved into a single slot in this
* inventory while also reserving one of the slots.<br>
* This returns true if there is a free slot or a slot with a matching item
* where adding this item's amount to that item's amount will not violate
* the maximum stack size for that item.
*
* @param contents inventory to check
* @param item item to check against
* @param reserved which slot should be reserved
* @return true if a free slot or single receiver slot is available
*/
public static boolean canMoveReserved(ItemStack[] contents, ItemStack item, int reserved) {
final ItemMeta itemMeta = item.getItemMeta();
for (int i = 0; i < contents.length; i++) {
if (i == reserved) {
continue;
}
final ItemStack stack = contents[i];
if (stack == null || stack.getAmount() == 0) {
return true;
}
final ItemMeta stackMeta;
if (isSimilarMaterial(stack, item) && (stack.getAmount() + item.getAmount()) < stack.getMaxStackSize()
&& ((itemMeta == null) == ((stackMeta = stack.getItemMeta()) == null))
&& (itemMeta == null || Bukkit.getItemFactory().equals(itemMeta, stackMeta))) {
return true;
}
}
return false;
}
/**
* Add up to a number of items to this inventory.
*
* @param item item to add
* @param amountToAdd how many of this item to attempt to add
* @param inventory a list that represents the inventory
* @param maxSize maximum number of different items this container can hold
* @return how many items were added
*/
public static int addAny(ItemStack item, int amountToAdd, List<ItemStack> inventory, int maxSize) {
return addAny(item, amountToAdd, inventory, maxSize, -1);
}
/**
* Add up to a number of items to this inventory.
*
* @param item item to add
* @param amountToAdd how many of this item to attempt to add
* @param inventory a list that represents the inventory
* @param maxSize maximum number of different items this container can hold
* @param reserved slot to reserve - will not fill this slot
* @return how many items were added
*/
public static int addAny(ItemStack item, int amountToAdd, List<ItemStack> inventory, int maxSize, int reserved) {
int totalAdded = 0;
if (inventory != null && item != null && amountToAdd > 0) {
final int maxStack = item.getMaxStackSize();
for (int i = 0; amountToAdd > 0 && i < maxSize; i++) {
if (i == reserved) {
continue;
}
final ItemStack cacheItem = i >= inventory.size() ? null : inventory.get(i);
if (cacheItem == null || cacheItem.getAmount() == 0) {
// free slot!
int toAdd = Math.min(maxStack, amountToAdd);
ItemStack item2 = item.clone();
item2.setAmount(toAdd);
if (i >= inventory.size()) {
inventory.add(item2);
} else {
inventory.set(i, item2);
}
totalAdded += toAdd;
amountToAdd -= toAdd;
} else if (maxStack > cacheItem.getAmount() && item.isSimilar(cacheItem)) {
// free space!
int toAdd = Math.min(maxStack - cacheItem.getAmount(), amountToAdd);
inventory.get(i).setAmount(toAdd + cacheItem.getAmount());
totalAdded += toAdd;
amountToAdd -= toAdd;
}
}
}
return totalAdded;
}
/**
* Add an item to this inventory, but only if it can be added completely.
*
* @param item item to add
* @param inventory a list that represents the inventory
* @param containerSize maximum number of different items this container can
* hold
* @return true if the item was added
*/
public static boolean addItem(ItemStack item, List<ItemStack> inventory, int containerSize) {
if (inventory == null || item == null || item.getAmount() <= 0 || containerSize <= 0) {
return false;
}
return addItem(item, item.getAmount(), inventory, containerSize);
}
/**
* Add an item to this inventory, but only if it can be added completely.
*
* @param item item to add
* @param inventory a list that represents the inventory
* @param containerSize maximum number of different items this container can
* hold
* @param reserved slot to reserve - will not fill this slot
* @return true if the item was added
*/
public static boolean addItem(ItemStack item, List<ItemStack> inventory, int containerSize, int reserved) {
if (inventory == null || item == null || item.getAmount() <= 0 || containerSize <= 0) {
return false;
}
return addItem(item, item.getAmount(), inventory, containerSize, reserved);
}
/**
*
* Add an item to this inventory.
*
* @param item item to add
* @param amount how many of this item should be added
* @param inventory a list that represents the inventory
* @param containerSize maximum number of different items this container can
* @param reserved slot to reserve - will not fill this slot hold
* @return true if the item was added
*/
public static boolean addItem(ItemStack item, int amount, List<ItemStack> inventory, int containerSize, int reserved) {
return addItem(item, amount, inventory, containerSize, reserved, null);
}
/**
*
* Add an item to this inventory, but only if it can be added completely.
*
* @param item item to add
* @param amount how many of this item should be added
* @param inventory a list that represents the inventory
* @param containerSize maximum number of different items this container can
* hold
* @param reserved slot to reserve - will not fill this slot
* @param inventorySource Material of the container
* @return true if the item was added
*/
public static boolean addItem(ItemStack item, int amount, List<ItemStack> inventory, int containerSize, int reserved, Material inventorySource) {
if (inventory == null || item == null || amount <= 0 || inventorySource == null) {
return false;
}
boolean[] check = null;
if (inventorySource != null && inventorySource != Material.AIR) {
// Don't transfer shulker boxes into other shulker boxes, that's a bad idea.
if (inventorySource.name().contains("SHULKER_BOX") && item.getType().name().contains("SHULKER_BOX")) {
return false;
}
// some destination containers have special conditions
switch (inventorySource.name()) {
case "BREWING_STAND": {
// first compile a list of what slots to check
check = new boolean[5];
String typeStr = item.getType().name().toUpperCase();
if (typeStr.contains("POTION") || typeStr.contains("BOTTLE")) {
// potion bottles are the first three slots
check[0] = check[1] = check[2] = true;
}
// fuel in 5th position, input in 4th
if (item.getType() == Material.BLAZE_POWDER) {
check[4] = true;
} else {
check[3] = true;
}
}
case "SMOKER":
case "BLAST_FURNACE":
case "BURNING_FURNACE":
case "FURNACE": {
check = new boolean[3];
boolean isFuel = !item.getType().name().contains("LOG") && LegacyMaterials.getMaterial(item.getType()).isFuel();
// fuel is 2nd slot, input is first
if (isFuel) {
check[1] = true;
} else {
check[0] = true;
}
}
}
}
// grab the amount to move and the max item stack size
int toAdd = item.getAmount();
final int maxStack = item.getMaxStackSize();
// we can reduce calls to ItemStack.isSimilar() by caching what cells to look at
if (check == null) {
check = new boolean[containerSize];
for (int i = 0; toAdd > 0 && i < check.length; i++) {
check[i] = true;
}
}
if (reserved >= 0 && check.length < reserved) {
check[reserved] = false;
}
// first verify that we can add this item
for (int i = 0; toAdd > 0 && i < containerSize; i++) {
if (check[i]) {
final ItemStack cacheItem = i >= inventory.size() ? null : inventory.get(i);
if (cacheItem == null || cacheItem.getAmount() == 0) {
// free slot!
toAdd -= Math.min(maxStack, toAdd);
check[i] = true;
} else if (maxStack > cacheItem.getAmount() && item.isSimilar(cacheItem)) {
// free space!
toAdd -= Math.min(maxStack - cacheItem.getAmount(), toAdd);
check[i] = true;
} else {
check[i] = false;
}
}
}
if (toAdd <= 0) {
// all good to add!
toAdd = item.getAmount();
for (int i = 0; toAdd > 0 && i < containerSize; i++) {
if (!check[i]) {
continue;
}
final ItemStack cacheItem = i >= inventory.size() ? null : inventory.get(i);
if (cacheItem == null || cacheItem.getAmount() == 0) {
// free slot!
int adding = Math.min(maxStack, toAdd);
ItemStack item2 = item.clone();
item2.setAmount(adding);
if (i >= inventory.size()) {
inventory.add(item2);
} else {
inventory.set(i, item2);
}
toAdd -= adding;
} else if (maxStack > cacheItem.getAmount()) {
// free space!
// (no need to check item.isSimilar(cacheItem), since we have that cached in check[])
int adding = Math.min(maxStack - cacheItem.getAmount(), toAdd);
inventory.get(i).setAmount(adding + cacheItem.getAmount());
toAdd -= adding;
}
}
return true;
}
return false;
}
/**
* Add up to a number of items to this inventory.
*
* @param item item to add
* @param amountToAdd how many of this item to attempt to add
* @param inventory a list that represents the inventory
* @return how many items were added
*/
public static int addAny(ItemStack item, int amountToAdd, Inventory inventory) {
int totalAdded = 0;
if (inventory != null && item != null && amountToAdd > 0) {
final int containerSize = inventory.getSize();
final int maxStack = item.getMaxStackSize();
for (int i = 0; amountToAdd > 0 && i < containerSize; i++) {
final ItemStack cacheItem = inventory.getItem(i);
if (cacheItem == null || cacheItem.getAmount() == 0) {
// free slot!
int toAdd = Math.min(maxStack, amountToAdd);
ItemStack item2 = item.clone();
item2.setAmount(toAdd);
inventory.setItem(i, item2);
totalAdded += toAdd;
amountToAdd -= toAdd;
} else if (maxStack > cacheItem.getAmount() && item.isSimilar(cacheItem)) {
// free space!
int toAdd = Math.min(maxStack - cacheItem.getAmount(), amountToAdd);
cacheItem.setAmount(toAdd + cacheItem.getAmount());
totalAdded += toAdd;
amountToAdd -= toAdd;
}
}
}
return totalAdded;
}
/**
* Add an item to this inventory, but only if it can be added completely.
*
* @param item item to add
* @param inventory a list that represents the inventory hold
* @return true if the item was added
*/
public static boolean addItem(ItemStack item, Inventory inventory) {
if (inventory == null || item == null || item.getAmount() <= 0) {
return false;
}
return addItem(item, item.getAmount(), inventory, -1, null);
}
/**
*
* Add an item to this inventory.
*
* @param item item to add
* @param amount how many of this item should be added
* @param inventory a list that represents the inventory
* @param reserved slot to reserve - will not fill this slot
* @return true if the item was added
*/
public static boolean addItem(ItemStack item, int amount, Inventory inventory, int reserved) {
return addItem(item, amount, inventory, reserved, null);
}
/**
*
* Add an item to this inventory, but only if it can be added completely.
*
* @param item item to add
* @param amount how many of this item should be added
* @param inventory a list that represents the inventory
* @param reserved slot to reserve - will not fill this slot
* @param inventorySource Material of the container
* @return true if the item was added
*/
public static boolean addItem(ItemStack item, int amount, Inventory inventory, int reserved, Material inventorySource) {
if (inventory == null || item == null || amount <= 0 || inventorySource == null) {
return false;
}
boolean[] check = null;
if (inventorySource != null && inventorySource != Material.AIR) {
// Don't transfer shulker boxes into other shulker boxes, that's a bad idea.
if (inventorySource.name().contains("SHULKER_BOX") && item.getType().name().contains("SHULKER_BOX")) {
return false;
}
// some destination containers have special conditions
switch (inventorySource.name()) {
case "BREWING_STAND": {
// first compile a list of what slots to check
check = new boolean[5];
String typeStr = item.getType().name().toUpperCase();
if (typeStr.contains("POTION") || typeStr.contains("BOTTLE")) {
// potion bottles are the first three slots
check[0] = check[1] = check[2] = true;
}
// fuel in 5th position, input in 4th
if (item.getType() == Material.BLAZE_POWDER) {
check[4] = true;
} else {
check[3] = true;
}
}
case "SMOKER":
case "BLAST_FURNACE":
case "BURNING_FURNACE":
case "FURNACE": {
check = new boolean[3];
boolean isFuel = !item.getType().name().contains("LOG") && LegacyMaterials.getMaterial(item.getType()).isFuel();
// fuel is 2nd slot, input is first
if (isFuel) {
check[1] = true;
} else {
check[0] = true;
}
}
}
}
// grab the amount to move and the max item stack size
int toAdd = item.getAmount();
final int maxStack = item.getMaxStackSize();
final int containerSize = inventory.getSize();
// we can reduce calls to ItemStack.isSimilar() by caching what cells to look at
if (check == null) {
check = new boolean[containerSize];
for (int i = 0; toAdd > 0 && i < check.length; i++) {
check[i] = true;
}
}
// first verify that we can add this item
for (int i = 0; toAdd > 0 && i < containerSize; i++) {
if (check[i]) {
final ItemStack cacheItem = inventory.getItem(i);
if (cacheItem == null || cacheItem.getAmount() == 0) {
// free slot!
toAdd -= Math.min(maxStack, toAdd);
check[i] = true;
} else if (maxStack > cacheItem.getAmount() && item.isSimilar(cacheItem)) {
// free space!
toAdd -= Math.min(maxStack - cacheItem.getAmount(), toAdd);
check[i] = true;
} else {
check[i] = false;
}
}
}
if (toAdd <= 0) {
// all good to add!
toAdd = item.getAmount();
for (int i = 0; toAdd > 0 && i < containerSize; i++) {
if (!check[i]) {
continue;
}
final ItemStack cacheItem = inventory.getItem(i);
if (cacheItem == null || cacheItem.getAmount() == 0) {
// free slot!
int adding = Math.min(maxStack, toAdd);
ItemStack item2 = item.clone();
item2.setAmount(adding);
inventory.setItem(i, item2);
toAdd -= adding;
} else if (maxStack > cacheItem.getAmount()) {
// free space!
// (no need to check item.isSimilar(cacheItem), since we have that cached in check[])
int adding = Math.min(maxStack - cacheItem.getAmount(), toAdd);
cacheItem.setAmount(adding + cacheItem.getAmount());
toAdd -= adding;
}
}
return true;
}
return false;
}
/**
*
* Add an item to this inventory.
*
* @param item item to add
* @param amount how many of this item should be added
* @param inventory a list that represents the inventory
* @param containerSize maximum number of different items this container can
* hold
* @return true if the item was added
*/
public static boolean addItem(ItemStack item, int amount, List<ItemStack> inventory, int containerSize) {
if (inventory == null || item == null || amount <= 0 || containerSize <= 0) {
return false;
}
// grab the amount to move and the max item stack size
int toAdd = amount;
final int maxStack = item.getMaxStackSize();
boolean[] check = null;
// we can reduce calls to ItemStack.isSimilar() by caching what cells to look at
if (check == null) {
check = new boolean[containerSize];
for (int i = 0; toAdd > 0 && i < check.length; i++) {
check[i] = true;
}
}
// first verify that we can add this item
for (int i = 0; toAdd > 0 && i < containerSize; i++) {
if (check[i]) {
final ItemStack cacheItem = i >= inventory.size() ? null : inventory.get(i);
if (cacheItem == null || cacheItem.getAmount() == 0) {
// free slot!
toAdd -= Math.min(maxStack, toAdd);
check[i] = true;
} else if (maxStack > cacheItem.getAmount() && item.isSimilar(cacheItem)) {
// free space!
toAdd -= Math.min(maxStack - cacheItem.getAmount(), toAdd);
check[i] = true;
} else {
check[i] = false;
}
}
}
if (toAdd <= 0) {
// all good to add!
toAdd = item.getAmount();
for (int i = 0; toAdd > 0 && i < containerSize; i++) {
if (!check[i]) {
continue;
}
final ItemStack cacheItem = i >= inventory.size() ? null : inventory.get(i);
if (cacheItem == null || cacheItem.getAmount() == 0) {
// free slot!
int adding = Math.min(maxStack, toAdd);
ItemStack item2 = item.clone();
item2.setAmount(adding);
if (i >= inventory.size()) {
inventory.add(item2);
} else {
inventory.set(i, item2);
}
toAdd -= adding;
} else if (maxStack > cacheItem.getAmount()) {
// free space!
// (no need to check item.isSimilar(cacheItem), since we have that cached in check[])
int adding = Math.min(maxStack - cacheItem.getAmount(), toAdd);
inventory.get(i).setAmount(adding + cacheItem.getAmount());
toAdd -= adding;
}
}
return true;
}
return false;
}
}

View File

@ -0,0 +1,698 @@
package com.songoda.core.utils;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import org.bukkit.plugin.RegisteredServiceProvider;
import org.bukkit.plugin.ServicePriority;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import javax.net.ssl.HttpsURLConnection;
import java.io.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.logging.Level;
import java.util.zip.GZIPOutputStream;
/**
* bStats collects some data for plugin authors.
* <p>
* Check out https://bStats.org/ to learn more about bStats!
*/
@SuppressWarnings({"WeakerAccess", "unused"})
public class Metrics {
static {
// You can use the property to disable the check in your test environment
if (System.getProperty("bstats.relocatecheck") == null || !System.getProperty("bstats.relocatecheck").equals("false")) {
// Maven's Relocate is clever and changes strings, too. So we have to use this little "trick" ... :D
final String defaultPackage = new String(
new byte[]{'o', 'r', 'g', '.', 'b', 's', 't', 'a', 't', 's', '.', 'b', 'u', 'k', 'k', 'i', 't'});
final String examplePackage = new String(new byte[]{'y', 'o', 'u', 'r', '.', 'p', 'a', 'c', 'k', 'a', 'g', 'e'});
// We want to make sure nobody just copy & pastes the example and use the wrong package names
if (Metrics.class.getPackage().getName().equals(defaultPackage) || Metrics.class.getPackage().getName().equals(examplePackage)) {
throw new IllegalStateException("bStats Metrics class has not been relocated correctly!");
}
}
}
// The version of this bStats class
public static final int B_STATS_VERSION = 1;
// The url to which the data is sent
private static final String URL = "https://bStats.org/submitData/bukkit";
// Is bStats enabled on this server?
private boolean enabled;
// Should failed requests be logged?
private static boolean logFailedRequests;
// Should the sent data be logged?
private static boolean logSentData;
// Should the response text be logged?
private static boolean logResponseStatusText;
// The uuid of the server
private static String serverUUID;
// The plugin
private final Plugin plugin;
// A list with all custom charts
private final List<CustomChart> charts = new ArrayList<>();
/**
* Class constructor.
*
* @param plugin The plugin which stats should be submitted.
*/
public static void start(Plugin plugin) {
Metrics m = new Metrics(plugin);
if (m.enabled) {
boolean found = false;
// Search for all other bStats Metrics classes to see if we are the first one
for (Class<?> service : Bukkit.getServicesManager().getKnownServices()) {
try {
service.getField("B_STATS_VERSION"); // Our identifier :)
found = true; // We aren't the first
break;
} catch (NoSuchFieldException ignored) { }
}
// Register this specific instance as a service
Bukkit.getServicesManager().register(Metrics.class, m, plugin, ServicePriority.Normal);
if (!found) {
// We are the first!
m.startSubmitting();
}
}
}
private Metrics(Plugin plugin) {
if (plugin == null) {
throw new IllegalArgumentException("Plugin cannot be null!");
}
this.plugin = plugin;
// Get the config file
File bStatsFolder = new File(plugin.getDataFolder().getParentFile(), "bStats");
File configFile = new File(bStatsFolder, "config.yml");
YamlConfiguration config = YamlConfiguration.loadConfiguration(configFile);
// Check if the config file exists
if (!config.isSet("serverUuid")) {
// Add default values
config.addDefault("enabled", true);
// Every server gets it's unique random id.
config.addDefault("serverUuid", UUID.randomUUID().toString());
// Should failed request be logged?
config.addDefault("logFailedRequests", false);
// Should the sent data be logged?
config.addDefault("logSentData", false);
// Should the response text be logged?
config.addDefault("logResponseStatusText", false);
// Inform the server owners about bStats
config.options().header(
"bStats collects some data for plugin authors like how many servers are using their plugins.\n" +
"To honor their work, you should not disable it.\n" +
"This has nearly blacklist effect on the server performance!\n" +
"Check out https://bStats.org/ to learn more :)"
).copyDefaults(true);
try {
config.save(configFile);
} catch (IOException ignored) { }
}
// Load the data
enabled = config.getBoolean("enabled", true);
serverUUID = config.getString("serverUuid");
logFailedRequests = config.getBoolean("logFailedRequests", false);
logSentData = config.getBoolean("logSentData", false);
logResponseStatusText = config.getBoolean("logResponseStatusText", false);
}
/**
* Checks if bStats is enabled.
*
* @return Whether bStats is enabled or not.
*/
public boolean isEnabled() {
return enabled;
}
/**
* Adds a custom chart.
*
* @param chart The chart to add.
*/
public void addCustomChart(CustomChart chart) {
if (chart == null) {
throw new IllegalArgumentException("Chart cannot be null!");
}
charts.add(chart);
}
/**
* Starts the Scheduler which submits our data every 30 minutes.
*/
private void startSubmitting() {
final Timer timer = new Timer(true); // We use a timer cause the Bukkit scheduler is affected by server lags
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
if (!plugin.isEnabled()) { // Plugin was disabled
timer.cancel();
return;
}
// Nevertheless we want our code to run in the Bukkit main thread, so we have to use the Bukkit scheduler
// Don't be afraid! The connection to the bStats server is still async, only the stats collection is sync ;)
Bukkit.getScheduler().runTask(plugin, () -> submitData());
}
}, 1000 * 60 * 5, 1000 * 60 * 30);
// Submit the data every 30 minutes, first time after 5 minutes to give other plugins enough time to start
// WARNING: Changing the frequency has blacklist effect but your plugin WILL be blocked/deleted!
// WARNING: Just don't do it!
}
/**
* Gets the plugin specific data.
* This method is called using Reflection.
*
* @return The plugin specific data.
*/
public JSONObject getPluginData() {
JSONObject data = new JSONObject();
String pluginName = plugin.getDescription().getName();
String pluginVersion = plugin.getDescription().getVersion();
data.put("pluginName", pluginName); // Append the name of the plugin
data.put("pluginVersion", pluginVersion); // Append the version of the plugin
JSONArray customCharts = new JSONArray();
for (CustomChart customChart : charts) {
// Add the data of the custom charts
JSONObject chart = customChart.getRequestJsonObject();
if (chart == null) { // If the chart is null, we skip it
continue;
}
customCharts.add(chart);
}
data.put("customCharts", customCharts);
return data;
}
/**
* Gets the server specific data.
*
* @return The server specific data.
*/
private JSONObject getServerData() {
// Minecraft specific data
int playerAmount;
try {
// Around MC 1.8 the return type was changed to a collection from an array,
// This fixes java.lang.NoSuchMethodError: org.bukkit.Bukkit.getOnlinePlayers()Ljava/util/Collection;
Method onlinePlayersMethod = Class.forName("org.bukkit.Server").getMethod("getOnlinePlayers");
playerAmount = onlinePlayersMethod.getReturnType().equals(Collection.class)
? ((Collection<?>) onlinePlayersMethod.invoke(Bukkit.getServer())).size()
: ((Player[]) onlinePlayersMethod.invoke(Bukkit.getServer())).length;
} catch (Exception e) {
playerAmount = Bukkit.getOnlinePlayers().size(); // Just use the new method if the Reflection failed
}
int onlineMode = Bukkit.getOnlineMode() ? 1 : 0;
String bukkitVersion = Bukkit.getVersion();
// OS/Java specific data
String javaVersion = System.getProperty("java.version");
String osName = System.getProperty("os.name");
String osArch = System.getProperty("os.arch");
String osVersion = System.getProperty("os.version");
int coreCount = Runtime.getRuntime().availableProcessors();
JSONObject data = new JSONObject();
data.put("serverUUID", serverUUID);
data.put("playerAmount", playerAmount);
data.put("onlineMode", onlineMode);
data.put("bukkitVersion", bukkitVersion);
data.put("javaVersion", javaVersion);
data.put("osName", osName);
data.put("osArch", osArch);
data.put("osVersion", osVersion);
data.put("coreCount", coreCount);
return data;
}
/**
* Collects the data and sends it afterwards.
*/
private void submitData() {
final JSONObject data = getServerData();
JSONArray pluginData = new JSONArray();
// Search for all other bStats Metrics classes to get their plugin data
for (Class<?> service : Bukkit.getServicesManager().getKnownServices()) {
try {
service.getField("B_STATS_VERSION"); // Our identifier :)
for (RegisteredServiceProvider<?> provider : Bukkit.getServicesManager().getRegistrations(service)) {
try {
pluginData.add(provider.getService().getMethod("getPluginData").invoke(provider.getProvider()));
} catch (NullPointerException | NoSuchMethodException | IllegalAccessException | InvocationTargetException ignored) { }
}
} catch (NoSuchFieldException ignored) { }
}
data.put("plugins", pluginData);
// Create a new thread for the connection to the bStats server
new Thread(new Runnable() {
@Override
public void run() {
try {
// Send the data
sendData(plugin, data);
} catch (Exception e) {
// Something went wrong! :(
if (logFailedRequests) {
plugin.getLogger().log(Level.WARNING, "Could not submit plugin stats of " + plugin.getName(), e);
}
}
}
}).start();
}
/**
* Sends the data to the bStats server.
*
* @param plugin Any plugin. It's just used to get a logger instance.
* @param data The data to send.
* @throws Exception If the request failed.
*/
private static void sendData(Plugin plugin, JSONObject data) throws Exception {
if (data == null) {
throw new IllegalArgumentException("Data cannot be null!");
}
if (Bukkit.isPrimaryThread()) {
throw new IllegalAccessException("This method must not be called from the main thread!");
}
if (logSentData) {
plugin.getLogger().info("Sending data to bStats: " + data.toString());
}
HttpsURLConnection connection = (HttpsURLConnection) new URL(URL).openConnection();
// Compress the data to save bandwidth
byte[] compressedData = compress(data.toString());
// Add headers
connection.setRequestMethod("POST");
connection.addRequestProperty("Accept", "application/json");
connection.addRequestProperty("Connection", "close");
connection.addRequestProperty("Content-Encoding", "gzip"); // We gzip our request
connection.addRequestProperty("Content-Length", String.valueOf(compressedData.length));
connection.setRequestProperty("Content-Type", "application/json"); // We send our data in JSON format
connection.setRequestProperty("User-Agent", "MC-Server/" + B_STATS_VERSION);
// Send data
connection.setDoOutput(true);
DataOutputStream outputStream = new DataOutputStream(connection.getOutputStream());
outputStream.write(compressedData);
outputStream.flush();
outputStream.close();
InputStream inputStream = connection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
StringBuilder builder = new StringBuilder();
String line;
while ((line = bufferedReader.readLine()) != null) {
builder.append(line);
}
bufferedReader.close();
if (logResponseStatusText) {
plugin.getLogger().info("Sent data to bStats and received response: " + builder.toString());
}
}
/**
* Gzips the given String.
*
* @param str The string to gzip.
* @return The gzipped String.
* @throws IOException If the compression failed.
*/
private static byte[] compress(final String str) throws IOException {
if (str == null) {
return null;
}
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(outputStream);
gzip.write(str.getBytes(StandardCharsets.UTF_8));
gzip.close();
return outputStream.toByteArray();
}
/**
* Represents a custom chart.
*/
public static abstract class CustomChart {
// The id of the chart
final String chartId;
/**
* Class constructor.
*
* @param chartId The id of the chart.
*/
CustomChart(String chartId) {
if (chartId == null || chartId.isEmpty()) {
throw new IllegalArgumentException("ChartId cannot be null or empty!");
}
this.chartId = chartId;
}
private JSONObject getRequestJsonObject() {
JSONObject chart = new JSONObject();
chart.put("chartId", chartId);
try {
JSONObject data = getChartData();
if (data == null) {
// If the data is null we don't send the chart.
return null;
}
chart.put("data", data);
} catch (Throwable t) {
if (logFailedRequests) {
Bukkit.getLogger().log(Level.WARNING, "Failed to get data for custom chart with id " + chartId, t);
}
return null;
}
return chart;
}
protected abstract JSONObject getChartData() throws Exception;
}
/**
* Represents a custom simple pie.
*/
public static class SimplePie extends CustomChart {
private final Callable<String> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimplePie(String chartId, Callable<String> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JSONObject getChartData() throws Exception {
JSONObject data = new JSONObject();
String value = callable.call();
if (value == null || value.isEmpty()) {
// Null = skip the chart
return null;
}
data.put("value", value);
return data;
}
}
/**
* Represents a custom advanced pie.
*/
public static class AdvancedPie extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedPie(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JSONObject getChartData() throws Exception {
JSONObject data = new JSONObject();
JSONObject values = new JSONObject();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
continue; // Skip this invalid
}
allSkipped = false;
values.put(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
data.put("values", values);
return data;
}
}
/**
* Represents a custom drilldown pie.
*/
public static class DrilldownPie extends CustomChart {
private final Callable<Map<String, Map<String, Integer>>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public DrilldownPie(String chartId, Callable<Map<String, Map<String, Integer>>> callable) {
super(chartId);
this.callable = callable;
}
@Override
public JSONObject getChartData() throws Exception {
JSONObject data = new JSONObject();
JSONObject values = new JSONObject();
Map<String, Map<String, Integer>> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean reallyAllSkipped = true;
for (Map.Entry<String, Map<String, Integer>> entryValues : map.entrySet()) {
JSONObject value = new JSONObject();
boolean allSkipped = true;
for (Map.Entry<String, Integer> valueEntry : map.get(entryValues.getKey()).entrySet()) {
value.put(valueEntry.getKey(), valueEntry.getValue());
allSkipped = false;
}
if (!allSkipped) {
reallyAllSkipped = false;
values.put(entryValues.getKey(), value);
}
}
if (reallyAllSkipped) {
// Null = skip the chart
return null;
}
data.put("values", values);
return data;
}
}
/**
* Represents a custom single line chart.
*/
public static class SingleLineChart extends CustomChart {
private final Callable<Integer> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SingleLineChart(String chartId, Callable<Integer> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JSONObject getChartData() throws Exception {
JSONObject data = new JSONObject();
int value = callable.call();
if (value == 0) {
// Null = skip the chart
return null;
}
data.put("value", value);
return data;
}
}
/**
* Represents a custom multi line chart.
*/
public static class MultiLineChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public MultiLineChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JSONObject getChartData() throws Exception {
JSONObject data = new JSONObject();
JSONObject values = new JSONObject();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, Integer> entry : map.entrySet()) {
if (entry.getValue() == 0) {
continue; // Skip this invalid
}
allSkipped = false;
values.put(entry.getKey(), entry.getValue());
}
if (allSkipped) {
// Null = skip the chart
return null;
}
data.put("values", values);
return data;
}
}
/**
* Represents a custom simple bar chart.
*/
public static class SimpleBarChart extends CustomChart {
private final Callable<Map<String, Integer>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public SimpleBarChart(String chartId, Callable<Map<String, Integer>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JSONObject getChartData() throws Exception {
JSONObject data = new JSONObject();
JSONObject values = new JSONObject();
Map<String, Integer> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
for (Map.Entry<String, Integer> entry : map.entrySet()) {
JSONArray categoryValues = new JSONArray();
categoryValues.add(entry.getValue());
values.put(entry.getKey(), categoryValues);
}
data.put("values", values);
return data;
}
}
/**
* Represents a custom advanced bar chart.
*/
public static class AdvancedBarChart extends CustomChart {
private final Callable<Map<String, int[]>> callable;
/**
* Class constructor.
*
* @param chartId The id of the chart.
* @param callable The callable which is used to request the chart data.
*/
public AdvancedBarChart(String chartId, Callable<Map<String, int[]>> callable) {
super(chartId);
this.callable = callable;
}
@Override
protected JSONObject getChartData() throws Exception {
JSONObject data = new JSONObject();
JSONObject values = new JSONObject();
Map<String, int[]> map = callable.call();
if (map == null || map.isEmpty()) {
// Null = skip the chart
return null;
}
boolean allSkipped = true;
for (Map.Entry<String, int[]> entry : map.entrySet()) {
if (entry.getValue().length == 0) {
continue; // Skip this invalid
}
allSkipped = false;
JSONArray categoryValues = new JSONArray();
for (int categoryValue : entry.getValue()) {
categoryValues.add(categoryValue);
}
values.put(entry.getKey(), categoryValues);
}
if (allSkipped) {
// Null = skip the chart
return null;
}
data.put("values", values);
return data;
}
}
}

View File

@ -0,0 +1,92 @@
package com.songoda.core.utils;
import com.songoda.core.compatibility.ServerVersion;
import org.bukkit.entity.Player;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
public class NMSUtils {
public static Class<?> getNMSClass(String className) {
try {
String fullName = "net.minecraft.server." + ServerVersion.getServerVersionString() + "." + className;
Class<?> clazz = Class.forName(fullName);
return clazz;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static Class<?> getCraftClass(String className) {
try {
String fullName = "org.bukkit.craftbukkit." + ServerVersion.getServerVersionString() + "." + className;
Class<?> clazz = Class.forName(fullName);
return clazz;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static Method getPrivateMethod(Class<?> c, String methodName, Class<?> ... parameters) throws Exception {
Method m = c.getDeclaredMethod(methodName, parameters);
m.setAccessible(true);
return m;
}
public static Field getField(Class<?> clazz, String name, boolean declared) {
try {
Field field;
if (declared) {
field = clazz.getDeclaredField(name);
} else {
field = clazz.getField(name);
}
field.setAccessible(true);
return field;
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static Object getFieldObject(Object object, Field field) {
try {
return field.get(object);
} catch (Exception e) {
e.printStackTrace();
return null;
}
}
public static void setField(Object object, String fieldName, Object fieldValue, boolean declared) {
try {
Field field;
if (declared) {
field = object.getClass().getDeclaredField(fieldName);
} else {
field = object.getClass().getField(fieldName);
}
field.setAccessible(true);
field.set(object, fieldValue);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void sendPacket(Player player, Object packet) {
try {
Object handle = player.getClass().getMethod("getHandle").invoke(player);
Object playerConnection = handle.getClass().getField("playerConnection").get(handle);
playerConnection.getClass().getMethod("sendPacket", getNMSClass("Packet")).invoke(playerConnection, packet);
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@ -0,0 +1,149 @@
package com.songoda.core.utils;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class PlayerUtils {
static Random random = new Random();
/**
* Get a list of all of the players that this player can "see"
*
* @param sender user to check against, or null for all players
* @param startingWith optional query to test: only players whose game names
* start with this
* @return list of player names that are "visible" to the player
*/
public static List<String> getVisiblePlayerNames(CommandSender sender, String startingWith) {
Player player = sender instanceof Player ? (Player) sender : null;
final String startsWith = startingWith == null || startingWith.isEmpty() ? null : startingWith.toLowerCase();
return Bukkit.getOnlinePlayers().stream()
.filter(p -> p != player)
.filter(p -> startsWith == null || p.getName().toLowerCase().startsWith(startsWith))
.filter(p -> player == null || (player.canSee(p) && p.getMetadata("vanished").isEmpty()))
.map(Player::getName)
.collect(Collectors.toList());
}
/**
* Get a list of all of the players that this player can "see"
*
* @param sender user to check against, or null for all players
* @param startingWith optional query to test: only players whose game names
* start with this
* @return list of player names that are "visible" to the player
*/
public static List<String> getVisiblePlayerDisplayNames(CommandSender sender, String startingWith) {
Player player = sender instanceof Player ? (Player) sender : null;
final String startsWith = startingWith == null || startingWith.isEmpty() ? null : startingWith.replaceAll("[^a-zA-Z]", "").toLowerCase();
return Bukkit.getOnlinePlayers().stream()
.filter(p -> p != player)
.filter(p -> startsWith == null || p.getDisplayName().replaceAll("[^a-zA-Z]", "").toLowerCase().startsWith(startsWith))
.filter(p -> player == null || (player.canSee(p) && p.getMetadata("vanished").isEmpty()))
.map(Player::getDisplayName)
.collect(Collectors.toList());
}
/**
* Get a list of all of the players that this player can "see"
*
* @param sender user to check against, or null for all players
* @param startingWith optional query to test: only players whose game names
* start with this
* @return list of players that are "visible" to the player
*/
public static List<Player> getVisiblePlayers(CommandSender sender, String startingWith) {
Player player = sender instanceof Player ? (Player) sender : null;
final String startsWith = startingWith == null || startingWith.isEmpty() ? null : startingWith.toLowerCase();
return Bukkit.getOnlinePlayers().stream()
.filter(p -> p != player)
.filter(p -> startsWith == null || p.getName().toLowerCase().startsWith(startsWith))
.filter(p -> player == null || (player.canSee(p) && p.getMetadata("vanished").isEmpty()))
.map(p -> (Player) p)
.collect(Collectors.toList());
}
/**
* Get a list of all online player names that start with a string.
*
* @param us Ourselves / who is requesting the list. Will not return this player.
* @param startsWith All names returned must start with this input string
* @return List of matching player IGN
*/
public static List<String> getAllPlayers(CommandSender us, String startsWith) {
final String arg = startsWith.toLowerCase();
return Bukkit.getOnlinePlayers().stream()
.filter(p -> us != p && p.getName().startsWith(arg))
.map(Player::getName)
.collect(Collectors.toList());
}
/**
* Get a list of all online player names that start with a string.
*
* @param us Ourselves / who is requesting the list. Will not return this player.
* @param startsWith All names returned must start with this input string
* @return List of matching player display names
*/
public static List<String> getAllPlayersDisplay(CommandSender us, String startsWith) {
final String arg = startsWith.replaceAll("[^a-zA-Z]", "").toLowerCase();
return Bukkit.getOnlinePlayers().stream()
.filter(p -> us != p && p.getDisplayName().replaceAll("[^a-zA-Z]", "").startsWith(arg))
.map(Player::getDisplayName)
.collect(Collectors.toList());
}
/**
* Search for and grab the closest match for a provided player name. <br />
* Also checks player display names if there is not an exact match.
*
* @param player player to search for
* @return Player that closest matches the input name, or null if none found
*/
public static Player findPlayer(String player) {
Player found = Bukkit.getServer().getPlayer(player);
if (found == null) {
final String searchName = player.toLowerCase();
final String searchDisplayName = player.replaceAll("[^a-zA-Z]", "").toLowerCase();
int d = 999;
for (Player p2 : Bukkit.getOnlinePlayers()) {
final String test;
if (p2.getName().toLowerCase().startsWith(searchName)) {
int d2 = p2.getName().length() - searchName.length();
if (d2 < d) {
found = p2;
d = d2;
} else if (d2 == d) {
found = null;
}
} else if ((test = p2.getDisplayName().replaceAll("[^a-zA-Z]", "")).toLowerCase().startsWith(searchDisplayName)) {
int d2 = test.length() - searchDisplayName.length();
if (d2 < d) {
found = p2;
d = d2;
} else if (d2 == d) {
found = null;
}
}
}
}
return found;
}
public static Player getRandomPlayer() {
final Collection<? extends Player> all = Bukkit.getOnlinePlayers();
final Iterator<? extends Player> alli = all.iterator();
int pick = random.nextInt(all.size());
for (; pick > 0; --pick) {
alli.next();
}
return alli.hasNext() ? alli.next() : null;
}
}

View File

@ -0,0 +1,327 @@
package com.songoda.core.utils;
import java.io.File;
import java.io.IOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.file.FileVisitResult;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.SimpleFileVisitor;
import java.nio.file.attribute.BasicFileAttributes;
import java.security.CodeSource;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
public class ReflectionUtils {
public final static double JAVA_VERSION = getVersion();
private static String system_os = System.getProperty("os.name").toLowerCase();
private static double getVersion() {
String version = System.getProperty("java.version");
int i = version.indexOf('.');
if (i != -1 && (i = version.indexOf('.', i + 1)) != -1) {
return Double.parseDouble(version.substring(0, i));
}
return Double.NaN;
}
public static File getJarFile(Class jarClass) {
return new File(jarClass.getProtectionDomain().getCodeSource().getLocation().getPath().
replace("%20", " ").replace("%25", "%"));
}
public static void setPrivateField(Class<?> c, Object handle, String fieldName, Object value) throws Exception {
Field f = c.getDeclaredField(fieldName);
f.setAccessible(true);
f.set(handle, value);
}
public static Object getPrivateField(Class<?> c, Object handle, String fieldName) throws Exception {
Field field = c.getDeclaredField(fieldName);
field.setAccessible(true);
return field.get(handle);
}
public static Object invokePrivateMethod(Class<?> c, String methodName, Object handle, Class[] types, Object[] parameters) throws Exception {
Method m = c.getDeclaredMethod(methodName, types);
m.setAccessible(true);
return m.invoke(handle, parameters);
}
// does not work in JRE 8+
private static Method getStackTraceElementMethod;
private static Method getStackTraceDepthMethod;
static {
try {
getStackTraceElementMethod = Throwable.class.getDeclaredMethod("getStackTraceElement", int.class);
getStackTraceElementMethod.setAccessible(true);
getStackTraceDepthMethod = Throwable.class.getDeclaredMethod("getStackTraceDepth");
getStackTraceDepthMethod.setAccessible(true);
} catch (Exception ex) {
getStackTraceElementMethod = getStackTraceDepthMethod = null;
}
}
/**
* If you only need one stack trace element this is faster than
* Throwable.getStackTrace()[element], it doesn't generate the full stack
* trace.
*/
public static StackTraceElement getStackTraceElement(int index) {
try {
Throwable dummy = new Throwable();
if (JAVA_VERSION >= 8 && JAVA_VERSION < 9) {
return sun.misc.SharedSecrets.getJavaLangAccess().getStackTraceElement(dummy, index);
// } else if (JAVA_VERSION >= 9) {
// return StackWalker.getInstance(Collections.emptySet(), index + 1)
// .walk(s -> s.skip(index).findFirst())
// .orElse(null);
} else if (getStackTraceElementMethod == null) {
// better than nothing, right? :/
return (new Throwable()).getStackTrace()[index];
} else {
if (index < (Integer) getStackTraceDepthMethod.invoke(dummy)) {
return (StackTraceElement) getStackTraceElementMethod.invoke(new Throwable(), index);
} else {
return null;
}
}
} catch (Throwable t) {
}
return null;
}
public static <T extends Annotation> Map<Class<?>, T> getClassesInClassPackageByAnnotation(Class<?> clazz, Class<T> annotation) throws IOException {
final Map<Class<?>, T> foundClasses = new HashMap<>();
for (Class<?> c : getAllClassesInClassPackage(clazz, false)) {
T t = c.getAnnotation(annotation);
if (t != null) {
foundClasses.put(c, t);
}
}
return foundClasses;
}
public static List<Class<?>> getAllClassesInClassPackage(Class<?> clazz, boolean recursive) throws IOException {
final List<Class<?>> packageClasses = new ArrayList<>();
final String clazzPackageName = clazz.getPackage().getName();
URL dot = clazz.getResource(".");
if (dot == null) {
// jar file
String packagePath = clazzPackageName.replace('.', '/');
CodeSource src = clazz.getProtectionDomain().getCodeSource();
if (src != null) {
URL jar = src.getLocation();
ZipInputStream zip = new ZipInputStream(jar.openStream());
ZipEntry e;
while ((e = zip.getNextEntry()) != null) {
String name = e.getName();
if (!name.endsWith("/") && name.startsWith(packagePath + "/")) {
if (recursive || name.indexOf('/', packagePath.length() + 1) == -1) {
try {
Class<?> loadedClazz = Class.forName(name.substring(0, name.lastIndexOf('.')).replace('/', '.'));
packageClasses.add(loadedClazz);
} catch (ClassNotFoundException e1) {
System.err.println("class not found: " + e1.getMessage());
}
}
}
}
}
} else {
String clazzPath = clazz.getResource(".").getPath();
if (clazzPath.startsWith("/") && system_os.contains("win")) {
clazzPath = clazzPath.substring(1);
}
Path packagePath = Paths.get(clazzPath);
Files.walkFileTree(packagePath, new SimpleFileVisitor<Path>() {
@Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
String filename = file.getName(file.getNameCount() - 1).toString();
if (filename.endsWith(".class")) {
String className = filename.replace(".class", "");
try {
Class<?> loadedClazz = Class.forName(
clazzPackageName + "." + className);
packageClasses.add(loadedClazz);
} catch (ClassNotFoundException e) {
System.err.println("class not found: " + e.getMessage());
}
}
return super.visitFile(file, attrs);
}
});
}
return packageClasses;
}
public static enum ITERATION {
NONE, CLASS, PACKAGE, FULL
}
public static List<String> getClassNamesFromPackage(Class classInPackage) throws IOException, URISyntaxException, ClassNotFoundException {
String classPath = classInPackage.getName();
int packageDelim = classPath.lastIndexOf('.');
return getClassNamesFromPackage(getJarFile(classInPackage), classPath.substring(0, packageDelim), ITERATION.NONE);
}
public static List<String> getClassNamesFromPackage(String packageName) throws IOException, URISyntaxException, ClassNotFoundException {
return getClassNamesFromPackage(packageName, ITERATION.NONE);
}
public static List<String> getClassNamesFromPackage(String packageName, ITERATION iterate) throws IOException, URISyntaxException, ClassNotFoundException {
return getClassNamesFromPackage(null, packageName, iterate);
}
public static List<String> getClassNamesFromPackage(File sourceJar, String packageName, ITERATION iterate) throws IOException, URISyntaxException, ClassNotFoundException {
// http://stackoverflow.com/questions/1456930/how-do-i-read-all-classes-from-a-java-package-in-the-classpath
ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
URL packageURL;
ArrayList<String> names = new ArrayList<String>();
if (packageName.contains("/")) {
// load as a file
packageURL = classLoader.getResource(packageName);
// todo - if there is an error, step backwards to find the first avaliable package
if (packageURL == null && packageName.contains("/")) {
// added - check to see if maybe trying to load a file?
final int i = packageName.lastIndexOf('/');
packageName = packageName.substring(0, i) + "." + packageName.substring(i + 1);
packageURL = classLoader.getResource(packageName);
}
} else {
packageName = packageName.replace(".", "/");
packageURL = classLoader.getResource(packageName);
if (sourceJar == null && packageURL == null) {
throw new IOException("Cannot open resource '" + packageName + "'");
}
}
if (sourceJar == null && packageURL == null) {
throw new IOException("Cannot open resource '" + packageName + "'");
//} else if (packageURL.getProtocol().equals("file") || ) {
// cannot do this..
} else if (sourceJar != null || packageURL.getProtocol().equals("jar")) {
// this can also be used to load jar from resources
String jarFileName;
JarFile jf;
Enumeration<JarEntry> jarEntries;
String entryName;
// build jar file name, then loop through zipped entries
jarFileName = sourceJar != null ? sourceJar.getAbsolutePath() : URLDecoder.decode(packageURL.getFile(), "UTF-8");
// changed - support for resource jar files, too
if (jarFileName.startsWith("file:/")) {
jarFileName = jarFileName.substring(system_os.contains("win") ? 5 : 4);
}
if (jarFileName.startsWith("/") && system_os.contains("win")) {
jarFileName = jarFileName.substring(1);
}
if (jarFileName.contains("!")) {
jarFileName = jarFileName.substring(0, jarFileName.indexOf("!"));
}
jf = new JarFile(jarFileName);
jarEntries = jf.entries();
// in case of multiple sub-classes, keep track of what classes have been searched
ArrayList<String> loaded = new ArrayList<String>();
while (jarEntries.hasMoreElements()) {
entryName = jarEntries.nextElement().getName();
if (entryName.startsWith(packageName) && entryName.length() > packageName.length() && entryName.toLowerCase().endsWith(".class")) {
if (entryName.contains(".")) {
entryName = entryName.substring(packageName.length() + 1, entryName.lastIndexOf('.'));
}
// iteration test
if (!entryName.contains("/") || (iterate == ITERATION.PACKAGE || iterate == ITERATION.FULL)) {
if (entryName.contains("$")) { // added - sub-package test
// added - iteration
if (iterate == ITERATION.CLASS || iterate == ITERATION.FULL) {
entryName = entryName.substring(0, entryName.indexOf('$')).replace('/', '.');
if (!loaded.contains(entryName)) {
loaded.add(entryName);
try {
Class c = Class.forName(packageName.replace('/', '.') + "." + entryName);
for (Class c2 : c.getDeclaredClasses()) {
names.add(entryName + "." + c2.getSimpleName());
}
} catch (Throwable t) {
}
}
}
} else {
names.add(entryName.replace('/', '.'));
}
}
}
}
} else {
// hits here if running in IDE
// loop through files in classpath
URI uri = new URI(packageURL.toString());
File folder = new File(uri.getPath());
// won't work with path which contains blank (%20)
// File folder = new File(packageURL.getFile());
File[] contenuti = folder.listFiles();
// in case of multiple sub-classes, keep track of what classes have been searched
ArrayList<String> loaded = new ArrayList<String>();
String entryName;
for (File actual : contenuti) {
entryName = actual.getName();
if (entryName.contains(".")) { // added - folder check
entryName = entryName.substring(0, entryName.lastIndexOf('.'));
if (entryName.contains("$")) { // added - sub-package test
// added - iteration
if (iterate == ITERATION.CLASS || iterate == ITERATION.FULL) {
entryName = entryName.substring(0, entryName.indexOf('$'));
if (!loaded.contains(entryName)) {
loaded.add(entryName);
Class c = Class.forName(packageName.replace('/', '.') + "." + entryName);
for (Class c2 : c.getDeclaredClasses()) {
names.add(entryName + "." + c2.getSimpleName());
}
}
}
} else {
names.add(entryName);
}
} else if (iterate == ITERATION.PACKAGE || iterate == ITERATION.FULL) {
// added - iteration
for (String sub : getClassNamesFromPackage(packageName + "/" + entryName, iterate)) {
names.add(entryName + "." + sub);
}
}
}
}
return names;
}
}

View File

@ -0,0 +1,40 @@
package com.songoda.core.utils;
import org.bukkit.block.BlockFace;
public class RotationUtils {
public static float faceToYaw(BlockFace face) {
switch (face) {
case NORTH:
return 180F;
case SOUTH:
return 0F;
case EAST:
return -90F;
case WEST:
return 90F;
}
return 0F;
}
public static BlockFace yawToFace(float face) {
switch ((int) Math.round((face + 360) / 90) * 90) {
case 0:
case 360:
return BlockFace.SOUTH;
case 180:
case 540:
return BlockFace.NORTH;
case 270:
case 630:
return BlockFace.EAST;
case 90:
case 450:
return BlockFace.WEST;
}
// idk
return BlockFace.SOUTH;
}
}

View File

@ -0,0 +1,111 @@
package com.songoda.core.utils;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.nio.ByteBuffer;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetDecoder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import org.bukkit.ChatColor;
public class TextUtils {
public static String formatText(String text) {
return formatText(text, false);
}
public static String formatText(String text, boolean capitalize) {
if (text == null || text.equals(""))
return "";
if (capitalize)
text = text.substring(0, 1).toUpperCase() + text.substring(1);
return ChatColor.translateAlternateColorCodes('&', text);
}
public static String convertToInvisibleString(String s) {
if (s == null || s.equals(""))
return "";
StringBuilder hidden = new StringBuilder();
for (char c : s.toCharArray()) hidden.append(ChatColor.COLOR_CHAR + "").append(c);
return hidden.toString();
}
protected static final List<Charset> supportedCharsets = new ArrayList();
static {
supportedCharsets.add(StandardCharsets.UTF_8); // UTF-8 BOM: EF BB BF
supportedCharsets.add(StandardCharsets.ISO_8859_1); // also starts with EF BB BF
//supportedCharsets.add(StandardCharsets.UTF_16LE); // FF FE
//supportedCharsets.add(StandardCharsets.UTF_16BE); // FE FF
//supportedCharsets.add(StandardCharsets.UTF_16);
try {
supportedCharsets.add(Charset.forName("windows-1253"));
supportedCharsets.add(Charset.forName("ISO-8859-7"));
} catch (Exception e) {
} // UnsupportedCharsetException technically can be thrown, but can also be ignored
supportedCharsets.add(StandardCharsets.US_ASCII);
}
public static Charset detectCharset(File f, Charset def) {
byte[] buffer = new byte[2048];
int read = -1;
// read the first 2kb of the file and test the file's encoding
try (FileInputStream input = new FileInputStream(f)) {
read = input.read(buffer);
} catch (Exception ex) {
return null;
}
return read != -1 ? detectCharset(buffer, read, def) : def;
}
public static Charset detectCharset(BufferedInputStream reader, Charset def) {
byte[] buffer = new byte[2048];
int read;
try {
reader.mark(2048);
read = reader.read(buffer);
reader.reset();
} catch (Exception ex) {
return null;
}
return read != -1 ? detectCharset(buffer, read, def) : def;
}
public static Charset detectCharset(byte[] data, int len, Charset def) {
// check the file header
if (len > 4) {
if (data[0] == (byte) 0xFF && data[1] == (byte) 0xFE) {
return StandardCharsets.UTF_16LE;
// FF FE 00 00 is UTF-32LE
} else if (data[0] == (byte) 0xFE && data[1] == (byte) 0xFF) {
return StandardCharsets.UTF_16BE;
// 00 00 FE FF is UTF-32BE
} else if (data[0] == (byte) 0xEF && data[1] == (byte) 0xBB && data[2] == (byte) 0xBF) { // UTF-8 with BOM, same sig as ISO-8859-1
return StandardCharsets.UTF_8;
}
}
// iterate through sets to test, and return the first that is ok
for (Charset charset : supportedCharsets) {
if (charset != null && isCharset(data, len, charset)) {
return charset;
}
}
return def;
}
public static boolean isCharset(byte[] data, int len, Charset charset) {
try {
CharsetDecoder decoder = charset.newDecoder();
decoder.reset();
decoder.decode(ByteBuffer.wrap(data));
return true;
} catch (CharacterCodingException e) {
}
return false;
}
}

View File

@ -1,7 +0,0 @@
package com.songoda.update;
public interface Module {
void run(Plugin plugin);
}

View File

@ -1,122 +0,0 @@
package com.songoda.update;
import com.songoda.update.command.CommandManager;
import com.songoda.update.listeners.LoginListener;
import com.songoda.update.utils.ServerVersion;
import org.apache.commons.lang.ArrayUtils;
import org.bukkit.Bukkit;
import org.bukkit.plugin.java.JavaPlugin;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import org.json.simple.parser.ParseException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.List;
public class SongodaUpdate {
private static String prefix = "[SongodaUpdate] ";
private ServerVersion serverVersion = ServerVersion.fromPackageName(Bukkit.getServer().getClass().getPackage().getName());
private static int version = 1;
private static List<Plugin> registeredPlugins = new ArrayList<>();
private static SongodaUpdate INSTANCE;
private static JavaPlugin hijackedPlugin;
public SongodaUpdate() {
hijackedPlugin = registeredPlugins.get(0).getJavaPlugin();
Bukkit.getPluginManager().registerEvents(new LoginListener(this), hijackedPlugin);
new CommandManager(this);
}
private void update(Plugin plugin) {
try {
URL url = new URL("http://update.songoda.com/index.php?plugin=" + plugin.getSongodaId()
+ "&version=" + plugin.getJavaPlugin().getDescription().getVersion()
+ "&updaterVersion=" + version);
HttpURLConnection urlConnection = (HttpURLConnection) url.openConnection();
urlConnection.setConnectTimeout(5000);
InputStream is = urlConnection.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
int numCharsRead;
char[] charArray = new char[1024];
StringBuffer sb = new StringBuffer();
while ((numCharsRead = isr.read(charArray)) > 0) {
sb.append(charArray, 0, numCharsRead);
}
String jsonString = sb.toString();
JSONObject json = (JSONObject) new JSONParser().parse(jsonString);
plugin.setLatestVersion((String) json.get("latestVersion"));
plugin.setMarketplaceLink((String) json.get("link"));
plugin.setNotification((String) json.get("notification"));
plugin.setChangeLog((String) json.get("changeLog"));
plugin.setJson(json);
for (Module module : plugin.getModules()) {
module.run(plugin);
}
} catch (IOException e) {
System.out.println("Connection with Songoda servers failed...");
} catch (ParseException e) {
System.out.println("Failed to parse json.");
}
}
public static Plugin load(Plugin plugin) {
registeredPlugins.add(plugin);
System.out.println(prefix + "Hooked " + plugin.getJavaPlugin().getName() + ".");
if (INSTANCE == null) INSTANCE = new SongodaUpdate();
getInstance().update(plugin);
return plugin;
}
public ServerVersion getServerVersion() {
return serverVersion;
}
public boolean isServerVersion(ServerVersion version) {
return serverVersion == version;
}
public boolean isServerVersion(ServerVersion... versions) {
return ArrayUtils.contains(versions, serverVersion);
}
public boolean isServerVersionAtLeast(ServerVersion version) {
return serverVersion.ordinal() >= version.ordinal();
}
public List<Plugin> getPlugins() {
return new ArrayList<>(registeredPlugins);
}
public static int getVersion() {
return version;
}
public String getPrefix() {
return prefix;
}
public static JavaPlugin getHijackedPlugin() {
return hijackedPlugin;
}
public static SongodaUpdate getInstance() {
return INSTANCE;
}
}

View File

@ -1,71 +0,0 @@
package com.songoda.update.command;
import com.songoda.update.SongodaUpdate;
import org.bukkit.command.CommandSender;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public abstract class AbstractCommand {
private final boolean noConsole;
private AbstractCommand parent = null;
private boolean hasArgs = false;
private String command;
private List<String> subCommand = new ArrayList<>();
protected AbstractCommand(AbstractCommand parent, boolean noConsole, String... command) {
if (parent != null) {
this.subCommand = Arrays.asList(command);
} else {
this.command = Arrays.asList(command).get(0);
}
this.parent = parent;
this.noConsole = noConsole;
}
protected AbstractCommand(boolean noConsole, boolean hasArgs, String... command) {
this.command = Arrays.asList(command).get(0);
this.hasArgs = hasArgs;
this.noConsole = noConsole;
}
public AbstractCommand getParent() {
return parent;
}
public String getCommand() {
return command;
}
public List<String> getSubCommand() {
return subCommand;
}
public void addSubCommand(String command) {
subCommand.add(command);
}
protected abstract ReturnType runCommand(SongodaUpdate instance, CommandSender sender, String... args);
protected abstract List<String> onTab(SongodaUpdate instance, CommandSender sender, String... args);
public abstract String getPermissionNode();
public abstract String getSyntax();
public abstract String getDescription();
public boolean hasArgs() {
return hasArgs;
}
public boolean isNoConsole() {
return noConsole;
}
public enum ReturnType {SUCCESS, FAILURE, SYNTAX_ERROR}
}

View File

@ -1,116 +0,0 @@
package com.songoda.update.command;
import com.songoda.update.SongodaUpdate;
import com.songoda.update.command.commands.CommandDiag;
import com.songoda.update.command.commands.CommandSongoda;
import com.songoda.update.utils.Methods;
import org.bukkit.Bukkit;
import org.bukkit.command.*;
import org.bukkit.entity.Player;
import org.bukkit.plugin.Plugin;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
public class CommandManager implements CommandExecutor {
private SongodaUpdate instance;
private TabManager tabManager;
private List<AbstractCommand> commands = new ArrayList<>();
public CommandManager(SongodaUpdate instance) {
this.instance = instance;
this.tabManager = new TabManager(this);
registerCommandDynamically("songoda", this);
AbstractCommand commandSongoda = addCommand(new CommandSongoda());
addCommand(new CommandDiag(commandSongoda));
for (AbstractCommand abstractCommand : commands) {
if (abstractCommand.getParent() != null) continue;
//instance.getCommand(abstractCommand.getCommand()).setTabCompleter(tabManager);
}
}
private AbstractCommand addCommand(AbstractCommand abstractCommand) {
commands.add(abstractCommand);
return abstractCommand;
}
@Override
public boolean onCommand(CommandSender commandSender, Command command, String s, String[] strings) {
for (AbstractCommand abstractCommand : commands) {
if (abstractCommand.getCommand() != null && abstractCommand.getCommand().equalsIgnoreCase(command.getName().toLowerCase())) {
if (strings.length == 0 || abstractCommand.hasArgs()) {
processRequirements(abstractCommand, commandSender, strings);
return true;
}
} else if (strings.length != 0 && abstractCommand.getParent() != null && abstractCommand.getParent().getCommand().equalsIgnoreCase(command.getName())) {
String cmd = strings[0];
String cmd2 = strings.length >= 2 ? String.join(" ", strings[0], strings[1]) : null;
for (String cmds : abstractCommand.getSubCommand()) {
if (cmd.equalsIgnoreCase(cmds) || (cmd2 != null && cmd2.equalsIgnoreCase(cmds))) {
processRequirements(abstractCommand, commandSender, strings);
return true;
}
}
}
}
commandSender.sendMessage(instance.getPrefix() + Methods.formatText("&7The command you entered does not exist or is spelt incorrectly."));
return true;
}
private void processRequirements(AbstractCommand command, CommandSender sender, String[] strings) {
if (!(sender instanceof Player) && command.isNoConsole()) {
sender.sendMessage("You must be a player to use this command.");
return;
}
if (command.getPermissionNode() == null || sender.hasPermission(command.getPermissionNode())) {
AbstractCommand.ReturnType returnType = command.runCommand(instance, sender, strings);
if (returnType == AbstractCommand.ReturnType.SYNTAX_ERROR) {
sender.sendMessage(instance.getPrefix() + Methods.formatText("&cInvalid Syntax!"));
sender.sendMessage(instance.getPrefix() + Methods.formatText("&7The valid syntax is: &6" + command.getSyntax() + "&7."));
}
return;
}
sender.sendMessage(instance.getPrefix() + "You do not have permission to run this command.");
}
public List<AbstractCommand> getCommands() {
return Collections.unmodifiableList(commands);
}
private void registerCommandDynamically(String command, CommandExecutor executor) {
try {
// Retrieve the SimpleCommandMap from the server
Class<?> classCraftServer = Bukkit.getServer().getClass();
Field fieldCommandMap = classCraftServer.getDeclaredField("commandMap");
fieldCommandMap.setAccessible(true);
SimpleCommandMap commandMap = (SimpleCommandMap) fieldCommandMap.get(Bukkit.getServer());
// Construct a new Command object
Constructor<PluginCommand> constructorPluginCommand = PluginCommand.class.getDeclaredConstructor(String.class, Plugin.class);
constructorPluginCommand.setAccessible(true);
PluginCommand commandObject = constructorPluginCommand.newInstance(command, SongodaUpdate.getHijackedPlugin());
commandObject.setExecutor(executor);
// Set tab complete
commandObject.setTabCompleter(tabManager);
// Register the command
Field fieldKnownCommands = SimpleCommandMap.class.getDeclaredField("knownCommands");
fieldKnownCommands.setAccessible(true);
Map<String, Command> knownCommands = (Map<String, Command>) fieldKnownCommands.get(commandMap);
knownCommands.put(command, commandObject);
} catch (ReflectiveOperationException e) {
e.printStackTrace();
}
}
}

View File

@ -1,63 +0,0 @@
package com.songoda.update.command;
import com.songoda.update.SongodaUpdate;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.command.TabCompleter;
import java.util.ArrayList;
import java.util.List;
public class TabManager implements TabCompleter {
private final CommandManager commandManager;
TabManager(CommandManager commandManager) {
this.commandManager = commandManager;
}
@Override
public List<String> onTabComplete(CommandSender sender, Command command, String alias, String[] strings) {
for (AbstractCommand abstractCommand : commandManager.getCommands()) {
if (abstractCommand.getCommand() != null && abstractCommand.getCommand().equalsIgnoreCase(command.getName()) && !abstractCommand.hasArgs()) {
if (strings.length == 1) {
List<String> subs = new ArrayList<>();
for (AbstractCommand ac : commandManager.getCommands()) {
if (ac.getSubCommand() == null) continue;
subs.addAll(ac.getSubCommand());
}
subs.removeIf(s -> !s.toLowerCase().startsWith(strings[0].toLowerCase()));
return subs;
}
} else if (strings.length != 0
&& abstractCommand.getCommand() != null
&& abstractCommand.getCommand().equalsIgnoreCase(command.getName().toLowerCase())) {
String cmd = strings[0];
String cmd2 = strings.length >= 2 ? String.join(" ", strings[0], strings[1]) : null;
if (abstractCommand.hasArgs()) {
return onCommand(abstractCommand, strings, sender);
} else {
for (String cmds : abstractCommand.getSubCommand()) {
if (cmd.equalsIgnoreCase(cmds) || (cmd2 != null && cmd2.equalsIgnoreCase(cmds))) {
return onCommand(abstractCommand, strings, sender);
}
}
}
}
}
return null;
}
private List<String> onCommand(AbstractCommand abstractCommand, String[] strings, CommandSender sender) {
List<String> list = abstractCommand.onTab(SongodaUpdate.getInstance(), sender, strings);
String str = strings[strings.length - 1];
if (list != null && str != null && str.length() >= 1) {
try {
list.removeIf(s -> !s.toLowerCase().startsWith(str.toLowerCase()));
} catch (UnsupportedOperationException ignored) {
}
}
return list;
}
}

View File

@ -1,43 +0,0 @@
package com.songoda.update.command.commands;
import com.songoda.update.SongodaUpdate;
import com.songoda.update.command.AbstractCommand;
import com.songoda.update.gui.GUIOverview;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import java.util.List;
public class CommandSongoda extends AbstractCommand {
public CommandSongoda() {
super(true, false, "songoda");
}
@Override
protected ReturnType runCommand(SongodaUpdate instance, CommandSender sender, String... args) {
new GUIOverview(instance, (Player) sender);
return ReturnType.SUCCESS;
}
@Override
protected List<String> onTab(SongodaUpdate instance, CommandSender sender, String... args) {
return null;
}
@Override
public String getPermissionNode() {
return "songoda.admin";
}
@Override
public String getSyntax() {
return "/songoda";
}
@Override
public String getDescription() {
return "Displays this interface.";
}
}

View File

@ -1,52 +0,0 @@
package com.songoda.update.gui;
import com.songoda.update.Plugin;
import com.songoda.update.SongodaUpdate;
import com.songoda.update.utils.gui.AbstractGUI;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import java.util.List;
public class GUIOverview extends AbstractGUI {
private final SongodaUpdate update;
public GUIOverview(SongodaUpdate update, Player player) {
super(player);
this.update = update;
init("Songoda Update", 36);
}
@Override
protected void constructGUI() {
List<Plugin> plugins = update.getPlugins();
for (int i = 0; i < plugins.size(); i++) {
Plugin plugin = plugins.get(i);
createButton(i + 9, Material.STONE, "&6" + plugin.getJavaPlugin().getName(),
"&7Latest Version: " + plugin.getLatestVersion(),
"&7Installed Version: " + plugin.getJavaPlugin().getDescription().getVersion(),
"",
"Change log:",
plugin.getChangeLog(),
"",
"&6Click for the marketplace page link.");
registerClickable(i + 9, ((player1, inventory1, cursor, slot, type) ->
player.sendMessage(plugin.getMarketplaceLink())));
}
}
@Override
protected void registerClickables() {
}
@Override
protected void registerOnCloses() {
}
}

View File

@ -1,28 +0,0 @@
package com.songoda.update.listeners;
import com.songoda.update.Plugin;
import com.songoda.update.SongodaUpdate;
import org.bukkit.Bukkit;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
public class LoginListener implements Listener {
private SongodaUpdate instance;
public LoginListener(SongodaUpdate instance) {
this.instance = instance;
}
@EventHandler
public void onLogin(PlayerLoginEvent event) {
if (event.getPlayer().isOp()) {
for (Plugin plugin : instance.getPlugins()) {
if (plugin.getNotification() != null)
Bukkit.getScheduler().scheduleSyncDelayedTask(plugin.getJavaPlugin(), () ->
event.getPlayer().sendMessage("[" + plugin.getJavaPlugin().getName() + "] " + plugin.getNotification()), 10L);
}
}
}
}

View File

@ -1,99 +0,0 @@
package com.songoda.update.utils;
import com.songoda.update.SongodaUpdate;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.HandlerList;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerChatEvent;
import org.bukkit.plugin.java.JavaPlugin;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
public class AbstractChatConfirm implements Listener {
private static final List<UUID> registered = new ArrayList<>();
private final Player player;
private final ChatConfirmHandler handler;
private OnClose onClose = null;
private Listener listener;
public AbstractChatConfirm(Player player, ChatConfirmHandler hander) {
this.player = player;
this.handler = hander;
player.closeInventory();
initializeListeners(SongodaUpdate.getHijackedPlugin());
registered.add(player.getUniqueId());
}
public static boolean isRegistered(Player player) {
return registered.contains(player.getUniqueId());
}
public static boolean unregister(Player player) {
return registered.remove(player.getUniqueId());
}
public void initializeListeners(JavaPlugin plugin) {
this.listener = new Listener() {
@EventHandler
public void onChat(AsyncPlayerChatEvent event) {
Player player = event.getPlayer();
if (!AbstractChatConfirm.isRegistered(player)) return;
AbstractChatConfirm.unregister(player);
event.setCancelled(true);
ChatConfirmEvent chatConfirmEvent = new ChatConfirmEvent(player, event.getMessage());
handler.onChat(chatConfirmEvent);
if (onClose != null) {
onClose.onClose();
}
HandlerList.unregisterAll(listener);
}
};
Bukkit.getPluginManager().registerEvents(listener, SongodaUpdate.getHijackedPlugin());
}
public void setOnClose(OnClose onClose) {
this.onClose = onClose;
}
public interface ChatConfirmHandler {
void onChat(ChatConfirmEvent event);
}
public interface OnClose {
void onClose();
}
public class ChatConfirmEvent {
private final Player player;
private final String message;
public ChatConfirmEvent(Player player, String message) {
this.player = player;
this.message = message;
}
public Player getPlayer() {
return player;
}
public String getMessage() {
return message;
}
}
}

View File

@ -1,19 +0,0 @@
package com.songoda.update.utils;
import org.bukkit.ChatColor;
public class Methods {
public static String formatText(String text) {
return formatText(text, false);
}
public static String formatText(String text, boolean cap) {
if (text == null || text.equals(""))
return "";
if (cap)
text = text.substring(0, 1).toUpperCase() + text.substring(1);
return ChatColor.translateAlternateColorCodes('&', text);
}
}

Some files were not shown because too many files have changed in this diff Show More