diff --git a/.gitignore b/.gitignore
index 278d1f64..f29a74b9 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,6 +5,7 @@
# Compilation files
*.class
target
+builds
# Log files
*.log
@@ -31,4 +32,3 @@ hs_err_pid*
# Maven
dependency-reduced-pom.xml
-
diff --git a/CHANGELOG/kelp-v0.0.5.md b/CHANGELOG/kelp-v0.0.5.md
new file mode 100644
index 00000000..bad482e0
--- /dev/null
+++ b/CHANGELOG/kelp-v0.0.5.md
@@ -0,0 +1,19 @@
+# v0.0.5
+> Release date: 30.12.2020
+
+**The player interaction update**:
+* Add `KelpServer` class, which serves as a kind of kelp wrapper for the `Bukkit` class:
+ * Broadcast messages to `KelpPlayers` (instead of bukkit players)
+ * Fetch all online `KelpPlayers` of the server
+* You can now send centered chat messages using the `KelpPlayer` as well as the `KelpServer` class.
+* Add player prompts accessible via the `KelpPlayer` class:
+ * `AnvilPrompt`: Use an anvil GUI to query text input from a player (default texts possible)
+ * `SignPrompt`: Use the sign editor to query multiple lines of text from a player (default lines are possible)
+ * `ChatPrompt`: Simple utility to use the chat as a method to query text input (default input not possible yet).
+* Add methods to display a `boss bar` to a player (including process modification and boss bar removal)
+* Add new custom event `KelpPlayerChangeSettingsEvent` that is triggered when a player changes their settings
+* Fix bug that custom kelp inventory events crashed the plugin when they were actually used in an event handler
+* Add `KelpPlayerLoginEvent` which is called on login of a player but after it has been initialized by kelp, so that calls to the `KelpPlayerRepository` are safe. If you want to handle logins now, it is recommended to use this event instead of the normal bukkit event.
+* You can now send interactive messages to KelpPlayers. You can assign messages that are displayable when you hover over them and define events which are triggered when a player clicks on a message. You can combine unlimited components per message unlike the normal `TextComponent#addExtra` method which is wide spread across the spigot community.
+* Documentation improvements in the code as well as in the GitHub wiki
+* Create empty kelp sql module
\ No newline at end of file
diff --git a/core/pom.xml b/core/pom.xml
index e6ea35fa..5d4767cb 100644
--- a/core/pom.xml
+++ b/core/pom.xml
@@ -5,7 +5,7 @@
com.github.pxav.kelpparent
- 0.0.4
+ 0.0.54.0.0
@@ -139,7 +139,7 @@
org.spigotmcspigot-api
- 1.14.4-R0.1-SNAPSHOT
+ 1.16.1-R0.1-SNAPSHOTprovided
diff --git a/core/src/main/java/de/pxav/kelp/core/KelpServer.java b/core/src/main/java/de/pxav/kelp/core/KelpServer.java
new file mode 100644
index 00000000..395db25f
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/KelpServer.java
@@ -0,0 +1,82 @@
+package de.pxav.kelp.core;
+
+import com.google.common.collect.Lists;
+import com.google.inject.Inject;
+import com.google.inject.Singleton;
+import de.pxav.kelp.core.player.KelpPlayer;
+import de.pxav.kelp.core.player.KelpPlayerRepository;
+import de.pxav.kelp.core.version.KelpVersion;
+import org.bukkit.Bukkit;
+
+import java.util.Collection;
+
+/**
+ * A class description goes here.
+ *
+ * @author pxav
+ */
+@Singleton
+public class KelpServer {
+
+ private KelpPlayerRepository kelpPlayerRepository;
+
+ @Inject
+ public KelpServer(KelpPlayerRepository kelpPlayerRepository) {
+ this.kelpPlayerRepository = kelpPlayerRepository;
+ }
+
+ public KelpVersion getVersion() {
+ return KelpVersion.withBukkitVersion(Bukkit.getBukkitVersion());
+ }
+
+ public Collection getOnlinePlayers() {
+ Collection output = Lists.newArrayList();
+ Bukkit.getOnlinePlayers().forEach(current -> output.add(kelpPlayerRepository.getKelpPlayer(current)));
+ return output;
+ }
+
+ public int getMaxPlayers() {
+ return Bukkit.getMaxPlayers();
+ }
+
+ public void broadcastMessage(String message) {
+ Bukkit.getOnlinePlayers().forEach(current -> current.sendMessage(message));
+ }
+
+ public void broadcastCenteredMessage(String message) {
+ this.getOnlinePlayers().forEach(current -> current.sendCenteredMessage(message));
+ }
+
+ public void broadcastMessages(String... message) {
+ Bukkit.getOnlinePlayers().forEach(current -> {
+ for (String s : message) {
+ current.sendMessage(s);
+ }
+ });
+ }
+
+ public void broadcastCenteredMessages(String... message) {
+ this.getOnlinePlayers().forEach(current -> {
+ for (String s : message) {
+ current.sendCenteredMessage(s);
+ }
+ });
+ }
+
+ public void broadcastCenteredMessages(Collection messages) {
+ this.getOnlinePlayers().forEach(current -> {
+ for (String s : messages) {
+ current.sendCenteredMessage(s);
+ }
+ });
+ }
+
+ public void broadcastMessages(Collection messages) {
+ Bukkit.getOnlinePlayers().forEach(current -> {
+ for (String s : messages) {
+ current.sendMessage(s);
+ }
+ });
+ }
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/common/StringUtils.java b/core/src/main/java/de/pxav/kelp/core/common/StringUtils.java
index 727990c6..aaeb728d 100644
--- a/core/src/main/java/de/pxav/kelp/core/common/StringUtils.java
+++ b/core/src/main/java/de/pxav/kelp/core/common/StringUtils.java
@@ -2,9 +2,11 @@
import com.google.common.collect.Lists;
import com.google.inject.Singleton;
+import net.md_5.bungee.api.ChatColor;
import java.util.Collections;
import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
/**
* This class contains useful methods to work with strings
@@ -241,6 +243,66 @@ public boolean isStyleCode(char indicator) {
return false;
}
+ public ChatColor getChatColor(char formattingCode) {
+ return isFormattingCode(formattingCode)
+ ? ChatColor.getByChar(formattingCode)
+ : ChatColor.WHITE;
+ }
+
+ public org.bukkit.ChatColor getBukkitChatColor(char formattingCode) {
+ return isFormattingCode(formattingCode)
+ ? org.bukkit.ChatColor.getByChar(formattingCode)
+ : org.bukkit.ChatColor.WHITE;
+ }
+
+ public ChatColor getChatColor(String formattingCode) {
+ char code = formattingCode.charAt(1);
+ return isFormattingCode(code) && formattingCode.charAt(0) == '§'
+ ? ChatColor.getByChar(code)
+ : ChatColor.WHITE;
+ }
+
+ public org.bukkit.ChatColor getBukkitChatColor(String formattingCode) {
+ char code = formattingCode.charAt(1);
+ return isFormattingCode(code) && formattingCode.charAt(0) == '§'
+ ? org.bukkit.ChatColor.getByChar(code)
+ : org.bukkit.ChatColor.WHITE;
+ }
+
+ public String endsWithColorCode(String text) {
+ if (text.length() < 2) {
+ return null;
+ }
+
+ String code = text.substring(text.length() - 2);
+ if (code.charAt(0) == '§' && isColorCode(code.charAt(1))) {
+ return code;
+ }
+
+ return null;
+ }
+
+ public String endsWithFormattingCode(String text) {
+ if (text.length() < 2) {
+ return null;
+ }
+
+ String code = text.substring(text.length() - 2);
+ if (code.charAt(0) == '§' && isFormattingCode(code.charAt(1))) {
+ return code;
+ }
+
+ return null;
+ }
+
+ public char randomColorCode() {
+ return colorCodes[ThreadLocalRandom.current().nextInt(colorCodes.length - 1)];
+ }
+
+ public char randomStyleCode() {
+ return styleCodes[ThreadLocalRandom.current().nextInt(colorCodes.length - 1)];
+ }
+
/**
* @param indicator The indicator of the code you want to check.
* @return {@code true} if the either a color code or a style code.
diff --git a/core/src/main/java/de/pxav/kelp/core/configuration/internal/KelpDefaultConfiguration.java b/core/src/main/java/de/pxav/kelp/core/configuration/internal/KelpDefaultConfiguration.java
index d0595c70..413fdcca 100644
--- a/core/src/main/java/de/pxav/kelp/core/configuration/internal/KelpDefaultConfiguration.java
+++ b/core/src/main/java/de/pxav/kelp/core/configuration/internal/KelpDefaultConfiguration.java
@@ -20,7 +20,7 @@ public final class KelpDefaultConfiguration extends KelpConfiguration {
@Override
public void defineDefaults() {
- defaultValues.add(new ConfigurationAttribute(developmentMode(), false));
+ add(developmentMode(), false);
}
public String developmentMode() {
diff --git a/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpInventoryCloseEvent.java b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpInventoryCloseEvent.java
index 0e96d043..04c3895a 100644
--- a/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpInventoryCloseEvent.java
+++ b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpInventoryCloseEvent.java
@@ -34,4 +34,9 @@ public boolean isAnimated() {
public HandlerList getHandlers() {
return handlers;
}
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
}
diff --git a/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpInventoryOpenEvent.java b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpInventoryOpenEvent.java
index ad4fb632..bdf23053 100644
--- a/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpInventoryOpenEvent.java
+++ b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpInventoryOpenEvent.java
@@ -35,4 +35,8 @@ public HandlerList getHandlers() {
return handlers;
}
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
}
diff --git a/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpInventoryUpdateEvent.java b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpInventoryUpdateEvent.java
index 9d48e3a4..1f7eb7ba 100644
--- a/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpInventoryUpdateEvent.java
+++ b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpInventoryUpdateEvent.java
@@ -28,4 +28,9 @@ public KelpInventory getInventory() {
public HandlerList getHandlers() {
return handlers;
}
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
}
diff --git a/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpPlayerLoginEvent.java b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpPlayerLoginEvent.java
new file mode 100644
index 00000000..36df3e8d
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpPlayerLoginEvent.java
@@ -0,0 +1,71 @@
+package de.pxav.kelp.core.event.kelpevent;
+
+import de.pxav.kelp.core.player.KelpPlayer;
+import org.bukkit.entity.Player;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.player.PlayerEvent;
+import org.bukkit.event.player.PlayerLoginEvent;
+
+/**
+ * This event is triggered on any player login. It is recommended to use this
+ * event instead of the normal {@link PlayerLoginEvent} as this event is used
+ * to handle the handshake between the client and server.
+ *
+ * If a kelp application would try to convert the {@link Player} instance into a
+ * {@link KelpPlayer} instance, there would be a {@link NullPointerException} as
+ * the player has not been registered yet. So the login event handler that creates
+ * the {@link KelpPlayer} instances calls this event after it has finished so that
+ * it is safe to use the kelp player object.
+ *
+ * All methods (except {@link #getKelpPlayer()}) are the same as those from the normal
+ * bukkit event, so you can look for more detailed information there.
+ *
+ * @author pxav
+ */
+public class KelpPlayerLoginEvent extends PlayerEvent {
+
+ // list of all event handlers listening for this event
+ private static final HandlerList handlers = new HandlerList();
+
+ private String hostname;
+ private PlayerLoginEvent.Result result;
+ private String message;
+ private KelpPlayer kelpPlayer;
+
+ public KelpPlayerLoginEvent(Player who, KelpPlayer kelpPlayer, String hostname, PlayerLoginEvent.Result result, String message) {
+ super(who);
+ this.hostname = hostname;
+ this.result = result;
+ this.message = message;
+ this.kelpPlayer = kelpPlayer;
+ }
+
+ /**
+ * @return The {@link KelpPlayer} instance of the player who logged in.
+ */
+ public KelpPlayer getKelpPlayer() {
+ return kelpPlayer;
+ }
+
+ public String getHostname() {
+ return hostname;
+ }
+
+ public PlayerLoginEvent.Result getResult() {
+ return result;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpPlayerUpdateSettingsEvent.java b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpPlayerUpdateSettingsEvent.java
new file mode 100644
index 00000000..3e00baea
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/KelpPlayerUpdateSettingsEvent.java
@@ -0,0 +1,123 @@
+package de.pxav.kelp.core.event.kelpevent;
+
+import de.pxav.kelp.core.player.KelpPlayer;
+import de.pxav.kelp.core.player.PlayerChatVisibility;
+import org.bukkit.event.HandlerList;
+import org.bukkit.event.player.PlayerEvent;
+
+/**
+ * This event is triggered when the client settings of a player change.
+ * It is triggered before the settings are actually updated in the
+ * {@link KelpPlayer} class, so you can do comparisons between the old
+ * and new values if you wish.
+ *
+ * @author pxav
+ */
+public class KelpPlayerUpdateSettingsEvent extends PlayerEvent {
+
+ // list of all event handlers listening for this event
+ private static final HandlerList handlers = new HandlerList();
+
+ // the player whose settings changed.
+ private KelpPlayer who;
+
+ // the stage when the settings changed
+ private SettingsUpdateStage updateStage;
+
+ // the new client language.
+ private String language;
+
+ // the new client view distance.
+ private int viewDistance;
+
+ // the new chat visibility
+ private PlayerChatVisibility playerChatVisibility;
+
+ // whether the player has chat colors enabled.
+ private boolean chatColorEnabled;
+
+ public KelpPlayerUpdateSettingsEvent(KelpPlayer who,
+ SettingsUpdateStage updateStage,
+ String language,
+ int viewDistance,
+ PlayerChatVisibility playerChatVisibility,
+ boolean chatColorEnabled) {
+ super(who.getBukkitPlayer());
+ this.who = who;
+ this.updateStage = updateStage;
+ this.language = language;
+ this.viewDistance = viewDistance;
+ this.playerChatVisibility = playerChatVisibility;
+ this.chatColorEnabled = chatColorEnabled;
+ }
+
+ /**
+ * @return How the server received the settings update. Look into the documentation
+ * of {@link SettingsUpdateStage} for more detailed information.
+ */
+ public SettingsUpdateStage getUpdateStage() {
+ return updateStage;
+ }
+
+ public String getLanguage() {
+ return this.language;
+ }
+
+ public boolean hasLanguageChanged() {
+ return !this.language.equalsIgnoreCase(who.getClientLanguage());
+ }
+
+ public int getViewDistance() {
+ return viewDistance;
+ }
+
+ public boolean hasViewDistanceChanged() {
+ return viewDistance != who.getClientViewDistance();
+ }
+
+ public boolean getChatColorEnabled() {
+ return chatColorEnabled;
+ }
+
+ /**
+ * Returns {@code true} if the chat color setting has changed.
+ * @return if the chat color setting has changed.
+ */
+ public boolean hasChatColorChanged() {
+ return chatColorEnabled != who.isPlayerChatColorEnabled();
+ }
+
+ /**
+ * Get the updated chat visibility.
+ * @return the updated chat visibility.
+ */
+ public PlayerChatVisibility getChatVisibility() {
+ return playerChatVisibility;
+ }
+
+ /**
+ * Returns {@code true} if the chat visibility settings have changed.
+ * @return if the chat visibility settings have changed.
+ */
+ public boolean hasChatVisibilityChanged() {
+ return playerChatVisibility != who.getPlayerChatVisibility();
+ }
+
+ /**
+ * Returns the kelp player instance of the player whose settings have changed.
+ * @return the kelp player instance of the player whose settings have changed.
+ */
+ public KelpPlayer getKelpPlayer() {
+ return who;
+ }
+
+ @Override
+ public HandlerList getHandlers() {
+ return handlers;
+ }
+
+ public static HandlerList getHandlerList() {
+ return handlers;
+ }
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/event/kelpevent/SettingsUpdateStage.java b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/SettingsUpdateStage.java
new file mode 100644
index 00000000..afcc239d
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/event/kelpevent/SettingsUpdateStage.java
@@ -0,0 +1,31 @@
+package de.pxav.kelp.core.event.kelpevent;
+
+/**
+ * Describes when an update of client settings has been received.
+ * There are two ways the server can receive information about
+ * the player's client settings:
+ * - By loading default settings on each plugin startup
+ * - By catching the packet sent by the client during the server runtime.
+ *
+ * This enum is used by {@link KelpPlayerUpdateSettingsEvent} to provide information
+ * about when the settings have changed.
+ *
+ * @author pxav
+ */
+public enum SettingsUpdateStage {
+
+ /**
+ * When the player has updated its settings during the server runtime
+ * and the server received an incoming settings packet. This packet is
+ * sent when the player presses the {@code Done} button in the video settings
+ * tab for example.
+ */
+ PACKET_PLAY_IN,
+
+ /**
+ * When the server automatically updates the settings values for the player
+ * on every plugin startup (relevant for server reloads for example).
+ */
+ PLUGIN_STARTUP
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/inventory/KelpInventoryRepository.java b/core/src/main/java/de/pxav/kelp/core/inventory/KelpInventoryRepository.java
index 2ff469d5..5b2e5faf 100644
--- a/core/src/main/java/de/pxav/kelp/core/inventory/KelpInventoryRepository.java
+++ b/core/src/main/java/de/pxav/kelp/core/inventory/KelpInventoryRepository.java
@@ -16,7 +16,6 @@
import org.bukkit.Bukkit;
import org.bukkit.inventory.Inventory;
-import java.lang.reflect.Method;
import java.util.Map;
import java.util.UUID;
@@ -36,8 +35,6 @@ public class KelpInventoryRepository {
private MethodFinder methodFinder;
private KelpListenerRepository kelpListenerRepository;
- private Map methods = Maps.newHashMap();
-
private Map playerInventories = Maps.newHashMap();
private Map> playerPages = Maps.newHashMap();
private Map playerAnimations = Maps.newHashMap();
@@ -58,16 +55,6 @@ public void loadMaterials() {
this.materialVersionTemplate.defineDefaults();
}
-/* public void detectInventories(String... packages) {
- methodFinder.filter(packages, MethodCriterion.annotatedWith(CreateInventory.class)).forEach(method -> {
- CreateInventory annotation = method.getAnnotation(CreateInventory.class);
- String identifier = annotation.identifier();
-
- methods.put(identifier, method);
-
- });
- }*/
-
/**
* Opens a kelp inventory to the given player. This also adds the
* player to all required lists.
diff --git a/core/src/main/java/de/pxav/kelp/core/inventory/material/MaterialContainer.java b/core/src/main/java/de/pxav/kelp/core/inventory/material/MaterialContainer.java
new file mode 100644
index 00000000..f09dbcc9
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/inventory/material/MaterialContainer.java
@@ -0,0 +1,32 @@
+package de.pxav.kelp.core.inventory.material;
+
+import org.bukkit.Material;
+
+/**
+ * A class description goes here.
+ *
+ * @author pxav
+ */
+public class MaterialContainer {
+
+ private String materialName;
+ private short subId;
+
+ public MaterialContainer(String materialName, short subId) {
+ this.materialName = materialName;
+ this.subId = subId;
+ }
+
+ public String getMaterialName() {
+ return materialName;
+ }
+
+ public Material getBukkitMaterial() {
+ return Material.valueOf(materialName);
+ }
+
+ public short getSubId() {
+ return subId;
+ }
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/inventory/material/MaterialRepository.java b/core/src/main/java/de/pxav/kelp/core/inventory/material/MaterialRepository.java
index 85a6400d..c6ef58f7 100644
--- a/core/src/main/java/de/pxav/kelp/core/inventory/material/MaterialRepository.java
+++ b/core/src/main/java/de/pxav/kelp/core/inventory/material/MaterialRepository.java
@@ -6,6 +6,7 @@
import com.google.inject.Singleton;
import java.util.Map;
+import java.util.function.BiConsumer;
/**
* This repository class is used to save all material names of the
@@ -52,6 +53,14 @@ public String getMaterial(String kelpMaterialName) {
return this.getMaterial(kelpMaterial);
}
+ public MaterialContainer getBukkitMaterial(KelpMaterial kelpMaterial) {
+ String[] format = materials.get(kelpMaterial).split(":");
+ if (format.length == 1) {
+ return new MaterialContainer(format[0], (short) 0);
+ }
+ return new MaterialContainer(format[0], Short.parseShort(format[1]));
+ }
+
/**
* Gets the kelp material matching the given bukkit material name.
* This method does not support sub IDs.
@@ -74,6 +83,9 @@ public KelpMaterial getKelpMaterial(String bukkitMaterial) {
* @return The final kelp material.
*/
public KelpMaterial getKelpMaterial(String bukkitMaterial, short subId) {
+ if (subId == 0) {
+ return this.getKelpMaterial(bukkitMaterial);
+ }
return materials.inverse().get(bukkitMaterial + ":" + subId);
}
@@ -88,6 +100,9 @@ public KelpMaterial getKelpMaterial(String bukkitMaterial, short subId) {
* @return The kelp material matching the given bukkit material.
*/
public KelpMaterial getKelpMaterial(String bukkitMaterial, int subId) {
+ if (subId == 0) {
+ return this.getKelpMaterial(bukkitMaterial);
+ }
return materials.inverse().get(bukkitMaterial + ":" + subId);
}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/KelpPlayer.java b/core/src/main/java/de/pxav/kelp/core/player/KelpPlayer.java
index 79c0a056..34ee9b2c 100644
--- a/core/src/main/java/de/pxav/kelp/core/player/KelpPlayer.java
+++ b/core/src/main/java/de/pxav/kelp/core/player/KelpPlayer.java
@@ -1,5 +1,6 @@
package de.pxav.kelp.core.player;
+import com.google.common.base.Preconditions;
import de.pxav.kelp.core.entity.KelpEntityType;
import de.pxav.kelp.core.entity.LivingKelpEntity;
import de.pxav.kelp.core.entity.version.EntityVersionTemplate;
@@ -8,6 +9,16 @@
import de.pxav.kelp.core.inventory.type.KelpInventory;
import de.pxav.kelp.core.particle.type.ParticleType;
import de.pxav.kelp.core.particle.version.ParticleVersionTemplate;
+import de.pxav.kelp.core.player.bossbar.BossBarColor;
+import de.pxav.kelp.core.player.bossbar.BossBarStyle;
+import de.pxav.kelp.core.player.message.InteractiveMessage;
+import de.pxav.kelp.core.player.prompt.anvil.AnvilPrompt;
+import de.pxav.kelp.core.player.prompt.anvil.AnvilPromptVersionTemplate;
+import de.pxav.kelp.core.player.prompt.chat.ChatPromptVersionTemplate;
+import de.pxav.kelp.core.player.prompt.chat.DefaultFontSize;
+import de.pxav.kelp.core.player.prompt.chat.SimpleChatPrompt;
+import de.pxav.kelp.core.player.prompt.sign.SignPrompt;
+import de.pxav.kelp.core.player.prompt.sign.SignPromptVersionTemplate;
import de.pxav.kelp.core.sidebar.SidebarRepository;
import de.pxav.kelp.core.sound.KelpSound;
import org.bukkit.Location;
@@ -47,6 +58,9 @@ public class KelpPlayer extends LivingKelpEntity {
private SidebarRepository sidebarRepository;
private KelpInventoryRepository inventoryRepository;
private ParticleVersionTemplate particleVersionTemplate;
+ private SignPromptVersionTemplate signPromptVersionTemplate;
+ private AnvilPromptVersionTemplate anvilPromptVersionTemplate;
+ private ChatPromptVersionTemplate chatPromptVersionTemplate;
private Player bukkitPlayer;
@@ -70,6 +84,9 @@ public KelpPlayer(Player bukkitPlayer,
KelpInventoryRepository inventoryRepository,
KelpPlayerRepository kelpPlayerRepository,
ParticleVersionTemplate particleVersionTemplate,
+ SignPromptVersionTemplate signPromptVersionTemplate,
+ AnvilPromptVersionTemplate anvilPromptVersionTemplate,
+ ChatPromptVersionTemplate chatPromptVersionTemplate,
EntityVersionTemplate entityVersionTemplate,
LivingEntityVersionTemplate livingEntityVersionTemplate,
UUID uuid,
@@ -87,6 +104,21 @@ public KelpPlayer(Player bukkitPlayer,
this.sidebarRepository = sidebarRepository;
this.inventoryRepository = inventoryRepository;
this.particleVersionTemplate = particleVersionTemplate;
+ this.signPromptVersionTemplate = signPromptVersionTemplate;
+ this.chatPromptVersionTemplate = chatPromptVersionTemplate;
+ this.anvilPromptVersionTemplate = anvilPromptVersionTemplate;
+ }
+
+ public SignPrompt openSignPrompt() {
+ return new SignPrompt(this.getBukkitPlayer(), this.signPromptVersionTemplate);
+ }
+
+ public AnvilPrompt openAnvilPrompt() {
+ return new AnvilPrompt(this.getBukkitPlayer(), this.anvilPromptVersionTemplate);
+ }
+
+ public SimpleChatPrompt openSimpleChatPrompt() {
+ return new SimpleChatPrompt(this.getBukkitPlayer(), this.chatPromptVersionTemplate);
}
/**
@@ -773,6 +805,210 @@ public KelpPlayer sendMessages(Collection messages) {
return this;
}
+ public KelpPlayer sendPrefixedMessages(String prefix, String... messages) {
+ for (String message : messages) {
+ sendMessage(prefix + message);
+ }
+ return this;
+ }
+
+ public KelpPlayer sendPrefixedMessages(String prefix, Collection messages) {
+ for (String message : messages) {
+ sendMessage(prefix + message);
+ }
+ return this;
+ }
+
+ public void sendCenteredMessage(String message) {
+ Preconditions.checkNotNull(message);
+ if(message.equals("")) {
+ this.sendMessage("");
+ }
+
+ final int CENTER_PX = 154;
+ int messagePxSize = 0;
+ boolean previousCode = false;
+ boolean isBold = false;
+
+ for(char c : message.toCharArray()){
+ if(c == '§') {
+ previousCode = true;
+ continue;
+ }
+
+ if(previousCode) {
+ previousCode = false;
+ isBold = c == 'l' || c == 'L';
+ } else {
+ DefaultFontSize dFI = DefaultFontSize.getDefaultFontInfo(c);
+ messagePxSize += isBold ? dFI.getBoldLength() : dFI.getLength();
+ messagePxSize++;
+ }
+ }
+
+ int halvedMessageSize = messagePxSize / 2;
+ int toCompensate = CENTER_PX - halvedMessageSize;
+ int spaceLength = DefaultFontSize.SPACE.getLength() + 1;
+ int compensated = 0;
+ StringBuilder sb = new StringBuilder();
+ while(compensated < toCompensate){
+ sb.append(" ");
+ compensated += spaceLength;
+ }
+ this.sendMessage(sb.toString() + message);
+ }
+
+ public void sendCenteredMessages(String... messages) {
+ for (String s : messages) {
+ this.sendCenteredMessage(s);
+ }
+ }
+
+ public void sendCenteredMessages(Collection messages) {
+ for (String s : messages) {
+ this.sendCenteredMessage(s);
+ }
+ }
+
+ public void sendCenteredMessages(String header, String footer, String... messages) {
+ if (header != null) {
+ this.sendMessage(header);
+ }
+ for (String s : messages) {
+ this.sendCenteredMessage(s);
+ }
+ if (footer != null) {
+ this.sendMessage(footer);
+ }
+ }
+
+ public void sendCenteredMessages(String header, Collection messages, String footer) {
+ if (header != null) {
+ this.sendMessage(header);
+ }
+ for (String s : messages) {
+ this.sendCenteredMessage(s);
+ }
+ if (footer != null) {
+ this.sendMessage(footer);
+ }
+ }
+
+ /**
+ * Sends a boss bar to the player by spawning a boss entity near it. If you use this
+ * method in 1.8, please keep in mind that bar colors other than {@code PURPLE} and bar styles
+ * other than {@code SOLID} are not supported.
+ *
+ * @param message The message you want to be displayed above the boss bar.
+ * @param health How much the boss bar should be loaded (equivalent to how much
+ * health the boss entity has. 300f is a full boss bar and 0f an empty one).
+ * @param barColor The color of the boss bar. Please note that in 1.8 only
+ * {@code PURPLE} is allowed. If you use any color, no exception
+ * is thrown but purple will be chosen automatically.
+ * @param barStyle The style of the boss bar (how many segments?, ...). Note that
+ * in 1.8 only {@code SOLID} is supported. If you use any different
+ * style, no exception will be thrown, but {@code SOLID} is chosen
+ * automatically.
+ */
+ public void sendBossBar(String message, float health, BossBarColor barColor, BossBarStyle barStyle) {
+ playerVersionTemplate.sendBossBar(bukkitPlayer, message, health, barColor, barStyle);
+ }
+
+ /**
+ * Sends a boss bar to the player by spawning a boss entity near it.
+ * This method uses {@link BossBarColor#PURPLE} and {@link BossBarStyle#SOLID}
+ * in order to be compatible with all versions. If you want to use another style or color
+ * you can use {@link #sendBossBar(String, float, BossBarColor, BossBarStyle)} instead.
+ *
+ * @param message The message you want to be displayed above the boss bar.
+ */
+ public void sendBossBar(String message) {
+ playerVersionTemplate.sendBossBar(bukkitPlayer, message, 300f, BossBarColor.PURPLE, BossBarStyle.SOLID);
+ }
+
+ /**
+ * Sets the progress of the player's boss bar by modifying the
+ * health of the boss bar entity. As withers are used for that
+ * purpose, the maximum value {@code 300f} represents full boss
+ * bar and {@code 0f} would be an empty boss bar (equivalent to
+ * the wither dieing.)
+ *
+ * @param health The health of the boss bar entity.
+ */
+ public void setBossBarProgressHealth(float health) {
+ playerVersionTemplate.setBossBarProgress(bukkitPlayer, health);
+ }
+
+ /**
+ * Sets the progress of the player's boss bar, where 1 means
+ * that the bar is fully loaded and 0 means the bar is completely
+ * unloaded (equivalent to the boss entity being killed).
+ *
+ * @param percentage The percentage value of the progress between 0 and 1.
+ */
+ public void setBossBarProgress(double percentage) {
+ float health = 300f * (float) percentage;
+ setBossBarProgressHealth(health);
+ }
+
+ /**
+ * Sets the progress of the player's boss bar. This method
+ * automatically calculates the percentage value based on the
+ * value representing your maximum and the current state you want
+ * to display. Example: If you have a 60 seconds timer and there
+ * are still 20 seconds to wait, you pass 60 seconds as your maximum
+ * and 40 seconds as your current value.
+ *
+ * @param current The current state of reaching the maximum in absolute numbers.
+ * @param max The maximum value that is reachable for parameter {@code current}.
+ */
+ public void setBossBarProgress(int current, int max) {
+ double percentage = (double) current / (double) max;
+ setBossBarProgress(percentage);
+ }
+
+ /**
+ * Sets the progress of the player's boss bar. This method
+ * automatically calculates the percentage value based on the
+ * value representing your maximum and the current state you want
+ * to display. Example: If you have a 60 seconds timer and there
+ * are still 20 seconds to wait, you pass 60 seconds as your maximum
+ * and 40 seconds as your current value.
+ *
+ * @param current The current state of reaching the maximum in absolute numbers.
+ * @param max The maximum value that is reachable for parameter {@code current}.
+ */
+ public void setBossBarProgress(double current, double max) {
+ double percentage = current / max;
+ setBossBarProgress(percentage);
+ }
+
+ /**
+ * Makes the boss bar disappear for the player.
+ */
+ public void removeBossBar() {
+ playerVersionTemplate.removeBossBar(bukkitPlayer);
+ }
+
+ /**
+ * Sends an interactive message to the player. An interactive message is a message
+ * the player can click on and events (execute a command, open a url, ...) are triggered.
+ * You can also add hover events to it. You can add as many components as you want.
+ * More detailed information about how to build an interactive message can be found out
+ * in {@link InteractiveMessage}.
+ *
+ * @param interactiveMessage The interactive message you want to send to the player.
+ */
+ public void sendInteractiveMessage(InteractiveMessage interactiveMessage) {
+ playerVersionTemplate.sendInteractiveMessage(bukkitPlayer, interactiveMessage);
+ }
+
+ /**
+ * Gets the bukkit instance of the current {@link Player}. Be aware that
+ * by using this, you might lose version independence.
+ *
+ * @return The current bukkit player instance.
+ */
public Player getBukkitPlayer() {
return bukkitPlayer;
}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/KelpPlayerRepository.java b/core/src/main/java/de/pxav/kelp/core/player/KelpPlayerRepository.java
index 7d26fcd2..d93c061d 100644
--- a/core/src/main/java/de/pxav/kelp/core/player/KelpPlayerRepository.java
+++ b/core/src/main/java/de/pxav/kelp/core/player/KelpPlayerRepository.java
@@ -9,6 +9,9 @@
import de.pxav.kelp.core.logger.KelpLogger;
import de.pxav.kelp.core.logger.LogLevel;
import de.pxav.kelp.core.particle.version.ParticleVersionTemplate;
+import de.pxav.kelp.core.player.prompt.anvil.AnvilPromptVersionTemplate;
+import de.pxav.kelp.core.player.prompt.chat.ChatPromptVersionTemplate;
+import de.pxav.kelp.core.player.prompt.sign.SignPromptVersionTemplate;
import de.pxav.kelp.core.sidebar.SidebarRepository;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -48,22 +51,22 @@ public class KelpPlayerRepository {
private EntityVersionTemplate entityVersionTemplate;
private LivingEntityVersionTemplate livingEntityVersionTemplate;
private ParticleVersionTemplate particleVersionTemplate;
+ private SignPromptVersionTemplate signPromptVersionTemplate;
+ private AnvilPromptVersionTemplate anvilPromptVersionTemplate;
+ private ChatPromptVersionTemplate chatPromptVersionTemplate;
@Inject
- public KelpPlayerRepository(PlayerVersionTemplate playerVersionTemplate,
- KelpLogger logger,
- SidebarRepository sidebarRepository,
- KelpInventoryRepository inventoryRepository,
- EntityVersionTemplate entityVersionTemplate,
- LivingEntityVersionTemplate livingEntityVersionTemplate,
- ParticleVersionTemplate particleVersionTemplate) {
+ public KelpPlayerRepository(PlayerVersionTemplate playerVersionTemplate, SidebarRepository sidebarRepository, KelpInventoryRepository inventoryRepository, KelpLogger logger, EntityVersionTemplate entityVersionTemplate, LivingEntityVersionTemplate livingEntityVersionTemplate, ParticleVersionTemplate particleVersionTemplate, SignPromptVersionTemplate signPromptVersionTemplate, AnvilPromptVersionTemplate anvilPromptVersionTemplate, ChatPromptVersionTemplate chatPromptVersionTemplate) {
this.playerVersionTemplate = playerVersionTemplate;
- this.logger = logger;
this.sidebarRepository = sidebarRepository;
this.inventoryRepository = inventoryRepository;
+ this.logger = logger;
this.entityVersionTemplate = entityVersionTemplate;
this.livingEntityVersionTemplate = livingEntityVersionTemplate;
this.particleVersionTemplate = particleVersionTemplate;
+ this.signPromptVersionTemplate = signPromptVersionTemplate;
+ this.anvilPromptVersionTemplate = anvilPromptVersionTemplate;
+ this.chatPromptVersionTemplate = chatPromptVersionTemplate;
}
/**
@@ -218,6 +221,9 @@ private KelpPlayer newKelpPlayerFrom(Player bukkitPlayer) {
inventoryRepository,
this,
particleVersionTemplate,
+ signPromptVersionTemplate,
+ anvilPromptVersionTemplate,
+ chatPromptVersionTemplate,
entityVersionTemplate,
livingEntityVersionTemplate,
playerVersionTemplate.getUniqueId(bukkitPlayer),
diff --git a/core/src/main/java/de/pxav/kelp/core/player/PlayerVersionTemplate.java b/core/src/main/java/de/pxav/kelp/core/player/PlayerVersionTemplate.java
index 4e3e8407..fe98e01b 100644
--- a/core/src/main/java/de/pxav/kelp/core/player/PlayerVersionTemplate.java
+++ b/core/src/main/java/de/pxav/kelp/core/player/PlayerVersionTemplate.java
@@ -1,18 +1,15 @@
package de.pxav.kelp.core.player;
import de.pxav.kelp.core.application.KelpVersionTemplate;
+import de.pxav.kelp.core.player.bossbar.BossBarColor;
+import de.pxav.kelp.core.player.bossbar.BossBarStyle;
+import de.pxav.kelp.core.player.message.InteractiveMessage;
import de.pxav.kelp.core.sound.KelpSound;
import org.bukkit.*;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
-import org.bukkit.permissions.Permissible;
-import org.bukkit.permissions.Permission;
-import org.bukkit.permissions.PermissionAttachment;
-import org.bukkit.permissions.PermissionAttachmentInfo;
-import org.bukkit.plugin.Plugin;
import java.net.InetSocketAddress;
-import java.util.Set;
import java.util.UUID;
/**
@@ -888,4 +885,54 @@ public abstract class PlayerVersionTemplate {
*/
public abstract void sendMessage(Player player, String message);
+ /**
+ * Sends a boss bar to the player by spawning a boss entity near it. If you use this
+ * method in 1.8, please keep in mind that bar colors other than {@code PURPLE} and bar styles
+ * other than {@code SOLID} are not supported.
+ *
+ * @param player The player you want to send the message to.
+ * @param message The message you want to be displayed above the boss bar.
+ * @param health How much the boss bar should be loaded (equivalent to how much
+ * health the boss entity has. 300f is a full boss bar and 0f an empty one).
+ * @param barColor The color of the boss bar. Please note that in 1.8 only
+ * {@code PURPLE} is allowed. If you use any color, no exception
+ * is thrown but purple will be chosen automatically.
+ * @param barStyle The style of the boss bar (how many segments?, ...). Note that
+ * in 1.8 only {@code SOLID} is supported. If you use any different
+ * style, no exception will be thrown, but {@code SOLID} is chosen
+ * automatically.
+ */
+ public abstract void sendBossBar(Player player, String message, float health, BossBarColor barColor, BossBarStyle barStyle);
+
+ /**
+ * Sets the progress of the player's boss bar by modifying the
+ * health of the boss bar entity. As withers are used for that
+ * purpose, the maximum value {@code 300f} represents full boss
+ * bar and {@code 0f} would be an empty boss bar (equivalent to
+ * the wither dieing.)
+ *
+ * @param health The health of the boss bar entity.
+ */
+ public abstract void setBossBarProgress(Player player, float health);
+
+ /**
+ * Makes the boss bar disappear for the given player.
+ *
+ * @param player The player whose boss bar you want to remove.
+ */
+ public abstract void removeBossBar(Player player);
+
+ /**
+ * Sends an interactive message to the player. An interactive message is a message
+ * the player can click on and events (execute a command, open a url, ...) are triggered.
+ * You can also add hover events to it. You can add as many components as you want.
+ * More detailed information about how to build an interactive message can be found out
+ * in {@link InteractiveMessage}.
+ *
+ * @param player The player who should receive this message and be able
+ * to interact with it.
+ * @param interactiveMessage The actual message you want to send to the player.
+ */
+ public abstract void sendInteractiveMessage(Player player, InteractiveMessage interactiveMessage);
+
}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/bossbar/BossBarColor.java b/core/src/main/java/de/pxav/kelp/core/player/bossbar/BossBarColor.java
new file mode 100644
index 00000000..c8dfd2a1
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/bossbar/BossBarColor.java
@@ -0,0 +1,19 @@
+package de.pxav.kelp.core.player.bossbar;
+
+import de.pxav.kelp.core.version.KelpVersion;
+import de.pxav.kelp.core.version.SinceKelpVersion;
+
+/**
+ * A class description goes here.
+ *
+ * @author pxav
+ */
+public enum BossBarColor {
+
+ @SinceKelpVersion(KelpVersion.MC_1_8_0)
+ PURPLE,
+
+ @SinceKelpVersion(KelpVersion.MC_1_9_0)
+ YELLOW;
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/bossbar/BossBarStyle.java b/core/src/main/java/de/pxav/kelp/core/player/bossbar/BossBarStyle.java
new file mode 100644
index 00000000..3dcafd98
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/bossbar/BossBarStyle.java
@@ -0,0 +1,21 @@
+package de.pxav.kelp.core.player.bossbar;
+
+import de.pxav.kelp.core.version.KelpVersion;
+import de.pxav.kelp.core.version.SinceKelpVersion;
+
+/**
+ * A class description goes here.
+ *
+ * @author pxav
+ */
+public enum BossBarStyle {
+
+ @SinceKelpVersion(KelpVersion.MC_1_8_0)
+ SOLID,
+
+ SEGMENTS_6,
+ SEGMENTS_10,
+ SEGMENTS_12,
+ SEGMENTS_20;
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/message/InteractiveMessage.java b/core/src/main/java/de/pxav/kelp/core/player/message/InteractiveMessage.java
new file mode 100644
index 00000000..2baf7a17
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/message/InteractiveMessage.java
@@ -0,0 +1,59 @@
+package de.pxav.kelp.core.player.message;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * An interactive message is a chat message on which
+ * players can click and trigger a specific event.
+ *
+ * @author pxav
+ */
+public class InteractiveMessage {
+
+ // all components (different interactions) of the message.
+ private Collection components;
+
+ private InteractiveMessage() {
+ // initialize the component list
+ this.components = new ArrayList<>();
+ }
+
+ /**
+ * Factory method for the interactive message. Use this method
+ * if you want to create a new message as the normal constructor
+ * is private.
+ *
+ * @return A new instance of an interactive message.
+ */
+ public static InteractiveMessage create() {
+ return new InteractiveMessage();
+ }
+
+ /**
+ * Add a new component to the message. This does not mean that you create
+ * a new line, but a new segment of events.
+ *
+ * Example: If you have a message to accept a party, you create a component
+ * for the prefix and the normal text ([PARTY] Click here to...) and a component,
+ * which is clickable and accepts the party (...accept the party.). Unlike in the
+ * normal spigot api, you can create unlimited components with unlimited color codes.
+ *
+ * @param messageComponent The component to add.
+ * @return The current message object (used for fluent builder structure).
+ */
+ public InteractiveMessage addComponent(MessageComponent messageComponent) {
+ this.components.add(messageComponent);
+ return this;
+ }
+
+ /**
+ * Returns a full {@link Collection} of all components of the interactive message.
+ *
+ * @return All components of the message.
+ */
+ public Collection getComponents() {
+ return components;
+ }
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/message/MessageClickAction.java b/core/src/main/java/de/pxav/kelp/core/player/message/MessageClickAction.java
new file mode 100644
index 00000000..d4a098ec
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/message/MessageClickAction.java
@@ -0,0 +1,57 @@
+package de.pxav.kelp.core.player.message;
+
+import de.pxav.kelp.core.version.KelpVersion;
+import de.pxav.kelp.core.version.SinceKelpVersion;
+
+/**
+ * Describes a click action that should be performed when
+ * an {@link InteractiveMessage} is clicked.
+ *
+ * You can apply those actions via {@link MessageComponent}
+ *
+ * @author pxav
+ */
+public enum MessageClickAction {
+
+ /**
+ * A given command is executed when the player clicks the message.
+ * Prefixing the message with a {@code /} is not necessary.
+ */
+ EXECUTE_COMMAND,
+
+ /**
+ * Suggests a given command to the player. This means that the command
+ * is written to the player's chat text box but not executed yet.
+ */
+ SUGGEST_COMMAND,
+
+ /**
+ * Opens a given URL to the player, which can be a website. Be sure to
+ * prefix your message with {@code https://} in this case.
+ */
+ OPEN_URL,
+
+ /**
+ * Opens a file at the given file path. Please note that only server files
+ * can be accessed. Local files of the client cannot be queried.
+ */
+ OPEN_FILE,
+
+ /**
+ * Changes the page of a book.
+ */
+ CHANGE_PAGE,
+
+ /**
+ * Makes the player send a chat message under his name.
+ */
+ SEND_CHAT_MESSAGE,
+
+ /**
+ * Copies a given text into the player's clipboard. Please note that
+ * this is not available in all versions.
+ */
+ @SinceKelpVersion(KelpVersion.MC_1_14_0)
+ COPY_TO_CLIPBOARD,
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/message/MessageComponent.java b/core/src/main/java/de/pxav/kelp/core/player/message/MessageComponent.java
new file mode 100644
index 00000000..faf6a65b
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/message/MessageComponent.java
@@ -0,0 +1,324 @@
+package de.pxav.kelp.core.player.message;
+
+import java.io.File;
+import java.net.URL;
+
+/**
+ * A message component is a part/segment of an {@link InteractiveMessage}.
+ * Unlike normal spigot {@link net.md_5.bungee.api.chat.TextComponent} you can add
+ * unlimited components to a clickable/hoverable message, so you can have
+ * different click events in one chat line/chat message.
+ *
+ * In order to keep control over those different events, you divide your message
+ * into different components: If you have a message for editing a player report
+ * for example, you have a message showing different options:
+ * {@code [SERVER] Select an option: BAN PLAYER | KICK PLAYER | CONTINUE}
+ *
+ * For the prefix and the message, you would create a component with no events,
+ * but then you would append a new component for each of the options (ban, kick, continue).
+ *
+ * For each component you can select what should happen when a player hovers over a message or
+ * clicks it.
+ *
+ * You can apply chat colors by using {@code § + code id (e. g. §a, §4, ...)}. If you want to use
+ * a style code (such as underlining, obfuscating, etc.) only use those codes in combination
+ * with a color code ({@code §a§o} not {@code §o})
+ *
+ * @author pxav
+ */
+public class MessageComponent {
+
+ // the text of the message displayed in the chat.
+ private String text;
+
+ // the type of action to be performed when the message is clicked.
+ private MessageClickAction clickAction;
+
+ // the type of action to be performed when the message is hovered.
+ private MessageHoverAction hoverAction;
+
+ // Values for the individual event types. If you have EXECUTE_COMMAND
+ // for example, you pass a string with the command here.
+ private Object clickValue;
+ private Object hoverValue;
+
+ private MessageComponent() {}
+
+ /**
+ * Static factory method. If you want to create new instances of message
+ * components, use this method instead of the normal constructor.
+ *
+ * @return A new instance of a message component.
+ */
+ public static MessageComponent create() {
+ return new MessageComponent();
+ }
+
+ /**
+ * The text which should appear in the chat when this component is rendered.
+ *
+ * @param text The text to be shown in the chat line.
+ *
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent text(String text) {
+ this.text = text;
+ return this;
+ }
+
+ /**
+ * Sets the click event action. Please remember to set the corresponding
+ * value as well.
+ *
+ * @param clickAction The action to be performed when the component is clicked.
+ *
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent clickAction(MessageClickAction clickAction) {
+ this.clickAction = clickAction;
+ return this;
+ }
+
+ /**
+ * Sets the hover event action. Please remember to set the corresponding
+ * hover value as well.
+ *
+ * @param hoverAction The action to be performed when the player hovers
+ * over the component.
+ *
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent hoverAction(MessageHoverAction hoverAction) {
+ this.hoverAction = hoverAction;
+ return this;
+ }
+
+ /**
+ * Sets the value of the currently set click event. If you selected
+ * EXECUTE_COMMAND as click event, pass the command here.
+ *
+ * @param clickValue The value of the click event.
+ *
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent clickValue(Object clickValue) {
+ this.clickValue = clickValue;
+ return this;
+ }
+
+ /**
+ * Sets the value of the currently set hover event. If you selected
+ * SHOW_MESSAGE as hover event, pass the message here.
+ *
+ * @param hoverValue The value of the hover event.
+ *
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent hoverValue(Object hoverValue) {
+ this.hoverValue = hoverValue;
+ return this;
+ }
+
+ /**
+ * Executes a command when the message is clicked. If the player who clicks
+ * the message does not have permissions to execute it, the command won't be executed.
+ *
+ * @param command The command to be executed by the player.
+ *
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent executeCommandOnClick(String command) {
+ this.clickAction = MessageClickAction.EXECUTE_COMMAND;
+ this.clickValue = command;
+ return this;
+ }
+
+ /**
+ * Copies the given command or message to the player's chat text box.
+ *
+ * @param command The command/message to be pasted to the chat box.
+ *
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent suggestCommandOnClick(String command) {
+ this.clickAction = MessageClickAction.SUGGEST_COMMAND;
+ this.clickValue = command;
+ return this;
+ }
+
+ /**
+ * Opens the given url to the player. This can be a website for example.
+ *
+ * @param url The {@code url} to be opened. If you want to open a website
+ * please remember to prefix your message with {@code https://}.
+ *
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent openURLOnClick(String url) {
+ this.clickAction = MessageClickAction.OPEN_URL;
+ this.clickValue = url;
+ return this;
+ }
+
+ /**
+ * Opens the given url to the player. This can be a website for example.
+ *
+ * @param url The {@code url} to be opened. If you want to open a website
+ * please remember to prefix your message with {@code https://}.
+ * This method only takes the path of the given {@link URL} object
+ * with {@link URL#getPath()}.
+ *
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent openURLOnClick(URL url) {
+ this.clickAction = MessageClickAction.OPEN_URL;
+ this.clickValue = url.getPath();
+ return this;
+ }
+
+ /**
+ * Opens the file at the given location when the player clicks on the
+ * message. Please ensure that the requested file is on the file system
+ * of the host machine and not on the client side as the server does not
+ * have access to those files.
+ *
+ * @param url The URL where the file is located. This method only takes the
+ * URL path with {@link URL#getPath()}.
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent openFileOnClick(URL url) {
+ this.clickAction = MessageClickAction.OPEN_FILE;
+ this.clickValue = url.getPath();
+ return this;
+ }
+
+ /**
+ * Opens the file at the given location when the player clicks on the
+ * message. Please ensure that the requested file is on the file system
+ * of the host machine and not on the client side as the server does not
+ * have access to those files.
+ *
+ * @param file The file object of the file to be accessed.
+ *
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent openFileOnClick(File file) {
+ this.clickAction = MessageClickAction.OPEN_FILE;
+ this.clickValue = file.getPath();
+ return this;
+ }
+
+ /**
+ * Opens the file at the given location when the player clicks on the
+ * message. Please ensure that the requested file is on the file system
+ * of the host machine and not on the client side as the server does not
+ * have access to those files.
+ *
+ * @param filePath The path of the file to be accessed.
+ *
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent openFileOnClick(String filePath) {
+ this.clickAction = MessageClickAction.OPEN_FILE;
+ this.clickValue = filePath;
+ return this;
+ }
+
+ /**
+ * Makes the player sends a chat message under his name when they click
+ * the message.
+ *
+ * @param message The message to be sent by the player.
+ *
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent sendChatMessageOnClick(String message) {
+ this.clickAction = MessageClickAction.SEND_CHAT_MESSAGE;
+ this.clickValue = message;
+ return this;
+ }
+
+ /**
+ * When the player clicks on the message a given text will
+ * be copied to its clipboard.
+ *
+ * @param text The text to be copied.
+ *
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent copyToClipboardOnClick(String text) {
+ this.clickAction = MessageClickAction.COPY_TO_CLIPBOARD;
+ this.clickValue = text;
+ return this;
+ }
+
+ /**
+ * Changes the page of the current player book.
+ *
+ * @param page The page to navigate to.
+ *
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent changeBookPageOnClick(int page) {
+ this.clickAction = MessageClickAction.CHANGE_PAGE;
+ this.clickValue = page;
+ return this;
+ }
+
+ /**
+ * Sets the hover event to showing a message.
+ *
+ * @param message The message to be shown when the player hovers over
+ * the chat message.
+ * @return The current component instance for fluent builder design.
+ */
+ public MessageComponent showMessageOnHover(String message) {
+ this.hoverAction = MessageHoverAction.SHOW_MESSAGE;
+ this.hoverValue = message;
+ return this;
+ }
+
+ /**
+ * Returns the type of action that should be performed
+ * when a player clicks on the message.
+ * @return The click action type.
+ */
+ public MessageClickAction getClickAction() {
+ return clickAction;
+ }
+
+ /**
+ * Returns the type of action that should be performed
+ * when a player hovers over the message.
+ * @return The hover action type.
+ */
+ public MessageHoverAction getHoverAction() {
+ return hoverAction;
+ }
+
+ /**
+ * Returns the event value which is set for the click event.
+ * If you selected SUGGEST_COMMAND, the command is returned here.
+ * @return The click event value object.
+ */
+ public Object getClickValue() {
+ return clickValue;
+ }
+
+ /**
+ * Returns the event value which is set for the hover event.
+ * If you selected SHOW_MESSAGE, the message is returned here.
+ * @return The hover event value object.
+ */
+ public Object getHoverValue() {
+ return hoverValue;
+ }
+
+ /**
+ * Returns the text displayed in the chat line.
+ * @return The text of the component.
+ */
+ public String getText() {
+ return text;
+ }
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/message/MessageHoverAction.java b/core/src/main/java/de/pxav/kelp/core/player/message/MessageHoverAction.java
new file mode 100644
index 00000000..af41ec38
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/message/MessageHoverAction.java
@@ -0,0 +1,16 @@
+package de.pxav.kelp.core.player.message;
+
+/**
+ * Describes the action to be performed when a {@link MessageComponent}
+ * is hovered by a player with the mouse courser.
+ *
+ * @author pxav
+ */
+public enum MessageHoverAction {
+
+ /**
+ * Displays a given message to the player.
+ */
+ SHOW_MESSAGE
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/prompt/PromptResponseType.java b/core/src/main/java/de/pxav/kelp/core/player/prompt/PromptResponseType.java
new file mode 100644
index 00000000..7f5fd5b4
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/prompt/PromptResponseType.java
@@ -0,0 +1,29 @@
+package de.pxav.kelp.core.player.prompt;
+
+/**
+ * This constant describes if an input from any player prompt
+ * has been accepted by the handler. If the prompt asked for a
+ * number but the input also consists of alphabetical chars for
+ * example, the handler would return {@link PromptResponseType#TRY_AGAIN},
+ * which means that the prompt will ask the player again.
+ *
+ * This simplifies input validation by a lot.
+ *
+ * @author pxav
+ */
+public enum PromptResponseType {
+
+ /**
+ * The result is valid and has been accepted by the result handler.
+ * The prompt will close.
+ */
+ ACCEPTED,
+
+ /**
+ * The result is invalid and has not been accepted by the result handler.
+ * The prompt will open again asking for another input by the player until
+ * the handler accepts it.
+ */
+ TRY_AGAIN
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/prompt/PromptTimeout.java b/core/src/main/java/de/pxav/kelp/core/player/prompt/PromptTimeout.java
new file mode 100644
index 00000000..a049de4b
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/prompt/PromptTimeout.java
@@ -0,0 +1,105 @@
+package de.pxav.kelp.core.player.prompt;
+
+import java.util.UUID;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class is used to create possible timeouts for player
+ * prompts. You can configure that your plugin waits for 10
+ * seconds until it will automatically close the prompt again
+ * if the player has given no valid input during that time.
+ *
+ * In addition, you can define what should happen on a timeout
+ * by configuring a {@link Runnable}, which can be used to
+ * send messages or play sounds.
+ *
+ * @author pxav
+ */
+public class PromptTimeout {
+
+ // how long the server should wait for an input
+ private int timeout = 0;
+ private TimeUnit timeUnit = TimeUnit.MILLISECONDS;
+
+ // the code to be executed when the prompt times out
+ // (can be used to send messages, play sounds, ...)
+ private Runnable onTimeout;
+
+ // whether the runnable should be executed on an async thread
+ private boolean async = true;
+
+ // whether the prompt should be closed on timeout.
+ private boolean closeOnTimeout = false;
+
+ // the task id of the scheduler handling the timeout.
+ private UUID taskId;
+
+ public PromptTimeout() {}
+
+ public PromptTimeout(int timeout, TimeUnit timeUnit) {
+ this.timeout = timeout;
+ this.timeUnit = timeUnit;
+ }
+
+ public PromptTimeout(int timeout, TimeUnit timeUnit, Runnable onTimeout, boolean async, boolean closeOnTimeout) {
+ this.timeout = timeout;
+ this.timeUnit = timeUnit;
+ this.onTimeout = onTimeout;
+ this.async = async;
+ this.closeOnTimeout = closeOnTimeout;
+ }
+
+ /**
+ * Sets the kelp-internal task id of the scheduler handling the timeout.
+ * This scheduler also executes the runnable {@link #getOnTimeout()} later.
+ *
+ * @param taskId The id of the scheduler that handles the timeout.
+ */
+ public void setTaskId(UUID taskId) {
+ this.taskId = taskId;
+ }
+
+ /**
+ * @return the time to wait until the prompt times out in the unit provided in {@link #getTimeUnit()}
+ */
+ public int getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * @return The code that should be executed when the prompt times out.
+ */
+ public Runnable getOnTimeout() {
+ return onTimeout;
+ }
+
+ /**
+ * @return the {@link TimeUnit} of the provided time out time in {@link #getTimeout()}
+ */
+ public TimeUnit getTimeUnit() {
+ return timeUnit;
+ }
+
+ /**
+ * @return whether the prompt should be closed on timeout.
+ */
+ public boolean shouldCloseOnTimeout() {
+ return this.closeOnTimeout;
+ }
+
+ /**
+ * @return whether the given runnable ({@link #getOnTimeout()}) should be
+ * executed asynchronously.
+ */
+ public boolean isAsync() {
+ return async;
+ }
+
+ /**
+ * @return The kelp-internal task id of the scheduler handling the timeout.
+ */
+ public UUID getTaskId() {
+ return taskId;
+ }
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/prompt/SimplePromptResponseHandler.java b/core/src/main/java/de/pxav/kelp/core/player/prompt/SimplePromptResponseHandler.java
new file mode 100644
index 00000000..2fb7d996
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/prompt/SimplePromptResponseHandler.java
@@ -0,0 +1,24 @@
+package de.pxav.kelp.core.player.prompt;
+
+/**
+ * This interface should be implemented by every class handling the input
+ * of a simple prompt (Chat or anvil). If you want to handle a sign prompt,
+ * please use {@link de.pxav.kelp.core.player.prompt.sign.SignPromptResponseHandler}.
+ *
+ * @author pxav
+ */
+public interface SimplePromptResponseHandler {
+
+ /**
+ * This method is executed every time a player submits their prompt
+ * input to the server. Then you can check this input and decide whether
+ * it is valid and the prompt can be closed or whether the player
+ * has to try it again.
+ *
+ * @param input The input sent by the player.
+ * @return Whether the player's input was valid and the prompt may close
+ * or whether the player has to try again.
+ */
+ PromptResponseType accept(String input);
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/prompt/anvil/AnvilPrompt.java b/core/src/main/java/de/pxav/kelp/core/player/prompt/anvil/AnvilPrompt.java
new file mode 100644
index 00000000..7ff5744b
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/prompt/anvil/AnvilPrompt.java
@@ -0,0 +1,187 @@
+package de.pxav.kelp.core.player.prompt.anvil;
+
+import com.google.common.collect.Lists;
+import de.pxav.kelp.core.inventory.material.KelpMaterial;
+import de.pxav.kelp.core.player.prompt.PromptTimeout;
+import de.pxav.kelp.core.player.prompt.SimplePromptResponseHandler;
+import de.pxav.kelp.core.player.prompt.sign.SignPrompt;
+import de.pxav.kelp.core.player.prompt.sign.SignPromptResponseHandler;
+import de.pxav.kelp.core.player.prompt.sign.SignPromptVersionTemplate;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * An anvil prompt is a simple and intuitive way to query user input.
+ * The player is presented an anvil gui and can use the renaming line
+ * as text prompt. The result item is used to confirm the input, which
+ * can be handled with a {@link SimplePromptResponseHandler}.
+ *
+ * @author pxav
+ */
+public class AnvilPrompt {
+
+ private final AnvilPromptVersionTemplate anvilPromptVersionTemplate;
+
+ private final Player player;
+ private String initialText = "";
+ private KelpMaterial sourceMaterial = KelpMaterial.NAME_TAG;
+ private Runnable onClose;
+ private PromptTimeout timeout = new PromptTimeout();
+ private SimplePromptResponseHandler responseHandler;
+
+ public AnvilPrompt(Player player, AnvilPromptVersionTemplate anvilPromptVersionTemplate) {
+ this.player = player;
+ this.anvilPromptVersionTemplate = anvilPromptVersionTemplate;
+ }
+
+ /**
+ * Give the prompt a default text that is displayed when the gui opens.
+ *
+ * @param initialText The text you want to display on inventory open.
+ * @return The current prompt instance for fluent builder design.
+ */
+ public AnvilPrompt initialText(String initialText) {
+ this.initialText = initialText;
+ return this;
+ }
+
+ /**
+ * Sets the source material used in the anvil inventory. The source item is
+ * on the very left slot and basically is the item the player is renaming.
+ * This material will be used for the result item later as well on which the
+ * player has to click in order to confirm their input.
+ *
+ * @param kelpMaterial The material you want to use for source and result.
+ * @return The current prompt instance for fluent builder design.
+ */
+ public AnvilPrompt sourceMaterial(KelpMaterial kelpMaterial) {
+ this.sourceMaterial = kelpMaterial;
+ return this;
+ }
+
+ /**
+ * Define what should happen when the player closes their inventory manually.
+ *
+ * @param onClose The code that should be executed.
+ * @return The current prompt instance for fluent builder design.
+ */
+ public AnvilPrompt onClose(Runnable onClose) {
+ this.onClose = onClose;
+ return this;
+ }
+
+ /**
+ * Configures a synchronous timeout. That means that the code which is ran on timeout will
+ * be executed on the server's main thread. You should prefer this option if you know your
+ * actions are not thread-safe.
+ *
+ * @param timeout How long it should take until the prompt times out.
+ * @param timeUnit Provide a time unit for the {@code timeout} value.
+ * @param onTimeout Define what should happen when the player closes its inventory.
+ * Please check if your actions are thread safe. If so, use {@link #withAsyncTimeout(int, TimeUnit, Runnable, boolean)}
+ * instead.
+ * @param closeOnTimeout Should the inventory be closed when the player prompt times out?
+ * @return The current prompt instance for fluent builder design.
+ */
+ public AnvilPrompt withSyncTimeout(int timeout, TimeUnit timeUnit, Runnable onTimeout, boolean closeOnTimeout) {
+ this.timeout = new PromptTimeout(timeout, timeUnit, onTimeout, false, closeOnTimeout);
+ return this;
+ }
+
+ /**
+ * Configures an asynchronous timeout. That means that the code which is ran on timeout will
+ * be executed on another thread than the server's main thread. You should prefer this option
+ * if you know your actions are thread-safe or you can jump to the main thread easily.
+ *
+ * @param timeout How long it should take until the prompt times out.
+ * @param timeUnit Provide a time unit for the {@code timeout} value.
+ * @param onTimeout Define what should happen when the player closes its inventory.
+ * Please check if your actions are thread safe. If not, use {@link #withSyncTimeout(int, TimeUnit, Runnable)}
+ * instead.
+ * @param closeOnTimeOut Should the inventory be closed when the prompt times out?
+ * @return The current prompt instance for fluent builder design.
+ */
+ public AnvilPrompt withAsyncTimeout(int timeout, TimeUnit timeUnit, Runnable onTimeout, boolean closeOnTimeOut) {
+ this.timeout = new PromptTimeout(timeout, timeUnit, onTimeout, true, closeOnTimeOut);
+ return this;
+ }
+
+ /**
+ * Configures a synchronous timeout. That means that the code which is ran on timeout will
+ * be executed on the server's main thread. You should prefer this option if you know your
+ * actions are not thread-safe.
+ *
+ * This method closes the inventory by default. If you want to change that, use
+ * {@link #withSyncTimeout(int, TimeUnit, Runnable, boolean)}.
+ *
+ * @param timeout How long it should take until the prompt times out.
+ * @param timeUnit Provide a time unit for the {@code timeout} value.
+ * @param onTimeout Define what should happen when the player closes its inventory.
+ * Please check if your actions are thread safe. If so, use {@link #withAsyncTimeout(int, TimeUnit, Runnable)}
+ * instead.
+ * @return The current prompt instance for fluent builder design.
+ */
+ public AnvilPrompt withSyncTimeout(int timeout, TimeUnit timeUnit, Runnable onTimeout) {
+ this.timeout = new PromptTimeout(timeout, timeUnit, onTimeout, false, true);
+ return this;
+ }
+
+ /**
+ * Configures an asynchronous timeout. That means that the code which is ran on timeout will
+ * be executed on another thread than the server's main thread. You should prefer this option
+ * if you know your actions are thread-safe or you can jump to the main thread easily.
+ *
+ * This method closes the inventory by default. If you want to change that, use
+ * {@link #withAsyncTimeout(int, TimeUnit, Runnable, boolean)}.
+ *
+ * @param timeout How long it should take until the prompt times out.
+ * @param timeUnit Provide a time unit for the {@code timeout} value.
+ * @param onTimeout Define what should happen when the player closes its inventory.
+ * Please check if your actions are thread safe. If not, use {@link #withSyncTimeout(int, TimeUnit, Runnable)}
+ * instead.
+ * @return The current prompt instance for fluent builder design.
+ */
+ public AnvilPrompt withAsyncTimeout(int timeout, TimeUnit timeUnit, Runnable onTimeout) {
+ this.timeout = new PromptTimeout(timeout, timeUnit, onTimeout, true, true);
+ return this;
+ }
+
+ /**
+ * Provide the handler class handling the input of the player.
+ *
+ * @param responseHandler The handler class implementing {@link SimplePromptResponseHandler}
+ */
+ public void handle(SimplePromptResponseHandler responseHandler) {
+ this.responseHandler = responseHandler;
+ this.anvilPromptVersionTemplate.openPrompt(this.player, this.initialText, this.sourceMaterial, this.onClose, this.timeout, this.responseHandler);
+ }
+
+ /**
+ * Gets the instance of the response handler for the current prompt.
+ * If no handler has been defined, it will return {@code null}.
+ * @return The current prompt response handler.
+ */
+ public SimplePromptResponseHandler getResponseHandler() {
+ return responseHandler;
+ }
+
+ /**
+ * Gets the default text that is displayed when the gui opens.
+ * @return The initial text of the current anvil prompt.
+ */
+ public String getInitialText() {
+ return this.initialText;
+ }
+
+ /**
+ * Gets all information about the timeout configuration. More information about that
+ * can be found in {@link PromptTimeout}
+ * @return The current prompt timeout configuration.
+ */
+ public PromptTimeout getTimeout() {
+ return timeout;
+ }
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/prompt/anvil/AnvilPromptVersionTemplate.java b/core/src/main/java/de/pxav/kelp/core/player/prompt/anvil/AnvilPromptVersionTemplate.java
new file mode 100644
index 00000000..5709af28
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/prompt/anvil/AnvilPromptVersionTemplate.java
@@ -0,0 +1,44 @@
+package de.pxav.kelp.core.player.prompt.anvil;
+
+import de.pxav.kelp.core.application.KelpVersionTemplate;
+import de.pxav.kelp.core.inventory.material.KelpMaterial;
+import de.pxav.kelp.core.player.KelpPlayer;
+import de.pxav.kelp.core.player.prompt.PromptTimeout;
+import de.pxav.kelp.core.player.prompt.SimplePromptResponseHandler;
+import org.bukkit.entity.Player;
+
+/**
+ * This version template is used to handle version specific stuff
+ * for anvil prompts.
+ *
+ * @author pxav
+ */
+@KelpVersionTemplate
+public abstract class AnvilPromptVersionTemplate {
+
+ /**
+ * Opens a new anvil prompt to the given player. This means that the player
+ * will see an anvil GUI and has to enter a text. After that he can confirm this
+ * text by clicking on the result item at the very right of the inventory.
+ * Then the input can be handled and the plugin can decide whether it accepts
+ * the input or the player has to try again.
+ *
+ * @param player The player you want the prompt to open to.
+ * @param initialText The initial text which should already be in the text line as default input.
+ * @param sourceMaterial The material to use in the inventory on the left slot (the item that is actually
+ * renamed and the item that has the default name) as well as in the right result
+ * slot (the item used to confirm the input).
+ * @param onClose If the player closes their inventory manually while being in the prompt, what should happen?
+ * @param timeout If you want to enable a timeout, you can configure it here. If you don't want
+ * a timeout, pass {@code null}.
+ * @param handler The class handling the input by the player. More detailed information can be found
+ * at {@link SimplePromptResponseHandler}
+ */
+ public abstract void openPrompt(Player player,
+ String initialText,
+ KelpMaterial sourceMaterial,
+ Runnable onClose,
+ PromptTimeout timeout,
+ SimplePromptResponseHandler handler);
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/prompt/chat/ChatPromptVersionTemplate.java b/core/src/main/java/de/pxav/kelp/core/player/prompt/chat/ChatPromptVersionTemplate.java
new file mode 100644
index 00000000..0089deb2
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/prompt/chat/ChatPromptVersionTemplate.java
@@ -0,0 +1,30 @@
+package de.pxav.kelp.core.player.prompt.chat;
+
+import de.pxav.kelp.core.application.KelpVersionTemplate;
+import de.pxav.kelp.core.player.KelpPlayer;
+import de.pxav.kelp.core.player.prompt.PromptTimeout;
+import de.pxav.kelp.core.player.prompt.SimplePromptResponseHandler;
+import org.bukkit.entity.Player;
+
+import java.util.Collection;
+
+/**
+ * This version template is used for handling the simple chat prompt for players.
+ *
+ * @author pxav
+ */
+@KelpVersionTemplate
+public abstract class ChatPromptVersionTemplate {
+
+ /**
+ * Sets the player's chat into chat prompt mode. This means that the normal
+ * chat is disabled and messages are not visible for other players anymore.
+ * If the player's input is handled successfully, the chat is turned into
+ * normal mode again.
+ *
+ * @param simpleChatPrompt The {@link SimpleChatPrompt} to open. It can be built using
+ * an instance of {@link KelpPlayer}.
+ */
+ public abstract void simpleChatPrompt(SimpleChatPrompt simpleChatPrompt);
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/prompt/chat/DefaultFontSize.java b/core/src/main/java/de/pxav/kelp/core/player/prompt/chat/DefaultFontSize.java
new file mode 100644
index 00000000..67bf5859
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/prompt/chat/DefaultFontSize.java
@@ -0,0 +1,145 @@
+package de.pxav.kelp.core.player.prompt.chat;
+
+/**
+ * This class is used to decide how big different chars appear in the chat,
+ * which is important for deciding where to put a message if it should be
+ * centered.
+ *
+ * This class is written by SirSpoodles from SpigotMC who provided a method for
+ * sending centered chat messages. Link to the corresponding spigot page:
+ * https://www.spigotmc.org/threads/free-code-sending-perfectly-centered-chat-message.95872/
+ *
+ * The file has been modified and some further chars have been implemented to
+ * be more universal and applicable for more use cases.
+ *
+ * @author SirSpoodles
+ * @author pxav
+ */
+public enum DefaultFontSize {
+
+ A('A', 5),
+ a('a', 5),
+ B('B', 5),
+ b('b', 5),
+ C('C', 5),
+ c('c', 5),
+ D('D', 5),
+ d('d', 5),
+ E('E', 5),
+ e('e', 5),
+ F('F', 5),
+ f('f', 4),
+ G('G', 5),
+ g('g', 5),
+ H('H', 5),
+ h('h', 5),
+ I('I', 3),
+ i('i', 1),
+ J('J', 5),
+ j('j', 5),
+ K('K', 5),
+ k('k', 4),
+ L('L', 5),
+ l('l', 1),
+ M('M', 5),
+ m('m', 5),
+ N('N', 5),
+ n('n', 5),
+ O('O', 5),
+ o('o', 5),
+ P('P', 5),
+ p('p', 5),
+ Q('Q', 5),
+ q('q', 5),
+ R('R', 5),
+ r('r', 5),
+ S('S', 5),
+ s('s', 5),
+ T('T', 5),
+ t('t', 4),
+ U('U', 5),
+ u('u', 5),
+ V('V', 5),
+ v('v', 5),
+ W('W', 5),
+ w('w', 5),
+ X('X', 5),
+ x('x', 5),
+ Y('Y', 5),
+ y('y', 5),
+ Z('Z', 5),
+ z('z', 5),
+ NUM_1('1', 5),
+ NUM_2('2', 5),
+ NUM_3('3', 5),
+ NUM_4('4', 5),
+ NUM_5('5', 5),
+ NUM_6('6', 5),
+ NUM_7('7', 5),
+ NUM_8('8', 5),
+ NUM_9('9', 5),
+ NUM_0('0', 5),
+ EXCLAMATION_POINT('!', 1),
+ AT_SYMBOL('@', 6),
+ NUM_SIGN('#', 5),
+ DOLLAR_SIGN('$', 5),
+ PERCENT('%', 5),
+ UP_ARROW('^', 5),
+ AMPERSAND('&', 5),
+ ASTERISK('*', 5),
+ LEFT_PARENTHESIS('(', 4),
+ RIGHT_PERENTHESIS(')', 4),
+ MINUS('-', 5),
+ UNDERSCORE('_', 5),
+ PLUS_SIGN('+', 5),
+ EQUALS_SIGN('=', 5),
+ LEFT_CURL_BRACE('{', 4),
+ RIGHT_CURL_BRACE('}', 4),
+ LEFT_BRACKET('[', 3),
+ RIGHT_BRACKET(']', 3),
+ COLON(':', 1),
+ SEMI_COLON(';', 1),
+ DOUBLE_QUOTE('"', 3),
+ SINGLE_QUOTE('\'', 1),
+ LEFT_ARROW('<', 4),
+ RIGHT_ARROW('>', 4),
+ QUESTION_MARK('?', 5),
+ SLASH('/', 5),
+ BACK_SLASH('\\', 5),
+ LINE('|', 1),
+ TILDE('~', 5),
+ TICK('`', 2),
+ PERIOD('.', 1),
+ COMMA(',', 1),
+ SPACE(' ', 3),
+ DEFAULT('a', 4);
+
+ private char character;
+ private int length;
+
+ DefaultFontSize(char character, int length) {
+ this.character = character;
+ this.length = length;
+ }
+
+ public char getCharacter(){
+ return this.character;
+ }
+
+ public int getLength(){
+ return this.length;
+ }
+
+ public int getBoldLength(){
+ if(this == DefaultFontSize.SPACE) return this.getLength();
+ return this.length + 1;
+ }
+
+ public static DefaultFontSize getDefaultFontInfo(char c){
+ for(DefaultFontSize dFI : DefaultFontSize.values()){
+ if(dFI.getCharacter() == c) return dFI;
+ }
+ return DefaultFontSize.DEFAULT;
+ }
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/prompt/chat/SimpleChatPrompt.java b/core/src/main/java/de/pxav/kelp/core/player/prompt/chat/SimpleChatPrompt.java
new file mode 100644
index 00000000..ae557825
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/prompt/chat/SimpleChatPrompt.java
@@ -0,0 +1,199 @@
+package de.pxav.kelp.core.player.prompt.chat;
+
+import de.pxav.kelp.core.player.prompt.PromptTimeout;
+import de.pxav.kelp.core.player.prompt.SimplePromptResponseHandler;
+import org.bukkit.entity.Player;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * The simple chat prompt is a way for developers to simply query
+ * small text data from the player. An example would be setting a nickname, etc.
+ *
+ * For querying the input, the normal chat text box is used and the normal chat is
+ * disabled for the player while they type in their input. The user can exit at
+ * every time by using an exit flag and an optional echo can be configured.
+ *
+ * If the input has been accepted or the player has left the input mode, the player
+ * is allowed to write into the normal public chat again.
+ *
+ * @author pxav
+ */
+public class SimpleChatPrompt {
+
+ private final ChatPromptVersionTemplate chatPromptVersionTemplate;
+
+ private final Player player;
+ private String cancelFlag;
+ private boolean enableEcho = false;
+ private String echoPrefix = "";
+ private String echoSuffix = "";
+ private Runnable onCancel;
+ private PromptTimeout timeout = new PromptTimeout();
+ private SimplePromptResponseHandler responseHandler;
+
+ public SimpleChatPrompt(Player player, ChatPromptVersionTemplate chatPromptVersionTemplate) {
+ this.player = player;
+ this.chatPromptVersionTemplate = chatPromptVersionTemplate;
+ }
+
+ /**
+ * Defines the message a player has to type in order to exit the
+ * prompt and use the chat normally again.
+ *
+ * @param cancelFlag The string a player has to type in order to exit again.
+ * @return The current instance of the prompt for fluent builder design.
+ */
+ public SimpleChatPrompt cancelFlag(String cancelFlag) {
+ this.cancelFlag = cancelFlag;
+ return this;
+ }
+
+ /**
+ * Enables an echo for the input the player types. An echo is a
+ * message printing the input the player typed into the chat again
+ * to make the input processing more transparent and allow the player
+ * to find typos, etc.
+ *
+ * @param prefix The text that should be displayed in front of the actual input.
+ * Can also be used to provide color codes for the message.
+ * Choose {@code null} if you do not want a prefix.
+ * @param suffix The text that should be displayed after the actual input.
+ * Choose {@code null} if you want no suffix.
+ * @return The current instance of the prompt for fluent builder design.
+ */
+ public SimpleChatPrompt enableEcho(String prefix, String suffix) {
+ this.enableEcho = true;
+ this.echoPrefix = prefix;
+ this.echoSuffix = suffix;
+ return this;
+ }
+
+ /**
+ * Disables the echo message. An echo message is a message printing
+ * the input from the player into the chat again so that the input
+ * processing becomes more transparent for the player.
+ *
+ * @return The current instance of the prompt for fluent builder design.
+ */
+ public SimpleChatPrompt disableEcho() {
+ this.enableEcho = false;
+ return this;
+ }
+
+ /**
+ * Enables a timeout for the current chat prompt. More detailed information
+ * about timeouts can be found in {@link PromptTimeout}.
+ *
+ * The code provided in the timeout is executed synchronously. Use
+ * {@link #withAsyncTimeout(int, TimeUnit, Runnable)} to invert that.
+ *
+ * @param timeout The amount of time that has to pass until the prompt times out.
+ * @param timeUnit The {@link TimeUnit} of the given {@code timeout}.
+ * @param onTimeout The code that should be executed when the prompt times out.
+ * In this case, this code is executed synchronously.
+ * @return The current instance of the prompt for fluent builder design.
+ */
+ public SimpleChatPrompt withSyncTimeout(int timeout, TimeUnit timeUnit, Runnable onTimeout) {
+ this.timeout = new PromptTimeout(timeout, timeUnit, onTimeout, false, false);
+ return this;
+ }
+
+ /**
+ * Enables a timeout for the current chat prompt. More detailed information
+ * about timeouts can be found in {@link PromptTimeout}.
+ *
+ * The code provided in the timeout is executed asynchronously. Use
+ * {@link #withSyncTimeout(int, TimeUnit, Runnable)} to invert that.
+ *
+ * @param timeout The amount of time that has to pass until the prompt times out.
+ * @param timeUnit The {@link TimeUnit} of the given {@code timeout}.
+ * @param onTimeout The code that should be executed when the prompt times out.
+ * In this case, this code is executed asynchronously.
+ * @return The current instance of the prompt for fluent builder design.
+ */
+ public SimpleChatPrompt withAsyncTimeout(int timeout, TimeUnit timeUnit, Runnable onTimeout) {
+ this.timeout = new PromptTimeout(timeout, timeUnit, onTimeout, true, false);
+ return this;
+ }
+
+ /**
+ * Defines the code that should be executed when the player enters
+ * the exit flag defined in {@link #cancelFlag(String)} to exit the
+ * prompt.
+ *
+ * @param onCancel The code to be executed when the player exits the prompt.
+ * @return The current instance of the prompt for fluent builder design.
+ */
+ public SimpleChatPrompt onCancel(Runnable onCancel) {
+ this.onCancel = onCancel;
+ return this;
+ }
+
+ /**
+ * Defines the handler that should handle the input of the player.
+ * More detailed information can be found in {@link SimplePromptResponseHandler}.
+ *
+ * @param responseHandler The class to handle the prompt's input.
+ */
+ public void handle(SimplePromptResponseHandler responseHandler) {
+ this.responseHandler = responseHandler;
+ this.chatPromptVersionTemplate.simpleChatPrompt(this);
+ }
+
+ /**
+ * @return The code handling the prompt's input string.
+ */
+ public SimplePromptResponseHandler getResponseHandler() {
+ return responseHandler;
+ }
+
+ /**
+ * @return The text a player has to type in order to exit the prompt and use the normal chat again.
+ */
+ public String getCancelFlag() {
+ return this.cancelFlag;
+ }
+
+ /**
+ * @return Detailed information about the prompt timeout if a timeout has been enabled.
+ */
+ public PromptTimeout getTimeout() {
+ return timeout;
+ }
+
+ /**
+ * @return whether echo messages are enabled.
+ */
+ public boolean isEchoEnabled() {
+ return enableEcho;
+ }
+
+ /**
+ * @return The prefix of the echo message. (might be {@code null}!)
+ */
+ public String getEchoPrefix() {
+ return echoPrefix;
+ }
+
+ /**
+ * @return The suffix of the echo message. (might be {@code null}!)
+ */
+ public String getEchoSuffix() {
+ return echoSuffix;
+ }
+
+ /**
+ * @return the code that should be executed when the player enters the exit flag defined in {@link #cancelFlag(String)}
+ */
+ public Runnable getOnCancel() {
+ return onCancel;
+ }
+
+ /**
+ * @return the player who owns the current prompt.
+ */
+ public Player getPlayer() {
+ return player;
+ }
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/prompt/sign/SignPrompt.java b/core/src/main/java/de/pxav/kelp/core/player/prompt/sign/SignPrompt.java
new file mode 100644
index 00000000..dcb3d3c3
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/prompt/sign/SignPrompt.java
@@ -0,0 +1,113 @@
+package de.pxav.kelp.core.player.prompt.sign;
+
+import com.google.common.collect.Lists;
+import de.pxav.kelp.core.player.prompt.PromptTimeout;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class represents a sign prompt.
+ *
+ * @author pxav
+ */
+public class SignPrompt {
+
+ private final SignPromptVersionTemplate signPromptVersionTemplate;
+
+ private final Player player;
+ private List initialLines = Lists.newArrayList();
+ private PromptTimeout timeout = new PromptTimeout();
+ private SignPromptResponseHandler responseHandler;
+
+ public SignPrompt(Player player, SignPromptVersionTemplate signPromptVersionTemplate) {
+ this.player = player;
+ this.signPromptVersionTemplate = signPromptVersionTemplate;
+ }
+
+ /**
+ * Define the default lines of the sign the player edits. If you
+ * only want to change the first line of the sign, you only have to
+ * set the first list element and leave the other 3 slots empty ({@code null}).
+ *
+ * @param initialLines The default text lines of the sign in chronological order.
+ * @return The current prompt instance for fluent builder design.
+ */
+ public SignPrompt initialLines(List initialLines) {
+ this.initialLines = initialLines;
+ return this;
+ }
+
+ /**
+ * Adds a synchronous timeout to the prompt, which means that the code provided in {@code onTimeout}
+ * is executed on the server's main thread.
+ *
+ * @param timeout How long it should take until the prompt times out.
+ * @param timeUnit Provide a unit for the above {@code timeout}
+ * @param onTimeout The code that should be executed when the prompt times out.
+ * If your code is thread-safe you might want to choose
+ * {@link #withAsyncTimeout(int, TimeUnit, Runnable)} instead.
+ * @return The current prompt instance for fluent builder design.
+ */
+ public SignPrompt withSyncTimeout(int timeout, TimeUnit timeUnit, Runnable onTimeout) {
+ this.timeout = new PromptTimeout(timeout, timeUnit, onTimeout, false, false);
+ return this;
+ }
+
+ /**
+ * Adds an asynchronous timeout to the prompt, which means that the code provided in {@code onTimeout}
+ * is executed on a thread other than the main thread.
+ *
+ * @param timeout How long it should take until the prompt times out.
+ * @param timeUnit Provide a unit for the above {@code timeout}
+ * @param onTimeout The code that should be executed when the prompt times out.
+ * Make sure the code is thread-safe. If this is not possible,
+ * choose {@link #withSyncTimeout(int, TimeUnit, Runnable)} instead.
+ * @return The current prompt instance for fluent builder design.
+ */
+ public SignPrompt withAsyncTimeout(int timeout, TimeUnit timeUnit, Runnable onTimeout) {
+ this.timeout = new PromptTimeout(timeout, timeUnit, onTimeout, true, false);
+ return this;
+ }
+
+ /**
+ * Provide the handler class implementing {@link SignPromptResponseHandler} interface and
+ * handling the input provided by the player in the sign editor.
+ *
+ * @param responseHandler The handler class for the player's input.
+ */
+ public void handle(SignPromptResponseHandler responseHandler) {
+ this.responseHandler = responseHandler;
+ this.signPromptVersionTemplate.openSignPrompt(this.player, this.initialLines, this.timeout, this.responseHandler);
+ }
+
+ /**
+ * Gets the current response handler for the sign prompt.
+ *
+ * @return The current reponse handler for the sign prompt.
+ */
+ public SignPromptResponseHandler getResponseHandler() {
+ return responseHandler;
+ }
+
+ /**
+ * Gets a list of the default lines displayed when the sign
+ * editor opens for the first time.
+ *
+ * @return The default text written on the sign.
+ */
+ public List getInitialLines() {
+ return initialLines;
+ }
+
+ /**
+ * Gets all information about the timeout configuration. More information about that
+ * can be found in {@link PromptTimeout}
+ *
+ * @return The current prompt timeout configuration.
+ */
+ public PromptTimeout getTimeout() {
+ return timeout;
+ }
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/prompt/sign/SignPromptResponseHandler.java b/core/src/main/java/de/pxav/kelp/core/player/prompt/sign/SignPromptResponseHandler.java
new file mode 100644
index 00000000..885d1dba
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/prompt/sign/SignPromptResponseHandler.java
@@ -0,0 +1,37 @@
+package de.pxav.kelp.core.player.prompt.sign;
+
+import de.pxav.kelp.core.player.prompt.PromptResponseType;
+
+import java.util.List;
+
+/**
+ * This interface is used to handle the input provided by a
+ * {@link SignPrompt}, similar to the {@link de.pxav.kelp.core.player.prompt.SimplePromptResponseHandler}
+ * with the only exception that this handles takes a list of strings instead of
+ * a single string, as a sign can hold up to 4 lines of text.
+ * Each item in the list represents one line of the sign, so the maximum length of the
+ * list is {@code 4} and the maximum index {@code 3}.
+ *
+ * This class should be implemented by every class handling sign input.
+ *
+ * @author pxav
+ */
+public interface SignPromptResponseHandler {
+
+ /**
+ * This method is executed when the player of the current sign
+ * prompt submits their input by pressing {@code 'Done'} in the sign
+ * editor GUI.
+ *
+ * @param input The lines the player has written on the sign.
+ * Each list item represents one line on the sign
+ * in chronological order. So the 0th item is the
+ * 1st line on the sign, the 1st list item is the
+ * 2nd line on the sign, ...
+ * @return Whether the input was valid ({@link PromptResponseType#ACCEPTED}) and the
+ * prompt may be closed now, or if the input was invalid ({@link PromptResponseType#TRY_AGAIN})
+ * and the player has to try another input.
+ */
+ PromptResponseType accept(List input);
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/player/prompt/sign/SignPromptVersionTemplate.java b/core/src/main/java/de/pxav/kelp/core/player/prompt/sign/SignPromptVersionTemplate.java
new file mode 100644
index 00000000..dc690941
--- /dev/null
+++ b/core/src/main/java/de/pxav/kelp/core/player/prompt/sign/SignPromptVersionTemplate.java
@@ -0,0 +1,38 @@
+package de.pxav.kelp.core.player.prompt.sign;
+
+import de.pxav.kelp.core.application.KelpVersionTemplate;
+import de.pxav.kelp.core.player.KelpPlayer;
+import de.pxav.kelp.core.player.prompt.PromptTimeout;
+import org.bukkit.entity.Player;
+
+import java.util.List;
+
+/**
+ * This version template is used for handling - especially opening - the sign prompt
+ * to a specific player. This process requires packets.
+ *
+ * @author pxav
+ */
+@KelpVersionTemplate
+public abstract class SignPromptVersionTemplate {
+
+ /**
+ * Opens the sign editor to a specific player. If the player clicks {@code Done} in this editor,
+ * the response handler is called automatically.
+ *
+ * @param player The player you want the prompt to show to.
+ * @param initialLines Pre-defined input. When the editor opens, there won't be an empty sign, but
+ * already some lines written to it providing help to the player on which input
+ * format is expected for example. The list follows a chronological order, so the
+ * 0th list item is the 1st line and so on. If you do not want a default input to
+ * be displayed, simply pass an empty list here.
+ * @param timeout If you want to enable a timeout, you can configure it here. Detailed information on
+ * how to do that can be found in {@link PromptTimeout}. If you want to disable timeout,
+ * pass {@code null} here.
+ * @param responseHandler The code handling the input by the player. If the player submits their input by pressing
+ * {@code Done}, the {@link SignPromptResponseHandler#accept(List)} method is executed.
+ * More information about handling the response can be found in {@link SignPromptResponseHandler}.
+ */
+ public abstract void openSignPrompt(Player player, List initialLines, PromptTimeout timeout, SignPromptResponseHandler responseHandler);
+
+}
diff --git a/core/src/main/java/de/pxav/kelp/core/scheduler/synchronize/ServerMainThread.java b/core/src/main/java/de/pxav/kelp/core/scheduler/synchronize/ServerMainThread.java
index f853fc79..bb800430 100644
--- a/core/src/main/java/de/pxav/kelp/core/scheduler/synchronize/ServerMainThread.java
+++ b/core/src/main/java/de/pxav/kelp/core/scheduler/synchronize/ServerMainThread.java
@@ -117,6 +117,13 @@ public static class RunParallel {
* @param runnable The code to execute on the main thread
*/
public static void run(Runnable runnable) {
+
+ // if we are already on the main thread, directly execute the operation instead of creating a new scheduler.
+ if (Bukkit.isPrimaryThread()) {
+ runnable.run();
+ return;
+ }
+
Bukkit.getScheduler().scheduleSyncDelayedTask(KelpPlugin.getPlugin(KelpPlugin.class), runnable);
}
diff --git a/core/src/test/java/de/pxav/kelp/core/test/common/StringUtilsTest.java b/core/src/test/java/de/pxav/kelp/core/test/common/StringUtilsTest.java
new file mode 100644
index 00000000..ca0ff71a
--- /dev/null
+++ b/core/src/test/java/de/pxav/kelp/core/test/common/StringUtilsTest.java
@@ -0,0 +1,65 @@
+package de.pxav.kelp.core.test.common;
+
+import de.pxav.kelp.core.common.StringUtils;
+import org.junit.Assert;
+import org.junit.Before;
+import org.junit.Test;
+
+import java.util.List;
+import java.util.UUID;
+
+/**
+ * This class contains a bunch of unit tests testing the
+ * functionality of {@link StringUtils}.
+ *
+ * @author pxav
+ */
+public class StringUtilsTest {
+
+ private StringUtils stringUtils;
+
+ /**
+ * Before each test, a fresh instance of {@link StringUtils}
+ * is created for neutral, unaffected test results.
+ */
+ @Before
+ public void newInstance() {
+ this.stringUtils = new StringUtils();
+ }
+
+ @Test
+ public void testColorCodeEndings() {
+ char colorCode = stringUtils.randomColorCode();
+ String text1 = UUID.randomUUID() + "§" + colorCode;
+
+ Assert.assertEquals("§" + colorCode, stringUtils.endsWithColorCode(text1));
+ }
+
+ @Test
+ public void testLastFormattingCodesOf() {
+ String lastCodes1 = stringUtils.lastFormattingCodesOf("§b§lYOUR TITLE STRING HERE");
+ Assert.assertEquals(lastCodes1, "§b§l");
+
+ String lastCodes2 = stringUtils.lastFormattingCodesOf("§b§lYOUR TITLE STRING §oHERE");
+ Assert.assertEquals(lastCodes2, "§b§l§o");
+ }
+
+ @Test
+ public void testStyleCodeExtraction() {
+ List styleCodes1 = stringUtils.extractStyleCodes("§a§lWelcome §6to the §oserver");
+ Assert.assertEquals(styleCodes1.size(), 2);
+ }
+
+ @Test
+ public void testColorCodeExtraction() {
+ List styleCodes1 = stringUtils.extractColorCodes("§a§lWelcome §6to the §oserver");
+ Assert.assertEquals(styleCodes1.size(), 2);
+ }
+
+ @Test
+ public void testFormattingCodeExtraction() {
+ List styleCodes1 = stringUtils.extractFormattingCodes("§a§lWelcome §6to the §oserver");
+ Assert.assertEquals(styleCodes1.size(), 4);
+ }
+
+}
diff --git a/core/src/test/java/de/pxav/kelp/core/test/scheduler/TimeConverterTest.java b/core/src/test/java/de/pxav/kelp/core/test/scheduler/TimeConverterTest.java
new file mode 100644
index 00000000..ef7f16eb
--- /dev/null
+++ b/core/src/test/java/de/pxav/kelp/core/test/scheduler/TimeConverterTest.java
@@ -0,0 +1,30 @@
+package de.pxav.kelp.core.test.scheduler;
+
+import de.pxav.kelp.core.scheduler.TimeConverter;
+import org.junit.Assert;
+import org.junit.Test;
+
+import java.util.concurrent.TimeUnit;
+
+/**
+ * This class tests if the {@link TimeConverter} converts
+ * ticks and real time correctly.
+ *
+ * @author pxav
+ */
+public class TimeConverterTest {
+
+ @Test
+ public void testSecondsToTicks() {
+ Assert.assertEquals(TimeConverter.secondsToTicks(1), 20);
+ Assert.assertEquals(TimeConverter.secondsToTicks(3), 60);
+ Assert.assertEquals(TimeConverter.secondsToTicks(10), 200);
+ }
+
+ @Test
+ public void testRealTimeToTicks() {
+ Assert.assertEquals(TimeConverter.getTicks(20, TimeUnit.MINUTES), 24_000);
+ Assert.assertEquals(TimeConverter.getTicks(1, TimeUnit.HOURS), 72_000);
+ }
+
+}
diff --git a/kelp-sql/pom.xml b/kelp-sql/pom.xml
new file mode 100644
index 00000000..2dac1ee2
--- /dev/null
+++ b/kelp-sql/pom.xml
@@ -0,0 +1,72 @@
+
+
+
+ parent
+ com.github.pxav.kelp
+ 0.0.5
+
+ 4.0.0
+
+ kelp-sql
+
+
+
+ com.google.inject
+ guice
+
+
+
+ com.github.pxav.kelp
+ core
+ 0.0.5
+ provided
+
+
+ org.spigotmc
+ plugin-annotations
+
+
+
+
+
+ org.spigotmc
+ spigot-api
+ 1.14-R0.1-SNAPSHOT
+ provided
+
+
+
+ io.netty
+ netty-buffer
+ 5.0.0.Alpha2
+ provided
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+ 3.8.1
+
+
+ 8
+
+
+
+ org.apache.maven.plugins
+ maven-deploy-plugin
+ 2.8.2
+
+ true
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/kelp-sql/src/main/java/de/pxav/kelp/sql/KelpSqlModule.java b/kelp-sql/src/main/java/de/pxav/kelp/sql/KelpSqlModule.java
new file mode 100644
index 00000000..9138049f
--- /dev/null
+++ b/kelp-sql/src/main/java/de/pxav/kelp/sql/KelpSqlModule.java
@@ -0,0 +1,27 @@
+package de.pxav.kelp.sql;
+
+import de.pxav.kelp.core.application.KelpApplication;
+import de.pxav.kelp.core.application.NewKelpApplication;
+
+/**
+ * A class description goes here.
+ *
+ * @author pxav
+ */
+@NewKelpApplication(applicationName = "KelpSQL",
+ version = "0.0.5",
+ authors = "pxav",
+ description = "Adds basic support for sql databases and hibernate integration")
+public class KelpSqlModule extends KelpApplication {
+
+ @Override
+ public void onEnable() {
+ super.onEnable();
+ }
+
+ @Override
+ public void onDisable() {
+ super.onDisable();
+ }
+
+}
diff --git a/pom.xml b/pom.xml
index aa37e9ef..409ceb8f 100644
--- a/pom.xml
+++ b/pom.xml
@@ -7,7 +7,7 @@
com.github.pxav.kelpparentpom
- 0.0.4
+ 0.0.5KelpA cross-version spigot framework to avoid boilerplate code and make your plugin compatible with multiple spigot versions easily
@@ -54,6 +54,7 @@
v1_8_implementationtesting-modulev_1_14_implementation
+ kelp-sql
@@ -66,6 +67,12 @@
com.google.injectguice4.0
+
+
+ com.google.guava
+ guava
+
+
@@ -196,6 +203,8 @@
falsetrue
+ ${project.artifactId}-${project.version}-shaded
+ ../builds
@@ -236,6 +245,11 @@
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 2.3.2
+
diff --git a/testing-module/pom.xml b/testing-module/pom.xml
index 07eef8aa..155ed166 100644
--- a/testing-module/pom.xml
+++ b/testing-module/pom.xml
@@ -5,7 +5,7 @@
parentcom.github.pxav.kelp
- 0.0.4
+ 0.0.54.0.0
@@ -45,7 +45,7 @@
com.github.pxav.kelpcore
- 0.0.4
+ 0.0.5provided
diff --git a/testing-module/src/main/java/de/pxav/kelp/testing/command/kbossbar/TestBossBarCommand.java b/testing-module/src/main/java/de/pxav/kelp/testing/command/kbossbar/TestBossBarCommand.java
new file mode 100644
index 00000000..de064102
--- /dev/null
+++ b/testing-module/src/main/java/de/pxav/kelp/testing/command/kbossbar/TestBossBarCommand.java
@@ -0,0 +1,155 @@
+package de.pxav.kelp.testing.command.kbossbar;
+
+import de.pxav.kelp.core.command.CreateCommand;
+import de.pxav.kelp.core.command.ExecutorType;
+import de.pxav.kelp.core.command.KelpCommand;
+import de.pxav.kelp.core.player.KelpPlayer;
+import de.pxav.kelp.core.player.bossbar.BossBarColor;
+import de.pxav.kelp.core.player.bossbar.BossBarStyle;
+import org.bukkit.ChatColor;
+
+/**
+ * A class description goes here.
+ *
+ * @author pxav
+ */
+@CreateCommand(name = "kbossbar", executorType = ExecutorType.PLAYER_ONLY)
+public class TestBossBarCommand extends KelpCommand {
+
+ @Override
+ public void onCommandRegister() {
+ noPlayerMessage("§cYou have to be a player to execute this command");
+ permission("kelp.test.bossbar");
+ allowCustomParameters(true);
+ }
+
+ @Override
+ public void onCommand(KelpPlayer player, String[] args) {
+
+ if (args.length >= 2 && args[0].equalsIgnoreCase("create")) {
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 1; i < args.length; i++) {
+ stringBuilder.append(args[i]).append(" ");
+ }
+ String message = ChatColor.translateAlternateColorCodes('&', stringBuilder.toString());
+ player.sendBossBar(message);
+ player.sendMessage("§8[§2Kelp§8] §7A boss bar with the message §a" + message + " §7has been created");
+ return;
+ }
+
+ if (args.length >= 2 && args[0].equalsIgnoreCase("createprop")) {
+ BossBarColor color = getColor(args[1]);
+ BossBarStyle style = getStyle(args[2]);
+
+ if (color == null) {
+ player.sendMessage("§8[§2Kelp§8] §cUnknown color (§7'" + args[1] + "'§c) §cuse §7/kbossbar colors §cto see all colors available.");
+ return;
+ }
+
+ if (style == null) {
+ player.sendMessage("§8[§2Kelp§8] §cUnknown style (§7'" + args[2] + "'§c) §cuse §7/kbossbar styles §cto see all styles available.");
+ return;
+ }
+
+ StringBuilder stringBuilder = new StringBuilder();
+ for (int i = 3; i < args.length; i++) {
+ stringBuilder.append(args[i]).append(" ");
+ }
+ String message = ChatColor.translateAlternateColorCodes('&', stringBuilder.toString());
+ player.sendBossBar(message, 300f, color, style);
+ player.sendMessage("§8[§2Kelp§8] §7A boss bar with the message §a" + message + " §7has been created. ");
+ return;
+ }
+
+ if (args.length == 2 && args[0].equalsIgnoreCase("progress")) {
+ double percentage;
+ try {
+ percentage = Double.parseDouble(args[1]);
+ } catch (NumberFormatException e) {
+ player.sendMessage("§8[§2Kelp§8] §cInvalid percentage values. ");
+ return;
+ }
+
+ if (percentage < 0 || percentage > 1) {
+ player.sendMessage("§8[§2Kelp§8] §cValue out of range. Please choose a value between 0 and 1.");
+ }
+
+ player.setBossBarProgress(percentage);
+ player.sendMessage("§8[§2Kelp§8] §7Progress of bossbar set. Current value §a" + (percentage * 100) + "%");
+
+ return;
+ }
+
+ if (args.length == 3 && args[0].equalsIgnoreCase("progress")) {
+ int current, max;
+ try {
+ max = Integer.parseInt(args[2]);
+ current = Integer.parseInt(args[1]);
+ } catch (NumberFormatException e) {
+ player.sendMessage("§8[§2Kelp§8] §cInvalid numerical values.");
+ return;
+ }
+
+ player.setBossBarProgress(current, max);
+ player.sendMessage("§8[§2Kelp§8] §7Progress of bossbar set. Current value §a" + current + " §7of §a" + max);
+ return;
+ }
+
+ if (args.length == 1 && args[0].equalsIgnoreCase("colors")) {
+ player.sendMessage("§8[§2Kelp§8] §7The following colors are available§8:");
+ for (BossBarColor value : BossBarColor.values()) {
+ player.sendMessage("§8[§2Kelp§8] §8- §a" + value.name());
+ }
+ return;
+ }
+
+ if (args.length == 1 && args[0].equalsIgnoreCase("styles")) {
+ player.sendMessage("§8[§2Kelp§8] §7The following styles are available§8:");
+ for (BossBarStyle value : BossBarStyle.values()) {
+ player.sendMessage("§8[§2Kelp§8] §8- §a" + value.name());
+ }
+ return;
+ }
+
+ if (args.length == 1 && args[0].equalsIgnoreCase("remove")) {
+ player.removeBossBar();
+ player.sendMessage("§8[§2Kelp§8] §7The boss bar has been §aremoved");
+ return;
+ }
+
+ this.sendHelp(player);
+
+ }
+
+ private BossBarColor getColor(String color) {
+ for (BossBarColor value : BossBarColor.values()) {
+ if (value.name().equalsIgnoreCase(color)) {
+ return value;
+ }
+ }
+ return null;
+ }
+
+ private BossBarStyle getStyle(String style) {
+ for (BossBarStyle value : BossBarStyle.values()) {
+ if (value.name().equalsIgnoreCase(style)) {
+ return value;
+ }
+ }
+ return null;
+ }
+
+ private void sendHelp(KelpPlayer player) {
+ player.sendPrefixedMessages("§8[§2Kelp§8] ",
+ "§8§m-------------------------------",
+ "§7/kbossar create ",
+ "§7/kbossar createprop