diff --git a/.idea/.name b/.idea/.name
new file mode 100644
index 0000000..50b31b8
--- /dev/null
+++ b/.idea/.name
@@ -0,0 +1 @@
+Zoot-1.0-SNAPSHOT
\ No newline at end of file
diff --git a/.idea/artifacts/zoot_jar.xml b/.idea/artifacts/zoot_jar.xml
new file mode 100644
index 0000000..4ec6b26
--- /dev/null
+++ b/.idea/artifacts/zoot_jar.xml
@@ -0,0 +1,17 @@
+
+
+ $PROJECT_DIR$/out/artifacts/zoot_jar
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/compiler.xml b/.idea/compiler.xml
new file mode 100644
index 0000000..3dc2ea7
--- /dev/null
+++ b/.idea/compiler.xml
@@ -0,0 +1,36 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/dictionaries/skruf.xml b/.idea/dictionaries/skruf.xml
new file mode 100644
index 0000000..ad61e56
--- /dev/null
+++ b/.idea/dictionaries/skruf.xml
@@ -0,0 +1,35 @@
+
+
+
+ addperm
+ addpermission
+ clearinv
+ deletepermission
+ delperm
+ deserialized
+ gamemode
+ gson
+ hideplayer
+ listpermissions
+ listperms
+ mojang
+ setcolor
+ setmaxslots
+ setprefix
+ setslots
+ setspawn
+ setsuffix
+ setweight
+ setworldspawn
+ showplayer
+ teleported
+ unblacklisted
+ uninherit
+ uninherited
+ unmuted
+ uuids
+ worldspawn
+ zoot
+
+
+
\ No newline at end of file
diff --git a/.idea/encodings.xml b/.idea/encodings.xml
new file mode 100644
index 0000000..66bd79e
--- /dev/null
+++ b/.idea/encodings.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/Maven__com_google_code_gson_gson_2_8_5.xml b/.idea/libraries/Maven__com_google_code_gson_gson_2_8_5.xml
new file mode 100644
index 0000000..2888f96
--- /dev/null
+++ b/.idea/libraries/Maven__com_google_code_gson_gson_2_8_5.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/Maven__org_apache_commons_commons_pool2_2_4_2.xml b/.idea/libraries/Maven__org_apache_commons_commons_pool2_2_4_2.xml
new file mode 100644
index 0000000..2970b5e
--- /dev/null
+++ b/.idea/libraries/Maven__org_apache_commons_commons_pool2_2_4_2.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/Maven__org_mongodb_mongo_java_driver_3_8_2.xml b/.idea/libraries/Maven__org_mongodb_mongo_java_driver_3_8_2.xml
new file mode 100644
index 0000000..e14cbdf
--- /dev/null
+++ b/.idea/libraries/Maven__org_mongodb_mongo_java_driver_3_8_2.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/libraries/Maven__redis_clients_jedis_2_9_0.xml b/.idea/libraries/Maven__redis_clients_jedis_2_9_0.xml
new file mode 100644
index 0000000..77144ff
--- /dev/null
+++ b/.idea/libraries/Maven__redis_clients_jedis_2_9_0.xml
@@ -0,0 +1,13 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/misc.xml b/.idea/misc.xml
new file mode 100644
index 0000000..90d6598
--- /dev/null
+++ b/.idea/misc.xml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..fbf9921
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/uiDesigner.xml b/.idea/uiDesigner.xml
new file mode 100644
index 0000000..e96534f
--- /dev/null
+++ b/.idea/uiDesigner.xml
@@ -0,0 +1,124 @@
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+ -
+
+
+
+
+ -
+
+
+ -
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/workspace.xml b/.idea/workspace.xml
new file mode 100644
index 0000000..ab7fe1b
--- /dev/null
+++ b/.idea/workspace.xml
@@ -0,0 +1,1050 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ tag
+ defaultTag
+ profile =
+ zoo
+ static
+ activetag
+ getFullDisplayName
+ profile
+ getActiveTag
+ activ
+ getActiveTag()
+ get
+ getActiveRank
+ activeGrantactiveGrant
+ pig
+ va
+ zootr
+ zoot
+ pidg
+ rage
+ lom
+ h
+ pid
+ org.bik
+ ChatColor
+ color
+ mongo
+ cha
+ onAsyncPlayerChat
+ MONGO
+
+
+ import com.minexd.proton.bootstrap
+ proton.game
+ Validate.isTrue(
+ = "
+ "
+ uuid-cache
+ @CPL(
+
+
+ C:\Users\skruf\Documents\Projects\MineXD\proton
+ C:\Users\skruf\Documents\Projects\MineXD\zoot
+ C:\Users\steve\Desktop\IntelliJ Workspace\minexd-zoot-premiumsrc\Zoot 12.12.18\Zoot\src\main\java\com\minexd\zoot\profile
+ C:\Zoot\zoot\src\main\java\com\minexd\zoot\cache
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 1537243184518
+
+
+ 1537243184518
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ zoot:jar
+
+
+
+
+
+
+
+
+
+
+
+
+ No facets are configured
+
+
+
+
+
+
+
+
+
+
+
+ Zoot
+
+
+
+
+
+
+
+
+
+
+
+ 1.8
+
+
+
+
+
+
+
+
+
+
+
+ zoot
+
+
+
+
+
+
+
+
+
+
+
+ 1.8
+
+
+
+
+
+
+
+
+
+
+
+ Maven: com.google.code.gson:gson:2.8.5
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index c54c134..89a678f 100644
--- a/README.md
+++ b/README.md
@@ -1 +1,31 @@
-# zoot
\ No newline at end of file
+zoot.admin.broadcast
+zoot.staff.clearinv
+zoot.staff.gamemode
+zoot.admin.heal
+zoot.admin.hideplayer
+zoot.staff.more
+zoot.staff.rename
+zoot.staff.setslots
+zoot.staff.setspawn
+zoot.admin.showplayer
+zoot.staff.spawn
+zoot.staff.alts
+zoot.staff.ban
+zoot.staff.check
+zoot.staff.kick
+zoot.staff.mute
+zoot.staff.unban
+zoot.staff.unmute
+zoot.staff.warn
+zoot.staff.grant
+zoot.staff.grant
+zoot.staff
+zoot.staff
+zoot.staff.mutechat
+zoot.admin.rank
+zoot.admin.rank
+zoot.admin.rank
+zoot.admin.rank
+zoot.admin.rank
+zoot.admin.rank
+zoot.admin.rank
\ No newline at end of file
diff --git a/Zoot-1.0-SNAPSHOT.iml b/Zoot-1.0-SNAPSHOT.iml
new file mode 100644
index 0000000..02e59f5
--- /dev/null
+++ b/Zoot-1.0-SNAPSHOT.iml
@@ -0,0 +1,71 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/dependency-reduced-pom.xml b/dependency-reduced-pom.xml
new file mode 100644
index 0000000..9978221
--- /dev/null
+++ b/dependency-reduced-pom.xml
@@ -0,0 +1,94 @@
+
+
+ 4.0.0
+ com.minexd
+ zoot
+ 1.0-SNAPSHOT
+
+
+
+ true
+ src/main/resources
+
+ plugin.yml
+
+
+
+ src/main/resources
+
+ plugin.yml
+
+
+
+
+
+ maven-compiler-plugin
+
+
+ 1.8
+ -parameters
+
+
+
+ maven-shade-plugin
+ 2.4.3
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
+
+ org.spigotmc
+ spigot-api
+ 1.8.8-R0.1-SNAPSHOT
+ system
+ ${project.basedir}/libs/solexspigot-1.8.8-R0.1-SNAPSHOT.jar
+
+
+ org.projectlombok
+ lombok
+ LATEST
+ system
+ ${project.basedir}/lib/lombok-1.18.6.jar
+
+
+ com.qrakn
+ honcho
+ 1.0-SNAPSHOT
+ system
+ ${project.basedir}/libs/honcho-1.0-SNAPSHOT.jar
+
+
+ com.qrakn
+ phoenix
+ 1.0-SNAPSHOT
+ system
+ ${project.basedir}/libs/phoenix-lang-1.0-SNAPSHOT.jar
+
+
+ ru.tehkode
+ PermissionsEx
+ 1.0-SNAPSHOT
+ system
+ ${project.basedir}/libs/PermissionsEx-1.23.4.jar
+
+
+ net.milkbowl.vault
+ VaultAPI
+ 1.7
+ system
+ ${project.basedir}/lib/VaultAPI-1.7.jar
+
+
+
+ UTF-8
+
+
+
diff --git a/lib/PermissionsEx-1.23.4.jar b/lib/PermissionsEx-1.23.4.jar
new file mode 100644
index 0000000..bb4b5f5
Binary files /dev/null and b/lib/PermissionsEx-1.23.4.jar differ
diff --git a/lib/VaultAPI-1.7.jar b/lib/VaultAPI-1.7.jar
new file mode 100644
index 0000000..a1a179d
Binary files /dev/null and b/lib/VaultAPI-1.7.jar differ
diff --git a/lib/honcho-1.0-SNAPSHOT.jar b/lib/honcho-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000..1ca6e86
Binary files /dev/null and b/lib/honcho-1.0-SNAPSHOT.jar differ
diff --git a/lib/lombok-1.18.6.jar b/lib/lombok-1.18.6.jar
new file mode 100644
index 0000000..bf5378a
Binary files /dev/null and b/lib/lombok-1.18.6.jar differ
diff --git a/lib/phoenix-lang-1.0-SNAPSHOT.jar b/lib/phoenix-lang-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000..3fa3fee
Binary files /dev/null and b/lib/phoenix-lang-1.0-SNAPSHOT.jar differ
diff --git a/lib/ragespigot-1.8.8-R0.1-SNAPSHOT.jar b/lib/ragespigot-1.8.8-R0.1-SNAPSHOT.jar
new file mode 100644
index 0000000..db31f49
Binary files /dev/null and b/lib/ragespigot-1.8.8-R0.1-SNAPSHOT.jar differ
diff --git a/libs/PermissionsEx-1.23.4.jar b/libs/PermissionsEx-1.23.4.jar
new file mode 100644
index 0000000..bb4b5f5
Binary files /dev/null and b/libs/PermissionsEx-1.23.4.jar differ
diff --git a/libs/honcho-1.0-SNAPSHOT.jar b/libs/honcho-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000..1ca6e86
Binary files /dev/null and b/libs/honcho-1.0-SNAPSHOT.jar differ
diff --git a/libs/phoenix-lang-1.0-SNAPSHOT.jar b/libs/phoenix-lang-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000..3fa3fee
Binary files /dev/null and b/libs/phoenix-lang-1.0-SNAPSHOT.jar differ
diff --git a/libs/ragespigot-1.8.8-R0.1-SNAPSHOT.jar b/libs/ragespigot-1.8.8-R0.1-SNAPSHOT.jar
new file mode 100644
index 0000000..db31f49
Binary files /dev/null and b/libs/ragespigot-1.8.8-R0.1-SNAPSHOT.jar differ
diff --git a/libs/solexspigot-1.8.8-R0.1-SNAPSHOT.jar b/libs/solexspigot-1.8.8-R0.1-SNAPSHOT.jar
new file mode 100644
index 0000000..cfba3f5
Binary files /dev/null and b/libs/solexspigot-1.8.8-R0.1-SNAPSHOT.jar differ
diff --git a/out/artifacts/zoot_jar/zoot.jar b/out/artifacts/zoot_jar/zoot.jar
new file mode 100644
index 0000000..1705522
Binary files /dev/null and b/out/artifacts/zoot_jar/zoot.jar differ
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000..9be199d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,122 @@
+
+
+ 4.0.0
+
+ com.minexd
+ zoot
+ 1.0-SNAPSHOT
+
+
+ UTF-8
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+
+ 1.8
+ -parameters
+
+
+
+ org.apache.maven.plugins
+ maven-shade-plugin
+ 2.4.3
+
+
+ package
+
+ shade
+
+
+
+
+
+
+
+ src/main/resources
+ true
+
+ plugin.yml
+
+
+
+ src/main/resources
+ false
+
+ plugin.yml
+
+
+
+
+
+
+
+
+ org.spigotmc
+ spigot-api
+ 1.8.8-R0.1-SNAPSHOT
+ system
+ ${project.basedir}/libs/solexspigot-1.8.8-R0.1-SNAPSHOT.jar
+
+
+ org.projectlombok
+ lombok
+ LATEST
+ system
+ ${project.basedir}/lib/lombok-1.18.6.jar
+
+
+ com.google.code.gson
+ gson
+ LATEST
+ compile
+
+
+ org.mongodb
+ mongo-java-driver
+ 3.8.2
+ compile
+
+
+ redis.clients
+ jedis
+ 2.9.0
+ compile
+
+
+ com.qrakn
+ honcho
+ 1.0-SNAPSHOT
+ system
+ ${project.basedir}/libs/honcho-1.0-SNAPSHOT.jar
+
+
+ com.qrakn
+ phoenix
+ 1.0-SNAPSHOT
+ system
+ ${project.basedir}/libs/phoenix-lang-1.0-SNAPSHOT.jar
+
+
+ ru.tehkode
+ PermissionsEx
+ 1.0-SNAPSHOT
+ system
+ ${project.basedir}/libs/PermissionsEx-1.23.4.jar
+
+
+ net.milkbowl.vault
+ VaultAPI
+ 1.7
+ system
+ ${project.basedir}/lib/VaultAPI-1.7.jar
+
+
+
+
\ No newline at end of file
diff --git a/src/main/java/com/minexd/zoot/Locale.java b/src/main/java/com/minexd/zoot/Locale.java
new file mode 100644
index 0000000..9616e92
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/Locale.java
@@ -0,0 +1,53 @@
+package com.minexd.zoot;
+
+import java.text.MessageFormat;
+import lombok.AllArgsConstructor;
+import org.bukkit.ChatColor;
+
+@AllArgsConstructor
+public enum Locale {
+
+ FAILED_TO_LOAD_PROFILE("COMMON_ERRORS.FAILED_TO_LOAD_PROFILE"),
+ COULD_NOT_RESOLVE_PLAYER("COMMON_ERRORS.COULD_NOT_RESOLVE_PLAYER"),
+ PLAYER_NOT_FOUND("COMMON_ERRORS.PLAYER_NOT_FOUND"),
+ RANK_NOT_FOUND("COMMON_ERRORS.RANK_NOT_FOUND"),
+ STAFF_CHAT("STAFF.CHAT"),
+ STAFF_BROADCAST_PREFIX("STAFF.BROADCAST_PREFIX"),
+ STAFF_JOIN_NETWORK("STAFF.JOIN_NETWORK"),
+ STAFF_SWITCH_SERVER("STAFF.SWITCH_SERVER"),
+ STAFF_LEAVE_NETWORK("STAFF.LEAVE_NETWORK"),
+ CLEAR_CHAT_BROADCAST("CHAT.CLEAR_CHAT_BROADCAST"),
+ MUTE_CHAT_BROADCAST("CHAT.MUTE_CHAT_BROADCAST"),
+ DELAY_CHAT_ENABLED_BROADCAST("CHAT.DELAY_CHAT_ENABLED_BROADCAST"),
+ DELAY_CHAT_DISABLED_BROADCAST("CHAT.DELAY_CHAT_DISABLED_BROADCAST"),
+ CHAT_DELAYED("CHAT.CHAT_DELAYED"),
+ NETWORK_BROADCAST_PREFIX("NETWORK.BROADCAST_PREFIX"),
+ NETWORK_RANK_REFRESHED("NETWORK.RANK_REFRESH"),
+ NETWORK_RANK_DELETED("NETWORK.RANK_DELETE"),
+ CONVERSATION_SEND_MESSAGE("CONVERSATION.SEND_MESSAGE"),
+ CONVERSATION_RECEIVE_MESSAGE("CONVERSATION.RECEIVE_MESSAGE"),
+ OPTIONS_PRIVATE_MESSAGES_ENABLED("OPTIONS.PRIVATE_MESSAGES_ENABLED"),
+ OPTIONS_PRIVATE_MESSAGES_DISABLED("OPTIONS.PRIVATE_MESSAGES_DISABLED"),
+ OPTIONS_PRIVATE_MESSAGE_SOUND_ENABLED("OPTIONS.PRIVATE_MESSAGE_SOUNDS_ENABLED"),
+ OPTIONS_PRIVATE_MESSAGE_SOUND_DISABLED("OPTIONS.PRIVATE_MESSAGE_SOUNDS_DISABLED"),
+ OPTIONS_GLOBAL_CHAT_ENABLED("OPTIONS.GLOBAL_CHAT_ENABLED"),
+ OPTIONS_GLOBAL_CHAT_DISABLED("OPTIONS.GLOBAL_CHAT_DISABLED");
+
+ private String path;
+
+ public String format(Object... objects) {
+ return new MessageFormat(ChatColor.translateAlternateColorCodes('&',
+ Zoot.get().getMainConfig().getString(path))).format(objects);
+ }
+
+// public static final String PUBLIC_CHAT_MUTE_APPLIED = "&bThe public chat has been muted by {actor}";
+// public static final String PUBLIC_CHAT_DELAY_APPLIED = "&bThe public chat has been delayed by {actor}";
+// public static final String CHAT_ATTEMPT_FILTERED = "&cYour message was filtered.";
+// public static final String CHAT_ATTEMPT_PLAYER_MUTED = "&cYou are currently muted for another {time-remaining}.";
+// public static final String CHAT_ATTEMPT_PUBLIC_CHAT_MUTED = "&cThe public chat is currently muted.";
+// public static final String CHAT_ATTEMPT_PUBLIC_CHAT_DELAYED = "&cYou may chat again in {time-remaining}.";
+// public static final String OPTIONS_GLOBAL_CHAT_DISABLED = "&cYou have your public chat disabled.";
+// public static final String OPTIONS_PRIVATE_CHAT_DISABLED = "&cYou have your private messages disabled.";
+// public static final String PTIONS_PRIVATE_CHAT_DISABLED_OTHER = "&cThat player has their private messages disabled.";
+
+}
diff --git a/src/main/java/com/minexd/zoot/Zoot.java b/src/main/java/com/minexd/zoot/Zoot.java
new file mode 100644
index 0000000..c6cdf25
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/Zoot.java
@@ -0,0 +1,316 @@
+package com.minexd.zoot;
+
+import com.google.gson.Gson;
+import com.google.gson.reflect.TypeToken;
+import com.minexd.zoot.chat.Chat;
+import com.minexd.zoot.chat.command.ClearChatCommand;
+import com.minexd.zoot.chat.command.MuteChatCommand;
+import com.minexd.zoot.chat.ChatListener;
+import com.minexd.zoot.chat.command.SlowChatCommand;
+import com.minexd.zoot.config.ConfigValidation;
+import com.minexd.zoot.essentials.Essentials;
+import com.minexd.zoot.essentials.command.*;
+import com.minexd.zoot.essentials.EssentialsListener;
+import com.minexd.zoot.network.NetworkPacketListener;
+import com.minexd.zoot.network.packet.PacketAddGrant;
+import com.minexd.zoot.network.packet.PacketBroadcastPunishment;
+import com.minexd.zoot.network.packet.PacketDeleteGrant;
+import com.minexd.zoot.network.packet.PacketDeleteRank;
+import com.minexd.zoot.network.packet.PacketRefreshRank;
+import com.minexd.zoot.network.packet.PacketStaffChat;
+import com.minexd.zoot.network.packet.PacketStaffJoinNetwork;
+import com.minexd.zoot.network.packet.PacketStaffLeaveNetwork;
+import com.minexd.zoot.network.packet.PacketStaffSwitchServer;
+import com.minexd.zoot.pidgin.Pidgin;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.ProfileTypeAdapter;
+import com.minexd.zoot.profile.option.command.OptionsCommand;
+import com.minexd.zoot.profile.conversation.command.MessageCommand;
+import com.minexd.zoot.profile.conversation.command.ReplyCommand;
+import com.minexd.zoot.profile.grant.command.GrantCommand;
+import com.minexd.zoot.profile.grant.command.GrantsCommand;
+import com.minexd.zoot.profile.grant.GrantListener;
+import com.minexd.zoot.profile.ProfileListener;
+import com.minexd.zoot.profile.option.command.ToggleGlobalChatCommand;
+import com.minexd.zoot.profile.option.command.TogglePrivateMessagesCommand;
+import com.minexd.zoot.profile.option.command.ToggleSoundsCommand;
+import com.minexd.zoot.profile.punishment.command.BanCommand;
+import com.minexd.zoot.profile.punishment.command.CheckCommand;
+import com.minexd.zoot.profile.punishment.command.KickCommand;
+import com.minexd.zoot.profile.punishment.command.MuteCommand;
+import com.minexd.zoot.profile.punishment.command.UnbanCommand;
+import com.minexd.zoot.profile.punishment.command.UnmuteCommand;
+import com.minexd.zoot.profile.punishment.command.WarnCommand;
+import com.minexd.zoot.profile.staff.command.AltsCommand;
+import com.minexd.zoot.profile.punishment.listener.PunishmentListener;
+import com.minexd.zoot.profile.staff.command.StaffModeCommand;
+import com.minexd.zoot.rank.Rank;
+import com.minexd.zoot.rank.RankTypeAdapter;
+import com.minexd.zoot.profile.staff.command.StaffChatCommand;
+import com.minexd.zoot.rank.command.RankAddPermissionCommand;
+import com.minexd.zoot.rank.command.RankCreateCommand;
+import com.minexd.zoot.rank.command.RankDeleteCommand;
+import com.minexd.zoot.rank.command.RankHelpCommand;
+import com.minexd.zoot.rank.command.RankInfoCommand;
+import com.minexd.zoot.rank.command.RankInheritCommand;
+import com.minexd.zoot.rank.command.RankRemovePermissionCommand;
+import com.minexd.zoot.rank.command.RankSetColorCommand;
+import com.minexd.zoot.rank.command.RankSetPrefixCommand;
+import com.minexd.zoot.rank.command.RankSetSuffixCommand;
+import com.minexd.zoot.rank.command.RankSetWeightCommand;
+import com.minexd.zoot.rank.command.RankUninheritCommand;
+import com.minexd.zoot.rank.command.RanksCommand;
+import com.minexd.zoot.util.CC;
+import com.minexd.zoot.util.adapter.ChatColorTypeAdapter;
+import com.minexd.zoot.util.duration.Duration;
+import com.minexd.zoot.util.duration.DurationTypeAdapter;
+import com.minexd.zoot.util.menu.MenuListener;
+import com.minexd.zoot.cache.RedisCache;
+import com.mongodb.MongoClient;
+import com.mongodb.MongoClientOptions;
+import com.mongodb.MongoCredential;
+import com.mongodb.ServerAddress;
+import com.mongodb.client.MongoDatabase;
+import com.qrakn.honcho.Honcho;
+import com.qrakn.phoenix.lang.file.type.BasicConfigurationFile;
+import java.lang.reflect.Type;
+import java.util.Arrays;
+import java.util.List;
+import java.util.logging.Level;
+import lombok.Getter;
+import lombok.Setter;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+import org.bukkit.plugin.java.JavaPlugin;
+import org.bukkit.scheduler.BukkitRunnable;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+
+public class Zoot extends JavaPlugin {
+
+ public static final Gson GSON = new Gson();
+ public static final Type LIST_STRING_TYPE = new TypeToken>() {}.getType();
+
+ private static Zoot zoot;
+
+ @Getter private BasicConfigurationFile mainConfig;
+
+ @Getter private Honcho honcho;
+ @Getter private Pidgin pidgin;
+
+ @Getter private MongoDatabase mongoDatabase;
+ @Getter private JedisPool jedisPool;
+ @Getter private RedisCache redisCache;
+
+ @Getter private Essentials essentials;
+ @Getter private Chat chat;
+
+ @Getter @Setter private boolean debug;
+
+ @Override
+ public void onEnable() {
+ zoot = this;
+
+ mainConfig = new BasicConfigurationFile(this, "config");
+
+ new ConfigValidation(mainConfig.getFile(), mainConfig.getConfiguration(), 3).check();
+
+ loadMongo();
+ loadRedis();
+
+ redisCache = new RedisCache(this);
+ essentials = new Essentials(this);
+ chat = new Chat(this);
+
+ honcho = new Honcho(this);
+
+ Arrays.asList(
+ new BroadcastCommand(),
+ new ClearCommand(),
+ new DayCommand(),
+ new GameModeCommand(),
+ new HealCommand(),
+ new HidePlayerCommand(),
+ new LocationCommand(),
+ new MoreCommand(),
+ new NightCommand(),
+ new RenameCommand(),
+ new SetSlotsCommand(),
+ new SetSpawnCommand(),
+ new ShowPlayerCommand(),
+ new SpawnCommand(),
+ new SunsetCommand(),
+ new ClearChatCommand(),
+ new SlowChatCommand(),
+ new AltsCommand(),
+ new BanCommand(),
+ new CheckCommand(),
+ new KickCommand(),
+ new MuteCommand(),
+ new UnbanCommand(),
+ new UnmuteCommand(),
+ new WarnCommand(),
+ new GrantCommand(),
+ new GrantsCommand(),
+ new StaffChatCommand(),
+ new StaffModeCommand(),
+ new MuteChatCommand(),
+ new OptionsCommand(),
+ new RankAddPermissionCommand(),
+ new RankCreateCommand(),
+ new RankDeleteCommand(),
+ new RankHelpCommand(),
+ new RankInfoCommand(),
+ new RankInheritCommand(),
+ new RankRemovePermissionCommand(),
+ new RanksCommand(),
+ new RankSetColorCommand(),
+ new RankSetPrefixCommand(),
+ new RankSetSuffixCommand(),
+ new RankSetWeightCommand(),
+ new RankUninheritCommand(),
+ new ZootDebugCommand(),
+ new TeleportWorldCommand(),
+ new MessageCommand(),
+ new ReplyCommand(),
+ new ToggleGlobalChatCommand(),
+ new TogglePrivateMessagesCommand(),
+ new ToggleSoundsCommand(),
+ new PingCommand(),
+ new ListCommand()
+ ).forEach(honcho::registerCommand);
+
+ honcho.registerTypeAdapter(Rank.class, new RankTypeAdapter());
+ honcho.registerTypeAdapter(Profile.class, new ProfileTypeAdapter());
+ honcho.registerTypeAdapter(Duration.class, new DurationTypeAdapter());
+ honcho.registerTypeAdapter(ChatColor.class, new ChatColorTypeAdapter());
+
+ pidgin = new Pidgin("zoot",
+ mainConfig.getString("REDIS.HOST"),
+ mainConfig.getInteger("REDIS.PORT"),
+ mainConfig.getBoolean("REDIS.AUTHENTICATION.ENABLED") ?
+ mainConfig.getString("REDIS.AUTHENTICATION.PASSWORD") : null
+ );
+
+ Arrays.asList(
+ PacketAddGrant.class,
+ PacketBroadcastPunishment.class,
+ PacketDeleteGrant.class,
+ PacketDeleteRank.class,
+ PacketRefreshRank.class,
+ PacketStaffChat.class,
+ PacketStaffJoinNetwork.class,
+ PacketStaffLeaveNetwork.class,
+ PacketStaffSwitchServer.class
+ ).forEach(pidgin::registerPacket);
+
+ pidgin.registerListener(new NetworkPacketListener(this));
+
+ Arrays.asList(
+ new ProfileListener(this),
+ new MenuListener(this),
+ new EssentialsListener(this),
+ new ChatListener(this),
+ new GrantListener(this),
+ new PunishmentListener(this)
+ ).forEach(listener -> getServer().getPluginManager().registerEvents(listener, this));
+
+ Rank.init();
+ Profile.init();
+
+ new BukkitRunnable() {
+ @Override
+ public void run() {
+ for (Profile profile : Profile.getProfiles().values()) {
+ profile.checkGrants();
+ }
+ }
+ }.runTaskTimerAsynchronously(this, 20L, 20L);
+ }
+
+ @Override
+ public void onDisable() {
+ try {
+ jedisPool.close();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ /**
+ * Prints a message and exception to console. If the server is not in debug mode, the messages will be suppressed.
+ *
+ * @param level The log level.
+ * @param message The message.
+ * @param exception The thrown exception.
+ */
+ public void debug(Level level, String message, Exception exception) {
+ getLogger().log(level, message);
+ exception.printStackTrace();
+ }
+
+ /**
+ * Prints a message to console and server operators.
+ *
+ * @param message The message.
+ */
+ public void debug(String message) {
+ if (debug) {
+ broadcastOps(CC.translate("&e(Debug) &r" + message));
+ }
+ }
+
+ /**
+ * Prints a message triggered by an action a player performed to console and server operators.
+ *
+ * @param player The player that triggered this log.
+ * @param message The message.
+ */
+ public void debug(Player player, String message) {
+ if (debug) {
+ broadcastOps(CC.translate("&e(Debug) &r" + player.getDisplayName() + ": " + message));
+ }
+ }
+
+ /**
+ * Broadcasts a message to all server operators.
+ *
+ * @param message The message.
+ */
+ public static void broadcastOps(String message) {
+ Bukkit.getOnlinePlayers().stream().filter(Player::isOp).forEach(op -> op.sendMessage(message));
+ }
+
+ private void loadMongo() {
+ if (mainConfig.getBoolean("MONGO.AUTHENTICATION.ENABLED")) {
+ ServerAddress serverAddress = new ServerAddress(mainConfig.getString("MONGO.HOST"),
+ mainConfig.getInteger("MONGO.PORT"));
+
+ MongoCredential credential = MongoCredential.createCredential(
+ mainConfig.getString("MONGO.AUTHENTICATION.USERNAME"), "admin",
+ mainConfig.getString("MONGO.AUTHENTICATION.PASSWORD").toCharArray());
+
+ mongoDatabase = new MongoClient(serverAddress, credential, MongoClientOptions.builder().build())
+ .getDatabase("zoot");
+ } else {
+ mongoDatabase = new MongoClient(mainConfig.getString("MONGO.HOST"),
+ mainConfig.getInteger("MONGO.PORT")).getDatabase("zoot");
+ }
+ }
+
+ private void loadRedis() {
+ jedisPool = new JedisPool(mainConfig.getString("REDIS.HOST"), mainConfig.getInteger("REDIS.PORT"));
+
+ if (mainConfig.getBoolean("REDIS.AUTHENTICATION.ENABLED")) {
+ try (Jedis jedis = jedisPool.getResource()) {
+ jedis.auth(mainConfig.getString("REDIS.AUTHENTICATION.PASSWORD"));
+ }
+ }
+ }
+
+ public static Zoot get() {
+ return zoot;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/ZootAPI.java b/src/main/java/com/minexd/zoot/ZootAPI.java
new file mode 100644
index 0000000..030eac2
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/ZootAPI.java
@@ -0,0 +1,30 @@
+package com.minexd.zoot;
+
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.rank.Rank;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+
+public class ZootAPI {
+
+ public static ChatColor getColorOfPlayer(Player player) {
+ Profile profile = Profile.getProfiles().get(player.getUniqueId());
+ return profile == null ? ChatColor.WHITE : ChatColor.getByChar(profile.getActiveRank().getColor());
+ }
+
+ public static String getColoredName(Player player) {
+ Profile profile = Profile.getProfiles().get(player.getUniqueId());
+ return (profile == null ? ChatColor.WHITE : profile.getActiveRank().getColor()) + player.getName();
+ }
+
+ public static Rank getRankOfPlayer(Player player) {
+ Profile profile = Profile.getProfiles().get(player.getUniqueId());
+ return profile == null ? Rank.getDefaultRank() : profile.getActiveRank();
+ }
+
+ public static boolean isInStaffMode(Player player) {
+ Profile profile = Profile.getProfiles().get(player.getUniqueId());
+ return profile != null && player.hasPermission("zoot.staff") && profile.getStaffOptions().staffModeEnabled();
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/bootstrap/Bootstrapped.java b/src/main/java/com/minexd/zoot/bootstrap/Bootstrapped.java
new file mode 100644
index 0000000..55c3412
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/bootstrap/Bootstrapped.java
@@ -0,0 +1,15 @@
+package com.minexd.zoot.bootstrap;
+
+import com.minexd.zoot.Zoot;
+import lombok.Getter;
+
+@Getter
+public class Bootstrapped {
+
+ protected final Zoot zoot;
+
+ public Bootstrapped(Zoot zoot) {
+ this.zoot = zoot;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/bootstrap/BootstrappedListener.java b/src/main/java/com/minexd/zoot/bootstrap/BootstrappedListener.java
new file mode 100644
index 0000000..28fd253
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/bootstrap/BootstrappedListener.java
@@ -0,0 +1,12 @@
+package com.minexd.zoot.bootstrap;
+
+import com.minexd.zoot.Zoot;
+import org.bukkit.event.Listener;
+
+public class BootstrappedListener extends Bootstrapped implements Listener {
+
+ public BootstrappedListener(Zoot zoot) {
+ super(zoot);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/cache/RedisCache.java b/src/main/java/com/minexd/zoot/cache/RedisCache.java
new file mode 100644
index 0000000..96afe63
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/cache/RedisCache.java
@@ -0,0 +1,114 @@
+package com.minexd.zoot.cache;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.bootstrap.Bootstrapped;
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.net.URL;
+import java.net.URLConnection;
+import java.util.UUID;
+import java.util.logging.Level;
+import org.bukkit.Bukkit;
+import org.json.simple.parser.ParseException;
+import redis.clients.jedis.Jedis;
+
+public class RedisCache extends Bootstrapped {
+
+ public RedisCache(Zoot zoot) {
+ super(zoot);
+ }
+
+ public UUID getUuid(String name) {
+ if (zoot.getServer().isPrimaryThread()) {
+ throw new IllegalStateException("Cannot query on main thread (Redis profile cache)");
+ }
+
+ try (Jedis jedis = zoot.getJedisPool().getResource()) {
+ String uuid = jedis.hget("uuid-cache:name-to-uuid", name.toLowerCase());
+
+ if (uuid != null) {
+ return UUID.fromString(uuid);
+ }
+ } catch (Exception e) {
+ zoot.debug(Level.WARNING, "Could not connect to redis", e);
+ }
+
+ try {
+ UUID uuid = getFromMojang(name);
+
+ if (uuid != null) {
+ updateNameAndUUID(name, uuid);
+ return uuid;
+ }
+ } catch (Exception e) {
+ zoot.debug(Level.WARNING, "Could not fetch from Mojang API", e);
+ }
+
+ return null;
+ }
+
+ public void updateNameAndUUID(String name, UUID uuid) {
+ if (Bukkit.isPrimaryThread()) {
+ throw new IllegalStateException("Cannot query redis on main thread!");
+ }
+
+ try (Jedis jedis = zoot.getJedisPool().getResource()) {
+ jedis.hset("uuid-cache:name-to-uuid", name.toLowerCase(), uuid.toString());
+ jedis.hset("uuid-cache:uuid-to-name", uuid.toString(), name);
+ }
+ }
+
+ public RedisPlayerData getPlayerData(UUID uuid) {
+ if (Bukkit.isPrimaryThread()) {
+ throw new IllegalStateException("Cannot query redis on main thread!");
+ }
+
+ try (Jedis jedis = zoot.getJedisPool().getResource()) {
+ String data = jedis.hget("player-data", uuid.toString());
+
+ if (data == null) {
+ return null;
+ }
+
+ try {
+ JsonObject dataJson = new JsonParser().parse(data).getAsJsonObject();
+ return new RedisPlayerData(dataJson);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ return null;
+ }
+
+ public void updatePlayerData(RedisPlayerData playerData) {
+ try (Jedis jedis = zoot.getJedisPool().getResource()) {
+ jedis.hset("player-data", playerData.getUuid().toString(), playerData.getJson().toString());
+ }
+ }
+
+ private static UUID getFromMojang(String name) throws IOException, ParseException {
+ URL url = new URL("https://api.mojang.com/users/profiles/minecraft/" + name);
+ URLConnection conn = url.openConnection();
+ conn.setDoOutput(true);
+
+ BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
+ String line = reader.readLine();
+
+ if (line == null) {
+ return null;
+ }
+
+ String[] id = line.split(",");
+
+ String part = id[0];
+ part = part.substring(7, 39);
+
+ return UUID.fromString(String.valueOf(part).replaceAll("(\\w{8})(\\w{4})(\\w{4})(\\w{4})(\\w{12})",
+ "$1-$2-$3-$4-$5"));
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/cache/RedisPlayerData.java b/src/main/java/com/minexd/zoot/cache/RedisPlayerData.java
new file mode 100644
index 0000000..18ff225
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/cache/RedisPlayerData.java
@@ -0,0 +1,50 @@
+package com.minexd.zoot.cache;
+
+import com.google.gson.JsonObject;
+import com.minexd.zoot.util.TimeUtil;
+import com.minexd.zoot.util.json.JsonChain;
+import java.util.UUID;
+import lombok.Data;
+
+@Data
+public class RedisPlayerData {
+
+ private UUID uuid;
+ private String username;
+ private LastAction lastAction;
+ private String lastSeenServer;
+ private long lastSeenAt;
+
+ public RedisPlayerData(JsonObject object) {
+ this.uuid = UUID.fromString(object.get("uuid").getAsString());
+ this.username = object.get("username").getAsString();
+ this.lastAction = LastAction.valueOf(object.get("lastAction").getAsString());
+ this.lastSeenServer = object.get("lastSeenServer").getAsString();
+ this.lastSeenAt = object.get("lastSeenAt").getAsLong();
+ }
+
+ public RedisPlayerData(UUID uuid, String username) {
+ this.uuid = uuid;
+ this.username = username;
+ }
+
+ public JsonObject getJson() {
+ return new JsonChain()
+ .addProperty("uuid", uuid.toString())
+ .addProperty("username", username)
+ .addProperty("lastAction", lastAction.name())
+ .addProperty("lastSeenServer", lastSeenServer)
+ .addProperty("lastSeenAt", lastSeenAt)
+ .get();
+ }
+
+ public String getTimeAgo() {
+ return TimeUtil.millisToRoundedTime(System.currentTimeMillis() - lastSeenAt) + " ago";
+ }
+
+ public enum LastAction {
+ LEAVING_SERVER,
+ JOINING_SERVER
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/chat/Chat.java b/src/main/java/com/minexd/zoot/chat/Chat.java
new file mode 100644
index 0000000..b205301
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/chat/Chat.java
@@ -0,0 +1,80 @@
+package com.minexd.zoot.chat;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.bootstrap.Bootstrapped;
+import com.minexd.zoot.chat.filter.ChatFilter;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.punishment.PunishmentType;
+import java.util.ArrayList;
+import java.util.List;
+
+import com.minexd.zoot.util.Cooldown;
+import lombok.Getter;
+import lombok.Setter;
+import org.bukkit.entity.Player;
+
+public class Chat extends Bootstrapped {
+
+ public Chat(Zoot zoot) {
+ super(zoot);
+ }
+
+ @Getter @Setter private int delayTime = 3;
+ @Getter private boolean publicChatMuted = false;
+ @Getter private boolean publicChatDelayed = false;
+ @Getter private final List filters = new ArrayList<>();
+ @Getter private List filteredPhrases = new ArrayList<>();
+ @Getter private List linkWhitelist = new ArrayList<>();
+
+ public void togglePublicChatMute() {
+ publicChatMuted = !publicChatMuted;
+ }
+
+ public void togglePublicChatDelay() {
+ publicChatDelayed = !publicChatDelayed;
+ }
+
+ public ChatAttempt attemptChatMessage(Player player, String message) {
+ Profile profile = Profile.getProfiles().get(player.getUniqueId());
+
+ if (profile.getActivePunishmentByType(PunishmentType.MUTE) != null) {
+ return new ChatAttempt(ChatAttempt.Response.PLAYER_MUTED, profile.getActivePunishmentByType(PunishmentType.MUTE));
+ }
+
+ if (publicChatMuted && !player.hasPermission("zoot.staff")) {
+ return new ChatAttempt(ChatAttempt.Response.CHAT_MUTED);
+ }
+
+ if (publicChatDelayed && !profile.getChatCooldown().hasExpired() && !player.hasPermission("zoot.staff")) {
+ ChatAttempt attempt = new ChatAttempt(ChatAttempt.Response.CHAT_DELAYED);
+ attempt.setValue(profile.getChatCooldown().getRemaining());
+ return attempt;
+ }
+
+ String msg = message.toLowerCase()
+ .replace("3", "e")
+ .replace("1", "i")
+ .replace("!", "i")
+ .replace("@", "a")
+ .replace("7", "t")
+ .replace("0", "o")
+ .replace("5", "s")
+ .replace("8", "b")
+ .replaceAll("\\p{Punct}|\\d", "").trim();
+
+ String[] words = msg.trim().split(" ");
+
+ for (ChatFilter chatFilter : filters) {
+ if (chatFilter.isFiltered(msg, words)) {
+ return new ChatAttempt(ChatAttempt.Response.MESSAGE_FILTERED);
+ }
+ }
+
+ if (publicChatDelayed) {
+ profile.setChatCooldown(new Cooldown(delayTime * 1000L));
+ }
+
+ return new ChatAttempt(ChatAttempt.Response.ALLOWED);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/chat/ChatAttempt.java b/src/main/java/com/minexd/zoot/chat/ChatAttempt.java
new file mode 100644
index 0000000..51236db
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/chat/ChatAttempt.java
@@ -0,0 +1,39 @@
+package com.minexd.zoot.chat;
+
+import com.minexd.zoot.chat.filter.ChatFilter;
+import com.minexd.zoot.profile.punishment.Punishment;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class ChatAttempt {
+
+ private Response response;
+ private ChatFilter filterFlagged;
+ private Punishment punishment;
+ private Object value;
+
+ public ChatAttempt(Response response) {
+ this.response = response;
+ }
+
+ public ChatAttempt(Response response, ChatFilter filterFlagged) {
+ this.response = response;
+ this.filterFlagged = filterFlagged;
+ }
+
+ public ChatAttempt(Response response, Punishment punishment) {
+ this.response = response;
+ this.punishment = punishment;
+ }
+
+ public enum Response {
+ ALLOWED,
+ MESSAGE_FILTERED,
+ PLAYER_MUTED,
+ CHAT_MUTED,
+ CHAT_DELAYED
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/chat/ChatListener.java b/src/main/java/com/minexd/zoot/chat/ChatListener.java
new file mode 100644
index 0000000..ea2a99a
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/chat/ChatListener.java
@@ -0,0 +1,63 @@
+package com.minexd.zoot.chat;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.bootstrap.BootstrappedListener;
+import com.minexd.zoot.chat.event.ChatAttemptEvent;
+import com.minexd.zoot.util.CC;
+import com.minexd.zoot.util.TimeUtil;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.player.AsyncPlayerChatEvent;
+
+public class ChatListener extends BootstrappedListener {
+
+ public ChatListener(Zoot zoot) {
+ super(zoot);
+ }
+
+ @EventHandler(priority = EventPriority.NORMAL, ignoreCancelled = true)
+ public void onAsyncPlayerChatEvent(AsyncPlayerChatEvent event) {
+ ChatAttempt chatAttempt = zoot.getChat().attemptChatMessage(event.getPlayer(), event.getMessage());
+ ChatAttemptEvent chatAttemptEvent = new ChatAttemptEvent(event.getPlayer(), chatAttempt, event.getMessage());
+
+ zoot.getServer().getPluginManager().callEvent(chatAttemptEvent);
+
+ if (!chatAttemptEvent.isCancelled()) {
+ switch (chatAttempt.getResponse()) {
+ case ALLOWED: {
+ event.setFormat("%1$s" + CC.RESET + ": %2$s");
+ }
+ break;
+ case MESSAGE_FILTERED: {
+ event.setCancelled(true);
+ chatAttempt.getFilterFlagged().punish(event.getPlayer());
+ }
+ break;
+ case PLAYER_MUTED: {
+ event.setCancelled(true);
+
+ if (chatAttempt.getPunishment().isPermanent()) {
+ event.getPlayer().sendMessage(CC.RED + "You are muted for forever.");
+ } else {
+ event.getPlayer().sendMessage(CC.RED + "You are muted for another " +
+ chatAttempt.getPunishment().getTimeRemaining() + ".");
+ }
+ }
+ break;
+ case CHAT_MUTED: {
+ event.setCancelled(true);
+ event.getPlayer().sendMessage(CC.RED + "The public chat is currently muted.");
+ }
+ break;
+ case CHAT_DELAYED: {
+ event.setCancelled(true);
+ event.getPlayer().sendMessage(Locale.CHAT_DELAYED.format(
+ TimeUtil.millisToSeconds((int) chatAttempt.getValue())) + " seconds");
+ }
+ break;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/chat/command/ClearChatCommand.java b/src/main/java/com/minexd/zoot/chat/command/ClearChatCommand.java
new file mode 100644
index 0000000..572adb7
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/chat/command/ClearChatCommand.java
@@ -0,0 +1,40 @@
+package com.minexd.zoot.chat.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.profile.Profile;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = { "clearchat", "cc" }, permission = "zoot.staff.clearchat")
+public class ClearChatCommand {
+
+ public void execute(CommandSender sender) {
+ String[] strings = new String[101];
+
+ for (Player player : Bukkit.getOnlinePlayers()) {
+ if (player.hasPermission("zoot.staff")) {
+ if (Zoot.get().getMainConfig().getBoolean("CHAT.CLEAR_CHAT_FOR_STAFF")) {
+ player.sendMessage(strings);
+ }
+ } else {
+ player.sendMessage(strings);
+ }
+ }
+
+ String senderName;
+
+ if (sender instanceof Player) {
+ Profile profile = Profile.getProfiles().get(((Player) sender).getUniqueId());
+ senderName = profile.getActiveRank().getColor() + sender.getName();
+ } else {
+ senderName = ChatColor.DARK_RED + "Console";
+ }
+
+ Bukkit.broadcastMessage(Locale.CLEAR_CHAT_BROADCAST.format(senderName));
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/chat/command/MuteChatCommand.java b/src/main/java/com/minexd/zoot/chat/command/MuteChatCommand.java
new file mode 100644
index 0000000..38deeaf
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/chat/command/MuteChatCommand.java
@@ -0,0 +1,32 @@
+package com.minexd.zoot.chat.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.profile.Profile;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "mutechat", permission = "zoot.staff.mutechat")
+public class MuteChatCommand {
+
+ public void execute(CommandSender sender) {
+ Zoot.get().getChat().togglePublicChatMute();
+
+ String senderName;
+
+ if (sender instanceof Player) {
+ Profile profile = Profile.getProfiles().get(((Player) sender).getUniqueId());
+ senderName = profile.getActiveRank().getColor() + sender.getName();
+ } else {
+ senderName = ChatColor.DARK_RED + "Console";
+ }
+
+ String context = Zoot.get().getChat().isPublicChatMuted() ? "muted" : "unmuted";
+
+ Bukkit.broadcastMessage(Locale.MUTE_CHAT_BROADCAST.format(context, senderName));
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/chat/command/SlowChatCommand.java b/src/main/java/com/minexd/zoot/chat/command/SlowChatCommand.java
new file mode 100644
index 0000000..980caa6
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/chat/command/SlowChatCommand.java
@@ -0,0 +1,48 @@
+package com.minexd.zoot.chat.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.ZootAPI;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "slowchat", permission = "zoot.staff.slowchat")
+public class SlowChatCommand {
+
+ public void execute(CommandSender sender) {
+ Zoot.get().getChat().togglePublicChatDelay();
+
+ String senderName;
+
+ if (sender instanceof Player) {
+ senderName = ZootAPI.getColoredName((Player) sender);
+ } else {
+ senderName = ChatColor.DARK_RED + "Console";
+ }
+
+ String context = Zoot.get().getChat().getDelayTime() == 1 ? "" : "s";
+
+ if (Zoot.get().getChat().isPublicChatDelayed()) {
+ Bukkit.broadcastMessage(Locale.DELAY_CHAT_ENABLED_BROADCAST.format(senderName,
+ Zoot.get().getChat().getDelayTime(), context));
+ } else {
+ Bukkit.broadcastMessage(Locale.DELAY_CHAT_DISABLED_BROADCAST.format(senderName));
+ }
+ }
+
+ public void execute(CommandSender sender, Integer seconds) {
+ if (seconds < 0 || seconds > 60) {
+ sender.sendMessage(ChatColor.RED + "A delay can only be between 1-60 seconds.");
+ return;
+ }
+
+ String context = seconds == 1 ? "" : "s";
+
+ sender.sendMessage(ChatColor.GREEN + "You have updated the chat delay to " + seconds + " second" + context + ".");
+ Zoot.get().getChat().setDelayTime(seconds);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/chat/event/ChatAttemptEvent.java b/src/main/java/com/minexd/zoot/chat/event/ChatAttemptEvent.java
new file mode 100644
index 0000000..2b54c34
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/chat/event/ChatAttemptEvent.java
@@ -0,0 +1,25 @@
+package com.minexd.zoot.chat.event;
+
+import com.minexd.zoot.chat.ChatAttempt;
+import com.minexd.zoot.util.BaseEvent;
+import lombok.Getter;
+import lombok.Setter;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+
+@Getter
+public class ChatAttemptEvent extends BaseEvent implements Cancellable {
+
+ private final Player player;
+ private final ChatAttempt chatAttempt;
+ @Setter private String chatMessage;
+ @Setter private boolean cancelled;
+ @Setter private String cancelReason = "";
+
+ public ChatAttemptEvent(Player player, ChatAttempt chatAttempt, String chatMessage) {
+ this.player = player;
+ this.chatAttempt = chatAttempt;
+ this.chatMessage = chatMessage;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/chat/filter/ChatFilter.java b/src/main/java/com/minexd/zoot/chat/filter/ChatFilter.java
new file mode 100644
index 0000000..1ec5139
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/chat/filter/ChatFilter.java
@@ -0,0 +1,27 @@
+package com.minexd.zoot.chat.filter;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.bootstrap.Bootstrapped;
+import org.bukkit.entity.Player;
+
+public abstract class ChatFilter extends Bootstrapped {
+
+ private String command;
+
+ public ChatFilter(Zoot zoot, String command) {
+ super(zoot);
+
+ this.command = command;
+ }
+
+ public abstract boolean isFiltered(String message, String[] words);
+
+ public void punish(Player player) {
+ if (command != null) {
+ zoot.getServer().dispatchCommand(zoot.getServer().getConsoleSender(), command
+ .replace("{player}", player.getName())
+ .replace("{player-uuid}", player.getUniqueId().toString()));
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/chat/filter/impl/ContainsFilter.java b/src/main/java/com/minexd/zoot/chat/filter/impl/ContainsFilter.java
new file mode 100644
index 0000000..550a8f5
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/chat/filter/impl/ContainsFilter.java
@@ -0,0 +1,30 @@
+package com.minexd.zoot.chat.filter.impl;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.chat.filter.ChatFilter;
+
+public class ContainsFilter extends ChatFilter {
+
+ private final String phrase;
+
+ public ContainsFilter(Zoot zoot, String phrase) {
+ this(zoot, phrase, null);
+ }
+
+ public ContainsFilter(Zoot zoot, String phrase, String command) {
+ super(zoot, command);
+ this.phrase = phrase;
+ }
+
+ @Override
+ public boolean isFiltered(String message, String[] words) {
+ for (String word : words) {
+ if (word.contains(this.phrase)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/chat/filter/impl/LinkFilter.java b/src/main/java/com/minexd/zoot/chat/filter/impl/LinkFilter.java
new file mode 100644
index 0000000..9e7fe58
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/chat/filter/impl/LinkFilter.java
@@ -0,0 +1,49 @@
+package com.minexd.zoot.chat.filter.impl;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.chat.filter.ChatFilter;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public class LinkFilter extends ChatFilter {
+
+ private static final Pattern URL_REGEX = Pattern.compile(
+ "^(http://www\\.|https://www\\.|http://|https://)?[a-z0-9]+([\\-.][a-z0-9]+)*\\.[a-z]{2,5}(:[0-9]{1,5})?(/.*)?$");
+ private static final Pattern IP_REGEX = Pattern.compile(
+ "^(([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])([.,])){3}([0-9]|[1-9][0-9]|1[0-9]{2}|2[0-4][0-9]|25[0-5])$");
+
+ public LinkFilter(Zoot zoot) {
+ super(zoot, null);
+ }
+
+ @Override
+ public boolean isFiltered(String message, String[] words) {
+ for (String word : message.replace("(dot)", ".").replace("[dot]", ".").trim().split(" ")) {
+ boolean continueIt = false;
+
+ for (String phrase : this.zoot.getChat().getLinkWhitelist()) {
+ if (word.toLowerCase().contains(phrase)) {
+ continueIt = true;
+ break;
+ }
+ }
+
+ if (!continueIt) {
+ Matcher matcher = IP_REGEX.matcher(word);
+
+ if (matcher.matches()) {
+ return true;
+ }
+
+ matcher = URL_REGEX.matcher(word);
+
+ if (matcher.matches()) {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/chat/util/ChatComponentBuilder.java b/src/main/java/com/minexd/zoot/chat/util/ChatComponentBuilder.java
new file mode 100644
index 0000000..9fbdcda
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/chat/util/ChatComponentBuilder.java
@@ -0,0 +1,219 @@
+package com.minexd.zoot.chat.util;
+
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Pattern;
+import net.md_5.bungee.api.ChatColor;
+import net.md_5.bungee.api.chat.BaseComponent;
+import net.md_5.bungee.api.chat.ClickEvent;
+import net.md_5.bungee.api.chat.ComponentBuilder;
+import net.md_5.bungee.api.chat.HoverEvent;
+import net.md_5.bungee.api.chat.TextComponent;
+
+public class ChatComponentBuilder extends ComponentBuilder {
+
+ private static Field partsField;
+ private static Field currField;
+
+ static {
+ try {
+ currField = ComponentBuilder.class.getDeclaredField("current");
+ partsField = ComponentBuilder.class.getDeclaredField("parts");
+
+ currField.setAccessible(true);
+ partsField.setAccessible(true);
+ } catch (NoSuchFieldException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public ChatComponentBuilder(String text) {
+ super("");
+ this.parse(text);
+ }
+
+ public TextComponent getCurrent() {
+ try {
+ return (TextComponent) currField.get(this);
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ return null;
+ }
+
+ public void setCurrent(TextComponent tc) {
+ try {
+ currField.set(this, tc);
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public List getParts() {
+ try {
+ return (List) partsField.get(this);
+ } catch (IllegalAccessException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ public ChatComponentBuilder setCurrentHoverEvent(HoverEvent hoverEvent) {
+ this.getCurrent().setHoverEvent(hoverEvent);
+
+ return this;
+ }
+
+ public ChatComponentBuilder setCurrentClickEvent(ClickEvent clickEvent) {
+ this.getCurrent().setClickEvent(clickEvent);
+
+ return this;
+ }
+
+ public ChatComponentBuilder attachToEachPart(HoverEvent hoverEvent) {
+ for (Object part : getParts()) {
+ TextComponent component = (TextComponent) part;
+
+ if (component.getHoverEvent() == null) {
+ component.setHoverEvent(hoverEvent);
+ }
+ }
+
+ this.getCurrent().setHoverEvent(hoverEvent);
+
+ return this;
+ }
+
+ public ChatComponentBuilder attachToEachPart(ClickEvent clickEvent) {
+ for (Object part : getParts()) {
+ TextComponent component = (TextComponent) part;
+
+ if (component.getClickEvent() == null) {
+ component.setClickEvent(clickEvent);
+ }
+ }
+
+ this.getCurrent().setClickEvent(clickEvent);
+
+ return this;
+ }
+
+ public ChatComponentBuilder parse(String text) {
+ String regex = "[&§]{1}([a-fA-Fl-oL-O0-9-r]){1}";
+ text = text.replaceAll(regex, "§$1");
+
+ if (!Pattern.compile(regex).matcher(text).find()) {
+ if (getParts().isEmpty() && getCurrent() != null && getCurrent().getText().isEmpty()) {
+ getCurrent().setText(text);
+ } else {
+ this.append(text);
+ }
+
+ return this;
+ }
+
+ String[] words = text.split(regex);
+ int index = words[0].length();
+
+ for (String word : words) {
+ try {
+ if (index != words[0].length()) {
+ if (getParts().isEmpty() && getCurrent() != null && getCurrent().getText().isEmpty()) {
+ getCurrent().setText(word);
+ } else {
+ this.append(word);
+ }
+
+ ChatColor color = ChatColor.getByChar(text.charAt(index - 1));
+
+ if (color == ChatColor.BOLD) {
+ this.bold(true);
+ } else if (color == ChatColor.STRIKETHROUGH) {
+ this.strikethrough(true);
+ } else if (color == ChatColor.MAGIC) {
+ this.obfuscated(true);
+ } else if (color == ChatColor.UNDERLINE) {
+ this.underlined(true);
+ } else if (color == ChatColor.RESET) {
+ this.bold(false);
+ this.strikethrough(false);
+ this.obfuscated(false);
+ this.underlined(false);
+ } else {
+ this.color(color);
+ }
+ }
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ index += word.length() + 2;
+ }
+
+ return this;
+ }
+
+ public ChatComponentBuilder append(BaseComponent[] components) {
+ for (BaseComponent component : components) {
+ append((TextComponent) component);
+ }
+
+ return this;
+ }
+
+ public ChatComponentBuilder append(TextComponent textComponent) {
+ if (textComponent == null) {
+ return this;
+ }
+
+ String text = textComponent.getText();
+ ChatColor color = textComponent.getColor();
+ boolean bold = textComponent.isBold();
+ boolean underline = textComponent.isUnderlined();
+ boolean italic = textComponent.isUnderlined();
+ boolean strike = textComponent.isStrikethrough();
+ HoverEvent he = textComponent.getHoverEvent();
+ ClickEvent ce = textComponent.getClickEvent();
+
+ append(text);
+ color(color);
+ underlined(underline);
+ italic(italic);
+ strikethrough(strike);
+ event(he);
+ event(ce);
+
+ if (textComponent.getExtra() != null) {
+ for (BaseComponent bc : textComponent.getExtra()) {
+ if (bc instanceof TextComponent) {
+ append((TextComponent) bc);
+ }
+ }
+ }
+
+ return this;
+ }
+
+ @Override
+ public BaseComponent[] create() {
+ List components = new ArrayList<>(getParts());
+ components.add(getCurrent());
+
+ TextComponent first = components.get(0);
+
+ if (first.getText().isEmpty()) {
+ components.remove(0);
+ }
+
+ TextComponent last = components.get(components.size() - 1);
+
+ if (last.getText().isEmpty()) {
+ components.remove(components.size() - 1);
+ }
+
+ return components.toArray(new BaseComponent[components.size()]);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/config/ConfigConversion.java b/src/main/java/com/minexd/zoot/config/ConfigConversion.java
new file mode 100644
index 0000000..4257d0e
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/config/ConfigConversion.java
@@ -0,0 +1,10 @@
+package com.minexd.zoot.config;
+
+import java.io.File;
+import org.bukkit.configuration.file.FileConfiguration;
+
+public interface ConfigConversion {
+
+ void convert(File file, FileConfiguration fileConfiguration);
+
+}
diff --git a/src/main/java/com/minexd/zoot/config/ConfigValidation.java b/src/main/java/com/minexd/zoot/config/ConfigValidation.java
new file mode 100644
index 0000000..28e9599
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/config/ConfigValidation.java
@@ -0,0 +1,36 @@
+package com.minexd.zoot.config;
+
+import java.io.File;
+import org.bukkit.configuration.file.FileConfiguration;
+
+public class ConfigValidation {
+
+ private final File file;
+ private final FileConfiguration fileConfiguration;
+ private final int version;
+
+ public ConfigValidation(File file, FileConfiguration fileConfiguration, int version) {
+ this.file = file;
+ this.fileConfiguration = fileConfiguration;
+ this.version = version;
+ }
+
+ public void check() {
+ if (fileConfiguration.contains("CONFIG_VERSION")) {
+ if (fileConfiguration.getInt("CONFIG_VERSION") != version) {
+ for (ConfigVersion version : ConfigVersion.values()) {
+ if (fileConfiguration.getInt("CONFIG_VERSION") < version.getNumber()) {
+ System.out.println("Converting current configuration into version " + version.name());
+ version.getConversion().convert(file, fileConfiguration);
+ }
+ }
+ }
+ } else {
+ for (ConfigVersion version : ConfigVersion.values()) {
+ System.out.println("Converting current configuration into version " + version.name());
+ version.getConversion().convert(file, fileConfiguration);
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/config/ConfigVersion.java b/src/main/java/com/minexd/zoot/config/ConfigVersion.java
new file mode 100644
index 0000000..e6a7534
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/config/ConfigVersion.java
@@ -0,0 +1,20 @@
+package com.minexd.zoot.config;
+
+import com.minexd.zoot.config.impl.ConfigConversion1;
+import com.minexd.zoot.config.impl.ConfigConversion2;
+import com.minexd.zoot.config.impl.ConfigConversion3;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public enum ConfigVersion {
+
+ VERSION_1(1, new ConfigConversion1()),
+ VERSION_2(2, new ConfigConversion2()),
+ VERSION_3(3, new ConfigConversion3());
+
+ private int number;
+ private ConfigConversion conversion;
+
+}
diff --git a/src/main/java/com/minexd/zoot/config/impl/ConfigConversion1.java b/src/main/java/com/minexd/zoot/config/impl/ConfigConversion1.java
new file mode 100644
index 0000000..6bc8cee
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/config/impl/ConfigConversion1.java
@@ -0,0 +1,25 @@
+package com.minexd.zoot.config.impl;
+
+import com.minexd.zoot.config.ConfigConversion;
+import java.io.File;
+import java.io.IOException;
+import org.bukkit.configuration.file.FileConfiguration;
+
+public class ConfigConversion1 implements ConfigConversion {
+
+ @Override
+ public void convert(File file, FileConfiguration fileConfiguration) {
+ fileConfiguration.set("CONFIG_VERSION", 1);
+ fileConfiguration.set("CHAT.FORMAT", "%1$s&r: %2$s");
+ fileConfiguration.set("CHAT.CLEAR_CHAT_BROADCAST", "&eThe chat has been cleared by &r{0}");
+ fileConfiguration.set("CHAT.CLEAR_CHAT_FOR_STAFF", false);
+ fileConfiguration.set("CHAT.MUTE_CHAT_BROADCAST", "&eThe chat has been {0} by &r{1}");
+
+ try {
+ fileConfiguration.save(file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/config/impl/ConfigConversion2.java b/src/main/java/com/minexd/zoot/config/impl/ConfigConversion2.java
new file mode 100644
index 0000000..951d142
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/config/impl/ConfigConversion2.java
@@ -0,0 +1,22 @@
+package com.minexd.zoot.config.impl;
+
+import com.minexd.zoot.config.ConfigConversion;
+import java.io.File;
+import java.io.IOException;
+import org.bukkit.configuration.file.FileConfiguration;
+
+public class ConfigConversion2 implements ConfigConversion {
+
+ @Override
+ public void convert(File file, FileConfiguration fileConfiguration) {
+ fileConfiguration.set("CONFIG_VERSION", 2);
+ fileConfiguration.set("SETTINGS.UPDATE_PLAYER_LIST_NAME", true);
+
+ try {
+ fileConfiguration.save(file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/config/impl/ConfigConversion3.java b/src/main/java/com/minexd/zoot/config/impl/ConfigConversion3.java
new file mode 100644
index 0000000..df373b0
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/config/impl/ConfigConversion3.java
@@ -0,0 +1,30 @@
+package com.minexd.zoot.config.impl;
+
+import com.minexd.zoot.config.ConfigConversion;
+import org.bukkit.configuration.file.FileConfiguration;
+
+import java.io.File;
+import java.io.IOException;
+
+public class ConfigConversion3 implements ConfigConversion {
+
+ @Override
+ public void convert(File file, FileConfiguration fileConfiguration) {
+ fileConfiguration.set("CONFIG_VERSION", 3);
+ fileConfiguration.set("CONVERSATION.SEND_MESSAGE", "&7(To &r{5}{3}&7) %MSG%");
+ fileConfiguration.set("CONVERSATION.RECEIVE_MESSAGE", "&7(From &r{5}{3}&7) %MSG%");
+ fileConfiguration.set("OPTIONS.GLOBAL_CHAT_ENABLED", "&eYou &aenabled &eglobal chat.");
+ fileConfiguration.set("OPTIONS.GLOBAL_CHAT_DISABLED", "&eYou &cdisabled &eglobal chat.");
+ fileConfiguration.set("OPTIONS.PRIVATE_MESSAGES_ENABLED", "&aYou can now receive new conversations.");
+ fileConfiguration.set("OPTIONS.PRIVATE_MESSAGES_DISABLED", "&cYou can no longer receive new conversations. If you start a conversation with a player, they will be able to message you back.");
+ fileConfiguration.set("OPTIONS.PRIVATE_MESSAGE_SOUNDS_ENABLED", "&eYou &aenabled &eprivate message sounds.");
+ fileConfiguration.set("OPTIONS.PRIVATE_MESSAGE_SOUNDS_DISABLED", "&eYou &cdisabled &eprivate message sounds.");
+
+ try {
+ fileConfiguration.save(file);
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/Essentials.java b/src/main/java/com/minexd/zoot/essentials/Essentials.java
new file mode 100644
index 0000000..f70d04d
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/Essentials.java
@@ -0,0 +1,88 @@
+package com.minexd.zoot.essentials;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.bootstrap.Bootstrapped;
+import com.minexd.zoot.essentials.event.SpawnTeleportEvent;
+import com.minexd.zoot.util.LocationUtil;
+import java.io.IOException;
+import org.bukkit.Location;
+import org.bukkit.World;
+import org.bukkit.entity.Entity;
+import org.bukkit.entity.EntityType;
+import org.bukkit.entity.Player;
+
+public class Essentials extends Bootstrapped {
+
+ private Location spawn;
+
+ public Essentials(Zoot zoot) {
+ super(zoot);
+
+ spawn = LocationUtil.deserialize(zoot.getMainConfig().getStringOrDefault("ESSENTIAL.SPAWN_LOCATION", null));
+ }
+
+ public void setSpawn(Location location) {
+ spawn = location;
+
+ if (spawn == null) {
+ zoot.getMainConfig().getConfiguration().set("ESSENTIAL.SPAWN_LOCATION", null);
+ } else {
+ zoot.getMainConfig().getConfiguration().set("ESSENTIAL.SPAWN_LOCATION", LocationUtil.serialize(this.spawn));
+ }
+
+ try {
+ zoot.getMainConfig().getConfiguration().save(zoot.getMainConfig().getFile());
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void teleportToSpawn(Player player) {
+ Location location = spawn == null ? zoot.getServer().getWorlds().get(0).getSpawnLocation() : spawn;
+
+ SpawnTeleportEvent event = new SpawnTeleportEvent(player, location);
+ event.call();
+
+ if (!event.isCancelled() && event.getLocation() != null) {
+ player.teleport(event.getLocation());
+ }
+ }
+
+ public int clearEntities(World world) {
+ int removed = 0;
+
+ for (Entity entity : world.getEntities()) {
+ if (entity.getType() == EntityType.PLAYER) {
+ continue;
+ }
+
+ removed++;
+ entity.remove();
+ }
+
+ return removed;
+ }
+
+ public int clearEntities(World world, EntityType... excluded) {
+ int removed = 0;
+
+ entityLoop:
+ for (Entity entity : world.getEntities()) {
+ for (EntityType type : excluded) {
+ if (entity.getType() == EntityType.PLAYER) {
+ continue entityLoop;
+ }
+
+ if (entity.getType() == type) {
+ continue entityLoop;
+ }
+ }
+
+ removed++;
+ entity.remove();
+ }
+
+ return removed;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/EssentialsListener.java b/src/main/java/com/minexd/zoot/essentials/EssentialsListener.java
new file mode 100644
index 0000000..10d7eed
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/EssentialsListener.java
@@ -0,0 +1,52 @@
+package com.minexd.zoot.essentials;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.bootstrap.BootstrappedListener;
+import com.minexd.zoot.util.CC;
+import java.util.Arrays;
+import java.util.List;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.player.PlayerCommandPreprocessEvent;
+
+public class EssentialsListener extends BootstrappedListener {
+
+ private static List BLOCKED_COMMANDS = Arrays.asList(
+ "//calc",
+ "//eval",
+ "//solve",
+ "/bukkit:",
+ "/me",
+ "/bukkit:me",
+ "/minecraft:",
+ "/minecraft:me",
+ "/version",
+ "/ver"
+ );
+
+ public EssentialsListener(Zoot zoot) {
+ super(zoot);
+ }
+
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void onCommandProcess(PlayerCommandPreprocessEvent event) {
+ Player player = event.getPlayer();
+ String message = (event.getMessage().startsWith("/") ? "" : "/") + event.getMessage();
+
+ for (String blockedCommand : BLOCKED_COMMANDS) {
+ if (message.startsWith(blockedCommand)) {
+ if (message.equalsIgnoreCase("/version") || message.equalsIgnoreCase("/ver")) {
+ if (event.getPlayer().isOp()) {
+ return;
+ }
+ }
+
+ player.sendMessage(CC.RED + "You cannot perform this command.");
+ event.setCancelled(true);
+ return;
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/BroadcastCommand.java b/src/main/java/com/minexd/zoot/essentials/command/BroadcastCommand.java
new file mode 100644
index 0000000..2b8932e
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/BroadcastCommand.java
@@ -0,0 +1,17 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import com.qrakn.honcho.command.CommandOption;
+import org.bukkit.Bukkit;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = { "broadcast", "bc" }, options = "r", permission = "zoot.admin.broadcast")
+public class BroadcastCommand {
+
+ public void execute(CommandSender sender, CommandOption option, String broadcast) {
+ String message = broadcast.replaceAll("(&([a-f0-9l-or]))", "\u00A7$2");
+ Bukkit.broadcastMessage(CC.translate((option == null ? "&6[Broadcast] &r" : "") + message));
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/ClearCommand.java b/src/main/java/com/minexd/zoot/essentials/command/ClearCommand.java
new file mode 100644
index 0000000..251c00c
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/ClearCommand.java
@@ -0,0 +1,26 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+@CommandMeta(label = { "clearinv", "clear", "ci" }, permission = "zoot.admin.clearinv")
+public class ClearCommand {
+
+ public void execute(Player player) {
+ player.getInventory().setContents(new ItemStack[36]);
+ player.getInventory().setArmorContents(new ItemStack[4]);
+ player.updateInventory();
+ player.sendMessage(CC.GOLD + "You cleared your inventory.");
+ }
+
+ public void execute(CommandSender sender, Player player) {
+ player.getInventory().setContents(new ItemStack[36]);
+ player.getInventory().setArmorContents(new ItemStack[4]);
+ player.updateInventory();
+ player.sendMessage(CC.GOLD + "Your inventory has been cleared by " + sender.getName());
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/DayCommand.java b/src/main/java/com/minexd/zoot/essentials/command/DayCommand.java
new file mode 100644
index 0000000..2afc0d3
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/DayCommand.java
@@ -0,0 +1,15 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "day")
+public class DayCommand {
+
+ public void execute(Player player) {
+ player.setPlayerTime(6000L, false);
+ player.sendMessage(CC.GREEN + "It's now day time.");
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/GameModeCommand.java b/src/main/java/com/minexd/zoot/essentials/command/GameModeCommand.java
new file mode 100644
index 0000000..6bf99e5
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/GameModeCommand.java
@@ -0,0 +1,40 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.GameMode;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = { "gamemode", "gm" }, permission = "zoot.admin.gamemode")
+public class GameModeCommand {
+
+ public void execute(Player player, GameMode gameMode) {
+ if (gameMode == null) {
+ player.sendMessage(CC.RED + "That game mode is not valid.");
+ return;
+ }
+
+ player.setGameMode(gameMode);
+ player.updateInventory();
+ player.sendMessage(CC.GOLD + "You updated your game mode.");
+ }
+
+ public void execute(CommandSender sender, Player target, GameMode gameMode) {
+ if (target == null) {
+ sender.sendMessage(Locale.PLAYER_NOT_FOUND.format());
+ return;
+ }
+
+ if (gameMode == null) {
+ sender.sendMessage(CC.RED + "That game mode is not valid.");
+ return;
+ }
+
+ target.setGameMode(gameMode);
+ target.updateInventory();
+ target.sendMessage(CC.GOLD + "Your game mode has been updated by " + sender.getName());
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/HealCommand.java b/src/main/java/com/minexd/zoot/essentials/command/HealCommand.java
new file mode 100644
index 0000000..b3ac120
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/HealCommand.java
@@ -0,0 +1,33 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "heal", permission = "zoot.admin.heal")
+public class HealCommand {
+
+ public void execute(Player player) {
+ player.setHealth(20.0);
+ player.setFoodLevel(20);
+ player.setSaturation(5.0F);
+ player.updateInventory();
+ player.sendMessage(CC.GOLD + "You healed yourself.");
+ }
+
+ public void execute(CommandSender sender, Player player) {
+ if (player == null) {
+ sender.sendMessage(Locale.PLAYER_NOT_FOUND.format());
+ return;
+ }
+
+ player.setHealth(20.0);
+ player.setFoodLevel(20);
+ player.setSaturation(5.0F);
+ player.updateInventory();
+ player.sendMessage(CC.GOLD + "You have been healed by " + sender.getName());
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/HidePlayerCommand.java b/src/main/java/com/minexd/zoot/essentials/command/HidePlayerCommand.java
new file mode 100644
index 0000000..de00d91
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/HidePlayerCommand.java
@@ -0,0 +1,13 @@
+package com.minexd.zoot.essentials.command;
+
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "hideplayer", permission = "zoot.admin.hideplayer")
+public class HidePlayerCommand {
+
+ public void execute(Player player, Player target) {
+ player.hidePlayer(target);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/ListCommand.java b/src/main/java/com/minexd/zoot/essentials/command/ListCommand.java
new file mode 100644
index 0000000..8fa2077
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/ListCommand.java
@@ -0,0 +1,53 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.ZootAPI;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.rank.Rank;
+import com.qrakn.honcho.command.CommandMeta;
+import org.apache.commons.lang.StringUtils;
+import org.bukkit.Bukkit;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+
+import java.util.*;
+
+@CommandMeta(label = "list")
+public class ListCommand {
+
+ public void executue(Player sender) {
+ List sortedPlayers = new ArrayList<>(Bukkit.getOnlinePlayers());
+ sortedPlayers.sort(new Comparator() {
+ @Override
+ public int compare(Player o1, Player o2) {
+ Profile p1 = Profile.getByUuid(o1.getUniqueId());
+ Profile p2 = Profile.getByUuid(o2.getUniqueId());
+ return p2.getActiveRank().getWeight() - p1.getActiveRank().getWeight();
+ }
+ });
+
+ List playerNames = new ArrayList<>();
+
+ for (Player player : sortedPlayers) {
+ playerNames.add(ZootAPI.getColoredName(player));
+ }
+
+ List sortedRanks = new ArrayList<>(Rank.getRanks().values());
+ sortedRanks.sort(new Comparator() {
+ @Override
+ public int compare(Rank o1, Rank o2) {
+ return o2.getWeight() - o1.getWeight();
+ }
+ });
+
+ List rankNames = new ArrayList<>();
+
+ for (Rank rank : sortedRanks) {
+ rankNames.add(rank.getColor() + rank.getDisplayName());
+ }
+
+ sender.sendMessage(StringUtils.join(rankNames, ChatColor.WHITE + ", "));
+ sender.sendMessage("(" + Bukkit.getOnlinePlayers().size() + "/" + Bukkit.getMaxPlayers() + "): " +
+ StringUtils.join(playerNames, ChatColor.WHITE + ", "));
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/LocationCommand.java b/src/main/java/com/minexd/zoot/essentials/command/LocationCommand.java
new file mode 100644
index 0000000..3530624
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/LocationCommand.java
@@ -0,0 +1,15 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.util.LocationUtil;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "loc", permission = "zoot.admin.loc")
+public class LocationCommand {
+
+ public void execute(Player player) {
+ player.sendMessage(LocationUtil.serialize(player.getLocation()));
+ System.out.println(LocationUtil.serialize(player.getLocation()));
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/MoreCommand.java b/src/main/java/com/minexd/zoot/essentials/command/MoreCommand.java
new file mode 100644
index 0000000..b9507d6
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/MoreCommand.java
@@ -0,0 +1,21 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "more", permission = "zoot.admin.more")
+public class MoreCommand {
+
+ public void execute(Player player) {
+ if (player.getItemInHand() == null) {
+ player.sendMessage(CC.RED + "There is nothing in your hand.");
+ return;
+ }
+
+ player.getItemInHand().setAmount(64);
+ player.updateInventory();
+ player.sendMessage(CC.GREEN + "You gave yourself more of the item in your hand.");
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/NightCommand.java b/src/main/java/com/minexd/zoot/essentials/command/NightCommand.java
new file mode 100644
index 0000000..34cd428
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/NightCommand.java
@@ -0,0 +1,15 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "night")
+public class NightCommand {
+
+ public void execute(Player player) {
+ player.setPlayerTime(18000L, false);
+ player.sendMessage(CC.GREEN + "It's now night time.");
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/PingCommand.java b/src/main/java/com/minexd/zoot/essentials/command/PingCommand.java
new file mode 100644
index 0000000..3029c3d
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/PingCommand.java
@@ -0,0 +1,37 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.ZootAPI;
+import com.minexd.zoot.util.BukkitReflection;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "ping")
+public class PingCommand {
+
+ public void execute(Player player) {
+ player.sendMessage(CC.YELLOW + "Your Ping: " + colorPing(BukkitReflection.getPing(player)));
+ }
+
+ public void execute(Player player, Player target) {
+ if (target == null) {
+ player.sendMessage(CC.RED + "A player with that name could not be found.");
+ } else {
+ player.sendMessage(ZootAPI.getColoredName(target) + CC.YELLOW + "'s Ping: " +
+ colorPing(BukkitReflection.getPing(target)));
+ }
+ }
+
+ private String colorPing(int ping) {
+ if (ping <= 40) {
+ return CC.GREEN + ping;
+ } else if (ping <= 70) {
+ return CC.YELLOW + ping;
+ } else if (ping <= 100) {
+ return CC.GOLD + ping;
+ } else {
+ return CC.RED + ping;
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/RenameCommand.java b/src/main/java/com/minexd/zoot/essentials/command/RenameCommand.java
new file mode 100644
index 0000000..f8b0c5f
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/RenameCommand.java
@@ -0,0 +1,26 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+@CommandMeta(label = "rename", permission = "zoot.staff.rename")
+public class RenameCommand {
+
+ public void execute(Player player, String name) {
+ if (player.getItemInHand() != null) {
+ ItemStack itemStack = player.getItemInHand();
+ ItemMeta itemMeta = itemStack.getItemMeta();
+ itemMeta.setDisplayName(CC.translate(name));
+ itemStack.setItemMeta(itemMeta);
+
+ player.updateInventory();
+ player.sendMessage(CC.GREEN + "You renamed the item in your hand.");
+ } else {
+ player.sendMessage(CC.RED + "There is nothing in your hand.");
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/SetSlotsCommand.java b/src/main/java/com/minexd/zoot/essentials/command/SetSlotsCommand.java
new file mode 100644
index 0000000..93684a0
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/SetSlotsCommand.java
@@ -0,0 +1,17 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.util.BukkitReflection;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = "setslots", async = true, permission = "zoot.admin.setslots")
+public class SetSlotsCommand {
+
+ public void execute(CommandSender sender, int slots) {
+ BukkitReflection.setMaxPlayers(Zoot.get().getServer(), slots);
+ sender.sendMessage(CC.GOLD + "You set the max slots to " + slots + ".");
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/SetSpawnCommand.java b/src/main/java/com/minexd/zoot/essentials/command/SetSpawnCommand.java
new file mode 100644
index 0000000..7f58840
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/SetSpawnCommand.java
@@ -0,0 +1,16 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "setspawn", permission = "zoot.admin.setspawn")
+public class SetSpawnCommand {
+
+ public void execute(Player player) {
+ Zoot.get().getEssentials().setSpawn(player.getLocation());
+ player.sendMessage(CC.GREEN + "You updated this world's spawn.");
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/ShowPlayerCommand.java b/src/main/java/com/minexd/zoot/essentials/command/ShowPlayerCommand.java
new file mode 100644
index 0000000..88f7861
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/ShowPlayerCommand.java
@@ -0,0 +1,13 @@
+package com.minexd.zoot.essentials.command;
+
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "showplayer", permission = "zoot.admin.showplayer")
+public class ShowPlayerCommand {
+
+ public void execute(Player player, Player target) {
+ player.showPlayer(target);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/SpawnCommand.java b/src/main/java/com/minexd/zoot/essentials/command/SpawnCommand.java
new file mode 100644
index 0000000..a3f3ec7
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/SpawnCommand.java
@@ -0,0 +1,16 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "spawn", permission = "zoot.staff.spawn")
+public class SpawnCommand {
+
+ public void execute(Player player) {
+ Zoot.get().getEssentials().teleportToSpawn(player);
+ player.sendMessage(CC.GREEN + "You teleported to this world's spawn.");
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/SunsetCommand.java b/src/main/java/com/minexd/zoot/essentials/command/SunsetCommand.java
new file mode 100644
index 0000000..7f98868
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/SunsetCommand.java
@@ -0,0 +1,15 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "sunset")
+public class SunsetCommand {
+
+ public void execute(Player player) {
+ player.setPlayerTime(12000, false);
+ player.sendMessage(CC.GREEN + "It's now sunset.");
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/TeleportWorldCommand.java b/src/main/java/com/minexd/zoot/essentials/command/TeleportWorldCommand.java
new file mode 100644
index 0000000..66e9f85
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/TeleportWorldCommand.java
@@ -0,0 +1,29 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.Bukkit;
+import org.bukkit.World;
+import org.bukkit.WorldCreator;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "tpworld", permission = "zoot.admin.tpworld")
+public class TeleportWorldCommand {
+
+ public void execute(Player player, String worldName) {
+ World world = Bukkit.getWorld(worldName);
+
+ if (world == null) {
+ world = Bukkit.createWorld(new WorldCreator(worldName));
+ player.sendMessage(CC.GOLD + "Generating new world \"" + worldName + "\"");
+ }
+
+ if (world == null) {
+ player.sendMessage(CC.RED + "A world with that name does not exist.");
+ } else {
+ player.teleport(world.getSpawnLocation());
+ player.sendMessage(CC.GOLD + "Teleported you to " + world.getName());
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/command/ZootDebugCommand.java b/src/main/java/com/minexd/zoot/essentials/command/ZootDebugCommand.java
new file mode 100644
index 0000000..4591bce
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/command/ZootDebugCommand.java
@@ -0,0 +1,15 @@
+package com.minexd.zoot.essentials.command;
+
+import com.minexd.zoot.Zoot;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = "zoot debug", permission = "zoot.admin")
+public class ZootDebugCommand {
+
+ public void execute(CommandSender sender) {
+ Zoot.get().setDebug(!Zoot.get().isDebug());
+ sender.sendMessage("Debug: " + Zoot.get().isDebug());
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/essentials/event/SpawnTeleportEvent.java b/src/main/java/com/minexd/zoot/essentials/event/SpawnTeleportEvent.java
new file mode 100644
index 0000000..12c9a5f
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/essentials/event/SpawnTeleportEvent.java
@@ -0,0 +1,21 @@
+package com.minexd.zoot.essentials.event;
+
+import com.minexd.zoot.util.BaseEvent;
+import lombok.Getter;
+import lombok.Setter;
+import org.bukkit.Location;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+
+public class SpawnTeleportEvent extends BaseEvent implements Cancellable {
+
+ @Getter private final Player player;
+ @Getter @Setter private Location location;
+ @Getter @Setter private boolean cancelled;
+
+ public SpawnTeleportEvent(Player player, Location location) {
+ this.player = player;
+ this.location = location;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/network/NetworkPacketListener.java b/src/main/java/com/minexd/zoot/network/NetworkPacketListener.java
new file mode 100644
index 0000000..eae4bc5
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/network/NetworkPacketListener.java
@@ -0,0 +1,150 @@
+package com.minexd.zoot.network;
+
+import com.minexd.zoot.pidgin.packet.handler.IncomingPacketHandler;
+import com.minexd.zoot.pidgin.packet.listener.PacketListener;
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.network.event.ReceiveStaffChatEvent;
+import com.minexd.zoot.network.packet.PacketAddGrant;
+import com.minexd.zoot.network.packet.PacketBroadcastPunishment;
+import com.minexd.zoot.network.packet.PacketDeleteGrant;
+import com.minexd.zoot.network.packet.PacketDeleteRank;
+import com.minexd.zoot.network.packet.PacketRefreshRank;
+import com.minexd.zoot.network.packet.PacketStaffChat;
+import com.minexd.zoot.network.packet.PacketStaffJoinNetwork;
+import com.minexd.zoot.network.packet.PacketStaffLeaveNetwork;
+import com.minexd.zoot.network.packet.PacketStaffSwitchServer;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.grant.Grant;
+import com.minexd.zoot.profile.grant.event.GrantAppliedEvent;
+import com.minexd.zoot.profile.grant.event.GrantExpireEvent;
+import com.minexd.zoot.profile.punishment.Punishment;
+import com.minexd.zoot.rank.Rank;
+import java.util.Objects;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+
+public class NetworkPacketListener implements PacketListener {
+
+ private Zoot zoot;
+
+ public NetworkPacketListener(Zoot zoot) {
+ this.zoot = zoot;
+ }
+
+ @IncomingPacketHandler
+ public void onAddGrant(PacketAddGrant packet) {
+ Player player = Bukkit.getPlayer(packet.getPlayerUuid());
+ Grant grant = packet.getGrant();
+
+ if (player != null) {
+ Profile profile = Profile.getProfiles().get(player.getUniqueId());
+ profile.getGrants().removeIf(other -> Objects.equals(other, grant));
+ profile.getGrants().add(grant);
+
+ new GrantAppliedEvent(player, grant);
+ }
+ }
+
+ @IncomingPacketHandler
+ public void onDeleteGrant(PacketDeleteGrant packet) {
+ Player player = Bukkit.getPlayer(packet.getPlayerUuid());
+ Grant grant = packet.getGrant();
+
+ if (player != null) {
+ Profile profile = Profile.getProfiles().get(player.getUniqueId());
+ profile.getGrants().removeIf(other -> Objects.equals(other, grant));
+ profile.getGrants().add(grant);
+
+ new GrantExpireEvent(player, grant);
+ }
+ }
+
+ @IncomingPacketHandler
+ public void onBroadcastPunishment(PacketBroadcastPunishment packet) {
+ Punishment punishment = packet.getPunishment();
+ punishment.broadcast(packet.getStaff(), packet.getTarget(), packet.isSilent());
+
+ Player player = Bukkit.getPlayer(packet.getTargetUuid());
+
+ if (player != null) {
+ Profile profile = Profile.getProfiles().get(player.getUniqueId());
+ profile.getPunishments().removeIf(other -> Objects.equals(other, punishment));
+ profile.getPunishments().add(punishment);
+
+ if (punishment.getType().isBan()) {
+ new BukkitRunnable() {
+ @Override
+ public void run() {
+ player.kickPlayer(punishment.getKickMessage());
+ }
+ }.runTask(Zoot.get());
+ }
+ }
+ }
+
+ @IncomingPacketHandler
+ public void onRankRefresh(PacketRefreshRank packet) {
+ Rank rank = Rank.getRankByUuid(packet.getUuid());
+
+ if (rank == null) {
+ rank = new Rank(packet.getUuid(), packet.getName());
+ }
+
+ rank.load();
+
+ Zoot.broadcastOps(Locale.NETWORK_RANK_REFRESHED.format(Locale.NETWORK_BROADCAST_PREFIX.format(),
+ rank.getDisplayName()));
+ }
+
+ @IncomingPacketHandler
+ public void onRankDelete(PacketDeleteRank packet) {
+ Rank rank = Rank.getRanks().remove(packet.getUuid());
+
+ if (rank != null) {
+ Zoot.broadcastOps(Locale.NETWORK_RANK_DELETED.format(Locale.NETWORK_BROADCAST_PREFIX.format(),
+ rank.getDisplayName()));
+ }
+ }
+
+ @IncomingPacketHandler
+ public void onStaffChat(PacketStaffChat packet) {
+ zoot.getServer().getOnlinePlayers().stream()
+ .filter(onlinePlayer -> onlinePlayer.hasPermission("zoot.staff"))
+ .forEach(onlinePlayer -> {
+ ReceiveStaffChatEvent event = new ReceiveStaffChatEvent(onlinePlayer);
+
+ zoot.getServer().getPluginManager().callEvent(event);
+
+ if (!event.isCancelled()) {
+ Profile profile = Profile.getProfiles().get(event.getPlayer().getUniqueId());
+
+ if (profile != null && profile.getStaffOptions().staffModeEnabled()) {
+ onlinePlayer.sendMessage(Locale.STAFF_CHAT.format(Locale.STAFF_BROADCAST_PREFIX.format(),
+ packet.getPlayerName(), packet.getServerName(), packet.getChatMessage()
+ ));
+ }
+ }
+ });
+ }
+
+ @IncomingPacketHandler
+ public void onStaffJoinNetwork(PacketStaffJoinNetwork packet) {
+ zoot.getServer().broadcast(Locale.STAFF_JOIN_NETWORK.format(Locale.STAFF_BROADCAST_PREFIX.format(),
+ packet.getPlayerName(), packet.getServerName()), "zoot.staff");
+ }
+
+ @IncomingPacketHandler
+ public void onStaffLeaveNetwork(PacketStaffLeaveNetwork packet) {
+ zoot.getServer().broadcast(Locale.STAFF_LEAVE_NETWORK.format(Locale.STAFF_BROADCAST_PREFIX.format(),
+ packet.getPlayerName()), "zoot.staff");
+ }
+
+ @IncomingPacketHandler
+ public void onStaffSwitchServer(PacketStaffSwitchServer packet) {
+ zoot.getServer().broadcast(Locale.STAFF_SWITCH_SERVER.format(Locale.STAFF_BROADCAST_PREFIX.format(),
+ packet.getPlayerName(), packet.getToServerName(), packet.getFromServerName()), "zoot.staff");
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/network/event/ReceiveStaffChatEvent.java b/src/main/java/com/minexd/zoot/network/event/ReceiveStaffChatEvent.java
new file mode 100644
index 0000000..8c72f8f
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/network/event/ReceiveStaffChatEvent.java
@@ -0,0 +1,18 @@
+package com.minexd.zoot.network.event;
+
+import com.minexd.zoot.util.BaseEvent;
+import lombok.Getter;
+import lombok.Setter;
+import org.bukkit.entity.Player;
+import org.bukkit.event.Cancellable;
+
+public class ReceiveStaffChatEvent extends BaseEvent implements Cancellable {
+
+ @Getter private Player player;
+ @Getter @Setter private boolean cancelled;
+
+ public ReceiveStaffChatEvent(Player player) {
+ this.player = player;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/network/packet/PacketAddGrant.java b/src/main/java/com/minexd/zoot/network/packet/PacketAddGrant.java
new file mode 100644
index 0000000..f146a69
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/network/packet/PacketAddGrant.java
@@ -0,0 +1,43 @@
+package com.minexd.zoot.network.packet;
+
+import com.google.gson.JsonObject;
+import com.minexd.zoot.pidgin.packet.Packet;
+import com.minexd.zoot.profile.grant.Grant;
+import com.minexd.zoot.util.json.JsonChain;
+import java.util.UUID;
+import lombok.Getter;
+
+public class PacketAddGrant implements Packet {
+
+ @Getter private UUID playerUuid;
+ @Getter private Grant grant;
+
+ public PacketAddGrant() {
+
+ }
+
+ public PacketAddGrant(UUID playerUuid, Grant grant) {
+ this.playerUuid = playerUuid;
+ this.grant = grant;
+ }
+
+ @Override
+ public int id() {
+ return 1;
+ }
+
+ @Override
+ public JsonObject serialize() {
+ return new JsonChain()
+ .addProperty("playerUuid", playerUuid.toString())
+ .add("grant", Grant.SERIALIZER.serialize(grant))
+ .get();
+ }
+
+ @Override
+ public void deserialize(JsonObject jsonObject) {
+ playerUuid = UUID.fromString(jsonObject.get("playerUuid").getAsString());
+ grant = Grant.DESERIALIZER.deserialize(jsonObject.get("grant").getAsJsonObject());
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/network/packet/PacketBroadcastPunishment.java b/src/main/java/com/minexd/zoot/network/packet/PacketBroadcastPunishment.java
new file mode 100644
index 0000000..3af5ffa
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/network/packet/PacketBroadcastPunishment.java
@@ -0,0 +1,55 @@
+package com.minexd.zoot.network.packet;
+
+import com.google.gson.JsonObject;
+import com.minexd.zoot.pidgin.packet.Packet;
+import com.minexd.zoot.profile.punishment.Punishment;
+import com.minexd.zoot.util.json.JsonChain;
+import java.util.UUID;
+import lombok.Getter;
+
+public class PacketBroadcastPunishment implements Packet {
+
+ @Getter private Punishment punishment;
+ @Getter private String staff;
+ @Getter private String target;
+ @Getter private UUID targetUuid;
+ @Getter private boolean silent;
+
+ public PacketBroadcastPunishment() {
+
+ }
+
+ public PacketBroadcastPunishment(Punishment punishment, String staff, String target, UUID targetUuid, boolean silent) {
+ this.punishment = punishment;
+ this.staff = staff;
+ this.target = target;
+ this.targetUuid = targetUuid;
+ this.silent = silent;
+ }
+
+ @Override
+ public int id() {
+ return 2;
+ }
+
+ @Override
+ public JsonObject serialize() {
+ return new JsonChain()
+ .add("punishment", Punishment.SERIALIZER.serialize(punishment))
+ .addProperty("staff", staff)
+ .addProperty("target", target)
+ .addProperty("targetUuid", targetUuid.toString())
+ .addProperty("silent", silent)
+ .get();
+ }
+
+ @Override
+ public void deserialize(JsonObject object) {
+ punishment = Punishment.DESERIALIZER.deserialize(object.get("punishment").getAsJsonObject());
+ staff = object.get("staff").getAsString();
+ target = object.get("target").getAsString();
+ targetUuid = UUID.fromString(object.get("targetUuid").getAsString());
+ silent = object.get("silent").getAsBoolean();
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/network/packet/PacketDeleteGrant.java b/src/main/java/com/minexd/zoot/network/packet/PacketDeleteGrant.java
new file mode 100644
index 0000000..b2c6e3f
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/network/packet/PacketDeleteGrant.java
@@ -0,0 +1,43 @@
+package com.minexd.zoot.network.packet;
+
+import com.google.gson.JsonObject;
+import com.minexd.zoot.pidgin.packet.Packet;
+import com.minexd.zoot.profile.grant.Grant;
+import com.minexd.zoot.util.json.JsonChain;
+import java.util.UUID;
+import lombok.Getter;
+
+public class PacketDeleteGrant implements Packet {
+
+ @Getter private UUID playerUuid;
+ @Getter private Grant grant;
+
+ public PacketDeleteGrant() {
+
+ }
+
+ public PacketDeleteGrant(UUID playerUuid, Grant grant) {
+ this.playerUuid = playerUuid;
+ this.grant = grant;
+ }
+
+ @Override
+ public int id() {
+ return 3;
+ }
+
+ @Override
+ public JsonObject serialize() {
+ return new JsonChain()
+ .addProperty("playerUuid", playerUuid.toString())
+ .add("grant", Grant.SERIALIZER.serialize(grant))
+ .get();
+ }
+
+ @Override
+ public void deserialize(JsonObject jsonObject) {
+ playerUuid = UUID.fromString(jsonObject.get("playerUuid").getAsString());
+ grant = Grant.DESERIALIZER.deserialize(jsonObject.get("grant").getAsJsonObject());
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/network/packet/PacketDeleteRank.java b/src/main/java/com/minexd/zoot/network/packet/PacketDeleteRank.java
new file mode 100644
index 0000000..dca47a4
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/network/packet/PacketDeleteRank.java
@@ -0,0 +1,37 @@
+package com.minexd.zoot.network.packet;
+
+import com.google.gson.JsonObject;
+import com.minexd.zoot.pidgin.packet.Packet;
+import com.minexd.zoot.util.json.JsonChain;
+import java.util.UUID;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class PacketDeleteRank implements Packet {
+
+ private UUID uuid;
+
+ public PacketDeleteRank() {
+
+ }
+
+ @Override
+ public int id() {
+ return 4;
+ }
+
+ @Override
+ public JsonObject serialize() {
+ return new JsonChain()
+ .addProperty("uuid", uuid.toString())
+ .get();
+ }
+
+ @Override
+ public void deserialize(JsonObject jsonObject) {
+ uuid = UUID.fromString(jsonObject.get("uuid").getAsString());
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/minexd/zoot/network/packet/PacketRefreshRank.java b/src/main/java/com/minexd/zoot/network/packet/PacketRefreshRank.java
new file mode 100644
index 0000000..5af7f87
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/network/packet/PacketRefreshRank.java
@@ -0,0 +1,40 @@
+package com.minexd.zoot.network.packet;
+
+import com.google.gson.JsonObject;
+import com.minexd.zoot.pidgin.packet.Packet;
+import com.minexd.zoot.util.json.JsonChain;
+import java.util.UUID;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+@AllArgsConstructor
+@Getter
+public class PacketRefreshRank implements Packet {
+
+ private UUID uuid;
+ private String name;
+
+ public PacketRefreshRank() {
+
+ }
+
+ @Override
+ public int id() {
+ return 5;
+ }
+
+ @Override
+ public JsonObject serialize() {
+ return new JsonChain()
+ .addProperty("uuid", uuid.toString())
+ .addProperty("name", name)
+ .get();
+ }
+
+ @Override
+ public void deserialize(JsonObject jsonObject) {
+ uuid = UUID.fromString(jsonObject.get("uuid").getAsString());
+ name = jsonObject.get("name").getAsString();
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/minexd/zoot/network/packet/PacketStaffChat.java b/src/main/java/com/minexd/zoot/network/packet/PacketStaffChat.java
new file mode 100644
index 0000000..5a49b50
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/network/packet/PacketStaffChat.java
@@ -0,0 +1,48 @@
+package com.minexd.zoot.network.packet;
+
+import com.google.gson.JsonObject;
+
+import com.minexd.zoot.pidgin.packet.Packet;
+import com.minexd.zoot.util.json.JsonChain;
+import lombok.Getter;
+import lombok.Setter;
+
+@Getter
+@Setter
+public class PacketStaffChat implements Packet {
+
+ private String playerName;
+ private String serverName;
+ private String chatMessage;
+
+ public PacketStaffChat() {
+
+ }
+
+ public PacketStaffChat(String playerName, String serverName, String chatMessage) {
+ this.playerName = playerName;
+ this.serverName = serverName;
+ this.chatMessage = chatMessage;
+ }
+
+ public int id() {
+ return 6;
+ }
+
+ @Override
+ public JsonObject serialize() {
+ return new JsonChain()
+ .addProperty("playerName", playerName)
+ .addProperty("serverName", serverName)
+ .addProperty("chatMessage", chatMessage)
+ .get();
+ }
+
+ @Override
+ public void deserialize(JsonObject jsonObject) {
+ playerName = jsonObject.get("playerName").getAsString();
+ serverName = jsonObject.get("serverName").getAsString();
+ chatMessage = jsonObject.get("chatMessage").getAsString();
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/network/packet/PacketStaffJoinNetwork.java b/src/main/java/com/minexd/zoot/network/packet/PacketStaffJoinNetwork.java
new file mode 100644
index 0000000..696d02c
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/network/packet/PacketStaffJoinNetwork.java
@@ -0,0 +1,42 @@
+package com.minexd.zoot.network.packet;
+
+import com.google.gson.JsonObject;
+import com.minexd.zoot.pidgin.packet.Packet;
+import com.minexd.zoot.util.json.JsonChain;
+import lombok.Getter;
+
+@Getter
+public class PacketStaffJoinNetwork implements Packet {
+
+ private String playerName;
+ private String serverName;
+
+ public PacketStaffJoinNetwork() {
+
+ }
+
+ public PacketStaffJoinNetwork(String playerName, String serverName) {
+ this.playerName = playerName;
+ this.serverName = serverName;
+ }
+
+ @Override
+ public int id() {
+ return 7;
+ }
+
+ @Override
+ public JsonObject serialize() {
+ return new JsonChain()
+ .addProperty("playerName", playerName)
+ .addProperty("serverName", serverName)
+ .get();
+ }
+
+ @Override
+ public void deserialize(JsonObject jsonObject) {
+ playerName = jsonObject.get("playerName").getAsString();
+ serverName = jsonObject.get("serverName").getAsString();
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/network/packet/PacketStaffLeaveNetwork.java b/src/main/java/com/minexd/zoot/network/packet/PacketStaffLeaveNetwork.java
new file mode 100644
index 0000000..c90a473
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/network/packet/PacketStaffLeaveNetwork.java
@@ -0,0 +1,42 @@
+package com.minexd.zoot.network.packet;
+
+import com.google.gson.JsonObject;
+import com.minexd.zoot.pidgin.packet.Packet;
+import com.minexd.zoot.util.json.JsonChain;
+import lombok.Getter;
+
+@Getter
+public class PacketStaffLeaveNetwork implements Packet {
+
+ private String playerName;
+ private String serverName;
+
+ public PacketStaffLeaveNetwork() {
+
+ }
+
+ public PacketStaffLeaveNetwork(String playerName, String serverName) {
+ this.playerName = playerName;
+ this.serverName = serverName;
+ }
+
+ @Override
+ public int id() {
+ return 8;
+ }
+
+ @Override
+ public JsonObject serialize() {
+ return new JsonChain()
+ .addProperty("playerName", playerName)
+ .addProperty("serverName", serverName)
+ .get();
+ }
+
+ @Override
+ public void deserialize(JsonObject jsonObject) {
+ playerName = jsonObject.get("playerName").getAsString();
+ serverName = jsonObject.get("serverName").getAsString();
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/network/packet/PacketStaffSwitchServer.java b/src/main/java/com/minexd/zoot/network/packet/PacketStaffSwitchServer.java
new file mode 100644
index 0000000..c1ad414
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/network/packet/PacketStaffSwitchServer.java
@@ -0,0 +1,46 @@
+package com.minexd.zoot.network.packet;
+
+import com.google.gson.JsonObject;
+import com.minexd.zoot.pidgin.packet.Packet;
+import com.minexd.zoot.util.json.JsonChain;
+import lombok.Getter;
+
+@Getter
+public class PacketStaffSwitchServer implements Packet {
+
+ private String playerName;
+ private String fromServerName;
+ private String toServerName;
+
+ public PacketStaffSwitchServer() {
+
+ }
+
+ public PacketStaffSwitchServer(String playerName, String fromServerName, String toServerName) {
+ this.playerName = playerName;
+ this.fromServerName = fromServerName;
+ this.toServerName = toServerName;
+ }
+
+ @Override
+ public int id() {
+ return 9;
+ }
+
+ @Override
+ public JsonObject serialize() {
+ return new JsonChain()
+ .addProperty("playerName", playerName)
+ .addProperty("fromServerName", fromServerName)
+ .addProperty("toServerName", toServerName)
+ .get();
+ }
+
+ @Override
+ public void deserialize(JsonObject jsonObject) {
+ playerName = jsonObject.get("playerName").getAsString();
+ fromServerName = jsonObject.get("fromServerName").getAsString();
+ toServerName = jsonObject.get("toServerName").getAsString();
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/pidgin/Pidgin.java b/src/main/java/com/minexd/zoot/pidgin/Pidgin.java
new file mode 100644
index 0000000..ce63302
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/pidgin/Pidgin.java
@@ -0,0 +1,184 @@
+package com.minexd.zoot.pidgin;
+
+import com.google.gson.JsonObject;
+import com.google.gson.JsonParser;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.pidgin.packet.Packet;
+import com.minexd.zoot.pidgin.packet.handler.IncomingPacketHandler;
+import com.minexd.zoot.pidgin.packet.handler.PacketExceptionHandler;
+import com.minexd.zoot.pidgin.packet.listener.PacketListener;
+import com.minexd.zoot.pidgin.packet.listener.PacketListenerData;
+import redis.clients.jedis.Jedis;
+import redis.clients.jedis.JedisPool;
+import redis.clients.jedis.JedisPubSub;
+
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ForkJoinPool;
+
+public class Pidgin {
+
+ private static JsonParser PARSER = new JsonParser();
+
+ private final String channel;
+ private JedisPool jedisPool;
+ private JedisPubSub jedisPubSub;
+ private List packetListeners = new ArrayList<>();
+ private Map idToType = new HashMap<>();
+ private Map typeToId = new HashMap<>();
+
+ public Pidgin(String channel, String host, int port, String password) {
+ this.channel = channel;
+ this.packetListeners = new ArrayList<>();
+
+ this.jedisPool = new JedisPool(host, port);
+
+ if (password != null && !password.equals("")) {
+ try (Jedis jedis = this.jedisPool.getResource()) {
+ jedis.auth(password);
+ System.out.println("[Pidgin] Authenticating..");
+ }
+ }
+
+ this.setupPubSub();
+ }
+
+ public void sendPacket(Packet packet) {
+ sendPacket(packet, null);
+ }
+
+ public void sendPacket(Packet packet, PacketExceptionHandler exceptionHandler) {
+ try {
+
+ final JsonObject object = packet.serialize();
+
+ if (object == null) {
+ throw new IllegalStateException("Packet cannot generate null serialized data");
+ }
+
+ try (Jedis jedis = this.jedisPool.getResource()) {
+
+ System.out.println("[Pidgin] Attempting to publish packet..");
+
+ try {
+
+ if (Zoot.get().getMainConfig().getBoolean("REDIS.AUTHENTICATION.ENABLED")) {
+ jedis.auth(Zoot.get().getMainConfig().getString("REDIS.AUTHENTICATION.PASSWORD"));
+ }
+
+ jedis.publish(this.channel, packet.id() + ";" + object.toString());
+ System.out.println("[Pidgin] Successfully published packet..");
+
+ } catch (Exception ex) {
+ System.out.println("[Pidgin] Failed to publish packet..");
+ ex.printStackTrace();
+ }
+
+ }
+ } catch (Exception e) {
+ if (exceptionHandler != null) {
+ exceptionHandler.onException(e);
+ }
+ }
+
+ }
+
+ public Packet buildPacket(int id) {
+ if (!idToType.containsKey(id)) {
+ throw new IllegalStateException("A packet with that ID does not exist");
+ }
+
+ try {
+ return (Packet) idToType.get(id).newInstance();
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+
+ throw new IllegalStateException("Could not create new instance of packet type");
+ }
+
+ public void registerPacket(Class clazz) {
+ try {
+ int id = (int) clazz.getDeclaredMethod("id").invoke(clazz.newInstance(), null);
+
+ if (idToType.containsKey(id) || typeToId.containsKey(clazz)) {
+ throw new IllegalStateException("A packet with that ID has already been registered");
+ }
+
+ idToType.put(id, clazz);
+ typeToId.put(clazz, id);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public void registerListener(PacketListener packetListener) {
+ methodLoop:
+ for (Method method : packetListener.getClass().getDeclaredMethods()) {
+ if (method.getDeclaredAnnotation(IncomingPacketHandler.class) != null) {
+ Class packetClass = null;
+
+ if (method.getParameters().length > 0) {
+ if (Packet.class.isAssignableFrom(method.getParameters()[0].getType())) {
+ packetClass = method.getParameters()[0].getType();
+ }
+ }
+
+ if (packetClass != null) {
+ this.packetListeners.add(new PacketListenerData(packetListener, method, packetClass));
+ }
+ }
+ }
+ }
+
+ private void setupPubSub() {
+ System.out.println("[Pidgin] Setting up PubSup..");
+
+ this.jedisPubSub = new JedisPubSub() {
+
+ @Override
+ public void onMessage(String channel, String message) {
+
+ if (channel.equalsIgnoreCase(Pidgin.this.channel)) {
+
+ try {
+
+ String[] args = message.split(";");
+
+ Integer id = Integer.valueOf(args[0]);
+
+ Packet packet = buildPacket(id);
+
+ if (packet != null) {
+ packet.deserialize(PARSER.parse(args[1]).getAsJsonObject());
+
+ for (PacketListenerData data : packetListeners) {
+ if (data.matches(packet)) {
+ data.getMethod().invoke(data.getInstance(), packet);
+ }
+ }
+ }
+ } catch (Exception e) {
+ System.out.println("[Pidgin] Failed to handle message");
+ e.printStackTrace();
+ }
+ }
+ }
+
+ };
+
+ ForkJoinPool.commonPool().execute(() -> {
+ try (Jedis jedis = this.jedisPool.getResource()) {
+ jedis.subscribe(this.jedisPubSub, channel);
+ System.out.println("[Pidgin] Successfully subscribing to channel..");
+ } catch (Exception exception) {
+ System.out.println("[Pidgin] Failed to subscribe to channel..");
+ exception.printStackTrace();
+ }
+ });
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/pidgin/packet/Packet.java b/src/main/java/com/minexd/zoot/pidgin/packet/Packet.java
new file mode 100644
index 0000000..32f3682
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/pidgin/packet/Packet.java
@@ -0,0 +1,13 @@
+package com.minexd.zoot.pidgin.packet;
+
+import com.google.gson.JsonObject;
+
+public interface Packet {
+
+ int id();
+
+ JsonObject serialize();
+
+ void deserialize(JsonObject object);
+
+}
diff --git a/src/main/java/com/minexd/zoot/pidgin/packet/handler/IncomingPacketHandler.java b/src/main/java/com/minexd/zoot/pidgin/packet/handler/IncomingPacketHandler.java
new file mode 100644
index 0000000..103cb70
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/pidgin/packet/handler/IncomingPacketHandler.java
@@ -0,0 +1,17 @@
+package com.minexd.zoot.pidgin.packet.handler;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Identifies a method and packet type assigned to the method.
+ * The only valid parameter types for a method that is annotated
+ * by this annotation are String and Packet.
+ */
+@Target({ ElementType.METHOD})
+@Retention(RetentionPolicy.RUNTIME)
+public @interface IncomingPacketHandler {
+
+}
diff --git a/src/main/java/com/minexd/zoot/pidgin/packet/handler/PacketExceptionHandler.java b/src/main/java/com/minexd/zoot/pidgin/packet/handler/PacketExceptionHandler.java
new file mode 100644
index 0000000..183bbfb
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/pidgin/packet/handler/PacketExceptionHandler.java
@@ -0,0 +1,10 @@
+package com.minexd.zoot.pidgin.packet.handler;
+
+public class PacketExceptionHandler {
+
+ public void onException(Exception e) {
+ System.out.println("Failed to send packet");
+ e.printStackTrace();
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/pidgin/packet/listener/PacketListener.java b/src/main/java/com/minexd/zoot/pidgin/packet/listener/PacketListener.java
new file mode 100644
index 0000000..6132294
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/pidgin/packet/listener/PacketListener.java
@@ -0,0 +1,5 @@
+package com.minexd.zoot.pidgin.packet.listener;
+
+public interface PacketListener {
+
+}
diff --git a/src/main/java/com/minexd/zoot/pidgin/packet/listener/PacketListenerData.java b/src/main/java/com/minexd/zoot/pidgin/packet/listener/PacketListenerData.java
new file mode 100644
index 0000000..db8f053
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/pidgin/packet/listener/PacketListenerData.java
@@ -0,0 +1,26 @@
+package com.minexd.zoot.pidgin.packet.listener;
+
+import java.lang.reflect.Method;
+
+import com.minexd.zoot.pidgin.packet.Packet;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * A wrapper class that holds all the information needed to
+ * identify and execute a message function.
+ *
+ */
+@AllArgsConstructor
+@Getter
+public class PacketListenerData {
+
+ private Object instance;
+ private Method method;
+ private Class packetClass;
+
+ public boolean matches(Packet packet) {
+ return this.packetClass == packet.getClass();
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/Profile.java b/src/main/java/com/minexd/zoot/profile/Profile.java
new file mode 100644
index 0000000..6692f68
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/Profile.java
@@ -0,0 +1,330 @@
+package com.minexd.zoot.profile;
+
+import com.google.gson.JsonArray;
+import com.google.gson.JsonElement;
+import com.google.gson.JsonParser;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.ZootAPI;
+import com.minexd.zoot.profile.conversation.ProfileConversations;
+import com.minexd.zoot.profile.grant.event.GrantAppliedEvent;
+import com.minexd.zoot.profile.grant.event.GrantExpireEvent;
+import com.minexd.zoot.profile.option.ProfileOptions;
+import com.minexd.zoot.profile.staff.ProfileStaffOptions;
+import com.minexd.zoot.profile.punishment.Punishment;
+import com.minexd.zoot.profile.grant.Grant;
+import com.minexd.zoot.profile.punishment.PunishmentType;
+import com.minexd.zoot.rank.Rank;
+import com.minexd.zoot.util.Cooldown;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoCursor;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.ReplaceOptions;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+import lombok.Getter;
+import lombok.Setter;
+import org.bson.Document;
+import org.bson.conversions.Bson;
+import org.bukkit.Bukkit;
+import org.bukkit.OfflinePlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.permissions.PermissionAttachment;
+import org.bukkit.permissions.PermissionAttachmentInfo;
+
+public class Profile {
+
+ @Getter private static Map profiles = new HashMap<>();
+ private static MongoCollection collection;
+
+ @Getter @Setter private String username;
+ @Getter private final UUID uuid;
+ @Getter @Setter private Long firstSeen;
+ @Getter @Setter private Long lastSeen;
+ @Getter @Setter private String currentAddress;
+ @Getter private List ipAddresses;
+ @Getter private final List knownAlts;
+ @Getter private final ProfileOptions options;
+ @Getter private final ProfileStaffOptions staffOptions;
+ @Getter private final ProfileConversations conversations;
+ @Getter private Grant activeGrant;
+ @Getter private final List grants;
+ @Getter private final List punishments;
+ @Getter @Setter private boolean loaded;
+ @Getter @Setter private Cooldown chatCooldown;
+
+ public Profile(String username, UUID uuid) {
+ this.username = username;
+ this.uuid = uuid;
+ this.grants = new ArrayList<>();
+ this.punishments = new ArrayList<>();
+ this.ipAddresses = new ArrayList<>();
+ this.knownAlts = new ArrayList<>();
+ this.options = new ProfileOptions();
+ this.staffOptions = new ProfileStaffOptions();
+ this.conversations = new ProfileConversations(this);
+ this.chatCooldown = new Cooldown(0);
+
+ load();
+ }
+
+ public Player getPlayer() {
+ return Bukkit.getPlayer(uuid);
+ }
+
+ public String getColoredUsername() {
+ return activeGrant.getRank().getColor() + username;
+ }
+
+ public Punishment getActivePunishmentByType(PunishmentType type) {
+ for (Punishment punishment : punishments) {
+ if (punishment.getType() == type && !punishment.isRemoved() && !punishment.hasExpired()) {
+ return punishment;
+ }
+ }
+
+ return null;
+ }
+
+ public int getPunishmentCountByType(PunishmentType type) {
+ int i = 0;
+
+ for (Punishment punishment : punishments) {
+ if (punishment.getType() == type) i++;
+ }
+
+ return i;
+ }
+
+ public Rank getActiveRank() {
+ return activeGrant.getRank();
+ }
+
+ public void activateNextGrant() {
+ List grants = new ArrayList<>(this.grants);
+
+ grants.sort(Comparator.comparingInt(grant -> grant.getRank().getWeight()));
+ Collections.reverse(grants);
+
+ for (Grant grant : grants) {
+ if (!grant.isRemoved() && !grant.hasExpired()) {
+ if (!grant.equals(activeGrant)) {
+ Zoot.get().debug("");
+ activeGrant = grant;
+ setupBukkitPlayer(getPlayer());
+ return;
+ }
+ }
+ }
+ }
+
+ public void checkGrants() {
+ Player player = getPlayer();
+
+ for (Grant grant : grants) {
+ if (!grant.isRemoved() && grant.hasExpired()) {
+ grant.setRemovedAt(System.currentTimeMillis());
+ grant.setRemovedReason("Grant expired");
+ grant.setRemoved(true);
+
+ if (player != null) {
+ new GrantExpireEvent(player, grant).call();
+ }
+
+ if (grant.equals(activeGrant)) {
+ activateNextGrant();
+ }
+ }
+ }
+
+ if (activeGrant == null) {
+ Grant defaultGrant = new Grant(UUID.randomUUID(), Rank.getDefaultRank(), null,
+ System.currentTimeMillis(), "Default", Integer.MAX_VALUE);
+
+ grants.add(defaultGrant);
+ activeGrant = defaultGrant;
+
+ if (player != null) {
+ new GrantAppliedEvent(player, defaultGrant).call();
+ }
+ }
+ }
+
+ public void setupBukkitPlayer(Player player) {
+ if (player == null) {
+ return;
+ }
+
+ for (PermissionAttachmentInfo attachmentInfo : player.getEffectivePermissions()) {
+ if (attachmentInfo.getAttachment() == null) {
+ continue;
+ }
+
+ attachmentInfo.getAttachment().getPermissions().forEach((permission, value) -> {
+ attachmentInfo.getAttachment().unsetPermission(permission);
+ });
+ }
+
+ PermissionAttachment attachment = player.addAttachment(Zoot.get());
+
+ for (String perm : activeGrant.getRank().getAllPermissions()) {
+ attachment.setPermission(perm, true);
+ }
+
+ player.recalculatePermissions();
+
+ String displayName = activeGrant.getRank().getPrefix() + player.getName() + activeGrant.getRank().getSuffix();
+ String coloredName = ZootAPI.getColoredName(player);
+
+ player.setDisplayName(displayName);
+
+ if (Zoot.get().getMainConfig().getBoolean("SETTINGS.UPDATE_PLAYER_LIST_NAME")) {
+ player.setPlayerListName(coloredName);
+ }
+ }
+
+ public void load() {
+ Document document = collection.find(Filters.eq("uuid", uuid.toString())).first();
+
+ if (document != null) {
+ if (username == null) {
+ username = document.getString("username");
+ }
+
+ firstSeen = document.getLong("firstSeen");
+ lastSeen = document.getLong("lastSeen");
+ currentAddress = document.getString("currentAddress");
+ ipAddresses = Zoot.GSON.fromJson(document.getString("ipAddresses"), Zoot.LIST_STRING_TYPE);
+
+ Document optionsDocument = (Document) document.get("options");
+ options.publicChatEnabled(optionsDocument.getBoolean("publicChatEnabled"));
+ options.receivingNewConversations(optionsDocument.getBoolean("receivingNewConversations"));
+ options.playingMessageSounds(optionsDocument.getBoolean("playingMessageSounds"));
+
+ JsonArray grantList = new JsonParser().parse(document.getString("grants")).getAsJsonArray();
+
+ for (JsonElement grantData : grantList) {
+ // Transform into a Grant object
+ Grant grant = Grant.DESERIALIZER.deserialize(grantData.getAsJsonObject());
+
+ if (grant != null) {
+ this.grants.add(grant);
+ }
+ }
+
+ JsonArray punishmentList = new JsonParser().parse(document.getString("punishments")).getAsJsonArray();
+
+ for (JsonElement punishmentData : punishmentList) {
+ // Transform into a Grant object
+ Punishment punishment = Punishment.DESERIALIZER.deserialize(punishmentData.getAsJsonObject());
+
+ if (punishment != null) {
+ this.punishments.add(punishment);
+ }
+ }
+ }
+
+ // Update active grants
+ activateNextGrant();
+ checkGrants();
+
+ // Set loaded to true
+ loaded = true;
+ }
+
+ public void save() {
+ Document document = new Document();
+ document.put("username", username);
+ document.put("uuid", uuid.toString());
+ document.put("firstSeen", firstSeen);
+ document.put("lastSeen", lastSeen);
+ document.put("currentAddress", currentAddress);
+ document.put("ipAddresses", Zoot.GSON.toJson(ipAddresses, Zoot.LIST_STRING_TYPE));
+
+ Document optionsDocument = new Document();
+ optionsDocument.put("publicChatEnabled", options.publicChatEnabled());
+ optionsDocument.put("receivingNewConversations", options.receivingNewConversations());
+ optionsDocument.put("playingMessageSounds", options.playingMessageSounds());
+ document.put("options", optionsDocument);
+
+ JsonArray grantList = new JsonArray();
+
+ for (Grant grant : this.grants) {
+ grantList.add(Grant.SERIALIZER.serialize(grant));
+ }
+
+ document.put("grants", grantList.toString());
+
+ JsonArray punishmentList = new JsonArray();
+
+ for (Punishment punishment : this.punishments) {
+ punishmentList.add(Punishment.SERIALIZER.serialize(punishment));
+ }
+
+ document.put("punishments", punishmentList.toString());
+
+ collection.replaceOne(Filters.eq("uuid", uuid.toString()), document, new ReplaceOptions().upsert(true));
+ }
+
+ public static void init() {
+ collection = Zoot.get().getMongoDatabase().getCollection("profiles");
+ }
+
+ public static Profile getByUuid(UUID uuid) {
+ if (profiles.containsKey(uuid)) {
+ return profiles.get(uuid);
+ }
+
+ return new Profile(null, uuid);
+ }
+
+ public static Profile getByUsername(String username) {
+ Player player = Bukkit.getPlayer(username);
+
+ if (player != null) {
+ return profiles.get(player.getUniqueId());
+ }
+
+ OfflinePlayer offlinePlayer = Bukkit.getOfflinePlayer(username);
+
+ if (offlinePlayer.hasPlayedBefore()) {
+ if (profiles.containsKey(offlinePlayer.getUniqueId())) {
+ return profiles.get(offlinePlayer.getUniqueId());
+ }
+
+ return new Profile(offlinePlayer.getName(), offlinePlayer.getUniqueId());
+ }
+
+ UUID uuid = Zoot.get().getRedisCache().getUuid(username);
+
+ if (uuid != null) {
+ if (profiles.containsKey(uuid)) {
+ return profiles.get(uuid);
+ }
+
+ return new Profile(username, uuid);
+ }
+
+ return null;
+ }
+
+ public static List getByIpAddress(String ipAddress) {
+ List profiles = new ArrayList<>();
+ Bson filter = Filters.eq("currentAddress", ipAddress);
+
+ try (MongoCursor cursor = collection.find(filter).iterator()) {
+ while (cursor.hasNext()) {
+ Document document = cursor.next();
+ profiles.add(new Profile(document.getString("username"),
+ UUID.fromString(document.getString("uuid"))));
+ }
+ }
+
+ return profiles;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/ProfileListener.java b/src/main/java/com/minexd/zoot/profile/ProfileListener.java
new file mode 100644
index 0000000..eb3fb88
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/ProfileListener.java
@@ -0,0 +1,171 @@
+package com.minexd.zoot.profile;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.bootstrap.BootstrappedListener;
+import com.minexd.zoot.cache.RedisPlayerData;
+import com.minexd.zoot.network.packet.PacketStaffChat;
+import com.minexd.zoot.profile.punishment.Punishment;
+import com.minexd.zoot.profile.punishment.PunishmentType;
+import com.minexd.zoot.util.CC;
+import java.util.List;
+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.player.AsyncPlayerChatEvent;
+import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
+import org.bukkit.event.player.PlayerJoinEvent;
+import org.bukkit.event.player.PlayerQuitEvent;
+import org.bukkit.scheduler.BukkitRunnable;
+
+public class ProfileListener extends BootstrappedListener {
+
+ public ProfileListener(Zoot zoot) {
+ super(zoot);
+ }
+
+ @EventHandler
+ public void onAsyncPlayerPreLogin(AsyncPlayerPreLoginEvent event) {
+ Player player = Bukkit.getPlayer(event.getUniqueId());
+
+ // Need to check if player is still logged in when receiving another login attempt
+ // This happens when a player using a custom client can access the server list while in-game (and reconnecting)
+ if (player != null && player.isOnline()) {
+ event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER);
+ event.setKickMessage(CC.RED + "You tried to login too quickly after disconnecting.\nTry again in a few seconds.");
+ zoot.getServer().getScheduler().runTask(zoot, () -> player.kickPlayer(CC.RED + "Duplicate login kick"));
+ return;
+ }
+
+ Profile profile = null;
+
+ try {
+ profile = new Profile(event.getName(), event.getUniqueId());
+
+ if (!profile.isLoaded()) {
+ event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER);
+ event.setKickMessage(Locale.FAILED_TO_LOAD_PROFILE.format());
+ return;
+ }
+
+ if (profile.getActivePunishmentByType(PunishmentType.BAN) != null) {
+ handleBan(event, profile.getActivePunishmentByType(PunishmentType.BAN));
+ return;
+ }
+
+ profile.setUsername(event.getName());
+
+ if (profile.getFirstSeen() == null) {
+ profile.setFirstSeen(System.currentTimeMillis());
+ }
+
+ profile.setLastSeen(System.currentTimeMillis());
+
+ if (profile.getCurrentAddress() == null) {
+ profile.setCurrentAddress(event.getAddress().getHostAddress());
+ }
+
+ if (!profile.getIpAddresses().contains(event.getAddress().getHostAddress())) {
+ profile.getIpAddresses().add(event.getAddress().getHostAddress());
+ }
+
+ if (!profile.getCurrentAddress().equals(event.getAddress().getHostAddress())) {
+ List alts = Profile.getByIpAddress(event.getAddress().getHostAddress());
+
+ for (Profile alt : alts) {
+ if (alt.getActivePunishmentByType(PunishmentType.BAN) != null) {
+ handleBan(event, alt.getActivePunishmentByType(PunishmentType.BAN));
+ return;
+ }
+ }
+ }
+
+ profile.save();
+ } catch (Exception e) {
+ e.printStackTrace();
+ zoot.debug(Level.SEVERE, "Failed to load profile for " + event.getName(), e);
+ }
+
+ if (profile == null || !profile.isLoaded()) {
+ event.setKickMessage(Locale.FAILED_TO_LOAD_PROFILE.format());
+ event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER);
+ return;
+ }
+
+ Profile.getProfiles().put(profile.getUuid(), profile);
+
+ RedisPlayerData playerData = new RedisPlayerData(event.getUniqueId(), event.getName());
+ playerData.setLastAction(RedisPlayerData.LastAction.JOINING_SERVER);
+ playerData.setLastSeenServer(zoot.getMainConfig().getString("SERVER_NAME"));
+ playerData.setLastSeenAt(System.currentTimeMillis());
+
+ zoot.getRedisCache().updatePlayerData(playerData);
+ zoot.getRedisCache().updateNameAndUUID(event.getName(), event.getUniqueId());
+ }
+
+ @EventHandler
+ public void onPlayerJoin(PlayerJoinEvent event) {
+ Player player = event.getPlayer();
+ Profile profile = Profile.getProfiles().get(player.getUniqueId());
+ profile.setupBukkitPlayer(player);
+
+ for (String perm : profile.getActiveGrant().getRank().getAllPermissions()) {
+ zoot.debug(player, perm);
+ }
+
+ if (player.hasPermission("zoot.staff")) {
+ player.sendMessage(CC.GOLD + "Your staff mode is currently: " +
+ (profile.getStaffOptions().staffModeEnabled() ? CC.GREEN + "Enabled" : CC.RED + "Disabled"));
+ }
+ }
+
+ @EventHandler
+ public void onPlayerQuit(PlayerQuitEvent event) {
+ Profile profile = Profile.getProfiles().remove(event.getPlayer().getUniqueId());
+ profile.setLastSeen(System.currentTimeMillis());
+
+ if (profile.isLoaded()) {
+ new BukkitRunnable() {
+ @Override
+ public void run() {
+ try {
+ profile.save();
+ } catch (Exception e) {
+ zoot.debug(Level.SEVERE, "Failed to save profile " + event.getPlayer().getName(), e);
+ }
+ }
+ }.runTaskAsynchronously(Zoot.get());
+ }
+
+ RedisPlayerData playerData = new RedisPlayerData(event.getPlayer().getUniqueId(), event.getPlayer().getName());
+ playerData.setLastAction(RedisPlayerData.LastAction.LEAVING_SERVER);
+ playerData.setLastSeenServer(zoot.getMainConfig().getString("SERVER_NAME"));
+ playerData.setLastSeenAt(System.currentTimeMillis());
+
+ zoot.getRedisCache().updatePlayerData(playerData);
+ }
+
+ @EventHandler(priority = EventPriority.LOW)
+ public void onAsyncPlayerChatEvent(AsyncPlayerChatEvent event) {
+ Profile profile = Profile.getByUuid(event.getPlayer().getUniqueId());
+
+ if (profile.getStaffOptions().staffChatModeEnabled()) {
+ if (profile.getStaffOptions().staffModeEnabled()) {
+ Zoot.get().getPidgin().sendPacket(new PacketStaffChat(event.getPlayer().getDisplayName(),
+ Zoot.get().getMainConfig().getString("SERVER_NAME"), event.getMessage()));
+ } else {
+ event.getPlayer().sendMessage(CC.RED + "You must enable staff mode or disable staff chat mode.");
+ }
+
+ event.setCancelled(true);
+ }
+ }
+
+ private void handleBan(AsyncPlayerPreLoginEvent event, Punishment punishment) {
+ event.setLoginResult(AsyncPlayerPreLoginEvent.Result.KICK_OTHER);
+ event.setKickMessage(punishment.getKickMessage());
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/ProfileTypeAdapter.java b/src/main/java/com/minexd/zoot/profile/ProfileTypeAdapter.java
new file mode 100644
index 0000000..cc4371f
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/ProfileTypeAdapter.java
@@ -0,0 +1,28 @@
+package com.minexd.zoot.profile;
+
+import com.qrakn.honcho.command.adapter.CommandTypeAdapter;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ProfileTypeAdapter implements CommandTypeAdapter {
+
+ public T convert(String string, Class type) {
+ return type.cast(Profile.getByUsername(string));
+ }
+
+ @Override
+ public List tabComplete(String string, Class type) {
+ List completed = new ArrayList<>();
+
+ for (Profile profile : Profile.getProfiles().values()) {
+ if (profile.getUsername() == null) continue;
+
+ if (profile.getUsername().toLowerCase().startsWith(string.toLowerCase())) {
+ completed.add(profile.getUsername());
+ }
+ }
+
+ return completed;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/conversation/Conversation.java b/src/main/java/com/minexd/zoot/profile/conversation/Conversation.java
new file mode 100644
index 0000000..d7a88ce
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/conversation/Conversation.java
@@ -0,0 +1,90 @@
+package com.minexd.zoot.profile.conversation;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.ZootAPI;
+import com.minexd.zoot.profile.Profile;
+import lombok.Getter;
+import org.bukkit.Bukkit;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+
+import java.util.UUID;
+
+public class Conversation {
+
+ @Getter private final UUID initiatedBy;
+ @Getter private final UUID target;
+ @Getter private long lastMessageSentAt;
+ @Getter private UUID lastMessageSentBy;
+
+ public Conversation(UUID initiatedBy, UUID target) {
+ this.initiatedBy = initiatedBy;
+ this.target = target;
+ this.lastMessageSentAt = System.currentTimeMillis();
+
+ Profile initiatorProfile = Profile.getByUuid(initiatedBy);
+ initiatorProfile.getConversations().getConversations().put(target, this);
+
+ Profile targetProfile = Profile.getByUuid(target);
+ targetProfile.getConversations().getConversations().put(initiatedBy, this);
+ }
+
+ public void sendMessage(Player sender, Player target, String message) {
+ sender.sendMessage(Locale.CONVERSATION_SEND_MESSAGE.format(
+ sender.getName(), sender.getDisplayName(), ZootAPI.getColorOfPlayer(sender),
+ target.getName(), target.getDisplayName(), ZootAPI.getColorOfPlayer(target))
+ .replace("%MSG%", message));
+
+ Profile targetProfile = Profile.getByUuid(target.getUniqueId());
+
+ if (targetProfile.getOptions().playingMessageSounds()) {
+ target.playSound(target.getLocation(), Sound.SUCCESSFUL_HIT, 1.0F, 1.0F);
+ }
+
+ target.sendMessage(Locale.CONVERSATION_RECEIVE_MESSAGE.format(
+ target.getName(), target.getDisplayName(), ZootAPI.getColorOfPlayer(target),
+ sender.getName(), sender.getDisplayName(), ZootAPI.getColorOfPlayer(sender))
+ .replace("%MSG%", message));
+
+ lastMessageSentAt = System.currentTimeMillis();
+ lastMessageSentBy = sender.getUniqueId();
+ }
+
+ public boolean validate() {
+ Player initiator = Bukkit.getPlayer(initiatedBy);
+
+ if (initiator == null || !initiator.isOnline()) {
+ destroy();
+ return false;
+ }
+
+ Player target = Bukkit.getPlayer(this.target);
+
+ if (target == null || !target.isOnline()) {
+ destroy();
+ return false;
+ }
+
+ return true;
+ }
+
+ public void destroy() {
+ for (Player player : new Player[] { Bukkit.getPlayer(initiatedBy), Bukkit.getPlayer(target) }) {
+ if (player != null && player.isOnline()) {
+ Profile profile = Profile.getByUuid(player.getUniqueId());
+ profile.getConversations().getConversations().remove(getPartner(player.getUniqueId()));
+ }
+ }
+ }
+
+ public UUID getPartner(UUID compareWith) {
+ if (initiatedBy.equals(compareWith)) {
+ return target;
+ } else if (target.equals(compareWith)) {
+ return initiatedBy;
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/conversation/ProfileConversations.java b/src/main/java/com/minexd/zoot/profile/conversation/ProfileConversations.java
new file mode 100644
index 0000000..d79f81f
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/conversation/ProfileConversations.java
@@ -0,0 +1,61 @@
+package com.minexd.zoot.profile.conversation;
+
+import com.minexd.zoot.profile.Profile;
+
+import java.util.*;
+import java.util.stream.Collectors;
+
+import lombok.Getter;
+import org.bukkit.entity.Player;
+
+public class ProfileConversations {
+
+ @Getter private final Profile profile;
+ @Getter private Map conversations;
+
+ public ProfileConversations(Profile profile) {
+ this.profile = profile;
+ this.conversations = new HashMap<>();
+ }
+
+ public boolean canBeMessagedBy(Player player) {
+ if (!profile.getOptions().receivingNewConversations()) {
+ return conversations.containsKey(player.getUniqueId());
+ }
+
+ return true;
+ }
+
+ public Conversation getOrCreateConversation(Player target) {
+ Player sender = profile.getPlayer();
+
+ if (sender != null) {
+ Conversation conversation = conversations.get(target.getUniqueId());
+
+ if (conversation == null) {
+ conversation = new Conversation(profile.getUuid(), target.getUniqueId());
+ }
+
+ return conversation;
+ }
+
+ return null;
+ }
+
+ public Conversation getLastRepliedConversation() {
+ List list = conversations
+ .values()
+ .stream()
+ .sorted(Comparator.comparingLong(Conversation::getLastMessageSentAt))
+ .collect(Collectors.toList());
+
+ Collections.reverse(list);
+
+ return list.isEmpty() ? null : list.get(0);
+ }
+
+ public void expireAllConversations() {
+ this.conversations.clear();
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/conversation/command/MessageCommand.java b/src/main/java/com/minexd/zoot/profile/conversation/command/MessageCommand.java
new file mode 100644
index 0000000..6c9bf10
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/conversation/command/MessageCommand.java
@@ -0,0 +1,39 @@
+package com.minexd.zoot.profile.conversation.command;
+
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.conversation.Conversation;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = { "message", "msg", "whisper", "tell", "t" })
+public class MessageCommand {
+
+ public void execute(Player player, Player target, String message) {
+ if (player.equals(target)) {
+ player.sendMessage(CC.RED + "You cannot message yourself!");
+ return;
+ }
+
+ if (target == null) {
+ player.sendMessage(CC.RED + "A player with that name could not be found.");
+ return;
+ }
+
+ Profile playerProfile = Profile.getByUuid(player.getUniqueId());
+ Profile targetProfile = Profile.getByUuid(target.getUniqueId());
+
+ if (targetProfile.getConversations().canBeMessagedBy(player)) {
+ Conversation conversation = playerProfile.getConversations().getOrCreateConversation(target);
+
+ if (conversation.validate()) {
+ conversation.sendMessage(player, target, message);
+ } else {
+ player.sendMessage(CC.RED + "That player is not receiving new conversations right now.");
+ }
+ } else {
+ player.sendMessage(CC.RED + "That player is not receiving new conversations right now.");
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/conversation/command/ReplyCommand.java b/src/main/java/com/minexd/zoot/profile/conversation/command/ReplyCommand.java
new file mode 100644
index 0000000..f6aee42
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/conversation/command/ReplyCommand.java
@@ -0,0 +1,28 @@
+package com.minexd.zoot.profile.conversation.command;
+
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.conversation.Conversation;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = { "reply", "r" })
+public class ReplyCommand {
+
+ public void execute(Player player, String message) {
+ Profile playerProfile = Profile.getByUuid(player.getUniqueId());
+ Conversation conversation = playerProfile.getConversations().getLastRepliedConversation();
+
+ if (conversation != null) {
+ if (conversation.validate()) {
+ conversation.sendMessage(player, Bukkit.getPlayer(conversation.getPartner(player.getUniqueId())), message);
+ } else {
+ player.sendMessage(CC.RED + "You can no longer reply to that player.");
+ }
+ } else {
+ player.sendMessage(CC.RED + "You have nobody to reply to.");
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/grant/Grant.java b/src/main/java/com/minexd/zoot/profile/grant/Grant.java
new file mode 100644
index 0000000..775fcac
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/grant/Grant.java
@@ -0,0 +1,68 @@
+package com.minexd.zoot.profile.grant;
+
+import com.minexd.zoot.rank.Rank;
+import com.minexd.zoot.util.TimeUtil;
+import java.util.Date;
+import java.util.UUID;
+import lombok.Getter;
+import lombok.Setter;
+
+public class Grant {
+
+ public static GrantJsonSerializer SERIALIZER = new GrantJsonSerializer();
+ public static GrantJsonDeserializer DESERIALIZER = new GrantJsonDeserializer();
+
+ @Getter private final UUID uuid;
+ @Getter private final Rank rank;
+ @Getter @Setter private UUID addedBy;
+ @Getter private final long addedAt;
+ @Getter private final String addedReason;
+ @Getter private final long duration;
+ @Getter @Setter private UUID removedBy;
+ @Getter @Setter private long removedAt;
+ @Getter @Setter private String removedReason;
+ @Getter @Setter private boolean removed;
+
+ public Grant(UUID uuid, Rank rank, UUID addedBy, long addedAt, String addedReason, long duration) {
+ this.uuid = uuid;
+ this.rank = rank;
+ this.addedBy = addedBy;
+ this.addedAt = addedAt;
+ this.addedReason = addedReason;
+ this.duration = duration;
+ }
+
+ public boolean isPermanent() {
+ return duration == Integer.MAX_VALUE;
+ }
+
+ public boolean hasExpired() {
+ return (!isPermanent()) && (System.currentTimeMillis() >= addedAt + duration);
+ }
+
+ public String getExpiresAtDate() {
+ if (duration == Integer.MAX_VALUE) {
+ return "Never";
+ }
+
+ return TimeUtil.dateToString(new Date(addedAt + duration), "&7");
+ }
+
+ public String getDurationText() {
+ if (removed) {
+ return "Removed";
+ }
+
+ if (isPermanent()) {
+ return "Permanent";
+ }
+
+ return TimeUtil.millisToRoundedTime(duration);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ return object instanceof Grant && ((Grant) object).uuid.equals(uuid);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/grant/GrantJsonDeserializer.java b/src/main/java/com/minexd/zoot/profile/grant/GrantJsonDeserializer.java
new file mode 100644
index 0000000..da24be3
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/grant/GrantJsonDeserializer.java
@@ -0,0 +1,50 @@
+package com.minexd.zoot.profile.grant;
+
+import com.google.gson.JsonObject;
+import com.minexd.zoot.rank.Rank;
+import com.minexd.zoot.util.json.JsonDeserializer;
+import java.util.UUID;
+
+public class GrantJsonDeserializer implements JsonDeserializer {
+
+ @Override
+ public Grant deserialize(JsonObject object) {
+ Rank rank = Rank.getRankByUuid(UUID.fromString(object.get("rank").getAsString()));
+
+ if (rank == null) {
+ return null;
+ }
+
+ Grant grant = new Grant(
+ UUID.fromString(object.get("uuid").getAsString()),
+ rank,
+ null,
+ object.get("addedAt").getAsLong(),
+ object.get("addedReason").getAsString(),
+ object.get("duration").getAsLong()
+ );
+
+ if (!object.get("addedBy").isJsonNull()) {
+ grant.setAddedBy(UUID.fromString(object.get("addedBy").getAsString()));
+ }
+
+ if (!object.get("removedBy").isJsonNull()) {
+ grant.setRemovedBy(UUID.fromString(object.get("removedBy").getAsString()));
+ }
+
+ if (!object.get("removedAt").isJsonNull()) {
+ grant.setRemovedAt(object.get("removedAt").getAsLong());
+ }
+
+ if (!object.get("removedReason").isJsonNull()) {
+ grant.setRemovedReason(object.get("removedReason").getAsString());
+ }
+
+ if (!object.get("removed").isJsonNull()) {
+ grant.setRemoved(object.get("removed").getAsBoolean());
+ }
+
+ return grant;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/grant/GrantJsonSerializer.java b/src/main/java/com/minexd/zoot/profile/grant/GrantJsonSerializer.java
new file mode 100644
index 0000000..5a11f55
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/grant/GrantJsonSerializer.java
@@ -0,0 +1,24 @@
+package com.minexd.zoot.profile.grant;
+
+import com.google.gson.JsonObject;
+import com.minexd.zoot.util.json.JsonSerializer;
+
+public class GrantJsonSerializer implements JsonSerializer {
+
+ @Override
+ public JsonObject serialize(Grant grant) {
+ JsonObject object = new JsonObject();
+ object.addProperty("uuid", grant.getUuid().toString());
+ object.addProperty("rank", grant.getRank().getUuid().toString());
+ object.addProperty("addedBy", grant.getAddedBy() == null ? null : grant.getAddedBy().toString());
+ object.addProperty("addedAt", grant.getAddedAt());
+ object.addProperty("addedReason", grant.getAddedReason());
+ object.addProperty("duration", grant.getDuration());
+ object.addProperty("removedBy", grant.getRemovedBy() == null ? null : grant.getRemovedBy().toString());
+ object.addProperty("removedAt", grant.getRemovedAt());
+ object.addProperty("removedReason", grant.getRemovedReason());
+ object.addProperty("removed", grant.isRemoved());
+ return object;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/grant/GrantListener.java b/src/main/java/com/minexd/zoot/profile/grant/GrantListener.java
new file mode 100644
index 0000000..5ab99e9
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/grant/GrantListener.java
@@ -0,0 +1,103 @@
+package com.minexd.zoot.profile.grant;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.bootstrap.BootstrappedListener;
+import com.minexd.zoot.network.packet.PacketDeleteGrant;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.grant.event.GrantAppliedEvent;
+import com.minexd.zoot.profile.grant.event.GrantExpireEvent;
+import com.minexd.zoot.profile.grant.procedure.GrantProcedure;
+import com.minexd.zoot.profile.grant.procedure.GrantProcedureStage;
+import com.minexd.zoot.profile.grant.procedure.GrantProcedureType;
+import com.minexd.zoot.util.CC;
+import com.minexd.zoot.util.TimeUtil;
+import com.minexd.zoot.util.callback.TypeCallback;
+import com.minexd.zoot.util.menu.menus.ConfirmMenu;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.player.AsyncPlayerChatEvent;
+
+public class GrantListener extends BootstrappedListener {
+
+ public GrantListener(Zoot zoot) {
+ super(zoot);
+ }
+
+ @EventHandler
+ public void onGrantAppliedEvent(GrantAppliedEvent event) {
+ Player player = event.getPlayer();
+ Grant grant = event.getGrant();
+
+ player.sendMessage(CC.GREEN + ("A `{rank}` grant has been applied to you for {time-remaining}.")
+ .replace("{rank}", grant.getRank().getDisplayName())
+ .replace("{time-remaining}", grant.getDuration() == Integer.MAX_VALUE ?
+ "forever" : TimeUtil.millisToRoundedTime((grant.getAddedAt() + grant.getDuration()) -
+ System.currentTimeMillis())));
+
+ Profile profile = Profile.getByUuid(player.getUniqueId());
+ profile.setupBukkitPlayer(player);
+ }
+
+ @EventHandler
+ public void onGrantExpireEvent(GrantExpireEvent event) {
+ Player player = event.getPlayer();
+ Grant grant = event.getGrant();
+
+ player.sendMessage(CC.RED + ("Your `{rank}` grant has expired.")
+ .replace("{rank}", grant.getRank().getDisplayName()));
+
+ Profile profile = Profile.getByUuid(player.getUniqueId());
+ profile.setupBukkitPlayer(player);
+ }
+
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void onAsyncPlayerChatEvent(AsyncPlayerChatEvent event) {
+ if (!event.getPlayer().hasPermission("zoot.staff.grant")) {
+ return;
+ }
+
+ GrantProcedure procedure = GrantProcedure.getByPlayer(event.getPlayer());
+
+ if (procedure != null && procedure.getStage() == GrantProcedureStage.REQUIRE_TEXT) {
+ event.setCancelled(true);
+
+ if (event.getMessage().equalsIgnoreCase("cancel")) {
+ GrantProcedure.getProcedures().remove(procedure);
+ event.getPlayer().sendMessage(CC.RED + "You have cancelled the grant procedure.");
+ return;
+ }
+
+ if (procedure.getType() == GrantProcedureType.REMOVE) {
+ new ConfirmMenu(CC.YELLOW + "Delete this grant?", new TypeCallback() {
+ @Override
+ public void callback(Boolean data) {
+ if (data) {
+ procedure.getGrant().setRemovedBy(event.getPlayer().getUniqueId());
+ procedure.getGrant().setRemovedAt(System.currentTimeMillis());
+ procedure.getGrant().setRemovedReason(event.getMessage());
+ procedure.getGrant().setRemoved(true);
+ procedure.finish();
+ event.getPlayer().sendMessage(CC.GREEN + "The grant has been removed.");
+
+ Zoot.get().getPidgin().sendPacket(new PacketDeleteGrant(procedure.getRecipient().getUuid(),
+ procedure.getGrant()));
+ } else {
+ procedure.cancel();
+ event.getPlayer().sendMessage(CC.RED + "You did not confirm to remove the grant.");
+ }
+ }
+ }, true) {
+ @Override
+ public void onClose(Player player) {
+ if (!isClosedByMenu()) {
+ procedure.cancel();
+ event.getPlayer().sendMessage(CC.RED + "You did not confirm to remove the grant.");
+ }
+ }
+ }.openMenu(event.getPlayer());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/grant/command/GrantCommand.java b/src/main/java/com/minexd/zoot/profile/grant/command/GrantCommand.java
new file mode 100644
index 0000000..8727024
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/grant/command/GrantCommand.java
@@ -0,0 +1,62 @@
+package com.minexd.zoot.profile.grant.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.network.packet.PacketAddGrant;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.grant.Grant;
+import com.minexd.zoot.profile.grant.event.GrantAppliedEvent;
+import com.minexd.zoot.rank.Rank;
+import com.minexd.zoot.util.CC;
+import com.minexd.zoot.util.TimeUtil;
+import com.minexd.zoot.util.duration.Duration;
+import com.qrakn.honcho.command.CPL;
+import com.qrakn.honcho.command.CommandMeta;
+import java.util.UUID;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "grant", async = true, permission = "zoot.staff.grant")
+public class GrantCommand {
+
+ public void execute(CommandSender sender, @CPL("player") Profile profile, Rank rank, Duration duration, String reason) {
+ if (rank == null) {
+ sender.sendMessage(Locale.RANK_NOT_FOUND.format());
+ return;
+ }
+
+ if (profile == null || !profile.isLoaded()) {
+ sender.sendMessage(Locale.COULD_NOT_RESOLVE_PLAYER.format());
+ return;
+ }
+
+ if (duration.getValue() == -1) {
+ sender.sendMessage(CC.RED + "That duration is not valid.");
+ sender.sendMessage(CC.RED + "Example: [perm/1y1m1w1d]");
+ return;
+ }
+
+ UUID addedBy = sender instanceof Player ? ((Player) sender).getUniqueId() : null;
+ Grant grant = new Grant(UUID.randomUUID(), rank, addedBy, System.currentTimeMillis(), reason,
+ duration.getValue());
+
+ profile.getGrants().add(grant);
+ profile.save();
+ profile.activateNextGrant();
+
+ Zoot.get().getPidgin().sendPacket(new PacketAddGrant(profile.getUuid(), grant));
+
+ sender.sendMessage(CC.GREEN + "You applied a `{rank}` grant to `{player}` for {time-remaining}."
+ .replace("{rank}", rank.getDisplayName())
+ .replace("{player}", profile.getUsername())
+ .replace("{time-remaining}", duration.getValue() == Integer.MAX_VALUE ? "forever"
+ : TimeUtil.millisToRoundedTime(duration.getValue())));
+
+ Player player = profile.getPlayer();
+
+ if (player != null) {
+ new GrantAppliedEvent(player, grant).call();
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/grant/command/GrantsCommand.java b/src/main/java/com/minexd/zoot/profile/grant/command/GrantsCommand.java
new file mode 100644
index 0000000..b2c8d7a
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/grant/command/GrantsCommand.java
@@ -0,0 +1,22 @@
+package com.minexd.zoot.profile.grant.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.grant.menu.GrantsListMenu;
+import com.qrakn.honcho.command.CPL;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "grants", async = true, permission = "zoot.staff.grant")
+public class GrantsCommand {
+
+ public void execute(Player player, @CPL("player") Profile profile) {
+ if (profile == null || !profile.isLoaded()) {
+ player.sendMessage(Locale.COULD_NOT_RESOLVE_PLAYER.format());
+ return;
+ }
+
+ new GrantsListMenu(profile).openMenu(player);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/grant/event/GrantAppliedEvent.java b/src/main/java/com/minexd/zoot/profile/grant/event/GrantAppliedEvent.java
new file mode 100644
index 0000000..bdddeb6
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/grant/event/GrantAppliedEvent.java
@@ -0,0 +1,16 @@
+package com.minexd.zoot.profile.grant.event;
+
+import com.minexd.zoot.profile.grant.Grant;
+import com.minexd.zoot.util.BaseEvent;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.bukkit.entity.Player;
+
+@AllArgsConstructor
+@Getter
+public class GrantAppliedEvent extends BaseEvent {
+
+ private Player player;
+ private Grant grant;
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/grant/event/GrantExpireEvent.java b/src/main/java/com/minexd/zoot/profile/grant/event/GrantExpireEvent.java
new file mode 100644
index 0000000..ac62a6f
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/grant/event/GrantExpireEvent.java
@@ -0,0 +1,16 @@
+package com.minexd.zoot.profile.grant.event;
+
+import com.minexd.zoot.profile.grant.Grant;
+import com.minexd.zoot.util.BaseEvent;
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.bukkit.entity.Player;
+
+@AllArgsConstructor
+@Getter
+public class GrantExpireEvent extends BaseEvent {
+
+ private Player player;
+ private Grant grant;
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/grant/menu/GrantsListMenu.java b/src/main/java/com/minexd/zoot/profile/grant/menu/GrantsListMenu.java
new file mode 100644
index 0000000..d5e769d
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/grant/menu/GrantsListMenu.java
@@ -0,0 +1,129 @@
+package com.minexd.zoot.profile.grant.menu;
+
+import com.minexd.zoot.profile.grant.Grant;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.grant.procedure.GrantProcedure;
+import com.minexd.zoot.profile.grant.procedure.GrantProcedureStage;
+import com.minexd.zoot.profile.grant.procedure.GrantProcedureType;
+import com.minexd.zoot.util.ItemBuilder;
+import com.minexd.zoot.util.CC;
+import com.minexd.zoot.util.TimeUtil;
+import com.minexd.zoot.util.menu.Button;
+import com.minexd.zoot.util.menu.pagination.PaginatedMenu;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.AllArgsConstructor;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+@AllArgsConstructor
+public class GrantsListMenu extends PaginatedMenu {
+
+ private Profile profile;
+
+ @Override
+ public String getPrePaginatedTitle(Player player) {
+ return "&6" + profile.getUsername() + "'s Grants (" + profile.getGrants().size() + ")";
+ }
+
+ @Override
+ public Map getAllPagesButtons(Player player) {
+ Map buttons = new HashMap<>();
+
+ for (Grant grant : profile.getGrants()) {
+ buttons.put(buttons.size(), new GrantInfoButton(profile, grant));
+ }
+
+ return buttons;
+ }
+
+ @AllArgsConstructor
+ private class GrantInfoButton extends Button {
+
+ private Profile profile;
+ private Grant grant;
+
+ @Override
+ public ItemStack getButtonItem(Player player) {
+ int durability;
+
+ if (grant.isRemoved()) {
+ durability = 5;
+ } else if (grant.hasExpired()) {
+ durability = 4;
+ } else {
+ durability = 14;
+ }
+
+ String addedBy = "Console";
+
+ if (grant.getAddedBy() != null) {
+ addedBy = "Could not fetch...";
+
+ Profile addedByProfile = Profile.getByUuid(grant.getAddedBy());
+
+ if (addedByProfile != null && addedByProfile.isLoaded()) {
+ addedBy = addedByProfile.getUsername();
+ }
+ }
+
+ String removedBy = "Console";
+
+ if (grant.getRemovedBy() != null) {
+ removedBy = "Could not fetch...";
+
+ Profile removedByProfile = Profile.getByUuid(grant.getRemovedBy());
+
+ if (removedByProfile != null && removedByProfile.isLoaded()) {
+ removedBy = removedByProfile.getUsername();
+ }
+ }
+
+ List lore = new ArrayList<>();
+
+ lore.add(CC.MENU_BAR);
+ lore.add("&3Rank: &e" + grant.getRank().getDisplayName());
+ lore.add("&3Duration: &e" + grant.getDurationText());
+ lore.add("&3Issued by: &e" + addedBy);
+ lore.add("&3Reason: &e" + grant.getAddedReason());
+
+ if (grant.isRemoved()) {
+ lore.add(CC.MENU_BAR);
+ lore.add("&a&lGrant Removed");
+ lore.add("&a" + TimeUtil.dateToString(new Date(grant.getRemovedAt()), "&7"));
+ lore.add("&aRemoved by: &7" + removedBy);
+ lore.add("&aReason: &7&o\"" + grant.getRemovedReason() + "\"");
+ } else {
+ if (!grant.hasExpired()) {
+ lore.add(CC.MENU_BAR);
+ lore.add("&aRight click to remove this grant");
+ }
+ }
+
+ lore.add(CC.MENU_BAR);
+
+ return new ItemBuilder(Material.PAPER)
+ .name("&3" + TimeUtil.dateToString(new Date(grant.getAddedAt()), "&7"))
+ .durability(durability)
+ .lore(lore)
+ .build();
+ }
+
+ @Override
+ public void clicked(Player player, ClickType clickType) {
+ if (clickType == ClickType.RIGHT && !grant.isRemoved() && !grant.hasExpired()) {
+ GrantProcedure procedure = new GrantProcedure(player, profile, GrantProcedureType.REMOVE, GrantProcedureStage.REQUIRE_TEXT);
+ procedure.setGrant(grant);
+
+ player.sendMessage(CC.GREEN + "Type a reason for removing this grant in chat...");
+ player.closeInventory();
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/grant/procedure/GrantProcedure.java b/src/main/java/com/minexd/zoot/profile/grant/procedure/GrantProcedure.java
new file mode 100644
index 0000000..0cd2982
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/grant/procedure/GrantProcedure.java
@@ -0,0 +1,49 @@
+package com.minexd.zoot.profile.grant.procedure;
+
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.grant.Grant;
+import java.util.HashSet;
+import java.util.Set;
+import lombok.Getter;
+import lombok.Setter;
+import org.bukkit.entity.Player;
+
+public class GrantProcedure {
+
+ @Getter private static final Set procedures = new HashSet<>();
+
+ @Getter private final Player issuer;
+ @Getter private final Profile recipient;
+ @Getter private final GrantProcedureType type;
+ @Getter private GrantProcedureStage stage;
+ @Getter @Setter private Grant grant;
+
+ public GrantProcedure(Player issuer, Profile recipient, GrantProcedureType type, GrantProcedureStage stage) {
+ this.issuer = issuer;
+ this.recipient = recipient;
+ this.type = type;
+ this.stage = stage;
+
+ procedures.add(this);
+ }
+
+ public void finish() {
+ this.recipient.save();
+ procedures.remove(this);
+ }
+
+ public void cancel() {
+ procedures.remove(this);
+ }
+
+ public static GrantProcedure getByPlayer(Player player) {
+ for (GrantProcedure procedure : procedures) {
+ if (procedure.issuer.equals(player)) {
+ return procedure;
+ }
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/grant/procedure/GrantProcedureStage.java b/src/main/java/com/minexd/zoot/profile/grant/procedure/GrantProcedureStage.java
new file mode 100644
index 0000000..91220d0
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/grant/procedure/GrantProcedureStage.java
@@ -0,0 +1,9 @@
+package com.minexd.zoot.profile.grant.procedure;
+
+public enum GrantProcedureStage {
+
+ REQUIRE_CLICK,
+ REQUIRE_TEXT,
+ REQUIRE_CONFIRMATION,
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/grant/procedure/GrantProcedureType.java b/src/main/java/com/minexd/zoot/profile/grant/procedure/GrantProcedureType.java
new file mode 100644
index 0000000..2fdab1a
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/grant/procedure/GrantProcedureType.java
@@ -0,0 +1,8 @@
+package com.minexd.zoot.profile.grant.procedure;
+
+public enum GrantProcedureType {
+
+ GRANT,
+ REMOVE
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/menu/ProfileMenuControlHeaderButton.java b/src/main/java/com/minexd/zoot/profile/menu/ProfileMenuControlHeaderButton.java
new file mode 100644
index 0000000..0e2a6b7
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/menu/ProfileMenuControlHeaderButton.java
@@ -0,0 +1,53 @@
+package com.minexd.zoot.profile.menu;
+
+import com.minexd.zoot.cache.RedisPlayerData;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.util.CC;
+import com.minexd.zoot.util.ItemBuilder;
+import com.minexd.zoot.util.menu.Button;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.SkullMeta;
+
+@AllArgsConstructor
+public class ProfileMenuControlHeaderButton extends Button {
+
+ private Profile profile;
+ private RedisPlayerData playerData;
+
+ @Override
+ public ItemStack getButtonItem(Player player) {
+ ItemStack itemStack = new ItemStack(Material.SKULL_ITEM, 1, (short) 3);
+ SkullMeta skullMeta = (SkullMeta) itemStack.getItemMeta();
+ skullMeta.setOwner(profile.getUsername());
+ itemStack.setItemMeta(skullMeta);
+
+ List lore = new ArrayList<>();
+ lore.add(CC.MENU_BAR);
+
+ if (playerData == null) {
+ lore.add("&cStatus data not available");
+ } else {
+ if (playerData.getLastAction() == RedisPlayerData.LastAction.JOINING_SERVER) {
+ lore.add("&aCurrently Online");
+ } else if (playerData.getLastAction() == RedisPlayerData.LastAction.LEAVING_SERVER) {
+ lore.add("&cLast Seen");
+ }
+
+ lore.add("&3Server: &e" + playerData.getLastSeenServer());
+ lore.add("&3Updated: &e" + playerData.getTimeAgo());
+ }
+
+ lore.add(CC.MENU_BAR);
+
+ return new ItemBuilder(itemStack)
+ .name("&3" + profile.getUsername())
+ .lore(lore)
+ .build();
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/option/ProfileOptions.java b/src/main/java/com/minexd/zoot/profile/option/ProfileOptions.java
new file mode 100644
index 0000000..cd4df92
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/option/ProfileOptions.java
@@ -0,0 +1,14 @@
+package com.minexd.zoot.profile.option;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+@Accessors(fluent = true)
+public class ProfileOptions {
+
+ @Getter @Setter private boolean publicChatEnabled = true;
+ @Getter @Setter private boolean receivingNewConversations = true;
+ @Getter @Setter private boolean playingMessageSounds = true;
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/option/command/OptionsCommand.java b/src/main/java/com/minexd/zoot/profile/option/command/OptionsCommand.java
new file mode 100644
index 0000000..dc37eab
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/option/command/OptionsCommand.java
@@ -0,0 +1,14 @@
+package com.minexd.zoot.profile.option.command;
+
+import com.minexd.zoot.profile.option.menu.ProfileOptionsMenu;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = { "options", "settings" })
+public class OptionsCommand {
+
+ public void execute(Player player) {
+ new ProfileOptionsMenu().openMenu(player);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/option/command/ToggleGlobalChatCommand.java b/src/main/java/com/minexd/zoot/profile/option/command/ToggleGlobalChatCommand.java
new file mode 100644
index 0000000..e17cb3c
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/option/command/ToggleGlobalChatCommand.java
@@ -0,0 +1,22 @@
+package com.minexd.zoot.profile.option.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.profile.Profile;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = { "toggleglobalchat", "tgc", "togglepublicchat", "tpc" })
+public class ToggleGlobalChatCommand {
+
+ public void execute(Player player) {
+ Profile profile = Profile.getByUuid(player.getUniqueId());
+ profile.getOptions().publicChatEnabled(!profile.getOptions().publicChatEnabled());
+
+ if (profile.getOptions().publicChatEnabled()) {
+ player.sendMessage(Locale.OPTIONS_GLOBAL_CHAT_ENABLED.format());
+ } else {
+ player.sendMessage(Locale.OPTIONS_GLOBAL_CHAT_DISABLED.format());
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/option/command/TogglePrivateMessagesCommand.java b/src/main/java/com/minexd/zoot/profile/option/command/TogglePrivateMessagesCommand.java
new file mode 100644
index 0000000..43886b9
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/option/command/TogglePrivateMessagesCommand.java
@@ -0,0 +1,23 @@
+package com.minexd.zoot.profile.option.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.profile.Profile;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = { "togglepm", "togglepms", "tpm", "tpms" })
+public class TogglePrivateMessagesCommand {
+
+ public void execute(Player player) {
+ Profile profile = Profile.getByUuid(player.getUniqueId());
+ profile.getOptions().receivingNewConversations(!profile.getOptions().receivingNewConversations());
+ profile.getConversations().expireAllConversations();
+
+ if (profile.getOptions().receivingNewConversations()) {
+ player.sendMessage(Locale.OPTIONS_PRIVATE_MESSAGES_ENABLED.format());
+ } else {
+ player.sendMessage(Locale.OPTIONS_PRIVATE_MESSAGES_DISABLED.format());
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/option/command/ToggleSoundsCommand.java b/src/main/java/com/minexd/zoot/profile/option/command/ToggleSoundsCommand.java
new file mode 100644
index 0000000..1adf1a1
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/option/command/ToggleSoundsCommand.java
@@ -0,0 +1,22 @@
+package com.minexd.zoot.profile.option.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.profile.Profile;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = { "togglesounds", "sounds" })
+public class ToggleSoundsCommand {
+
+ public void execute(Player player) {
+ Profile profile = Profile.getByUuid(player.getUniqueId());
+ profile.getOptions().playingMessageSounds(!profile.getOptions().playingMessageSounds());
+
+ if (profile.getOptions().playingMessageSounds()) {
+ player.sendMessage(Locale.OPTIONS_PRIVATE_MESSAGE_SOUND_ENABLED.format());
+ } else {
+ player.sendMessage(Locale.OPTIONS_PRIVATE_MESSAGE_SOUND_DISABLED.format());
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/option/event/OptionsOpenedEvent.java b/src/main/java/com/minexd/zoot/profile/option/event/OptionsOpenedEvent.java
new file mode 100644
index 0000000..0381104
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/option/event/OptionsOpenedEvent.java
@@ -0,0 +1,18 @@
+package com.minexd.zoot.profile.option.event;
+
+import com.minexd.zoot.profile.option.menu.ProfileOptionButton;
+import com.minexd.zoot.util.BaseEvent;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import org.bukkit.entity.Player;
+
+@RequiredArgsConstructor
+@Getter
+public class OptionsOpenedEvent extends BaseEvent {
+
+ private final Player player;
+ private List buttons = new ArrayList<>();
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/option/menu/ProfileOptionButton.java b/src/main/java/com/minexd/zoot/profile/option/menu/ProfileOptionButton.java
new file mode 100644
index 0000000..7ed5885
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/option/menu/ProfileOptionButton.java
@@ -0,0 +1,55 @@
+package com.minexd.zoot.profile.option.menu;
+
+import com.minexd.zoot.util.CC;
+import com.minexd.zoot.util.ItemBuilder;
+import com.minexd.zoot.util.TextSplitter;
+import com.minexd.zoot.util.menu.Button;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang.StringEscapeUtils;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+@AllArgsConstructor
+public abstract class ProfileOptionButton extends Button {
+
+ @Override
+ public ItemStack getButtonItem(Player player) {
+ ItemBuilder itemBuilder = new ItemBuilder(isEnabled(player) ? getEnabledItem(player) : getDisabledItem(player));
+
+ List lore = new ArrayList<>();
+ lore.add("");
+ lore.addAll(TextSplitter.split(40, getDescription(), CC.GRAY, " "));
+ lore.add("");
+ lore.add((isEnabled(player) ? CC.BLUE + StringEscapeUtils.unescapeJava(" » ") : " ") + "&e" + getEnabledOption());
+ lore.add((!isEnabled(player) ? CC.BLUE + StringEscapeUtils.unescapeJava(" » ") : " ") + "&e" + getDisabledOption());
+ lore.add("");
+ lore.add("&eClick to toggle this option.");
+
+ return itemBuilder.name(getOptionName())
+ .lore(lore)
+ .build();
+ }
+
+ public abstract ItemStack getEnabledItem(Player player);
+
+ public abstract ItemStack getDisabledItem(Player player);
+
+ public abstract String getOptionName();
+
+ public abstract String getDescription();
+
+ public abstract String getEnabledOption();
+
+ public abstract String getDisabledOption();
+
+ public abstract boolean isEnabled(Player player);
+
+ @Override
+ public boolean shouldUpdate(Player player, ClickType clickType) {
+ return true;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/option/menu/ProfileOptionsMenu.java b/src/main/java/com/minexd/zoot/profile/option/menu/ProfileOptionsMenu.java
new file mode 100644
index 0000000..9c22399
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/option/menu/ProfileOptionsMenu.java
@@ -0,0 +1,39 @@
+package com.minexd.zoot.profile.option.menu;
+
+import com.minexd.zoot.profile.option.event.OptionsOpenedEvent;
+import com.minexd.zoot.profile.option.menu.button.PrivateChatOptionButton;
+import com.minexd.zoot.profile.option.menu.button.PrivateChatSoundsOptionButton;
+import com.minexd.zoot.profile.option.menu.button.PublicChatOptionButton;
+import com.minexd.zoot.util.menu.Button;
+import com.minexd.zoot.util.menu.Menu;
+import java.util.HashMap;
+import java.util.Map;
+import org.bukkit.entity.Player;
+
+public class ProfileOptionsMenu extends Menu {
+
+ @Override
+ public String getTitle(Player player) {
+ return "&6&lOptions";
+ }
+
+ @Override
+ public Map getButtons(Player player) {
+ Map buttons = new HashMap<>();
+ buttons.put(buttons.size(), new PublicChatOptionButton());
+ buttons.put(buttons.size(), new PrivateChatOptionButton());
+ buttons.put(buttons.size(), new PrivateChatSoundsOptionButton());
+
+ OptionsOpenedEvent event = new OptionsOpenedEvent(player);
+ event.call();
+
+ if (!event.getButtons().isEmpty()) {
+ for (ProfileOptionButton button : event.getButtons()) {
+ buttons.put(buttons.size(), button);
+ }
+ }
+
+ return buttons;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/option/menu/button/PrivateChatOptionButton.java b/src/main/java/com/minexd/zoot/profile/option/menu/button/PrivateChatOptionButton.java
new file mode 100644
index 0000000..69a43e4
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/option/menu/button/PrivateChatOptionButton.java
@@ -0,0 +1,54 @@
+package com.minexd.zoot.profile.option.menu.button;
+
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.option.menu.ProfileOptionButton;
+import com.minexd.zoot.util.ItemBuilder;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+public class PrivateChatOptionButton extends ProfileOptionButton {
+
+ @Override
+ public String getOptionName() {
+ return "&c&lPrivate Chat";
+ }
+
+ @Override
+ public ItemStack getEnabledItem(Player player) {
+ return new ItemBuilder(Material.NAME_TAG).build();
+ }
+
+ @Override
+ public ItemStack getDisabledItem(Player player) {
+ return new ItemBuilder(Material.NAME_TAG).build();
+ }
+
+ @Override
+ public String getDescription() {
+ return "If enabled, you will receive private chat messages.";
+ }
+
+ @Override
+ public String getEnabledOption() {
+ return "Receive private chat messages";
+ }
+
+ @Override
+ public String getDisabledOption() {
+ return "Do not receive private chat messages";
+ }
+
+ @Override
+ public boolean isEnabled(Player player) {
+ return Profile.getProfiles().get(player.getUniqueId()).getOptions().receivingNewConversations();
+ }
+
+ @Override
+ public void clicked(Player player, ClickType clickType) {
+ Profile profile = Profile.getProfiles().get(player.getUniqueId());
+ profile.getOptions().receivingNewConversations(!profile.getOptions().receivingNewConversations());
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/option/menu/button/PrivateChatSoundsOptionButton.java b/src/main/java/com/minexd/zoot/profile/option/menu/button/PrivateChatSoundsOptionButton.java
new file mode 100644
index 0000000..03241ed
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/option/menu/button/PrivateChatSoundsOptionButton.java
@@ -0,0 +1,54 @@
+package com.minexd.zoot.profile.option.menu.button;
+
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.option.menu.ProfileOptionButton;
+import com.minexd.zoot.util.ItemBuilder;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+public class PrivateChatSoundsOptionButton extends ProfileOptionButton {
+
+ @Override
+ public String getOptionName() {
+ return "&e&lPrivate Chat Sounds";
+ }
+
+ @Override
+ public ItemStack getEnabledItem(Player player) {
+ return new ItemBuilder(Material.NOTE_BLOCK).build();
+ }
+
+ @Override
+ public ItemStack getDisabledItem(Player player) {
+ return new ItemBuilder(Material.NOTE_BLOCK).build();
+ }
+
+ @Override
+ public String getDescription() {
+ return "If enabled, a sound will be played when you receive a private chat message.";
+ }
+
+ @Override
+ public String getEnabledOption() {
+ return "Play private chat message sounds";
+ }
+
+ @Override
+ public String getDisabledOption() {
+ return "Do not play private chat message sounds";
+ }
+
+ @Override
+ public boolean isEnabled(Player player) {
+ return Profile.getProfiles().get(player.getUniqueId()).getOptions().playingMessageSounds();
+ }
+
+ @Override
+ public void clicked(Player player, ClickType clickType) {
+ Profile profile = Profile.getProfiles().get(player.getUniqueId());
+ profile.getOptions().playingMessageSounds(!profile.getOptions().playingMessageSounds());
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/option/menu/button/PublicChatOptionButton.java b/src/main/java/com/minexd/zoot/profile/option/menu/button/PublicChatOptionButton.java
new file mode 100644
index 0000000..506acb8
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/option/menu/button/PublicChatOptionButton.java
@@ -0,0 +1,54 @@
+package com.minexd.zoot.profile.option.menu.button;
+
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.option.menu.ProfileOptionButton;
+import com.minexd.zoot.util.ItemBuilder;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+public class PublicChatOptionButton extends ProfileOptionButton {
+
+ @Override
+ public String getOptionName() {
+ return "&a&lPublic Chat";
+ }
+
+ @Override
+ public ItemStack getEnabledItem(Player player) {
+ return new ItemBuilder(Material.BOOK_AND_QUILL).build();
+ }
+
+ @Override
+ public ItemStack getDisabledItem(Player player) {
+ return new ItemBuilder(Material.BOOK_AND_QUILL).build();
+ }
+
+ @Override
+ public String getDescription() {
+ return "If enabled, you will receive public chat messages.";
+ }
+
+ @Override
+ public String getEnabledOption() {
+ return "Receive public chat messages";
+ }
+
+ @Override
+ public String getDisabledOption() {
+ return "Do not receive public chat messages";
+ }
+
+ @Override
+ public boolean isEnabled(Player player) {
+ return Profile.getProfiles().get(player.getUniqueId()).getOptions().publicChatEnabled();
+ }
+
+ @Override
+ public void clicked(Player player, ClickType clickType) {
+ Profile profile = Profile.getProfiles().get(player.getUniqueId());
+ profile.getOptions().publicChatEnabled(!profile.getOptions().publicChatEnabled());
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/Punishment.java b/src/main/java/com/minexd/zoot/profile/punishment/Punishment.java
new file mode 100644
index 0000000..b5a9ca2
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/Punishment.java
@@ -0,0 +1,131 @@
+package com.minexd.zoot.profile.punishment;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.util.CC;
+import com.minexd.zoot.util.TimeUtil;
+import java.util.UUID;
+import lombok.Getter;
+import lombok.Setter;
+import org.bukkit.Bukkit;
+
+public class Punishment {
+
+ public static PunishmentJsonSerializer SERIALIZER = new PunishmentJsonSerializer();
+ public static PunishmentJsonDeserializer DESERIALIZER = new PunishmentJsonDeserializer();
+
+ @Getter private final UUID uuid;
+ @Getter private final PunishmentType type;
+ @Getter @Setter private UUID addedBy;
+ @Getter final private long addedAt;
+ @Getter private final String addedReason;
+ @Getter final private long duration;
+ @Getter @Setter private UUID removedBy;
+ @Getter @Setter private long removedAt;
+ @Getter @Setter private String removedReason;
+ @Getter @Setter private boolean removed;
+
+ public Punishment(UUID uuid, PunishmentType type, long addedAt, String addedReason, long duration) {
+ this.uuid = uuid;
+ this.type = type;
+ this.addedAt = addedAt;
+ this.addedReason = addedReason;
+ this.duration = duration;
+ }
+
+ public boolean isPermanent() {
+ return type == PunishmentType.BLACKLIST || duration == Integer.MAX_VALUE;
+ }
+
+ public boolean hasExpired() {
+ return (!isPermanent()) && (System.currentTimeMillis() >= addedAt + duration);
+ }
+
+ public String getDurationText() {
+ if (removed) {
+ return "Removed";
+ }
+
+ if (isPermanent()) {
+ return "Permanent";
+ }
+
+ return TimeUtil.millisToRoundedTime(duration);
+ }
+
+ public String getTimeRemaining() {
+ if (removed) {
+ return "Removed";
+ }
+
+ if (isPermanent()) {
+ return "Permanent";
+ }
+
+ if (hasExpired()) {
+ return "Expired";
+ }
+
+ return TimeUtil.millisToRoundedTime((addedAt + duration) - System.currentTimeMillis());
+ }
+
+ public String getContext() {
+ if (!(type == PunishmentType.BAN || type == PunishmentType.MUTE)) {
+ return removed ? type.getUndoContext() : type.getContext();
+ }
+
+ if (isPermanent()) {
+ return (removed ? type.getUndoContext() : "permanently " + type.getContext());
+ } else {
+ return (removed ? type.getUndoContext() : "temporarily " + type.getContext());
+ }
+ }
+
+ public void broadcast(String sender, String target, boolean silent) {
+ if (silent) {
+ Bukkit.getOnlinePlayers().forEach(player -> {
+ if (player.hasPermission("zoot.staff")) {
+ player.sendMessage(Zoot.get().getMainConfig().getString("PUNISHMENTS.BROADCAST_SILENT")
+ .replace("{context}", getContext())
+ .replace("{target}", target)
+ .replace("{sender}", sender));
+ }
+ });
+ } else {
+ Bukkit.broadcastMessage(Zoot.get().getMainConfig().getString("PUNISHMENTS.BROADCAST")
+ .replace("{context}", getContext())
+ .replace("{target}", target)
+ .replace("{sender}", sender));
+ }
+ }
+
+ public String getKickMessage() {
+ String kickMessage;
+
+ if (type == PunishmentType.BAN) {
+ kickMessage = Zoot.get().getMainConfig().getString("PUNISHMENTS.BAN.KICK");
+ String temporary = "";
+
+ if (!isPermanent()) {
+ temporary = Zoot.get().getMainConfig().getString("PUNISHMENTS.BAN.TEMPORARY");
+ temporary = temporary.replace("{time-remaining}", getTimeRemaining());
+ }
+
+ kickMessage = kickMessage.replace("{context}", getContext())
+ .replace("{temporary}", temporary);
+ } else if (type == PunishmentType.KICK) {
+ kickMessage = Zoot.get().getMainConfig().getString("PUNISHMENTS.KICK.KICK")
+ .replace("{context}", getContext())
+ .replace("{reason}", addedReason);
+ } else {
+ kickMessage = null;
+ }
+
+ return CC.translate(kickMessage);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ return object != null && object instanceof Punishment && ((Punishment) object).uuid.equals(uuid);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/PunishmentJsonDeserializer.java b/src/main/java/com/minexd/zoot/profile/punishment/PunishmentJsonDeserializer.java
new file mode 100644
index 0000000..d7c4ac5
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/PunishmentJsonDeserializer.java
@@ -0,0 +1,42 @@
+package com.minexd.zoot.profile.punishment;
+
+import com.google.gson.JsonObject;
+import com.minexd.zoot.util.json.JsonDeserializer;
+import java.util.UUID;
+
+public class PunishmentJsonDeserializer implements JsonDeserializer {
+
+ @Override
+ public Punishment deserialize(JsonObject object) {
+ Punishment punishment = new Punishment(
+ UUID.fromString(object.get("uuid").getAsString()),
+ PunishmentType.valueOf(object.get("type").getAsString()),
+ object.get("addedAt").getAsLong(),
+ object.get("addedReason").getAsString(),
+ object.get("duration").getAsLong()
+ );
+
+ if (!object.get("addedBy").isJsonNull()) {
+ punishment.setAddedBy(UUID.fromString(object.get("addedBy").getAsString()));
+ }
+
+ if (!object.get("removedBy").isJsonNull()) {
+ punishment.setRemovedBy(UUID.fromString(object.get("removedBy").getAsString()));
+ }
+
+ if (!object.get("removedAt").isJsonNull()) {
+ punishment.setRemovedAt(object.get("removedAt").getAsLong());
+ }
+
+ if (!object.get("removedReason").isJsonNull()) {
+ punishment.setRemovedReason(object.get("removedReason").getAsString());
+ }
+
+ if (!object.get("removed").isJsonNull()) {
+ punishment.setRemoved(object.get("removed").getAsBoolean());
+ }
+
+ return punishment;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/PunishmentJsonSerializer.java b/src/main/java/com/minexd/zoot/profile/punishment/PunishmentJsonSerializer.java
new file mode 100644
index 0000000..f2db5bc
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/PunishmentJsonSerializer.java
@@ -0,0 +1,24 @@
+package com.minexd.zoot.profile.punishment;
+
+import com.google.gson.JsonObject;
+import com.minexd.zoot.util.json.JsonSerializer;
+
+public class PunishmentJsonSerializer implements JsonSerializer {
+
+ @Override
+ public JsonObject serialize(Punishment punishment) {
+ JsonObject object = new JsonObject();
+ object.addProperty("uuid", punishment.getUuid().toString());
+ object.addProperty("type", punishment.getType().name());
+ object.addProperty("addedBy", punishment.getAddedBy() == null ? null : punishment.getAddedBy().toString());
+ object.addProperty("addedAt", punishment.getAddedAt());
+ object.addProperty("addedReason", punishment.getAddedReason());
+ object.addProperty("duration", punishment.getDuration());
+ object.addProperty("removedBy", punishment.getRemovedBy() == null ? null : punishment.getRemovedBy().toString());
+ object.addProperty("removedAt", punishment.getRemovedAt());
+ object.addProperty("removedReason", punishment.getRemovedReason());
+ object.addProperty("removed", punishment.isRemoved());
+ return object;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/PunishmentType.java b/src/main/java/com/minexd/zoot/profile/punishment/PunishmentType.java
new file mode 100644
index 0000000..c299628
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/PunishmentType.java
@@ -0,0 +1,34 @@
+package com.minexd.zoot.profile.punishment;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import org.bukkit.ChatColor;
+
+@Getter
+@AllArgsConstructor
+public enum PunishmentType {
+
+ BLACKLIST("Blacklist", "blacklisted", "unblacklisted", true, true, new PunishmentTypeData("Blacklists", ChatColor.DARK_RED, 14)),
+ BAN("Ban", "banned", "unbanned", true, true, new PunishmentTypeData("Bans", ChatColor.GOLD, 1)),
+ MUTE("Mute", "muted", "unmuted", false, true, new PunishmentTypeData("Mutes", ChatColor.YELLOW, 4)),
+ WARN("Warning", "warned", null, false, false, new PunishmentTypeData("Warnings", ChatColor.GREEN, 13)),
+ KICK("Kick", "kicked", null, false, false, new PunishmentTypeData("Kicks", ChatColor.GRAY, 7));
+
+ private String readable;
+ private String context;
+ private String undoContext;
+ private boolean ban;
+ private boolean removable;
+ private PunishmentTypeData typeData;
+
+ @AllArgsConstructor
+ @Getter
+ public static class PunishmentTypeData {
+
+ private String readable;
+ private ChatColor color;
+ private int durability;
+
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/command/BanCommand.java b/src/main/java/com/minexd/zoot/profile/punishment/command/BanCommand.java
new file mode 100644
index 0000000..c2a378e
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/command/BanCommand.java
@@ -0,0 +1,67 @@
+package com.minexd.zoot.profile.punishment.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.network.packet.PacketBroadcastPunishment;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.punishment.Punishment;
+import com.minexd.zoot.profile.punishment.PunishmentType;
+import com.minexd.zoot.util.CC;
+import com.minexd.zoot.util.duration.Duration;
+import com.qrakn.honcho.command.CPL;
+import com.qrakn.honcho.command.CommandMeta;
+import com.qrakn.honcho.command.CommandOption;
+import java.util.UUID;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+
+@CommandMeta(label = "ban", permission = "zoot.staff.ban", async = true, options = "s")
+public class BanCommand {
+
+ public void execute(CommandSender sender, CommandOption option, @CPL("player") Profile profile, Duration duration, String reason) {
+ if (profile == null || !profile.isLoaded()) {
+ sender.sendMessage(Locale.COULD_NOT_RESOLVE_PLAYER.format());
+ return;
+ }
+
+ if (profile.getActivePunishmentByType(PunishmentType.BAN) != null) {
+ sender.sendMessage(CC.RED + "That player is already banned.");
+ return;
+ }
+
+ if (duration.getValue() == -1) {
+ sender.sendMessage(CC.RED + "That duration is not valid.");
+ sender.sendMessage(CC.RED + "Example: [perm/1y1m1w1d]");
+ return;
+ }
+
+ String staffName = sender instanceof Player ? Profile.getProfiles().get(((Player) sender)
+ .getUniqueId()).getColoredUsername() : CC.DARK_RED + "Console";
+
+ Punishment punishment = new Punishment(UUID.randomUUID(), PunishmentType.BAN, System.currentTimeMillis(),
+ reason, duration.getValue());
+
+ if (sender instanceof Player) {
+ punishment.setAddedBy(((Player) sender).getUniqueId());
+ }
+
+ profile.getPunishments().add(punishment);
+ profile.save();
+
+ Zoot.get().getPidgin().sendPacket(new PacketBroadcastPunishment(punishment, staffName,
+ profile.getColoredUsername(), profile.getUuid(), option != null));
+
+ Player player = profile.getPlayer();
+
+ if (player != null) {
+ new BukkitRunnable() {
+ @Override
+ public void run() {
+ player.kickPlayer(punishment.getKickMessage());
+ }
+ }.runTask(Zoot.get());
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/command/CheckCommand.java b/src/main/java/com/minexd/zoot/profile/punishment/command/CheckCommand.java
new file mode 100644
index 0000000..b8ae666
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/command/CheckCommand.java
@@ -0,0 +1,31 @@
+package com.minexd.zoot.profile.punishment.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.cache.RedisPlayerData;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.punishment.menu.PunishmentsListMenu;
+import com.qrakn.honcho.command.CPL;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = { "check", "c" }, permission = "zoot.staff.check", async = true)
+public class CheckCommand {
+
+ public void execute(Player player, @CPL("player") Profile profile) {
+ if (profile == null || !profile.isLoaded()) {
+ player.sendMessage(Locale.COULD_NOT_RESOLVE_PLAYER.format());
+ return;
+ }
+
+ RedisPlayerData redisPlayerData = Zoot.get().getRedisCache().getPlayerData(profile.getUuid());
+
+ if (redisPlayerData == null) {
+ player.sendMessage(Locale.COULD_NOT_RESOLVE_PLAYER.format());
+ return;
+ }
+
+ new PunishmentsListMenu(profile, redisPlayerData).openMenu(player);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/command/KickCommand.java b/src/main/java/com/minexd/zoot/profile/punishment/command/KickCommand.java
new file mode 100644
index 0000000..9fa9b58
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/command/KickCommand.java
@@ -0,0 +1,57 @@
+package com.minexd.zoot.profile.punishment.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.network.packet.PacketBroadcastPunishment;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.punishment.Punishment;
+import com.minexd.zoot.profile.punishment.PunishmentType;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import com.qrakn.honcho.command.CommandOption;
+import java.util.UUID;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+import org.bukkit.scheduler.BukkitRunnable;
+
+@CommandMeta(label = "kick", permission = "zoot.staff.kick", async = true, options = "s")
+public class KickCommand {
+
+ public void execute(CommandSender sender, CommandOption option, Player player, String reason) {
+ if (player == null) {
+ sender.sendMessage(Locale.COULD_NOT_RESOLVE_PLAYER.format());
+ return;
+ }
+
+ Profile profile = Profile.getProfiles().get(player.getUniqueId());
+
+ if (profile == null || !profile.isLoaded()) {
+ sender.sendMessage(Locale.COULD_NOT_RESOLVE_PLAYER.format());
+ return;
+ }
+
+ String staffName = sender instanceof Player ? Profile.getProfiles().get(((Player) sender)
+ .getUniqueId()).getColoredUsername() : CC.DARK_RED + "Console";
+
+ Punishment punishment = new Punishment(UUID.randomUUID(), PunishmentType.KICK, System.currentTimeMillis(),
+ reason, -1);
+
+ if (sender instanceof Player) {
+ punishment.setAddedBy(((Player) sender).getUniqueId());
+ }
+
+ profile.getPunishments().add(punishment);
+ profile.save();
+
+ Zoot.get().getPidgin().sendPacket(new PacketBroadcastPunishment(punishment, staffName,
+ profile.getColoredUsername(), profile.getUuid(), option != null));
+
+ new BukkitRunnable() {
+ @Override
+ public void run() {
+ player.kickPlayer(punishment.getKickMessage());
+ }
+ }.runTask(Zoot.get());
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/command/MuteCommand.java b/src/main/java/com/minexd/zoot/profile/punishment/command/MuteCommand.java
new file mode 100644
index 0000000..48c2107
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/command/MuteCommand.java
@@ -0,0 +1,70 @@
+package com.minexd.zoot.profile.punishment.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.network.packet.PacketBroadcastPunishment;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.punishment.Punishment;
+import com.minexd.zoot.profile.punishment.PunishmentType;
+import com.minexd.zoot.util.CC;
+import com.minexd.zoot.util.duration.Duration;
+import com.qrakn.honcho.command.CPL;
+import com.qrakn.honcho.command.CommandMeta;
+import com.qrakn.honcho.command.CommandOption;
+import java.util.UUID;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "mute", permission = "zoot.staff.mute", async = true, options = "s")
+public class MuteCommand {
+
+ public void execute(CommandSender sender, CommandOption option, @CPL("player") Profile profile, Duration duration, String reason) {
+ if (profile == null || !profile.isLoaded()) {
+ sender.sendMessage(Locale.COULD_NOT_RESOLVE_PLAYER.format());
+ return;
+ }
+
+ if (profile.getActivePunishmentByType(PunishmentType.MUTE) != null) {
+ sender.sendMessage(CC.RED + "That player is already muted.");
+ return;
+ }
+
+ if (duration.getValue() == -1) {
+ sender.sendMessage(CC.RED + "That duration is not valid.");
+ sender.sendMessage(CC.RED + "Example: [perm/1y1m1w1d]");
+ return;
+ }
+
+ String staffName = sender instanceof Player ? Profile.getProfiles().get(((Player) sender)
+ .getUniqueId()).getColoredUsername() : CC.DARK_RED + "Console";
+
+ Punishment punishment = new Punishment(UUID.randomUUID(), PunishmentType.MUTE, System.currentTimeMillis(),
+ reason, duration.getValue());
+
+ if (sender instanceof Player) {
+ punishment.setAddedBy(((Player) sender).getUniqueId());
+ }
+
+ profile.getPunishments().add(punishment);
+ profile.save();
+
+ Player player = profile.getPlayer();
+
+ if (player != null) {
+ String senderName = sender instanceof Player ? Profile.getProfiles().get(((Player) sender).getUniqueId()).getColoredUsername() : CC.DARK_RED + "Console";
+ player.sendMessage(CC.RED + "You have been " + punishment.getContext() + " by " +
+ senderName + CC.RED + ".");
+ player.sendMessage(CC.RED + "The reason for this punishment: " + CC.WHITE +
+ punishment.getAddedReason());
+
+ if (!punishment.isPermanent()) {
+ player.sendMessage(CC.RED + "This mute will expire in " + CC.WHITE +
+ punishment.getTimeRemaining() + CC.RED + ".");
+ }
+ }
+
+ Zoot.get().getPidgin().sendPacket(new PacketBroadcastPunishment(punishment, staffName,
+ profile.getColoredUsername(), profile.getUuid(), option != null));
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/command/UnbanCommand.java b/src/main/java/com/minexd/zoot/profile/punishment/command/UnbanCommand.java
new file mode 100644
index 0000000..43f3d7b
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/command/UnbanCommand.java
@@ -0,0 +1,48 @@
+package com.minexd.zoot.profile.punishment.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.network.packet.PacketBroadcastPunishment;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.punishment.Punishment;
+import com.minexd.zoot.profile.punishment.PunishmentType;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CPL;
+import com.qrakn.honcho.command.CommandMeta;
+import com.qrakn.honcho.command.CommandOption;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "unban", permission = "zoot.staff.unban", async = true, options = "s")
+public class UnbanCommand {
+
+ public void execute(CommandSender sender, CommandOption option, @CPL("player") Profile profile, String reason) {
+ if (profile == null || !profile.isLoaded()) {
+ sender.sendMessage(Locale.COULD_NOT_RESOLVE_PLAYER.format());
+ return;
+ }
+
+ if (profile.getActivePunishmentByType(PunishmentType.BAN) == null) {
+ sender.sendMessage(CC.RED + "That player is not banned.");
+ return;
+ }
+
+ String staffName = sender instanceof Player ? Profile.getProfiles().get(((Player) sender)
+ .getUniqueId()).getColoredUsername() : CC.DARK_RED + "Console";
+
+ Punishment punishment = profile.getActivePunishmentByType(PunishmentType.BAN);
+ punishment.setRemovedAt(System.currentTimeMillis());
+ punishment.setRemovedReason(reason);
+ punishment.setRemoved(true);
+
+ if (sender instanceof Player) {
+ punishment.setRemovedBy(((Player) sender).getUniqueId());
+ }
+
+ profile.save();
+
+ Zoot.get().getPidgin().sendPacket(new PacketBroadcastPunishment(punishment, staffName,
+ profile.getColoredUsername(), profile.getUuid(), option != null));
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/command/UnmuteCommand.java b/src/main/java/com/minexd/zoot/profile/punishment/command/UnmuteCommand.java
new file mode 100644
index 0000000..872b989
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/command/UnmuteCommand.java
@@ -0,0 +1,48 @@
+package com.minexd.zoot.profile.punishment.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.network.packet.PacketBroadcastPunishment;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.punishment.Punishment;
+import com.minexd.zoot.profile.punishment.PunishmentType;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CPL;
+import com.qrakn.honcho.command.CommandMeta;
+import com.qrakn.honcho.command.CommandOption;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "unmute", permission = "zoot.staff.unmute", async = true, options = "s")
+public class UnmuteCommand {
+
+ public void execute(CommandSender sender, CommandOption option, @CPL("player") Profile profile, String reason) {
+ if (profile == null || !profile.isLoaded()) {
+ sender.sendMessage(Locale.COULD_NOT_RESOLVE_PLAYER.format());
+ return;
+ }
+
+ if (profile.getActivePunishmentByType(PunishmentType.MUTE) == null) {
+ sender.sendMessage(CC.RED + "That player is not muted.");
+ return;
+ }
+
+ String staffName = sender instanceof Player ? Profile.getProfiles().get(((Player) sender)
+ .getUniqueId()).getColoredUsername() : CC.DARK_RED + "Console";
+
+ Punishment punishment = profile.getActivePunishmentByType(PunishmentType.MUTE);
+ punishment.setRemovedAt(System.currentTimeMillis());
+ punishment.setRemovedReason(reason);
+ punishment.setRemoved(true);
+
+ if (sender instanceof Player) {
+ punishment.setRemovedBy(((Player) sender).getUniqueId());
+ }
+
+ profile.save();
+
+ Zoot.get().getPidgin().sendPacket(new PacketBroadcastPunishment(punishment, staffName,
+ profile.getColoredUsername(), profile.getUuid(), option != null));
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/command/WarnCommand.java b/src/main/java/com/minexd/zoot/profile/punishment/command/WarnCommand.java
new file mode 100644
index 0000000..86a941b
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/command/WarnCommand.java
@@ -0,0 +1,51 @@
+package com.minexd.zoot.profile.punishment.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.network.packet.PacketBroadcastPunishment;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.punishment.Punishment;
+import com.minexd.zoot.profile.punishment.PunishmentType;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CPL;
+import com.qrakn.honcho.command.CommandMeta;
+import com.qrakn.honcho.command.CommandOption;
+import java.util.UUID;
+import org.bukkit.command.CommandSender;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "warn", permission = "zoot.staff.warn", async = true, options = "s")
+public class WarnCommand {
+
+ public void execute(CommandSender sender, CommandOption option, @CPL("player") Profile profile, String reason) {
+ if (profile == null || !profile.isLoaded()) {
+ sender.sendMessage(Locale.COULD_NOT_RESOLVE_PLAYER.format());
+ return;
+ }
+
+ String staffName = sender instanceof Player ? Profile.getProfiles().get(((Player) sender)
+ .getUniqueId()).getColoredUsername() : CC.DARK_RED + "Console";
+
+ Punishment punishment = new Punishment(UUID.randomUUID(), PunishmentType.WARN, System.currentTimeMillis(),
+ reason, -1);
+
+ if (sender instanceof Player) {
+ punishment.setAddedBy(((Player) sender).getUniqueId());
+ }
+
+ profile.getPunishments().add(punishment);
+ profile.save();
+
+ Player player = profile.getPlayer();
+
+ if (player != null) {
+ String senderName = sender instanceof Player ? Profile.getProfiles().get(((Player) sender).getUniqueId()).getColoredUsername() : CC.DARK_RED + "Console";
+ player.sendMessage(CC.RED + "You have been warned by " + senderName + CC.RED + ".");
+ player.sendMessage(CC.RED + "The reason for this punishment: " + CC.WHITE + punishment.getAddedReason());
+ }
+
+ Zoot.get().getPidgin().sendPacket(new PacketBroadcastPunishment(punishment, staffName,
+ profile.getColoredUsername(), profile.getUuid(), option != null));
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/listener/PunishmentListener.java b/src/main/java/com/minexd/zoot/profile/punishment/listener/PunishmentListener.java
new file mode 100644
index 0000000..214deba
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/listener/PunishmentListener.java
@@ -0,0 +1,69 @@
+package com.minexd.zoot.profile.punishment.listener;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.bootstrap.BootstrappedListener;
+import com.minexd.zoot.profile.punishment.procedure.PunishmentProcedure;
+import com.minexd.zoot.profile.punishment.procedure.PunishmentProcedureStage;
+import com.minexd.zoot.profile.punishment.procedure.PunishmentProcedureType;
+import com.minexd.zoot.util.CC;
+import com.minexd.zoot.util.callback.TypeCallback;
+import com.minexd.zoot.util.menu.menus.ConfirmMenu;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.player.AsyncPlayerChatEvent;
+
+public class PunishmentListener extends BootstrappedListener {
+
+ public PunishmentListener(Zoot zoot) {
+ super(zoot);
+ }
+
+ @EventHandler(priority = EventPriority.LOWEST)
+ public void onAsyncPlayerChatEvent(AsyncPlayerChatEvent event) {
+ if (!event.getPlayer().hasPermission("zoot.staff.grant")) {
+ return;
+ }
+
+ PunishmentProcedure procedure = PunishmentProcedure.getByPlayer(event.getPlayer());
+
+ if (procedure != null && procedure.getStage() == PunishmentProcedureStage.REQUIRE_TEXT) {
+ event.setCancelled(true);
+
+ if (event.getMessage().equalsIgnoreCase("cancel")) {
+ PunishmentProcedure.getProcedures().remove(procedure);
+ event.getPlayer().sendMessage(CC.RED + "You have cancelled the punishment procedure.");
+ return;
+ }
+
+ if (procedure.getType() == PunishmentProcedureType.PARDON) {
+ new ConfirmMenu(CC.YELLOW + "Pardon this punishment?", new TypeCallback() {
+ @Override
+ public void callback(Boolean data) {
+ if (data) {
+ procedure.getPunishment().setRemovedBy(event.getPlayer().getUniqueId());
+ procedure.getPunishment().setRemovedAt(System.currentTimeMillis());
+ procedure.getPunishment().setRemovedReason(event.getMessage());
+ procedure.getPunishment().setRemoved(true);
+ procedure.finish();
+
+ event.getPlayer().sendMessage(CC.GREEN + "The punishment has been removed.");
+ } else {
+ procedure.cancel();
+ event.getPlayer().sendMessage(CC.RED + "You did not confirm to pardon the punishment.");
+ }
+ }
+ }, true) {
+ @Override
+ public void onClose(Player player) {
+ if (!isClosedByMenu()) {
+ procedure.cancel();
+ event.getPlayer().sendMessage(CC.RED + "You did not confirm to pardon the punishment.");
+ }
+ }
+ }.openMenu(event.getPlayer());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/menu/PunishmentsListMenu.java b/src/main/java/com/minexd/zoot/profile/punishment/menu/PunishmentsListMenu.java
new file mode 100644
index 0000000..e97d7aa
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/menu/PunishmentsListMenu.java
@@ -0,0 +1,177 @@
+package com.minexd.zoot.profile.punishment.menu;
+
+import com.minexd.zoot.cache.RedisPlayerData;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.menu.ProfileMenuControlHeaderButton;
+import com.minexd.zoot.profile.punishment.PunishmentType;
+import com.minexd.zoot.profile.punishment.procedure.PunishmentProcedure;
+import com.minexd.zoot.profile.punishment.procedure.PunishmentProcedureStage;
+import com.minexd.zoot.profile.punishment.procedure.PunishmentProcedureType;
+import com.minexd.zoot.util.menu.Button;
+import com.minexd.zoot.profile.punishment.Punishment;
+import com.minexd.zoot.util.ItemBuilder;
+import com.minexd.zoot.util.TimeUtil;
+import com.minexd.zoot.util.CC;
+import com.minexd.zoot.util.menu.pagination.PageFilter;
+import com.minexd.zoot.util.menu.pagination.FilterablePaginatedMenu;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.AllArgsConstructor;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+@AllArgsConstructor
+public class PunishmentsListMenu extends FilterablePaginatedMenu {
+
+ private Profile profile;
+ private RedisPlayerData redisPlayerData;
+
+ @Override
+ public String getPrePaginatedTitle(Player player) {
+ return "&c" + profile.getUsername() + "'s Punishments (" + getCountOfFilteredPunishments() + ")";
+ }
+
+ @Override
+ public Map getFilteredButtons(Player player) {
+ Map buttons = new HashMap<>();
+
+ obj:
+ for (Punishment punishment : profile.getPunishments()) {
+ for (PageFilter filter : getFilters()) {
+ if (!filter.test(punishment)) {
+ continue obj;
+ }
+ }
+
+ buttons.put(buttons.size(), new PunishmentInfoButton(punishment));
+ }
+
+ return buttons;
+ }
+
+ @Override
+ public Map getGlobalButtons(Player player) {
+ Map buttons = super.getGlobalButtons(player);
+
+ if (profile.getUsername() != null) {
+ buttons.put(4, new ProfileMenuControlHeaderButton(profile, redisPlayerData));
+ }
+
+ return buttons;
+ }
+
+ @Override
+ public List> generateFilters() {
+ List> filters = new ArrayList<>();
+ filters.add(new PageFilter<>("Type: Ban", (punishment -> punishment.getType() == PunishmentType.BAN)));
+ filters.add(new PageFilter<>("Type: Mute", (punishment -> punishment.getType() == PunishmentType.MUTE)));
+ filters.add(new PageFilter<>("Type: Warn", (punishment -> punishment.getType() == PunishmentType.WARN)));
+ filters.add(new PageFilter<>("Type: Kick", (punishment -> punishment.getType() == PunishmentType.KICK)));
+ filters.add(new PageFilter<>("Issued By Console", (punishment -> punishment.getAddedBy() == null)));
+ filters.add(new PageFilter<>("Removed", Punishment::isRemoved));
+ return filters;
+ }
+
+ private int getCountOfFilteredPunishments() {
+ int i = 0;
+
+ obj:
+ for (Punishment punishment : profile.getPunishments()) {
+ for (PageFilter filter : getFilters()) {
+ if (!filter.test(punishment)) {
+ continue obj;
+ }
+ }
+
+ i++;
+ }
+
+ return i;
+ }
+
+ @AllArgsConstructor
+ private class PunishmentInfoButton extends Button {
+
+ private Punishment punishment;
+
+ @Override
+ public ItemStack getButtonItem(Player player) {
+ int durability;
+
+ if (punishment.isRemoved()) {
+ durability = 5;
+ } else if (punishment.hasExpired()) {
+ durability = 4;
+ } else {
+ durability = 14;
+ }
+
+ String addedBy = "Console";
+
+ if (punishment.getAddedBy() != null) {
+ try {
+ Profile addedByProfile = Profile.getByUuid(punishment.getAddedBy());
+ addedBy = addedByProfile.getUsername();
+ } catch (Exception e) {
+ addedBy = "Could not fetch...";
+ }
+ }
+
+ String removedBy = "Console";
+
+ if (punishment.getRemovedBy() != null) {
+ try {
+ Profile removedByProfile = Profile.getByUuid(punishment.getRemovedBy());
+ removedBy = removedByProfile.getUsername();
+ } catch (Exception e) {
+ removedBy = "Could not fetch...";
+ }
+ }
+
+ List lore = new ArrayList<>();
+ lore.add(CC.MENU_BAR);
+ lore.add("&3Type: &e" + punishment.getType().getReadable());
+ lore.add("&3Duration: &e" + punishment.getDurationText());
+ lore.add("&3Issued by: &e" + addedBy);
+ lore.add("&3Reason: &e&o\"" + punishment.getAddedReason() + "\"");
+
+ if (punishment.isRemoved()) {
+ lore.add(CC.MENU_BAR);
+ lore.add("&a&lPunishment Removed");
+ lore.add("&a" + TimeUtil.dateToString(new Date(punishment.getRemovedAt()), "&7"));
+ lore.add("&aRemoved by: &7" + removedBy);
+ lore.add("&aReason: &7&o\"" + punishment.getRemovedReason() + "\"");
+ } else {
+ if (!punishment.hasExpired() && punishment.getType().isRemovable()) {
+ lore.add(CC.MENU_BAR);
+ lore.add("&aRight click to remove this punishment");
+ }
+ }
+
+ lore.add(CC.MENU_BAR);
+
+ return new ItemBuilder(Material.STAINED_GLASS_PANE)
+ .durability(durability)
+ .name("&3" + TimeUtil.dateToString(new Date(punishment.getAddedAt()), "&7"))
+ .lore(lore)
+ .build();
+ }
+
+ @Override
+ public void clicked(Player player, ClickType clickType) {
+ if (clickType == ClickType.RIGHT && !punishment.isRemoved() && !punishment.hasExpired() && punishment.getType().isRemovable()) {
+ PunishmentProcedure procedure = new PunishmentProcedure(player, profile, PunishmentProcedureType.PARDON, PunishmentProcedureStage.REQUIRE_TEXT);
+ procedure.setPunishment(punishment);
+
+ player.sendMessage(CC.GREEN + "Enter a reason for removing this punishment.");
+ player.closeInventory();
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedure.java b/src/main/java/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedure.java
new file mode 100644
index 0000000..360fbc6
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedure.java
@@ -0,0 +1,51 @@
+package com.minexd.zoot.profile.punishment.procedure;
+
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.profile.punishment.Punishment;
+import java.util.HashSet;
+import java.util.Set;
+import lombok.Getter;
+import lombok.Setter;
+import org.bukkit.entity.Player;
+
+public class PunishmentProcedure {
+
+ @Getter private static final Set procedures = new HashSet<>();
+
+ @Getter private final Player issuer;
+ @Getter private final Profile recipient;
+ @Getter private final PunishmentProcedureType type;
+ @Getter private PunishmentProcedureStage stage;
+ @Getter @Setter private Punishment punishment;
+
+ public PunishmentProcedure(Player issuer, Profile recipient, PunishmentProcedureType type, PunishmentProcedureStage stage) {
+ this.issuer = issuer;
+ this.recipient = recipient;
+ this.type = type;
+ this.stage = stage;
+
+ procedures.add(this);
+ }
+
+ public void finish() {
+ this.recipient.save();
+ procedures.remove(this);
+ }
+
+ public void cancel() {
+ procedures.remove(this);
+ }
+
+ public static PunishmentProcedure getByPlayer(Player player) {
+ for (PunishmentProcedure procedure : procedures) {
+ if (procedure.issuer.equals(player)) {
+ return procedure;
+ }
+ }
+
+ return null;
+ }
+
+
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedureStage.java b/src/main/java/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedureStage.java
new file mode 100644
index 0000000..856808b
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedureStage.java
@@ -0,0 +1,9 @@
+package com.minexd.zoot.profile.punishment.procedure;
+
+public enum PunishmentProcedureStage {
+
+ REQUIRE_CLICK,
+ REQUIRE_TEXT,
+ REQUIRE_CONFIRMATION,
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedureType.java b/src/main/java/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedureType.java
new file mode 100644
index 0000000..46c72da
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedureType.java
@@ -0,0 +1,8 @@
+package com.minexd.zoot.profile.punishment.procedure;
+
+public enum PunishmentProcedureType {
+
+ ADD,
+ PARDON
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/staff/ProfileStaffOptions.java b/src/main/java/com/minexd/zoot/profile/staff/ProfileStaffOptions.java
new file mode 100644
index 0000000..b21cc4b
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/staff/ProfileStaffOptions.java
@@ -0,0 +1,13 @@
+package com.minexd.zoot.profile.staff;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.experimental.Accessors;
+
+@Accessors(fluent = true)
+public class ProfileStaffOptions {
+
+ @Getter @Setter private boolean staffModeEnabled = true;
+ @Getter @Setter private boolean staffChatModeEnabled = false;
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/staff/command/AltsCommand.java b/src/main/java/com/minexd/zoot/profile/staff/command/AltsCommand.java
new file mode 100644
index 0000000..a56750e
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/staff/command/AltsCommand.java
@@ -0,0 +1,46 @@
+package com.minexd.zoot.profile.staff.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CPL;
+import com.qrakn.honcho.command.CommandMeta;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.UUID;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = "alts", async = true, permission = "zoot.staff.alts")
+public class AltsCommand {
+
+ public void execute(CommandSender sender, @CPL("player") Profile profile) {
+ if (profile == null || !profile.isLoaded()) {
+ sender.sendMessage(Locale.COULD_NOT_RESOLVE_PLAYER.format());
+ return;
+ }
+
+ List alts = new ArrayList<>();
+
+ for (UUID altUuid : profile.getKnownAlts()) {
+ Profile altProfile = Profile.getByUuid(altUuid);
+
+ if (altProfile != null && altProfile.isLoaded()) {
+ alts.add(altProfile);
+ }
+ }
+
+ if (alts.isEmpty()) {
+ sender.sendMessage(CC.RED + "This player has no known alt accounts.");
+ } else {
+ StringBuilder builder = new StringBuilder();
+
+ for (Profile altProfile : alts) {
+ builder.append(altProfile.getUsername());
+ builder.append(", ");
+ }
+
+ sender.sendMessage(CC.GOLD + "Alts: " + CC.RESET + builder.toString());
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/staff/command/StaffChatCommand.java b/src/main/java/com/minexd/zoot/profile/staff/command/StaffChatCommand.java
new file mode 100644
index 0000000..e7ab531
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/staff/command/StaffChatCommand.java
@@ -0,0 +1,33 @@
+package com.minexd.zoot.profile.staff.command;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.network.packet.PacketStaffChat;
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = { "staffchat", "sc" }, permission = "zoot.staff")
+public class StaffChatCommand {
+
+ public void execute(Player player) {
+ Profile profile = Profile.getProfiles().get(player.getUniqueId());
+ profile.getStaffOptions().staffChatModeEnabled(!profile.getStaffOptions().staffChatModeEnabled());
+
+ player.sendMessage(profile.getStaffOptions().staffChatModeEnabled() ?
+ CC.GREEN + "You are now talking in staff chat." : CC.RED + "You are no longer talking in staff chat.");
+ }
+
+ public void execute(Player player, String message) {
+ Profile profile = Profile.getProfiles().get(player.getUniqueId());
+
+ if (!profile.getStaffOptions().staffModeEnabled()) {
+ player.sendMessage(CC.RED + "You are not in staff mode.");
+ return;
+ }
+
+ Zoot.get().getPidgin().sendPacket(new PacketStaffChat(player.getDisplayName(),
+ Zoot.get().getMainConfig().getString("SERVER_NAME"), message));
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/profile/staff/command/StaffModeCommand.java b/src/main/java/com/minexd/zoot/profile/staff/command/StaffModeCommand.java
new file mode 100644
index 0000000..c0a0dec
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/profile/staff/command/StaffModeCommand.java
@@ -0,0 +1,19 @@
+package com.minexd.zoot.profile.staff.command;
+
+import com.minexd.zoot.profile.Profile;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = { "staffmode", "sm" }, permission = "zoot.staff")
+public class StaffModeCommand {
+
+ public void execute(Player player) {
+ Profile profile = Profile.getByUuid(player.getUniqueId());
+ profile.getStaffOptions().staffModeEnabled(!profile.getStaffOptions().staffModeEnabled());
+
+ player.sendMessage(profile.getStaffOptions().staffModeEnabled() ?
+ CC.GREEN + "You are now in staff mode." : CC.RED + "You are no longer in staff mode.");
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/rank/Rank.java b/src/main/java/com/minexd/zoot/rank/Rank.java
new file mode 100644
index 0000000..3a298cf
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/rank/Rank.java
@@ -0,0 +1,265 @@
+package com.minexd.zoot.rank;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.network.packet.PacketDeleteRank;
+import com.minexd.zoot.network.packet.PacketRefreshRank;
+import com.minexd.zoot.util.CC;
+import com.mongodb.client.MongoCollection;
+import com.mongodb.client.MongoCursor;
+import com.mongodb.client.model.Filters;
+import com.mongodb.client.model.ReplaceOptions;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.UUID;
+import java.util.stream.Collectors;
+import lombok.Getter;
+import lombok.Setter;
+import org.bson.Document;
+import org.bukkit.ChatColor;
+
+public class Rank {
+
+ @Getter private static Map ranks = new HashMap<>();
+ private static MongoCollection collection;
+
+ @Getter private final UUID uuid;
+ @Getter private final String displayName;
+ @Getter @Setter private String prefix = "";
+ @Getter @Setter private String suffix = "";
+ @Getter @Setter private String color = "";
+ @Getter @Setter private int weight;
+ @Setter private boolean defaultRank;
+ @Getter private final List permissions = new ArrayList<>();
+ @Getter private final List inherited = new ArrayList<>();
+
+ public Rank(String displayName) {
+ this.uuid = UUID.randomUUID();
+ this.displayName = displayName;
+
+ ranks.put(uuid, this);
+ }
+
+ public Rank(UUID uuid, String displayName) {
+ this.uuid = uuid;
+ this.displayName = displayName;
+
+ ranks.put(uuid, this);
+ }
+
+ public Rank(UUID uuid, String displayName, String prefix, String suffix, String color, int weight,
+ boolean defaultRank) {
+ this.uuid = uuid;
+ this.displayName = displayName;
+ this.prefix = prefix;
+ this.suffix = suffix;
+ this.color = color;
+ this.weight = weight;
+ this.defaultRank = defaultRank;
+
+ ranks.put(uuid, this);
+ }
+
+ public boolean isDefaultRank() {
+ return defaultRank;
+ }
+
+ public boolean addPermission(String permission) {
+ if (!permissions.contains(permission)) {
+ permissions.add(permission);
+ return true;
+ }
+
+ return false;
+ }
+
+ public boolean removePermission(String permission) {
+ return permissions.remove(permission);
+ }
+
+ public boolean hasPermission(String permission) {
+ if (permissions.contains(permission)) {
+ return true;
+ }
+
+ for (Rank rank : inherited) {
+ if (rank.hasPermission(permission)) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ public boolean canInherit(Rank rankToCheck) {
+ if (inherited.contains(rankToCheck) || rankToCheck.inherited.contains(this)) {
+ return false;
+ }
+
+ for (Rank rank : inherited) {
+ if (!rank.canInherit(rankToCheck)) {
+ return false;
+ }
+ }
+
+ return true;
+ }
+
+ public List getAllPermissions() {
+ List permissions = new ArrayList<>();
+ permissions.addAll(this.permissions);
+
+ for (Rank rank : inherited) {
+ permissions.addAll(rank.getAllPermissions());
+ }
+
+ return permissions;
+ }
+
+ public void load() {
+ load(collection.find(Filters.eq("uuid", uuid.toString())).first());
+ }
+
+ private void load(Document document) {
+ if (document == null) {
+ return;
+ }
+
+ prefix = ChatColor.translateAlternateColorCodes('&', document.getString("prefix"));
+ suffix = ChatColor.translateAlternateColorCodes('&', document.getString("suffix"));
+ color = ChatColor.translateAlternateColorCodes('&', document.getString("color"));
+ weight = document.getInteger("weight");
+ defaultRank = document.getBoolean("defaultRank");
+
+ inherited.clear();
+
+ Zoot.GSON.>fromJson(document.getString("inherits"), Zoot.LIST_STRING_TYPE)
+ .stream()
+ .map(s -> Rank.getRankByUuid(UUID.fromString(s)))
+ .filter(Objects::nonNull)
+ .forEach(inherited::add);
+ }
+
+ public void save() {
+ Document document = new Document();
+ document.put("uuid", uuid.toString());
+ document.put("displayName", displayName);
+ document.put("prefix", prefix.replace(String.valueOf(ChatColor.COLOR_CHAR), "&"));
+ document.put("suffix", suffix.replace(String.valueOf(ChatColor.COLOR_CHAR), "&"));
+ document.put("color", color.replace(String.valueOf(ChatColor.COLOR_CHAR), "&"));
+ document.put("weight", weight);
+ document.put("defaultRank", defaultRank);
+ document.put("permissions", Zoot.GSON.toJson(permissions));
+ document.put("inherits", Zoot.GSON.toJson(inherited
+ .stream()
+ .map(Rank::getUuid)
+ .map(UUID::toString)
+ .collect(Collectors.toList())));
+
+ collection.replaceOne(Filters.eq("uuid", uuid.toString()), document,
+ new ReplaceOptions().upsert(true));
+
+ Zoot.get().getPidgin().sendPacket(new PacketRefreshRank(uuid, displayName));
+ }
+
+ public void delete() {
+ ranks.remove(uuid);
+ collection.deleteOne(Filters.eq("uuid", uuid.toString()));
+
+ Zoot.get().getPidgin().sendPacket(new PacketDeleteRank(uuid));
+ }
+
+ public static void init() {
+ collection = Zoot.get().getMongoDatabase().getCollection("ranks");
+
+ Map> inheritanceReferences = new HashMap<>();
+
+ try (MongoCursor cursor = collection.find().iterator()) {
+ while (cursor.hasNext()) {
+ Document document = cursor.next();
+
+ Rank rank = new Rank(UUID.fromString(document.getString("uuid")),
+ document.getString("displayName"));
+ rank.load(document);
+
+ Zoot.GSON.>fromJson(document.getString("permissions"), Zoot.LIST_STRING_TYPE)
+ .forEach(perm -> rank.getPermissions().add(perm));
+
+ List ranksToInherit = new ArrayList<>();
+
+ Zoot.GSON.>fromJson(document.getString("inherits"), Zoot.LIST_STRING_TYPE)
+ .stream()
+ .map(UUID::fromString)
+ .forEach(ranksToInherit::add);
+
+ inheritanceReferences.put(rank, ranksToInherit);
+
+ ranks.put(rank.getUuid(), rank);
+ }
+ }
+
+ inheritanceReferences.forEach((rank, list) -> {
+ list.forEach(uuid -> {
+ Rank inherited = ranks.get(uuid);
+
+ if (inherited != null) {
+ rank.getInherited().add(inherited);
+ }
+ });
+ });
+
+ getDefaultRank();
+ }
+
+ /**
+ * Retrieves a rank by UUID if one exists.
+ *
+ * @param uuid The UUID.
+ *
+ * @return A rank that matches the given UUID if found.
+ */
+ public static Rank getRankByUuid(UUID uuid) {
+ return ranks.get(uuid);
+ }
+
+ /**
+ * Retrieves a rank by name if one exists.
+ *
+ * @param name The name.
+ *
+ * @return A rank that matches the given name if found.
+ */
+ public static Rank getRankByDisplayName(String name) {
+ for (Rank rank : ranks.values()) {
+ if (rank.getDisplayName().equalsIgnoreCase(name)) {
+ return rank;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Retrieves the default rank or creates a new default rank if one does not already exist.
+ *
+ * @return A default rank, or a new default rank if unavailable.
+ */
+ public static Rank getDefaultRank() {
+ for (Rank rank : ranks.values()) {
+ if (rank.isDefaultRank()) {
+ return rank;
+ }
+ }
+
+ Rank defaultRank = new Rank("Default");
+ defaultRank.setDefaultRank(true);
+ defaultRank.save();
+
+ ranks.put(defaultRank.getUuid(), defaultRank);
+
+ return defaultRank;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/rank/RankTypeAdapter.java b/src/main/java/com/minexd/zoot/rank/RankTypeAdapter.java
new file mode 100644
index 0000000..f944934
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/rank/RankTypeAdapter.java
@@ -0,0 +1,27 @@
+package com.minexd.zoot.rank;
+
+import com.qrakn.honcho.command.adapter.CommandTypeAdapter;
+import java.util.ArrayList;
+import java.util.List;
+
+public class RankTypeAdapter implements CommandTypeAdapter {
+
+ @Override
+ public T convert(String string, Class type) {
+ return type.cast(Rank.getRankByDisplayName(string));
+ }
+
+ @Override
+ public List tabComplete(String string, Class type) {
+ List completed = new ArrayList<>();
+
+ for (Rank rank : Rank.getRanks().values()) {
+ if (rank.getDisplayName().toLowerCase().startsWith(string)) {
+ completed.add(rank.getDisplayName());
+ }
+ }
+
+ return completed;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/rank/command/RankAddPermissionCommand.java b/src/main/java/com/minexd/zoot/rank/command/RankAddPermissionCommand.java
new file mode 100644
index 0000000..71fa9a7
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/rank/command/RankAddPermissionCommand.java
@@ -0,0 +1,20 @@
+package com.minexd.zoot.rank.command;
+
+import com.minexd.zoot.rank.Rank;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = { "rank addpermission", "rank addperm" }, permission = "zoot.admin.rank", async = true)
+public class RankAddPermissionCommand {
+
+ public void execute(CommandSender sender, Rank rank, String permission) {
+ if (!rank.addPermission(permission)) {
+ sender.sendMessage(CC.RED + "That rank already has that permission.");
+ } else {
+ rank.save();
+ sender.sendMessage(CC.GREEN + "Successfully added permission to rank.");
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/rank/command/RankCreateCommand.java b/src/main/java/com/minexd/zoot/rank/command/RankCreateCommand.java
new file mode 100644
index 0000000..fdcdd35
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/rank/command/RankCreateCommand.java
@@ -0,0 +1,23 @@
+package com.minexd.zoot.rank.command;
+
+import com.minexd.zoot.rank.Rank;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = "rank create", permission = "zoot.admin.rank", async = true)
+public class RankCreateCommand {
+
+ public void execute(CommandSender sender, String name) {
+ if (Rank.getRankByDisplayName(name) != null) {
+ sender.sendMessage(CC.RED + "A rank with that name already exists.");
+ return;
+ }
+
+ Rank rank = new Rank(name);
+ rank.save();
+
+ sender.sendMessage(CC.GREEN + "You created a new rank.");
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/rank/command/RankDeleteCommand.java b/src/main/java/com/minexd/zoot/rank/command/RankDeleteCommand.java
new file mode 100644
index 0000000..ebee733
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/rank/command/RankDeleteCommand.java
@@ -0,0 +1,23 @@
+package com.minexd.zoot.rank.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.rank.Rank;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = "rank delete", permission = "zoot.admin.rank", async = true)
+public class RankDeleteCommand {
+
+ public void execute(CommandSender sender, Rank rank) {
+ if (rank == null) {
+ sender.sendMessage(Locale.RANK_NOT_FOUND.format());
+ return;
+ }
+
+ rank.delete();
+
+ sender.sendMessage(CC.GREEN + "You deleted the rank.");
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/rank/command/RankHelpCommand.java b/src/main/java/com/minexd/zoot/rank/command/RankHelpCommand.java
new file mode 100644
index 0000000..5938368
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/rank/command/RankHelpCommand.java
@@ -0,0 +1,39 @@
+package com.minexd.zoot.rank.command;
+
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = { "rank", "rank help" }, permission = "zoot.admin.rank")
+public class RankHelpCommand {
+
+ private static final String[][] HELP;
+
+ static {
+ HELP = new String[][]{
+ new String[]{ "ranks", "List all existing ranks" },
+ new String[]{ "rank create ", "Create a new rank" },
+ new String[]{ "rank delete ", "Delete an existing rank" },
+ new String[]{ "rank setcolor ", "Set a rank's color" },
+ new String[]{ "rank setprefix ", "Set a rank's prefix" },
+ new String[]{ "rank setsuffix ", "Set a rank's suffix" },
+ new String[]{ "rank setweight ", "Set a rank's weight" },
+ new String[]{ "rank addperm ", "Add a permission to a rank" },
+ new String[]{ "rank delperm ", "Remove a permission from a rank" },
+ new String[]{ "rank inherit ", "Make a parent rank inherit a child rank" },
+ new String[]{ "rank uninherit ", "Make a parent rank uninherit a child rank" }
+ };
+ }
+
+ public void execute(CommandSender sender) {
+ sender.sendMessage(CC.CHAT_BAR);
+ sender.sendMessage(CC.GOLD + "Rank Help");
+
+ for (String[] help : HELP) {
+ sender.sendMessage(CC.BLUE + help[0] + CC.GRAY + " - " + CC.RESET + help[1]);
+ }
+
+ sender.sendMessage(CC.CHAT_BAR);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/rank/command/RankInfoCommand.java b/src/main/java/com/minexd/zoot/rank/command/RankInfoCommand.java
new file mode 100644
index 0000000..6558ba9
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/rank/command/RankInfoCommand.java
@@ -0,0 +1,60 @@
+package com.minexd.zoot.rank.command;
+
+import com.minexd.zoot.Locale;
+import com.minexd.zoot.rank.Rank;
+import com.minexd.zoot.util.CC;
+import com.minexd.zoot.util.TextSplitter;
+import com.qrakn.honcho.command.CommandMeta;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+import org.apache.commons.lang.StringUtils;
+import org.bukkit.ChatColor;
+import org.bukkit.entity.Player;
+
+@CommandMeta(label = "rank info", permission = "zoot.admin.rank", async = true)
+public class RankInfoCommand {
+
+ public void execute(Player player, Rank rank) {
+ if (rank == null) {
+ player.sendMessage(Locale.RANK_NOT_FOUND.format());
+ } else {
+ List toSend = new ArrayList<>();
+ toSend.add(CC.CHAT_BAR);
+ toSend.add(ChatColor.GOLD + "Rank Information " + ChatColor.GRAY + "(" + ChatColor.RESET +
+ rank.getColor() + rank.getDisplayName() + ChatColor.GRAY + ")");
+
+ toSend.add(ChatColor.GRAY + "Weight: " + ChatColor.RESET + rank.getWeight());
+ toSend.add(ChatColor.GRAY + "Prefix: " + ChatColor.RESET + rank.getPrefix() + "Example");
+ toSend.add(ChatColor.GRAY + "Suffix: " + ChatColor.RESET + rank.getSuffix() + "Example");
+
+ List permissions = rank.getAllPermissions();
+
+ toSend.add("");
+ toSend.add(ChatColor.GRAY + "Permissions: " + ChatColor.RESET + "(" + permissions.size() + ")");
+
+ if (!permissions.isEmpty()) {
+ toSend.addAll(TextSplitter.split(46, StringUtils.join(permissions, " "), "", ", "));
+ }
+
+ List inherited = rank.getInherited();
+
+ toSend.add("");
+ toSend.add(ChatColor.GRAY + "Inherits: " + ChatColor.RESET + "(" + inherited.size() + ")");
+
+ if (!rank.getInherited().isEmpty()) {
+ toSend.addAll(rank.getInherited()
+ .stream()
+ .map(inheritedRank -> inheritedRank.getColor() + inheritedRank.getDisplayName())
+ .collect(Collectors.toList()));
+ }
+
+ toSend.add(CC.CHAT_BAR);
+
+ for (String line : toSend) {
+ player.sendMessage(line);
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/rank/command/RankInheritCommand.java b/src/main/java/com/minexd/zoot/rank/command/RankInheritCommand.java
new file mode 100644
index 0000000..d5094bd
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/rank/command/RankInheritCommand.java
@@ -0,0 +1,32 @@
+package com.minexd.zoot.rank.command;
+
+import com.minexd.zoot.rank.Rank;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = "rank inherit", permission = "zoot.admin.rank", async = true)
+public class RankInheritCommand {
+
+ public void execute(CommandSender sender, Rank parent, Rank child) {
+ if (parent == null) {
+ sender.sendMessage(ChatColor.RED + "A rank with that name does not exist (parent).");
+ return;
+ }
+
+ if (child == null) {
+ sender.sendMessage(ChatColor.RED + "A rank with that name does not exist (child).");
+ return;
+ }
+
+ if (parent.canInherit(child)) {
+ parent.getInherited().add(child);
+ parent.save();
+ sender.sendMessage(ChatColor.GREEN + "You made the parent rank " + parent.getDisplayName() +
+ " inherit the child rank " + child.getDisplayName() + ".");
+ } else {
+ sender.sendMessage(ChatColor.RED + "That parent rank cannot inherit that child rank.");
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/rank/command/RankRemovePermissionCommand.java b/src/main/java/com/minexd/zoot/rank/command/RankRemovePermissionCommand.java
new file mode 100644
index 0000000..f174ed1
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/rank/command/RankRemovePermissionCommand.java
@@ -0,0 +1,22 @@
+package com.minexd.zoot.rank.command;
+
+import com.minexd.zoot.rank.Rank;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = { "rank removepermission", "rank removeperm", "rank deleteperm", "rank delperm" },
+ permission = "zoot.admin.rank",
+ async = true)
+public class RankRemovePermissionCommand {
+
+ public void execute(CommandSender sender, Rank rank, String permission) {
+ if (!rank.removePermission(permission)) {
+ sender.sendMessage(CC.RED + "That rank does not have that permission.");
+ } else {
+ rank.save();
+ sender.sendMessage(CC.GREEN + "Successfully removed permission from rank.");
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/rank/command/RankSetColorCommand.java b/src/main/java/com/minexd/zoot/rank/command/RankSetColorCommand.java
new file mode 100644
index 0000000..b8e42a3
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/rank/command/RankSetColorCommand.java
@@ -0,0 +1,29 @@
+package com.minexd.zoot.rank.command;
+
+import com.minexd.zoot.rank.Rank;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = "rank setcolor", permission = "zoot.admin.rank", async = true)
+public class RankSetColorCommand {
+
+ public void execute(CommandSender sender, Rank rank, String color) {
+ if (rank == null) {
+ sender.sendMessage(CC.RED + "A rank with that name does not exist.");
+ return;
+ }
+
+ if (color == null) {
+ sender.sendMessage(CC.RED + "That color is not valid.");
+ return;
+ }
+
+ rank.setColor(CC.translate(color));
+ rank.save();
+
+ sender.sendMessage(CC.GREEN + "You updated the rank's color.");
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/rank/command/RankSetPrefixCommand.java b/src/main/java/com/minexd/zoot/rank/command/RankSetPrefixCommand.java
new file mode 100644
index 0000000..d18e316
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/rank/command/RankSetPrefixCommand.java
@@ -0,0 +1,23 @@
+package com.minexd.zoot.rank.command;
+
+import com.minexd.zoot.rank.Rank;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = "rank setprefix", permission = "zoot.admin.rank", async = true)
+public class RankSetPrefixCommand {
+
+ public void execute(CommandSender sender, Rank rank, String prefix) {
+ if (rank == null) {
+ sender.sendMessage(CC.RED + "A rank with that name does not exist.");
+ return;
+ }
+
+ rank.setPrefix(CC.translate(prefix));
+ rank.save();
+
+ sender.sendMessage(CC.GREEN + "You updated the rank's prefix.");
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/rank/command/RankSetSuffixCommand.java b/src/main/java/com/minexd/zoot/rank/command/RankSetSuffixCommand.java
new file mode 100644
index 0000000..3fbcffb
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/rank/command/RankSetSuffixCommand.java
@@ -0,0 +1,23 @@
+package com.minexd.zoot.rank.command;
+
+import com.minexd.zoot.rank.Rank;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = "rank setsuffix", permission = "zoot.admin.rank", async = true)
+public class RankSetSuffixCommand {
+
+ public void execute(CommandSender sender, Rank rank, String suffix) {
+ if (rank == null) {
+ sender.sendMessage(CC.RED + "A rank with that name does not exist.");
+ return;
+ }
+
+ rank.setSuffix(CC.translate(suffix));
+ rank.save();
+
+ sender.sendMessage(CC.GREEN + "You updated the rank's suffix.");
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/rank/command/RankSetWeightCommand.java b/src/main/java/com/minexd/zoot/rank/command/RankSetWeightCommand.java
new file mode 100644
index 0000000..bfa5aef
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/rank/command/RankSetWeightCommand.java
@@ -0,0 +1,30 @@
+package com.minexd.zoot.rank.command;
+
+import com.minexd.zoot.rank.Rank;
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = "rank setweight", permission = "zoot.admin.rank", async = true)
+public class RankSetWeightCommand {
+
+ public void execute(CommandSender sender, Rank rank, String weight) {
+ if (rank == null) {
+ sender.sendMessage(CC.RED + "A rank with that name does not exist.");
+ return;
+ }
+
+ try {
+ Integer.parseInt(weight);
+ } catch (Exception e) {
+ sender.sendMessage(CC.RED + "Invalid number.");
+ return;
+ }
+
+ rank.setWeight(Integer.parseInt(weight));
+ rank.save();
+
+ sender.sendMessage(CC.GREEN + "You updated the rank's weight.");
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/rank/command/RankUninheritCommand.java b/src/main/java/com/minexd/zoot/rank/command/RankUninheritCommand.java
new file mode 100644
index 0000000..4d4fc43
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/rank/command/RankUninheritCommand.java
@@ -0,0 +1,31 @@
+package com.minexd.zoot.rank.command;
+
+import com.minexd.zoot.rank.Rank;
+import com.qrakn.honcho.command.CommandMeta;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = "rank uninherit", permission = "zoot.admin.rank", async = true)
+public class RankUninheritCommand {
+
+ public void execute(CommandSender sender, Rank parent, Rank child) {
+ if (parent == null) {
+ sender.sendMessage(ChatColor.RED + "A rank with that name does not exist (parent).");
+ return;
+ }
+
+ if (child == null) {
+ sender.sendMessage(ChatColor.RED + "A rank with that name does not exist (child).");
+ return;
+ }
+
+ if (parent.getInherited().remove(child)) {
+ parent.save();
+ sender.sendMessage(ChatColor.GREEN + "You made the parent rank " + parent.getDisplayName() +
+ " uninherit the child rank " + child.getDisplayName() + ".");
+ } else {
+ sender.sendMessage(ChatColor.RED + "That parent rank does not inherit that child rank.");
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/rank/command/RanksCommand.java b/src/main/java/com/minexd/zoot/rank/command/RanksCommand.java
new file mode 100644
index 0000000..b94a09f
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/rank/command/RanksCommand.java
@@ -0,0 +1,31 @@
+package com.minexd.zoot.rank.command;
+
+import com.minexd.zoot.rank.Rank;
+import com.qrakn.honcho.command.CommandMeta;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.List;
+import org.bukkit.ChatColor;
+import org.bukkit.command.CommandSender;
+
+@CommandMeta(label = "ranks", permission = "zoot.admin.rank")
+public class RanksCommand {
+
+ public void execute(CommandSender sender) {
+ List ranks = new ArrayList<>(Rank.getRanks().values());
+ ranks.sort(new Comparator() {
+ @Override
+ public int compare(Rank o1, Rank o2) {
+ return o2.getWeight() - o1.getWeight();
+ }
+ });
+
+ sender.sendMessage(ChatColor.GOLD + ChatColor.BOLD.toString() + "Ranks");
+
+ for (Rank rank : ranks) {
+ sender.sendMessage(ChatColor.GRAY + " - " + ChatColor.RESET + rank.getColor() + rank.getDisplayName() +
+ ChatColor.RESET + " (Weight: " + rank.getWeight() + ")");
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/BaseEvent.java b/src/main/java/com/minexd/zoot/util/BaseEvent.java
new file mode 100644
index 0000000..2dbf7c0
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/BaseEvent.java
@@ -0,0 +1,23 @@
+package com.minexd.zoot.util;
+
+import com.minexd.zoot.Zoot;
+import org.bukkit.event.Event;
+import org.bukkit.event.HandlerList;
+
+public class BaseEvent extends Event {
+
+ private static final HandlerList handlers = new HandlerList();
+
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+ public void call() {
+ Zoot.get().getServer().getPluginManager().callEvent(this);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/BukkitReflection.java b/src/main/java/com/minexd/zoot/util/BukkitReflection.java
new file mode 100644
index 0000000..9b50c82
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/BukkitReflection.java
@@ -0,0 +1,112 @@
+package com.minexd.zoot.util;
+
+import java.lang.reflect.Field;
+import java.lang.reflect.Method;
+import org.bukkit.Bukkit;
+import org.bukkit.Server;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.ItemStack;
+
+public class BukkitReflection {
+
+ private static final String CRAFT_BUKKIT_PACKAGE;
+ private static final String NET_MINECRAFT_SERVER_PACKAGE;
+
+ private static final Class CRAFT_SERVER_CLASS;
+ private static final Method CRAFT_SERVER_GET_HANDLE_METHOD;
+
+ private static final Class PLAYER_LIST_CLASS;
+ private static final Field PLAYER_LIST_MAX_PLAYERS_FIELD;
+
+ private static final Class CRAFT_PLAYER_CLASS;
+ private static final Method CRAFT_PLAYER_GET_HANDLE_METHOD;
+
+ private static final Class ENTITY_PLAYER_CLASS;
+ private static final Field ENTITY_PLAYER_PING_FIELD;
+
+ private static final Class CRAFT_ITEM_STACK_CLASS;
+ private static final Method CRAFT_ITEM_STACK_AS_NMS_COPY_METHOD;
+ private static final Class ENTITY_ITEM_STACK_CLASS;
+ private static final Method ENTITY_ITEM_STACK_GET_NAME;
+
+ private static final Class SPIGOT_CONFIG_CLASS;
+ private static final Field SPIGOT_CONFIG_BUNGEE_FIELD;
+
+ static {
+ try {
+ String version = Bukkit.getServer().getClass().getPackage().getName().replace(".", ",").split(",")[3];
+
+ CRAFT_BUKKIT_PACKAGE = "org.bukkit.craftbukkit." + version + ".";
+ NET_MINECRAFT_SERVER_PACKAGE = "net.minecraft.server." + version + ".";
+
+ CRAFT_SERVER_CLASS = Class.forName(CRAFT_BUKKIT_PACKAGE + "CraftServer");
+ CRAFT_SERVER_GET_HANDLE_METHOD = CRAFT_SERVER_CLASS.getDeclaredMethod("getHandle");
+ CRAFT_SERVER_GET_HANDLE_METHOD.setAccessible(true);
+
+ PLAYER_LIST_CLASS = Class.forName(NET_MINECRAFT_SERVER_PACKAGE + "PlayerList");
+ PLAYER_LIST_MAX_PLAYERS_FIELD = PLAYER_LIST_CLASS.getDeclaredField("maxPlayers");
+ PLAYER_LIST_MAX_PLAYERS_FIELD.setAccessible(true);
+
+ CRAFT_PLAYER_CLASS = Class.forName(CRAFT_BUKKIT_PACKAGE + "entity.CraftPlayer");
+ CRAFT_PLAYER_GET_HANDLE_METHOD = CRAFT_PLAYER_CLASS.getDeclaredMethod("getHandle");
+ CRAFT_PLAYER_GET_HANDLE_METHOD.setAccessible(true);
+
+ ENTITY_PLAYER_CLASS = Class.forName(NET_MINECRAFT_SERVER_PACKAGE + "EntityPlayer");
+ ENTITY_PLAYER_PING_FIELD = ENTITY_PLAYER_CLASS.getDeclaredField("ping");
+ ENTITY_PLAYER_PING_FIELD.setAccessible(true);
+
+ CRAFT_ITEM_STACK_CLASS = Class.forName(CRAFT_BUKKIT_PACKAGE + "inventory.CraftItemStack");
+ CRAFT_ITEM_STACK_AS_NMS_COPY_METHOD =
+ CRAFT_ITEM_STACK_CLASS.getDeclaredMethod("asNMSCopy", ItemStack.class);
+ CRAFT_ITEM_STACK_AS_NMS_COPY_METHOD.setAccessible(true);
+
+ ENTITY_ITEM_STACK_CLASS = Class.forName(NET_MINECRAFT_SERVER_PACKAGE + "ItemStack");
+ ENTITY_ITEM_STACK_GET_NAME = ENTITY_ITEM_STACK_CLASS.getDeclaredMethod("getName");
+
+ SPIGOT_CONFIG_CLASS = Class.forName("org.spigotmc.SpigotConfig");
+ SPIGOT_CONFIG_BUNGEE_FIELD = SPIGOT_CONFIG_CLASS.getDeclaredField("bungee");
+ SPIGOT_CONFIG_BUNGEE_FIELD.setAccessible(true);
+ } catch (Exception e) {
+ e.printStackTrace();
+
+ throw new RuntimeException("Failed to initialize Bukkit/NMS Reflection");
+ }
+ }
+
+ public static int getPing(Player player) {
+ try {
+ int ping = ENTITY_PLAYER_PING_FIELD.getInt(CRAFT_PLAYER_GET_HANDLE_METHOD.invoke(player));
+
+ return ping > 0 ? ping : 0;
+ } catch (Exception e) {
+ return 1;
+ }
+ }
+
+ public static void setMaxPlayers(Server server, int slots) {
+ try {
+ PLAYER_LIST_MAX_PLAYERS_FIELD.set(CRAFT_SERVER_GET_HANDLE_METHOD.invoke(server), slots);
+ } catch (Exception e) {
+ e.printStackTrace();
+ }
+ }
+
+ public static String getItemStackName(ItemStack itemStack) {
+ try {
+ return (String) ENTITY_ITEM_STACK_GET_NAME.invoke(CRAFT_ITEM_STACK_AS_NMS_COPY_METHOD.invoke(itemStack, itemStack));
+ } catch (Exception e) {
+ e.printStackTrace();
+ return "";
+ }
+ }
+
+ public static boolean isBungeeServer() {
+ try {
+ return (boolean) SPIGOT_CONFIG_BUNGEE_FIELD.get(null);
+ } catch (Exception e) {
+ e.printStackTrace();
+ return false;
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/CC.java b/src/main/java/com/minexd/zoot/util/CC.java
new file mode 100644
index 0000000..91132ca
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/CC.java
@@ -0,0 +1,123 @@
+package com.minexd.zoot.util;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.bukkit.ChatColor;
+
+public class CC {
+
+ private static final Map MAP;
+
+ public static final String BLUE;
+ public static final String AQUA;
+ public static final String YELLOW;
+ public static final String RED;
+ public static final String GRAY;
+ public static final String GOLD;
+ public static final String GREEN;
+ public static final String WHITE;
+ public static final String BLACK;
+ public static final String BOLD;
+ public static final String ITALIC;
+ public static final String UNDER_LINE;
+ public static final String STRIKE_THROUGH;
+ public static final String RESET;
+ public static final String MAGIC;
+ public static final String DARK_BLUE;
+ public static final String DARK_AQUA;
+ public static final String DARK_GRAY;
+ public static final String DARK_GREEN;
+ public static final String DARK_PURPLE;
+ public static final String DARK_RED;
+ public static final String PINK;
+ public static final String MENU_BAR;
+ public static final String CHAT_BAR;
+ public static final String SB_BAR;
+
+ static {
+ MAP = new HashMap<>();
+ MAP.put("pink", ChatColor.LIGHT_PURPLE);
+ MAP.put("orange", ChatColor.GOLD);
+ MAP.put("purple", ChatColor.DARK_PURPLE);
+
+ for (ChatColor chatColor : ChatColor.values()) {
+ MAP.put(chatColor.name().toLowerCase().replace("_", ""), chatColor);
+ }
+
+ BLUE = ChatColor.BLUE.toString();
+ AQUA = ChatColor.AQUA.toString();
+ YELLOW = ChatColor.YELLOW.toString();
+ RED = ChatColor.RED.toString();
+ GRAY = ChatColor.GRAY.toString();
+ GOLD = ChatColor.GOLD.toString();
+ GREEN = ChatColor.GREEN.toString();
+ WHITE = ChatColor.WHITE.toString();
+ BLACK = ChatColor.BLACK.toString();
+ BOLD = ChatColor.BOLD.toString();
+ ITALIC = ChatColor.ITALIC.toString();
+ UNDER_LINE = ChatColor.UNDERLINE.toString();
+ STRIKE_THROUGH = ChatColor.STRIKETHROUGH.toString();
+ RESET = ChatColor.RESET.toString();
+ MAGIC = ChatColor.MAGIC.toString();
+ DARK_BLUE = ChatColor.DARK_BLUE.toString();
+ DARK_AQUA = ChatColor.DARK_AQUA.toString();
+ DARK_GRAY = ChatColor.DARK_GRAY.toString();
+ DARK_GREEN = ChatColor.DARK_GREEN.toString();
+ DARK_PURPLE = ChatColor.DARK_PURPLE.toString();
+ DARK_RED = ChatColor.DARK_RED.toString();
+ PINK = ChatColor.LIGHT_PURPLE.toString();
+ MENU_BAR = ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH.toString() + "------------------------";
+ CHAT_BAR = ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH.toString() + "------------------------------------------------";
+ SB_BAR = ChatColor.GRAY.toString() + ChatColor.STRIKETHROUGH.toString() + "----------------------";
+ }
+
+ public static Set getColorNames() {
+ return MAP.keySet();
+ }
+
+ public static ChatColor getColorFromName(String name) {
+ if (MAP.containsKey(name.trim().toLowerCase())) {
+ return MAP.get(name.trim().toLowerCase());
+ }
+
+ ChatColor color;
+
+ try {
+ color = ChatColor.valueOf(name.toUpperCase().replace(" ", "_"));
+ } catch (Exception e) {
+ return null;
+ }
+
+ return color;
+ }
+
+ public static String translate(String in) {
+ return ChatColor.translateAlternateColorCodes('&', in);
+ }
+
+ public static List translate(List lines) {
+ List toReturn = new ArrayList<>();
+
+ for (String line : lines) {
+ toReturn.add(ChatColor.translateAlternateColorCodes('&', line));
+ }
+
+ return toReturn;
+ }
+
+ public static List translate(String[] lines) {
+ List toReturn = new ArrayList<>();
+
+ for (String line : lines) {
+ if (line != null) {
+ toReturn.add(ChatColor.translateAlternateColorCodes('&', line));
+ }
+ }
+
+ return toReturn;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/ChatHelper.java b/src/main/java/com/minexd/zoot/util/ChatHelper.java
new file mode 100644
index 0000000..3be01b0
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/ChatHelper.java
@@ -0,0 +1,21 @@
+package com.minexd.zoot.util;
+
+import com.minexd.zoot.chat.util.ChatComponentBuilder;
+import net.md_5.bungee.api.chat.ClickEvent;
+import net.md_5.bungee.api.chat.HoverEvent;
+
+public class ChatHelper {
+
+ public static ClickEvent click(String command) {
+ return new ClickEvent(ClickEvent.Action.RUN_COMMAND, command);
+ }
+
+ public static ClickEvent suggest(String command) {
+ return new ClickEvent(ClickEvent.Action.SUGGEST_COMMAND, command);
+ }
+
+ public static HoverEvent hover(String text) {
+ return new HoverEvent(HoverEvent.Action.SHOW_TEXT, new ChatComponentBuilder("").parse(text).create());
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/Cooldown.java b/src/main/java/com/minexd/zoot/util/Cooldown.java
new file mode 100644
index 0000000..70a40bc
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/Cooldown.java
@@ -0,0 +1,40 @@
+package com.minexd.zoot.util;
+
+import lombok.Data;
+
+@Data
+public class Cooldown {
+
+ private long start = System.currentTimeMillis();
+ private long expire;
+ private boolean notified;
+
+ public Cooldown(long duration) {
+ this.expire = this.start + duration;
+
+ if (duration == 0) {
+ this.notified = true;
+ }
+ }
+
+ public long getPassed() {
+ return System.currentTimeMillis() - this.start;
+ }
+
+ public long getRemaining() {
+ return this.expire - System.currentTimeMillis();
+ }
+
+ public boolean hasExpired() {
+ return System.currentTimeMillis() - this.expire >= 0;
+ }
+
+ public String getTimeLeft() {
+ if (this.getRemaining() >= 60_000) {
+ return TimeUtil.millisToRoundedTime(this.getRemaining());
+ } else {
+ return TimeUtil.millisToSeconds(this.getRemaining());
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/ItemBuilder.java b/src/main/java/com/minexd/zoot/util/ItemBuilder.java
new file mode 100644
index 0000000..d926f6c
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/ItemBuilder.java
@@ -0,0 +1,121 @@
+package com.minexd.zoot.util;
+
+import java.util.ArrayList;
+import java.util.List;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.enchantments.Enchantment;
+import org.bukkit.event.Listener;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+public class ItemBuilder implements Listener {
+
+ private ItemStack is;
+
+ public ItemBuilder(Material mat) {
+ is = new ItemStack(mat);
+ }
+
+ public ItemBuilder(ItemStack is) {
+ this.is = is;
+ }
+
+ public ItemBuilder amount(int amount) {
+ is.setAmount(amount);
+ return this;
+ }
+
+ public ItemBuilder name(String name) {
+ ItemMeta meta = is.getItemMeta();
+ meta.setDisplayName(ChatColor.translateAlternateColorCodes('&', name));
+ is.setItemMeta(meta);
+ return this;
+ }
+
+ public ItemBuilder lore(String name) {
+ ItemMeta meta = is.getItemMeta();
+ List lore = meta.getLore();
+
+ if (lore == null) {
+ lore = new ArrayList<>();
+ }
+
+ lore.add(ChatColor.translateAlternateColorCodes('&', name));
+ meta.setLore(lore);
+
+ is.setItemMeta(meta);
+
+ return this;
+ }
+
+ public ItemBuilder lore(String... lore) {
+ List toSet = new ArrayList<>();
+ ItemMeta meta = is.getItemMeta();
+
+ for (String string : lore) {
+ toSet.add(ChatColor.translateAlternateColorCodes('&', string));
+ }
+
+ meta.setLore(toSet);
+ is.setItemMeta(meta);
+
+ return this;
+ }
+
+ public ItemBuilder lore(List lore) {
+ List toSet = new ArrayList<>();
+ ItemMeta meta = is.getItemMeta();
+
+ for (String string : lore) {
+ toSet.add(ChatColor.translateAlternateColorCodes('&', string));
+ }
+
+ meta.setLore(toSet);
+ is.setItemMeta(meta);
+
+ return this;
+ }
+
+ public ItemBuilder durability(int durability) {
+ is.setDurability((short) durability);
+ return this;
+ }
+
+ public ItemBuilder enchantment(Enchantment enchantment, int level) {
+ is.addUnsafeEnchantment(enchantment, level);
+ return this;
+ }
+
+ public ItemBuilder enchantment(Enchantment enchantment) {
+ is.addUnsafeEnchantment(enchantment, 1);
+ return this;
+ }
+
+ public ItemBuilder type(Material material) {
+ is.setType(material);
+ return this;
+ }
+
+ public ItemBuilder clearLore() {
+ ItemMeta meta = is.getItemMeta();
+
+ meta.setLore(new ArrayList<>());
+ is.setItemMeta(meta);
+
+ return this;
+ }
+
+ public ItemBuilder clearEnchantments() {
+ for (Enchantment e : is.getEnchantments().keySet()) {
+ is.removeEnchantment(e);
+ }
+
+ return this;
+ }
+
+ public ItemStack build() {
+ return is;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/minexd/zoot/util/LocationUtil.java b/src/main/java/com/minexd/zoot/util/LocationUtil.java
new file mode 100644
index 0000000..9b40fca
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/LocationUtil.java
@@ -0,0 +1,42 @@
+package com.minexd.zoot.util;
+
+import org.bukkit.Bukkit;
+import org.bukkit.Location;
+import org.bukkit.World;
+
+public class LocationUtil {
+
+ public static Location[] getFaces(Location start) {
+ Location[] faces = new Location[4];
+ faces[0] = new Location(start.getWorld(), start.getX() + 1, start.getY(), start.getZ());
+ faces[1] = new Location(start.getWorld(), start.getX() - 1, start.getY(), start.getZ());
+ faces[2] = new Location(start.getWorld(), start.getX(), start.getY() + 1, start.getZ());
+ faces[3] = new Location(start.getWorld(), start.getX(), start.getY() - 1, start.getZ());
+ return faces;
+ }
+
+ public static String serialize(Location location) {
+ if (location == null) {
+ return "null";
+ }
+
+ return location.getWorld().getName() + ":" + location.getX() + ":" + location.getY() + ":" + location.getZ() +
+ ":" + location.getYaw() + ":" + location.getPitch();
+ }
+
+ public static Location deserialize(String source) {
+ if (source == null) {
+ return null;
+ }
+
+ String[] split = source.split(":");
+ World world = Bukkit.getServer().getWorld(split[0]);
+
+ if (world == null) {
+ return null;
+ }
+
+ return new Location(world, Double.parseDouble(split[1]), Double.parseDouble(split[2]), Double.parseDouble(split[3]), Float.parseFloat(split[4]), Float.parseFloat(split[5]));
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/PotionUtil.java b/src/main/java/com/minexd/zoot/util/PotionUtil.java
new file mode 100644
index 0000000..99e8346
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/PotionUtil.java
@@ -0,0 +1,21 @@
+package com.minexd.zoot.util;
+
+import org.bukkit.potion.PotionEffectType;
+
+public class PotionUtil {
+
+ public static String getName(PotionEffectType potionEffectType) {
+ if (potionEffectType.getName().equalsIgnoreCase("fire_resistance")) {
+ return "Fire Resistance";
+ } else if (potionEffectType.getName().equalsIgnoreCase("speed")) {
+ return "Speed";
+ } else if (potionEffectType.getName().equalsIgnoreCase("weakness")) {
+ return "Weakness";
+ } else if (potionEffectType.getName().equalsIgnoreCase("slowness")) {
+ return "Slowness";
+ } else {
+ return "Unknown";
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/TextSplitter.java b/src/main/java/com/minexd/zoot/util/TextSplitter.java
new file mode 100644
index 0000000..fc2be38
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/TextSplitter.java
@@ -0,0 +1,50 @@
+package com.minexd.zoot.util;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+public class TextSplitter {
+
+ public static List split(int length, List lines, String linePrefix, String wordSuffix) {
+ StringBuilder builder = new StringBuilder();
+
+ for (String line : lines) {
+ builder.append(line.trim());
+ builder.append(" ");
+ }
+
+ return split(length, builder.substring(0, builder.length() - 1), linePrefix, wordSuffix);
+ }
+
+ public static List split(int length, String text, String linePrefix, String wordSuffix) {
+ if (text.length() <= length) {
+ return Collections.singletonList(linePrefix + text);
+ }
+
+ List lines = new ArrayList<>();
+ String[] split = text.split(" ");
+ StringBuilder builder = new StringBuilder(linePrefix);
+
+ for (int i = 0; i < split.length; ++i) {
+ if (builder.length() + split[i].length() >= length) {
+ lines.add(builder.toString());
+ builder = new StringBuilder(linePrefix);
+ }
+
+ builder.append(split[i]);
+ builder.append(wordSuffix);
+
+ if (i == split.length - 1) {
+ builder.replace(builder.length() - wordSuffix.length(), builder.length(), "");
+ }
+ }
+
+ if (builder.length() != 0) {
+ lines.add(builder.toString());
+ }
+
+ return lines;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/TimeUtil.java b/src/main/java/com/minexd/zoot/util/TimeUtil.java
new file mode 100644
index 0000000..e15f7a1
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/TimeUtil.java
@@ -0,0 +1,146 @@
+package com.minexd.zoot.util;
+
+import java.sql.Timestamp;
+import java.text.DecimalFormat;
+import java.text.SimpleDateFormat;
+import java.util.Calendar;
+import java.util.Date;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public final class TimeUtil {
+
+ private static final String HOUR_FORMAT = "%02d:%02d:%02d";
+ private static final String MINUTE_FORMAT = "%02d:%02d";
+
+ private TimeUtil() {
+ throw new RuntimeException("Cannot instantiate a utility class.");
+ }
+
+ public static String millisToTimer(long millis) {
+ long seconds = millis / 1000L;
+
+ if (seconds > 3600L) {
+ return String.format(HOUR_FORMAT, seconds / 3600L, seconds % 3600L / 60L, seconds % 60L);
+ } else {
+ return String.format(MINUTE_FORMAT, seconds / 60L, seconds % 60L);
+ }
+ }
+
+ /**
+ * Return the amount of seconds from milliseconds.
+ * Note: We explicitly use 1000.0F (float) instead of 1000L (long).
+ *
+ * @param millis the amount of time in milliseconds
+ * @return the seconds
+ */
+ public static String millisToSeconds(long millis) {
+ return new DecimalFormat("#0.0").format(millis / 1000.0F);
+ }
+
+ public static String dateToString(Date date, String secondaryColor) {
+ Calendar calendar = Calendar.getInstance();
+ calendar.setTime(date);
+
+ return new SimpleDateFormat("MMM dd yyyy " + (secondaryColor == null ? "" : secondaryColor) +
+ "(hh:mm aa zz)").format(date);
+ }
+
+ public static Timestamp addDuration(long duration) {
+ return truncateTimestamp(new Timestamp(System.currentTimeMillis() + duration));
+ }
+
+ public static Timestamp truncateTimestamp(Timestamp timestamp) {
+ if (timestamp.toLocalDateTime().getYear() > 2037) {
+ timestamp.setYear(2037);
+ }
+
+ return timestamp;
+ }
+
+ public static Timestamp addDuration(Timestamp timestamp) {
+ return truncateTimestamp(new Timestamp(System.currentTimeMillis() + timestamp.getTime()));
+ }
+
+ public static Timestamp fromMillis(long millis) {
+ return new Timestamp(millis);
+ }
+
+ public static Timestamp getCurrentTimestamp() {
+ return new Timestamp(System.currentTimeMillis());
+ }
+
+ public static String millisToRoundedTime(long millis) {
+ millis += 1L;
+
+ long seconds = millis / 1000L;
+ long minutes = seconds / 60L;
+ long hours = minutes / 60L;
+ long days = hours / 24L;
+ long weeks = days / 7L;
+ long months = weeks / 4L;
+ long years = months / 12L;
+
+ if (years > 0) {
+ return years + " year" + (years == 1 ? "" : "s");
+ } else if (months > 0) {
+ return months + " month" + (months == 1 ? "" : "s");
+ } else if (weeks > 0) {
+ return weeks + " week" + (weeks == 1 ? "" : "s");
+ } else if (days > 0) {
+ return days + " day" + (days == 1 ? "" : "s");
+ } else if (hours > 0) {
+ return hours + " hour" + (hours == 1 ? "" : "s");
+ } else if (minutes > 0) {
+ return minutes + " minute" + (minutes == 1 ? "" : "s");
+ } else {
+ return seconds + " second" + (seconds == 1 ? "" : "s");
+ }
+ }
+
+ public static long parseTime(String time) {
+ long totalTime = 0L;
+ boolean found = false;
+ Matcher matcher = Pattern.compile("\\d+\\D+").matcher(time);
+
+ while (matcher.find()) {
+ String s = matcher.group();
+ Long value = Long.parseLong(s.split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")[0]);
+ String type = s.split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")[1];
+
+ switch (type) {
+ case "s":
+ totalTime += value;
+ found = true;
+ break;
+ case "m":
+ totalTime += value * 60;
+ found = true;
+ break;
+ case "h":
+ totalTime += value * 60 * 60;
+ found = true;
+ break;
+ case "d":
+ totalTime += value * 60 * 60 * 24;
+ found = true;
+ break;
+ case "w":
+ totalTime += value * 60 * 60 * 24 * 7;
+ found = true;
+ break;
+ case "M":
+ totalTime += value * 60 * 60 * 24 * 30;
+ found = true;
+ break;
+ case "y":
+ totalTime += value * 60 * 60 * 24 * 365;
+ found = true;
+ break;
+ }
+ }
+
+ return !found ? -1 : totalTime * 1000;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/adapter/ChatColorTypeAdapter.java b/src/main/java/com/minexd/zoot/util/adapter/ChatColorTypeAdapter.java
new file mode 100644
index 0000000..45925b2
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/adapter/ChatColorTypeAdapter.java
@@ -0,0 +1,30 @@
+package com.minexd.zoot.util.adapter;
+
+import com.minexd.zoot.util.CC;
+import com.qrakn.honcho.command.adapter.CommandTypeAdapter;
+import java.util.ArrayList;
+import java.util.List;
+
+public class ChatColorTypeAdapter implements CommandTypeAdapter {
+
+ @Override
+ public T convert(String string, Class type) {
+ return type.cast(CC.getColorFromName(string));
+ }
+
+ @Override
+ public List tabComplete(String string, Class type) {
+ final String compare = string.trim().toLowerCase();
+
+ List completed = new ArrayList<>();
+
+ for (String colorName : CC.getColorNames()) {
+ if (colorName.startsWith(compare)) {
+ completed.add(colorName);
+ }
+ }
+
+ return completed;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/callback/Callback.java b/src/main/java/com/minexd/zoot/util/callback/Callback.java
new file mode 100644
index 0000000..c41f89c
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/callback/Callback.java
@@ -0,0 +1,12 @@
+package com.minexd.zoot.util.callback;
+
+import java.io.Serializable;
+
+public interface Callback extends Serializable {
+
+ /**
+ * A callback for running a task on a set of data.
+ */
+ void callback();
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/callback/ReturnableTypeCallback.java b/src/main/java/com/minexd/zoot/util/callback/ReturnableTypeCallback.java
new file mode 100644
index 0000000..fc1b7aa
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/callback/ReturnableTypeCallback.java
@@ -0,0 +1,7 @@
+package com.minexd.zoot.util.callback;
+
+public interface ReturnableTypeCallback {
+
+ T call();
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/callback/TypeCallback.java b/src/main/java/com/minexd/zoot/util/callback/TypeCallback.java
new file mode 100644
index 0000000..a3aa661
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/callback/TypeCallback.java
@@ -0,0 +1,14 @@
+package com.minexd.zoot.util.callback;
+
+import java.io.Serializable;
+
+public interface TypeCallback extends Serializable {
+
+ /**
+ * A callback for running a task on a set of data.
+ *
+ * @param data the data needed to run the task.
+ */
+ void callback(T data);
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/duration/Duration.java b/src/main/java/com/minexd/zoot/util/duration/Duration.java
new file mode 100644
index 0000000..b809df5
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/duration/Duration.java
@@ -0,0 +1,64 @@
+package com.minexd.zoot.util.duration;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import lombok.Getter;
+
+public class Duration {
+
+ @Getter private long value;
+
+ public Duration(long value) {
+ this.value = value;
+ }
+
+ public static Duration fromString(String source) {
+ if (source.equalsIgnoreCase("perm") || source.equalsIgnoreCase("permanent")) {
+ return new Duration(Integer.MAX_VALUE);
+ }
+
+ long totalTime = 0L;
+ boolean found = false;
+ Matcher matcher = Pattern.compile("\\d+\\D+").matcher(source);
+
+ while (matcher.find()) {
+ String s = matcher.group();
+ Long value = Long.parseLong(s.split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")[0]);
+ String type = s.split("(?<=\\D)(?=\\d)|(?<=\\d)(?=\\D)")[1];
+
+ switch (type) {
+ case "s":
+ totalTime += value;
+ found = true;
+ break;
+ case "m":
+ totalTime += value * 60;
+ found = true;
+ break;
+ case "h":
+ totalTime += value * 60 * 60;
+ found = true;
+ break;
+ case "d":
+ totalTime += value * 60 * 60 * 24;
+ found = true;
+ break;
+ case "w":
+ totalTime += value * 60 * 60 * 24 * 7;
+ found = true;
+ break;
+ case "M":
+ totalTime += value * 60 * 60 * 24 * 30;
+ found = true;
+ break;
+ case "y":
+ totalTime += value * 60 * 60 * 24 * 365;
+ found = true;
+ break;
+ }
+ }
+
+ return new Duration(!found ? -1 : totalTime * 1000);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/duration/DurationTypeAdapter.java b/src/main/java/com/minexd/zoot/util/duration/DurationTypeAdapter.java
new file mode 100644
index 0000000..3e869d8
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/duration/DurationTypeAdapter.java
@@ -0,0 +1,13 @@
+package com.minexd.zoot.util.duration;
+
+import com.qrakn.honcho.command.adapter.CommandTypeAdapter;
+
+public class DurationTypeAdapter implements CommandTypeAdapter {
+
+ @Override
+ public T convert(String string, Class type) {
+ return type.cast(Duration.fromString(string));
+ }
+
+}
+
diff --git a/src/main/java/com/minexd/zoot/util/json/JsonChain.java b/src/main/java/com/minexd/zoot/util/json/JsonChain.java
new file mode 100644
index 0000000..98787d5
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/json/JsonChain.java
@@ -0,0 +1,39 @@
+package com.minexd.zoot.util.json;
+
+import com.google.gson.JsonElement;
+import com.google.gson.JsonObject;
+
+public class JsonChain {
+
+ private JsonObject json = new JsonObject();
+
+ public JsonChain addProperty(String property, String value) {
+ this.json.addProperty(property, value);
+ return this;
+ }
+
+ public JsonChain addProperty(String property, Number value) {
+ this.json.addProperty(property, value);
+ return this;
+ }
+
+ public JsonChain addProperty(String property, Boolean value) {
+ this.json.addProperty(property, value);
+ return this;
+ }
+
+ public JsonChain addProperty(String property, Character value) {
+ this.json.addProperty(property, value);
+ return this;
+ }
+
+ public JsonChain add(String property, JsonElement element) {
+ this.json.add(property, element);
+ return this;
+ }
+
+ public JsonObject get() {
+ return this.json;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/json/JsonDeserializer.java b/src/main/java/com/minexd/zoot/util/json/JsonDeserializer.java
new file mode 100644
index 0000000..b658af1
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/json/JsonDeserializer.java
@@ -0,0 +1,9 @@
+package com.minexd.zoot.util.json;
+
+import com.google.gson.JsonObject;
+
+public interface JsonDeserializer {
+
+ T deserialize(JsonObject object);
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/json/JsonSerializer.java b/src/main/java/com/minexd/zoot/util/json/JsonSerializer.java
new file mode 100644
index 0000000..b7d0b2a
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/json/JsonSerializer.java
@@ -0,0 +1,9 @@
+package com.minexd.zoot.util.json;
+
+import com.google.gson.JsonObject;
+
+public interface JsonSerializer {
+
+ JsonObject serialize(T t);
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/menu/Button.java b/src/main/java/com/minexd/zoot/util/menu/Button.java
new file mode 100644
index 0000000..b509d20
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/Button.java
@@ -0,0 +1,54 @@
+package com.minexd.zoot.util.menu;
+
+import org.apache.commons.lang.StringUtils;
+import org.bukkit.Material;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+public abstract class Button {
+
+ public static Button placeholder(final Material material, final byte data, String... title) {
+ return (new Button() {
+ public ItemStack getButtonItem(Player player) {
+ ItemStack it = new ItemStack(material, 1, data);
+ ItemMeta meta = it.getItemMeta();
+
+ meta.setDisplayName(StringUtils.join(title));
+ it.setItemMeta(meta);
+
+ return it;
+ }
+ });
+ }
+
+ public static void playFail(Player player) {
+ player.playSound(player.getLocation(), Sound.DIG_GRASS, 20F, 0.1F);
+
+ }
+
+ public static void playSuccess(Player player) {
+ player.playSound(player.getLocation(), Sound.NOTE_PIANO, 20F, 15F);
+ }
+
+ public static void playNeutral(Player player) {
+ player.playSound(player.getLocation(), Sound.CLICK, 20F, 1F);
+ }
+
+ public abstract ItemStack getButtonItem(Player player);
+
+ public void clicked(Player player, ClickType clickType) {}
+
+ public void clicked(Player player, int slot, ClickType clickType, int hotbarSlot) {}
+
+ public boolean shouldCancel(Player player, ClickType clickType) {
+ return true;
+ }
+
+ public boolean shouldUpdate(Player player, ClickType clickType) {
+ return false;
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/minexd/zoot/util/menu/Menu.java b/src/main/java/com/minexd/zoot/util/menu/Menu.java
new file mode 100644
index 0000000..e2c4876
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/Menu.java
@@ -0,0 +1,136 @@
+package com.minexd.zoot.util.menu;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.util.CC;
+import java.util.HashMap;
+import java.util.Map;
+import lombok.Getter;
+import lombok.Setter;
+import org.bukkit.Bukkit;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.inventory.Inventory;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+@Getter
+@Setter
+public abstract class Menu {
+
+ public static Map currentlyOpenedMenus = new HashMap<>();
+
+ @Getter protected Zoot zoot = Zoot.get();
+ private Map buttons = new HashMap<>();
+ private boolean autoUpdate = false;
+ private boolean updateAfterClick = true;
+ private boolean closedByMenu = false;
+ private boolean placeholder = false;
+ private Button placeholderButton = Button.placeholder(Material.STAINED_GLASS_PANE, (byte) 15, " ");
+
+ private ItemStack createItemStack(Player player, Button button) {
+ ItemStack item = button.getButtonItem(player);
+
+ if (item.getType() != Material.SKULL_ITEM) {
+ ItemMeta meta = item.getItemMeta();
+
+ if (meta != null && meta.hasDisplayName()) {
+ meta.setDisplayName(meta.getDisplayName() + "§b§c§d§e");
+ }
+
+ item.setItemMeta(meta);
+ }
+
+ return item;
+ }
+
+ public void openMenu(final Player player) {
+ this.buttons = this.getButtons(player);
+
+ Menu previousMenu = Menu.currentlyOpenedMenus.get(player.getName());
+ Inventory inventory = null;
+ int size = this.getSize() == -1 ? this.size(this.buttons) : this.getSize();
+ boolean update = false;
+ String title = CC.translate(this.getTitle(player));
+
+ if (title.length() > 32) {
+ title = title.substring(0, 32);
+ }
+
+ if (player.getOpenInventory() != null) {
+ if (previousMenu == null) {
+ player.closeInventory();
+ } else {
+ int previousSize = player.getOpenInventory().getTopInventory().getSize();
+
+ if (previousSize == size && player.getOpenInventory().getTopInventory().getTitle().equals(title)) {
+ inventory = player.getOpenInventory().getTopInventory();
+ update = true;
+ } else {
+ previousMenu.setClosedByMenu(true);
+ player.closeInventory();
+ }
+ }
+ }
+
+ if (inventory == null) {
+ inventory = Bukkit.createInventory(player, size, title);
+ }
+
+ inventory.setContents(new ItemStack[inventory.getSize()]);
+
+ currentlyOpenedMenus.put(player.getName(), this);
+
+ for (Map.Entry buttonEntry : this.buttons.entrySet()) {
+ inventory.setItem(buttonEntry.getKey(), createItemStack(player, buttonEntry.getValue()));
+ }
+
+ if (this.isPlaceholder()) {
+ for (int index = 0; index < size; index++) {
+ if (this.buttons.get(index) == null) {
+ this.buttons.put(index, this.placeholderButton);
+ inventory.setItem(index, this.placeholderButton.getButtonItem(player));
+ }
+ }
+ }
+
+ if (update) {
+ player.updateInventory();
+ } else {
+ player.openInventory(inventory);
+ }
+
+ this.onOpen(player);
+ this.setClosedByMenu(false);
+ }
+
+ public int size(Map buttons) {
+ int highest = 0;
+
+ for (int buttonValue : buttons.keySet()) {
+ if (buttonValue > highest) {
+ highest = buttonValue;
+ }
+ }
+
+ return (int) (Math.ceil((highest + 1) / 9D) * 9D);
+ }
+
+ public int getSize() {
+ return -1;
+ }
+
+ public int getSlot(int x, int y) {
+ return ((9 * y) + x);
+ }
+
+ public abstract String getTitle(Player player);
+
+ public abstract Map getButtons(Player player);
+
+ public void onOpen(Player player) {
+ }
+
+ public void onClose(Player player) {
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/menu/MenuListener.java b/src/main/java/com/minexd/zoot/util/menu/MenuListener.java
new file mode 100644
index 0000000..b27a0fa
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/MenuListener.java
@@ -0,0 +1,93 @@
+package com.minexd.zoot.util.menu;
+
+import com.minexd.zoot.Zoot;
+import com.minexd.zoot.bootstrap.BootstrappedListener;
+import org.bukkit.Bukkit;
+import org.bukkit.entity.Player;
+import org.bukkit.event.EventHandler;
+import org.bukkit.event.EventPriority;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.event.inventory.InventoryClickEvent;
+import org.bukkit.event.inventory.InventoryCloseEvent;
+
+public class MenuListener extends BootstrappedListener {
+
+ public MenuListener(Zoot zoot) {
+ super(zoot);
+ }
+
+ @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR)
+ public void onButtonPress(InventoryClickEvent event) {
+ Player player = (Player) event.getWhoClicked();
+ Menu openMenu = Menu.currentlyOpenedMenus.get(player.getName());
+
+ if (openMenu != null) {
+ if (event.getSlot() != event.getRawSlot()) {
+ if ((event.getClick() == ClickType.SHIFT_LEFT || event.getClick() == ClickType.SHIFT_RIGHT)) {
+ event.setCancelled(true);
+ }
+
+ return;
+ }
+
+ if (openMenu.getButtons().containsKey(event.getSlot())) {
+ Button button = openMenu.getButtons().get(event.getSlot());
+ boolean cancel = button.shouldCancel(player, event.getClick());
+
+ if (!cancel && (event.getClick() == ClickType.SHIFT_LEFT || event.getClick() == ClickType.SHIFT_RIGHT)) {
+ event.setCancelled(true);
+
+ if (event.getCurrentItem() != null) {
+ player.getInventory().addItem(event.getCurrentItem());
+ }
+ } else {
+ event.setCancelled(cancel);
+ }
+
+ button.clicked(player, event.getClick());
+ button.clicked(player, event.getSlot(), event.getClick(), event.getHotbarButton());
+
+ if (Menu.currentlyOpenedMenus.containsKey(player.getName())) {
+ Menu newMenu = Menu.currentlyOpenedMenus.get(player.getName());
+
+ if (newMenu == openMenu) {
+ boolean buttonUpdate = button.shouldUpdate(player, event.getClick());
+
+ if ((newMenu.isUpdateAfterClick() && buttonUpdate) || buttonUpdate) {
+ openMenu.setClosedByMenu(true);
+ newMenu.openMenu(player);
+ }
+ }
+ } else if (button.shouldUpdate(player, event.getClick())) {
+ openMenu.setClosedByMenu(true);
+ openMenu.openMenu(player);
+ }
+
+ if (event.isCancelled()) {
+ Bukkit.getScheduler().runTaskLater(zoot, player::updateInventory, 1L);
+ }
+ } else {
+ if (event.getCurrentItem() != null) {
+ event.setCancelled(true);
+ }
+
+ if ((event.getClick() == ClickType.SHIFT_LEFT || event.getClick() == ClickType.SHIFT_RIGHT)) {
+ event.setCancelled(true);
+ }
+ }
+ }
+ }
+
+ @EventHandler(priority = EventPriority.HIGH)
+ public void onInventoryClose(InventoryCloseEvent event) {
+ Player player = (Player) event.getPlayer();
+ Menu openMenu = Menu.currentlyOpenedMenus.get(player.getName());
+
+ if (openMenu != null) {
+ openMenu.onClose(player);
+
+ Menu.currentlyOpenedMenus.remove(player.getName());
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/src/main/java/com/minexd/zoot/util/menu/button/BackButton.java b/src/main/java/com/minexd/zoot/util/menu/button/BackButton.java
new file mode 100644
index 0000000..cb9a0e2
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/button/BackButton.java
@@ -0,0 +1,36 @@
+package com.minexd.zoot.util.menu.button;
+
+import com.minexd.zoot.util.CC;
+import com.minexd.zoot.util.ItemBuilder;
+import com.minexd.zoot.util.menu.Button;
+import com.minexd.zoot.util.menu.Menu;
+import java.util.Arrays;
+import lombok.AllArgsConstructor;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+@AllArgsConstructor
+public class BackButton extends Button {
+
+ private Menu back;
+
+ @Override
+ public ItemStack getButtonItem(Player player) {
+ return new ItemBuilder(Material.REDSTONE)
+ .name(CC.RED + CC.BOLD + "Back")
+ .lore(Arrays.asList(
+ CC.RED + "Click here to return to",
+ CC.RED + "the previous menu.")
+ )
+ .build();
+ }
+
+ @Override
+ public void clicked(Player player, ClickType clickType) {
+ Button.playNeutral(player);
+ back.openMenu(player);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/menu/button/ConfirmationButton.java b/src/main/java/com/minexd/zoot/util/menu/button/ConfirmationButton.java
new file mode 100644
index 0000000..5803af9
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/button/ConfirmationButton.java
@@ -0,0 +1,54 @@
+package com.minexd.zoot.util.menu.button;
+
+import com.minexd.zoot.util.callback.TypeCallback;
+import com.minexd.zoot.util.menu.Button;
+import com.minexd.zoot.util.menu.Menu;
+import lombok.AllArgsConstructor;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.Sound;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+@AllArgsConstructor
+public class ConfirmationButton extends Button {
+
+ private boolean confirm;
+ private TypeCallback callback;
+ private boolean closeAfterResponse;
+
+ @Override
+ public ItemStack getButtonItem(Player player) {
+ ItemStack itemStack = new ItemStack(Material.WOOL, 1, this.confirm ? ((byte) 5) : ((byte) 14));
+ ItemMeta itemMeta = itemStack.getItemMeta();
+
+ itemMeta.setDisplayName(this.confirm ? ChatColor.GREEN + "Confirm" : ChatColor.RED + "Cancel");
+ itemStack.setItemMeta(itemMeta);
+
+ return itemStack;
+ }
+
+ @Override
+ public void clicked(Player player, ClickType clickType) {
+ if (this.confirm) {
+ player.playSound(player.getLocation(), Sound.NOTE_PIANO, 20f, 0.1f);
+ } else {
+ player.playSound(player.getLocation(), Sound.DIG_GRAVEL, 20f, 0.1F);
+ }
+
+ if (this.closeAfterResponse) {
+ Menu menu = Menu.currentlyOpenedMenus.get(player.getName());
+
+ if (menu != null) {
+ menu.setClosedByMenu(true);
+ }
+
+ player.closeInventory();
+ }
+
+ this.callback.callback(this.confirm);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/menu/button/DisplayButton.java b/src/main/java/com/minexd/zoot/util/menu/button/DisplayButton.java
new file mode 100644
index 0000000..1e537df
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/button/DisplayButton.java
@@ -0,0 +1,34 @@
+package com.minexd.zoot.util.menu.button;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+import lombok.Setter;
+import com.minexd.zoot.util.menu.Button;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+@AllArgsConstructor
+@Getter
+@Setter
+public class DisplayButton extends Button {
+
+ private ItemStack itemStack;
+ private boolean cancel;
+
+ @Override
+ public ItemStack getButtonItem(Player player) {
+ if (this.itemStack == null) {
+ return new ItemStack(Material.AIR);
+ } else {
+ return this.itemStack;
+ }
+ }
+
+ @Override
+ public boolean shouldCancel(Player player, ClickType clickType) {
+ return this.cancel;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/menu/button/JumpToMenuButton.java b/src/main/java/com/minexd/zoot/util/menu/button/JumpToMenuButton.java
new file mode 100644
index 0000000..107c1ed
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/button/JumpToMenuButton.java
@@ -0,0 +1,29 @@
+package com.minexd.zoot.util.menu.button;
+
+import com.minexd.zoot.util.menu.Button;
+import com.minexd.zoot.util.menu.Menu;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+public class JumpToMenuButton extends Button {
+
+ private Menu menu;
+ private ItemStack itemStack;
+
+ public JumpToMenuButton(Menu menu, ItemStack itemStack) {
+ this.menu = menu;
+ this.itemStack = itemStack;
+ }
+
+ @Override
+ public ItemStack getButtonItem(Player player) {
+ return itemStack;
+ }
+
+ @Override
+ public void clicked(Player player, ClickType clickType) {
+ menu.openMenu(player);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/menu/menus/ConfirmMenu.java b/src/main/java/com/minexd/zoot/util/menu/menus/ConfirmMenu.java
new file mode 100644
index 0000000..76c31c5
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/menus/ConfirmMenu.java
@@ -0,0 +1,52 @@
+package com.minexd.zoot.util.menu.menus;
+
+import com.minexd.zoot.util.menu.Button;
+import com.minexd.zoot.util.menu.Menu;
+import com.minexd.zoot.util.menu.button.ConfirmationButton;
+import com.minexd.zoot.util.callback.TypeCallback;
+import java.util.HashMap;
+import java.util.Map;
+import org.bukkit.entity.Player;
+
+public class ConfirmMenu extends Menu {
+
+ private String title;
+ private TypeCallback response;
+ private boolean closeAfterResponse;
+ private Button[] centerButtons;
+
+ public ConfirmMenu(String title, TypeCallback response, boolean closeAfter, Button... centerButtons) {
+ this.title = title;
+ this.response = response;
+ this.closeAfterResponse = closeAfter;
+ this.centerButtons = centerButtons;
+ }
+
+ @Override
+ public Map getButtons(Player player) {
+ HashMap buttons = new HashMap<>();
+
+ for (int x = 0; x < 3; x++) {
+ for (int y = 0; y < 3; y++) {
+ buttons.put(getSlot(x, y), new ConfirmationButton(true, response, closeAfterResponse));
+ buttons.put(getSlot(8 - x, y), new ConfirmationButton(false, response, closeAfterResponse));
+ }
+ }
+
+ if (centerButtons != null) {
+ for (int i = 0; i < centerButtons.length; i++) {
+ if (centerButtons[i] != null) {
+ buttons.put(getSlot(4, i), centerButtons[i]);
+ }
+ }
+ }
+
+ return buttons;
+ }
+
+ @Override
+ public String getTitle(Player player) {
+ return title;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/menu/pagination/FilterablePaginatedMenu.java b/src/main/java/com/minexd/zoot/util/menu/pagination/FilterablePaginatedMenu.java
new file mode 100644
index 0000000..4e8e90b
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/pagination/FilterablePaginatedMenu.java
@@ -0,0 +1,39 @@
+package com.minexd.zoot.util.menu.pagination;
+
+import com.minexd.zoot.util.menu.Button;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import lombok.Getter;
+import lombok.Setter;
+import org.bukkit.entity.Player;
+
+public abstract class FilterablePaginatedMenu extends PaginatedMenu {
+
+ @Getter private final List> filters;
+ @Getter @Setter private int scrollIndex = 0;
+
+ {
+ filters = generateFilters();
+ }
+
+ @Override
+ public Map getGlobalButtons(Player player) {
+ Map buttons = new HashMap<>();
+ buttons.put(7, new PageFilterButton<>(this));
+ return buttons;
+ }
+
+ @Override
+ public Map getAllPagesButtons(Player player) {
+ return getFilteredButtons(player);
+ }
+
+ public abstract Map getFilteredButtons(Player player);
+
+ public List> generateFilters() {
+ return new ArrayList<>();
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/menu/pagination/JumpToPageButton.java b/src/main/java/com/minexd/zoot/util/menu/pagination/JumpToPageButton.java
new file mode 100644
index 0000000..c443c0f
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/pagination/JumpToPageButton.java
@@ -0,0 +1,45 @@
+package com.minexd.zoot.util.menu.pagination;
+
+import com.minexd.zoot.util.menu.Button;
+import java.util.Arrays;
+import lombok.AllArgsConstructor;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.ItemMeta;
+
+@AllArgsConstructor
+public class JumpToPageButton extends Button {
+
+ private int page;
+ private PaginatedMenu menu;
+ private boolean current;
+
+ @Override
+ public ItemStack getButtonItem(Player player) {
+ ItemStack itemStack = new ItemStack(this.current ? Material.ENCHANTED_BOOK : Material.BOOK, this.page);
+ ItemMeta itemMeta = itemStack.getItemMeta();
+
+ itemMeta.setDisplayName(ChatColor.YELLOW + "Page " + this.page);
+
+ if (this.current) {
+ itemMeta.setLore(Arrays.asList(
+ "",
+ ChatColor.GREEN + "Current page"
+ ));
+ }
+
+ itemStack.setItemMeta(itemMeta);
+
+ return itemStack;
+ }
+
+ @Override
+ public void clicked(Player player, ClickType clickType) {
+ this.menu.modPage(player, this.page - this.menu.getPage());
+ Button.playNeutral(player);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/menu/pagination/PageButton.java b/src/main/java/com/minexd/zoot/util/menu/pagination/PageButton.java
new file mode 100644
index 0000000..72c6c52
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/pagination/PageButton.java
@@ -0,0 +1,89 @@
+package com.minexd.zoot.util.menu.pagination;
+
+import com.minexd.zoot.util.ItemBuilder;
+import java.util.Arrays;
+import lombok.AllArgsConstructor;
+import com.minexd.zoot.util.menu.Button;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+@AllArgsConstructor
+public class PageButton extends Button {
+
+ private int mod;
+ private PaginatedMenu menu;
+
+ @Override
+ public ItemStack getButtonItem(Player player) {
+ if (this.mod > 0) {
+ if (hasNext(player)) {
+ return new ItemBuilder(Material.REDSTONE_TORCH_ON)
+ .name(ChatColor.GREEN + "Next Page")
+ .lore(Arrays.asList(
+ ChatColor.YELLOW + "Click here to jump",
+ ChatColor.YELLOW + "to the next page."
+ ))
+ .build();
+ } else {
+ return new ItemBuilder(Material.LEVER)
+ .name(ChatColor.GRAY + "Next Page")
+ .lore(Arrays.asList(
+ ChatColor.YELLOW + "There is no available",
+ ChatColor.YELLOW + "next page."
+ ))
+ .build();
+ }
+ } else {
+ if (hasPrevious(player)) {
+ return new ItemBuilder(Material.REDSTONE_TORCH_ON)
+ .name(ChatColor.GREEN + "Previous Page")
+ .lore(Arrays.asList(
+ ChatColor.YELLOW + "Click here to jump",
+ ChatColor.YELLOW + "to the previous page."
+ ))
+ .build();
+ } else {
+ return new ItemBuilder(Material.LEVER)
+ .name(ChatColor.GRAY + "Previous Page")
+ .lore(Arrays.asList(
+ ChatColor.YELLOW + "There is no available",
+ ChatColor.YELLOW + "previous page."
+ ))
+ .build();
+ }
+ }
+ }
+
+ @Override
+ public void clicked(Player player, ClickType clickType) {
+ if (this.mod > 0) {
+ if (hasNext(player)) {
+ this.menu.modPage(player, this.mod);
+ Button.playNeutral(player);
+ } else {
+ Button.playFail(player);
+ }
+ } else {
+ if (hasPrevious(player)) {
+ this.menu.modPage(player, this.mod);
+ Button.playNeutral(player);
+ } else {
+ Button.playFail(player);
+ }
+ }
+ }
+
+ private boolean hasNext(Player player) {
+ int pg = this.menu.getPage() + this.mod;
+ return this.menu.getPages(player) >= pg;
+ }
+
+ private boolean hasPrevious(Player player) {
+ int pg = this.menu.getPage() + this.mod;
+ return pg > 0;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/menu/pagination/PageFilter.java b/src/main/java/com/minexd/zoot/util/menu/pagination/PageFilter.java
new file mode 100644
index 0000000..8ad1967
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/pagination/PageFilter.java
@@ -0,0 +1,24 @@
+package com.minexd.zoot.util.menu.pagination;
+
+import java.util.function.Predicate;
+import lombok.Getter;
+import lombok.RequiredArgsConstructor;
+import lombok.Setter;
+
+@RequiredArgsConstructor
+public class PageFilter {
+
+ @Getter private final String name;
+ @Getter @Setter private boolean enabled;
+ private final Predicate predicate;
+
+ public boolean test(T t) {
+ return !enabled || predicate.test(t);
+ }
+
+ @Override
+ public boolean equals(Object object) {
+ return object instanceof PageFilter && ((PageFilter) object).getName().equals(name);
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/menu/pagination/PageFilterButton.java b/src/main/java/com/minexd/zoot/util/menu/pagination/PageFilterButton.java
new file mode 100644
index 0000000..4234058
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/pagination/PageFilterButton.java
@@ -0,0 +1,84 @@
+package com.minexd.zoot.util.menu.pagination;
+
+import com.minexd.zoot.util.CC;
+import com.minexd.zoot.util.ItemBuilder;
+import com.minexd.zoot.util.menu.Button;
+import java.util.ArrayList;
+import java.util.List;
+import lombok.AllArgsConstructor;
+import org.apache.commons.lang.StringEscapeUtils;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+@AllArgsConstructor
+public class PageFilterButton extends Button {
+
+ private FilterablePaginatedMenu menu;
+
+ @Override
+ public ItemStack getButtonItem(Player player) {
+ if (menu.getFilters() == null || menu.getFilters().isEmpty()) {
+ return new ItemStack(Material.AIR);
+ }
+
+ List lore = new ArrayList<>();
+ lore.add(CC.MENU_BAR);
+
+ for (PageFilter filter : menu.getFilters()) {
+ String color;
+ String decoration = "";
+ String icon;
+
+ if (filter.isEnabled()) {
+ color = ChatColor.GREEN.toString();
+ icon = StringEscapeUtils.unescapeJava("\u2713");
+ } else {
+ color = ChatColor.RED.toString();
+ icon = StringEscapeUtils.unescapeJava("\u2717");
+ }
+
+ if (menu.getFilters().get(menu.getScrollIndex()).equals(filter)) {
+ decoration = ChatColor.YELLOW + StringEscapeUtils.unescapeJava("» ") + " ";
+ }
+
+ lore.add(decoration + color + icon + " " + filter.getName());
+ }
+
+ lore.add(CC.MENU_BAR);
+ lore.add("&eLeft click to scroll.");
+ lore.add("&eRight click to toggle a filter.");
+ lore.add(CC.MENU_BAR);
+
+ return new ItemBuilder(Material.HOPPER)
+ .name("&7Filters")
+ .lore(lore)
+ .build();
+ }
+
+ @Override
+ public void clicked(Player player, ClickType clickType) {
+ if (menu.getFilters() == null || menu.getFilters().isEmpty()) {
+ player.sendMessage(ChatColor.RED + "There are no filters.");
+ } else {
+ if (clickType == ClickType.LEFT) {
+ if (menu.getScrollIndex() == menu.getFilters().size() - 1) {
+ menu.setScrollIndex(0);
+ } else {
+ menu.setScrollIndex(menu.getScrollIndex() + 1);
+ }
+ } else if (clickType == ClickType.RIGHT) {
+ PageFilter filter = menu.getFilters().get(menu.getScrollIndex());
+ filter.setEnabled(!filter.isEnabled());
+ }
+ }
+ }
+
+ @Override
+ public boolean shouldUpdate(Player player, ClickType clickType) {
+ return true;
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/menu/pagination/PageInfoButton.java b/src/main/java/com/minexd/zoot/util/menu/pagination/PageInfoButton.java
new file mode 100644
index 0000000..fbe500e
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/pagination/PageInfoButton.java
@@ -0,0 +1,41 @@
+package com.minexd.zoot.util.menu.pagination;
+
+import com.minexd.zoot.util.ItemBuilder;
+import com.minexd.zoot.util.menu.Button;
+import lombok.AllArgsConstructor;
+import org.bukkit.ChatColor;
+import org.bukkit.Material;
+import org.bukkit.entity.Player;
+import org.bukkit.event.inventory.ClickType;
+import org.bukkit.inventory.ItemStack;
+
+@AllArgsConstructor
+public class PageInfoButton extends Button {
+
+ private PaginatedMenu menu;
+
+ @Override
+ public ItemStack getButtonItem(Player player) {
+ int pages = menu.getPages(player);
+
+ return new ItemBuilder(Material.PAPER)
+ .name(ChatColor.GOLD + "Page Info")
+ .lore(
+ ChatColor.YELLOW + "You are viewing page #" + menu.getPage() + ".",
+ ChatColor.YELLOW + (pages == 1 ? "There is 1 page." : "There are " + pages + " pages."),
+ "",
+ ChatColor.YELLOW + "Middle click here to",
+ ChatColor.YELLOW + "view all pages."
+ )
+ .build();
+ }
+
+ @Override
+ public void clicked(Player player, ClickType clickType) {
+ if (clickType == ClickType.RIGHT) {
+ new ViewAllPagesMenu(this.menu).openMenu(player);
+ playNeutral(player);
+ }
+ }
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/menu/pagination/PaginatedMenu.java b/src/main/java/com/minexd/zoot/util/menu/pagination/PaginatedMenu.java
new file mode 100644
index 0000000..61b7005
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/pagination/PaginatedMenu.java
@@ -0,0 +1,114 @@
+package com.minexd.zoot.util.menu.pagination;
+
+import com.minexd.zoot.util.menu.Button;
+import com.minexd.zoot.util.menu.Menu;
+import java.util.HashMap;
+import java.util.Map;
+import lombok.Getter;
+import org.bukkit.entity.Player;
+
+public abstract class PaginatedMenu extends Menu {
+
+ @Getter private int page = 1;
+
+ {
+ setUpdateAfterClick(false);
+ }
+
+ @Override
+ public String getTitle(Player player) {
+ return getPrePaginatedTitle(player);
+ }
+
+ /**
+ * Changes the page number
+ *
+ * @param player player viewing the inventory
+ * @param mod delta to modify the page number by
+ */
+ public final void modPage(Player player, int mod) {
+ page += mod;
+ getButtons().clear();
+ openMenu(player);
+ }
+
+ /**
+ * @param player player viewing the inventory
+ */
+ public final int getPages(Player player) {
+ int buttonAmount = getAllPagesButtons(player).size();
+
+ if (buttonAmount == 0) {
+ return 1;
+ }
+
+ return (int) Math.ceil(buttonAmount / (double) getMaxItemsPerPage(player));
+ }
+
+ @Override
+ public final Map getButtons(Player player) {
+ int minIndex = (int) ((double) (page - 1) * getMaxItemsPerPage(player));
+ int maxIndex = (int) ((double) (page) * getMaxItemsPerPage(player));
+ int topIndex = 0;
+
+ HashMap buttons = new HashMap<>();
+
+ for (Map.Entry entry : getAllPagesButtons(player).entrySet()) {
+ int ind = entry.getKey();
+
+ if (ind >= minIndex && ind < maxIndex) {
+ ind -= (int) ((double) (getMaxItemsPerPage(player)) * (page - 1)) - 9;
+ buttons.put(ind, entry.getValue());
+
+ if (ind > topIndex) {
+ topIndex = ind;
+ }
+ }
+ }
+
+ buttons.put(0, new PageButton(-1, this));
+ buttons.put(8, new PageButton(1, this));
+
+ for (int i = 1; i < 8; i++) {
+ buttons.put(i, getPlaceholderButton());
+ }
+
+ Map global = getGlobalButtons(player);
+
+ if (global != null) {
+ for (Map.Entry gent : global.entrySet()) {
+ buttons.put(gent.getKey(), gent.getValue());
+ }
+ }
+
+ return buttons;
+ }
+
+ public int getMaxItemsPerPage(Player player) {
+ return 18;
+ }
+
+ /**
+ * @param player player viewing the inventory
+ *
+ * @return a Map of button that returns items which will be present on all pages
+ */
+ public Map getGlobalButtons(Player player) {
+ return null;
+ }
+
+ /**
+ * @param player player viewing the inventory
+ *
+ * @return title of the inventory before the page number is added
+ */
+ public abstract String getPrePaginatedTitle(Player player);
+
+ /**
+ * @param player player viewing the inventory
+ *
+ * @return a map of button that will be paginated and spread across pages
+ */
+ public abstract Map getAllPagesButtons(Player player);
+
+}
diff --git a/src/main/java/com/minexd/zoot/util/menu/pagination/ViewAllPagesMenu.java b/src/main/java/com/minexd/zoot/util/menu/pagination/ViewAllPagesMenu.java
new file mode 100644
index 0000000..c60d10b
--- /dev/null
+++ b/src/main/java/com/minexd/zoot/util/menu/pagination/ViewAllPagesMenu.java
@@ -0,0 +1,49 @@
+package com.minexd.zoot.util.menu.pagination;
+
+import com.minexd.zoot.util.menu.Button;
+import com.minexd.zoot.util.menu.Menu;
+import com.minexd.zoot.util.menu.button.BackButton;
+import java.util.HashMap;
+import java.util.Map;
+import lombok.Getter;
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+import org.bukkit.entity.Player;
+
+@RequiredArgsConstructor
+public class ViewAllPagesMenu extends Menu {
+
+ @NonNull
+ @Getter
+ PaginatedMenu menu;
+
+ @Override
+ public String getTitle(Player player) {
+ return "Jump to page";
+ }
+
+ @Override
+ public Map getButtons(Player player) {
+ HashMap buttons = new HashMap<>();
+
+ buttons.put(0, new BackButton(menu));
+
+ int index = 10;
+
+ for (int i = 1; i <= menu.getPages(player); i++) {
+ buttons.put(index++, new JumpToPageButton(i, menu, menu.getPage() == i));
+
+ if ((index - 8) % 9 == 0) {
+ index += 2;
+ }
+ }
+
+ return buttons;
+ }
+
+ @Override
+ public boolean isAutoUpdate() {
+ return true;
+ }
+
+}
diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml
new file mode 100644
index 0000000..0a56f62
--- /dev/null
+++ b/src/main/resources/config.yml
@@ -0,0 +1,60 @@
+CONFIG_VERSION: 1
+SERVER_NAME: "Development"
+MONGO:
+ HOST: "127.0.0.1"
+ PORT: 27017
+ AUTHENTICATION:
+ ENABLED: false
+ USERNAME: ""
+ PASSWORD: ""
+REDIS:
+ HOST: "127.0.0.1"
+ PORT: 6379
+ AUTHENTICATION:
+ ENABLED: false
+ PASSWORD: ""
+SETTINGS:
+ UPDATE_PLAYER_LIST_NAME: true
+PUNISHMENTS:
+ BROADCAST: "&r{target} &ahas been {context} by &r{sender}"
+ BROADCAST_SILENT: "&7[Silent] &r{target} &ahas been {context} by &r{sender}"
+ BAN:
+ KICK: "&cYour account is {context} from Zonix.{temporary}\n\n&cIf you feel this punishment is unjust, you may appeal at:\n&ehttps://www.zonix.com"
+ TEMPORARY: "\n&cThis punishment expires in &e{time-remaining}&c."
+ KICK:
+ KICK: "&cYou were kicked by a staff member.\nReason: &e{reason}"
+COMMON_ERRORS:
+ FAILED_TO_LOAD_PROFILE: "&cFailed to load your profile. Try again later."
+ COULD_NOT_RESOLVE_PLAYER: "&cCould not resolve player information..."
+ PLAYER_NOT_FOUND: "&cA player with that name could not be found."
+ RANK_NOT_FOUND: "&cA rank with that name could not be found."
+STAFF:
+ CHAT: "{0}&r{1}&b ({2}): {3}"
+ BROADCAST_PREFIX: "&9&l[Staff] &r"
+ JOIN_NETWORK: "{0}{1} &ajoined the network &2({2})"
+ LEAVE_NETWORK: "{0}{1} &aleft the network"
+ SWITCH_SERVER: "{0}({1} &ajoined {2} &2(from {3})"
+NETWORK:
+ BROADCAST_PREFIX: "&8[&eNetwork&8] &f"
+ RANK_REFRESH: "{0}Refreshed rank \"{1}\""
+ RANK_DELETE: "{0}Deleted rank \"{1}\""
+CHAT:
+ FORMAT: "%1$s&r: %2$s"
+ CLEAR_CHAT_BROADCAST: "&eThe chat has been cleared by &r{0}"
+ CLEAR_CHAT_FOR_STAFF: false
+ MUTE_CHAT_BROADCAST: "&eThe chat has been {0} by &r{1}"
+ DELAY_CHAT_ENABLED_BROADCAST: "&eThe chat has been delayed by &r{0} &7({1} second{2})"
+ DELAY_CHAT_DISABLED_BROADCAST: "&eThe chat delay has been lifted by &r{0}"
+ CHAT_DELAYED: "&cSlow down! You may chat again in {0} seconds."
+# using both text formatter and text replacing because I need to escape colors in the input message
+# format: player name, player display name, player color, target name, target display name, target color
+CONVERSATION:
+ SEND_MESSAGE: "&7(To &r{5}{3}&7) %MSG%"
+ RECEIVE_MESSAGE: "&7(From &r{5}{3}&7) %MSG%"
+OPTIONS:
+ GLOBAL_CHAT_ENABLED: "&eYou can now see global chat."
+ GLOBAL_CHAT_DISABLED: "&cYou will no longer see global chat."
+ PRIVATE_MESSAGES_ENABLED: "&aYou can now receive new conversations."
+ PRIVATE_MESSAGES_DISABLED: "&cYou can no longer receive new conversations. If you start a conversation with a player, they will be able to message you back."
+ PRIVATE_MESSAGE_SOUNDS_ENABLED: "&eYou enabled private message sounds."
+ PRIVATE_MESSAGE_SOUNDS_DISABLED: "&cYou disabled private message sounds."
\ No newline at end of file
diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml
new file mode 100644
index 0000000..1f4e2dd
--- /dev/null
+++ b/src/main/resources/plugin.yml
@@ -0,0 +1,5 @@
+main: com.minexd.zoot.Zoot
+name: Zoot
+author: joeleoli
+version: 1.0
+depend: [ProtocolLib]
\ No newline at end of file
diff --git a/target/classes/com/minexd/zoot/Locale.class b/target/classes/com/minexd/zoot/Locale.class
new file mode 100644
index 0000000..bc0df3e
Binary files /dev/null and b/target/classes/com/minexd/zoot/Locale.class differ
diff --git a/target/classes/com/minexd/zoot/Zoot$1.class b/target/classes/com/minexd/zoot/Zoot$1.class
new file mode 100644
index 0000000..0ca8aae
Binary files /dev/null and b/target/classes/com/minexd/zoot/Zoot$1.class differ
diff --git a/target/classes/com/minexd/zoot/Zoot$2.class b/target/classes/com/minexd/zoot/Zoot$2.class
new file mode 100644
index 0000000..a30cc62
Binary files /dev/null and b/target/classes/com/minexd/zoot/Zoot$2.class differ
diff --git a/target/classes/com/minexd/zoot/Zoot.class b/target/classes/com/minexd/zoot/Zoot.class
new file mode 100644
index 0000000..1f5663f
Binary files /dev/null and b/target/classes/com/minexd/zoot/Zoot.class differ
diff --git a/target/classes/com/minexd/zoot/ZootAPI.class b/target/classes/com/minexd/zoot/ZootAPI.class
new file mode 100644
index 0000000..08adcc7
Binary files /dev/null and b/target/classes/com/minexd/zoot/ZootAPI.class differ
diff --git a/target/classes/com/minexd/zoot/bootstrap/Bootstrapped.class b/target/classes/com/minexd/zoot/bootstrap/Bootstrapped.class
new file mode 100644
index 0000000..71ebc6a
Binary files /dev/null and b/target/classes/com/minexd/zoot/bootstrap/Bootstrapped.class differ
diff --git a/target/classes/com/minexd/zoot/bootstrap/BootstrappedListener.class b/target/classes/com/minexd/zoot/bootstrap/BootstrappedListener.class
new file mode 100644
index 0000000..2e9c2c0
Binary files /dev/null and b/target/classes/com/minexd/zoot/bootstrap/BootstrappedListener.class differ
diff --git a/target/classes/com/minexd/zoot/cache/RedisCache.class b/target/classes/com/minexd/zoot/cache/RedisCache.class
new file mode 100644
index 0000000..e005dc3
Binary files /dev/null and b/target/classes/com/minexd/zoot/cache/RedisCache.class differ
diff --git a/target/classes/com/minexd/zoot/cache/RedisPlayerData$LastAction.class b/target/classes/com/minexd/zoot/cache/RedisPlayerData$LastAction.class
new file mode 100644
index 0000000..665c46c
Binary files /dev/null and b/target/classes/com/minexd/zoot/cache/RedisPlayerData$LastAction.class differ
diff --git a/target/classes/com/minexd/zoot/cache/RedisPlayerData.class b/target/classes/com/minexd/zoot/cache/RedisPlayerData.class
new file mode 100644
index 0000000..b27fd0e
Binary files /dev/null and b/target/classes/com/minexd/zoot/cache/RedisPlayerData.class differ
diff --git a/target/classes/com/minexd/zoot/chat/Chat.class b/target/classes/com/minexd/zoot/chat/Chat.class
new file mode 100644
index 0000000..b586157
Binary files /dev/null and b/target/classes/com/minexd/zoot/chat/Chat.class differ
diff --git a/target/classes/com/minexd/zoot/chat/ChatAttempt$Response.class b/target/classes/com/minexd/zoot/chat/ChatAttempt$Response.class
new file mode 100644
index 0000000..4640855
Binary files /dev/null and b/target/classes/com/minexd/zoot/chat/ChatAttempt$Response.class differ
diff --git a/target/classes/com/minexd/zoot/chat/ChatAttempt.class b/target/classes/com/minexd/zoot/chat/ChatAttempt.class
new file mode 100644
index 0000000..7c627a4
Binary files /dev/null and b/target/classes/com/minexd/zoot/chat/ChatAttempt.class differ
diff --git a/target/classes/com/minexd/zoot/chat/ChatListener$1.class b/target/classes/com/minexd/zoot/chat/ChatListener$1.class
new file mode 100644
index 0000000..fc5e5fb
Binary files /dev/null and b/target/classes/com/minexd/zoot/chat/ChatListener$1.class differ
diff --git a/target/classes/com/minexd/zoot/chat/ChatListener.class b/target/classes/com/minexd/zoot/chat/ChatListener.class
new file mode 100644
index 0000000..7310ce1
Binary files /dev/null and b/target/classes/com/minexd/zoot/chat/ChatListener.class differ
diff --git a/target/classes/com/minexd/zoot/chat/command/ClearChatCommand.class b/target/classes/com/minexd/zoot/chat/command/ClearChatCommand.class
new file mode 100644
index 0000000..f93d631
Binary files /dev/null and b/target/classes/com/minexd/zoot/chat/command/ClearChatCommand.class differ
diff --git a/target/classes/com/minexd/zoot/chat/command/MuteChatCommand.class b/target/classes/com/minexd/zoot/chat/command/MuteChatCommand.class
new file mode 100644
index 0000000..d86a4ff
Binary files /dev/null and b/target/classes/com/minexd/zoot/chat/command/MuteChatCommand.class differ
diff --git a/target/classes/com/minexd/zoot/chat/command/SlowChatCommand.class b/target/classes/com/minexd/zoot/chat/command/SlowChatCommand.class
new file mode 100644
index 0000000..4dac83d
Binary files /dev/null and b/target/classes/com/minexd/zoot/chat/command/SlowChatCommand.class differ
diff --git a/target/classes/com/minexd/zoot/chat/event/ChatAttemptEvent.class b/target/classes/com/minexd/zoot/chat/event/ChatAttemptEvent.class
new file mode 100644
index 0000000..080675e
Binary files /dev/null and b/target/classes/com/minexd/zoot/chat/event/ChatAttemptEvent.class differ
diff --git a/target/classes/com/minexd/zoot/chat/filter/ChatFilter.class b/target/classes/com/minexd/zoot/chat/filter/ChatFilter.class
new file mode 100644
index 0000000..0d604ca
Binary files /dev/null and b/target/classes/com/minexd/zoot/chat/filter/ChatFilter.class differ
diff --git a/target/classes/com/minexd/zoot/chat/filter/impl/ContainsFilter.class b/target/classes/com/minexd/zoot/chat/filter/impl/ContainsFilter.class
new file mode 100644
index 0000000..ef69b9d
Binary files /dev/null and b/target/classes/com/minexd/zoot/chat/filter/impl/ContainsFilter.class differ
diff --git a/target/classes/com/minexd/zoot/chat/filter/impl/LinkFilter.class b/target/classes/com/minexd/zoot/chat/filter/impl/LinkFilter.class
new file mode 100644
index 0000000..699ea64
Binary files /dev/null and b/target/classes/com/minexd/zoot/chat/filter/impl/LinkFilter.class differ
diff --git a/target/classes/com/minexd/zoot/chat/util/ChatComponentBuilder.class b/target/classes/com/minexd/zoot/chat/util/ChatComponentBuilder.class
new file mode 100644
index 0000000..d0bf5db
Binary files /dev/null and b/target/classes/com/minexd/zoot/chat/util/ChatComponentBuilder.class differ
diff --git a/target/classes/com/minexd/zoot/config/ConfigConversion.class b/target/classes/com/minexd/zoot/config/ConfigConversion.class
new file mode 100644
index 0000000..55b1590
Binary files /dev/null and b/target/classes/com/minexd/zoot/config/ConfigConversion.class differ
diff --git a/target/classes/com/minexd/zoot/config/ConfigValidation.class b/target/classes/com/minexd/zoot/config/ConfigValidation.class
new file mode 100644
index 0000000..0c76de7
Binary files /dev/null and b/target/classes/com/minexd/zoot/config/ConfigValidation.class differ
diff --git a/target/classes/com/minexd/zoot/config/ConfigVersion.class b/target/classes/com/minexd/zoot/config/ConfigVersion.class
new file mode 100644
index 0000000..ff5ac70
Binary files /dev/null and b/target/classes/com/minexd/zoot/config/ConfigVersion.class differ
diff --git a/target/classes/com/minexd/zoot/config/impl/ConfigConversion1.class b/target/classes/com/minexd/zoot/config/impl/ConfigConversion1.class
new file mode 100644
index 0000000..397a62b
Binary files /dev/null and b/target/classes/com/minexd/zoot/config/impl/ConfigConversion1.class differ
diff --git a/target/classes/com/minexd/zoot/config/impl/ConfigConversion2.class b/target/classes/com/minexd/zoot/config/impl/ConfigConversion2.class
new file mode 100644
index 0000000..ac2b75f
Binary files /dev/null and b/target/classes/com/minexd/zoot/config/impl/ConfigConversion2.class differ
diff --git a/target/classes/com/minexd/zoot/config/impl/ConfigConversion3.class b/target/classes/com/minexd/zoot/config/impl/ConfigConversion3.class
new file mode 100644
index 0000000..ad7200c
Binary files /dev/null and b/target/classes/com/minexd/zoot/config/impl/ConfigConversion3.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/Essentials.class b/target/classes/com/minexd/zoot/essentials/Essentials.class
new file mode 100644
index 0000000..e2b735e
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/Essentials.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/EssentialsListener.class b/target/classes/com/minexd/zoot/essentials/EssentialsListener.class
new file mode 100644
index 0000000..df97638
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/EssentialsListener.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/BroadcastCommand.class b/target/classes/com/minexd/zoot/essentials/command/BroadcastCommand.class
new file mode 100644
index 0000000..e1942b4
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/BroadcastCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/ClearCommand.class b/target/classes/com/minexd/zoot/essentials/command/ClearCommand.class
new file mode 100644
index 0000000..ab7860c
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/ClearCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/DayCommand.class b/target/classes/com/minexd/zoot/essentials/command/DayCommand.class
new file mode 100644
index 0000000..501e74e
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/DayCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/GameModeCommand.class b/target/classes/com/minexd/zoot/essentials/command/GameModeCommand.class
new file mode 100644
index 0000000..32b6dfb
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/GameModeCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/HealCommand.class b/target/classes/com/minexd/zoot/essentials/command/HealCommand.class
new file mode 100644
index 0000000..8d99897
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/HealCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/HidePlayerCommand.class b/target/classes/com/minexd/zoot/essentials/command/HidePlayerCommand.class
new file mode 100644
index 0000000..6714dd6
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/HidePlayerCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/ListCommand$1.class b/target/classes/com/minexd/zoot/essentials/command/ListCommand$1.class
new file mode 100644
index 0000000..e248450
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/ListCommand$1.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/ListCommand$2.class b/target/classes/com/minexd/zoot/essentials/command/ListCommand$2.class
new file mode 100644
index 0000000..7826348
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/ListCommand$2.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/ListCommand.class b/target/classes/com/minexd/zoot/essentials/command/ListCommand.class
new file mode 100644
index 0000000..6cdfb25
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/ListCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/LocationCommand.class b/target/classes/com/minexd/zoot/essentials/command/LocationCommand.class
new file mode 100644
index 0000000..2e6e354
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/LocationCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/MoreCommand.class b/target/classes/com/minexd/zoot/essentials/command/MoreCommand.class
new file mode 100644
index 0000000..3e819a1
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/MoreCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/NightCommand.class b/target/classes/com/minexd/zoot/essentials/command/NightCommand.class
new file mode 100644
index 0000000..812e724
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/NightCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/PingCommand.class b/target/classes/com/minexd/zoot/essentials/command/PingCommand.class
new file mode 100644
index 0000000..3fa195f
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/PingCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/RenameCommand.class b/target/classes/com/minexd/zoot/essentials/command/RenameCommand.class
new file mode 100644
index 0000000..fbd31bb
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/RenameCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/SetSlotsCommand.class b/target/classes/com/minexd/zoot/essentials/command/SetSlotsCommand.class
new file mode 100644
index 0000000..ae6ce5d
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/SetSlotsCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/SetSpawnCommand.class b/target/classes/com/minexd/zoot/essentials/command/SetSpawnCommand.class
new file mode 100644
index 0000000..851d79e
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/SetSpawnCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/ShowPlayerCommand.class b/target/classes/com/minexd/zoot/essentials/command/ShowPlayerCommand.class
new file mode 100644
index 0000000..26dca60
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/ShowPlayerCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/SpawnCommand.class b/target/classes/com/minexd/zoot/essentials/command/SpawnCommand.class
new file mode 100644
index 0000000..fd698f4
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/SpawnCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/SunsetCommand.class b/target/classes/com/minexd/zoot/essentials/command/SunsetCommand.class
new file mode 100644
index 0000000..081d359
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/SunsetCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/TeleportWorldCommand.class b/target/classes/com/minexd/zoot/essentials/command/TeleportWorldCommand.class
new file mode 100644
index 0000000..6e24691
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/TeleportWorldCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/command/ZootDebugCommand.class b/target/classes/com/minexd/zoot/essentials/command/ZootDebugCommand.class
new file mode 100644
index 0000000..2ab20dd
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/command/ZootDebugCommand.class differ
diff --git a/target/classes/com/minexd/zoot/essentials/event/SpawnTeleportEvent.class b/target/classes/com/minexd/zoot/essentials/event/SpawnTeleportEvent.class
new file mode 100644
index 0000000..e7a9b41
Binary files /dev/null and b/target/classes/com/minexd/zoot/essentials/event/SpawnTeleportEvent.class differ
diff --git a/target/classes/com/minexd/zoot/network/NetworkPacketListener$1.class b/target/classes/com/minexd/zoot/network/NetworkPacketListener$1.class
new file mode 100644
index 0000000..13951a5
Binary files /dev/null and b/target/classes/com/minexd/zoot/network/NetworkPacketListener$1.class differ
diff --git a/target/classes/com/minexd/zoot/network/NetworkPacketListener.class b/target/classes/com/minexd/zoot/network/NetworkPacketListener.class
new file mode 100644
index 0000000..bd03437
Binary files /dev/null and b/target/classes/com/minexd/zoot/network/NetworkPacketListener.class differ
diff --git a/target/classes/com/minexd/zoot/network/event/ReceiveStaffChatEvent.class b/target/classes/com/minexd/zoot/network/event/ReceiveStaffChatEvent.class
new file mode 100644
index 0000000..219dd70
Binary files /dev/null and b/target/classes/com/minexd/zoot/network/event/ReceiveStaffChatEvent.class differ
diff --git a/target/classes/com/minexd/zoot/network/packet/PacketAddGrant.class b/target/classes/com/minexd/zoot/network/packet/PacketAddGrant.class
new file mode 100644
index 0000000..708dda7
Binary files /dev/null and b/target/classes/com/minexd/zoot/network/packet/PacketAddGrant.class differ
diff --git a/target/classes/com/minexd/zoot/network/packet/PacketBroadcastPunishment.class b/target/classes/com/minexd/zoot/network/packet/PacketBroadcastPunishment.class
new file mode 100644
index 0000000..09e8408
Binary files /dev/null and b/target/classes/com/minexd/zoot/network/packet/PacketBroadcastPunishment.class differ
diff --git a/target/classes/com/minexd/zoot/network/packet/PacketDeleteGrant.class b/target/classes/com/minexd/zoot/network/packet/PacketDeleteGrant.class
new file mode 100644
index 0000000..e13d14a
Binary files /dev/null and b/target/classes/com/minexd/zoot/network/packet/PacketDeleteGrant.class differ
diff --git a/target/classes/com/minexd/zoot/network/packet/PacketDeleteRank.class b/target/classes/com/minexd/zoot/network/packet/PacketDeleteRank.class
new file mode 100644
index 0000000..de922ee
Binary files /dev/null and b/target/classes/com/minexd/zoot/network/packet/PacketDeleteRank.class differ
diff --git a/target/classes/com/minexd/zoot/network/packet/PacketRefreshRank.class b/target/classes/com/minexd/zoot/network/packet/PacketRefreshRank.class
new file mode 100644
index 0000000..f3f4643
Binary files /dev/null and b/target/classes/com/minexd/zoot/network/packet/PacketRefreshRank.class differ
diff --git a/target/classes/com/minexd/zoot/network/packet/PacketStaffChat.class b/target/classes/com/minexd/zoot/network/packet/PacketStaffChat.class
new file mode 100644
index 0000000..686f149
Binary files /dev/null and b/target/classes/com/minexd/zoot/network/packet/PacketStaffChat.class differ
diff --git a/target/classes/com/minexd/zoot/network/packet/PacketStaffJoinNetwork.class b/target/classes/com/minexd/zoot/network/packet/PacketStaffJoinNetwork.class
new file mode 100644
index 0000000..cdd9c2f
Binary files /dev/null and b/target/classes/com/minexd/zoot/network/packet/PacketStaffJoinNetwork.class differ
diff --git a/target/classes/com/minexd/zoot/network/packet/PacketStaffLeaveNetwork.class b/target/classes/com/minexd/zoot/network/packet/PacketStaffLeaveNetwork.class
new file mode 100644
index 0000000..7c3c531
Binary files /dev/null and b/target/classes/com/minexd/zoot/network/packet/PacketStaffLeaveNetwork.class differ
diff --git a/target/classes/com/minexd/zoot/network/packet/PacketStaffSwitchServer.class b/target/classes/com/minexd/zoot/network/packet/PacketStaffSwitchServer.class
new file mode 100644
index 0000000..c14ae8d
Binary files /dev/null and b/target/classes/com/minexd/zoot/network/packet/PacketStaffSwitchServer.class differ
diff --git a/target/classes/com/minexd/zoot/pidgin/Pidgin$1.class b/target/classes/com/minexd/zoot/pidgin/Pidgin$1.class
new file mode 100644
index 0000000..44d3f03
Binary files /dev/null and b/target/classes/com/minexd/zoot/pidgin/Pidgin$1.class differ
diff --git a/target/classes/com/minexd/zoot/pidgin/Pidgin.class b/target/classes/com/minexd/zoot/pidgin/Pidgin.class
new file mode 100644
index 0000000..c773ed7
Binary files /dev/null and b/target/classes/com/minexd/zoot/pidgin/Pidgin.class differ
diff --git a/target/classes/com/minexd/zoot/pidgin/packet/Packet.class b/target/classes/com/minexd/zoot/pidgin/packet/Packet.class
new file mode 100644
index 0000000..a9dfe50
Binary files /dev/null and b/target/classes/com/minexd/zoot/pidgin/packet/Packet.class differ
diff --git a/target/classes/com/minexd/zoot/pidgin/packet/handler/IncomingPacketHandler.class b/target/classes/com/minexd/zoot/pidgin/packet/handler/IncomingPacketHandler.class
new file mode 100644
index 0000000..62bf95a
Binary files /dev/null and b/target/classes/com/minexd/zoot/pidgin/packet/handler/IncomingPacketHandler.class differ
diff --git a/target/classes/com/minexd/zoot/pidgin/packet/handler/PacketExceptionHandler.class b/target/classes/com/minexd/zoot/pidgin/packet/handler/PacketExceptionHandler.class
new file mode 100644
index 0000000..94990e4
Binary files /dev/null and b/target/classes/com/minexd/zoot/pidgin/packet/handler/PacketExceptionHandler.class differ
diff --git a/target/classes/com/minexd/zoot/pidgin/packet/listener/PacketListener.class b/target/classes/com/minexd/zoot/pidgin/packet/listener/PacketListener.class
new file mode 100644
index 0000000..aba80b7
Binary files /dev/null and b/target/classes/com/minexd/zoot/pidgin/packet/listener/PacketListener.class differ
diff --git a/target/classes/com/minexd/zoot/pidgin/packet/listener/PacketListenerData.class b/target/classes/com/minexd/zoot/pidgin/packet/listener/PacketListenerData.class
new file mode 100644
index 0000000..69c372f
Binary files /dev/null and b/target/classes/com/minexd/zoot/pidgin/packet/listener/PacketListenerData.class differ
diff --git a/target/classes/com/minexd/zoot/profile/Profile.class b/target/classes/com/minexd/zoot/profile/Profile.class
new file mode 100644
index 0000000..076bc41
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/Profile.class differ
diff --git a/target/classes/com/minexd/zoot/profile/ProfileListener$1.class b/target/classes/com/minexd/zoot/profile/ProfileListener$1.class
new file mode 100644
index 0000000..39e8425
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/ProfileListener$1.class differ
diff --git a/target/classes/com/minexd/zoot/profile/ProfileListener.class b/target/classes/com/minexd/zoot/profile/ProfileListener.class
new file mode 100644
index 0000000..a1f8a79
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/ProfileListener.class differ
diff --git a/target/classes/com/minexd/zoot/profile/ProfileTypeAdapter.class b/target/classes/com/minexd/zoot/profile/ProfileTypeAdapter.class
new file mode 100644
index 0000000..efef43c
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/ProfileTypeAdapter.class differ
diff --git a/target/classes/com/minexd/zoot/profile/conversation/Conversation.class b/target/classes/com/minexd/zoot/profile/conversation/Conversation.class
new file mode 100644
index 0000000..15cba7d
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/conversation/Conversation.class differ
diff --git a/target/classes/com/minexd/zoot/profile/conversation/ProfileConversations.class b/target/classes/com/minexd/zoot/profile/conversation/ProfileConversations.class
new file mode 100644
index 0000000..91c0c87
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/conversation/ProfileConversations.class differ
diff --git a/target/classes/com/minexd/zoot/profile/conversation/command/MessageCommand.class b/target/classes/com/minexd/zoot/profile/conversation/command/MessageCommand.class
new file mode 100644
index 0000000..ac9fc98
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/conversation/command/MessageCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/conversation/command/ReplyCommand.class b/target/classes/com/minexd/zoot/profile/conversation/command/ReplyCommand.class
new file mode 100644
index 0000000..2fa3f88
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/conversation/command/ReplyCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/grant/Grant.class b/target/classes/com/minexd/zoot/profile/grant/Grant.class
new file mode 100644
index 0000000..c30bbf8
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/grant/Grant.class differ
diff --git a/target/classes/com/minexd/zoot/profile/grant/GrantJsonDeserializer.class b/target/classes/com/minexd/zoot/profile/grant/GrantJsonDeserializer.class
new file mode 100644
index 0000000..a39d509
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/grant/GrantJsonDeserializer.class differ
diff --git a/target/classes/com/minexd/zoot/profile/grant/GrantJsonSerializer.class b/target/classes/com/minexd/zoot/profile/grant/GrantJsonSerializer.class
new file mode 100644
index 0000000..67419f0
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/grant/GrantJsonSerializer.class differ
diff --git a/target/classes/com/minexd/zoot/profile/grant/GrantListener$1.class b/target/classes/com/minexd/zoot/profile/grant/GrantListener$1.class
new file mode 100644
index 0000000..477e97a
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/grant/GrantListener$1.class differ
diff --git a/target/classes/com/minexd/zoot/profile/grant/GrantListener$2.class b/target/classes/com/minexd/zoot/profile/grant/GrantListener$2.class
new file mode 100644
index 0000000..65cf7d6
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/grant/GrantListener$2.class differ
diff --git a/target/classes/com/minexd/zoot/profile/grant/GrantListener.class b/target/classes/com/minexd/zoot/profile/grant/GrantListener.class
new file mode 100644
index 0000000..7922c41
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/grant/GrantListener.class differ
diff --git a/target/classes/com/minexd/zoot/profile/grant/command/GrantCommand.class b/target/classes/com/minexd/zoot/profile/grant/command/GrantCommand.class
new file mode 100644
index 0000000..a74a2a2
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/grant/command/GrantCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/grant/command/GrantsCommand.class b/target/classes/com/minexd/zoot/profile/grant/command/GrantsCommand.class
new file mode 100644
index 0000000..41590c1
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/grant/command/GrantsCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/grant/event/GrantAppliedEvent.class b/target/classes/com/minexd/zoot/profile/grant/event/GrantAppliedEvent.class
new file mode 100644
index 0000000..9d23d7d
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/grant/event/GrantAppliedEvent.class differ
diff --git a/target/classes/com/minexd/zoot/profile/grant/event/GrantExpireEvent.class b/target/classes/com/minexd/zoot/profile/grant/event/GrantExpireEvent.class
new file mode 100644
index 0000000..63d6b98
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/grant/event/GrantExpireEvent.class differ
diff --git a/target/classes/com/minexd/zoot/profile/grant/menu/GrantsListMenu$GrantInfoButton.class b/target/classes/com/minexd/zoot/profile/grant/menu/GrantsListMenu$GrantInfoButton.class
new file mode 100644
index 0000000..2c71007
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/grant/menu/GrantsListMenu$GrantInfoButton.class differ
diff --git a/target/classes/com/minexd/zoot/profile/grant/menu/GrantsListMenu.class b/target/classes/com/minexd/zoot/profile/grant/menu/GrantsListMenu.class
new file mode 100644
index 0000000..6b49eba
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/grant/menu/GrantsListMenu.class differ
diff --git a/target/classes/com/minexd/zoot/profile/grant/procedure/GrantProcedure.class b/target/classes/com/minexd/zoot/profile/grant/procedure/GrantProcedure.class
new file mode 100644
index 0000000..fcc0be7
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/grant/procedure/GrantProcedure.class differ
diff --git a/target/classes/com/minexd/zoot/profile/grant/procedure/GrantProcedureStage.class b/target/classes/com/minexd/zoot/profile/grant/procedure/GrantProcedureStage.class
new file mode 100644
index 0000000..7a1b821
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/grant/procedure/GrantProcedureStage.class differ
diff --git a/target/classes/com/minexd/zoot/profile/grant/procedure/GrantProcedureType.class b/target/classes/com/minexd/zoot/profile/grant/procedure/GrantProcedureType.class
new file mode 100644
index 0000000..dd4bbf0
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/grant/procedure/GrantProcedureType.class differ
diff --git a/target/classes/com/minexd/zoot/profile/menu/ProfileMenuControlHeaderButton.class b/target/classes/com/minexd/zoot/profile/menu/ProfileMenuControlHeaderButton.class
new file mode 100644
index 0000000..974e200
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/menu/ProfileMenuControlHeaderButton.class differ
diff --git a/target/classes/com/minexd/zoot/profile/option/ProfileOptions.class b/target/classes/com/minexd/zoot/profile/option/ProfileOptions.class
new file mode 100644
index 0000000..44d6c4a
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/option/ProfileOptions.class differ
diff --git a/target/classes/com/minexd/zoot/profile/option/command/OptionsCommand.class b/target/classes/com/minexd/zoot/profile/option/command/OptionsCommand.class
new file mode 100644
index 0000000..26b0d36
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/option/command/OptionsCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/option/command/ToggleGlobalChatCommand.class b/target/classes/com/minexd/zoot/profile/option/command/ToggleGlobalChatCommand.class
new file mode 100644
index 0000000..fb9f12f
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/option/command/ToggleGlobalChatCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/option/command/TogglePrivateMessagesCommand.class b/target/classes/com/minexd/zoot/profile/option/command/TogglePrivateMessagesCommand.class
new file mode 100644
index 0000000..129f39e
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/option/command/TogglePrivateMessagesCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/option/command/ToggleSoundsCommand.class b/target/classes/com/minexd/zoot/profile/option/command/ToggleSoundsCommand.class
new file mode 100644
index 0000000..adb3a23
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/option/command/ToggleSoundsCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/option/event/OptionsOpenedEvent.class b/target/classes/com/minexd/zoot/profile/option/event/OptionsOpenedEvent.class
new file mode 100644
index 0000000..eed89e1
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/option/event/OptionsOpenedEvent.class differ
diff --git a/target/classes/com/minexd/zoot/profile/option/menu/ProfileOptionButton.class b/target/classes/com/minexd/zoot/profile/option/menu/ProfileOptionButton.class
new file mode 100644
index 0000000..5d9f6c8
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/option/menu/ProfileOptionButton.class differ
diff --git a/target/classes/com/minexd/zoot/profile/option/menu/ProfileOptionsMenu.class b/target/classes/com/minexd/zoot/profile/option/menu/ProfileOptionsMenu.class
new file mode 100644
index 0000000..a0f973c
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/option/menu/ProfileOptionsMenu.class differ
diff --git a/target/classes/com/minexd/zoot/profile/option/menu/button/PrivateChatOptionButton.class b/target/classes/com/minexd/zoot/profile/option/menu/button/PrivateChatOptionButton.class
new file mode 100644
index 0000000..c0a9201
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/option/menu/button/PrivateChatOptionButton.class differ
diff --git a/target/classes/com/minexd/zoot/profile/option/menu/button/PrivateChatSoundsOptionButton.class b/target/classes/com/minexd/zoot/profile/option/menu/button/PrivateChatSoundsOptionButton.class
new file mode 100644
index 0000000..815a673
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/option/menu/button/PrivateChatSoundsOptionButton.class differ
diff --git a/target/classes/com/minexd/zoot/profile/option/menu/button/PublicChatOptionButton.class b/target/classes/com/minexd/zoot/profile/option/menu/button/PublicChatOptionButton.class
new file mode 100644
index 0000000..f2c367b
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/option/menu/button/PublicChatOptionButton.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/Punishment.class b/target/classes/com/minexd/zoot/profile/punishment/Punishment.class
new file mode 100644
index 0000000..6c7684b
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/Punishment.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/PunishmentJsonDeserializer.class b/target/classes/com/minexd/zoot/profile/punishment/PunishmentJsonDeserializer.class
new file mode 100644
index 0000000..e580f80
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/PunishmentJsonDeserializer.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/PunishmentJsonSerializer.class b/target/classes/com/minexd/zoot/profile/punishment/PunishmentJsonSerializer.class
new file mode 100644
index 0000000..aec023a
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/PunishmentJsonSerializer.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/PunishmentType$PunishmentTypeData.class b/target/classes/com/minexd/zoot/profile/punishment/PunishmentType$PunishmentTypeData.class
new file mode 100644
index 0000000..47e571e
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/PunishmentType$PunishmentTypeData.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/PunishmentType.class b/target/classes/com/minexd/zoot/profile/punishment/PunishmentType.class
new file mode 100644
index 0000000..d47d02f
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/PunishmentType.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/command/BanCommand$1.class b/target/classes/com/minexd/zoot/profile/punishment/command/BanCommand$1.class
new file mode 100644
index 0000000..178384b
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/command/BanCommand$1.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/command/BanCommand.class b/target/classes/com/minexd/zoot/profile/punishment/command/BanCommand.class
new file mode 100644
index 0000000..d61f0d6
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/command/BanCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/command/CheckCommand.class b/target/classes/com/minexd/zoot/profile/punishment/command/CheckCommand.class
new file mode 100644
index 0000000..e0808cb
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/command/CheckCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/command/KickCommand$1.class b/target/classes/com/minexd/zoot/profile/punishment/command/KickCommand$1.class
new file mode 100644
index 0000000..fa31477
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/command/KickCommand$1.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/command/KickCommand.class b/target/classes/com/minexd/zoot/profile/punishment/command/KickCommand.class
new file mode 100644
index 0000000..a7ad583
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/command/KickCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/command/MuteCommand.class b/target/classes/com/minexd/zoot/profile/punishment/command/MuteCommand.class
new file mode 100644
index 0000000..1d3ff10
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/command/MuteCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/command/UnbanCommand.class b/target/classes/com/minexd/zoot/profile/punishment/command/UnbanCommand.class
new file mode 100644
index 0000000..25dc4c0
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/command/UnbanCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/command/UnmuteCommand.class b/target/classes/com/minexd/zoot/profile/punishment/command/UnmuteCommand.class
new file mode 100644
index 0000000..a262dfa
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/command/UnmuteCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/command/WarnCommand.class b/target/classes/com/minexd/zoot/profile/punishment/command/WarnCommand.class
new file mode 100644
index 0000000..0429e57
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/command/WarnCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/listener/PunishmentListener$1.class b/target/classes/com/minexd/zoot/profile/punishment/listener/PunishmentListener$1.class
new file mode 100644
index 0000000..93d9d19
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/listener/PunishmentListener$1.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/listener/PunishmentListener$2.class b/target/classes/com/minexd/zoot/profile/punishment/listener/PunishmentListener$2.class
new file mode 100644
index 0000000..4e05179
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/listener/PunishmentListener$2.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/listener/PunishmentListener.class b/target/classes/com/minexd/zoot/profile/punishment/listener/PunishmentListener.class
new file mode 100644
index 0000000..e234af9
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/listener/PunishmentListener.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/menu/PunishmentsListMenu$PunishmentInfoButton.class b/target/classes/com/minexd/zoot/profile/punishment/menu/PunishmentsListMenu$PunishmentInfoButton.class
new file mode 100644
index 0000000..a517af3
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/menu/PunishmentsListMenu$PunishmentInfoButton.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/menu/PunishmentsListMenu.class b/target/classes/com/minexd/zoot/profile/punishment/menu/PunishmentsListMenu.class
new file mode 100644
index 0000000..c78f137
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/menu/PunishmentsListMenu.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedure.class b/target/classes/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedure.class
new file mode 100644
index 0000000..3ea48b2
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedure.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedureStage.class b/target/classes/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedureStage.class
new file mode 100644
index 0000000..ed2c73c
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedureStage.class differ
diff --git a/target/classes/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedureType.class b/target/classes/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedureType.class
new file mode 100644
index 0000000..3ef300e
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/punishment/procedure/PunishmentProcedureType.class differ
diff --git a/target/classes/com/minexd/zoot/profile/staff/ProfileStaffOptions.class b/target/classes/com/minexd/zoot/profile/staff/ProfileStaffOptions.class
new file mode 100644
index 0000000..2ee500e
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/staff/ProfileStaffOptions.class differ
diff --git a/target/classes/com/minexd/zoot/profile/staff/command/AltsCommand.class b/target/classes/com/minexd/zoot/profile/staff/command/AltsCommand.class
new file mode 100644
index 0000000..465ec61
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/staff/command/AltsCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/staff/command/StaffChatCommand.class b/target/classes/com/minexd/zoot/profile/staff/command/StaffChatCommand.class
new file mode 100644
index 0000000..8447317
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/staff/command/StaffChatCommand.class differ
diff --git a/target/classes/com/minexd/zoot/profile/staff/command/StaffModeCommand.class b/target/classes/com/minexd/zoot/profile/staff/command/StaffModeCommand.class
new file mode 100644
index 0000000..f633a92
Binary files /dev/null and b/target/classes/com/minexd/zoot/profile/staff/command/StaffModeCommand.class differ
diff --git a/target/classes/com/minexd/zoot/rank/Rank.class b/target/classes/com/minexd/zoot/rank/Rank.class
new file mode 100644
index 0000000..5b2c801
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/Rank.class differ
diff --git a/target/classes/com/minexd/zoot/rank/RankTypeAdapter.class b/target/classes/com/minexd/zoot/rank/RankTypeAdapter.class
new file mode 100644
index 0000000..d6dd2c4
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/RankTypeAdapter.class differ
diff --git a/target/classes/com/minexd/zoot/rank/command/RankAddPermissionCommand.class b/target/classes/com/minexd/zoot/rank/command/RankAddPermissionCommand.class
new file mode 100644
index 0000000..7787c5c
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/command/RankAddPermissionCommand.class differ
diff --git a/target/classes/com/minexd/zoot/rank/command/RankCreateCommand.class b/target/classes/com/minexd/zoot/rank/command/RankCreateCommand.class
new file mode 100644
index 0000000..4b6f7d8
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/command/RankCreateCommand.class differ
diff --git a/target/classes/com/minexd/zoot/rank/command/RankDeleteCommand.class b/target/classes/com/minexd/zoot/rank/command/RankDeleteCommand.class
new file mode 100644
index 0000000..7d5a4d9
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/command/RankDeleteCommand.class differ
diff --git a/target/classes/com/minexd/zoot/rank/command/RankHelpCommand.class b/target/classes/com/minexd/zoot/rank/command/RankHelpCommand.class
new file mode 100644
index 0000000..bfd40b7
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/command/RankHelpCommand.class differ
diff --git a/target/classes/com/minexd/zoot/rank/command/RankInfoCommand.class b/target/classes/com/minexd/zoot/rank/command/RankInfoCommand.class
new file mode 100644
index 0000000..5bf1400
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/command/RankInfoCommand.class differ
diff --git a/target/classes/com/minexd/zoot/rank/command/RankInheritCommand.class b/target/classes/com/minexd/zoot/rank/command/RankInheritCommand.class
new file mode 100644
index 0000000..9dc8049
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/command/RankInheritCommand.class differ
diff --git a/target/classes/com/minexd/zoot/rank/command/RankRemovePermissionCommand.class b/target/classes/com/minexd/zoot/rank/command/RankRemovePermissionCommand.class
new file mode 100644
index 0000000..ba15d72
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/command/RankRemovePermissionCommand.class differ
diff --git a/target/classes/com/minexd/zoot/rank/command/RankSetColorCommand.class b/target/classes/com/minexd/zoot/rank/command/RankSetColorCommand.class
new file mode 100644
index 0000000..1a58dc0
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/command/RankSetColorCommand.class differ
diff --git a/target/classes/com/minexd/zoot/rank/command/RankSetPrefixCommand.class b/target/classes/com/minexd/zoot/rank/command/RankSetPrefixCommand.class
new file mode 100644
index 0000000..5a2cef2
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/command/RankSetPrefixCommand.class differ
diff --git a/target/classes/com/minexd/zoot/rank/command/RankSetSuffixCommand.class b/target/classes/com/minexd/zoot/rank/command/RankSetSuffixCommand.class
new file mode 100644
index 0000000..af2f167
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/command/RankSetSuffixCommand.class differ
diff --git a/target/classes/com/minexd/zoot/rank/command/RankSetWeightCommand.class b/target/classes/com/minexd/zoot/rank/command/RankSetWeightCommand.class
new file mode 100644
index 0000000..27790e9
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/command/RankSetWeightCommand.class differ
diff --git a/target/classes/com/minexd/zoot/rank/command/RankUninheritCommand.class b/target/classes/com/minexd/zoot/rank/command/RankUninheritCommand.class
new file mode 100644
index 0000000..d2bda37
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/command/RankUninheritCommand.class differ
diff --git a/target/classes/com/minexd/zoot/rank/command/RanksCommand$1.class b/target/classes/com/minexd/zoot/rank/command/RanksCommand$1.class
new file mode 100644
index 0000000..d6eaec6
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/command/RanksCommand$1.class differ
diff --git a/target/classes/com/minexd/zoot/rank/command/RanksCommand.class b/target/classes/com/minexd/zoot/rank/command/RanksCommand.class
new file mode 100644
index 0000000..2f614bf
Binary files /dev/null and b/target/classes/com/minexd/zoot/rank/command/RanksCommand.class differ
diff --git a/target/classes/com/minexd/zoot/util/BaseEvent.class b/target/classes/com/minexd/zoot/util/BaseEvent.class
new file mode 100644
index 0000000..fe34a60
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/BaseEvent.class differ
diff --git a/target/classes/com/minexd/zoot/util/BukkitReflection.class b/target/classes/com/minexd/zoot/util/BukkitReflection.class
new file mode 100644
index 0000000..d5b4a6c
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/BukkitReflection.class differ
diff --git a/target/classes/com/minexd/zoot/util/CC.class b/target/classes/com/minexd/zoot/util/CC.class
new file mode 100644
index 0000000..2171ccb
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/CC.class differ
diff --git a/target/classes/com/minexd/zoot/util/ChatHelper.class b/target/classes/com/minexd/zoot/util/ChatHelper.class
new file mode 100644
index 0000000..7081478
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/ChatHelper.class differ
diff --git a/target/classes/com/minexd/zoot/util/Cooldown.class b/target/classes/com/minexd/zoot/util/Cooldown.class
new file mode 100644
index 0000000..458d524
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/Cooldown.class differ
diff --git a/target/classes/com/minexd/zoot/util/ItemBuilder.class b/target/classes/com/minexd/zoot/util/ItemBuilder.class
new file mode 100644
index 0000000..6a07477
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/ItemBuilder.class differ
diff --git a/target/classes/com/minexd/zoot/util/LocationUtil.class b/target/classes/com/minexd/zoot/util/LocationUtil.class
new file mode 100644
index 0000000..0c8889f
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/LocationUtil.class differ
diff --git a/target/classes/com/minexd/zoot/util/PotionUtil.class b/target/classes/com/minexd/zoot/util/PotionUtil.class
new file mode 100644
index 0000000..ae2ce7e
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/PotionUtil.class differ
diff --git a/target/classes/com/minexd/zoot/util/TextSplitter.class b/target/classes/com/minexd/zoot/util/TextSplitter.class
new file mode 100644
index 0000000..8267721
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/TextSplitter.class differ
diff --git a/target/classes/com/minexd/zoot/util/TimeUtil.class b/target/classes/com/minexd/zoot/util/TimeUtil.class
new file mode 100644
index 0000000..3121d87
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/TimeUtil.class differ
diff --git a/target/classes/com/minexd/zoot/util/adapter/ChatColorTypeAdapter.class b/target/classes/com/minexd/zoot/util/adapter/ChatColorTypeAdapter.class
new file mode 100644
index 0000000..8029af9
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/adapter/ChatColorTypeAdapter.class differ
diff --git a/target/classes/com/minexd/zoot/util/callback/Callback.class b/target/classes/com/minexd/zoot/util/callback/Callback.class
new file mode 100644
index 0000000..f517081
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/callback/Callback.class differ
diff --git a/target/classes/com/minexd/zoot/util/callback/ReturnableTypeCallback.class b/target/classes/com/minexd/zoot/util/callback/ReturnableTypeCallback.class
new file mode 100644
index 0000000..a922cb1
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/callback/ReturnableTypeCallback.class differ
diff --git a/target/classes/com/minexd/zoot/util/callback/TypeCallback.class b/target/classes/com/minexd/zoot/util/callback/TypeCallback.class
new file mode 100644
index 0000000..a3c6a53
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/callback/TypeCallback.class differ
diff --git a/target/classes/com/minexd/zoot/util/duration/Duration.class b/target/classes/com/minexd/zoot/util/duration/Duration.class
new file mode 100644
index 0000000..96da0da
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/duration/Duration.class differ
diff --git a/target/classes/com/minexd/zoot/util/duration/DurationTypeAdapter.class b/target/classes/com/minexd/zoot/util/duration/DurationTypeAdapter.class
new file mode 100644
index 0000000..af654e7
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/duration/DurationTypeAdapter.class differ
diff --git a/target/classes/com/minexd/zoot/util/json/JsonChain.class b/target/classes/com/minexd/zoot/util/json/JsonChain.class
new file mode 100644
index 0000000..d691b62
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/json/JsonChain.class differ
diff --git a/target/classes/com/minexd/zoot/util/json/JsonDeserializer.class b/target/classes/com/minexd/zoot/util/json/JsonDeserializer.class
new file mode 100644
index 0000000..c8f38a5
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/json/JsonDeserializer.class differ
diff --git a/target/classes/com/minexd/zoot/util/json/JsonSerializer.class b/target/classes/com/minexd/zoot/util/json/JsonSerializer.class
new file mode 100644
index 0000000..cd97abe
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/json/JsonSerializer.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/Button$1.class b/target/classes/com/minexd/zoot/util/menu/Button$1.class
new file mode 100644
index 0000000..60e9924
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/Button$1.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/Button.class b/target/classes/com/minexd/zoot/util/menu/Button.class
new file mode 100644
index 0000000..ee3e3dd
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/Button.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/Menu.class b/target/classes/com/minexd/zoot/util/menu/Menu.class
new file mode 100644
index 0000000..948b45c
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/Menu.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/MenuListener.class b/target/classes/com/minexd/zoot/util/menu/MenuListener.class
new file mode 100644
index 0000000..79c3e7f
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/MenuListener.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/button/BackButton.class b/target/classes/com/minexd/zoot/util/menu/button/BackButton.class
new file mode 100644
index 0000000..6cc64bd
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/button/BackButton.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/button/ConfirmationButton.class b/target/classes/com/minexd/zoot/util/menu/button/ConfirmationButton.class
new file mode 100644
index 0000000..b194e6a
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/button/ConfirmationButton.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/button/DisplayButton.class b/target/classes/com/minexd/zoot/util/menu/button/DisplayButton.class
new file mode 100644
index 0000000..91f0b41
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/button/DisplayButton.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/button/JumpToMenuButton.class b/target/classes/com/minexd/zoot/util/menu/button/JumpToMenuButton.class
new file mode 100644
index 0000000..4e9468e
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/button/JumpToMenuButton.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/menus/ConfirmMenu.class b/target/classes/com/minexd/zoot/util/menu/menus/ConfirmMenu.class
new file mode 100644
index 0000000..b0500d9
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/menus/ConfirmMenu.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/pagination/FilterablePaginatedMenu.class b/target/classes/com/minexd/zoot/util/menu/pagination/FilterablePaginatedMenu.class
new file mode 100644
index 0000000..b341e0b
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/pagination/FilterablePaginatedMenu.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/pagination/JumpToPageButton.class b/target/classes/com/minexd/zoot/util/menu/pagination/JumpToPageButton.class
new file mode 100644
index 0000000..30f23a0
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/pagination/JumpToPageButton.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/pagination/PageButton.class b/target/classes/com/minexd/zoot/util/menu/pagination/PageButton.class
new file mode 100644
index 0000000..30715de
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/pagination/PageButton.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/pagination/PageFilter.class b/target/classes/com/minexd/zoot/util/menu/pagination/PageFilter.class
new file mode 100644
index 0000000..eb053d2
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/pagination/PageFilter.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/pagination/PageFilterButton.class b/target/classes/com/minexd/zoot/util/menu/pagination/PageFilterButton.class
new file mode 100644
index 0000000..99360d8
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/pagination/PageFilterButton.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/pagination/PageInfoButton.class b/target/classes/com/minexd/zoot/util/menu/pagination/PageInfoButton.class
new file mode 100644
index 0000000..e29f699
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/pagination/PageInfoButton.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/pagination/PaginatedMenu.class b/target/classes/com/minexd/zoot/util/menu/pagination/PaginatedMenu.class
new file mode 100644
index 0000000..a43c9d8
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/pagination/PaginatedMenu.class differ
diff --git a/target/classes/com/minexd/zoot/util/menu/pagination/ViewAllPagesMenu.class b/target/classes/com/minexd/zoot/util/menu/pagination/ViewAllPagesMenu.class
new file mode 100644
index 0000000..5377dff
Binary files /dev/null and b/target/classes/com/minexd/zoot/util/menu/pagination/ViewAllPagesMenu.class differ
diff --git a/target/classes/config.yml b/target/classes/config.yml
new file mode 100644
index 0000000..0a56f62
--- /dev/null
+++ b/target/classes/config.yml
@@ -0,0 +1,60 @@
+CONFIG_VERSION: 1
+SERVER_NAME: "Development"
+MONGO:
+ HOST: "127.0.0.1"
+ PORT: 27017
+ AUTHENTICATION:
+ ENABLED: false
+ USERNAME: ""
+ PASSWORD: ""
+REDIS:
+ HOST: "127.0.0.1"
+ PORT: 6379
+ AUTHENTICATION:
+ ENABLED: false
+ PASSWORD: ""
+SETTINGS:
+ UPDATE_PLAYER_LIST_NAME: true
+PUNISHMENTS:
+ BROADCAST: "&r{target} &ahas been {context} by &r{sender}"
+ BROADCAST_SILENT: "&7[Silent] &r{target} &ahas been {context} by &r{sender}"
+ BAN:
+ KICK: "&cYour account is {context} from Zonix.{temporary}\n\n&cIf you feel this punishment is unjust, you may appeal at:\n&ehttps://www.zonix.com"
+ TEMPORARY: "\n&cThis punishment expires in &e{time-remaining}&c."
+ KICK:
+ KICK: "&cYou were kicked by a staff member.\nReason: &e{reason}"
+COMMON_ERRORS:
+ FAILED_TO_LOAD_PROFILE: "&cFailed to load your profile. Try again later."
+ COULD_NOT_RESOLVE_PLAYER: "&cCould not resolve player information..."
+ PLAYER_NOT_FOUND: "&cA player with that name could not be found."
+ RANK_NOT_FOUND: "&cA rank with that name could not be found."
+STAFF:
+ CHAT: "{0}&r{1}&b ({2}): {3}"
+ BROADCAST_PREFIX: "&9&l[Staff] &r"
+ JOIN_NETWORK: "{0}{1} &ajoined the network &2({2})"
+ LEAVE_NETWORK: "{0}{1} &aleft the network"
+ SWITCH_SERVER: "{0}({1} &ajoined {2} &2(from {3})"
+NETWORK:
+ BROADCAST_PREFIX: "&8[&eNetwork&8] &f"
+ RANK_REFRESH: "{0}Refreshed rank \"{1}\""
+ RANK_DELETE: "{0}Deleted rank \"{1}\""
+CHAT:
+ FORMAT: "%1$s&r: %2$s"
+ CLEAR_CHAT_BROADCAST: "&eThe chat has been cleared by &r{0}"
+ CLEAR_CHAT_FOR_STAFF: false
+ MUTE_CHAT_BROADCAST: "&eThe chat has been {0} by &r{1}"
+ DELAY_CHAT_ENABLED_BROADCAST: "&eThe chat has been delayed by &r{0} &7({1} second{2})"
+ DELAY_CHAT_DISABLED_BROADCAST: "&eThe chat delay has been lifted by &r{0}"
+ CHAT_DELAYED: "&cSlow down! You may chat again in {0} seconds."
+# using both text formatter and text replacing because I need to escape colors in the input message
+# format: player name, player display name, player color, target name, target display name, target color
+CONVERSATION:
+ SEND_MESSAGE: "&7(To &r{5}{3}&7) %MSG%"
+ RECEIVE_MESSAGE: "&7(From &r{5}{3}&7) %MSG%"
+OPTIONS:
+ GLOBAL_CHAT_ENABLED: "&eYou can now see global chat."
+ GLOBAL_CHAT_DISABLED: "&cYou will no longer see global chat."
+ PRIVATE_MESSAGES_ENABLED: "&aYou can now receive new conversations."
+ PRIVATE_MESSAGES_DISABLED: "&cYou can no longer receive new conversations. If you start a conversation with a player, they will be able to message you back."
+ PRIVATE_MESSAGE_SOUNDS_ENABLED: "&eYou enabled private message sounds."
+ PRIVATE_MESSAGE_SOUNDS_DISABLED: "&cYou disabled private message sounds."
\ No newline at end of file
diff --git a/target/classes/plugin.yml b/target/classes/plugin.yml
new file mode 100644
index 0000000..1f4e2dd
--- /dev/null
+++ b/target/classes/plugin.yml
@@ -0,0 +1,5 @@
+main: com.minexd.zoot.Zoot
+name: Zoot
+author: joeleoli
+version: 1.0
+depend: [ProtocolLib]
\ No newline at end of file
diff --git a/target/maven-archiver/pom.properties b/target/maven-archiver/pom.properties
new file mode 100644
index 0000000..4f1fe29
--- /dev/null
+++ b/target/maven-archiver/pom.properties
@@ -0,0 +1,5 @@
+#Generated by Maven
+#Sun Feb 17 16:58:10 EST 2019
+version=1.0-SNAPSHOT
+groupId=com.minexd
+artifactId=zoot
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
new file mode 100644
index 0000000..0dfb432
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/createdFiles.lst
@@ -0,0 +1,185 @@
+com\minexd\zoot\util\menu\pagination\FilterablePaginatedMenu.class
+com\minexd\zoot\Zoot$2.class
+com\minexd\zoot\network\packet\PacketRefreshRank.class
+com\minexd\zoot\profile\grant\GrantJsonDeserializer.class
+com\minexd\zoot\rank\command\RankSetSuffixCommand.class
+com\minexd\zoot\pidgin\packet\handler\IncomingPacketHandler.class
+com\minexd\zoot\essentials\command\SpawnCommand.class
+com\minexd\zoot\config\ConfigValidation.class
+com\minexd\zoot\util\menu\MenuListener.class
+com\minexd\zoot\Locale.class
+com\minexd\zoot\util\LocationUtil.class
+com\minexd\zoot\util\menu\Button$1.class
+com\minexd\zoot\util\menu\Menu.class
+com\minexd\zoot\profile\punishment\menu\PunishmentsListMenu$PunishmentInfoButton.class
+com\minexd\zoot\chat\ChatListener$1.class
+com\minexd\zoot\profile\option\ProfileOptions.class
+com\minexd\zoot\profile\grant\procedure\GrantProcedure.class
+com\minexd\zoot\util\callback\TypeCallback.class
+com\minexd\zoot\util\ChatHelper.class
+com\minexd\zoot\ZootAPI.class
+com\minexd\zoot\profile\staff\command\StaffModeCommand.class
+com\minexd\zoot\profile\conversation\command\ReplyCommand.class
+com\minexd\zoot\chat\filter\impl\LinkFilter.class
+com\minexd\zoot\profile\ProfileListener.class
+com\minexd\zoot\profile\grant\menu\GrantsListMenu.class
+com\minexd\zoot\util\BaseEvent.class
+com\minexd\zoot\rank\command\RankAddPermissionCommand.class
+com\minexd\zoot\rank\command\RanksCommand.class
+com\minexd\zoot\profile\conversation\ProfileConversations.class
+com\minexd\zoot\util\Cooldown.class
+com\minexd\zoot\profile\option\menu\button\PrivateChatOptionButton.class
+com\minexd\zoot\network\packet\PacketStaffLeaveNetwork.class
+com\minexd\zoot\util\TimeUtil.class
+com\minexd\zoot\profile\staff\ProfileStaffOptions.class
+com\minexd\zoot\chat\command\MuteChatCommand.class
+com\minexd\zoot\rank\command\RankDeleteCommand.class
+com\minexd\zoot\essentials\event\SpawnTeleportEvent.class
+com\minexd\zoot\profile\grant\event\GrantAppliedEvent.class
+com\minexd\zoot\rank\command\RankRemovePermissionCommand.class
+com\minexd\zoot\rank\command\RankSetPrefixCommand.class
+com\minexd\zoot\chat\ChatListener.class
+com\minexd\zoot\essentials\command\NightCommand.class
+com\minexd\zoot\network\packet\PacketAddGrant.class
+com\minexd\zoot\rank\command\RankUninheritCommand.class
+com\minexd\zoot\bootstrap\Bootstrapped.class
+com\minexd\zoot\pidgin\packet\listener\PacketListenerData.class
+com\minexd\zoot\profile\staff\command\AltsCommand.class
+com\minexd\zoot\profile\punishment\menu\PunishmentsListMenu.class
+com\minexd\zoot\profile\punishment\command\MuteCommand.class
+com\minexd\zoot\util\CC.class
+com\minexd\zoot\essentials\command\PingCommand.class
+com\minexd\zoot\essentials\command\ClearCommand.class
+com\minexd\zoot\config\impl\ConfigConversion3.class
+com\minexd\zoot\profile\punishment\procedure\PunishmentProcedureStage.class
+com\minexd\zoot\util\PotionUtil.class
+com\minexd\zoot\rank\RankTypeAdapter.class
+com\minexd\zoot\chat\util\ChatComponentBuilder.class
+com\minexd\zoot\pidgin\packet\Packet.class
+com\minexd\zoot\rank\Rank.class
+com\minexd\zoot\profile\grant\procedure\GrantProcedureType.class
+com\minexd\zoot\profile\punishment\command\WarnCommand.class
+com\minexd\zoot\essentials\command\GameModeCommand.class
+com\minexd\zoot\profile\ProfileTypeAdapter.class
+com\minexd\zoot\essentials\command\SetSlotsCommand.class
+com\minexd\zoot\util\ItemBuilder.class
+com\minexd\zoot\essentials\command\ShowPlayerCommand.class
+com\minexd\zoot\profile\grant\menu\GrantsListMenu$GrantInfoButton.class
+com\minexd\zoot\chat\filter\impl\ContainsFilter.class
+com\minexd\zoot\util\menu\button\JumpToMenuButton.class
+com\minexd\zoot\chat\filter\ChatFilter.class
+com\minexd\zoot\pidgin\packet\handler\PacketExceptionHandler.class
+com\minexd\zoot\util\menu\button\ConfirmationButton.class
+com\minexd\zoot\essentials\command\DayCommand.class
+com\minexd\zoot\network\packet\PacketStaffJoinNetwork.class
+com\minexd\zoot\config\impl\ConfigConversion2.class
+com\minexd\zoot\profile\grant\command\GrantsCommand.class
+com\minexd\zoot\profile\punishment\listener\PunishmentListener.class
+com\minexd\zoot\util\duration\Duration.class
+com\minexd\zoot\profile\punishment\Punishment.class
+com\minexd\zoot\util\menu\button\DisplayButton.class
+com\minexd\zoot\network\packet\PacketStaffChat.class
+com\minexd\zoot\util\menu\button\BackButton.class
+com\minexd\zoot\essentials\command\HidePlayerCommand.class
+com\minexd\zoot\util\json\JsonDeserializer.class
+com\minexd\zoot\network\packet\PacketDeleteRank.class
+com\minexd\zoot\profile\option\event\OptionsOpenedEvent.class
+com\minexd\zoot\profile\option\command\ToggleSoundsCommand.class
+com\minexd\zoot\profile\punishment\command\UnbanCommand.class
+com\minexd\zoot\rank\command\RankInheritCommand.class
+com\minexd\zoot\network\NetworkPacketListener$1.class
+com\minexd\zoot\profile\Profile.class
+com\minexd\zoot\essentials\Essentials.class
+com\minexd\zoot\essentials\command\HealCommand.class
+com\minexd\zoot\profile\conversation\command\MessageCommand.class
+com\minexd\zoot\network\event\ReceiveStaffChatEvent.class
+com\minexd\zoot\profile\option\command\ToggleGlobalChatCommand.class
+com\minexd\zoot\config\impl\ConfigConversion1.class
+com\minexd\zoot\util\adapter\ChatColorTypeAdapter.class
+com\minexd\zoot\util\BukkitReflection.class
+com\minexd\zoot\util\menu\pagination\PageFilter.class
+com\minexd\zoot\essentials\command\LocationCommand.class
+com\minexd\zoot\profile\punishment\PunishmentType.class
+com\minexd\zoot\profile\option\menu\button\PrivateChatSoundsOptionButton.class
+com\minexd\zoot\rank\command\RankCreateCommand.class
+com\minexd\zoot\essentials\command\ZootDebugCommand.class
+com\minexd\zoot\network\packet\PacketBroadcastPunishment.class
+com\minexd\zoot\profile\punishment\command\BanCommand.class
+com\minexd\zoot\rank\command\RankSetWeightCommand.class
+com\minexd\zoot\network\packet\PacketStaffSwitchServer.class
+com\minexd\zoot\profile\punishment\procedure\PunishmentProcedureType.class
+com\minexd\zoot\network\NetworkPacketListener.class
+com\minexd\zoot\profile\ProfileListener$1.class
+com\minexd\zoot\profile\conversation\Conversation.class
+com\minexd\zoot\config\ConfigVersion.class
+com\minexd\zoot\essentials\command\ListCommand$1.class
+com\minexd\zoot\pidgin\packet\listener\PacketListener.class
+com\minexd\zoot\util\menu\pagination\JumpToPageButton.class
+com\minexd\zoot\chat\command\SlowChatCommand.class
+com\minexd\zoot\util\TextSplitter.class
+com\minexd\zoot\rank\command\RankHelpCommand.class
+com\minexd\zoot\chat\command\ClearChatCommand.class
+com\minexd\zoot\util\duration\DurationTypeAdapter.class
+com\minexd\zoot\util\menu\pagination\PaginatedMenu.class
+com\minexd\zoot\profile\option\command\OptionsCommand.class
+com\minexd\zoot\util\callback\ReturnableTypeCallback.class
+com\minexd\zoot\util\callback\Callback.class
+com\minexd\zoot\profile\grant\Grant.class
+com\minexd\zoot\essentials\command\MoreCommand.class
+com\minexd\zoot\profile\punishment\PunishmentJsonDeserializer.class
+com\minexd\zoot\profile\punishment\command\KickCommand.class
+com\minexd\zoot\profile\punishment\PunishmentType$PunishmentTypeData.class
+com\minexd\zoot\cache\RedisCache.class
+com\minexd\zoot\profile\grant\GrantListener$2.class
+com\minexd\zoot\profile\punishment\command\KickCommand$1.class
+com\minexd\zoot\util\menu\pagination\PageButton.class
+com\minexd\zoot\chat\event\ChatAttemptEvent.class
+com\minexd\zoot\profile\punishment\command\BanCommand$1.class
+com\minexd\zoot\Zoot$1.class
+com\minexd\zoot\chat\ChatAttempt.class
+com\minexd\zoot\util\menu\Button.class
+com\minexd\zoot\essentials\command\BroadcastCommand.class
+com\minexd\zoot\essentials\command\ListCommand.class
+com\minexd\zoot\profile\punishment\listener\PunishmentListener$2.class
+com\minexd\zoot\profile\grant\event\GrantExpireEvent.class
+com\minexd\zoot\Zoot.class
+com\minexd\zoot\profile\option\menu\button\PublicChatOptionButton.class
+com\minexd\zoot\cache\RedisPlayerData$LastAction.class
+com\minexd\zoot\cache\RedisPlayerData.class
+com\minexd\zoot\profile\punishment\command\UnmuteCommand.class
+com\minexd\zoot\rank\command\RanksCommand$1.class
+com\minexd\zoot\profile\punishment\procedure\PunishmentProcedure.class
+com\minexd\zoot\essentials\command\TeleportWorldCommand.class
+com\minexd\zoot\profile\option\menu\ProfileOptionsMenu.class
+com\minexd\zoot\network\packet\PacketDeleteGrant.class
+com\minexd\zoot\rank\command\RankInfoCommand.class
+com\minexd\zoot\chat\Chat.class
+com\minexd\zoot\chat\ChatAttempt$Response.class
+com\minexd\zoot\essentials\command\SunsetCommand.class
+com\minexd\zoot\profile\punishment\command\CheckCommand.class
+com\minexd\zoot\bootstrap\BootstrappedListener.class
+com\minexd\zoot\profile\menu\ProfileMenuControlHeaderButton.class
+com\minexd\zoot\util\menu\menus\ConfirmMenu.class
+com\minexd\zoot\profile\grant\procedure\GrantProcedureStage.class
+com\minexd\zoot\rank\command\RankSetColorCommand.class
+com\minexd\zoot\profile\punishment\PunishmentJsonSerializer.class
+com\minexd\zoot\util\menu\pagination\PageInfoButton.class
+com\minexd\zoot\profile\grant\command\GrantCommand.class
+com\minexd\zoot\profile\grant\GrantListener.class
+com\minexd\zoot\profile\staff\command\StaffChatCommand.class
+com\minexd\zoot\profile\grant\GrantJsonSerializer.class
+com\minexd\zoot\profile\grant\GrantListener$1.class
+com\minexd\zoot\essentials\command\RenameCommand.class
+com\minexd\zoot\essentials\EssentialsListener.class
+com\minexd\zoot\util\menu\pagination\ViewAllPagesMenu.class
+com\minexd\zoot\essentials\command\SetSpawnCommand.class
+com\minexd\zoot\pidgin\Pidgin$1.class
+com\minexd\zoot\profile\option\menu\ProfileOptionButton.class
+com\minexd\zoot\util\menu\pagination\PageFilterButton.class
+com\minexd\zoot\profile\option\command\TogglePrivateMessagesCommand.class
+com\minexd\zoot\pidgin\Pidgin.class
+com\minexd\zoot\util\json\JsonSerializer.class
+com\minexd\zoot\util\json\JsonChain.class
+com\minexd\zoot\config\ConfigConversion.class
+com\minexd\zoot\essentials\command\ListCommand$2.class
+com\minexd\zoot\profile\punishment\listener\PunishmentListener$1.class
diff --git a/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
new file mode 100644
index 0000000..1441f0f
--- /dev/null
+++ b/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
@@ -0,0 +1,164 @@
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\network\packet\PacketStaffSwitchServer.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\config\impl\ConfigConversion1.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\grant\menu\GrantsListMenu.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\network\packet\PacketStaffLeaveNetwork.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\listener\PunishmentListener.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\menus\ConfirmMenu.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\chat\event\ChatAttemptEvent.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\PingCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\HidePlayerCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\MenuListener.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\option\command\TogglePrivateMessagesCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\SpawnCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\PunishmentJsonDeserializer.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\Essentials.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\Menu.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\chat\filter\impl\LinkFilter.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\TimeUtil.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\network\packet\PacketRefreshRank.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\LocationUtil.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\pagination\JumpToPageButton.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\conversation\command\ReplyCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\grant\event\GrantExpireEvent.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\grant\command\GrantsCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\PunishmentJsonSerializer.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\SetSpawnCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\pidgin\packet\Packet.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\command\BanCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\Zoot.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\BukkitReflection.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\command\WarnCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\option\event\OptionsOpenedEvent.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\pagination\PageFilterButton.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\config\impl\ConfigConversion2.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\staff\command\StaffChatCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\rank\command\RankAddPermissionCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\rank\command\RankUninheritCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\option\menu\button\PublicChatOptionButton.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\pagination\ViewAllPagesMenu.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\event\SpawnTeleportEvent.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\pagination\PaginatedMenu.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\ShowPlayerCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\callback\TypeCallback.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\RenameCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\adapter\ChatColorTypeAdapter.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\grant\procedure\GrantProcedure.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\config\ConfigValidation.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\NightCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\rank\command\RankInheritCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\rank\command\RankSetSuffixCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\bootstrap\Bootstrapped.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\pagination\FilterablePaginatedMenu.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\HealCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\staff\command\StaffModeCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\EssentialsListener.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\duration\DurationTypeAdapter.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\button\JumpToMenuButton.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\pagination\PageButton.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\TeleportWorldCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\network\event\ReceiveStaffChatEvent.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\conversation\command\MessageCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\PotionUtil.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\rank\command\RankSetWeightCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\Cooldown.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\Button.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\rank\command\RankHelpCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\command\KickCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\conversation\Conversation.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\pidgin\packet\handler\PacketExceptionHandler.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\option\menu\ProfileOptionsMenu.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\button\BackButton.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\button\ConfirmationButton.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\callback\Callback.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\pagination\PageInfoButton.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\chat\filter\impl\ContainsFilter.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\grant\GrantJsonSerializer.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\button\DisplayButton.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\Locale.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\CC.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\Profile.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\rank\RankTypeAdapter.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\option\command\ToggleSoundsCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\menu\ProfileMenuControlHeaderButton.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\command\CheckCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\callback\ReturnableTypeCallback.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\chat\util\ChatComponentBuilder.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\ListCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\BroadcastCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\chat\command\MuteChatCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\pidgin\packet\listener\PacketListenerData.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\procedure\PunishmentProcedure.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\ChatHelper.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\MoreCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\option\menu\button\PrivateChatSoundsOptionButton.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\GameModeCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\PunishmentType.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\rank\command\RankCreateCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\rank\command\RankRemovePermissionCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\procedure\PunishmentProcedureType.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\chat\command\ClearChatCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\bootstrap\BootstrappedListener.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\pidgin\Pidgin.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\option\ProfileOptions.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\staff\ProfileStaffOptions.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\cache\RedisPlayerData.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\json\JsonSerializer.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\pidgin\packet\handler\IncomingPacketHandler.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\ProfileListener.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\BaseEvent.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\network\packet\PacketAddGrant.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\grant\event\GrantAppliedEvent.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\chat\ChatAttempt.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\chat\filter\ChatFilter.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\ItemBuilder.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\rank\Rank.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\LocationCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\network\packet\PacketDeleteRank.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\menu\PunishmentsListMenu.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\conversation\ProfileConversations.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\network\packet\PacketBroadcastPunishment.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\rank\command\RankSetColorCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\config\ConfigConversion.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\rank\command\RankInfoCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\duration\Duration.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\option\menu\button\PrivateChatOptionButton.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\chat\Chat.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\config\ConfigVersion.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\Punishment.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\option\menu\ProfileOptionButton.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\TextSplitter.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\DayCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\menu\pagination\PageFilter.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\staff\command\AltsCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\rank\command\RanksCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\command\UnbanCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\ClearCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\network\NetworkPacketListener.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\command\UnmuteCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\network\packet\PacketDeleteGrant.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\network\packet\PacketStaffChat.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\ProfileTypeAdapter.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\network\packet\PacketStaffJoinNetwork.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\pidgin\packet\listener\PacketListener.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\cache\RedisCache.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\grant\GrantJsonDeserializer.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\json\JsonChain.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\grant\procedure\GrantProcedureStage.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\chat\ChatListener.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\grant\Grant.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\ZootAPI.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\SunsetCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\rank\command\RankDeleteCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\grant\command\GrantCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\option\command\OptionsCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\util\json\JsonDeserializer.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\SetSlotsCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\grant\GrantListener.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\rank\command\RankSetPrefixCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\grant\procedure\GrantProcedureType.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\essentials\command\ZootDebugCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\option\command\ToggleGlobalChatCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\command\MuteCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\config\impl\ConfigConversion3.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\chat\command\SlowChatCommand.java
+C:\Zoot\zoot\src\main\java\com\minexd\zoot\profile\punishment\procedure\PunishmentProcedureStage.java
diff --git a/target/original-zoot-1.0-SNAPSHOT.jar b/target/original-zoot-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000..27e3f08
Binary files /dev/null and b/target/original-zoot-1.0-SNAPSHOT.jar differ
diff --git a/target/zoot-1.0-SNAPSHOT.jar b/target/zoot-1.0-SNAPSHOT.jar
new file mode 100644
index 0000000..de1f741
Binary files /dev/null and b/target/zoot-1.0-SNAPSHOT.jar differ
diff --git a/zoot.iml b/zoot.iml
new file mode 100644
index 0000000..e936585
--- /dev/null
+++ b/zoot.iml
@@ -0,0 +1,72 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file