diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/configuration/migrations/Migration_0035_Move_warp_inventory_to_dedicated_file.java b/eternalcore-core/src/main/java/com/eternalcode/core/configuration/migrations/Migration_0035_Move_warp_inventory_to_dedicated_file.java new file mode 100644 index 000000000..986959996 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/configuration/migrations/Migration_0035_Move_warp_inventory_to_dedicated_file.java @@ -0,0 +1,178 @@ +package com.eternalcode.core.configuration.migrations; + +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.migrate.ConfigMigration; +import eu.okaeri.configs.migrate.view.RawConfigView; +import java.io.File; +import java.io.FileReader; +import java.io.FileWriter; +import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.logging.Level; +import java.util.logging.Logger; +import lombok.NonNull; +import org.yaml.snakeyaml.DumperOptions; +import org.yaml.snakeyaml.Yaml; + +public class Migration_0035_Move_warp_inventory_to_dedicated_file implements ConfigMigration { + + private static final Logger LOGGER = Logger + .getLogger(Migration_0035_Move_warp_inventory_to_dedicated_file.class.getName()); + private static final String ROOT_KEY = "warpInventory"; + private static final String NESTED_KEY = "warp.warpInventory"; + private static final String NESTED_SECTION = "warp"; + + @Override + public boolean migrate(@NonNull OkaeriConfig config, @NonNull RawConfigView view) { + String foundKey = null; + Map warpInventory = this.getFromView(view, ROOT_KEY); + + if (warpInventory != null) { + foundKey = ROOT_KEY; + } else { + warpInventory = this.getFromView(view, NESTED_KEY); + if (warpInventory != null) { + foundKey = NESTED_KEY; + } + } + + if (warpInventory == null) { + warpInventory = this.getFromFileFallback(config); + } + + if (warpInventory == null) { + return false; + } + + Map newContent = this.transformData(warpInventory); + + if (!this.saveNewConfig(config, newContent)) { + return false; + } + + if (foundKey != null) { + view.remove(foundKey); + } else { + view.remove(ROOT_KEY); + view.remove(NESTED_KEY); + } + + return true; + } + + private Map getFromView(RawConfigView view, String key) { + if (!view.exists(key)) { + return null; + } + Object obj = view.get(key); + return obj instanceof Map ? (Map) obj : null; + } + + private Map getFromFileFallback(OkaeriConfig config) { + File bindFile = config.getBindFile().toFile(); + if (bindFile == null || !bindFile.exists()) { + return null; + } + + try (FileReader reader = new FileReader(bindFile)) { + Map content = new Yaml().load(reader); + if (content == null) { + return null; + } + + if (content.containsKey(ROOT_KEY) && content.get(ROOT_KEY) instanceof Map) { + return (Map) content.get(ROOT_KEY); + } + + if (content.containsKey(NESTED_SECTION) && content.get(NESTED_SECTION) instanceof Map) { + Map warpSection = (Map) content.get(NESTED_SECTION); + if (warpSection.containsKey(ROOT_KEY) && warpSection.get(ROOT_KEY) instanceof Map) { + return (Map) warpSection.get(ROOT_KEY); + } + } + } catch (Exception exception) { + LOGGER.log(Level.SEVERE, "Failed to read configuration file: " + bindFile.getAbsolutePath(), exception); + } + return null; + } + + private Map transformData(Map source) { + Map result = new LinkedHashMap<>(); + + if (source.containsKey("title")) { + Map display = new LinkedHashMap<>(); + display.put("title", source.get("title")); + result.put("display", display); + } + + this.copyIfPresent(source, result, "items"); + this.copyIfPresent(source, result, "border"); + this.copyIfPresent(source, result, "decorationItems"); + + return result; + } + + private void copyIfPresent(Map source, Map target, String key) { + if (source.containsKey(key)) { + target.put(key, source.get(key)); + } + } + + private boolean saveNewConfig(OkaeriConfig config, Map content) { + File destFile = this.getDestinationFile(config); + if (destFile == null) { + return false; + } + + if (destFile.exists() && !this.targetIsSafeToWrite(destFile)) { + return true; + } + + if (!destFile.getParentFile().exists()) { + destFile.getParentFile().mkdirs(); + } + + DumperOptions options = new DumperOptions(); + options.setDefaultFlowStyle(DumperOptions.FlowStyle.BLOCK); + options.setPrettyFlow(true); + options.setIndent(2); + options.setIndicatorIndent(0); + options.setSplitLines(false); + + try (FileWriter writer = new FileWriter(destFile)) { + new Yaml(options).dump(content, writer); + return true; + } catch (IOException e) { + LOGGER.log(Level.SEVERE, "Failed to save new configuration file: " + destFile.getAbsolutePath(), e); + return false; + } + } + + private File getDestinationFile(OkaeriConfig config) { + File bindFile = config.getBindFile().toFile(); + if (bindFile == null) { + return null; + } + File dataFolder = bindFile.getParentFile(); + if ("lang".equals(dataFolder.getName())) { + dataFolder = dataFolder.getParentFile(); + } + return new File(dataFolder, "warp-inventory.yml"); + } + + private boolean targetIsSafeToWrite(File file) { + try (FileReader reader = new FileReader(file)) { + Map existing = new Yaml().load(reader); + if (existing == null || !existing.containsKey("items")) { + return true; + } + Object items = existing.get("items"); + return items instanceof Map && ((Map) items).isEmpty(); + } catch (Exception exception) { + LOGGER.log(Level.SEVERE, "Failed to check if target file is safe to write: " + file.getAbsolutePath(), + exception); + return false; + } + } +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/configuration/migrations/Migrations.java b/eternalcore-core/src/main/java/com/eternalcode/core/configuration/migrations/Migrations.java index 09372801e..d0c6cff5b 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/configuration/migrations/Migrations.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/configuration/migrations/Migrations.java @@ -5,37 +5,38 @@ public class Migrations { public static final ConfigMigration[] ALL = new ConfigMigration[] { - new Migration_0001_Rename_privateChat_to_msg(), - new Migration_0002_Move_Spawn_Settings_to_spawn_config_section(), - new Migration_0003_Move_tprp_to_dedicated_section(), - new Migration_0006_Move_alert_to_broadcast_section(), - new Migration_0007_Move_clear_to_dedicated_section(), - new Migration_0008_Move_repair_to_dedicated_section(), - new Migration_0009_Improve_Homes_Config(), - new Migration_0010_Move_give_to_dedicated_section(), - new Migration_0011_Move_enchant_to_dedicated_section(), - new Migration_0012_Move_repair_argument_messages_to_dedicated_section(), - new Migration_0013_Move_enchant_messages_to_dedicated_section(), - new Migration_0014_Move_butcher_argument_messages_to_dedicated_section(), - new Migration_0016_Move_feed_messages_to_dedicated_section(), - new Migration_0017_Move_heal_messages_to_dedicated_section(), - new Migration_0018_Move_kill_messages_to_dedicated_section(), - new Migration_0019_Move_speed_messages_to_dedicated_section(), - new Migration_0020_Move_godmode_messages_to_dedicated_section(), - new Migration_0021_Move_fly_messages_to_dedicated_section(), - new Migration_0022_Move_ping_messages_to_dedicated_section(), - new Migration_0023_Move_gamemode_messages_to_dedicated_section(), - new Migration_0024_Move_online_messages_to_dedicated_section(), - new Migration_0025_Move_whois_messages_to_dedicated_section(), - new Migration_0026_Move_butcher_messages_to_dedicated_section(), - new Migration_0027_Move_give_messages_to_dedicated_section(), - new Migration_0028_Move_skull_messages_to_dedicated_section(), - new Migration_0029_Move_enchant_messages_to_dedicated_section(), - new Migration_0015_Move_ignore_messages_to_dedicated_section(), - new Migration_0030_Move_back_to_dedicated_section(), - new Migration_0031_Move_death_messages_to_dedicated_section(), - new Migration_0032_Move_join_quit_messages_to_dedicated_section(), - new Migration_0033_Move_disposal_messages_to_dedicated_section(), - new Migration_0034_Move_chat_settings_messages_to_dedicated_section(), - }; + new Migration_0001_Rename_privateChat_to_msg(), + new Migration_0002_Move_Spawn_Settings_to_spawn_config_section(), + new Migration_0003_Move_tprp_to_dedicated_section(), + new Migration_0006_Move_alert_to_broadcast_section(), + new Migration_0007_Move_clear_to_dedicated_section(), + new Migration_0008_Move_repair_to_dedicated_section(), + new Migration_0009_Improve_Homes_Config(), + new Migration_0010_Move_give_to_dedicated_section(), + new Migration_0011_Move_enchant_to_dedicated_section(), + new Migration_0012_Move_repair_argument_messages_to_dedicated_section(), + new Migration_0013_Move_enchant_messages_to_dedicated_section(), + new Migration_0014_Move_butcher_argument_messages_to_dedicated_section(), + new Migration_0016_Move_feed_messages_to_dedicated_section(), + new Migration_0017_Move_heal_messages_to_dedicated_section(), + new Migration_0018_Move_kill_messages_to_dedicated_section(), + new Migration_0019_Move_speed_messages_to_dedicated_section(), + new Migration_0020_Move_godmode_messages_to_dedicated_section(), + new Migration_0021_Move_fly_messages_to_dedicated_section(), + new Migration_0022_Move_ping_messages_to_dedicated_section(), + new Migration_0023_Move_gamemode_messages_to_dedicated_section(), + new Migration_0024_Move_online_messages_to_dedicated_section(), + new Migration_0025_Move_whois_messages_to_dedicated_section(), + new Migration_0026_Move_butcher_messages_to_dedicated_section(), + new Migration_0027_Move_give_messages_to_dedicated_section(), + new Migration_0028_Move_skull_messages_to_dedicated_section(), + new Migration_0029_Move_enchant_messages_to_dedicated_section(), + new Migration_0015_Move_ignore_messages_to_dedicated_section(), + new Migration_0030_Move_back_to_dedicated_section(), + new Migration_0031_Move_death_messages_to_dedicated_section(), + new Migration_0032_Move_join_quit_messages_to_dedicated_section(), + new Migration_0033_Move_disposal_messages_to_dedicated_section(), + new Migration_0034_Move_chat_settings_messages_to_dedicated_section(), + new Migration_0035_Move_warp_inventory_to_dedicated_file(), + }; } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/Warp.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/Warp.java new file mode 100644 index 000000000..42a64b7b1 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/Warp.java @@ -0,0 +1,29 @@ +package com.eternalcode.core.feature.warp; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import java.util.List; + +public interface Warp { + + String getName(); + + Location getLocation(); + + List getPermissions(); + + default boolean hasPermissions(Player player) { + if (this.getPermissions().isEmpty()) { + return true; + } + + for (String permission : this.getPermissions()) { + if (player.hasPermission(permission)) { + return true; + } + } + + return false; + } + +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpConfig.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpConfig.java index 683222060..bd83d488a 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpConfig.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpConfig.java @@ -1,12 +1,20 @@ package com.eternalcode.core.feature.warp; +import com.eternalcode.commons.bukkit.position.Position; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.Comment; -import java.time.Duration; +import lombok.AllArgsConstructor; import lombok.Getter; +import lombok.NoArgsConstructor; import lombok.experimental.Accessors; import org.bukkit.Material; +import java.time.Duration; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + @Getter @Accessors(fluent = true) public class WarpConfig extends OkaeriConfig implements WarpSettings { @@ -19,8 +27,8 @@ public class WarpConfig extends OkaeriConfig implements WarpSettings { @Comment("# Warp inventory auto add new warps") public boolean autoAddNewWarps = true; - @Comment({"# Options below allow you to customize item representing warp added to GUI, ", - "# you can change almost everything inside language files, after the warp has been added to the inventory."}) + @Comment({ "# Options below allow you to customize item representing warp added to GUI, ", + "# you can change almost everything inside language files, after the warp has been added to the inventory." }) public String itemNamePrefix = "&8» &6Warp: &f"; public String itemLore = "&7Click to teleport!"; @@ -29,4 +37,15 @@ public class WarpConfig extends OkaeriConfig implements WarpSettings { @Comment("# Texture of the item (only for PLAYER_HEAD material)") public String itemTexture = "eyJ0ZXh0dXJlcyI6eyJTS0lOIjp7InVybCI6Imh0dHA6Ly90ZXh0dXJlcy5taW5lY3JhZnQubmV0L3RleHR1cmUvNzk4ODVlODIzZmYxNTkyNjdjYmU4MDkwOTNlMzNhNDc2ZTI3NDliNjU5OGNhNGEyYTgxZWU2OTczODAzZmI2NiJ9fX0="; + + public Map warps = new ConcurrentHashMap<>(); + + @Getter + @Accessors(fluent = true) + @NoArgsConstructor + @AllArgsConstructor + public static class WarpConfigEntry extends OkaeriConfig { + public Position position; + public List permissions = new ArrayList<>(); + } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpImpl.java index 425692291..0eb662166 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpImpl.java @@ -2,22 +2,15 @@ import com.eternalcode.commons.bukkit.position.Position; import com.eternalcode.commons.bukkit.position.PositionAdapter; -import java.util.ArrayList; import org.bukkit.Location; import java.util.Collections; import java.util.List; -public class WarpImpl implements Warp { +public record WarpImpl(String name, Position position, List permissions) implements Warp { - private final String name; - private final Position position; - private final List permissions; - - public WarpImpl(String name, Position position, List permissions) { - this.name = name; - this.position = position; - this.permissions = new ArrayList<>(permissions); + public WarpImpl { + permissions = Collections.unmodifiableList(permissions); } @Override @@ -32,7 +25,7 @@ public Location getLocation() { @Override public List getPermissions() { - return Collections.unmodifiableList(this.permissions); + return this.permissions; } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpInventory.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpInventory.java deleted file mode 100644 index 8a250463d..000000000 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpInventory.java +++ /dev/null @@ -1,283 +0,0 @@ -package com.eternalcode.core.feature.warp; - -import com.eternalcode.commons.adventure.AdventureUtil; -import com.eternalcode.commons.scheduler.Scheduler; -import com.eternalcode.core.configuration.ConfigurationManager; -import com.eternalcode.core.configuration.contextual.ConfigItem; -import com.eternalcode.core.feature.warp.messages.WarpMessages; -import com.eternalcode.core.feature.warp.messages.WarpMessages.WarpInventorySection; -import com.eternalcode.core.injector.annotations.Inject; -import com.eternalcode.core.injector.annotations.component.Service; -import com.eternalcode.core.translation.AbstractTranslation; -import com.eternalcode.core.translation.Translation; -import com.eternalcode.core.translation.TranslationManager; -import dev.triumphteam.gui.builder.item.BaseItemBuilder; -import dev.triumphteam.gui.builder.item.ItemBuilder; -import dev.triumphteam.gui.guis.Gui; -import dev.triumphteam.gui.guis.GuiItem; -import java.util.Collections; -import java.util.Comparator; -import java.util.List; -import java.util.Optional; -import net.kyori.adventure.text.Component; -import net.kyori.adventure.text.minimessage.MiniMessage; -import org.bukkit.Material; -import org.bukkit.Server; -import org.bukkit.entity.Player; - -@Service -public class WarpInventory { - - private static final int GUI_ITEM_SLOT_WITH_TOP_BORDER = 9; - private static final int GUI_ITEM_SLOT_WITHOUT_BORDER = 0; - private static final int GUI_ITEM_SLOT_WITH_ALL_BORDER = 1; - private static final int GUI_ITEM_SLOT_WITH_BORDER = 10; - - private static final int GUI_ROW_SIZE_WITHOUT_BORDER = 9; - private static final int GUI_ROW_SIZE_WITH_BORDER = 7; - - private static final int BORDER_ROW_COUNT = 2; - private static final int UGLY_BORDER_ROW_COUNT = 1; - - private final TranslationManager translationManager; - private final WarpService warpService; - private final Server server; - private final MiniMessage miniMessage; - private final WarpTeleportService warpTeleportService; - private final ConfigurationManager configurationManager; - private final WarpSettings warpSettings; - private final Scheduler scheduler; - - @Inject - WarpInventory( - TranslationManager translationManager, - WarpService warpService, - Server server, - MiniMessage miniMessage, - WarpTeleportService warpTeleportService, - ConfigurationManager configurationManager, - WarpSettings warpSettings, - Scheduler scheduler - ) { - this.translationManager = translationManager; - this.warpService = warpService; - this.server = server; - this.miniMessage = miniMessage; - this.warpTeleportService = warpTeleportService; - this.configurationManager = configurationManager; - this.warpSettings = warpSettings; - this.scheduler = scheduler; - } - - public void openInventory(Player player) { - Gui gui = this.createInventory(player); - this.scheduler.run(() -> gui.open(player)); - } - - private Gui createInventory(Player player) { - Translation translation = this.translationManager.getMessages(); - WarpMessages.WarpInventorySection warpSection = translation.warp().warpInventory(); - - int rowsCount; - int size = warpSection.items().size(); - - if (!warpSection.border().enabled()) { - rowsCount = (size + 1) / GUI_ROW_SIZE_WITHOUT_BORDER + 1; - } - else { - switch (warpSection.border().fillType()) { - case BORDER, ALL -> rowsCount = (size - 1) / GUI_ROW_SIZE_WITH_BORDER + 1 + BORDER_ROW_COUNT; - case TOP, BOTTOM -> rowsCount = (size - 1) / GUI_ROW_SIZE_WITHOUT_BORDER + 1 + UGLY_BORDER_ROW_COUNT; - default -> throw new IllegalStateException("Unexpected value: " + warpSection.border().fillType()); - } - } - - Gui gui = Gui.gui() - .title(this.miniMessage.deserialize(warpSection.title())) - .rows(rowsCount) - .disableAllInteractions() - .create(); - - this.createWarpItems(player, warpSection, gui); - this.createBorder(warpSection, gui); - this.createDecorations(warpSection, gui); - - return gui; - } - - private void createBorder(WarpInventorySection warpSection, Gui gui) { - if (warpSection.border().enabled()) { - WarpInventorySection.BorderSection borderSection = warpSection.border(); - - ItemBuilder borderItem = ItemBuilder.from(borderSection.material()); - - if (!borderSection.name().isBlank()) { - borderItem.name(AdventureUtil.resetItalic(this.miniMessage.deserialize(borderSection.name()))); - } - - if (!borderSection.lore().isEmpty()) { - borderItem.lore(borderSection.lore() - .stream() - .map(entry -> AdventureUtil.resetItalic(this.miniMessage.deserialize(entry))) - .toList()); - } - - GuiItem guiItem = new GuiItem(borderItem.build()); - - switch (borderSection.fillType()) { - case BORDER -> gui.getFiller().fillBorder(guiItem); - case ALL -> gui.getFiller().fill(guiItem); - case TOP -> gui.getFiller().fillTop(guiItem); - case BOTTOM -> gui.getFiller().fillBottom(guiItem); - default -> throw new IllegalStateException("Unexpected value: " + borderSection.fillType()); - } - } - } - - private void createDecorations(WarpInventorySection warpSection, Gui gui) { - for (ConfigItem item : warpSection.decorationItems().items()) { - BaseItemBuilder baseItemBuilder = this.createItem(item); - GuiItem guiItem = baseItemBuilder.asGuiItem(); - - guiItem.setAction(event -> { - Player player = (Player) event.getWhoClicked(); - - if (item.commands.isEmpty()) { - return; - } - - for (String command : item.commands) { - this.server.dispatchCommand(player, command); - } - - player.closeInventory(); - }); - - gui.setItem(item.slot(), guiItem); - } - } - - private void createWarpItems(Player player, WarpInventorySection warpSection, Gui gui) { - warpSection.items().values().forEach(item -> { - Optional warpOptional = this.warpService.findWarp(item.warpName()); - - if (warpOptional.isEmpty()) { - return; - } - - Warp warp = warpOptional.get(); - ConfigItem warpItem = item.warpItem(); - - if (!warp.hasPermissions(player)) { - return; - } - - BaseItemBuilder baseItemBuilder = this.createItem(warpItem); - GuiItem guiItem = baseItemBuilder.asGuiItem(); - - guiItem.setAction(event -> { - if (!warp.hasPermissions(player)) { - return; - } - - player.closeInventory(); - this.warpTeleportService.teleport(player, warp); - }); - - gui.setItem(warpItem.slot(), guiItem); - }); - } - - private BaseItemBuilder createItem(ConfigItem item) { - Component name = AdventureUtil.resetItalic(this.miniMessage.deserialize(item.name())); - - List lore = item.lore() - .stream() - .map(entry -> AdventureUtil.resetItalic(this.miniMessage.deserialize(entry))) - .toList(); - - if (item.material() == Material.PLAYER_HEAD && !item.texture().isEmpty()) { - return ItemBuilder.skull() - .name(name) - .lore(lore) - .texture(item.texture()) - .glow(item.glow()); - } - - return ItemBuilder.from(item.material()) - .name(name) - .lore(lore) - .glow(item.glow()); - } - - public void addWarp(Warp warp) { - if (!this.warpService.exists(warp.getName())) { - return; - } - - AbstractTranslation translation = (AbstractTranslation) this.translationManager.getMessages(); - WarpMessages.WarpInventorySection warpSection = translation.warp().warpInventory(); - int slot = getSlot(warpSection); - - warpSection.addItem( - warp.getName(), - WarpInventoryItem.builder() - .withWarpName(warp.getName()) - .withWarpItem(ConfigItem.builder() - .withName(this.warpSettings.itemNamePrefix() + warp.getName()) - .withLore(Collections.singletonList(this.warpSettings.itemLore())) - .withMaterial(this.warpSettings.itemMaterial()) - .withTexture(this.warpSettings.itemTexture()) - .withSlot(slot) - .withGlow(true) - .build()) - .build()); - - this.configurationManager.save(translation); - } - - private int getSlot(WarpMessages.WarpInventorySection warpSection) { - int size = warpSection.items().size(); - if (!warpSection.border().enabled()) { - return GUI_ITEM_SLOT_WITHOUT_BORDER + size; - } - - return switch (warpSection.border().fillType()) { - case BORDER -> GUI_ITEM_SLOT_WITH_BORDER + size + ((size / WarpInventory.GUI_ROW_SIZE_WITH_BORDER) * 2); - case ALL -> GUI_ITEM_SLOT_WITH_ALL_BORDER + size + ((size / WarpInventory.GUI_ROW_SIZE_WITH_BORDER) * 2); - case TOP -> GUI_ITEM_SLOT_WITH_TOP_BORDER + size; - case BOTTOM -> size; - }; - } - - public void removeWarp(String warpName) { - if (!this.warpSettings.autoAddNewWarps()) { - return; - } - - AbstractTranslation translation = (AbstractTranslation) this.translationManager.getMessages(); - WarpMessages.WarpInventorySection warpSection = translation.warp().warpInventory(); - WarpInventoryItem removed = warpSection.removeItem(warpName); - - if (removed != null) { - this.shiftWarpItems(removed, warpSection); - } - - this.configurationManager.save(translation); - } - - private void shiftWarpItems(WarpInventoryItem removed, WarpMessages.WarpInventorySection warpSection) { - int removedSlot = removed.warpItem.slot; - List itemsToShift = warpSection.items().values().stream() - .filter(item -> item.warpItem.slot > removedSlot) - .sorted(Comparator.comparingInt(item -> item.warpItem.slot)) - .toList(); - - int currentShift = removedSlot; - for (WarpInventoryItem item : itemsToShift) { - int nextShift = item.warpItem.slot; - item.warpItem.slot = currentShift; - currentShift = nextShift; - } - } -} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpInventoryItem.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpInventoryItem.java index 34f40d49c..17d6fd9f6 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpInventoryItem.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpInventoryItem.java @@ -7,52 +7,68 @@ public class WarpInventoryItem implements Serializable { - public String warpName = "default"; + private String warpName; + private ConfigItem warpItem; - public ConfigItem warpItem = ConfigItem.builder() - .withName("&6Warp: &fdefault") - .withLore(Collections.singletonList("&7Click to teleport to warp")) - .withMaterial(Material.PLAYER_HEAD) - .withTexture("ewogICJ0aW1lc3RhbXAiIDogMTY2NDAzNTM1MjUyNCwKICAicHJvZmlsZUlkIiA6ICJjYjIzZWZhOWY1N2U0ZTQyOGE0MDU2OTM4NDlhODAxZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJWMUdHTyIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82MThhZjFiODNhZGZmNzM1MDA3ZmVkMjMwMTkxOWMwYjYzZWJmZTgwZTVkNjFiYTkzN2M5MmViMWVhY2Y2ZDI4IgogICAgfQogIH0KfQ==") - .withSlot(10) - .withGlow(true) - .build(); + public WarpInventoryItem() { + this.warpName = "default"; + this.warpItem = ConfigItem.builder() + .withName("&6Warp: &fdefault") + .withLore(Collections.singletonList("&7Click to teleport to warp")) + .withMaterial(Material.PLAYER_HEAD) + .withTexture( + "ewogICJ0aW1lc3RhbXAiIDogMTY2NDAzNTM1MjUyNCwKICAicHJvZmlsZUlkIiA6ICJjYjIzZWZhOWY1N2U0ZTQyOGE0MDU2OTM4NDlhODAxZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJWMUdHTyIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82MThhZjFiODNhZGZmNzM1MDA3ZmVkMjMwMTkxOWMwYjYzZWJmZTgwZTVkNjFiYTkzN2M5MmViMWVhY2Y2ZDI4IgogICAgfQogIH0KfQ==") + .withSlot(10) + .withGlow(true) + .build(); + } public WarpInventoryItem(String warpName, ConfigItem warpItem) { this.warpName = warpName; this.warpItem = warpItem; } - public WarpInventoryItem() { - - } - public String warpName() { return this.warpName; } + public WarpInventoryItem warpName(String warpName) { + this.warpName = warpName; + return this; + } + public ConfigItem warpItem() { return this.warpItem; } + public WarpInventoryItem warpItem(ConfigItem warpItem) { + this.warpItem = warpItem; + return this; + } + public static Builder builder() { return new Builder(); } public static class Builder { - - private String warpName; - private ConfigItem warpItem; - - public Builder withWarpName(String warpName) { + private String warpName = "default"; + private ConfigItem warpItem = ConfigItem.builder() + .withName("&6Warp: &fdefault") + .withLore(Collections.singletonList("&7Click to teleport to warp")) + .withMaterial(Material.PLAYER_HEAD) + .withTexture( + "ewogICJ0aW1lc3RhbXAiIDogMTY2NDAzNTM1MjUyNCwKICAicHJvZmlsZUlkIiA6ICJjYjIzZWZhOWY1N2U0ZTQyOGE0MDU2OTM4NDlhODAxZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJWMUdHTyIsCiAgInNpZ25hdHVyZVJlcXVpcmVkIiA6IHRydWUsCiAgInRleHR1cmVzIiA6IHsKICAgICJTS0lOIiA6IHsKICAgICAgInVybCIgOiAiaHR0cDovL3RleHR1cmVzLm1pbmVjcmFmdC5uZXQvdGV4dHVyZS82MThhZjFiODNhZGZmNzM1MDA3ZmVkMjMwMTkxOWMwYjYzZWJmZTgwZTVkNjFiYTkzN2M5MmViMWVhY2Y2ZDI4IgogICAgfQogIH0KfQ==") + .withSlot(10) + .withGlow(true) + .build(); + + public Builder warpName(String warpName) { this.warpName = warpName; - return this; } - public Builder withWarpItem(ConfigItem warpItem) { + public Builder warpItem(ConfigItem warpItem) { this.warpItem = warpItem; - return this; } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpServiceImpl.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpServiceImpl.java index 97f0c6d80..f74d7215f 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpServiceImpl.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpServiceImpl.java @@ -22,14 +22,14 @@ class WarpServiceImpl implements WarpService { private final WarpRepository warpRepository; @Inject - private WarpServiceImpl(WarpRepository warpRepository) { + WarpServiceImpl(WarpRepository warpRepository) { this.warpRepository = warpRepository; + this.loadWarps(); + } - warpRepository.getWarps().thenAcceptAsync(warps -> { - for (Warp warp : warps) { - this.warps.put(warp.getName(), warp); - } - }); + private void loadWarps() { + this.warpRepository.getWarps() + .thenAcceptAsync(warps -> warps.forEach(warp -> this.warps.put(warp.getName(), warp))); } @Override @@ -43,19 +43,19 @@ public Warp createWarp(String name, Location location) { } @Override - public void removeWarp(String warp) { - Warp remove = this.warps.remove(warp); - if (remove == null) { - return; - } + public void removeWarp(String warpName) { + Warp removed = this.warps.remove(warpName); - this.warpRepository.removeWarp(remove.getName()); + if (removed != null) { + this.warpRepository.removeWarp(removed.getName()); + } } @Override public Warp addPermissions(String warpName, String... permissions) { Warp warp = this.modifyPermissions(warpName, perms -> perms.addAll(List.of(permissions))); this.warpRepository.saveWarp(warp); + return warp; } @@ -63,11 +63,13 @@ public Warp addPermissions(String warpName, String... permissions) { public Warp removePermissions(String warpName, String... permissions) { Warp warp = this.modifyPermissions(warpName, perms -> perms.removeAll(List.of(permissions))); this.warpRepository.saveWarp(warp); + return warp; } private Warp modifyPermissions(String warpName, Consumer> modifier) { Warp warp = this.warps.get(warpName); + if (warp == null) { throw new IllegalArgumentException("Warp " + warpName + " does not exist"); } @@ -76,10 +78,9 @@ private Warp modifyPermissions(String warpName, Consumer> modifier) modifier.accept(updatedPermissions); Warp updatedWarp = new WarpImpl( - warp.getName(), - PositionAdapter.convert(warp.getLocation()), - updatedPermissions - ); + warp.getName(), + PositionAdapter.convert(warp.getLocation()), + updatedPermissions); this.warps.put(warpName, updatedWarp); return updatedWarp; diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpSettings.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpSettings.java index a07f7cd89..d272817f6 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpSettings.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpSettings.java @@ -1,11 +1,22 @@ package com.eternalcode.core.feature.warp; +import org.bukkit.Material; +import java.time.Duration; + public interface WarpSettings { + String itemTexture(); - org.bukkit.Material itemMaterial(); + + Material itemMaterial(); + String itemLore(); + String itemNamePrefix(); + boolean autoAddNewWarps(); + boolean inventoryEnabled(); - java.time.Duration teleportTimeToWarp(); + + Duration teleportTimeToWarp(); + } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpTeleportService.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpTeleportService.java index 66cd6085c..51d8c6f43 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpTeleportService.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/WarpTeleportService.java @@ -10,17 +10,14 @@ import com.eternalcode.core.feature.warp.event.WarpTeleportEvent; import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.injector.annotations.component.Service; -import java.time.Duration; -import java.util.UUID; import org.bukkit.Location; import org.bukkit.entity.Player; +import java.time.Duration; +import java.util.UUID; + @Service -@PermissionDocs( - name = "Warp bypass", - description = "Allows player to bypass warp teleport time.", - permission = WarpTeleportService.WARP_BYPASS -) +@PermissionDocs(name = "Warp bypass", description = "Allows player to bypass warp teleport time.", permission = WarpTeleportService.WARP_BYPASS) public class WarpTeleportService { static final String WARP_BYPASS = "eternalcore.warp.bypass"; @@ -31,41 +28,49 @@ public class WarpTeleportService { @Inject public WarpTeleportService( - TeleportTaskService teleportTaskService, - WarpSettings warpSettings, - EventCaller eventCaller - ) { + TeleportTaskService teleportTaskService, + WarpSettings warpSettings, + EventCaller eventCaller) { this.teleportTaskService = teleportTaskService; this.warpSettings = warpSettings; this.eventCaller = eventCaller; } public void teleport(Player player, Warp warp) { - Duration teleportTime = player.hasPermission(WARP_BYPASS) - ? Duration.ZERO - : this.warpSettings.teleportTimeToWarp(); + Duration teleportTime = this.calculateTeleportTime(player); - PreWarpTeleportEvent pre = this.eventCaller.callEvent(new PreWarpTeleportEvent(player, warp, teleportTime)); + PreWarpTeleportEvent event = this.eventCaller.callEvent(new PreWarpTeleportEvent(player, warp, teleportTime)); - if (pre.isCancelled()) { + if (event.isCancelled()) { return; } - Warp destinationWarp = pre.getWarp(); - Location destination = pre.getDestination(); - Position destinationLocation = PositionAdapter.convert(destination); - Position playerLocation = PositionAdapter.convert(player.getLocation()); + this.processTeleport(player, event.getWarp(), event.getDestination(), event.getTeleportTime()); + } + + private Duration calculateTeleportTime(Player player) { + if (player.hasPermission(WARP_BYPASS)) { + return Duration.ZERO; + } + + return this.warpSettings.teleportTimeToWarp(); + } + + private void processTeleport(Player player, Warp warp, Location destination, Duration duration) { + Position destinationPosition = PositionAdapter.convert(destination); + Position playerPosition = PositionAdapter.convert(player.getLocation()); UUID uniqueId = player.getUniqueId(); Teleport teleport = this.teleportTaskService.createTeleport( - uniqueId, - playerLocation, - destinationLocation, - pre.getTeleportTime() - ); + uniqueId, + playerPosition, + destinationPosition, + duration); teleport.getResult().whenComplete((result, throwable) -> { - this.eventCaller.callEvent(new WarpTeleportEvent(player, destinationWarp, destination)); + if (throwable == null) { + this.eventCaller.callEvent(new WarpTeleportEvent(player, warp, destination)); + } }); } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/command/DelWarpCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/command/DelWarpCommand.java index 54aaf7e91..fc2a885c0 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/command/DelWarpCommand.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/command/DelWarpCommand.java @@ -2,7 +2,7 @@ import com.eternalcode.annotations.scan.command.DescriptionDocs; import com.eternalcode.core.feature.warp.Warp; -import com.eternalcode.core.feature.warp.WarpInventory; +import com.eternalcode.core.feature.warp.inventory.WarpInventory; import com.eternalcode.core.feature.warp.WarpService; import com.eternalcode.core.injector.annotations.Inject; import com.eternalcode.core.notice.NoticeService; diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/command/SetWarpCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/command/SetWarpCommand.java index 8698a24ca..9ac1073a2 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/command/SetWarpCommand.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/command/SetWarpCommand.java @@ -2,7 +2,7 @@ import com.eternalcode.annotations.scan.command.DescriptionDocs; import com.eternalcode.core.feature.warp.Warp; -import com.eternalcode.core.feature.warp.WarpInventory; +import com.eternalcode.core.feature.warp.inventory.WarpInventory; import com.eternalcode.core.feature.warp.WarpService; import com.eternalcode.core.feature.warp.WarpSettings; import com.eternalcode.core.injector.annotations.Inject; diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/command/WarpCommand.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/command/WarpCommand.java index 18a21392a..592313bca 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/command/WarpCommand.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/command/WarpCommand.java @@ -3,7 +3,7 @@ import com.eternalcode.annotations.scan.command.DescriptionDocs; import com.eternalcode.core.configuration.implementation.PluginConfiguration; import com.eternalcode.core.feature.warp.Warp; -import com.eternalcode.core.feature.warp.WarpInventory; +import com.eternalcode.core.feature.warp.inventory.WarpInventory; import com.eternalcode.core.feature.warp.WarpService; import com.eternalcode.core.feature.warp.WarpSettings; import com.eternalcode.core.feature.warp.WarpTeleportService; @@ -68,7 +68,7 @@ void warp(@Sender Player player) { return; } - this.warpInventory.openInventory(player); + this.warpInventory.open(player); } @Execute(name = "warp") diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/inventory/WarpInventory.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/inventory/WarpInventory.java new file mode 100644 index 000000000..7f19752fc --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/inventory/WarpInventory.java @@ -0,0 +1,303 @@ +package com.eternalcode.core.feature.warp.inventory; + +import com.eternalcode.commons.adventure.AdventureUtil; +import com.eternalcode.commons.concurrent.FutureHandler; +import com.eternalcode.commons.scheduler.Scheduler; +import com.eternalcode.core.configuration.contextual.ConfigItem; +import com.eternalcode.core.feature.warp.Warp; +import com.eternalcode.core.feature.warp.WarpInventoryItem; +import com.eternalcode.core.feature.warp.WarpService; +import com.eternalcode.core.feature.warp.WarpSettings; +import com.eternalcode.core.feature.warp.WarpTeleportService; +import com.eternalcode.core.injector.annotations.Inject; +import com.eternalcode.core.injector.annotations.component.Service; +import dev.triumphteam.gui.builder.item.BaseItemBuilder; +import dev.triumphteam.gui.builder.item.ItemBuilder; +import dev.triumphteam.gui.guis.Gui; +import dev.triumphteam.gui.guis.GuiItem; +import java.util.Collections; +import java.util.Comparator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.concurrent.CompletableFuture; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import org.bukkit.Material; +import org.bukkit.Server; +import org.bukkit.entity.Player; + +@Service +public class WarpInventory { + + private static final int GUI_ITEM_SLOT_WITH_TOP_BORDER = 9; + private static final int GUI_ITEM_SLOT_WITHOUT_BORDER = 0; + private static final int GUI_ITEM_SLOT_WITH_ALL_BORDER = 1; + private static final int GUI_ITEM_SLOT_WITH_BORDER = 10; + + private static final int GUI_ROW_SIZE_WITHOUT_BORDER = 9; + private static final int GUI_ROW_SIZE_WITH_BORDER = 7; + + private static final int BORDER_ROW_COUNT = 2; + private static final int UGLY_BORDER_ROW_COUNT = 1; + + private final WarpService warpService; + private final Server server; + private final MiniMessage miniMessage; + private final WarpTeleportService warpTeleportService; + private final WarpSettings warpSettings; + private final Scheduler scheduler; + private final WarpInventoryConfigService warpInventoryConfigService; + + @Inject + public WarpInventory( + WarpService warpService, + Server server, + MiniMessage miniMessage, + WarpTeleportService warpTeleportService, + WarpSettings warpSettings, + Scheduler scheduler, + WarpInventoryConfigService warpInventoryConfigService) { + this.warpService = warpService; + this.server = server; + this.miniMessage = miniMessage; + this.warpTeleportService = warpTeleportService; + this.warpSettings = warpSettings; + this.scheduler = scheduler; + this.warpInventoryConfigService = warpInventoryConfigService; + } + + public void open(Player player) { + this.warpInventoryConfigService.getWarpInventoryData() + .thenAccept(warpData -> { + this.scheduler.run(() -> { + Gui gui = this.create(player, warpData); + gui.open(player); + }); + }) + .exceptionally(FutureHandler::handleException); + } + + private Gui create(Player player, WarpInventoryConfigService.WarpInventoryConfigData warpData) { + int rows = calculateRowsCount(warpData); + + Gui gui = Gui.gui() + .title(this.miniMessage.deserialize(warpData.title())) + .rows(rows) + .disableAllInteractions() + .create(); + + this.createWarpItems(player, warpData, gui); + this.createBorder(warpData, gui); + this.createDecorations(warpData, gui); + + return gui; + } + + private int calculateRowsCount(WarpInventoryConfigService.WarpInventoryConfigData warpData) { + int size = warpData.items().size(); + + if (!warpData.border().enabled()) { + return (size + GUI_ROW_SIZE_WITHOUT_BORDER - 1) / GUI_ROW_SIZE_WITHOUT_BORDER; + } + + return switch (warpData.border().fillType()) { + case BORDER, ALL -> (size + GUI_ROW_SIZE_WITH_BORDER - 1) / GUI_ROW_SIZE_WITH_BORDER + BORDER_ROW_COUNT; + case TOP, BOTTOM -> + (size + GUI_ROW_SIZE_WITHOUT_BORDER - 1) / GUI_ROW_SIZE_WITHOUT_BORDER + UGLY_BORDER_ROW_COUNT; + }; + } + + private void createBorder(WarpInventoryConfigService.WarpInventoryConfigData warpData, Gui gui) { + if (!warpData.border().enabled()) { + return; + } + + WarpInventoryConfig.BorderSection borderSection = warpData.border(); + GuiItem guiItem = createBorderItem(borderSection); + + switch (borderSection.fillType()) { + case BORDER -> gui.getFiller().fillBorder(guiItem); + case ALL -> gui.getFiller().fill(guiItem); + case TOP -> gui.getFiller().fillTop(guiItem); + case BOTTOM -> gui.getFiller().fillBottom(guiItem); + } + } + + private GuiItem createBorderItem(WarpInventoryConfig.BorderSection borderSection) { + ItemBuilder borderItem = ItemBuilder.from(borderSection.material()); + + if (!borderSection.name().isBlank()) { + borderItem.name(AdventureUtil.resetItalic(this.miniMessage.deserialize(borderSection.name()))); + } + + if (!borderSection.lore().isEmpty()) { + List loreComponents = borderSection.lore() + .stream() + .map(entry -> AdventureUtil.resetItalic(this.miniMessage.deserialize(entry))) + .toList(); + borderItem.lore(loreComponents); + } + + return new GuiItem(borderItem.build()); + } + + private void createDecorations(WarpInventoryConfigService.WarpInventoryConfigData warpData, Gui gui) { + for (ConfigItem item : warpData.decorationItems().items()) { + BaseItemBuilder baseItemBuilder = this.createItem(item); + GuiItem guiItem = baseItemBuilder.asGuiItem(); + + guiItem.setAction(event -> { + Player player = (Player) event.getWhoClicked(); + this.executeDecorationCommands(player, item); + }); + + gui.setItem(item.slot(), guiItem); + } + } + + private void executeDecorationCommands(Player player, ConfigItem item) { + if (item.commands.isEmpty()) { + return; + } + + for (String command : item.commands) { + this.server.dispatchCommand(player, command); + } + player.closeInventory(); + } + + private void createWarpItems(Player player, WarpInventoryConfigService.WarpInventoryConfigData warpData, Gui gui) { + warpData.items().values().forEach(item -> { + Optional warpOptional = this.warpService.findWarp(item.warpName()); + + if (warpOptional.isEmpty()) { + return; + } + + Warp warp = warpOptional.get(); + + if (!warp.hasPermissions(player)) { + return; + } + + this.createWarpGuiItem(player, warp, item.warpItem(), gui); + }); + } + + private void createWarpGuiItem(Player player, Warp warp, ConfigItem warpItem, Gui gui) { + BaseItemBuilder baseItemBuilder = this.createItem(warpItem); + GuiItem guiItem = baseItemBuilder.asGuiItem(); + + guiItem.setAction(event -> { + if (!warp.hasPermissions(player)) { + return; + } + + player.closeInventory(); + this.warpTeleportService.teleport(player, warp); + }); + + gui.setItem(warpItem.slot(), guiItem); + } + + private BaseItemBuilder createItem(ConfigItem item) { + Component name = AdventureUtil.resetItalic(this.miniMessage.deserialize(item.name())); + + List lore = item.lore() + .stream() + .map(entry -> AdventureUtil.resetItalic(this.miniMessage.deserialize(entry))) + .toList(); + + if (item.material() == Material.PLAYER_HEAD && !item.texture().isEmpty()) { + return ItemBuilder.skull() + .name(name) + .lore(lore) + .texture(item.texture()) + .glow(item.glow()); + } + + return ItemBuilder.from(item.material()) + .name(name) + .lore(lore) + .glow(item.glow()); + } + + public CompletableFuture addWarp(Warp warp) { + if (!this.warpService.exists(warp.getName())) { + return CompletableFuture.completedFuture(null); + } + + return this.warpInventoryConfigService.getWarpInventoryData() + .thenCompose(warpData -> { + int slot = getSlot(warpData); + + WarpInventoryItem warpInventoryItem = createWarpInventoryItem(warp, slot); + + return this.warpInventoryConfigService.addWarpItem(warp.getName(), warpInventoryItem); + }) + .exceptionally(FutureHandler::handleException); + } + + private WarpInventoryItem createWarpInventoryItem(Warp warp, int slot) { + return WarpInventoryItem.builder() + .warpName(warp.getName()) + .warpItem(ConfigItem.builder() + .withName(this.warpSettings.itemNamePrefix() + warp.getName()) + .withLore(Collections.singletonList(this.warpSettings.itemLore())) + .withMaterial(this.warpSettings.itemMaterial()) + .withTexture(this.warpSettings.itemTexture()) + .withSlot(slot) + .withGlow(true) + .build()) + .build(); + } + + private int getSlot(WarpInventoryConfigService.WarpInventoryConfigData warpData) { + int size = warpData.items().size(); + if (!warpData.border().enabled()) { + return GUI_ITEM_SLOT_WITHOUT_BORDER + size; + } + + return switch (warpData.border().fillType()) { + case BORDER -> GUI_ITEM_SLOT_WITH_BORDER + size + ((size / GUI_ROW_SIZE_WITH_BORDER) * 2); + case ALL -> GUI_ITEM_SLOT_WITH_ALL_BORDER + size + ((size / GUI_ROW_SIZE_WITH_BORDER) * 2); + case TOP -> GUI_ITEM_SLOT_WITH_TOP_BORDER + size; + case BOTTOM -> size; + }; + } + + public CompletableFuture removeWarp(String warpName) { + Map items = this.warpInventoryConfigService.getWarpItems(); + if (!items.containsKey(warpName)) { + return CompletableFuture.completedFuture(null); + } + + return this.warpInventoryConfigService.removeWarpItem(warpName) + .thenCompose(removed -> { + if (removed != null) { + return this.shiftWarpItems(removed, items) + .thenCompose(v -> this.warpInventoryConfigService.save()); + } + return CompletableFuture.completedFuture(null); + }); + } + + private CompletableFuture shiftWarpItems(WarpInventoryItem removed, Map items) { + int removedSlot = removed.warpItem().slot; + + List itemsToShift = items.values().stream() + .filter(item -> item.warpItem().slot > removedSlot) + .sorted(Comparator.comparingInt(item -> item.warpItem().slot)) + .toList(); + + int currentShift = removedSlot; + for (WarpInventoryItem item : itemsToShift) { + int nextShift = item.warpItem().slot; + item.warpItem().slot = currentShift; + currentShift = nextShift; + } + + return CompletableFuture.completedFuture(null); + } +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/inventory/WarpInventoryConfig.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/inventory/WarpInventoryConfig.java new file mode 100644 index 000000000..a0284a409 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/inventory/WarpInventoryConfig.java @@ -0,0 +1,89 @@ +package com.eternalcode.core.feature.warp.inventory; + +import com.eternalcode.core.configuration.AbstractConfigurationFile; +import com.eternalcode.core.configuration.contextual.ConfigItem; +import com.eternalcode.core.feature.warp.WarpInventoryItem; +import com.eternalcode.core.injector.annotations.component.ConfigurationFile; +import eu.okaeri.configs.OkaeriConfig; +import eu.okaeri.configs.annotation.Comment; +import java.io.File; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import lombok.Getter; +import lombok.experimental.Accessors; +import org.bukkit.Material; + +@Getter +@Accessors(fluent = true) +@ConfigurationFile +public class WarpInventoryConfig extends AbstractConfigurationFile { + + @Comment({ + "# Warp inventory configuration", + "# This file contains the GUI layout and item definitions for the warp inventory", + "# Text content (titles, names, lore) should be configured in language files" + }) + public DisplaySection display = new DisplaySection(); + + @Comment({ + "# Border configuration for the warp inventory" + }) + public BorderSection border = new BorderSection(); + + @Comment({ + "# Decoration items that can be placed in the inventory" + }) + public DecorationItemsSection decorationItems = new DecorationItemsSection(); + + @Comment({ + "# Warp items configuration - maps warp names to their inventory representation" + }) + public Map items = new HashMap<>(); + + @Override + public File getConfigFile(File dataFolder) { + return new File(dataFolder, "warp-inventory.yml"); + } + + public enum FillType { + BORDER, + ALL, + TOP, + BOTTOM + } + + @Getter + @Accessors(fluent = true) + public static class DisplaySection extends OkaeriConfig { + @Comment("# Title of the warp inventory GUI") + public String title = "» Available warps:"; + } + + @Getter + @Accessors(fluent = true) + public static class BorderSection extends OkaeriConfig { + @Comment("# Changes of border section may affect the appearance of the GUI inventory, after changes adjust slots of existing items.") + public boolean enabled = true; + + @Comment("# Material for border items") + public Material material = Material.GRAY_STAINED_GLASS_PANE; + + @Comment("# How to fill the border") + public FillType fillType = FillType.BORDER; + + @Comment("# Display name for border items (empty for no name)") + public String name = ""; + + @Comment("# Lore lines for border items") + public List lore = Collections.emptyList(); + } + + @Getter + @Accessors(fluent = true) + public static class DecorationItemsSection extends OkaeriConfig { + @Comment("# List of decoration items to display in the inventory") + public List items = List.of(); + } +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/inventory/WarpInventoryConfigService.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/inventory/WarpInventoryConfigService.java new file mode 100644 index 000000000..cb423e286 --- /dev/null +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/inventory/WarpInventoryConfigService.java @@ -0,0 +1,90 @@ +package com.eternalcode.core.feature.warp.inventory; + +import com.eternalcode.commons.scheduler.Scheduler; +import com.eternalcode.core.configuration.ConfigurationManager; +import com.eternalcode.core.feature.warp.WarpInventoryItem; +import com.eternalcode.core.feature.warp.inventory.WarpInventoryConfig.BorderSection; +import com.eternalcode.core.feature.warp.inventory.WarpInventoryConfig.DecorationItemsSection; +import com.eternalcode.core.injector.annotations.Inject; +import com.eternalcode.core.injector.annotations.component.Service; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +@Service +public class WarpInventoryConfigService { + + private final WarpInventoryConfig config; + private final ConfigurationManager configurationManager; + private final Scheduler scheduler; + + @Inject + public WarpInventoryConfigService( + ConfigurationManager configurationManager, + WarpInventoryConfig config, + Scheduler scheduler) { + this.configurationManager = configurationManager; + this.config = config; + this.scheduler = scheduler; + } + + public CompletableFuture getWarpInventoryData() { + return scheduler.>completeAsync(() -> new HashMap<>(config.items())) + .thenApply(items -> new WarpInventoryConfigData( + config.display().title(), + config.border(), + config.decorationItems(), + items) + ); + } + + public CompletableFuture addWarpItem(String warpName, WarpInventoryItem item) { + return scheduler.completeAsync(() -> { + config.items().put(warpName, item); + configurationManager.save(config); + return null; + }); + } + + public CompletableFuture removeWarpItem(String warpName) { + if (isBlank(warpName)) { + return CompletableFuture.completedFuture(null); + } + + return scheduler.completeAsync(() -> config.items().get(warpName)) + .thenCompose(item -> { + if (item == null) { + return CompletableFuture.completedFuture(null); + } + + return scheduler.completeAsync(() -> { + config.items().remove(warpName); + configurationManager.save(config); + return null; + }) + .thenApply(v -> item); + }); + } + + public CompletableFuture save() { + return scheduler.completeAsync(() -> { + configurationManager.save(config); + return null; + }); + } + + public Map getWarpItems() { + return new HashMap<>(config.items()); + } + + private boolean isBlank(String value) { + return value == null || value.trim().isEmpty(); + } + + public record WarpInventoryConfigData( + String title, + BorderSection border, + DecorationItemsSection decorationItems, + Map items) { + } +} diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/messages/ENWarpMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/messages/ENWarpMessages.java index b563c772f..22b99c290 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/messages/ENWarpMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/messages/ENWarpMessages.java @@ -1,17 +1,10 @@ package com.eternalcode.core.feature.warp.messages; -import com.eternalcode.core.configuration.contextual.ConfigItem; -import com.eternalcode.core.feature.warp.WarpInventoryItem; import com.eternalcode.multification.notice.Notice; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.Comment; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import lombok.Getter; import lombok.experimental.Accessors; -import org.bukkit.Material; @Getter @Accessors(fluent = true) @@ -41,45 +34,4 @@ public class ENWarpMessages extends OkaeriConfig implements WarpMessages { @Comment({ " ", "# {WARPS} - List of warps (separated by commas)" }) Notice available = Notice.chat("Available warps: {WARPS}"); - - @Comment({ " ", "# Settings for warp inventory" }) - public ENWarpInventory warpInventory = new ENWarpInventory(); - - @Getter - @Accessors(fluent = true) - public static class ENWarpInventory extends OkaeriConfig implements WarpInventorySection { - public String title = "» Available warps:"; - - @Comment({ " ", - "# Warps located inside GUI inventory can be customized here. More warps will be added on creation with /setwarp command. " }) - public Map items = new HashMap<>(); - - public void setItems(Map items) { - this.items = items; - } - - public ENWarpInventory.ENBorderSection border = new ENWarpInventory.ENBorderSection(); - public ENWarpInventory.ENDecorationItemsSection decorationItems = new ENWarpInventory.ENDecorationItemsSection(); - - @Getter - public static class ENBorderSection extends OkaeriConfig implements BorderSection { - - @Comment({ " ", - "# Changes of border section may affect the appearance of the GUI inventory, after changes adjust slots of existing items." }) - public boolean enabled = true; - - public Material material = Material.GRAY_STAINED_GLASS_PANE; - - public FillType fillType = FillType.BORDER; - - public String name = ""; - - public List lore = Collections.emptyList(); - } - - @Getter - public static class ENDecorationItemsSection extends OkaeriConfig implements DecorationItemsSection { - public List items = List.of(); - } - } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/messages/PLWarpMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/messages/PLWarpMessages.java index 66da398d7..d30a8d94a 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/messages/PLWarpMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/messages/PLWarpMessages.java @@ -1,17 +1,10 @@ package com.eternalcode.core.feature.warp.messages; -import com.eternalcode.core.configuration.contextual.ConfigItem; -import com.eternalcode.core.feature.warp.WarpInventoryItem; import com.eternalcode.multification.notice.Notice; import eu.okaeri.configs.OkaeriConfig; import eu.okaeri.configs.annotation.Comment; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import lombok.Getter; import lombok.experimental.Accessors; -import org.bukkit.Material; @Getter @Accessors(fluent = true) @@ -39,47 +32,4 @@ public class PLWarpMessages extends OkaeriConfig implements WarpMessages { @Comment({ " ", "# {WARPS} - Lista dostępnych warpów" }) Notice available = Notice.chat("Dostepne warpy: {WARPS}!"); - - @Comment({ " ", "# Ustawienia gui listy dostępnych warpów" }) - public PLWarpInventory warpInventory = new PLWarpInventory(); - - @Getter - @Accessors(fluent = true) - public static class PLWarpInventory extends OkaeriConfig implements WarpInventorySection { - public String title = "» Lista dostępnych warpów"; - - @Comment({ - " ", - "# Poniższa lista określa przedmioty w GUI, które są wyświetlane w liście dostępnych warpów.", - "# Możesz edytować przedmioty, a dodawanie kolejnych warpów następuje automatycznie za pomocą komendy /setwarp", - }) - public Map items = new HashMap<>(); - - public void setItems(Map items) { - this.items = items; - } - - public PLWarpInventory.PLBorderSection border = new PLWarpInventory.PLBorderSection(); - public PLWarpInventory.PLDecorationItemsSection decorationItems = new PLWarpInventory.PLDecorationItemsSection(); - - @Getter - public static class PLBorderSection extends OkaeriConfig implements BorderSection { - @Comment({ " ", - "# Zmiany w tej sekcji mogą wpłynąć na wygląd GUI, zwróć uwagę na zmiany slotów przedmiotów w GUI." }) - public boolean enabled = true; - - public Material material = Material.GRAY_STAINED_GLASS_PANE; - - public FillType fillType = FillType.BORDER; - - public String name = ""; - - public List lore = Collections.emptyList(); - } - - @Getter - public static class PLDecorationItemsSection extends OkaeriConfig implements DecorationItemsSection { - public List items = List.of(); - } - } } diff --git a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/messages/WarpMessages.java b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/messages/WarpMessages.java index 6c5f9f7f5..9eabdfaa0 100644 --- a/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/messages/WarpMessages.java +++ b/eternalcore-core/src/main/java/com/eternalcode/core/feature/warp/messages/WarpMessages.java @@ -1,12 +1,6 @@ package com.eternalcode.core.feature.warp.messages; -import com.eternalcode.core.configuration.contextual.ConfigItem; -import com.eternalcode.core.feature.warp.WarpInventoryItem; import com.eternalcode.multification.notice.Notice; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import org.bukkit.Material; public interface WarpMessages { Notice warpAlreadyExists(); @@ -25,51 +19,4 @@ public interface WarpMessages { Notice noPermissionsProvided(); Notice missingWarpArgument(); Notice missingPermissionArgument(); - - WarpInventorySection warpInventory(); - - interface WarpInventorySection { - String title(); - - Map items(); - void setItems(Map items); - - default void addItem(String name, WarpInventoryItem item) { - Map items = new HashMap<>(this.items()); - items.put(name, item); - - this.setItems(items); - } - - default WarpInventoryItem removeItem(String name) { - Map items = new HashMap<>(this.items()); - WarpInventoryItem removed = items.remove(name); - - this.setItems(items); - return removed; - } - - BorderSection border(); - DecorationItemsSection decorationItems(); - - interface BorderSection { - boolean enabled(); - - Material material(); - - FillType fillType(); - - String name(); - - List lore(); - - enum FillType { - TOP, BOTTOM, BORDER, ALL - } - } - - interface DecorationItemsSection { - List items(); - } - } } diff --git a/eternalcore-plugin/build.gradle.kts b/eternalcore-plugin/build.gradle.kts index cf7cd04f4..96c522848 100644 --- a/eternalcore-plugin/build.gradle.kts +++ b/eternalcore-plugin/build.gradle.kts @@ -36,7 +36,7 @@ dependencies { tasks { runServer { - minecraftVersion("1.21.10") + minecraftVersion("1.21.11") downloadPlugins.modrinth("luckperms", "v${Versions.LUCKPERMS}-bukkit") } }