("maven") {
+ artifactId = globalArtifactId
+ groupId = "tech.carcadex"
+ version = version
+ from(components["java"])
+ versionMapping {
+ usage("java-api") {
+ fromResolutionOf("runtimeClasspath")
+ }
+ usage("java-runtime") {
+ fromResolutionResult()
+ }
+ }
+ pom {
+ artifactId = globalArtifactId
+ groupId = "tech.carcadex"
+ version = version
+ name.set(globalArtifactId)
+ description.set(globalArtifactId)
+ url.set("https://github.com/iRedTea/CarcadeX")
+
+ licenses {
+ license {
+ name.set("GNU General Public License v3.0")
+ url.set("https://www.gnu.org/licenses/gpl-3.0.en.html")
+ }
+ }
+ developers {
+ developer {
+ id.set("itzRedTea")
+ name.set("Red Tea")
+ email.set("red__tea@outlook.com")
+ }
+ }
+ scm {
+ connection.set("scm:https://github.com/CarcadeX/$globalName.git")
+ developerConnection.set("git@github.com:CarcadeX/$globalName.git")
+ url.set("https://github.com/iRedTea/CarcadeX")
+ }
+ }
+
+ }
+ }
+
+
+
+ repositories {
+ maven {
+ val releasesRepoUrl = "https://s01.oss.sonatype.org/service/local/staging/deploy/maven2/"
+ val snapshotsRepoUrl = "https://s01.oss.sonatype.org/content/repositories/snapshots/"
+ url = uri(if (version.toString().endsWith("SNAPSHOT")) snapshotsRepoUrl else releasesRepoUrl)
+ credentials {
+ username = "itzRedTea"
+ password = project.findProperty("password").toString()
+ }
+ }
+
+ }
+}
+
+signing {
+ useGpgCmd()
+ sign(publishing.publications.getByName("maven"))
+}
diff --git a/messages/libs/MiniMessages.jar b/messages/libs/MiniMessages.jar
new file mode 100644
index 0000000..7bdeb9e
Binary files /dev/null and b/messages/libs/MiniMessages.jar differ
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/container/Messages.java b/messages/src/main/java/me/redtea/carcadex/messages/container/Messages.java
new file mode 100644
index 0000000..7cb8ee7
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/container/Messages.java
@@ -0,0 +1,257 @@
+package me.redtea.carcadex.messages.container;
+
+import me.redtea.carcadex.messages.factory.impl.MessageFactoryImpl;
+import me.redtea.carcadex.messages.model.Message;
+import me.redtea.carcadex.messages.container.impl.MessagesImpl;
+import me.redtea.carcadex.messages.factory.MessageFactory;
+import me.redtea.carcadex.messages.factory.impl.LegacyFactoryImpl;
+import me.redtea.carcadex.messages.model.impl.NullMessage;
+import me.redtea.carcadex.messages.verifier.MessageVerifier;
+import me.redtea.carcadex.messages.verifier.impl.FileMessageVerifier;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+
+import java.io.File;
+import java.nio.file.Path;
+
+/**
+ * Container of messages
+ * Create container by plugin with file messages.yml
+ * Step 1. Create file messages.yml in resources folder (in jar of plugin)
+ * Write in file your messages in YAML format. Use MiniMessages format for color messages.
+ * See https://docs.advntr.dev/minimessage/format.html
+ * For example I will be use this content:
+ *
+ * {@code
+ * error: "Error!"
+ * someRoot:
+ * message1: "Message"
+ * message2:
+ * - "Message start!"
+ * - "Message end!"
+ *
+ * }
+ *
+ * Step 2. Load Messages container in your onEnable method
+ *
+ * {@code
+ * Messages messages = Messages.of(this);
+ * }
+ *
+ * Step 3. Done! Now you can use the messages variable to manage messages.
+ * Create container by plugin with custom file
+ * Everything is the same, the same, only in jar create a file with the name you need (or several such files)
+ * and change OnEnable like this:
+ *
+ * {@code
+ * Messages messages = Messages.of(this, "YOURFILENAME.yml");
+ * }
+ *
+ * Create container by ConfigurationSection
+ * Here are a couple of examples of how this can be done:
+ *
+ * {@code
+ * Messages messages = Messages.of(getConfig().getConfigurationSection("messages"));
+ *
+ * //or so
+ *
+ * ConfigurationSection section = ...;
+ * Messages messages = Messages.of(section);
+ * }
+ *
+ * Reloadable Container:
+ * section - ConfigurationSection with messages
+ * file - File with messages
+ * SectionProvider - provider of section with messages
+ * @since 1.0.0
+ * @author itzRedTea
+ */
+public interface Messages {
+ /**
+ * Get message from container by key/yaml path
+ *
+ * For example
+ * Yaml structure:
+ *
+ * {@code
+ * error: "Error!"
+ * someRoot:
+ * message1: "Message"
+ * message2:
+ * - "Message start!"
+ * - "Message end!"
+ *
+ * }
+ *
+ *
+ * Getting messages in code:
+ *
+ * {@code
+ * Messages messages = ...; //Init Messages
+ * CommandSender sender = ...; //Getting CommandSender
+ * messages.get("error").send(sender); //Getting error message
+ * message.get("someRoot.message1").send(sender); //Getting someRoot.message1 message
+ * message.get("someRoot.message2").send(sender); //Getting someRoot.message2 message
+ * }
+ *
+ *
+ * @param key key of message
+ * @return Message
+ * @see Message
+ */
+ @NotNull Message get(@NotNull String key);
+
+ /**
+ * Put message to container
+ * @param key key/yaml path to message
+ * @param message message to mut
+ * @return putted message
+ * @see Message
+ */
+ @NotNull Message put(@NotNull String key, @NotNull Message message);
+
+ /**
+ * @param key key/yaml path to message
+ * @return is there a message with such a key in the container?
+ */
+ boolean has(String key);
+
+ /**
+ * Reload container with messages in section from args.
+ * @param section - ConfigurationSection with messages
+ */
+ void reload(@NotNull ConfigurationSection section);
+
+ /**
+ * Sets custom message parse factory.
+ */
+ void factory(@NotNull MessageFactory messageFactory);
+
+ /**
+ * Parses all messages in stored section
+ */
+ void parse();
+
+ void verifier(MessageVerifier verifier);
+
+ /**
+ * Create container using configuration section
+ * Create container using messages from configuration section.
+ *
+ * {@code
+ * ConfigurationSection section = ...;
+ * Messages messages = Messages.of(section);
+ * }
+ *
+ * @param section - configuration section with messages
+ * @return container with messages
+ */
+ static Messages of(ConfigurationSection section, MessageFactory factory) {
+ return new MessagesImpl(section, factory);
+ }
+
+ /**
+ * Create container using current plugin and custom filename
+ * Create container using file with your name from jar entry by default.
+ * A new file in plugin folder will be created with content from your file.
+ *
+ * {@code
+ * Messages messages = Messages.of(this, "YOURFILENAME.yml");
+ * }
+ *
+ * @param plugin - plugin that creates a container
+ * @return container with messages
+ */
+ static Messages of(String fileName, Plugin plugin) {
+ File file = new File(plugin.getDataFolder(), fileName);
+ if(!file.exists()) plugin.saveResource(fileName, false);
+ Messages messages = of(YamlConfiguration.loadConfiguration(file), new MessageFactoryImpl(plugin));
+ messages.verifier(new FileMessageVerifier(plugin.getResource(fileName), file));
+ return messages;
+ }
+
+ /**
+ * Create container using current plugin
+ * Create container using messages.yml file by default from jar entry.
+ * A new file in plugin folder will be created with content from messages.yml
+ *
+ * {@code
+ * Messages messages = Messages.of(this);
+ * }
+ *
+ * @param plugin - plugin that creates a container
+ * @return container with messages
+ */
+ static Messages of(Plugin plugin) {
+ return of("messages.yml", plugin);
+ }
+
+ /**
+ * Create container using file
+ *
+ * {@code
+ * File file = new File("msg_file.yml");
+ * Messages messages = Messages.of(file);
+ * }
+ *
+ * @param file - file with messages
+ * @return container with messages
+ */
+ static Messages of(File file, Plugin plugin) {
+ return of(YamlConfiguration.loadConfiguration(file), new MessageFactoryImpl(plugin));
+ }
+
+ /**
+ * Create container using path
+ *
+ * @param path - file with messages
+ * @return container with messages
+ * @see Messages#of(File, Plugin)
+ */
+ static Messages of(Path path, Plugin plugin) {
+ return of(path.toFile(), plugin);
+ }
+
+ /**
+ * Only legacy color codes. For versions lower than 1.18
+ * @return container with messages
+ */
+ static Messages legacy(ConfigurationSection section, Plugin plugin) {
+ return new MessagesImpl(section, new LegacyFactoryImpl(plugin));
+ }
+
+ /** @see Messages#legacy(ConfigurationSection, Plugin) */
+ static Messages legacy(String fileName, Plugin plugin) {
+ File file = new File(plugin.getDataFolder(), fileName);
+ if(!file.exists()) plugin.saveResource(fileName, false);
+ Messages messages = new MessagesImpl(file, new LegacyFactoryImpl(plugin));
+ messages.verifier(new FileMessageVerifier(plugin.getResource(fileName), file));
+ return messages;
+ }
+
+ /** @see Messages#legacy(ConfigurationSection, Plugin) */
+ static Messages legacy(Plugin plugin) {
+ return legacy("messages.yml", plugin);
+ }
+
+ /** @see Messages#legacy(ConfigurationSection, Plugin) */
+ static Messages legacy(File file, Plugin plugin) {
+ return new MessagesImpl(file, new LegacyFactoryImpl(plugin));
+ }
+
+ /**
+ * @return empty messages container
+ * Use this with Reloader
+ */
+ static @NotNull Messages empty() {
+ return new MessagesImpl();
+ }
+
+ /**
+ * Singleton NULL_MESSAGE
+ * @see NullMessage
+ */
+ Message NULL_MESSAGE = new NullMessage();
+}
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/container/impl/MessagesImpl.java b/messages/src/main/java/me/redtea/carcadex/messages/container/impl/MessagesImpl.java
new file mode 100644
index 0000000..4945ece
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/container/impl/MessagesImpl.java
@@ -0,0 +1,120 @@
+package me.redtea.carcadex.messages.container.impl;
+
+import me.redtea.carcadex.messages.container.Messages;
+import me.redtea.carcadex.messages.model.Message;
+import me.redtea.carcadex.messages.factory.MessageFactory;
+import me.redtea.carcadex.messages.model.impl.NullMessage;
+import me.redtea.carcadex.messages.verifier.MessageVerifier;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.YamlConfiguration;
+import org.jetbrains.annotations.NotNull;
+import org.jetbrains.annotations.Nullable;
+
+import java.io.File;
+import java.util.*;
+
+public class MessagesImpl implements Messages {
+ private Map messages;
+
+ private MessageFactory factory;
+
+ private final Message NULL_MESSAGE;
+
+ private ConfigurationSection section;
+
+ @Nullable
+ private MessageVerifier verifier;
+
+ public MessagesImpl() {
+ NULL_MESSAGE = new NullMessage();
+ }
+
+ public MessagesImpl(@NotNull ConfigurationSection section, MessageFactory factory) {
+ this.section = section;
+ this.factory = factory;
+ NULL_MESSAGE = factory.nullMessage();
+ parse();
+ }
+
+ public MessagesImpl(@NotNull File file, MessageFactory factory) {
+ this(YamlConfiguration.loadConfiguration(file), factory);
+ }
+
+ @Override
+ public @NotNull Message get(@NotNull String key) {
+ if(!has(key) || messages.get(key) == null) {
+ if(verifier != null) {
+ Optional def = verifier.fromDefault(key);
+ if(def.isPresent()) {
+ Message message = def.get() instanceof List ? factory.message((List) def.get())
+ : factory.message(def.get().toString());
+ messages.put(key, message);
+ return message;
+ }
+ return NULL_MESSAGE;
+ }
+ return NULL_MESSAGE;
+ }
+ return messages.get(key) == null ? NULL_MESSAGE : messages.get(key);
+ }
+
+ @Override
+ public @NotNull Message put(@NotNull String key, @NotNull Message message) {
+ if(!has(key)) return NULL_MESSAGE;
+ return Objects.requireNonNull(messages.put(key, message));
+ }
+
+ @Override
+ public boolean has(String key) {
+ if(key == null) return false;
+ return messages.containsKey(key);
+ }
+
+ @Override
+ public void reload(@NotNull ConfigurationSection section) {
+ messages = fromConfigurationToMap(section);
+ }
+
+ @Override
+ public void factory(@NotNull MessageFactory messageFactory) {
+ this.factory = messageFactory;
+ }
+
+ @Override
+ public void parse() {
+ if(section != null) messages = fromConfigurationToMap(section);
+ }
+
+ @Override
+ public void verifier(MessageVerifier verifier) {
+ this.verifier = verifier;
+ }
+
+
+
+ private Map fromConfigurationToMap(@NotNull ConfigurationSection section) {
+ Map data = new HashMap<>();
+ section.getKeys(false).forEach(key -> {
+ if (section.isConfigurationSection(key)) {
+ Map newMessages = fromConfigurationToMap(Objects.
+ requireNonNull(section.getConfigurationSection(key)));
+ newMessages.forEach((keyMessage, message) -> data.put(key + "." + keyMessage, message));
+ } else {
+ fromSection(section, key).ifPresent(value -> data.put(key, value));
+ }
+ });
+
+ return data;
+ }
+
+ private Optional fromSection(@NotNull ConfigurationSection section, String key) {
+ Message message = null;
+ if (section.isString(key)) {
+ message = factory.message(section.getString(key));
+ } else if (section.isList(key)) {
+ message = factory.message(section.getStringList(key));
+ }
+ return Optional.ofNullable(message);
+ }
+
+}
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/factory/MessageFactory.java b/messages/src/main/java/me/redtea/carcadex/messages/factory/MessageFactory.java
new file mode 100644
index 0000000..fb0d5b1
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/factory/MessageFactory.java
@@ -0,0 +1,17 @@
+package me.redtea.carcadex.messages.factory;
+
+import me.redtea.carcadex.messages.container.Messages;
+import me.redtea.carcadex.messages.model.Message;
+
+import java.util.List;
+
+public interface MessageFactory {
+ Message message(List unparsed);
+ Message message(String msg);
+ Message legacyMessage(List unparsed);
+ Message legacyMessage(String msg);
+
+ default Message nullMessage() {
+ return Messages.NULL_MESSAGE;
+ }
+}
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/factory/impl/LegacyFactoryImpl.java b/messages/src/main/java/me/redtea/carcadex/messages/factory/impl/LegacyFactoryImpl.java
new file mode 100644
index 0000000..f78d45e
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/factory/impl/LegacyFactoryImpl.java
@@ -0,0 +1,45 @@
+package me.redtea.carcadex.messages.factory.impl;
+
+import com.google.common.collect.Lists;
+import me.redtea.carcadex.messages.model.Message;
+import me.redtea.carcadex.messages.utils.MD5ColorUtils;
+import me.redtea.carcadex.messages.factory.MessageFactory;
+import me.redtea.carcadex.messages.model.impl.serialized.impl.LegacyMessage;
+import org.bukkit.plugin.Plugin;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class LegacyFactoryImpl implements MessageFactory {
+ private final Plugin plugin;
+
+ public LegacyFactoryImpl(Plugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public Message message(List unparsed) {
+ if(unparsed == null || unparsed.isEmpty()) return nullMessage();
+ if(unparsed.stream().anyMatch(it -> it.contains("&"))) unparsed =
+ unparsed.stream()
+ .map(MD5ColorUtils::translateHexColorCodes)
+ .collect(Collectors.toList());
+ return new LegacyMessage(unparsed, plugin);
+ }
+
+ @Override
+ public Message message(String msg) {
+ if(msg == null) return nullMessage();
+ return message(Lists.newArrayList(msg));
+ }
+
+ @Override
+ public Message legacyMessage(List unparsed) {
+ return message(unparsed);
+ }
+
+ @Override
+ public Message legacyMessage(String msg) {
+ return message(Lists.newArrayList(msg));
+ }
+}
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/factory/impl/MessageFactoryImpl.java b/messages/src/main/java/me/redtea/carcadex/messages/factory/impl/MessageFactoryImpl.java
new file mode 100644
index 0000000..854805d
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/factory/impl/MessageFactoryImpl.java
@@ -0,0 +1,46 @@
+package me.redtea.carcadex.messages.factory.impl;
+
+import com.google.common.collect.Lists;
+import me.redtea.carcadex.messages.model.Message;
+import me.redtea.carcadex.messages.utils.MD5ColorUtils;
+import me.redtea.carcadex.messages.factory.MessageFactory;
+import me.redtea.carcadex.messages.model.impl.ComponentMessage;
+import me.redtea.carcadex.messages.model.impl.serialized.impl.LegacyMessage;
+import org.bukkit.plugin.Plugin;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class MessageFactoryImpl implements MessageFactory {
+ private final Plugin plugin;
+
+ public MessageFactoryImpl(Plugin plugin) {
+ this.plugin = plugin;
+ }
+
+ @Override
+ public Message message(List unparsed) {
+ if(unparsed == null || unparsed.isEmpty()) return nullMessage();
+ return new ComponentMessage(unparsed, plugin);
+ }
+
+ @Override
+ public Message message(String msg) {
+ return message(Lists.newArrayList(msg));
+ }
+
+ @Override
+ public Message legacyMessage(List unparsed) {
+ if(unparsed == null || unparsed.isEmpty()) return nullMessage();
+ if(unparsed.stream().anyMatch(it -> it.contains("&"))) unparsed =
+ unparsed.stream()
+ .map(MD5ColorUtils::translateHexColorCodes)
+ .collect(Collectors.toList());
+ return new LegacyMessage(unparsed, plugin);
+ }
+
+ @Override
+ public Message legacyMessage(String msg) {
+ return legacyMessage(Lists.newArrayList(msg));
+ }
+}
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/kotlin/MessagesExtensions.kt b/messages/src/main/java/me/redtea/carcadex/messages/kotlin/MessagesExtensions.kt
new file mode 100644
index 0000000..77afa94
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/kotlin/MessagesExtensions.kt
@@ -0,0 +1,32 @@
+package me.redtea.carcadex.messages.kotlin
+
+import me.redtea.carcadex.messages.container.Messages
+import me.redtea.carcadex.messages.model.Message
+import me.redtea.carcadex.messages.verifier.MessageVerifier
+import org.bukkit.command.CommandSender
+import org.bukkit.plugin.Plugin
+import java.io.File
+
+fun CommandSender.send(message: Message) = message.send(this)
+
+fun messages(init: MessagesBuilder.() -> Unit): Messages {
+ val builder = MessagesBuilder()
+ builder.init()
+ return builder.build()
+}
+
+class MessagesBuilder(var legacy: Boolean = false,
+ var plugin: Plugin? = null,
+ var fileName: String? = null,
+ var verifier: MessageVerifier? = null,
+ var file: File? = null) {
+ fun build(): Messages {
+ if(plugin == null) throw RuntimeException("Missed plugin")
+ return if(file == null) {
+ return (if(legacy) Messages.legacy(fileName ?: "messages.yml", plugin!!) else Messages.of(fileName ?: "messages.yml", plugin!!)).also { m -> verifier?.let { m.verifier(it) } }
+ } else return (if(legacy) Messages.legacy(file!!, plugin!!) else Messages.of(file!!, plugin!!)).also { m -> verifier?.let { m.verifier(it) } }
+ }
+}
+
+fun Plugin.loadMessages(): Messages = Messages.of(this)
+fun Plugin.loadMessages(filename: String): Messages = Messages.of(filename, this)
\ No newline at end of file
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/kotlin/TriumphGuiExtensions.kt b/messages/src/main/java/me/redtea/carcadex/messages/kotlin/TriumphGuiExtensions.kt
new file mode 100644
index 0000000..5105cf6
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/kotlin/TriumphGuiExtensions.kt
@@ -0,0 +1,8 @@
+package me.redtea.carcadex.messages.kotlin
+
+import dev.triumphteam.gui.builder.item.ItemBuilder
+import me.redtea.carcadex.messages.model.Message
+
+fun ItemBuilder.name(message: Message) = this.name(message.asComponent())
+
+fun ItemBuilder.lore(message: Message) = this.lore(message.asComponentList())
\ No newline at end of file
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/model/AbstractMessage.java b/messages/src/main/java/me/redtea/carcadex/messages/model/AbstractMessage.java
new file mode 100644
index 0000000..4d8404d
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/model/AbstractMessage.java
@@ -0,0 +1,83 @@
+package me.redtea.carcadex.messages.model;
+
+import com.cryptomorin.xseries.XMaterial;
+import net.kyori.adventure.audience.Audience;
+import net.kyori.adventure.platform.bukkit.BukkitAudiences;
+import net.kyori.adventure.text.Component;
+import org.bukkit.command.CommandSender;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public abstract class AbstractMessage implements Message {
+ protected final List unparsed;
+
+ protected List parsed = null;
+
+ protected final BukkitAudiences adventure;
+
+ public AbstractMessage(List unparsed, Plugin plugin) {
+ this.unparsed = unparsed;
+ adventure = BukkitAudiences.create(plugin);
+ }
+
+ protected abstract Component parse(String unparsed);
+
+ private void parse() {
+ parsed = new ArrayList<>();
+ for(String s : unparsed) {
+ parsed.add(parse(s));
+ }
+ }
+
+ @Override
+ public void send(@NotNull CommandSender sender) {
+ parse();
+ Audience audience = adventure.sender(sender);
+ parsed.forEach(audience::sendMessage);
+ }
+
+ private Component cacheComponent = null;
+
+ @Override
+ @NotNull
+ public List asComponentList() {
+ parse();
+ return parsed;
+ }
+
+ @Override
+ public @NotNull Component asComponent() {
+ if(cacheComponent == null) {
+ parse();
+ Component first = parsed.get(0);
+ for(int i = 1; i < parsed.size(); i++) {
+ first = first.append(parsed.get(i)).append(Component.newline());
+ }
+ cacheComponent = first;
+ }
+ return cacheComponent;
+ }
+
+ @Override
+ @NotNull
+ public List asUnparsedStringList() {
+ return unparsed;
+ }
+
+ private String cacheString = null;
+ @Override
+ @NotNull
+ public String asUnparsedString(@NotNull String newLine) {
+ if(cacheString == null) cacheString = String.join(newLine, unparsed);
+ return cacheString;
+ }
+
+ @Override
+ @NotNull
+ public XMaterial toXMaterial() {
+ return XMaterial.valueOf(asUnparsedString(""));
+ }
+}
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/model/Message.java b/messages/src/main/java/me/redtea/carcadex/messages/model/Message.java
new file mode 100644
index 0000000..8df3d69
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/model/Message.java
@@ -0,0 +1,79 @@
+package me.redtea.carcadex.messages.model;
+
+import com.cryptomorin.xseries.XMaterial;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.ComponentLike;
+import org.bukkit.Material;
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+
+/**
+ * Message
+ * This class provides message management methods. You can also convert it to Component.
+ * @since 1.0.0
+ * @author itzRedTea
+ */
+public interface Message extends ComponentLike {
+ /**
+ * Send message to CommandSender
+ * @param sender recipient of the message
+ */
+ void send(@NotNull CommandSender sender);
+
+ /**
+ * Replaces all matches in unparsed message
+ * @param from what you want to replace
+ * @param to what do you want to replace
+ * @return copy of the Message with the changes applied to it
+ */
+ @NotNull
+ Message replaceAll(@NotNull String from, @NotNull String to);
+
+ /**
+ * Convert message to list of components
+ * @return list of components
+ */
+ @NotNull
+ List asComponentList();
+ /**
+ * Get unparsed value of message
+ */
+ @NotNull
+ List asUnparsedStringList();
+ /**
+ * Get unparsed value of message joined to one string
+ */
+ @NotNull
+ String asUnparsedString(@NotNull String newLine);
+
+ /**
+ * Convert message to XMaterial
+ * @return found material
+ */
+ @NotNull
+ XMaterial toXMaterial();
+
+ /**
+ * @see Message#toXMaterial()
+ */
+ default Material toMaterial() {
+ return toXMaterial().parseMaterial();
+ }
+
+ /**
+ * @see Message#asUnparsedString(String) ()
+ */
+ default String asUnparsedString() {
+ return asUnparsedString("\n");
+ }
+
+ /**
+ * @see Message#replaceAll(String, String)
+ */
+ @NotNull
+ default Message replaceAll(@NotNull Object from, @NotNull Object to) {
+ return replaceAll(String.valueOf(from), String.valueOf(to));
+ }
+}
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/model/impl/ComponentMessage.java b/messages/src/main/java/me/redtea/carcadex/messages/model/impl/ComponentMessage.java
new file mode 100644
index 0000000..bf4abdc
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/model/impl/ComponentMessage.java
@@ -0,0 +1,31 @@
+package me.redtea.carcadex.messages.model.impl;
+
+import me.redtea.carcadex.messages.model.AbstractMessage;
+import me.redtea.carcadex.messages.model.Message;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.minimessage.MiniMessage;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class ComponentMessage extends AbstractMessage {
+ private static final MiniMessage mm = MiniMessage.builder().build();;
+ private final Plugin plugin;
+
+ public ComponentMessage(List unparsed, Plugin plugin) {
+ super(unparsed, plugin);
+ this.plugin = plugin;
+ }
+
+ @Override
+ protected Component parse(String unparsed) {
+ return mm.deserialize(unparsed);
+ }
+
+ @Override
+ public @NotNull Message replaceAll(@NotNull String from, @NotNull String to) {
+ return new ComponentMessage(unparsed.stream().map(it -> it.replaceAll(from, to)).collect(Collectors.toList()), plugin);
+ }
+}
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/model/impl/NullMessage.java b/messages/src/main/java/me/redtea/carcadex/messages/model/impl/NullMessage.java
new file mode 100644
index 0000000..4d1791b
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/model/impl/NullMessage.java
@@ -0,0 +1,47 @@
+package me.redtea.carcadex.messages.model.impl;
+
+import com.cryptomorin.xseries.XMaterial;
+import me.redtea.carcadex.messages.model.Message;
+import net.kyori.adventure.text.Component;
+import org.bukkit.command.CommandSender;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.ArrayList;
+import java.util.List;
+
+public final class NullMessage implements Message {
+ @Override
+ public void send(@NotNull CommandSender sender) {
+
+ }
+
+ @Override
+ public @NotNull Message replaceAll(@NotNull String from, @NotNull String to) {
+ return this;
+ }
+
+ @Override
+ public @NotNull List asComponentList() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public @NotNull List asUnparsedStringList() {
+ return new ArrayList<>();
+ }
+
+ @Override
+ public @NotNull String asUnparsedString(@NotNull String newLine) {
+ return "";
+ }
+
+ @Override
+ public @NotNull XMaterial toXMaterial() {
+ return XMaterial.AIR;
+ }
+
+ @Override
+ public @NotNull Component asComponent() {
+ return Component.text("");
+ }
+}
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/model/impl/serialized/SerializedMessage.java b/messages/src/main/java/me/redtea/carcadex/messages/model/impl/serialized/SerializedMessage.java
new file mode 100644
index 0000000..b6f103c
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/model/impl/serialized/SerializedMessage.java
@@ -0,0 +1,33 @@
+package me.redtea.carcadex.messages.model.impl.serialized;
+
+import me.redtea.carcadex.messages.model.AbstractMessage;
+import me.redtea.carcadex.messages.model.Message;
+import net.kyori.adventure.text.Component;
+import net.kyori.adventure.text.TextComponent;
+import net.kyori.adventure.text.serializer.ComponentSerializer;
+import org.bukkit.plugin.Plugin;
+import org.jetbrains.annotations.NotNull;
+
+import java.util.List;
+import java.util.stream.Collectors;
+
+public class SerializedMessage extends AbstractMessage {
+ private final ComponentSerializer serializer;
+ private final Plugin plugin;
+
+ public SerializedMessage(List unparsed, ComponentSerializer serializer, Plugin plugin) {
+ super(unparsed, plugin);
+ this.serializer = serializer;
+ this.plugin = plugin;
+ }
+
+ @Override
+ protected Component parse(String unparsed) {
+ return serializer.deserialize(unparsed);
+ }
+
+ @Override
+ public @NotNull Message replaceAll(@NotNull String from, @NotNull String to) {
+ return new SerializedMessage(unparsed.stream().map(it -> it.replaceAll(from, to)).collect(Collectors.toList()), serializer, plugin);
+ }
+}
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/model/impl/serialized/impl/LegacyMessage.java b/messages/src/main/java/me/redtea/carcadex/messages/model/impl/serialized/impl/LegacyMessage.java
new file mode 100644
index 0000000..a1fcf02
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/model/impl/serialized/impl/LegacyMessage.java
@@ -0,0 +1,13 @@
+package me.redtea.carcadex.messages.model.impl.serialized.impl;
+
+import me.redtea.carcadex.messages.model.impl.serialized.SerializedMessage;
+import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
+import org.bukkit.plugin.Plugin;
+
+import java.util.List;
+
+public class LegacyMessage extends SerializedMessage {
+ public LegacyMessage(List unparsed, Plugin plugin) {
+ super(unparsed, LegacyComponentSerializer.builder().build(), plugin);
+ }
+}
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/utils/MD5ColorUtils.java b/messages/src/main/java/me/redtea/carcadex/messages/utils/MD5ColorUtils.java
new file mode 100644
index 0000000..b27c6da
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/utils/MD5ColorUtils.java
@@ -0,0 +1,26 @@
+package me.redtea.carcadex.messages.utils;
+
+import net.md_5.bungee.api.ChatColor;
+
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+import static net.md_5.bungee.api.ChatColor.COLOR_CHAR;
+
+public class MD5ColorUtils {
+ private static final Pattern HEX_PATTERN = Pattern.compile("([a-f0-9]{6})");
+
+ public static String translateHexColorCodes(String message) {
+ Matcher matcher = HEX_PATTERN.matcher(message);
+ StringBuffer buffer = new StringBuffer(message.length() + 4 * 8);
+ while (matcher.find()) {
+ String group = matcher.group(1);
+ matcher.appendReplacement(buffer, COLOR_CHAR + "x"
+ + COLOR_CHAR + group.charAt(0) + COLOR_CHAR + group.charAt(1)
+ + COLOR_CHAR + group.charAt(2) + COLOR_CHAR + group.charAt(3)
+ + COLOR_CHAR + group.charAt(4) + COLOR_CHAR + group.charAt(5)
+ );
+ }
+ return ChatColor.translateAlternateColorCodes('&', matcher.appendTail(buffer).toString());
+ }
+}
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/verifier/MessageVerifier.java b/messages/src/main/java/me/redtea/carcadex/messages/verifier/MessageVerifier.java
new file mode 100644
index 0000000..4e7692a
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/verifier/MessageVerifier.java
@@ -0,0 +1,7 @@
+package me.redtea.carcadex.messages.verifier;
+
+import java.util.Optional;
+
+public interface MessageVerifier {
+ Optional fromDefault(String key);
+}
diff --git a/messages/src/main/java/me/redtea/carcadex/messages/verifier/impl/FileMessageVerifier.java b/messages/src/main/java/me/redtea/carcadex/messages/verifier/impl/FileMessageVerifier.java
new file mode 100644
index 0000000..3a089e9
--- /dev/null
+++ b/messages/src/main/java/me/redtea/carcadex/messages/verifier/impl/FileMessageVerifier.java
@@ -0,0 +1,39 @@
+package me.redtea.carcadex.messages.verifier.impl;
+
+import lombok.SneakyThrows;
+import me.redtea.carcadex.messages.verifier.MessageVerifier;
+import org.bukkit.configuration.ConfigurationSection;
+import org.bukkit.configuration.file.FileConfiguration;
+import org.bukkit.configuration.file.YamlConfiguration;
+
+import java.io.File;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.Optional;
+
+public class FileMessageVerifier implements MessageVerifier {
+ private final ConfigurationSection defaultSection;
+
+ private final FileConfiguration toSaveSection;
+ private final File toSaveFile;
+
+ @SneakyThrows
+ public FileMessageVerifier(InputStream defaultResource, File toSaveFile) {
+ defaultSection = YamlConfiguration.loadConfiguration(new InputStreamReader(defaultResource, "UTF-8"));
+ this.toSaveSection = YamlConfiguration.loadConfiguration(toSaveFile);
+ this.toSaveFile = toSaveFile;
+ }
+
+ @Override
+ public Optional fromDefault(String key) {
+ Optional o = Optional.ofNullable(defaultSection.get(key));
+ o.ifPresent(s -> saveToMain(key, s));
+ return o;
+ }
+
+ @SneakyThrows
+ public void saveToMain(String path, Object obj) {
+ toSaveSection.set(path, obj);
+ toSaveSection.save(toSaveFile);
+ }
+}
diff --git a/settings.gradle.kts b/settings.gradle.kts
index 07dc374..44eb7f6 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -4,3 +4,5 @@ include("architecture")
include("messages")
include("extensions")
include("serialization")
+include("genref")
+include("messages")