diff --git a/.github/workflows/gradle-publish.yml b/.github/workflows/gradle-publish.yml index cb4f06b..c4e4956 100644 --- a/.github/workflows/gradle-publish.yml +++ b/.github/workflows/gradle-publish.yml @@ -1,53 +1,146 @@ -name: Build and Release Plugin +name: Build and Release with Semantic Version on: push: branches: - - dev # Виконується тільки для гілки dev + - dev + - main jobs: - build: + build-and-release: runs-on: ubuntu-latest - steps: - # Клонування репозиторію - - name: Checkout repository + # ------------------------------------------------ + # 1. Клонуємо репозиторій + # ------------------------------------------------ + - name: Checkout uses: actions/checkout@v3 - # Налаштування JDK + # ------------------------------------------------ + # 2. Встановлюємо Java (для Gradle) + # ------------------------------------------------ - name: Set up JDK uses: actions/setup-java@v3 with: distribution: 'temurin' - java-version: '17' # Відповідає конфігурації Gradle + java-version: '17' + + # ------------------------------------------------ + # 3. Semantic Versioning (PaulHatch/semantic-version) + # ------------------------------------------------ + - name: Determine Version + id: version + uses: PaulHatch/semantic-version@v5.4.0 + with: + tag_prefix: v + major_pattern: /BREAKING CHANGE|feat!/ # Патерн для основного збільшення версії + minor_pattern: /feat:/ # Патерн для незначного збільшення версії + default_version: '0.1.0' + search_commit_body: true + bump_version: true # Автоматичне збільшення версії + + # ------------------------------------------------ + # 4. Формуємо тег і назву JAR (залежно від гілки) + # ------------------------------------------------ + - name: Prepare Tag Variables + id: prepare_tag + run: | + BRANCH_NAME="${GITHUB_REF_NAME}" # "dev" або "main" + RAW_VERSION="${{ steps.version.outputs.version }}" # Напр.: "1.2.3" + TAG_PREFIX="v" # Префікс для тегу, який визначили вище. - # Збірка проєкту Gradle + if [ "$BRANCH_NAME" = "dev" ]; then + # Пререліз для dev + NEW_TAG="${TAG_PREFIX}${RAW_VERSION}-dev" + IS_PRERELEASE="true" + JAR_NAME="BirthDay-${RAW_VERSION}-dev.jar" + RELEASE_NAME="Dev Release BirthDay ${RAW_VERSION}" + else + # Стабільна версія (main) + NEW_TAG="${TAG_PREFIX}${RAW_VERSION}" + IS_PRERELEASE="false" + JAR_NAME="BirthDay-${RAW_VERSION}.jar" + RELEASE_NAME="Release BirthDay ${RAW_VERSION}" + fi + + echo "new_tag=$NEW_TAG" >> $GITHUB_OUTPUT + echo "is_prerelease=$IS_PRERELEASE" >> $GITHUB_OUTPUT + echo "jar_name=$JAR_NAME" >> $GITHUB_OUTPUT + echo "release_name=$RELEASE_NAME" >> $GITHUB_OUTPUT + + # ------------------------------------------------ + # 5. Збираємо проєкт (Gradle) + # ------------------------------------------------ - name: Build with Gradle run: ./gradlew shadowJar - # Перевірка створення JAR - - name: Verify JAR file - run: ls build/libs/*.jar + # ------------------------------------------------ + # 6. Перейменовуємо JAR + # ------------------------------------------------ + - name: Rename JAR + run: | + FILE=$(find build/libs -name '*.jar' | head -n 1) + if [ -z "$FILE" ]; then + echo "No JAR file found!" + exit 1 + fi + mv "$FILE" "build/libs/${{ steps.prepare_tag.outputs.jar_name }}" + echo "Renamed to ${{ steps.prepare_tag.outputs.jar_name }}" + + # ------------------------------------------------ + # 7. Створюємо і пушимо тег + # ------------------------------------------------ + - name: Create and Push Tag + run: | + NEW_TAG="${{ steps.prepare_tag.outputs.new_tag }}" + echo "Creating and pushing tag: $NEW_TAG" - # Створення релізу + # Перевіряємо чи існує вже тег + git fetch --tags + if git rev-parse "$NEW_TAG" >/dev/null 2>&1; then + echo "Tag $NEW_TAG already exists, deleting..." + git tag -d "$NEW_TAG" + git push origin --delete "$NEW_TAG" + fi + + git config user.name "github-actions" + git config user.email "github-actions@github.com" + + git tag "$NEW_TAG" -m "Auto-bumped to $NEW_TAG" + git push origin "$NEW_TAG" + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + # ------------------------------------------------ + # 8. Створюємо GitHub Release + # ------------------------------------------------ - name: Create GitHub Release id: create_release uses: actions/create-release@v1 with: - tag_name: dev-${{ github.run_number }} # Унікальний тег для dev - release_name: Development Release ${{ github.run_number }} + tag_name: ${{ steps.prepare_tag.outputs.new_tag }} + release_name: ${{ steps.prepare_tag.outputs.release_name }} body: | - Це автоматичний реліз для гілки dev. + **Автоматичний реліз** з гілки `${{ github.ref_name }}` + + Версія: `${{ steps.version.outputs.version }}` + (за версією semantic-version): `${{ steps.version.outputs.version_type }}` + + JAR: `${{ steps.prepare_tag.outputs.jar_name }}` draft: false - prerelease: true + prerelease: ${{ steps.prepare_tag.outputs.is_prerelease }} env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - # Завантаження JAR у реліз + # ------------------------------------------------ + # 9. Завантажуємо JAR у реліз + # ------------------------------------------------ - name: Upload Plugin JAR uses: actions/upload-release-asset@v1 with: upload_url: ${{ steps.create_release.outputs.upload_url }} - asset_path: build/libs/*-all.jar # Файл із залежностями, зібраний ShadowJar - asset_name: ${{ github.repository }}-dev.jar # Назва репозиторію + "dev" + asset_path: build/libs/${{ steps.prepare_tag.outputs.jar_name }} + asset_name: ${{ steps.prepare_tag.outputs.jar_name }} asset_content_type: application/java-archive + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/README.md b/README.md new file mode 100644 index 0000000..36ec33f --- /dev/null +++ b/README.md @@ -0,0 +1,77 @@ +# Birthday Plugin + +A Minecraft plugin to celebrate players' birthdays with customizable features and integrations. + +--- + +## Features + +1. **Placeholders** + - Use `%birthday_prefix%` and `%birthday_date%` placeholders in other plugins to display a player's birthday prefix and date. + +2. **Integration with Oraxen and ItemAdder** + - Customize the birthday prefix using Oraxen or ItemAdder glyphs. + - Example: Use `(%oraxen_id%)` or directly insert a glyph symbol in the prefix. + +3. **Birthday Presents** + - Configure special gifts for players, delivered automatically on their birthdays. + - Gifts can include custom items, materials, or other rewards. + +4. **LuckPerms Integration** + - Assign temporary permissions or perks as birthday rewards. + - Example: Grant a donation rank or special privileges for a limited time. + +5. **Discord Support** + - Connect a Discord channel to receive birthday notifications. + - Example message: "🎉 Happy Birthday to [Player]! 🎂" + +--- + +## Commands and Permissions + +### Commands: +- **/birthday** + View and interact with your birthday data. + **Permission:** `birthday.use` + +- **/birthday reload** + Reload the plugin’s configuration files. + **Permission:** `birthday.reload` + +- **/birthday delete** + Delete a player's birthday data. + **Permission:** `birthday.delete` + +- **/birthday list** + List all stored birthday data on the server. + **Permission:** `birthday.list` + +- **/birthday present** + Manage the birthday present system. + **Permission:** `birthday.present` + + - **/birthday present open** + Add items to the birthday present configuration. + **Permission:** `birthday.present.open` + + - **/birthday present give [player]** + Give the configured birthday present to a player. + **Permission:** `birthday.present.give` + +--- + +## Integration Tips + +- **Placeholders:** Use placeholders with scoreboard or chat formatting plugins to dynamically display birthday-related information. +- **Oraxen/ItemAdder:** Add custom glyphs to make birthday prefixes unique and visually appealing. +- **LuckPerms:** Automatically grant temporary perks such as XP boosts, ranks, or permissions as part of the birthday celebration. +- **Discord Notifications:** Share birthday announcements with your community in a linked Discord channel. + +--- + +## Support + +For assistance, feature requests, or bug reports, please reach out to us on Discord. Bring joy to your server with the Birthday Plugin! 🎉 + + +DISCORD: https://discord.gg/eA4YhNQRJM diff --git a/build.gradle b/build.gradle index 40e2747..f6690b4 100644 --- a/build.gradle +++ b/build.gradle @@ -1,10 +1,10 @@ plugins { id 'java' - id 'com.github.johnrengelman.shadow' version '7.1.0' // Плагін для shading + id 'com.github.johnrengelman.shadow' version '8.1.1' // Плагін для shading } group = 'org.referix' -version = '1.0-SNAPSHOT' +version = '1.0.0' repositories { mavenCentral() @@ -23,22 +23,24 @@ repositories { maven { url = 'https://repo.extendedclip.com/content/repositories/placeholderapi/' } + maven { url 'https://repo.luckperms.net/' } } dependencies { compileOnly "io.papermc.paper:paper-api:1.20.1-R0.1-SNAPSHOT" + implementation 'org.bstats:bstats-bukkit:3.0.0' implementation 'com.mojang:authlib:1.5.21' - + compileOnly 'net.luckperms:api:5.4' compileOnly 'me.clip:placeholderapi:2.11.6' // JDBI Core implementation 'org.jdbi:jdbi3-core:3.38.0' // SQLite JDBC драйвер implementation 'org.xerial:sqlite-jdbc:3.45.1.0' + implementation 'com.squareup.okhttp3:okhttp:4.12.0' // Логування для JDBI (опціонально, для налагодження) - implementation 'org.jdbi:jdbi3-sqlobject:3.38.0' - runtimeOnly 'org.slf4j:slf4j-api:2.0.7' + } @@ -70,11 +72,17 @@ processResources { // ShadowJar конфігурація shadowJar { - archiveClassifier.set('') // Замінює стандартний JAR + archiveBaseName.set('BIRTHDAY') // Назва файлу + archiveVersion.set('1.0.0-DEV') // Версія файлу + archiveClassifier.set('') // Без класифікатора + mergeServiceFiles() minimize() exclude 'org/sqlite/native/**' + + // Переносим все зависимости в пространство имен org.referix.libs relocate 'org.jdbi', 'org.referix.libs.jdbi' relocate 'org.sqlite', 'org.referix.libs.sqlite' + relocate 'org.bstats', 'org.referix.libs.bstats' } diff --git a/src/main/java/org/referix/birthDayReload/BirthDayReload.java b/src/main/java/org/referix/birthDayReload/BirthDayReload.java index 801b1f6..40a13fb 100644 --- a/src/main/java/org/referix/birthDayReload/BirthDayReload.java +++ b/src/main/java/org/referix/birthDayReload/BirthDayReload.java @@ -1,94 +1,263 @@ package org.referix.birthDayReload; +import net.luckperms.api.LuckPerms; +import net.luckperms.api.LuckPermsProvider; +import org.bstats.bukkit.Metrics; import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.java.JavaPlugin; import org.referix.birthDayReload.command.MainCommand; import org.referix.birthDayReload.database.Database; -import org.referix.birthDayReload.inventory.YearInventory; +import org.referix.birthDayReload.discord.DiscordHttp; +import org.referix.birthDayReload.inventory.PresentInventory; import org.referix.birthDayReload.inventory.InventoryClickHandler; import org.referix.birthDayReload.inventory.InventoryManager; import org.referix.birthDayReload.papi.BirthdayPlaceholder; -import org.referix.birthDayReload.utils.LoggerUtils; +import org.referix.birthDayReload.utils.configmannagers.ConfigUtils; +import org.referix.birthDayReload.utils.configmannagers.DiscordConfig; +import org.referix.birthDayReload.utils.configmannagers.ItemManagerConfig; +import org.referix.birthDayReload.utils.configmannagers.MessageManager; +import org.referix.birthDayReload.utils.luckperm.LuckPerm; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Set; + +import static org.referix.birthDayReload.utils.LoggerUtils.log; +import static org.referix.birthDayReload.utils.LoggerUtils.logWarning; public final class BirthDayReload extends JavaPlugin { private static BirthDayReload instance; + private MessageManager messageManager; + + private ItemManagerConfig itemConfig; + + private LuckPerm luckPermUtils; + + private NamespacedKey textureKey; + private DiscordHttp discordHttp; + + private File configFile; @Override public void onEnable() { + // Завантаження та синхронізація конфігурації з дефолтним файлом + this.configFile = new File(getDataFolder(), "config.yml"); + if (!configFile.exists()) { + saveDefaultConfig(); // Зберігаємо дефолтний конфіг якщо його немає + } + + syncConfigWithDefaults(); // Викликаємо метод синхронізації конфігурацій + int pluginId = 24407; // <-- Replace with the id of your plugin! + Metrics metrics = new Metrics(this, pluginId); instance = this; - LoggerUtils.log("System initialization started..."); + log("System initialization started..."); - // Ініціалізація бази даних + // Инициализация базы данных try { - LoggerUtils.log("Starting DataBase BirthDayReload..."); + textureKey = new NamespacedKey(this, "custom_texture"); + log("Starting DataBase BirthDayReload..."); Database.getJdbi(); - LoggerUtils.log("Database initialized successfully."); + log("Database initialized successfully."); } catch (Exception e) { - LoggerUtils.log("Error starting DataBase BirthDayReload: " + e.getMessage()); - e.printStackTrace(); + log("Error starting DataBase BirthDayReload: " + e.getMessage()); getServer().getPluginManager().disablePlugin(this); - return; // Зупиняємо ініціалізацію плагіна + return; } - // Створення та реєстрація команди + // Загрузка конфигурации try { - LoggerUtils.log("Registering commands..."); + ConfigUtils configUtils = new ConfigUtils(this); + this.itemConfig = new ItemManagerConfig(this); // Инициализируем здесь + this.messageManager = new MessageManager(this); + this.itemConfig = new ItemManagerConfig(this); + log("ItemManagerConfig initialized: " + true); + } catch (Exception e) { + log("ItemManagerConfig is null: " + (itemConfig == null)); + log("Error loading config file: " + e.getMessage()); + getServer().getPluginManager().disablePlugin(this); + return; + } - // Створення інвентаря - LoggerUtils.log("Initializing CustomInventory..."); - YearInventory birthdayInventory = new YearInventory("Select Year your Birthday"); - InventoryManager.registerInventory(birthdayInventory); - LoggerUtils.log("CustomInventory initialized successfully."); + // Проверяем, доступен ли LuckPerms API + try { + LuckPerms luckPerms = LuckPermsProvider.get(); + getLogger().info("LuckPerms detected. Enabling related features."); + this.luckPermUtils = new LuckPerm(luckPerms,messageManager); + } catch (Exception e) { + this.luckPermUtils = null; + } - // Створення та реєстрація команди - LoggerUtils.log("Creating MainCommand instance..."); - new MainCommand("birthday", birthdayInventory); - LoggerUtils.log("MainCommand registered successfully as 'birthday'."); + try { + log("Try to start Discord Bot..."); + DiscordConfig discordSettings = new DiscordConfig(getConfig()); + if (discordSettings.isEnabled()) { + this.discordHttp = new DiscordHttp(discordSettings); +// discordManager.getMessageService().sendMessage("Плагин BirthDayReload успешно запущен!"); + + log("The Discord bot has been successfully started"); + } + } catch (Exception e) { + log("Error when launching the plugin: " + e.getMessage()); + } + + // Регистрация инвентарей и команды + try { + log("Registering commands..."); + + // Создаем инвентари + log("Initializing CustomInventory..."); + PresentInventory presentInventory = new PresentInventory("Present", 45, itemConfig); + InventoryManager.registerInventory(presentInventory); + log("CustomInventory initialized successfully."); + + // Создаем и регистрируем команды + log("Creating MainCommand instance..."); + new MainCommand("birthday", null, messageManager, presentInventory, discordHttp); + log("MainCommand registered successfully as 'birthday'."); - LoggerUtils.log("Commands registered successfully."); + log("Commands registered successfully."); } catch (Exception e) { - LoggerUtils.log("Error registering commands: " + e.getMessage()); + log("Error registering commands: " + e.getMessage()); e.printStackTrace(); getServer().getPluginManager().disablePlugin(this); return; } - // Перевірка наявності PlaceholderAPI + // Регистрация PlaceholderAPI if (Bukkit.getPluginManager().getPlugin("PlaceholderAPI") != null) { new BirthdayPlaceholder().register(); - getLogger().info("BirthdayPlaceholder successfully registered with PlaceholderAPI."); + log("BirthdayPlaceholder successfully registered with PlaceholderAPI."); } else { - getLogger().warning("PlaceholderAPI not found. BirthdayPlaceholder not registered."); + logWarning("PlaceholderAPI not found. BirthdayPlaceholder not registered."); } - - // Реєстрація Listener'ів + // Регистрация событий try { - LoggerUtils.log("Registering listeners..."); - // Реєстрація обробника кліків + log("Registering listeners..."); getServer().getPluginManager().registerEvents(new InventoryClickHandler(), this); - - // Створення та реєстрація кастомних інвентарів - getServer().getPluginManager().registerEvents(new MainListener(), this); - LoggerUtils.log("Listeners registered successfully."); + getServer().getPluginManager().registerEvents(new MainListener(textureKey,luckPermUtils,messageManager,discordHttp), this); + log("Listeners registered successfully."); } catch (Exception e) { - LoggerUtils.log("Error registering listeners: " + e.getMessage()); - e.printStackTrace(); + log("Error registering listeners: " + e.getMessage()); getServer().getPluginManager().disablePlugin(this); } - LoggerUtils.log("BirthDayReload successfully enabled!"); + log("BirthDayReload successfully enabled!"); } + @Override public void onDisable() { - // Plugin shutdown logic + discordHttp.close(); + } + + + + public LuckPerm getLuckPermUtils() { + return this.luckPermUtils; + } + + public NamespacedKey getTextureKey() { + return textureKey; + } + + public MessageManager getMessageManager() { + return messageManager; } public static BirthDayReload getInstance() { return instance; } + + + /** + * Синхронізація конфігурації на сервері з дефолтною конфігурацією + */ + public void syncConfigWithDefaults() { + try { + // Завантажуємо дефолтну конфігурацію з JAR + InputStream defaultConfigStream = getResource("config.yml"); + if (defaultConfigStream == null) { + getLogger().severe("Default config.yml is missing in the plugin JAR."); + return; + } + + // Завантажуємо конфігурацію з потоку + FileConfiguration defaultConfig = YamlConfiguration.loadConfiguration(new InputStreamReader(defaultConfigStream)); + getLogger().info("Default config loaded successfully from JAR."); + + // Завантажуємо конфігурацію, яка на сервері (з файлу) + FileConfiguration serverConfig = YamlConfiguration.loadConfiguration(configFile); + getLogger().info("Server config loaded successfully."); + + // Порівнюємо конфігурації та додаємо відсутні елементи з дефолтного конфігу в серверний + boolean updated = syncSections(defaultConfig, serverConfig, ""); + + if (updated) { + // Зберігаємо оновлений конфіг на сервері + serverConfig.save(configFile); + getLogger().info("Config.yml has been updated with missing default values."); + } else { + getLogger().info("Config.yml is already up-to-date."); + } + } catch (Exception e) { + getLogger().severe("Failed to sync config.yml with defaults: " + e.getMessage()); + } + } + + private boolean syncSections(FileConfiguration defaultConfig, FileConfiguration serverConfig, String path) { + boolean updated = false; + + // Логування для порівняння файлів + getLogger().info("Comparing default config file (from JAR): " + getDataFolder().getAbsolutePath() + "/config.yml"); + + // Перевіряємо всі ключі в секціях на всіх рівнях + for (String key : defaultConfig.getConfigurationSection(path).getKeys(true)) { + String fullPath = path.isEmpty() ? key : path + "." + key; + + // Логування для порівняння шляху + getLogger().info("Comparing path: " + fullPath); + + // Якщо це секція, перевіряємо, чи вона відсутня на сервері + if (defaultConfig.isConfigurationSection(fullPath)) { + if (!serverConfig.isConfigurationSection(fullPath)) { + serverConfig.createSection(fullPath); // Якщо секція відсутня — створюємо її + updated = true; + getLogger().info("Created missing section: " + fullPath); + } + updated |= syncSections(defaultConfig, serverConfig, fullPath); // Рекурсивно перевіряємо вкладені секції + } else { + // Якщо це ключ, перевіряємо його відсутність + if (!serverConfig.contains(fullPath)) { + serverConfig.set(fullPath, defaultConfig.get(fullPath)); // Додаємо відсутній ключ + updated = true; + getLogger().info("Added missing key: " + fullPath + " = " + defaultConfig.get(fullPath)); + } else { + getLogger().info("Key already exists: " + fullPath); + } + } + } + + return updated; + } + + + + + + + + + + } diff --git a/src/main/java/org/referix/birthDayReload/MainListener.java b/src/main/java/org/referix/birthDayReload/MainListener.java index 6064bd2..b646aac 100644 --- a/src/main/java/org/referix/birthDayReload/MainListener.java +++ b/src/main/java/org/referix/birthDayReload/MainListener.java @@ -1,12 +1,28 @@ package org.referix.birthDayReload; -import org.bukkit.entity.Player; + +import org.bukkit.*; +import org.bukkit.block.Block; +import org.bukkit.block.Skull; +import org.bukkit.entity.Firework; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; +import org.bukkit.event.block.BlockBreakEvent; +import org.bukkit.entity.Player; +import org.bukkit.event.block.BlockPlaceEvent; import org.bukkit.event.player.PlayerJoinEvent; import org.bukkit.event.player.PlayerQuitEvent; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.FireworkMeta; +import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.referix.birthDayReload.discord.DiscordHttp; +import org.referix.birthDayReload.inventory.InventoryManager; import org.referix.birthDayReload.playerdata.PlayerData; import org.referix.birthDayReload.playerdata.PlayerManager; +import org.referix.birthDayReload.utils.configmannagers.MessageManager; +import org.referix.birthDayReload.utils.luckperm.LuckPerm; import java.time.LocalDate; @@ -14,6 +30,19 @@ public class MainListener implements Listener { + private final NamespacedKey textureKey; + private final LuckPerm luckPerm; + private final MessageManager messageManager; + private final DiscordHttp discordHttp; + + + public MainListener(NamespacedKey textureKey, LuckPerm luckPerm, MessageManager messageManager , DiscordHttp discordHttp) { + this.textureKey = textureKey; + this.luckPerm = luckPerm; + this.messageManager = messageManager; + this.discordHttp = discordHttp; + } + @EventHandler public void onPlayerJoin(PlayerJoinEvent event) { Player player = event.getPlayer(); @@ -32,37 +61,115 @@ public void onPlayerJoin(PlayerJoinEvent event) { // Перевірка на день народження if (data.getBirthday() != null) { LocalDate today = LocalDate.now(); - LocalDate birthday = data.getBirthday(); - if (birthday.getDayOfMonth() == today.getDayOfMonth() && birthday.getMonth() == today.getMonth()) { + if (PlayerManager.getInstance().isBirthdayToday(data, today)) { if (!data.isWished()) { player.sendMessage("§6§lHappy Birthday, " + player.getName() + "! 🎉"); player.sendMessage("§aMay your day be filled with joy and celebration!"); - data.setWished(true); // Встановлюємо прапорець - - log("Birthday prefix and wish set for: " + player.getName()); - } else { - player.sendMessage("§eWelcome back and Happy Birthday once again! 🎂"); - data.setPrefix("Test Birthday"); // Встановлюємо префікс - manager.savePlayerData(player); // Зберігаємо дані +// Map placeholders = Map.of( +// "player", player.getName(), +// "date", data.getBirthday().toString() +// ); +// +// DiscordMessage discordMessage = messageManager.getParsedDiscordMessage("Discord.Embedded-messages.happy-birthday", placeholders); +// messageService.sendEmbed(discordMessage); + if (discordHttp != null) discordHttp.sendHappyBirthdayMessage(player.getName()); } + data.setPrefix(BirthDayReload.getInstance().getMessageManager().BIRTHDAY_BOY_PREFIX); // Встановлюємо префікс } else { // Скидання isWished, якщо сьогодні не день народження if (data.isWished()) { - data.setWished(false); + discordHttp.resetPlayerMessageStatus(player.getName()); + data.setIsWished(false); data.setPrefix(null); // Встановлюємо префікс manager.savePlayerData(player); - log("Birthday flag reset for: " + player.getName()); } } } } + @EventHandler public void onPlayerQuit(PlayerQuitEvent event) { Player player = event.getPlayer(); PlayerManager.getInstance().savePlayerData(player); log("Player data saved for: " + player.getName()); } + + //custom head present + @EventHandler + public void onBlockPlace(BlockPlaceEvent event) { + ItemStack item = event.getItemInHand(); + + if (item.getType() == Material.PLAYER_HEAD && item.hasItemMeta()) { + ItemMeta meta = item.getItemMeta(); + PersistentDataContainer itemData = meta.getPersistentDataContainer(); + + if (itemData.has(textureKey, PersistentDataType.STRING)) { + String texture = itemData.get(textureKey, PersistentDataType.STRING); + + + Block block = event.getBlockPlaced(); + if (block.getState() instanceof Skull) { + Skull skull = (Skull) block.getState(); + + + PersistentDataContainer blockData = skull.getPersistentDataContainer(); + blockData.set(textureKey, PersistentDataType.STRING, texture); + + skull.update(); + } + } + } + } + + + + + @EventHandler + public void onBlockBreak(BlockBreakEvent event) { + Block block = event.getBlock(); + if (block.getType() == Material.PLAYER_HEAD || block.getType() == Material.PLAYER_WALL_HEAD) { + Skull skull = (Skull) block.getState(); + PersistentDataContainer data = skull.getPersistentDataContainer(); + + if (data.has(textureKey, PersistentDataType.STRING)) { + Player player = event.getPlayer(); + event.setDropItems(false); + for (ItemStack item : InventoryManager.getInventory("Present").getInventory().getContents()) { + if (item != null) { + player.getInventory().addItem(item); + } + } + if (luckPerm != null) { + luckPerm.applyLuckPermGroup(player); + player.sendMessage(messageManager.BIRTHDAY_LUCKPERMS_MESSAGE); + } + Location center = block.getLocation().add(0.5, 0.5, 0.5); + + launchFireworks(center); + launchFireworks(center.clone().add(1, 0, 0)); + launchFireworks(center.clone().add(-1, 0, 0)); + launchFireworks(center.clone().add(0, 0, 1)); + launchFireworks(center.clone().add(0, 0, -1)); + } + } + } + + private void launchFireworks(Location loc) { + Firework firework = loc.getWorld().spawn(loc, Firework.class); + + FireworkMeta meta = firework.getFireworkMeta(); + meta.addEffect(FireworkEffect.builder() + .withColor(Color.RED, Color.ORANGE, Color.YELLOW) + .withFade(Color.BLUE) + .with(FireworkEffect.Type.BALL_LARGE) + .trail(true) + .flicker(true) + .build()); + meta.setPower(2); + firework.setFireworkMeta(meta); + } + } diff --git a/src/main/java/org/referix/birthDayReload/command/InventoryCommand.java b/src/main/java/org/referix/birthDayReload/command/InventoryCommand.java new file mode 100644 index 0000000..79ae15b --- /dev/null +++ b/src/main/java/org/referix/birthDayReload/command/InventoryCommand.java @@ -0,0 +1,106 @@ +package org.referix.birthDayReload.command; + +import net.kyori.adventure.text.Component; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.referix.birthDayReload.BirthDayReload; +import org.referix.birthDayReload.inventory.PresentInventory; +import org.referix.birthDayReload.playerdata.PlayerManager; +import org.referix.birthDayReload.playerdata.PlayerData; +import org.referix.birthDayReload.utils.headutils.CustomHeadUtil; + +import java.time.LocalDate; + +public class InventoryCommand { + + private final PresentInventory presentInventory; + + public InventoryCommand(PresentInventory presentInventory) { + this.presentInventory = presentInventory; + } + + public void handleInventory(CommandSender sender, String[] args) { + if (!(sender instanceof Player player)) { + sendMessage(sender, Component.text("§cOnly players can use this command.")); + return; + } + + if (!player.hasPermission("birthday.present")) { + sendMessage(player, Component.text("§cYou don't have permission to use this command.")); + return; + } + + if (args.length < 2) { + sendMessage(player, Component.text("§cUsage: /birthday present ")); + return; + } + + String action = args[1].toLowerCase(); + + switch (action) { + case "open": + handleOpen(player); + break; + + case "give": + handleGive(player); + break; + + default: + sendMessage(player, Component.text("§cUnknown action. Usage: /birthday present ")); + } + } + + private void handleOpen(Player player) { + presentInventory.open(player); // Открываем инвентарь + sendMessage(player, Component.text("§aYou have opened the birthday present inventory.")); + } + + private void handleGive(Player player) { + PlayerData playerData = PlayerManager.getInstance().getPlayerData(player); + + if (playerData == null) { + sendMessage(player, Component.text("§cYour data could not be loaded.")); + return; + } + + // Проверяем, совпадает ли дата рождения с сегодняшней + LocalDate today = LocalDate.now(); + if (!PlayerManager.getInstance().isBirthdayToday(playerData, today)) { + sendMessage(player, Component.text("§cIt's not your birthday today!")); + return; + } + + // Проверяем, может ли игрок получить подарок + if (playerData.isWished()) { + sendMessage(player, Component.text("§cYou have already received your birthday present.")); + return; + } + + // Передаем предметы из инвентаря игроку +// for (ItemStack item : presentInventory.getInventory().getContents()) { +// if (item != null) { +// player.getInventory().addItem(item); +// } +// } + ItemStack customHead = CustomHeadUtil.getNumberHead(1, BirthDayReload.getInstance().getTextureKey()); + player.getInventory().addItem(customHead); + + // Помечаем, что игрок получил подарок + playerData.setIsWished(true); + PlayerManager.getInstance().savePlayerData(player); + + sendMessage(player, Component.text("§aYou have received your birthday present!")); + } + + + + private void sendMessage(CommandSender sender, Component message) { + if (sender instanceof Player player) { + player.sendMessage(message); + } else { + sender.sendMessage(message); + } + } +} diff --git a/src/main/java/org/referix/birthDayReload/command/MainCommand.java b/src/main/java/org/referix/birthDayReload/command/MainCommand.java index 98db5f0..c211880 100644 --- a/src/main/java/org/referix/birthDayReload/command/MainCommand.java +++ b/src/main/java/org/referix/birthDayReload/command/MainCommand.java @@ -1,24 +1,41 @@ package org.referix.birthDayReload.command; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.Bukkit; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; +import org.referix.birthDayReload.BirthDayReload; +import org.referix.birthDayReload.discord.DiscordHttp; +import org.referix.birthDayReload.inventory.PresentInventory; import org.referix.birthDayReload.inventory.YearInventory; import org.referix.birthDayReload.playerdata.PlayerData; import org.referix.birthDayReload.playerdata.PlayerManager; +import org.referix.birthDayReload.utils.configmannagers.MessageManager; import java.time.LocalDate; +import java.time.format.DateTimeParseException; +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.Map; + +import static org.referix.birthDayReload.utils.LoggerUtils.*; + public class MainCommand extends AbstractCommand { - private YearInventory inventoryData; + private final MessageManager messageManager; + InventoryCommand inventoryCommand; + DiscordHttp discordHttp; - public MainCommand(String command, YearInventory inventoryData) { + public MainCommand(String command, YearInventory birthdayInventory, MessageManager messageManager, PresentInventory presentInventory, DiscordHttp discordHttp) { super(command); - this.inventoryData = inventoryData; + this.messageManager = messageManager; + inventoryCommand = new InventoryCommand(presentInventory); + this.discordHttp = discordHttp; } - @Override public boolean execute(CommandSender sender, String label, String[] args) { if (args.length < 1) { @@ -40,74 +57,120 @@ public boolean execute(CommandSender sender, String label, String[] args) { case "help": sendHelp(sender); break; + case "reload": + handleReload(sender); + break; + case "present": + inventoryCommand.handleInventory(sender, args); + break; default: - sender.sendMessage("§cUnknown subcommand. Use /birthday help for more information."); + sendMessage(sender, messageManager.BIRTHDAY_UNKNOWN_COMMAND); } return true; } + + // -- reload -- + private void handleReload(CommandSender sender) { + if (!sender.hasPermission("birthday.reload")) { + sendMessage(sender, Component.text("§cYou don't have permission to reload the plugin.")); + return; + } + + try { + BirthDayReload.getInstance().reloadConfig(); + messageManager.reloadMessages(); + PlayerManager.getInstance().updateBirthdayPrefixes(); + sendMessage(sender, Component.text("§aPlugin configuration and messages reloaded successfully.")); + } catch (Exception e) { + sendMessage(sender, Component.text("§cAn error occurred while reloading the plugin. Check the console for details.")); + logSevere(e.getMessage()); + } + } + + + private void handleSet(CommandSender sender, String[] args) { - if (!(sender instanceof Player)) { - sender.sendMessage("§cOnly players can set their own birthday."); + if (!(sender instanceof Player player)) { + sendMessage(sender, messageManager.BIRTHDAY_ONLY_PLAYERS); + return; + } + + if (args.length != 2) { + sendMessage(player, messageManager.BIRTHDAY_SET_FORMAT_ERROR + .replaceText(builder -> builder.match("%date%").replacement(messageManager.getDateFormat()))); return; } - if (args.length != 2) { // Перевіряємо, чи аргумент містить дату - sender.sendMessage("§cUsage: /birthday set "); + PlayerData data = PlayerManager.getInstance().getPlayerData(player); + + if (data.getBirthday() != null) { + sendMessage(player, messageManager.BIRTHDAY_ALREADY_SET + .replaceText(builder -> builder.match("%date%") + .replacement(messageManager.formatDate(data.getBirthday())))); return; } - Player player = (Player) sender; String dateInput = args[1]; try { - LocalDate birthday = LocalDate.parse(dateInput); // Конвертуємо аргумент у LocalDate - // if (isValidBirthday(birthday)) { // Валідність дати (не в майбутньому) - // Збереження дати в PlayerData - PlayerData data = PlayerManager.getInstance().getPlayerData(player); + LocalDate birthday = messageManager.parseDate(dateInput); + if (isValidBirthday(birthday)) { data.setBirthday(birthday); PlayerManager.getInstance().savePlayerData(player); - player.sendMessage("§aYour birthday has been set to: " + birthday); -// } else { -// player.sendMessage("§cInvalid date. The birthday cannot be in the future."); -// } - } catch (Exception e) { - player.sendMessage("§cInvalid date format. Use yyyy-mm-dd."); + sendMessage(player, messageManager.BIRTHDAY_SET_SUCCESS + .replaceText(builder -> builder.match("%date%") + .replacement(messageManager.formatDate(birthday)))); + + if (discordHttp != null) discordHttp.sendSetBirthdayMessage(player.getName(),data.getBirthday().toString()); + + + } else { + sendMessage(player, messageManager.BIRTHDAY_SET_FUTURE_ERROR); + } + } catch (DateTimeParseException e) { + sendMessage(player, messageManager.BIRTHDAY_SET_FORMAT_ERROR + .replaceText(builder -> builder.match("%date%").replacement(messageManager.getDateFormat()))); } } -// // Метод перевірки валідності дати -// private boolean isValidBirthday(LocalDate date) { -// return !date.isAfter(LocalDate.now()); // Дата не повинна бути в майбутньому -// } -// try { -// inventoryData.open(player); -// } catch (Exception e) { -// sender.sendMessage("§cInvalid date format. Use yyyy-mm-dd."); -// } + private boolean isValidBirthday(LocalDate date) { + // Дата має бути сьогодні або в минулому + return !date.isAfter(LocalDate.now()); + } + + + + private void handleDelete(CommandSender sender, String[] args) { if (!sender.hasPermission("birthday.delete")) { - sender.sendMessage("§cYou don't have permission to use this command."); + sendMessage(sender, messageManager.BIRTHDAY_DELETE_NO_PERMISSION); return; } if (args.length != 2) { - sender.sendMessage("§cUsage: /birthday delete "); + sendMessage(sender, messageManager.BIRTHDAY_DELETE_USAGE); return; } Player target = Bukkit.getPlayer(args[1]); if (target == null) { - sender.sendMessage("§cPlayer not found."); + sendMessage(sender, messageManager.BIRTHDAY_DELETE_PLAYER_NOT_FOUND); return; } + Player player = (Player) sender; + PlayerManager.getInstance().removePlayerData(target); - sender.sendMessage("§aBirthday data for " + target.getName() + " has been deleted."); + sendMessage(sender, messageManager.BIRTHDAY_DELETE_SUCCESS + .replaceText(builder -> builder.match("%player%").replacement(target.getName()))); + + if (discordHttp != null) discordHttp.sendAdminDeleteBirthdayMessage(player.getName(),target.getName()); + } private void handleList(CommandSender sender, String[] args) { @@ -115,13 +178,11 @@ private void handleList(CommandSender sender, String[] args) { sender.sendMessage("§cYou don't have permission to use this command."); return; } - sender.sendMessage("§aList of all players' birthdays:"); PlayerManager.getInstance().getAllPlayerData().forEach((player, data) -> { sender.sendMessage("§7" + data.getPlayer().getName() + ": §e" + data.getBirthday()); }); } - private void sendHelp(CommandSender sender) { sender.sendMessage("§a===== §6Birthday Commands §a====="); sender.sendMessage("§e/birthday set §7- Set your birthday."); @@ -134,4 +195,92 @@ private void sendHelp(CommandSender sender) { sender.sendMessage("§e/birthday help §7- Show this help message."); sender.sendMessage("§a================================"); } + + private void sendMessage(CommandSender sender, Component message) { + if (message == null) { + logWarning("Attempted to send a null message to: " + sender.getName()); + return; + } + + if (sender instanceof Player player) { + log("Sending message to player: " + player.getName()); + player.sendMessage(message); + } else { + log("Sending message to console..."); + String serializedMessage = LegacyComponentSerializer.legacySection().serialize(message); + sender.sendMessage(serializedMessage); + } + } + + + private void sendMessage(CommandSender sender, String message) { + sender.sendMessage(message); + } + + + @Override + public List complete(CommandSender sender, String[] args) { + List completions = new ArrayList<>(); + + if (args.length == 1) { + // Основные подкоманды + completions.add("set"); + if (sender.hasPermission("birthday.delete")) completions.add("delete"); + if (sender.hasPermission("birthday.list")) completions.add("list"); + if (sender.hasPermission("birthday.reload")) completions.add("reload"); + if (sender.hasPermission("birthday.present")) completions.add("present"); + + return filterSuggestions(completions, args[0]); + } + + if (args.length == 2) { + String subCommand = args[0].toLowerCase(); + + switch (subCommand) { + case "set": + // Підказка для команди "set" + completions.add(messageManager.getDateFormat()); + break; + + + case "delete": + if (sender.hasPermission("birthday.delete")) { + // Предлагаем список онлайн игроков для удаления + return filterSuggestions(Bukkit.getOnlinePlayers().stream() + .map(Player::getName) + .collect(Collectors.toList()), args[1]); + } + break; + + case "present": + if (sender.hasPermission("birthday.present")) { + // Подкоманды для управления инвентарем + if(sender.hasPermission("birthday.present.open")) completions.add("open"); + if(sender.hasPermission("birthday.present.give")) completions.add("give"); + } + break; + + default: + break; + } + } + + if (args.length == 3 && args[0].equalsIgnoreCase("present") && args[1].equalsIgnoreCase("give")) { + if (sender.hasPermission("birthday.inventory")) { + // Список игроков для передачи подарка + return filterSuggestions(Bukkit.getOnlinePlayers().stream() + .map(Player::getName) + .collect(Collectors.toList()), args[2]); + } + } + + return completions; + } + + + private List filterSuggestions(List completions, String input) { + return completions.stream() + .filter(suggestion -> suggestion.toLowerCase().startsWith(input.toLowerCase())) + .collect(Collectors.toList()); + } } diff --git a/src/main/java/org/referix/birthDayReload/discord/DiscordHttp.java b/src/main/java/org/referix/birthDayReload/discord/DiscordHttp.java new file mode 100644 index 0000000..2f887ff --- /dev/null +++ b/src/main/java/org/referix/birthDayReload/discord/DiscordHttp.java @@ -0,0 +1,178 @@ +package org.referix.birthDayReload.discord; + +import okhttp3.*; +import org.referix.birthDayReload.utils.configmannagers.DiscordConfig; + +import javax.print.DocFlavor; +import java.io.IOException; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.HashMap; +import java.util.Map; + +public class DiscordHttp { + + private final String botToken; + private final String channelId; + private final OkHttpClient client; + private boolean enabled; // Стан бота + private final DiscordConfig config; // Конфігурація Discord + + private final Map playerMessageStatus = new HashMap<>(); + + + // Конструктор для ініціалізації через DiscordConfig + public DiscordHttp(DiscordConfig config) { + this.botToken = config.getToken(); + this.channelId = config.getChannelId(); + this.client = new OkHttpClient(); + this.enabled = config.isEnabled(); + this.config = config; + } + + public void sendSetBirthdayMessage(String player, String date) { + if (!config.isEnableSetBirthdayMessage()) return; + if (!enabled) { + return; + } + + // Форматування дати у більш простий формат + String formattedDate = formatDate(date); + + String title = config.getSetBirthdayTitle().replace("%player%", player); + String description = String.join("\n", config.getSetBirthdayMessage()) + .replace("%player%", player) + .replace("%date%", formattedDate) + .replace("\n", "\\n"); // Заміняємо символи нового рядка + + + int color = parseColor(config.getSetBirthdayColor()); + + + + sendEmbedMessage(title, description, color); + } + + // Відправлення повідомлення "Happy Birthday" + public void sendHappyBirthdayMessage(String player) { + // Перевірка, чи повідомлення вже було відправлено + if (!config.isEnableHappyBirthdayMessage()) return; + if (playerMessageStatus.getOrDefault(player, false)) { + + return; // Якщо повідомлення вже відправлено, не відправляємо знову + } + + if (!enabled) { + System.err.println("Discord-бот вимкнений. Повідомлення не буде відправлено."); + return; + } + + // Формування заголовку та опису для повідомлення + String title = config.getHappyBirthdayTitle().replace("%player%", player); + String description = String.join("\n", config.getHappyBirthdayMessage()) + .replace("%player%", player); + + int color = parseColor(config.getHappyBirthdayColor()); + + + + // Відправка повідомлення через Discord + sendEmbedMessage(title, description, color); + + // Оновлення статусу відправки повідомлення для цього гравця + playerMessageStatus.put(player, true); + } + + public void resetPlayerMessageStatus(String playerName) { + playerMessageStatus.put(playerName, false); // Скидаємо статус відправлення + } + + // Відправлення повідомлення "Admin Delete Birthday" + public void sendAdminDeleteBirthdayMessage(String adminPlayer, String targetPlayer) { + if (!config.isEnableDeleteBirthdayMessage()) return; + if (!enabled) { + return; + } + + String title = config.getAdminDeleteBirthdayTitle().replace("%target_player%", targetPlayer); + String description = String.join("\n", config.getAdminDeleteBirthdayMessage()) + .replace("%player%", adminPlayer) + .replace("%target_player%", targetPlayer); + + int color = parseColor(config.getAdminDeleteBirthdayColor()); + sendEmbedMessage(title, description, color); + } + + // Приватний метод для відправлення вбудованого повідомлення + private void sendEmbedMessage(String title, String description, int color) { + String embedJson = String.format(""" + { + "embeds": [ + { + "title": "%s", + "description": "%s", + "color": %d + } + ] + } + """, title, description, color); + sendRequest(embedJson); + } + + // Приватний метод для виконання HTTP-запиту + private void sendRequest(String json) { + RequestBody body = RequestBody.create( + MediaType.parse("application/json"), + json + ); + + Request request = new Request.Builder() + .url("https://discord.com/api/v10/channels/" + channelId + "/messages") + .post(body) + .addHeader("Authorization", "Bot " + botToken) + .addHeader("Content-Type", "application/json") + .build(); + + try (Response response = client.newCall(request).execute()) { + if (response.isSuccessful()) { + System.out.println("Message sent successfully!"); + } else { + System.err.println("Failed to send message. Error: " + response.code()); + System.err.println(response.body().string()); + } + } catch (IOException e) { + e.printStackTrace(); + } + } + + // Перетворення кольору з HEX у ціле число + private int parseColor(String hexColor) { + try { + return Integer.parseInt(hexColor.replace("#", ""), 16); + } catch (NumberFormatException e) { + System.err.println("Invalid color format: " + hexColor + ". Defaulting to white."); + return 0xFFFFFF; + } + } + private String formatDate(String date) { + try { + // Якщо дата у вигляді "2000-01-07", спробуємо перетворити в "07.01.2000" + SimpleDateFormat inputFormat = new SimpleDateFormat("yyyy-MM-dd"); + Date parsedDate = inputFormat.parse(date); + SimpleDateFormat outputFormat = new SimpleDateFormat("dd.MM.yyyy"); + return outputFormat.format(parsedDate); + } catch (Exception e) { + e.printStackTrace(); + return date; // Повертаємо оригінальну дату, якщо не вдалося відформатувати + } + } + + + public void close() { + if (client != null) { + client.dispatcher().executorService().shutdown(); // Зупинка потоків + client.connectionPool().evictAll(); // Очищення пулу з'єднань + } + } + +} diff --git a/src/main/java/org/referix/birthDayReload/inventory/PresentInventory.java b/src/main/java/org/referix/birthDayReload/inventory/PresentInventory.java new file mode 100644 index 0000000..5da65d0 --- /dev/null +++ b/src/main/java/org/referix/birthDayReload/inventory/PresentInventory.java @@ -0,0 +1,68 @@ +package org.referix.birthDayReload.inventory; + +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryCloseEvent; +import org.bukkit.inventory.ItemStack; +import org.referix.birthDayReload.utils.configmannagers.ItemManagerConfig; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class PresentInventory extends BaseInventory { + + private final ItemManagerConfig itemManagerConfig; + + public PresentInventory(String title, int size, ItemManagerConfig itemManagerConfig) { + super(title, size); + this.itemManagerConfig = itemManagerConfig; + + // Загружаем предметы из конфигурации + loadItems(); + } + + @Override + protected void setupInventory() { + } + + @Override + public void onInventoryClick(InventoryClickEvent event) { + } + + @Override + public void onInventoryClose(InventoryCloseEvent event) { + // Сохраняем предметы при закрытии инвентаря + saveItems(); + } + + private void loadItems() { + // Загружаем предметы из конфигурации + List items = itemManagerConfig.getItems(); + if (items == null || items.isEmpty()) return; + for (int i = 0; i < items.size() && i < inventory.getSize(); i++) { + inventory.setItem(i, items.get(i)); + } + } + + private void saveItems() { + if (itemManagerConfig == null) { + throw new IllegalStateException("ItemManagerConfig is not initialized!"); + } + + // Фильтруем содержимое инвентаря, исключая null + List items = new ArrayList<>(); + for (ItemStack item : inventory.getContents()) { + if (item != null) { + items.add(item); + } + } + + // Сохраняем только если список не пустой + if (items.isEmpty()) { + itemManagerConfig.saveItems(Collections.emptyList()); + } else { + itemManagerConfig.saveItems(items); + } + } + +} diff --git a/src/main/java/org/referix/birthDayReload/inventory/YearInventory.java b/src/main/java/org/referix/birthDayReload/inventory/YearInventory.java index 5d63671..037cbcb 100644 --- a/src/main/java/org/referix/birthDayReload/inventory/YearInventory.java +++ b/src/main/java/org/referix/birthDayReload/inventory/YearInventory.java @@ -4,7 +4,8 @@ import org.bukkit.event.inventory.InventoryClickEvent; import org.bukkit.event.inventory.InventoryCloseEvent; import org.bukkit.inventory.ItemStack; -import org.referix.birthDayReload.utils.CustomHeadUtil; +import org.referix.birthDayReload.BirthDayReload; +import org.referix.birthDayReload.utils.headutils.CustomHeadUtil; import java.util.Calendar; import java.util.LinkedList; @@ -24,7 +25,7 @@ public YearInventory(String title) { protected void setupInventory() { Map numberTextures = CustomHeadUtil.getAllNumberTextures(); for (Integer number : numberTextures.keySet()) { - ItemStack head = CustomHeadUtil.getNumberHead(number); + ItemStack head = CustomHeadUtil.getNumberHead(number, BirthDayReload.getInstance().getTextureKey()); inventory.setItem(number, head); // Вставляємо голову у відповідний слот } } diff --git a/src/main/java/org/referix/birthDayReload/papi/BirthdayPlaceholder.java b/src/main/java/org/referix/birthDayReload/papi/BirthdayPlaceholder.java index c668b51..3f586e9 100644 --- a/src/main/java/org/referix/birthDayReload/papi/BirthdayPlaceholder.java +++ b/src/main/java/org/referix/birthDayReload/papi/BirthdayPlaceholder.java @@ -1,6 +1,9 @@ package org.referix.birthDayReload.papi; +import me.clip.placeholderapi.PlaceholderAPI; import me.clip.placeholderapi.expansion.PlaceholderExpansion; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; import org.bukkit.entity.Player; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -16,7 +19,7 @@ public class BirthdayPlaceholder extends PlaceholderExpansion { @Override public @NotNull String getIdentifier() { - return "birthday"; // Префікс плейсхолдера + return "birthday"; // Префикс плейсхолдера } @Override @@ -36,7 +39,7 @@ public boolean canRegister() { @Override public boolean persist() { - return true; // Не реєструвати плейсхолдер після перезавантаження + return true; // Плейсхолдер будет сохраняться между перезагрузками } @Override @@ -46,33 +49,34 @@ public boolean persist() { } PlayerData data = PlayerManager.getInstance().getPlayerData(player); + if (data == null) { + return ""; + } - // Плейсхолдери - switch (identifier) { - case "date": // %birthday_date% - LocalDate birthday = data.getBirthday(); - return (birthday != null) ? birthday.format(DATE_FORMAT) : "Not set"; - - case "is_today": // %birthday_is_today% - LocalDate today = LocalDate.now(); - if (data.getBirthday() != null && data.getBirthday().getMonth() == today.getMonth() && - data.getBirthday().getDayOfMonth() == today.getDayOfMonth()) { - return "Yes"; - } - return "No"; - - case "wished": // %birthday_wished% - return data.isWished() ? "Yes" : "No"; - - case "prefix": { + LocalDate today = LocalDate.now(); + + return switch (identifier) { + case "date" -> // %birthday_date% + (data.getBirthday() != null) ? data.getBirthday().format(DATE_FORMAT) : "Not set"; + case "is_today" -> // %birthday_is_today% + (data.getBirthday() != null && + data.getBirthday().getMonth() == today.getMonth() && + data.getBirthday().getDayOfMonth() == today.getDayOfMonth()) ? "Yes" : "No"; + case "wished" -> // %birthday_wished% + data.isWished() ? "Yes" : "No"; + case "prefix" -> { if (data.getPrefix() != null) { - return data.getPrefix(); + Component prefixComponent = data.getPrefix(); + // Сериализуем компонент в строку + String rawPrefix = LegacyComponentSerializer.legacyAmpersand().serialize(prefixComponent); + // Обрабатываем вложенные плейсхолдеры (если есть) + String resolvedPrefix = PlaceholderAPI.setPlaceholders(player, rawPrefix); + yield resolvedPrefix + "&r"; // Возвращаем обработанную строку } - return ""; - } // %birthday_prefix% + yield ""; + } - default: - return null; - } + default -> null; + }; } } diff --git a/src/main/java/org/referix/birthDayReload/playerdata/PlayerData.java b/src/main/java/org/referix/birthDayReload/playerdata/PlayerData.java index 593fd64..c87e141 100644 --- a/src/main/java/org/referix/birthDayReload/playerdata/PlayerData.java +++ b/src/main/java/org/referix/birthDayReload/playerdata/PlayerData.java @@ -1,5 +1,6 @@ package org.referix.birthDayReload.playerdata; +import net.kyori.adventure.text.Component; import org.bukkit.entity.Player; import java.time.LocalDate; @@ -10,12 +11,13 @@ public class PlayerData { private Player player; private LocalDate birthday; + private boolean isWished; - private List Wished; - private String prefix; + private List Wished; + private Component prefix; - public PlayerData(Player player, LocalDate birthday, boolean isWished, List wished , String prefix) { + public PlayerData(Player player, LocalDate birthday, boolean isWished, List wished , Component prefix) { this.player = player; this.birthday = birthday; this.isWished = isWished; @@ -32,7 +34,7 @@ public PlayerData(Player player) { } - public String getPrefix() { + public Component getPrefix() { return prefix; } @@ -45,6 +47,7 @@ public boolean isWished() { return isWished; } + public Player getPlayer() { return player; } @@ -57,7 +60,7 @@ public void setBirthday(LocalDate birthday) { this.birthday = birthday; } - public void setPrefix(String prefix) { + public void setPrefix(Component prefix) { this.prefix = prefix; } @@ -65,11 +68,11 @@ public void setPlayer(Player player) { this.player = player; } - public void setWished(boolean wished) { + public void setIsWished(boolean wished) { isWished = wished; } - public void setWished(List wished) { + public void setIsWished(List wished) { Wished = wished; } } diff --git a/src/main/java/org/referix/birthDayReload/playerdata/PlayerManager.java b/src/main/java/org/referix/birthDayReload/playerdata/PlayerManager.java index fca5b23..80a3e08 100644 --- a/src/main/java/org/referix/birthDayReload/playerdata/PlayerManager.java +++ b/src/main/java/org/referix/birthDayReload/playerdata/PlayerManager.java @@ -1,9 +1,11 @@ package org.referix.birthDayReload.playerdata; import org.bukkit.entity.Player; +import org.referix.birthDayReload.BirthDayReload; import org.referix.birthDayReload.database.Database; import org.referix.birthDayReload.database.PlayerDataDAO; +import java.time.LocalDate; import java.util.Collections; import java.util.HashMap; import java.util.List; @@ -53,4 +55,27 @@ public Map getAllPlayerData() { public List getAllPlayerDataFromDatabase() { return dao.loadAllPlayerData(); } + + + public void updateBirthdayPrefixes() { + LocalDate today = LocalDate.now(); + + for (PlayerData playerData : getAllPlayerData().values()) { + if (isBirthdayToday(playerData, today)) { + // Установить префикс "Birthday Boy" для игрока + playerData.setPrefix(BirthDayReload.getInstance().getMessageManager().BIRTHDAY_BOY_PREFIX); + savePlayerData(playerData.getPlayer()); + } + } + } + + // Проверка, совпадает ли дата рождения игрока с текущей датой + public boolean isBirthdayToday(PlayerData playerData, LocalDate today) { + LocalDate birthday = playerData.getBirthday(); + + // Проверяем, что дата рождения задана, и сравниваем месяц и день + return birthday != null && + birthday.getMonth() == today.getMonth() && + birthday.getDayOfMonth() == today.getDayOfMonth(); + } } diff --git a/src/main/java/org/referix/birthDayReload/playerdata/PlayerRunnable.java b/src/main/java/org/referix/birthDayReload/playerdata/PlayerRunnable.java new file mode 100644 index 0000000..4e8535f --- /dev/null +++ b/src/main/java/org/referix/birthDayReload/playerdata/PlayerRunnable.java @@ -0,0 +1,34 @@ +package org.referix.birthDayReload.playerdata; + +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.scheduler.BukkitRunnable; + +import java.time.LocalTime; + +public class PlayerRunnable extends BukkitRunnable { + + private final JavaPlugin plugin; + + public PlayerRunnable(JavaPlugin plugin) { + this.plugin = plugin; + } + + @Override + public void run() { + // Виконати перевірку всіх гравців на день народження + PlayerManager.getInstance().updateBirthdayPrefixes(); + } + + public void scheduleAtMidnight() { + LocalTime now = LocalTime.now(); + LocalTime midnight = LocalTime.MIDNIGHT; + + // Обчислюємо затримку до 00:00 + long delaySeconds = now.isBefore(midnight) ? + midnight.toSecondOfDay() - now.toSecondOfDay() : + 24 * 60 * 60 - now.toSecondOfDay(); + + // Плануємо виконання задачі раз на добу + this.runTaskTimer(plugin, delaySeconds * 20L, 24 * 60 * 60 * 20L); // Затримка і період в тиках + } +} diff --git a/src/main/java/org/referix/birthDayReload/utils/CustomHeadUtil.java b/src/main/java/org/referix/birthDayReload/utils/CustomHeadUtil.java deleted file mode 100644 index 56fa509..0000000 --- a/src/main/java/org/referix/birthDayReload/utils/CustomHeadUtil.java +++ /dev/null @@ -1,90 +0,0 @@ -package org.referix.birthDayReload.utils; - -import com.mojang.authlib.GameProfile; -import com.mojang.authlib.properties.Property; -import org.bukkit.Material; -import org.bukkit.entity.Player; -import org.bukkit.inventory.ItemStack; -import org.bukkit.inventory.meta.SkullMeta; - -import java.lang.reflect.Field; -import java.util.HashMap; -import java.util.Map; -import java.util.UUID; - -public class CustomHeadUtil { - - private static final Map numberTextures = new HashMap<>(); - - public static ItemStack getNumberHead(int number) { - String texture = getNumberTexture(number); - if (texture == null) { - throw new IllegalArgumentException("Invalid number: " + number); - } - - ItemStack head = new ItemStack(Material.PLAYER_HEAD); - SkullMeta meta = (SkullMeta) head.getItemMeta(); - - if (meta != null) { - GameProfile profile = new GameProfile(UUID.randomUUID(), "name"); // Ім'я профілю не null - profile.getProperties().put("textures", new Property("textures", texture)); - - try { - Field profileField = meta.getClass().getDeclaredField("profile"); - profileField.setAccessible(true); - profileField.set(meta, profile); - } catch (NoSuchFieldException | IllegalAccessException e) { - e.printStackTrace(); - } - meta.setDisplayName(" "); - head.setItemMeta(meta); - } - - return head; - } - - - static { - numberTextures.put(8, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTg3NDQ0ZWQ2ZDg0NTY5MDExNWIzODU4OGNmMWEyNjQ0ZGE5YzcxZTZmMzFhZTI0NjUxMDM4Y2IxZDQ3NmVhIn19fQ=="); - numberTextures.put(9, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOTg3NDQ0ZWQ2ZDg0NTY5MDExNWIzODU4OGNmMWEyNjQ0ZGE5YzcxZTZmMzFhZTI0NjUxMDM4Y2IxZDQ3NmVhIn19fQ=="); - numberTextures.put(10, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvOWU0NGI3MDdiMjA1YjdkMjNlNDBhM2MwZGYxOWIwOWI2MmQwMjc4MDcyNzhlZjMwMWIzYzJhYjk4YjRmMGQ2In19fQ=="); - numberTextures.put(11, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTNmZDZjMjI1OTk4YzJkYjRhNGFmZGRkMjRhOTNjNmYxY2YzNDk5MTRhYTNjNWExNTI1Y2M1NTg3M2IzMGQ3MiJ9fX0="); - numberTextures.put(12, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2YzMTg3YTVhMGFhN2FiNDg5MzdjN2NmOTE5ODgxZDYxNzg5OTVhNTYxNWQyZWEyNDRlMmRhY2VjNjZlOGQ1NiJ9fX0="); - numberTextures.put(13, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMTg2YzEyOGJhZmY5YWRkMzVkY2M3MTgzNWRlY2M1NTNiNThjOWIyMTE2ZjU0ZWY0MjM5NjBjZDI2Yzk0NjljOSJ9fX0="); - numberTextures.put(14, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvMjMxODFmYThjZjZiZjA4OTg3N2FkOTIxZTNmODQwMTQzMDQ0NWEzZTZiMTg1MGVhNGQ2NzUwMWRhYjZiMjAyNCJ9fX0="); - numberTextures.put(15, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2FjODRiYWZjNDNlNGQzZGU2NjFkN2Y4NWRlNDhkNWU5YTYyZjRlNDg4Mjc4ODM0Yzc5ZTMwOTUxNjMxNTQ5NiJ9fX0="); - numberTextures.put(16, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvN2FjODRiYWZjNDNlNGQzZGU2NjFkN2Y4NWRlNDhkNWU5YTYyZjRlNDg4Mjc4ODM0Yzc5ZTMwOTUxNjMxNTQ5NiJ9fX0="); - numberTextures.put(17, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNGE1MGU5YTExMmZlYjc5NWE2NDViZTI0NDBlYWQ1YTcxMmM4ODllYjE5MDI4MDVmMWMwN2YyZjU2ZmY0OTA2NyJ9fX0="); - //numberTextures.put(21, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZDliMzk5NzYwMTc3YjI3NzVlMTc0ZDQ5NTEyZjdiNmZhOWFhMDgwNzEzZWY0MzRkNDQwN2IxMWUzZDk5N2E4NCJ9fX0="); - //numberTextures.put(22, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvM2ZhZGViZjRmMDllMDdmNTMzMTBhZGQxMmI3ZjQ3ZDQ4ZGFlNmM3N2FhM2U2MjcwMDFiNTMwOTJlNjlmODcxOSJ9fX0="); - //numberTextures.put(23, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZmRhOWNiYmYzM2Y4Yjc5ZTcwNjEzOGQ0ZmFlODQzZGNkNDlkNDQxOTAwMTMzZDE1ODhlZDQ4NmM0NWU4ODIxMSJ9fX0="); - - - - - - - - - - - - - - - - - numberTextures.put(40, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvYWZkMjQwMDAwMmFkOWZiYmJkMDA2Njk0MWViNWIxYTM4NGFiOWIwZTQ4YTE3OGVlOTZlNGQxMjlhNTIwODY1NCJ9fX0="); - numberTextures.put(44, "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvZTk5YWI0NDFmYzk3ZjYwOTA4MWFkM2NlMzNkNTk4MjkxZDUxYmVmOGNiN2FkMjQ4NGI1YzEzODdjN2E4NCJ9fX0="); - } - - public static String getNumberTexture(int number) { - return numberTextures.getOrDefault(number, null); - } - - - public static Map getAllNumberTextures() { - return new HashMap<>(numberTextures); // Повертає копію мапи - } - -} diff --git a/src/main/java/org/referix/birthDayReload/utils/configmannagers/ConfigUtils.java b/src/main/java/org/referix/birthDayReload/utils/configmannagers/ConfigUtils.java new file mode 100644 index 0000000..66db445 --- /dev/null +++ b/src/main/java/org/referix/birthDayReload/utils/configmannagers/ConfigUtils.java @@ -0,0 +1,72 @@ +package org.referix.birthDayReload.utils.configmannagers; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.plugin.Plugin; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.StandardCopyOption; +import java.util.List; + +public class ConfigUtils { + private final Plugin plugin; + private FileConfiguration config; + private final File configFile; + private final MiniMessage miniMessage = MiniMessage.miniMessage(); + + public ConfigUtils(Plugin plugin) { + this.plugin = plugin; + this.configFile = new File(plugin.getDataFolder(), "config.yml"); + + if (!configFile.exists()) { + plugin.saveResource("config.yml", false); + } + + loadConfig(); + } + + public void loadConfig() { + this.config = YamlConfiguration.loadConfiguration(configFile); + } + + public Component getComponent(String path, String defaultValue) { + String value = config.getString(path, defaultValue); + return parseMiniMessage(value); + } + + public Component getComponent(String path) { + return getComponent(path, "Message not found: " + path + ""); + } + + public void reloadConfig() { + try { + if (configFile.exists()) { + File backupFile = new File(plugin.getDataFolder(), "config_backup.yml"); + Files.copy(configFile.toPath(), backupFile.toPath(), StandardCopyOption.REPLACE_EXISTING); + } + loadConfig(); + plugin.getLogger().info("Config reloaded successfully!"); + } catch (IOException e) { + plugin.getLogger().severe("Failed to reload config: " + e.getMessage()); + } + } + + private Component parseMiniMessage(String input) { + if (input == null) return Component.empty(); + return miniMessage.deserialize(input); + } + + public boolean getBoolean(String path, boolean defaultValue) { + return config.getBoolean(path, defaultValue); + } + + public String getString(String path, String defaultValue) { + return config.getString(path, defaultValue); + } + + public List getStringList(String path) { return config.getStringList(path);} +} diff --git a/src/main/java/org/referix/birthDayReload/utils/configmannagers/DiscordConfig.java b/src/main/java/org/referix/birthDayReload/utils/configmannagers/DiscordConfig.java new file mode 100644 index 0000000..9ff2d1a --- /dev/null +++ b/src/main/java/org/referix/birthDayReload/utils/configmannagers/DiscordConfig.java @@ -0,0 +1,135 @@ +package org.referix.birthDayReload.utils.configmannagers; + + +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.configuration.file.FileConfiguration; + +import java.util.List; + +public class DiscordConfig { + + private final boolean enabled; + private final String token; + private final String channelId; + + private String setBirthdayTitle; + private String setBirthdayColor; + private List setBirthdayMessage; + + private boolean isEnableSetBirthdayMessage; + + private String happyBirthdayTitle; + + private String happyBirthdayColor; + private List happyBirthdayMessage; + private boolean isEnableHappyBirthdayMessage; + private String adminDeleteBirthdayTitle; + + private String adminDeleteBirthdayColor; + private List adminDeleteBirthdayMessage; + private boolean isEnableDeleteBirthdayMessage; + public DiscordConfig(FileConfiguration config) { + this.enabled = config.getBoolean("Discord.enabled"); + this.token = config.getString("Discord.token"); + this.channelId = config.getString("Discord.channel-id"); + + ConfigurationSection embeddedMessages = config.getConfigurationSection("Discord.Embedded-messages"); + if (embeddedMessages != null) { + loadSetBirthday(embeddedMessages.getConfigurationSection("set-birthday")); + loadHappyBirthday(embeddedMessages.getConfigurationSection("happy-birthday")); + loadAdminDeleteBirthday(embeddedMessages.getConfigurationSection("admin-delete-birthday")); + } + } + + private void loadSetBirthday(ConfigurationSection section) { + if (section != null) { + this.setBirthdayTitle = section.getString("title", ""); + this.setBirthdayColor = section.getString("color", "#FFFFFF"); + this.setBirthdayMessage = section.getStringList("message"); + this.isEnableSetBirthdayMessage = section.getBoolean("enable", false); + } + } + + private void loadHappyBirthday(ConfigurationSection section) { + if (section != null) { + this.happyBirthdayTitle = section.getString("title", ""); + this.happyBirthdayColor = section.getString("color", "#FFFFFF"); + this.happyBirthdayMessage = section.getStringList("message"); + this.isEnableHappyBirthdayMessage = section.getBoolean("enable", false); + } + } + + private void loadAdminDeleteBirthday(ConfigurationSection section) { + if (section != null) { + this.adminDeleteBirthdayTitle = section.getString("title", ""); + this.adminDeleteBirthdayColor = section.getString("color", "#FFFFFF"); + this.adminDeleteBirthdayMessage = section.getStringList("message"); + this.isEnableDeleteBirthdayMessage = section.getBoolean("enable", false); + } + } + + // Геттери для основних параметрів + + public boolean isEnabled() { + return enabled; + } + public String getToken() { + return token; + } + + public String getChannelId() { + return channelId; + } + + // Геттери для "set-birthday" + + public String getSetBirthdayTitle() { + return setBirthdayTitle; + } + public String getSetBirthdayColor() { + return setBirthdayColor; + } + + public List getSetBirthdayMessage() { + return setBirthdayMessage; + } + + // Геттери для "happy-birthday" + + public String getHappyBirthdayTitle() { + return happyBirthdayTitle; + } + public String getHappyBirthdayColor() { + return happyBirthdayColor; + } + + public List getHappyBirthdayMessage() { + return happyBirthdayMessage; + } + + // Геттери для "admin-delete-birthday" + + public String getAdminDeleteBirthdayTitle() { + return adminDeleteBirthdayTitle; + } + public String getAdminDeleteBirthdayColor() { + return adminDeleteBirthdayColor; + } + + public List getAdminDeleteBirthdayMessage() { + return adminDeleteBirthdayMessage; + } + + public boolean isEnableSetBirthdayMessage() { + return isEnableSetBirthdayMessage; + } + + public boolean isEnableHappyBirthdayMessage() { + return isEnableHappyBirthdayMessage; + } + + public boolean isEnableDeleteBirthdayMessage() { + return isEnableDeleteBirthdayMessage; + } +} + diff --git a/src/main/java/org/referix/birthDayReload/utils/configmannagers/EmbeddedMessage.java b/src/main/java/org/referix/birthDayReload/utils/configmannagers/EmbeddedMessage.java new file mode 100644 index 0000000..dc1adfa --- /dev/null +++ b/src/main/java/org/referix/birthDayReload/utils/configmannagers/EmbeddedMessage.java @@ -0,0 +1,36 @@ +package org.referix.birthDayReload.utils.configmannagers; + +import java.util.List; + +public class EmbeddedMessage { + private final String title; + private final List messages; + + public EmbeddedMessage(String title, List messages) { + this.title = title; + this.messages = messages; + } + + public String getTitle() { + return title; + } + + public List getMessages() { + return messages; + } + + public String formatMessage(String player, String data, String wish) { + String formattedTitle = title.replace("%player%", player); + StringBuilder formattedMessages = new StringBuilder(); + + for (String message : messages) { + formattedMessages.append( + message.replace("%player%", player) + .replace("%data%", data) + .replace("%wish%", wish) + ).append("\n"); + } + + return "**" + formattedTitle + "**\n" + formattedMessages.toString(); + } +} diff --git a/src/main/java/org/referix/birthDayReload/utils/configmannagers/ItemManagerConfig.java b/src/main/java/org/referix/birthDayReload/utils/configmannagers/ItemManagerConfig.java new file mode 100644 index 0000000..40b5a51 --- /dev/null +++ b/src/main/java/org/referix/birthDayReload/utils/configmannagers/ItemManagerConfig.java @@ -0,0 +1,89 @@ +package org.referix.birthDayReload.utils.configmannagers; + +import org.bukkit.Material; +import org.bukkit.configuration.file.FileConfiguration; +import org.bukkit.configuration.file.YamlConfiguration; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; + +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class ItemManagerConfig { + + private final JavaPlugin plugin; + private final File file; + private final FileConfiguration config; + + public ItemManagerConfig(JavaPlugin plugin) { + this.plugin = plugin; + this.file = new File(plugin.getDataFolder(), "items.yml"); + this.config = YamlConfiguration.loadConfiguration(file); + plugin.getLogger().info("File path: " + file.getAbsolutePath()); + plugin.getLogger().info("Config loaded: " + (config != null)); + createFileIfNotExists(); + } + + private void createFileIfNotExists() { + if (!file.exists()) { + try { + if (file.getParentFile().mkdirs()) { + plugin.getLogger().info("Created plugin data folder."); + } + if (file.createNewFile()) { + plugin.getLogger().info("Created items.yml file."); + } + + // Додаємо базовий список предметів + initializeDefaultItems(); + config.save(file); + plugin.getLogger().info("Initialized items.yml with default items."); + } catch (IOException e) { + plugin.getLogger().severe("Could not create items.yml: " + e.getMessage()); + } + } + } + + private void initializeDefaultItems() { + List defaultItems = new ArrayList<>(); + defaultItems.add(new ItemStack(Material.DIAMOND, 5)); // 5 діамантів як стартовий приклад + config.set("items", defaultItems); + } + + // Зберегти список предметів у items.yml + public void saveItems(List items) { + if (items == null || items.isEmpty()) { + plugin.getLogger().warning("Tried to save empty or null item list to items.yml."); + return; + } + + config.set("items", items); // Bukkit вміє серіалізувати ItemStack + try { + config.save(file); + plugin.getLogger().info("Items saved to items.yml."); + } catch (IOException e) { + plugin.getLogger().severe("Could not save items.yml: " + e.getMessage()); + } + } + + public List getItems() { + List itemList = config.getList("items", Collections.emptyList()); + if (itemList == null || itemList.isEmpty()) { + plugin.getLogger().info("No items found in items.yml."); + return new ArrayList<>(); + } + + List items = new ArrayList<>(); + for (Object obj : itemList) { + if (obj instanceof ItemStack) { + items.add((ItemStack) obj); + } else { + plugin.getLogger().warning("Invalid item found in items.yml, skipping..."); + } + } + return items; + } +} diff --git a/src/main/java/org/referix/birthDayReload/utils/configmannagers/MessageManager.java b/src/main/java/org/referix/birthDayReload/utils/configmannagers/MessageManager.java new file mode 100644 index 0000000..67ff5d0 --- /dev/null +++ b/src/main/java/org/referix/birthDayReload/utils/configmannagers/MessageManager.java @@ -0,0 +1,148 @@ +package org.referix.birthDayReload.utils.configmannagers; + +import net.kyori.adventure.text.Component; +import org.bukkit.plugin.Plugin; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + + +public class MessageManager { + private final ConfigUtils configUtils; + private final Plugin plugin; + + // Messages + public Component BIRTHDAY_BOY_PREFIX; + public Component USER_NO_ENTER_DATA; + public Component BIRTHDAY_SET_SUCCESS; + public Component BIRTHDAY_SET_FUTURE_ERROR; + public Component BIRTHDAY_SET_FORMAT_ERROR; + public Component BIRTHDAY_DELETE_NO_PERMISSION; + public Component BIRTHDAY_DELETE_USAGE; + public Component BIRTHDAY_DELETE_SUCCESS; + public Component BIRTHDAY_DELETE_PLAYER_NOT_FOUND; + public Component BIRTHDAY_UNKNOWN_COMMAND; + public Component BIRTHDAY_ONLY_PLAYERS; + public Component BIRTHDAY_ALREADY_SET; + public Component BIRTHDAY_LUCKPERMS_MESSAGE; + //LuckPerm + public boolean LUCK_PERM_ENABLED; + public String LUCK_PERM_GROUP; + public String LUCK_PERM_TIME; + + + // Date format + private String dateFormat; + private DateTimeFormatter dateFormatter; + + // Discord + + public MessageManager(Plugin plugin) { + this.plugin = plugin; + this.configUtils = new ConfigUtils(plugin); + loadMessages(); + } + + private void loadMessages() { + BIRTHDAY_BOY_PREFIX = logComponentLoad("Birthday-boy-prefix"); + USER_NO_ENTER_DATA = logComponentLoad("Messages.user-no-enter-data"); + BIRTHDAY_SET_SUCCESS = logComponentLoad("Messages.birthday-set-success"); + BIRTHDAY_SET_FUTURE_ERROR = logComponentLoad("Messages.birthday-set-future-error"); + BIRTHDAY_SET_FORMAT_ERROR = logComponentLoad("Messages.birthday-set-format-error"); + BIRTHDAY_DELETE_NO_PERMISSION = logComponentLoad("Messages.birthday-delete-no-permission"); + BIRTHDAY_DELETE_USAGE = logComponentLoad("Messages.birthday-delete-usage"); + BIRTHDAY_DELETE_SUCCESS = logComponentLoad("Messages.birthday-delete-success"); + BIRTHDAY_DELETE_PLAYER_NOT_FOUND = logComponentLoad("Messages.birthday-delete-player-not-found"); + BIRTHDAY_UNKNOWN_COMMAND = logComponentLoad("Messages.birthday-unknown-command"); + BIRTHDAY_ONLY_PLAYERS = logComponentLoad("Messages.birthday-only-players"); + BIRTHDAY_ALREADY_SET = logComponentLoad("Messages.birthday-already-set"); + + BIRTHDAY_LUCKPERMS_MESSAGE = logComponentLoad("Messages.birthday-luckperm-message"); + + // Зчитування формату дати з конфігурації + dateFormat = configUtils.getString("Format-Data", "yyyy-MM-dd"); + updateDateFormatter(); + + //luckperms + LUCK_PERM_ENABLED = configUtils.getBoolean("birthday-luckPerm.enable", false); + LUCK_PERM_GROUP = configUtils.getString("birthday-luckPerm.group", ""); + LUCK_PERM_TIME = configUtils.getString("birthday-luckPerm.time", "1d"); + } + + private Component logComponentLoad(String path) { + Component component = configUtils.getComponent(path); + if (component == null) { + plugin.getLogger().warning("Message not found in config: " + path); + } else { + plugin.getLogger().info("Message loaded successfully: " + path); + } + return component; + } + + private void updateDateFormatter() { + try { + dateFormatter = DateTimeFormatter.ofPattern(dateFormat); + plugin.getLogger().info("Date format updated to: " + dateFormat); + } catch (IllegalArgumentException e) { + plugin.getLogger().warning("Invalid date format in config: " + dateFormat + ". Defaulting to yyyy-MM-dd."); + dateFormat = "yyyy-MM-dd"; + dateFormatter = DateTimeFormatter.ofPattern(dateFormat); + } + } + + public void reloadMessages() { + configUtils.reloadConfig(); + loadMessages(); + plugin.getLogger().info("Messages reloaded successfully."); + } + + + public LocalDate parseDate(String date) { + String normalizedDate = normalizeDate(date); + + try { + // Перевіряємо, чи введена повна дата у форматі yyyy-MM-dd або yyyy.MM.dd + if (normalizedDate.matches("\\d{4}[-.]\\d{2}[-.]\\d{2}")) { + // Якщо формат yyyy.MM.dd, замінюємо "." на "-" + normalizedDate = normalizedDate.replace(".", "-"); + DateTimeFormatter fullDateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + return LocalDate.parse(normalizedDate, fullDateFormatter); + } + + // Якщо формат MM-dd або MM.dd, додаємо рік 2000 + if (normalizedDate.matches("\\d{2}[-.]\\d{2}")) { + normalizedDate = "2000-" + normalizedDate.replace(".", "-"); + DateTimeFormatter partialDateFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + return LocalDate.parse(normalizedDate, partialDateFormatter); + } + + // Якщо формат не підтримується + throw new DateTimeParseException("Unsupported date format", date, 0); + } catch (DateTimeParseException e) { + plugin.getLogger().warning("Unable to parse date: " + date + " with formats: yyyy-MM-dd, yyyy.MM.dd, MM-dd, or MM.dd."); + throw new IllegalArgumentException("Дата повинна бути у форматі yyyy-MM-dd, yyyy.MM.dd, MM-dd або MM.dd."); + } + } + + + private String normalizeDate(String date) { + // Видалення зайвих пробілів + date = date.trim(); + + // Заміна будь-яких роздільників на дефіс + return date.replaceAll("[./,;\\s-]", "-"); + } + + + + + public String formatDate(LocalDate date) { + return date.format(dateFormatter); + } + + public String getDateFormat() { + return dateFormat; + } +} + diff --git a/src/main/java/org/referix/birthDayReload/utils/headutils/CustomHeadUtil.java b/src/main/java/org/referix/birthDayReload/utils/headutils/CustomHeadUtil.java new file mode 100644 index 0000000..a0cc6cb --- /dev/null +++ b/src/main/java/org/referix/birthDayReload/utils/headutils/CustomHeadUtil.java @@ -0,0 +1,73 @@ +package org.referix.birthDayReload.utils.headutils; + +import net.kyori.adventure.text.Component; +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.SkullMeta; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; +import org.bukkit.profile.PlayerProfile; +import org.bukkit.profile.PlayerTextures; + +import java.net.MalformedURLException; +import java.net.URL; +import java.util.HashMap; +import java.util.Map; + +public class CustomHeadUtil { + + private static final Map numberTextures = new HashMap<>(); + + public static ItemStack getNumberHead(int number, NamespacedKey key) { + String textureUrl = getNumberTexture(number); + if (textureUrl == null) { + throw new IllegalArgumentException("Invalid number: " + number); + } + + ItemStack head = new ItemStack(Material.PLAYER_HEAD); + SkullMeta skullMeta = (SkullMeta) head.getItemMeta(); + + if (skullMeta != null) { + PlayerProfile playerProfile = org.bukkit.Bukkit.createPlayerProfile("UUID-" + number); + PlayerTextures textures = playerProfile.getTextures(); + + try { + // Преобразование строки в URL + URL url = new URL(textureUrl); + textures.setSkin(url); + } catch (MalformedURLException e) { + throw new IllegalArgumentException("Invalid texture URL: " + textureUrl, e); + } + + playerProfile.setTextures(textures); + skullMeta.setOwnerProfile(playerProfile); + + // Adventure API: Устанавливаем отображаемое имя + skullMeta.displayName(Component.text("Present")); + + // Добавляем NamespacedKey в PersistentDataContainer + PersistentDataContainer data = skullMeta.getPersistentDataContainer(); + data.set(key, PersistentDataType.STRING, textureUrl); + + head.setItemMeta(skullMeta); + } + + return head; + } + + static { + numberTextures.put(1, "http://textures.minecraft.net/texture/a891f34412fcb460bc6e348a9b1f2dacfc2165cb5957f03dbdb3db6e1a8f9cc"); + numberTextures.put(40, "http://textures.minecraft.net/texture/afd2400002ad9fbbbd0066941eb5b1a384ab9b0e48a178ee96e4d129a5208654"); + numberTextures.put(44, "http://textures.minecraft.net/texture/e99ab441fc97f609081ad3ce33d598291d51bef8cb7ad2484b5c1387c7a84"); + } + + public static String getNumberTexture(int number) { + return numberTextures.getOrDefault(number, null); + } + + public static Map getAllNumberTextures() { + return new HashMap<>(numberTextures); // Возвращает копию мапы + } + +} diff --git a/src/main/java/org/referix/birthDayReload/utils/luckperm/LuckPerm.java b/src/main/java/org/referix/birthDayReload/utils/luckperm/LuckPerm.java new file mode 100644 index 0000000..983a220 --- /dev/null +++ b/src/main/java/org/referix/birthDayReload/utils/luckperm/LuckPerm.java @@ -0,0 +1,151 @@ +package org.referix.birthDayReload.utils.luckperm; + +import net.luckperms.api.LuckPerms; +import net.luckperms.api.model.user.User; +import net.luckperms.api.node.types.InheritanceNode; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; +import org.bukkit.plugin.Plugin; +import org.referix.birthDayReload.BirthDayReload; +import org.referix.birthDayReload.utils.configmannagers.MessageManager; + +import java.time.Duration; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class LuckPerm { + + private final LuckPerms luckPerms; + private final Plugin plugin = BirthDayReload.getInstance(); + private final MessageManager messageManager; + + public LuckPerm(LuckPerms luckPerms, MessageManager messageManager) { + this.luckPerms = luckPerms; + this.messageManager = messageManager; + } + + /** + * Публичный метод: добавляет игрока в группу, указанную в конфиге (LUCK_PERM_GROUP), + * с истечением срока, если (LUCK_PERM_TIME) задан. Поддерживаются d/h/m/s/w/mo/y. + */ + public void applyLuckPermGroup(Player player) { + if (luckPerms == null) { + plugin.getLogger().severe("[LuckPerms] LuckPerms API no connected"); + return; + } + if (!messageManager.LUCK_PERM_ENABLED) { + return; + } else { + String group = messageManager.LUCK_PERM_GROUP; + String time = messageManager.LUCK_PERM_TIME; + + if (group == null || group.isEmpty()) { + return; + } + + Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> { + addGroupWithConfigTime(player, group, time); + }); + } + + } + + /** + * Приватный метод, который действительно вносит игрока в группу + * с использованием Duration (если распарсился) или навсегда (если парсинг = null). + */ + private void addGroupWithConfigTime(Player player, String groupName, String timeString) { + User user = getUser(player.getUniqueId()); + if (user == null) { + return; + } + + try { + Duration duration = parseDuration(timeString); + + InheritanceNode node; + if (duration == null || duration.isZero() || duration.isNegative()) { + node = InheritanceNode.builder(groupName).build(); + } else { + node = InheritanceNode.builder(groupName) + .expiry(duration) + .build(); + } + + if (!user.data().add(node).wasSuccessful()) { + return; + } + + luckPerms.getUserManager().saveUser(user).join(); + } catch (Exception e) { + return; + } + } + + /** + * Парсим строку в Duration. + * Поддерживаем форматы: d/h/m/s/w/mo/y (дни, часы, минуты, секунды, недели, месяцы, годы). + * Можно комбинировать: "1d2h30m", "2w1d", "3mo" и т.д. + * Возвращаем null, если не нашли ни одного валидного фрагмента. + */ + private Duration parseDuration(String input) { + if (input == null || input.isEmpty()) { + return null; + } + + Pattern pattern = Pattern.compile("(\\d+)(d|h|m|s|w|mo|y)"); + Matcher matcher = pattern.matcher(input); + + Duration total = Duration.ZERO; + boolean foundAny = false; + + while (matcher.find()) { + foundAny = true; + int value = Integer.parseInt(matcher.group(1)); + String unit = matcher.group(2); + + switch (unit) { + case "d": + total = total.plusDays(value); + break; + case "h": + total = total.plusHours(value); + break; + case "m": + total = total.plusMinutes(value); + break; + case "s": + total = total.plusSeconds(value); + break; + case "w": + total = total.plusDays(value * 7L); + break; + case "mo": + total = total.plusDays(value * 30L); + break; + case "y": + total = total.plusDays(value * 365L); + break; + default: + return null; + } + } + + if (!foundAny) { + return null; + } + return total; + } + + /** + * Приватный метод: Загружаем LuckPerms-пользователя по UUID + */ + private User getUser(UUID uuid) { + try { + return luckPerms.getUserManager().loadUser(uuid).join(); + } catch (Exception e) { + return null; + } + } +} diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..1d40c18 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,55 @@ +Birthday-boy-prefix: "Birthday-boy" +# - MM-dd (example: 12-31) +# - MM.dd (example: 12.31) +# - yyyy-MM-dd (example: 2000-12-31) +# - yyyy.MM.dd (example: 2000.12.31) +Format-Data: "MM-dd" + +currentDate: "Europe/Kiev" # the timezone the plugin will focus on + +birthday-luckPerm: + enable: false # to use need the setup LuckPerm + group: "birthday-vip" # group luck perm + time: 7d # time (d - day, h - hour, w - week , mo, y) + +Discord: + enabled: false + token: "" + channel-id: "" + Embedded-messages: + set-birthday: + enable: false + title: "%player%" + color: "#ffffff" + message: + - "Gave his birthday: %date%" + - "Wishes: " + happy-birthday: + enable: true + title: "%player%" + color: "#ffffff" + message: + - "Today is %player%'s Birthday! Let's congratulate them in the Minecraft chat and Discord on this wonderful occasion, wish them all the best, and give them some amazing gifts!" + admin-delete-birthday: + enable: false + title: "The birthday was deleted from %target_player%" + color: "#ffffff" + message: + - "By the administrator - %player%" + + +Messages: + user-no-enter-data: "You haven't specified your birthday. Use /birthday set " + birthday-set-success: "Your birthday has been set to: %date%" + birthday-set-future-error: "Invalid date. The birthday cannot be in the future." + birthday-set-format-error: "Invalid date format. Use %data%." + birthday-delete-no-permission: "You don't have permission to use this command." + birthday-delete-usage: "Usage: /birthday delete " + birthday-delete-success: "Birthday data for %player% has been deleted." + birthday-delete-player-not-found: "Player not found." + birthday-unknown-command: "Unknown subcommand. Use /birthday help for more information." + birthday-only-players: "Only players can set their own birthday." + birthday-already-set: "Your birthday is already set to: %date%. It cannot be changed." + birthday-luckperm-message: "Congratulations! You have received a donation gift: VIP group! Enjoy your special perks!" + + diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index a63a038..f7c31e3 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,6 +3,8 @@ version: '1.0-SNAPSHOT' main: org.referix.birthDayReload.BirthDayReload api-version: '1.20' depend: [PlaceholderAPI] +softdepend: + - LuckPerms commands: birthday: description: Manage birthdays. @@ -13,9 +15,21 @@ permissions: birthday.use: description: Allow use of the /birthday command default: true + birthday.reload: + description: reload plugin + default: op birthday.delete: description: Allow deletion of birthday data default: op birthday.list: description: Allow listing all birthday data default: op + birthday.present: + description: Allow present command + default: op + birthday.present.open: + description: Allow adding items to the birthday present + default: op + birthday.present.give: + description: Allow giving the birthday present to a player + default: op