40 Commits
v1.1 ... master

Author SHA1 Message Date
3fe1be1ee0 Changes version back to SNAPSHOT
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2024-05-04 14:03:56 +02:00
1374329d8b Bumps version for release
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
This release just contains some bug-fixes for the TabCompletionHelper's getStringList.
2024-05-04 14:03:19 +02:00
5427e2a401 Fixes some problems with TabCompletionHelper.getStringList
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2024-05-04 13:59:53 +02:00
1d6a3f976e Fixes some problems with TabCompletionHelper.getStringList
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2024-05-04 13:37:49 +02:00
5bdbaa351a Bumps Spigot version
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2024-05-04 02:29:28 +02:00
334dc3852f Changes version back to snapshot
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2024-05-03 15:28:56 +02:00
265b014cd6 Bumps version for release
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2024-05-03 15:27:29 +02:00
3499dfdabc Adds POM relocations
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2024-05-03 15:22:27 +02:00
09a5692b84 Adds POM relocations
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2024-05-03 15:18:54 +02:00
a0f81879db Adds a new tab-completion helper function
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
Adds a new tab-completion helper function which is able to tab-complete comma-separated inputs.
2024-05-03 14:58:55 +02:00
75d4f84884 Fixes typos and float instead of double
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2024-04-30 02:46:19 +02:00
e9efb7cd34 Makes getting lines that are not key value pairs possible
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2024-04-29 05:56:19 +02:00
c35325c5a7 Improves particles
Some checks failed
KnarCraft/KnarLib/pipeline/head There was a failure building this commit
Allows specifying separate particle trails for each player.
Removes random particles as a class option.
Adds a particle config builder for easier particle specification.
2024-02-12 23:06:44 +01:00
085696476a Changes back to snapshot
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2024-01-25 15:23:15 +01:00
fabaf5a7ae Bumps version for release
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2024-01-25 15:22:32 +01:00
17fd61e8b9 Adds methods for getting raw messages from the string formatter
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2024-01-25 15:21:01 +01:00
7dc0315469 Makes both String[] and List<String> available for string formatting
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2024-01-11 14:04:36 +01:00
21611dd90d Changes version to snapshot
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2023-12-14 23:05:45 +01:00
22b4be2baf Updates spigot API version
Some checks failed
KnarCraft/KnarLib/pipeline/head There was a failure building this commit
2023-12-14 19:24:50 +01:00
46e05d7e32 Bumps version
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2023-11-14 13:36:46 +01:00
a89ed1fb39 Removes unnecessary processingBlock variable
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2023-09-29 00:32:29 +02:00
8028a8556b Allows creating a particle spawner without material configurations 2023-09-27 14:56:20 +02:00
6ba13a1ced Improves some descriptions, and fixes a nullability problem
All checks were successful
KnarCraft/KnarLib/pipeline/head This commit looks good
2023-09-23 19:14:12 +02:00
5c3c3045eb Adds method for parsing a material without tag support
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
KnarCraft/KnarLib/pipeline/head This commit looks good
2023-07-10 13:15:23 +02:00
223c70b28e Back to SNAPSHOT
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
2023-07-09 18:13:07 +02:00
e152f85bf4 Bumps version
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
2023-07-09 18:12:25 +02:00
5719a8ec4c Makes sure to color message text if a custom prefix is used
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
2023-07-09 18:03:22 +02:00
3248704950 Back to SNAPSHOT
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
2023-07-08 17:53:49 +02:00
0068097f55 Bumps version
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
2023-07-08 17:53:22 +02:00
46f85d37ab Allows specifying the material tag prefix
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
2023-07-08 17:49:39 +02:00
94d74e7794 Adds missing @NotNull annotations
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
2023-07-08 17:22:56 +02:00
2c8182e552 Back to SNAPSHOT
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
2023-07-08 16:52:28 +02:00
ccf3e527d9 Bumps version
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
2023-07-08 16:51:44 +02:00
914485c67b Adds a helper class for parsing materials and tags
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
2023-07-08 16:49:16 +02:00
0bd029db20 Changes back to SNAPSHOT
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
2023-07-08 15:36:56 +02:00
1e729af5e6 Bumps version
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
2023-07-08 15:35:54 +02:00
e35156bea9 Makes some particle classes final
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
2023-07-08 15:33:27 +02:00
ad5dabc712 Adds code for spawning customizable particle and trail effects
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
2023-07-08 15:20:59 +02:00
4fb4429f8b Adds more options to StringFormatter
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
Makes it possible to set the prefix before the plugin name
Makes it possible to set the suffix after the plugin name
Makes it possible to set the ColorConversion for translated messages
2023-07-08 03:01:15 +02:00
58ee5a0756 Adds SNAPSHOT to version
All checks were successful
EpicKnarvik97/KnarLib/pipeline/head This commit looks good
2023-03-24 21:45:25 +01:00
19 changed files with 1394 additions and 123 deletions

26
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>net.knarcraft</groupId>
<artifactId>KnarLib</artifactId>
<version>1.1</version>
<version>1.2.7-SNAPSHOT</version>
<packaging>jar</packaging>
<name>KnarLib</name>
@ -30,7 +30,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.2.4</version>
<version>3.3.0</version>
<executions>
<execution>
<phase>package</phase>
@ -39,6 +39,20 @@
</goals>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<relocations>
<relocation>
<pattern>org.jetbrains.annotations</pattern>
<shadedPattern>net.knarcraft.knarlib.lib.annotations</shadedPattern>
</relocation>
</relocations>
<filters>
<filter>
<artifact>org.jetbrains:annotations</artifact>
<includes>
<include>org/jetbrains/annotations/**</include>
</includes>
</filter>
</filters>
</configuration>
</execution>
</executions>
@ -81,7 +95,7 @@
<dependency>
<groupId>org.spigotmc</groupId>
<artifactId>spigot-api</artifactId>
<version>1.19.4-R0.1-SNAPSHOT</version>
<version>1.20.6-R0.1-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<dependency>
@ -90,5 +104,11 @@
<version>5.9.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.jetbrains</groupId>
<artifactId>annotations</artifactId>
<version>24.0.1</version>
<scope>compile</scope>
</dependency>
</dependencies>
</project>

View File

@ -4,6 +4,9 @@ import net.knarcraft.knarlib.property.ColorConversion;
import net.knarcraft.knarlib.util.ColorHelper;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.command.CommandSender;
import org.jetbrains.annotations.NotNull;
import java.util.List;
/**
* A formatter for formatting displayed messages
@ -15,6 +18,9 @@ public final class StringFormatter {
private final Translator translator;
private ChatColor successColor = ChatColor.GREEN;
private ChatColor errorColor = ChatColor.DARK_RED;
private String namePrefix = "[";
private String nameSuffix = "]";
private ColorConversion colorConversion = ColorConversion.NORMAL;
/**
* Instantiates a new string formatter
@ -24,22 +30,45 @@ public final class StringFormatter {
* @param pluginName <p>The name of the using plugin (used for the message prefix)</p>
* @param translator <p>The translator to use for translating messages</p>
*/
public StringFormatter(String pluginName, Translator translator) {
public StringFormatter(@NotNull String pluginName, @NotNull Translator translator) {
this.pluginName = pluginName;
this.translator = translator;
}
/**
* Sets the type of color conversion to use for translated messages
*
* @param colorConversion <p>The color conversion to use</p>
*/
public void setColorConversion(@NotNull ColorConversion colorConversion) {
this.colorConversion = colorConversion;
}
/**
* Sets the prefix used in front of the plugin name. Normally just "["
*
* @param namePrefix <p>The new name prefix</p>
*/
public void setNamePrefix(@NotNull String namePrefix) {
this.namePrefix = namePrefix;
}
/**
* Sets the suffix used after the plugin name. Normally just "]"
*
* @param nameSuffix <p>The new name suffix</p>
*/
public void setNameSuffix(@NotNull String nameSuffix) {
this.nameSuffix = nameSuffix;
}
/**
* Sets the color to prepend when using displaySuccessMessage
*
* @param color <p>The color to use</p>
*/
public void setSuccessColor(ChatColor color) {
if (color != null) {
public void setSuccessColor(@NotNull ChatColor color) {
this.successColor = color;
} else {
throw new IllegalArgumentException("Success color cannot be null");
}
}
/**
@ -47,12 +76,8 @@ public final class StringFormatter {
*
* @param color <p>The color to use</p>
*/
public void setErrorColor(ChatColor color) {
if (color != null) {
public void setErrorColor(@NotNull ChatColor color) {
this.errorColor = color;
} else {
throw new IllegalArgumentException("Error color cannot be null");
}
}
/**
@ -61,8 +86,8 @@ public final class StringFormatter {
* @param sender <p>The command sender to display the message to</p>
* @param message <p>The translatable message to display</p>
*/
public void displayNeutralMessage(CommandSender sender, TranslatableMessage message) {
sender.sendMessage(getFormattedMessage(this.translator.getTranslatedMessage(message)));
public void displayNeutralMessage(@NotNull CommandSender sender, @NotNull TranslatableMessage message) {
displayNeutralMessage(sender, this.translator.getTranslatedMessage(message));
}
/**
@ -71,7 +96,7 @@ public final class StringFormatter {
* @param sender <p>The command sender to display the message to</p>
* @param message <p>The raw message to display</p>
*/
public void displayNeutralMessage(CommandSender sender, String message) {
public void displayNeutralMessage(@NotNull CommandSender sender, @NotNull String message) {
sender.sendMessage(getFormattedMessage(message));
}
@ -81,8 +106,8 @@ public final class StringFormatter {
* @param sender <p>The command sender to display the message to</p>
* @param message <p>The translatable message to display</p>
*/
public void displaySuccessMessage(CommandSender sender, TranslatableMessage message) {
sender.sendMessage(successColor + getFormattedMessage(this.translator.getTranslatedMessage(message)));
public void displaySuccessMessage(@NotNull CommandSender sender, @NotNull TranslatableMessage message) {
displaySuccessMessage(sender, this.translator.getTranslatedMessage(message));
}
/**
@ -91,9 +116,13 @@ public final class StringFormatter {
* @param sender <p>The command sender to display the message to</p>
* @param message <p>The raw message to display</p>
*/
public void displaySuccessMessage(CommandSender sender, String message) {
public void displaySuccessMessage(@NotNull CommandSender sender, @NotNull String message) {
if (!this.namePrefix.equals("[") || !this.nameSuffix.equals("]")) {
sender.sendMessage(getFormattedMessage(successColor + message));
} else {
sender.sendMessage(successColor + getFormattedMessage(message));
}
}
/**
* Displays a message signifying an unsuccessful action
@ -101,8 +130,8 @@ public final class StringFormatter {
* @param sender <p>The command sender to display the message to</p>
* @param message <p>The translatable message to display</p>
*/
public void displayErrorMessage(CommandSender sender, TranslatableMessage message) {
sender.sendMessage(errorColor + getFormattedMessage(this.translator.getTranslatedMessage(message)));
public void displayErrorMessage(@NotNull CommandSender sender, @NotNull TranslatableMessage message) {
displayErrorMessage(sender, this.translator.getTranslatedMessage(message));
}
/**
@ -111,9 +140,34 @@ public final class StringFormatter {
* @param sender <p>The command sender to display the message to</p>
* @param message <p>The raw message to display</p>
*/
public void displayErrorMessage(CommandSender sender, String message) {
public void displayErrorMessage(@NotNull CommandSender sender, @NotNull String message) {
if (!this.namePrefix.equals("[") || !this.nameSuffix.equals("]")) {
sender.sendMessage(getFormattedMessage(errorColor + message));
} else {
sender.sendMessage(errorColor + getFormattedMessage(message));
}
}
/**
* Gets the raw, un-formatted translation of the given translatable message
*
* @param translatableMessage <p>The message to get the translation of</p>
* @return <p>The raw translation</p>
*/
public @NotNull String getUnFormattedMessage(@NotNull TranslatableMessage translatableMessage) {
return this.translator.getTranslatedMessage(translatableMessage);
}
/**
* Gets the un-formatted translation of the given translatable message, with translated color codes
*
* @param translatableMessage <p>The message to get the colored translation of</p>
* @return <p>The colored raw translation</p>
*/
public @NotNull String getUnFormattedColoredMessage(@NotNull TranslatableMessage translatableMessage) {
return ColorHelper.translateColorCodes(this.translator.getTranslatedMessage(translatableMessage),
this.colorConversion);
}
/**
* Replaces a placeholder in a translatable message
@ -123,7 +177,8 @@ public final class StringFormatter {
* @param replacement <p>The replacement value</p>
* @return <p>The input string with the placeholder replaced</p>
*/
public String replacePlaceholder(TranslatableMessage translatableMessage, String placeholder, String replacement) {
public String replacePlaceholder(@NotNull TranslatableMessage translatableMessage, @NotNull String placeholder,
@NotNull String replacement) {
return replacePlaceholder(this.translator.getTranslatedMessage(translatableMessage), placeholder, replacement);
}
@ -135,8 +190,22 @@ public final class StringFormatter {
* @param replacements <p>The replacement values</p>
* @return <p>The input string with placeholders replaced</p>
*/
public String replacePlaceholders(TranslatableMessage translatableMessage, String[] placeholders,
String[] replacements) {
public String replacePlaceholders(@NotNull TranslatableMessage translatableMessage, @NotNull String[] placeholders,
@NotNull String[] replacements) {
return replacePlaceholders(this.translator.getTranslatedMessage(translatableMessage), List.of(placeholders),
List.of(replacements));
}
/**
* Replaces placeholders in a translatable message
*
* @param translatableMessage <p>The translatable message to replace in</p>
* @param placeholders <p>The placeholders to replace</p>
* @param replacements <p>The replacement values</p>
* @return <p>The input string with placeholders replaced</p>
*/
public String replacePlaceholders(@NotNull TranslatableMessage translatableMessage, @NotNull List<String> placeholders,
@NotNull List<String> replacements) {
return replacePlaceholders(this.translator.getTranslatedMessage(translatableMessage), placeholders, replacements);
}
@ -148,7 +217,8 @@ public final class StringFormatter {
* @param replacement <p>The replacement value</p>
* @return <p>The input string with the placeholder replaced</p>
*/
public static String replacePlaceholder(String input, String placeholder, String replacement) {
public static String replacePlaceholder(@NotNull String input, @NotNull String placeholder,
@NotNull String replacement) {
return input.replace(placeholder, replacement);
}
@ -160,9 +230,23 @@ public final class StringFormatter {
* @param replacements <p>The replacement values</p>
* @return <p>The input string with placeholders replaced</p>
*/
public static String replacePlaceholders(String input, String[] placeholders, String[] replacements) {
for (int i = 0; i < Math.min(placeholders.length, replacements.length); i++) {
input = replacePlaceholder(input, placeholders[i], replacements[i]);
public static String replacePlaceholders(@NotNull String input, @NotNull String[] placeholders,
@NotNull String[] replacements) {
return replacePlaceholders(input, List.of(placeholders), List.of(replacements));
}
/**
* Replaces placeholders in a string
*
* @param input <p>The input string to replace in</p>
* @param placeholders <p>The placeholders to replace</p>
* @param replacements <p>The replacement values</p>
* @return <p>The input string with placeholders replaced</p>
*/
public static String replacePlaceholders(@NotNull String input, @NotNull List<String> placeholders,
@NotNull List<String> replacements) {
for (int i = 0; i < Math.min(placeholders.size(), replacements.size()); i++) {
input = replacePlaceholder(input, placeholders.get(i), replacements.get(i));
}
return input;
}
@ -173,13 +257,11 @@ public final class StringFormatter {
* @param message <p>The message to format</p>
* @return <p>The formatted message</p>
*/
private String getFormattedMessage(String message) {
message = ColorHelper.translateColorCodes(message, ColorConversion.NORMAL);
if (this.pluginName == null) {
return message;
} else {
return "[" + this.pluginName + "] " + ChatColor.RESET + message;
}
private String getFormattedMessage(@NotNull String message) {
message = ColorHelper.translateColorCodes(message, this.colorConversion);
String coloredPrefix = ColorHelper.translateColorCodes(namePrefix + this.pluginName +
nameSuffix, this.colorConversion);
return coloredPrefix + " " + ChatColor.RESET + message;
}
}

View File

@ -1,5 +1,8 @@
package net.knarcraft.knarlib.formatting;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
@ -25,7 +28,7 @@ public final class StringReplacer {
*
* @param replacementInput <p>The input string to replace placeholders for</p>
*/
public StringReplacer(String replacementInput) {
public StringReplacer(@NotNull String replacementInput) {
this.replacementInput = replacementInput;
}
@ -35,7 +38,7 @@ public final class StringReplacer {
* @param placeholder <p>The placeholder to replace</p>
* @param value <p>The replacement value</p>
*/
public void add(String placeholder, String value) {
public void add(@NotNull String placeholder, @NotNull String value) {
this.replacements.put(placeholder, value);
}
@ -44,7 +47,7 @@ public final class StringReplacer {
*
* @param placeholder <p>The placeholder to remove</p>
*/
public void remove(String placeholder) {
public void remove(@NotNull String placeholder) {
this.replacements.remove(placeholder);
}
@ -55,7 +58,7 @@ public final class StringReplacer {
*
* @return <p>The string with placeholders replaced</p>
*/
public String replace() {
public @NotNull String replace() {
if (replacementInput == null) {
throw new IllegalStateException("This method cannot be run without a defined string");
}
@ -68,9 +71,9 @@ public final class StringReplacer {
* @param input <p>The string to replace placeholders in</p>
* @return <p>The string with placeholders replaced</p>
*/
public String replace(String input) {
return StringFormatter.replacePlaceholders(input, replacements.keySet().toArray(new String[0]),
replacements.values().toArray(new String[0]));
public @NotNull String replace(@NotNull String input) {
return StringFormatter.replacePlaceholders(input, new ArrayList<>(replacements.keySet()),
new ArrayList<>(replacements.values()));
}
}

View File

@ -1,5 +1,7 @@
package net.knarcraft.knarlib.formatting;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
@ -28,7 +30,7 @@ public final class TimeFormatter {
* @param duration <p>The duration, in seconds, to display</p>
* @return <p>The string used for displaying this sign's duration</p>
*/
public static String getDurationString(Translator translator, long duration) {
public static String getDurationString(@NotNull Translator translator, long duration) {
//Initialize time units once, and only if this method is actually used
if (sortedUnits == null) {
initializeUnits(translator);
@ -76,8 +78,8 @@ public final class TimeFormatter {
* @param castToInt <p>Whether to cast the duration to an int</p>
* @return <p>The formatted duration string</p>
*/
private static String formatDurationString(double duration, Translator translator,
TranslatableMessage translatableMessage, boolean castToInt) {
private static String formatDurationString(double duration, @NotNull Translator translator,
@NotNull TranslatableMessage translatableMessage, boolean castToInt) {
String durationFormat = translator.getTranslatedMessage(TranslatableTimeUnit.DURATION_FORMAT);
durationFormat = replacePlaceholder(durationFormat, "{unit}",
translator.getTranslatedMessage(translatableMessage));
@ -88,7 +90,7 @@ public final class TimeFormatter {
/**
* Initializes the mapping of available time units for formatting permission sign duration
*/
private static void initializeUnits(Translator translator) {
private static void initializeUnits(@NotNull Translator translator) {
//Register each time unit with a registered singular and plural translation
timeUnits = new HashMap<>();
registerTimeUnit(translator, 315360000, TranslatableTimeUnit.UNIT_DECADE, TranslatableTimeUnit.UNIT_DECADES);
@ -102,7 +104,7 @@ public final class TimeFormatter {
TranslatableTimeUnit.UNIT_SECONDS);
//As now and seconds are the base units, those must be registered to use getDurationString
if (!registeredSeconds || !hasTranslation(translator, TranslatableTimeUnit.UNIT_NOW)) {
if (!registeredSeconds || !translator.hasTranslation(TranslatableTimeUnit.UNIT_NOW)) {
throw new IllegalStateException("A translation is missing for UNIT_SECOND, UNIT_SECONDS or UNIT_NOW");
}
@ -121,11 +123,12 @@ public final class TimeFormatter {
* @param plural <p>The translatable time unit to use for the plural form</p>
* @return <p>True if the time unit was registered. False if a translation is missing</p>
*/
private static boolean registerTimeUnit(Translator translator, double seconds, TranslatableTimeUnit singular,
TranslatableTimeUnit plural) {
private static boolean registerTimeUnit(@NotNull Translator translator, double seconds,
@NotNull TranslatableTimeUnit singular,
@NotNull TranslatableTimeUnit plural) {
String singularTranslation = translator.getTranslatedMessage(singular);
String pluralTranslation = translator.getTranslatedMessage(plural);
if (hasTranslation(translator, singular) && hasTranslation(translator, plural)) {
if (translator.hasTranslation(singular) && translator.hasTranslation(plural)) {
timeUnits.put(seconds, new TranslatableTimeUnit[]{singular, plural});
return true;
} else {
@ -133,16 +136,4 @@ public final class TimeFormatter {
}
}
/**
* Checks whether the given translatable message has a registered translation
*
* @param translator <p>The translator to check</p>
* @param translatableMessage <p>The translatable message to check</p>
* @return <p>True if a non-empty translation has been registered</p>
*/
private static boolean hasTranslation(Translator translator, TranslatableMessage translatableMessage) {
String translation = translator.getTranslatedMessage(translatableMessage);
return translation != null && !translation.isBlank();
}
}

View File

@ -1,5 +1,7 @@
package net.knarcraft.knarlib.formatting;
import org.jetbrains.annotations.NotNull;
/**
* A message which can be translated
*/
@ -13,7 +15,7 @@ public interface TranslatableMessage {
*
* @return <p>The name of this translatable message</p>
*/
String name();
@NotNull String name();
/**
* Gets all translatable messages
@ -22,6 +24,6 @@ public interface TranslatableMessage {
*
* @return <p>All translatable messages</p>
*/
TranslatableMessage[] getAllMessages();
@NotNull TranslatableMessage[] getAllMessages();
}

View File

@ -1,5 +1,7 @@
package net.knarcraft.knarlib.formatting;
import org.jetbrains.annotations.NotNull;
/**
* An enum containing all translatable time units
*
@ -10,7 +12,7 @@ package net.knarcraft.knarlib.formatting;
public enum TranslatableTimeUnit implements TranslatableMessage {
/**
* The format for displaying the exact duration of a blacksmith's cool-down or delay
* The format for displaying the duration of something
*/
DURATION_FORMAT,
@ -101,7 +103,7 @@ public enum TranslatableTimeUnit implements TranslatableMessage {
;
@Override
public TranslatableMessage[] getAllMessages() {
public @NotNull TranslatableMessage[] getAllMessages() {
return TranslatableTimeUnit.values();
}

View File

@ -5,12 +5,12 @@ import net.knarcraft.knarlib.util.ColorHelper;
import net.knarcraft.knarlib.util.FileHelper;
import org.bukkit.Bukkit;
import org.bukkit.configuration.file.YamlConfiguration;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
@ -41,7 +41,7 @@ public final class Translator {
*
* @param translatableMessage <p>A translatable message in the category to register</p>
*/
public void registerMessageCategory(TranslatableMessage translatableMessage) {
public void registerMessageCategory(@NotNull TranslatableMessage translatableMessage) {
messageCategories.add(translatableMessage);
}
@ -54,12 +54,8 @@ public final class Translator {
*
* @param colorConversion <p>The color conversion to be used</p>
*/
public void setColorConversion(ColorConversion colorConversion) {
if (colorConversion != null) {
public void setColorConversion(@NotNull ColorConversion colorConversion) {
this.colorConversion = colorConversion;
} else {
throw new IllegalArgumentException("Color conversion cannot be null");
}
}
/**
@ -69,7 +65,8 @@ public final class Translator {
* @param fallbackLanguage <p>The default language to fall back on in the case of missing translations</p>
* @param selectedLanguage <p>The currently selected language</p>
*/
public void loadLanguages(File dataFolder, String fallbackLanguage, String selectedLanguage) {
public void loadLanguages(@NotNull File dataFolder, @NotNull String fallbackLanguage,
@NotNull String selectedLanguage) {
backupTranslatedMessages = loadTranslatedMessages(fallbackLanguage);
translatedMessages = loadCustomTranslatedMessages(dataFolder, selectedLanguage);
if (translatedMessages == null) {
@ -77,13 +74,24 @@ public final class Translator {
}
}
/**
* Checks whether the given translatable message has a translation
*
* @param translatableMessage <p>The translatable message to check</p>
* @return <p>True if the message has a translation</p>
*/
public boolean hasTranslation(@NotNull TranslatableMessage translatableMessage) {
return backupTranslatedMessages.containsKey(translatableMessage) &&
!backupTranslatedMessages.get(translatableMessage).isBlank();
}
/**
* Gets a translated version of the given translatable message
*
* @param translatableMessage <p>The message to translate</p>
* @return <p>The translated message</p>
*/
public String getTranslatedMessage(TranslatableMessage translatableMessage) {
public @NotNull String getTranslatedMessage(@NotNull TranslatableMessage translatableMessage) {
if (translatedMessages == null) {
return "Translated strings not loaded";
}
@ -104,7 +112,7 @@ public final class Translator {
* @param language <p>The language chosen by the user</p>
* @return <p>A mapping of all strings for the given language</p>
*/
public Map<TranslatableMessage, String> loadTranslatedMessages(String language) {
public @Nullable Map<TranslatableMessage, String> loadTranslatedMessages(@NotNull String language) {
try {
BufferedReader reader = FileHelper.getBufferedReaderForInternalFile("/strings.yml");
return loadTranslatableMessages(language, reader);
@ -121,10 +129,8 @@ public final class Translator {
* @param language <p>The selected language</p>
* @return <p>The loaded translated strings, or null if no custom language file exists</p>
*/
public Map<TranslatableMessage, String> loadCustomTranslatedMessages(File dataFolder, String language) {
if (dataFolder == null) {
return null;
}
public @Nullable Map<TranslatableMessage, String> loadCustomTranslatedMessages(@NotNull File dataFolder,
@NotNull String language) {
File strings = new File(dataFolder, "strings.yml");
if (!strings.exists()) {
Bukkit.getLogger().log(Level.FINEST, "Strings file not found");
@ -133,9 +139,9 @@ public final class Translator {
try {
Bukkit.getLogger().log(Level.INFO, "Loading custom strings...");
return loadTranslatableMessages(language, new BufferedReader(new InputStreamReader(
new FileInputStream(strings))));
} catch (FileNotFoundException e) {
return loadTranslatableMessages(language,
FileHelper.getBufferedReaderFromString(strings.toPath().toString()));
} catch (FileNotFoundException exception) {
Bukkit.getLogger().log(Level.WARNING, "Unable to load custom messages");
return null;
}
@ -148,7 +154,8 @@ public final class Translator {
* @param reader <p>The buffered reader to read from</p>
* @return <p>The loaded translated strings</p>
*/
private Map<TranslatableMessage, String> loadTranslatableMessages(String language, BufferedReader reader) {
private @NotNull Map<TranslatableMessage, String> loadTranslatableMessages(@NotNull String language,
@NotNull BufferedReader reader) {
Map<TranslatableMessage, String> translatedMessages = new HashMap<>();
YamlConfiguration configuration = YamlConfiguration.loadConfiguration(reader);

View File

@ -0,0 +1,168 @@
package net.knarcraft.knarlib.particle;
import org.bukkit.Particle;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
/**
* A configuration describing a particle
*/
@SuppressWarnings("unused")
public final class ParticleConfig {
private final ParticleMode particleMode;
private final Particle particleType;
private final int particleAmount;
private final double particleDensity;
private final double heightOffset;
private final double offsetX;
private final double offsetY;
private final double offsetZ;
private final double extra;
/**
* Instantiates a new particle config
*
* @param particlesSection <p>The configuration section containing the particle's settings</p>
*/
public ParticleConfig(@NotNull ConfigurationSection particlesSection) {
@NotNull Particle particleType;
try {
particleType = Particle.valueOf(particlesSection.getString("type"));
} catch (IllegalArgumentException | NullPointerException exception) {
particleType = Particle.ASH;
}
this.particleType = particleType;
this.particleAmount = particlesSection.getInt("amount", 30);
this.offsetX = particlesSection.getDouble("offsetX", 0.5);
this.offsetY = particlesSection.getDouble("offsetY", 1);
this.offsetZ = particlesSection.getDouble("offsetZ", 0.5);
this.heightOffset = particlesSection.getDouble("heightOffset", 0.5);
this.extra = particlesSection.getDouble("extra", 0);
ParticleMode particleMode;
try {
particleMode = ParticleMode.valueOf(particlesSection.getString("mode"));
} catch (IllegalArgumentException | NullPointerException exception) {
particleMode = ParticleMode.SINGLE;
}
this.particleMode = particleMode;
// Make sure particle density is between 1 (inclusive) and 0 (exclusive)
double particleDensity = particlesSection.getDouble("particleDensity", 0.1);
if (particleDensity <= 0) {
particleDensity = 0.1;
} else if (particleDensity > 360) {
particleDensity = 360;
}
this.particleDensity = particleDensity;
}
/**
* Instantiates a new particle config
*
* @param particleMode <p>The mode to use when spawning the particle</p>
* @param particleType <p>The type of particle to spawn</p>
* @param particleAmount <p>The amount of particles to spawn at once</p>
* @param particleDensity <p>The density of the particles, if spawning a shape</p>
* @param heightOffset <p>The offset above the block to spawn the particle</p>
* @param offsetX <p>The x-offset/spread of the spawned particles</p>
* @param offsetY <p>The y-offset/spread of the spawned particles</p>
* @param offsetZ <p>The z-offset/spread of the spawned particles</p>
* @param extra <p>Extra data for the particle. Usage depends on the particle type.</p>
*/
public ParticleConfig(@NotNull ParticleMode particleMode, @NotNull Particle particleType, int particleAmount,
double particleDensity, double heightOffset, double offsetX, double offsetY, double offsetZ,
double extra) {
this.particleMode = particleMode;
this.particleType = particleType;
this.particleAmount = particleAmount;
this.particleDensity = particleDensity;
this.heightOffset = heightOffset;
this.offsetX = offsetX;
this.offsetY = offsetY;
this.offsetZ = offsetZ;
this.extra = extra;
}
/**
* The mode to use when drawing/spawning the particle(s)
*
* @return <p>The particle mode</p>
*/
public @NotNull ParticleMode getParticleMode() {
return particleMode;
}
/**
* The type of particle to spawn
*
* @return <p>The particle type</p>
*/
public @NotNull Particle getParticleType() {
return particleType;
}
/**
* The amount of particles to spawn
*
* @return <p>The amount of particles</p>
*/
public int getParticleAmount() {
return particleAmount;
}
/**
* The density of particles to use in shapes closer to 0 causes larger density
*
* @return <p>The particle density</p>
*/
public double getParticleDensity() {
return particleDensity;
}
/**
* The number of blocks above the block the particle(s) should spawn
*
* @return <p>The y-offset</p>
*/
public double getHeightOffset() {
return heightOffset;
}
/**
* The offset/spread of particles in the x-direction
*
* @return <p>The x-offset</p>
*/
public double getOffsetX() {
return offsetX;
}
/**
* The offset/spread of particles in the y-direction
*
* @return <p>The y-offset</p>
*/
public double getOffsetY() {
return offsetY;
}
/**
* The offset/spread of particles in the z-direction
*
* @return <p>The z-offset</p>
*/
public double getOffsetZ() {
return offsetZ;
}
/**
* The extra value to set for the particle. Exactly what it does depends on the particle.
*
* @return <p>The particle's extra value</p>
*/
public double getExtra() {
return extra;
}
}

View File

@ -0,0 +1,148 @@
package net.knarcraft.knarlib.particle;
import org.bukkit.Particle;
import org.jetbrains.annotations.NotNull;
/**
* A builder for creating particle configurations
*
* <p>The default behavior will spawn a single particle with no offset or extra data, and density 1.</p>
*/
@SuppressWarnings("unused")
public class ParticleConfigBuilder {
private final Particle particleType;
private ParticleMode particleMode = ParticleMode.SINGLE;
private int particleAmount = 1;
private double particleDensity = 1;
private double heightOffset = 0;
private double offsetX = 0;
private double offsetY = 0;
private double offsetZ = 0;
private double extra = 0;
/**
* Instantiates a new particle config builder
*
* @param particleType <p>The type of particle to spawn</p>
*/
public ParticleConfigBuilder(@NotNull Particle particleType) {
this.particleType = particleType;
}
/**
* Sets the mode to use when spawning the particles
*
* @param particleMode <p>The mode to use</p>
* @return <p>This particle configuration builder</p>
*/
public @NotNull ParticleConfigBuilder setMode(@NotNull ParticleMode particleMode) {
this.particleMode = particleMode;
return this;
}
/**
* Sets the amount of particles to spawn
*
* @param particleAmount <p>The number/amount of particles to spawn</p>
* @return <p>This particle configuration builder</p>
*/
public @NotNull ParticleConfigBuilder setAmount(int particleAmount) {
this.particleAmount = particleAmount;
return this;
}
/**
* Sets the density of the particles to spawn
*
* @param particleDensity <p>The new density</p>
* @return <p>This particle configuration builder</p>
*/
public @NotNull ParticleConfigBuilder setDensity(double particleDensity) {
this.particleDensity = particleDensity;
return this;
}
/**
* Sets the x,y,z offsets of the spawned particles
*
* <p>These values control the spread of the spawned particles</p>
*
* @param offsetX <p>The offset in the x-direction</p>
* @param offsetY <p>The offset in the y-direction</p>
* @param offsetZ <p>The offset in the z-direction</p>
* @return <p>This particle configuration builder</p>
*/
public @NotNull ParticleConfigBuilder setOffsets(double offsetX, double offsetY, double offsetZ) {
this.offsetX = offsetX;
this.offsetY = offsetY;
this.offsetZ = offsetZ;
return this;
}
/**
* Sets the x offset of the spawned particles
*
* @param offsetX <p>The offset in the x-direction</p>
* @return <p>This particle configuration builder</p>
*/
public @NotNull ParticleConfigBuilder setXOffset(double offsetX) {
this.offsetX = offsetX;
return this;
}
/**
* Sets the y offset of the spawned particles
*
* @param offsetY <p>The offset in the y-direction</p>
* @return <p>This particle configuration builder</p>
*/
public @NotNull ParticleConfigBuilder setYOffset(double offsetY) {
this.offsetY = offsetY;
return this;
}
/**
* Sets the z offset of the spawned particles
*
* @param offsetZ <p>The offset in the z-direction</p>
* @return <p>This particle configuration builder</p>
*/
public @NotNull ParticleConfigBuilder setZOffset(double offsetZ) {
this.offsetZ = offsetZ;
return this;
}
/**
* Sets the extra data for the spawned particles
*
* @param extra <p>The extra data to set</p>
* @return <p>This particle configuration builder</p>
*/
public @NotNull ParticleConfigBuilder setExtra(double extra) {
this.extra = extra;
return this;
}
/**
* Sets the height offset for the spawned particles
*
* @param heightOffset <p>The height offset to use</p>
* @return <p>This particle configuration builder</p>
*/
public @NotNull ParticleConfigBuilder setHeightOffset(double heightOffset) {
this.heightOffset = heightOffset;
return this;
}
/**
* Builds the particle configuration
*
* @return <p>The built particle configuration</p>
*/
public @NotNull ParticleConfig build() {
return new ParticleConfig(particleMode, particleType, particleAmount, particleDensity, heightOffset, offsetX,
offsetY, offsetZ, extra);
}
}

View File

@ -0,0 +1,38 @@
package net.knarcraft.knarlib.particle;
/**
* The mode used for spawning one or more particle(s)
*/
public enum ParticleMode {
/**
* Spawns the set amount of particles on a single point in the world
*/
SINGLE,
/**
* Spawns the set amount of particles in a square around the block
*/
SQUARE,
/**
* Spawns the set amount of particles in a circle around the block
*/
CIRCLE,
/**
* Spawns the set amount of particles in a pyramid centered on the block
*/
PYRAMID,
/**
* Spawns the set amount of particles in a sphere centered on the block
*/
SPHERE,
/**
* Spawns the set amount of particles in a cube centered on the block
*/
CUBE,
}

View File

@ -0,0 +1,142 @@
package net.knarcraft.knarlib.particle;
import net.knarcraft.knarlib.util.ParticleHelper;
import org.bukkit.Location;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Collection;
import java.util.Map;
import java.util.UUID;
import java.util.function.Supplier;
/**
* A runnable tasks that spawns particles at the given blocks
*/
@SuppressWarnings("unused")
public final class ParticleSpawner implements Runnable {
private final ParticleConfig particleConfig;
private final Map<Material, ParticleConfig> materialConfigs;
private final Collection<Block> blocks;
private final Supplier<Collection<Block>> blockSupplier;
private final UUID storedCalculationId;
/**
* Instantiates a new particle spawner
*
* @param particleConfig <p>The configuration for the particle to spawn</p>
* @param materialConfigs <p>Extra particle configurations for specific materials</p>
* @param blocks <p>The blocks to spawn particles on</p>
*/
public ParticleSpawner(@NotNull ParticleConfig particleConfig,
@Nullable Map<Material, ParticleConfig> materialConfigs, @NotNull Collection<Block> blocks) {
this.particleConfig = particleConfig;
this.materialConfigs = materialConfigs;
this.blocks = blocks;
this.blockSupplier = null;
this.storedCalculationId = UUID.randomUUID();
}
/**
* Instantiates a new particle spawner
*
* @param particleConfig <p>The configuration for the particle to spawn</p>
* @param materialConfigs <p>Extra particle configurations for specific materials</p>
* @param blockSupplier <p>The supplier supplying the blocks to spawn particles on</p>
*/
public ParticleSpawner(@NotNull ParticleConfig particleConfig,
@Nullable Map<Material, ParticleConfig> materialConfigs,
@NotNull Supplier<Collection<Block>> blockSupplier) {
this.particleConfig = particleConfig;
this.materialConfigs = materialConfigs;
this.blocks = null;
this.blockSupplier = blockSupplier;
this.storedCalculationId = UUID.randomUUID();
}
/**
* Gets the id used for stored calculations
*
* <p>You should run ParticleHelper.clearStoredCalculations(id) with this id when discarding this particle spawner
* to prevent a memory leak.</p>
*
* @return <p>The id used for stored calculations</p>
*/
public @NotNull UUID getStoredCalculationId() {
return this.storedCalculationId;
}
@Override
public void run() {
// Use the static collection of blocks, or a dynamically changing supplier
Collection<Block> blocksToSpawnOn;
if (blocks != null) {
blocksToSpawnOn = blocks;
} else if (blockSupplier != null) {
blocksToSpawnOn = blockSupplier.get();
} else {
throw new RuntimeException("There is a bug in the plugin code. Please contact the developer!");
}
for (Block block : blocksToSpawnOn) {
// Ignore blocks in unloaded chunks
if (!block.getChunk().isLoaded()) {
continue;
}
Location location = block.getLocation().clone();
World world = location.getWorld();
if (world == null) {
continue;
}
spawnParticleForBlock(block, world, location);
}
}
/**
* Spawns the defined particle for the given block
*
* @param block <p>The block to spawn a particle effect for</p>
* @param world <p>The world the block belongs to</p>
* @param location <p>A clone of the block's location</p>
*/
private void spawnParticleForBlock(@NotNull Block block, @NotNull World world, @NotNull Location location) {
double blockHeight = ParticleHelper.getBlockHeight(block);
ParticleConfig activeConfig = getParticleConfig(block.getType());
switch (activeConfig.getParticleMode()) {
case SINGLE -> ParticleHelper.spawnParticle(world, location.clone().add(0.5,
activeConfig.getHeightOffset(), 0.5), activeConfig, blockHeight);
case SQUARE -> ParticleHelper.drawSquare(world, location, activeConfig, blockHeight);
case CIRCLE -> ParticleHelper.drawCircle(world, location, activeConfig, blockHeight, storedCalculationId);
case PYRAMID -> ParticleHelper.drawPyramid(world, location, activeConfig, blockHeight, storedCalculationId);
case SPHERE -> ParticleHelper.drawSphere(world, location, activeConfig, blockHeight, storedCalculationId);
case CUBE -> ParticleHelper.drawCube(world, location, activeConfig, blockHeight);
}
}
/**
* Gets the particle config to use for the current block's material
*
* @param material <p>The material of the block to get a configuration for</p>
* @return <p>The particle config to use</p>
*/
private @NotNull ParticleConfig getParticleConfig(@NotNull Material material) {
if (this.materialConfigs == null) {
return this.particleConfig;
}
ParticleConfig materialConfig = this.materialConfigs.get(material);
if (materialConfig != null) {
return materialConfig;
}
return this.particleConfig;
}
}

View File

@ -0,0 +1,123 @@
package net.knarcraft.knarlib.particle;
import net.knarcraft.knarlib.util.ParticleHelper;
import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.Particle;
import org.bukkit.World;
import org.bukkit.entity.Player;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Set;
import java.util.UUID;
/**
* A task for spawning trails behind players
*/
@SuppressWarnings("unused")
public final class ParticleTrailSpawner implements Runnable {
private final Set<UUID> playersWithTrails = new HashSet<>();
private final Map<UUID, Particle> playerParticles = new HashMap<>();
private final Random random = new Random();
private final Particle particle;
private final List<Particle> randomTrailTypes;
/**
* Instantiates a new particle trail spawner
*
* @param particle <p>The type of particle used for the trail</p>
* @param randomTrailTypes <p>The types of particles to use for random trails</p>
*/
public ParticleTrailSpawner(@NotNull Particle particle, @NotNull List<Particle> randomTrailTypes) {
this.particle = particle;
this.randomTrailTypes = randomTrailTypes;
}
@Override
public void run() {
Set<UUID> offlinePlayers = new HashSet<>();
for (UUID playerId : playersWithTrails) {
// Clear offline players from the list
Player player = Bukkit.getPlayer(playerId);
if (player == null) {
offlinePlayers.add(playerId);
continue;
}
// Get the world and location to spawn the particle at
Location playerLocation = player.getLocation();
World playerWorld = playerLocation.getWorld();
if (playerWorld == null) {
continue;
}
// Decide on which type of particle to spawn
Particle spawnParticle = playerParticles.get(playerId);
if (spawnParticle == null) {
spawnParticle = this.particle;
}
// Spawn a trail particle
ParticleConfig particleConfig = new ParticleConfigBuilder(spawnParticle).build();
ParticleHelper.spawnParticle(playerWorld, playerLocation, particleConfig, 0);
}
playersWithTrails.removeAll(offlinePlayers);
}
/**
* Removes the trail behind the player with the given id
*
* @param playerId <p>The id of the player to remove the trail for</p>
*/
public void removeTrail(@NotNull UUID playerId) {
this.playersWithTrails.remove(playerId);
this.playerParticles.remove(playerId);
}
/**
* Adds a random trail behind the player with the given id
*
* @param playerId <p>The id of the player to add the trail to</p>
*/
public void startRandomTrail(@NotNull UUID playerId) {
this.playerParticles.put(playerId, randomParticle());
this.playersWithTrails.add(playerId);
}
/**
* Adds a trail behind the player with the given id
*
* @param playerId <p>The id of the player to add the trail to</p>
* @param particle <p>The particle to spawn, or null for the particle specified in the constructor</p>
*/
public void startTrail(@NotNull UUID playerId, @Nullable Particle particle) {
this.playerParticles.put(playerId, particle);
this.playersWithTrails.add(playerId);
}
/**
* Gets a random particle from random the specified trail types
*
* @return <p>A random particle, or EGG_CRACK if no random trail types have been specified</p>
*/
private @NotNull Particle randomParticle() {
if (this.randomTrailTypes.isEmpty()) {
return Particle.EGG_CRACK;
}
Particle spawnParticle = null;
while (spawnParticle == null || spawnParticle.getDataType() != Void.class) {
spawnParticle = randomTrailTypes.get(random.nextInt(randomTrailTypes.size()));
}
return spawnParticle;
}
}

View File

@ -3,6 +3,7 @@ package net.knarcraft.knarlib.util;
import net.knarcraft.knarlib.property.ColorConversion;
import net.md_5.bungee.api.ChatColor;
import org.bukkit.Color;
import org.jetbrains.annotations.NotNull;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -25,7 +26,7 @@ public final class ColorHelper {
* @param color <p>The color to invert</p>
* @return <p>The inverted color</p>
*/
public static Color invert(Color color) {
public static @NotNull Color invert(@NotNull Color color) {
return color.setRed(255 - color.getRed()).setGreen(255 - color.getGreen()).setBlue(255 - color.getBlue());
}
@ -35,7 +36,7 @@ public final class ColorHelper {
* @param color <p>The color to convert into a chat color</p>
* @return <p>The resulting chat color</p>
*/
public static ChatColor fromColor(Color color) {
public static @NotNull ChatColor fromColor(@NotNull Color color) {
return ChatColor.of(String.format("#%02X%02X%02X", color.getRed(), color.getGreen(), color.getBlue()));
}
@ -58,7 +59,7 @@ public final class ColorHelper {
* @param colorConversion <p>The type of color conversion to apply before stripping</p>
* @return <p>The message without color codes</p>
*/
public static String stripColorCodes(String message, ColorConversion colorConversion) {
public static @NotNull String stripColorCodes(@NotNull String message, @NotNull ColorConversion colorConversion) {
return ChatColor.stripColor(translateColorCodes(message, colorConversion));
}
@ -69,7 +70,7 @@ public final class ColorHelper {
* @param colorConversion <p>The type of color conversion to apply</p>
* @return <p>The string with color codes applied</p>
*/
public static String translateColorCodes(String message, ColorConversion colorConversion) {
public static @NotNull String translateColorCodes(@NotNull String message, @NotNull ColorConversion colorConversion) {
return switch (colorConversion) {
case NONE -> message;
case NORMAL -> ChatColor.translateAlternateColorCodes('&', message);
@ -85,7 +86,7 @@ public final class ColorHelper {
* @param onlyRGB <p>Whether to only convert RGB (hexadecimal) color codes. If false, &[a-f0-9] will be converted as well.</p>
* @return <p>The message with color codes translated</p>
*/
private static String translateAllColorCodes(String message, boolean onlyRGB) {
private static @NotNull String translateAllColorCodes(@NotNull String message, boolean onlyRGB) {
if (!onlyRGB) {
message = ChatColor.translateAlternateColorCodes('&', message);
}

View File

@ -1,6 +1,8 @@
package net.knarcraft.knarlib.util;
import net.knarcraft.knarlib.property.ColorConversion;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.BufferedWriter;
@ -12,7 +14,9 @@ import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
@ -33,7 +37,7 @@ public final class FileHelper {
* @return <p>A buffered read for reading the file</p>
* @throws FileNotFoundException <p>If unable to get an input stream for the given file</p>
*/
public static BufferedReader getBufferedReaderForInternalFile(String file) throws FileNotFoundException {
public static @NotNull BufferedReader getBufferedReaderForInternalFile(@NotNull String file) throws FileNotFoundException {
InputStream inputStream = getInputStreamForInternalFile(file);
if (inputStream == null) {
throw new FileNotFoundException("Unable to read the given file");
@ -50,7 +54,7 @@ public final class FileHelper {
* @param file <p>The file to read</p>
* @return <p>An input stream for the file</p>
*/
public static InputStream getInputStreamForInternalFile(String file) {
public static @Nullable InputStream getInputStreamForInternalFile(@NotNull String file) {
return FileHelper.class.getResourceAsStream(file);
}
@ -61,7 +65,7 @@ public final class FileHelper {
* @return <p>A buffered reader reading the file</p>
* @throws FileNotFoundException <p>If the given file does not exist</p>
*/
public static BufferedReader getBufferedReaderFromString(String file) throws FileNotFoundException {
public static @NotNull BufferedReader getBufferedReaderFromString(@NotNull String file) throws FileNotFoundException {
FileInputStream fileInputStream = new FileInputStream(file);
return getBufferedReaderFromInputStream(fileInputStream);
}
@ -72,19 +76,19 @@ public final class FileHelper {
* @param inputStream <p>The input stream to read</p>
* @return <p>A buffered reader reading the input stream</p>
*/
public static BufferedReader getBufferedReaderFromInputStream(InputStream inputStream) {
public static @NotNull BufferedReader getBufferedReaderFromInputStream(@NotNull InputStream inputStream) {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
return new BufferedReader(inputStreamReader);
}
/**
* Gets a buffered writer from a string pointing to a file
* Gets a buffered writer from a buffered reader
*
* @param file <p>The file to write to</p>
* @return <p>A buffered writer writing to the file</p>
* @throws FileNotFoundException <p>If the file does not exist</p>
*/
public static BufferedWriter getBufferedWriterFromString(String file) throws FileNotFoundException {
public static @NotNull BufferedWriter getBufferedWriterFromString(@NotNull String file) throws FileNotFoundException {
FileOutputStream fileOutputStream = new FileOutputStream(file);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(fileOutputStream, StandardCharsets.UTF_8);
return new BufferedWriter(outputStreamWriter);
@ -99,9 +103,38 @@ public final class FileHelper {
* @return <p>A map containing the read pairs</p>
* @throws IOException <p>If unable to read from the stream</p>
*/
public static Map<String, String> readKeyValuePairs(BufferedReader bufferedReader, String separator,
ColorConversion colorConversion) throws IOException {
@NotNull
public static Map<String, String> readKeyValuePairs(@NotNull BufferedReader bufferedReader,
@NotNull String separator,
@NotNull ColorConversion colorConversion) throws IOException {
Map<String, String> readPairs = new HashMap<>();
List<String> lines = readLines(bufferedReader);
for (String line : lines) {
int separatorIndex = line.indexOf(separator);
if (separatorIndex == -1) {
continue;
}
//Read the line
String key = line.substring(0, separatorIndex);
String value = ColorHelper.translateColorCodes(line.substring(separatorIndex + 1), colorConversion);
readPairs.put(key, value);
}
return readPairs;
}
/**
* Reads all lines from a buffered reader
*
* @param bufferedReader <p>The buffered reader to read</p>
* @return <p>A list of the read lines</p>
* @throws IOException <p>If unable to read from the stream</p>
*/
@NotNull
public static List<String> readLines(@NotNull BufferedReader bufferedReader) throws IOException {
List<String> readLines = new ArrayList<>();
String line = bufferedReader.readLine();
boolean firstLine = true;
@ -112,22 +145,17 @@ public final class FileHelper {
firstLine = false;
}
//Split at first separator
int separatorIndex = line.indexOf(separator);
if (separatorIndex == -1) {
if (line.isEmpty()) {
line = bufferedReader.readLine();
continue;
}
//Read the line
String key = line.substring(0, separatorIndex);
String value = ColorHelper.translateColorCodes(line.substring(separatorIndex + 1), colorConversion);
readPairs.put(key, value);
readLines.add(line);
line = bufferedReader.readLine();
}
bufferedReader.close();
return readPairs;
return readLines;
}
/**
@ -136,7 +164,7 @@ public final class FileHelper {
* @param string <p>The string to remove the BOM from</p>
* @return <p>A string guaranteed without a BOM</p>
*/
private static String removeUTF8BOM(String string) {
private static @NotNull String removeUTF8BOM(@NotNull String string) {
String UTF8_BOM = "\uFEFF";
if (string.startsWith(UTF8_BOM)) {
string = string.substring(1);

View File

@ -0,0 +1,121 @@
package net.knarcraft.knarlib.util;
import net.knarcraft.knarlib.formatting.StringFormatter;
import org.bukkit.Bukkit;
import org.bukkit.Material;
import org.bukkit.NamespacedKey;
import org.bukkit.Tag;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* A helper class for dealing with and parsing materials
*/
@SuppressWarnings("unused")
public final class MaterialHelper {
private MaterialHelper() {
}
/**
* Loads the materials specified in the given list
*
* @param materials <p>The list of material strings to load</p>
* @param tagPrefix <p>The prefix differentiating a tag from a material</p>
* @param logger <p>The logger to use for logging parsing errors</p>
*/
public static @NotNull Set<Material> loadMaterialList(@NotNull List<?> materials, @NotNull String tagPrefix,
@NotNull Logger logger) {
Set<Material> parsedMaterials = new HashSet<>();
for (Object value : materials) {
if (!(value instanceof String string)) {
continue;
}
parsedMaterials.addAll(loadMaterialString(string, tagPrefix, logger));
}
return parsedMaterials;
}
/**
* Parses a string representing a material or a material tag
*
* @param materialString <p>The material string to parse</p>
* @param tagPrefix <p>The prefix differentiating a tag from a material</p>
* @param logger <p>The logger to use for logging parsing errors</p>
* @return <p>The materials defined by the material string, or an empty list if none were found</p>
*/
public static @NotNull Set<Material> loadMaterialString(@NotNull String materialString, @NotNull String tagPrefix,
@NotNull Logger logger) {
Set<Material> parsedMaterials = new HashSet<>();
// Try to parse a material tag first
if (parseMaterialTag(parsedMaterials, materialString, tagPrefix, logger)) {
return parsedMaterials;
}
// Try to parse a material name
Material matched = loadMaterialString(materialString, logger);
if (matched != null) {
parsedMaterials.add(matched);
}
return parsedMaterials;
}
/**
* Parses a string representing a material
*
* @param materialString <p>The material string to parse</p>
* @param logger <p>The logger to use for logging parsing errors</p>
* @return <p>The material defined by the material string, or null if not found</p>
*/
public static @Nullable Material loadMaterialString(@NotNull String materialString, @NotNull Logger logger) {
// Try to parse a material name
Material matched = Material.matchMaterial(materialString.replace("-", "_"));
if (matched != null) {
return matched;
} else {
logger.log(Level.WARNING, StringFormatter.replacePlaceholder("Unable to parse material: {material}",
"{material}", materialString));
return null;
}
}
/**
* Tries to parse the material tag in the specified material name
*
* @param targetSet <p>The set all parsed materials should be added to</p>
* @param materialName <p>The material name that might be a material tag</p>
* @param tagPrefix <p>The prefix differentiating a tag from a material</p>
* @param logger <p>The logger to use for logging parsing errors</p>
* @return <p>True if a tag was found</p>
*/
private static boolean parseMaterialTag(@NotNull Set<Material> targetSet, @NotNull String materialName,
@NotNull String tagPrefix, @NotNull Logger logger) {
Pattern pattern = Pattern.compile("^" + Pattern.quote(tagPrefix) + "([a-zA-Z_]+)");
Matcher matcher = pattern.matcher(materialName);
if (matcher.find()) {
// The material is a material tag
Tag<Material> tag = Bukkit.getTag(Tag.REGISTRY_BLOCKS, NamespacedKey.minecraft(
matcher.group(1).toLowerCase()), Material.class);
if (tag != null) {
targetSet.addAll(tag.getValues());
} else {
logger.log(Level.WARNING, StringFormatter.replacePlaceholder(
"Unable to parse material: {material}", "{material}", materialName));
}
return true;
}
return false;
}
}

View File

@ -0,0 +1,242 @@
package net.knarcraft.knarlib.util;
import net.knarcraft.knarlib.particle.ParticleConfig;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.util.BoundingBox;
import org.bukkit.util.Vector;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
/**
* A helper class for spawning particle effects
*/
@SuppressWarnings("unused")
public final class ParticleHelper {
private static final Map<UUID, Vector[]> pyramidVectors = new HashMap<>();
private static final Map<UUID, Double[][]> circleCoordinates = new HashMap<>();
private static final Map<UUID, Double[][]> sphereCoordinates = new HashMap<>();
private ParticleHelper() {
}
/**
* Removes any stored calculations for the given id
*
* <p>If you frequently change the id without clearing, you'll create a memory leak!</p>
*
* @param id <p>The id specified when generating a shape</p>
*/
public static void clearStoredCalculations(UUID id) {
pyramidVectors.remove(id);
circleCoordinates.remove(id);
sphereCoordinates.remove(id);
}
/**
* Spawns a cube of particles at the given location
*
* <p>It is recommended to only spawn one particle with no spread, and a density between 1 and 0.01</p>
*
* @param world <p>The world to spawn the particles in</p>
* @param location <p>The location of the block to spawn the particles at</p>
* @param particleConfig <p>The configuration describing the particle to spawn</p>
* @param blockHeight <p>The height of the block to spawn the particle above</p>
*/
public static void drawCube(@NotNull World world, @NotNull Location location, @NotNull ParticleConfig particleConfig,
double blockHeight) {
// Draw the top and bottom of the cube
drawSquare(world, location, particleConfig, blockHeight);
drawSquare(world, location.clone().add(0, 1, 0), particleConfig, blockHeight);
for (double y = 0; y <= 1; y += particleConfig.getParticleDensity()) {
double height = particleConfig.getHeightOffset() + y;
spawnParticle(world, location.clone().add(0, height, 0), particleConfig, blockHeight);
spawnParticle(world, location.clone().add(0, height, 1), particleConfig, blockHeight);
spawnParticle(world, location.clone().add(1, height, 0), particleConfig, blockHeight);
spawnParticle(world, location.clone().add(1, height, 1), particleConfig, blockHeight);
}
}
/**
* Spawns a sphere of particles at the given location
*
* <p>It is recommended to only spawn one particle with no spread, and a density between 4 and 0.1</p>
*
* @param world <p>The world to spawn the particles in</p>
* @param location <p>The location of the block to spawn the particles at</p>
* @param particleConfig <p>The configuration describing the particle to spawn</p>
* @param blockHeight <p>The height of the block to spawn the particle above</p>
* @param storedCalculationsId <p>An id for stored calculations. Use clearStoredCalculations with this id later.</p>
*/
public static void drawSphere(@NotNull World world, @NotNull Location location, @NotNull ParticleConfig particleConfig,
double blockHeight, UUID storedCalculationsId) {
// For spheres, densities below 0.1 has weird bugs such as blinking in and out of existence, and floating point
// errors when calculating the length of circleCoordinates
double density = Math.max(1, particleConfig.getParticleDensity());
// Store calculations for improved efficiency
if (sphereCoordinates.get(storedCalculationsId) == null) {
int length = (int) Math.ceil((180 / density));
Double[][] coordinates = new Double[length * 3][];
double height = particleConfig.getHeightOffset() + 0.5;
int i = 0;
for (double x = 0; x < 180; x += density) {
if (i >= coordinates.length) {
continue;
}
double cos = 0.5 * Math.cos(x);
double sin = 0.5 * Math.sin(x);
coordinates[i++] = new Double[]{sin + 0.5, height, cos + 0.5};
coordinates[i++] = new Double[]{sin + 0.5, height + cos, 0.5};
coordinates[i++] = new Double[]{0.5, height + sin, cos + 0.5};
}
sphereCoordinates.put(storedCalculationsId, coordinates);
}
// Spawn particles on the stored locations, relative to the launchpad
for (Double[] sphereCoordinate : sphereCoordinates.get(storedCalculationsId)) {
spawnParticle(world, location.clone().add(sphereCoordinate[0], sphereCoordinate[1], sphereCoordinate[2]),
particleConfig, blockHeight);
}
}
/**
* Spawns a pyramid of particles at the given location
*
* <p>It is recommended to only spawn one particle with no spread, and a density between 1 and 0.01</p>
*
* @param world <p>The world to spawn the particles in</p>
* @param location <p>The location of the block to spawn the particles at</p>
* @param particleConfig <p>The configuration describing the particle to spawn</p>
* @param blockHeight <p>The height of the block to spawn the particle above</p>
* @param storedCalculationsId <p>An id for stored calculations. Use clearStoredCalculations with this id later.</p>
*/
public static void drawPyramid(@NotNull World world, @NotNull Location location, @NotNull ParticleConfig particleConfig,
double blockHeight, UUID storedCalculationsId) {
// Draw the bottom of the pyramid
drawSquare(world, location, particleConfig, blockHeight);
// Store calculations for improved efficiency
if (pyramidVectors.get(storedCalculationsId) == null) {
// The 0.5 offsets are required for the angle of the pyramid's 4 lines to be correct
double height = particleConfig.getHeightOffset();
double coordinateMin = -0.5 * height;
double coordinateMax = 1 + (0.5 * height);
Vector[] vectors = new Vector[5];
// The vector from the origin to the top of the pyramid
vectors[0] = new Vector(0.5, 1 + height, 0.5);
// The vectors from the top of the pyramid towards each corner
vectors[1] = new Vector(coordinateMin, 0, coordinateMin).subtract(vectors[0]).normalize();
vectors[2] = new Vector(coordinateMax, 0, coordinateMin).subtract(vectors[0]).normalize();
vectors[3] = new Vector(coordinateMin, 0, coordinateMax).subtract(vectors[0]).normalize();
vectors[4] = new Vector(coordinateMax, 0, coordinateMax).subtract(vectors[0]).normalize();
pyramidVectors.put(storedCalculationsId, vectors);
}
Vector[] storedVectors = pyramidVectors.get(storedCalculationsId);
Location topLocation = location.clone().add(storedVectors[0]);
for (double x = 0; x <= 1.2; x += particleConfig.getParticleDensity()) {
spawnParticle(world, topLocation.clone().add(storedVectors[1].clone().multiply(x)), particleConfig, blockHeight);
spawnParticle(world, topLocation.clone().add(storedVectors[2].clone().multiply(x)), particleConfig, blockHeight);
spawnParticle(world, topLocation.clone().add(storedVectors[3].clone().multiply(x)), particleConfig, blockHeight);
spawnParticle(world, topLocation.clone().add(storedVectors[4].clone().multiply(x)), particleConfig, blockHeight);
}
}
/**
* Spawns a circle of particles at the given location
*
* <p>It is recommended to only spawn one particle with no spread, and a density between 4 and 0.1</p>
*
* @param world <p>The world to spawn the particles in</p>
* @param location <p>The location of the block to spawn the particles at</p>
* @param particleConfig <p>The configuration describing the particle to spawn</p>
* @param blockHeight <p>The height of the block to spawn the particle above</p>
* @param storedCalculationsId <p>An id for stored calculations. Use clearStoredCalculations with this id later.</p>
*/
public static void drawCircle(@NotNull World world, @NotNull Location location, @NotNull ParticleConfig particleConfig,
double blockHeight, UUID storedCalculationsId) {
// For circles, densities below 0.1 has weird bugs such as blinking in and out of existence, and floating point
// errors when calculating the length of circleCoordinates
double density = Math.max(1, particleConfig.getParticleDensity());
// Store calculations for improved efficiency
if (circleCoordinates.get(storedCalculationsId) == null) {
Double[][] coordinates = new Double[(int) Math.ceil((180 / density))][];
int i = 0;
for (double x = 0; x < 180; x += density) {
if (i >= coordinates.length) {
continue;
}
coordinates[i++] = new Double[]{(0.5 * Math.sin(x)) + 0.5, (0.5 * Math.cos(x)) + 0.5};
}
circleCoordinates.put(storedCalculationsId, coordinates);
}
// Spawn particles on the stored locations, relative to the launchpad
for (Double[] circleCoordinate : circleCoordinates.get(storedCalculationsId)) {
spawnParticle(world, location.clone().add(circleCoordinate[0], particleConfig.getHeightOffset(),
circleCoordinate[1]), particleConfig, blockHeight);
}
}
/**
* Spawns a square of particles at the given location
*
* <p>It is recommended to only spawn one particle with no spread, and a density between 1 and 0.01</p>
*
* @param world <p>The world to spawn the particles in</p>
* @param location <p>The location of the block to spawn the particles at</p>
* @param particleConfig <p>The configuration describing the particle to spawn</p>
* @param blockHeight <p>The height of the block to spawn the particle above</p>
*/
public static void drawSquare(@NotNull World world, @NotNull Location location, @NotNull ParticleConfig particleConfig,
double blockHeight) {
for (double x = 0; x <= 1; x += particleConfig.getParticleDensity()) {
spawnParticle(world, location.clone().add(x, particleConfig.getHeightOffset(), 0), particleConfig, blockHeight);
spawnParticle(world, location.clone().add(x, particleConfig.getHeightOffset(), 1), particleConfig, blockHeight);
spawnParticle(world, location.clone().add(0, particleConfig.getHeightOffset(), x), particleConfig, blockHeight);
spawnParticle(world, location.clone().add(1, particleConfig.getHeightOffset(), x), particleConfig, blockHeight);
}
}
/**
* Spawns the specified particle at the given location
*
* @param world <p>The world to spawn the particle in</p>
* @param location <p>The location to spawn the particle at</p>
* @param particleConfig <p>The configuration describing the particle to spawn</p>
* @param blockHeight <p>The height of the block to spawn the particle above</p>
*/
public static void spawnParticle(@NotNull World world, @NotNull Location location,
@NotNull ParticleConfig particleConfig, double blockHeight) {
world.spawnParticle(particleConfig.getParticleType(),
location.add(0, blockHeight, 0), particleConfig.getParticleAmount(),
particleConfig.getOffsetX(), particleConfig.getOffsetY(), particleConfig.getOffsetZ(),
particleConfig.getExtra());
}
/**
* Gets the height of the block at the given location
*
* @param block <p>The block to check</p>
* @return <p>The height of the block</p>
*/
public static double getBlockHeight(@NotNull Block block) {
double maxY = 0;
for (BoundingBox boundingBox : block.getCollisionShape().getBoundingBoxes()) {
if (boundingBox.getMaxY() > maxY) {
maxY = boundingBox.getMaxY();
}
}
return maxY;
}
}

View File

@ -1,7 +1,11 @@
package net.knarcraft.knarlib.util;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.function.BiFunction;
/**
* Helper class for getting string lists required for auto-completion
@ -12,6 +16,58 @@ public final class TabCompletionHelper {
private TabCompletionHelper() {
}
/**
* Finds available tab-completions for a list where several comma-separated values are allowed
*
* <p>If, for example, the available values are [fish,potatoes,stew,strawberries], and the player has already typed
* "fish,potatoes,", then available tab-completions become fish,potatoes,stew and fish,potatoes,strawberries.</p>
*
* @param values <p>The available tab-completion values</p>
* @param typedText <p>The text the player has typed</p>
* @param filterer <p>The filterer (filterMatching) to use to filter by typed text</p>
* @return <p>Available tab-completions</p>
*/
public static @NotNull List<String> getStringList(@NotNull List<String> values, @NotNull String typedText,
@Nullable BiFunction<List<String>, String, List<String>> filterer) {
if (typedText.isBlank() || !typedText.contains(",")) {
if (filterer != null) {
return filterer.apply(values, typedText);
} else {
return values;
}
} else {
// The argument base is everything the player has typed, until the last comma
List<String> arguments = List.of(typedText.split(","));
String base;
List<String> baseArguments;
if (typedText.endsWith(",")) {
baseArguments = arguments;
} else {
baseArguments = arguments.subList(0, arguments.size() - 1);
}
base = String.join(",", baseArguments) + ",";
String lastArgument = arguments.get(arguments.size() - 1);
// Filter available values by the text after the last comma only
List<String> filtered;
if (filterer != null && !typedText.endsWith(",")) {
filtered = filterer.apply(new ArrayList<>(values), lastArgument);
} else {
filtered = values;
}
// Remove any already used values
List<String> unused = new ArrayList<>();
for (String string : filtered) {
if (!baseArguments.contains(string)) {
unused.add(base + string);
}
}
return unused;
}
}
/**
* Finds tab complete values that contain the typed text
*
@ -19,7 +75,7 @@ public final class TabCompletionHelper {
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values that contain the player's typed text</p>
*/
public static List<String> filterMatchingContains(List<String> values, String typedText) {
public static @NotNull List<String> filterMatchingContains(@NotNull List<String> values, @NotNull String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().contains(typedText.toLowerCase())) {
@ -36,7 +92,7 @@ public final class TabCompletionHelper {
* @param typedText <p>The text the player has started typing</p>
* @return <p>The given string values that start with the player's typed text</p>
*/
public static List<String> filterMatchingStartsWith(List<String> values, String typedText) {
public static @NotNull List<String> filterMatchingStartsWith(@NotNull List<String> values, @NotNull String typedText) {
List<String> configValues = new ArrayList<>();
for (String value : values) {
if (value.toLowerCase().startsWith(typedText.toLowerCase())) {
@ -46,4 +102,15 @@ public final class TabCompletionHelper {
return configValues;
}
/**
* Gets the "base" string from the given arguments, not including the incomplete last argument
*
* @param arguments <p>The string arguments to get the base of</p>
* @return <p>The base string</p>
*/
private static @NotNull String getBase(@NotNull List<String> arguments) {
List<String> baseArray = arguments.subList(0, arguments.size() - 1);
return String.join(",", baseArray) + ",";
}
}

View File

@ -2,6 +2,8 @@ package net.knarcraft.knarlib.util;
import org.bukkit.plugin.Plugin;
import org.bukkit.scheduler.BukkitScheduler;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.BufferedReader;
import java.io.IOException;
@ -33,8 +35,9 @@ public final class UpdateChecker {
* @param getVersionSupplier <p>The supplier used to get the current plugin version</p>
* @param setVersionMethod <p>A method to call with the new version as an argument. Can be used to alert admins about an available update or similar.</p>
*/
public static void checkForUpdate(Plugin plugin, String apiResourceURL, Supplier<String> getVersionSupplier,
Consumer<String> setVersionMethod) {
public static void checkForUpdate(@NotNull Plugin plugin, @NotNull String apiResourceURL,
@NotNull Supplier<String> getVersionSupplier,
@Nullable Consumer<String> setVersionMethod) {
BukkitScheduler scheduler = plugin.getServer().getScheduler();
scheduler.runTaskAsynchronously(plugin, () -> UpdateChecker.queryAPI(plugin, apiResourceURL, getVersionSupplier,
setVersionMethod));
@ -48,8 +51,9 @@ public final class UpdateChecker {
* @param getVersionMethod <p>The supplier used to get the current plugin version</p>
* @param setVersionMethod <p>A method to call with the new version as an argument. Can be used to alert admins about an available update or similar.</p>
*/
private static void queryAPI(Plugin plugin, String APIResourceURL, Supplier<String> getVersionMethod,
Consumer<String> setVersionMethod) {
private static void queryAPI(@NotNull Plugin plugin, @NotNull String APIResourceURL,
@NotNull Supplier<String> getVersionMethod,
@Nullable Consumer<String> setVersionMethod) {
try {
InputStream inputStream = new URL(APIResourceURL).openStream();
BufferedReader reader = FileHelper.getBufferedReaderFromInputStream(inputStream);
@ -77,7 +81,7 @@ public final class UpdateChecker {
* @param oldVersion <p>The old (current) plugin version</p>
* @return <p>The string to display</p>
*/
public static String getUpdateAvailableString(String newVersion, String oldVersion) {
public static @NotNull String getUpdateAvailableString(@NotNull String newVersion, @NotNull String oldVersion) {
return String.format(updateNotice, newVersion, oldVersion);
}
@ -88,7 +92,7 @@ public final class UpdateChecker {
* @param newVersion <p>The new version to check</p>
* @return <p>True if the new version is higher than the old one</p>
*/
public static boolean isVersionHigher(String oldVersion, String newVersion) {
public static boolean isVersionHigher(@NotNull String oldVersion, @NotNull String newVersion) {
oldVersion = removeNonNumericCharacters(oldVersion);
newVersion = removeNonNumericCharacters(newVersion);
String[] oldVersionParts = oldVersion.split("\\.");
@ -110,7 +114,7 @@ public final class UpdateChecker {
* @param versionString <p>The version string to clean</p>
* @return <p>The string with non-numeric characters replaced</p>
*/
private static String removeNonNumericCharacters(String versionString) {
private static @NotNull String removeNonNumericCharacters(@NotNull String versionString) {
return versionString.replaceAll("[^0-9.]", "");
}

View File

@ -0,0 +1,82 @@
package net.knarcraft.knarlib.util;
import org.junit.jupiter.api.Test;
import java.util.List;
import static org.junit.jupiter.api.Assertions.assertEquals;
/**
* Tests for the tab-completion helper
*/
public class TabCompletionHelperTest {
@Test
public void getStringListUnfilteredTest() {
List<String> input = List.of("a", "b", "c", "d");
List<String> result = TabCompletionHelper.getStringList(input, "a,b,", null);
List<String> expected = List.of("a,b,c", "a,b,d");
assertEquals(expected, result);
}
@Test
public void getStringListUnfilteredTest2() {
List<String> input = List.of("a", "b", "c", "d");
List<String> result = TabCompletionHelper.getStringList(input, "a,b", null);
List<String> expected = List.of("a,b", "a,c", "a,d");
assertEquals(expected, result);
}
@Test
public void getStringListContainsTest() {
List<String> input = List.of("ape", "ball", "car", "donut");
List<String> result = TabCompletionHelper.getStringList(input, "ball,donut,r",
TabCompletionHelper::filterMatchingContains);
List<String> expected = List.of("ball,donut,car");
assertEquals(expected, result);
}
@Test
public void getStringListContainsTest2() {
List<String> input = List.of("ape", "ball", "car", "donut");
List<String> result = TabCompletionHelper.getStringList(input, "ball,donut,",
TabCompletionHelper::filterMatchingContains);
List<String> expected = List.of("ball,donut,ape", "ball,donut,car");
assertEquals(expected, result);
}
@Test
public void getStringListStartsWithTest() {
List<String> input = List.of("ape", "ball", "car", "donut");
List<String> result = TabCompletionHelper.getStringList(input, "ball,donut,a",
TabCompletionHelper::filterMatchingStartsWith);
List<String> expected = List.of("ball,donut,ape");
assertEquals(expected, result);
}
@Test
public void getStringListStartsWithTest2() {
List<String> input = List.of("ape", "ball", "car", "donut");
List<String> result = TabCompletionHelper.getStringList(input, "ball,donut,",
TabCompletionHelper::filterMatchingStartsWith);
List<String> expected = List.of("ball,donut,ape", "ball,donut,car");
assertEquals(expected, result);
}
@Test
public void filterMatchingContainsTest() {
List<String> input = List.of("fish", "ape", "banana", "toy", "human", "stick", "potato", "detergent", "magazine");
List<String> result = TabCompletionHelper.filterMatchingContains(input, "a");
List<String> expected = List.of("ape", "banana", "human", "potato", "magazine");
assertEquals(expected, result);
}
@Test
public void filterMatchingStartsWithTest() {
List<String> input = List.of("fish", "ape", "banana", "toy", "human", "stick", "potato", "detergent", "magazine");
List<String> result = TabCompletionHelper.filterMatchingStartsWith(input, "a");
List<String> expected = List.of("ape");
assertEquals(expected, result);
}
}