diff --git a/plugin-utils/src/main/java/org/axolotsh/pluginutils/modules/itemlore/ItemLoreFactory.java b/plugin-utils/src/main/java/org/axolotsh/pluginutils/modules/itemlore/ItemLoreFactory.java new file mode 100644 index 0000000..a68f26a --- /dev/null +++ b/plugin-utils/src/main/java/org/axolotsh/pluginutils/modules/itemlore/ItemLoreFactory.java @@ -0,0 +1,92 @@ +package org.axolotsh.pluginutils.modules.itemlore; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.axolotsh.pluginutils.modules.itemlore.entities.LoreEntry; +import org.axolotsh.pluginutils.modules.itemlore.entities.LoreEntryType; +import org.axolotsh.pluginutils.persistent_data_container.PersistentMap; +import org.bukkit.NamespacedKey; +import org.bukkit.inventory.ItemStack; +import org.bukkit.plugin.java.JavaPlugin; + +import com.google.inject.Inject; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NonNull; +import lombok.experimental.Accessors; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; + +@Getter +@Accessors(chain = true, fluent = true) +@AllArgsConstructor +public class ItemLoreFactory { + private final Configuration configuration; + + @Getter + @Accessors(chain = true, fluent = true) + @Builder(toBuilder = true) + public static class Configuration { + @NonNull + private String namespace; + + @Builder.Default + private String loreEntriesKey = "lore_entries"; + + public NamespacedKey loreEntries() { + return new NamespacedKey(namespace, loreEntriesKey); + }; + + public static Configuration fromPlugin(JavaPlugin plugin) { + return builder() + .namespace(plugin.namespace()) + .build(); + } + } + + @Inject + public ItemLoreFactory(JavaPlugin plugin) { + this(Configuration.fromPlugin(plugin)); + } + + public ItemLoreService service(ItemStack item) { + Map> lore = new LinkedHashMap<>(); + var loreMap = loreEntries(item).get(); + var itemLore = item.lore(); + if (itemLore == null) + itemLore = List.of(); + + var usedIndices = new boolean[itemLore.size()]; + + for (var entry : loreMap.entrySet()) { + var info = entry.getValue(); + lore.put(entry.getKey(), info.get(itemLore)); + + var start = Math.min(info.position(), itemLore.size()); + var end = Math.min(info.endPosition(), itemLore.size()); + for (var i = start; i < end; i++) + usedIndices[i] = true; + } + + List unmappedLines = new ArrayList<>(); + for (int i = 0; i < itemLore.size(); i++) + if (!usedIndices[i]) + unmappedLines.add(itemLore.get(i)); + + if (!unmappedLines.isEmpty()) + lore.put(ItemLoreService.UNKNOWN_LORE_KEY, unmappedLines); + + return new ItemLoreService(this, lore, item); + } + + PersistentMap loreEntries(ItemStack item) { + return new PersistentMap( + configuration.loreEntries(), LoreEntryType.INSTANCE, item, + (entry) -> Map.entry(entry.id(), entry), LinkedHashMap::new); + } +} diff --git a/plugin-utils/src/main/java/org/axolotsh/pluginutils/modules/itemlore/ItemLoreModule.java b/plugin-utils/src/main/java/org/axolotsh/pluginutils/modules/itemlore/ItemLoreModule.java new file mode 100644 index 0000000..6c8c72e --- /dev/null +++ b/plugin-utils/src/main/java/org/axolotsh/pluginutils/modules/itemlore/ItemLoreModule.java @@ -0,0 +1,15 @@ +package org.axolotsh.pluginutils.modules.itemlore; + +import org.axolotsh.pluginutils.dependency_injection.PluginModule; +import org.bukkit.plugin.java.JavaPlugin; + +import com.google.inject.Provides; +import com.google.inject.Singleton; + +public class ItemLoreModule extends PluginModule { + @Provides + @Singleton + protected ItemLoreFactory provideItemLoreFactory(JavaPlugin plugin) { + return new ItemLoreFactory(plugin); + } +} \ No newline at end of file diff --git a/plugin-utils/src/main/java/org/axolotsh/pluginutils/modules/itemlore/ItemLoreService.java b/plugin-utils/src/main/java/org/axolotsh/pluginutils/modules/itemlore/ItemLoreService.java new file mode 100644 index 0000000..8918d89 --- /dev/null +++ b/plugin-utils/src/main/java/org/axolotsh/pluginutils/modules/itemlore/ItemLoreService.java @@ -0,0 +1,51 @@ +package org.axolotsh.pluginutils.modules.itemlore; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +import org.axolotsh.pluginutils.modules.itemlore.entities.LoreEntry; +import org.axolotsh.pluginutils.wrappers.MapWrapper; +import org.bukkit.inventory.ItemStack; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; + +public class ItemLoreService extends MapWrapper> implements AutoCloseable { + public static final Key UNKNOWN_LORE_KEY = Key.key("unknown_lore"); + + private final ItemLoreFactory factory; + private final ItemStack item; + + ItemLoreService(ItemLoreFactory factory, Map> innerMap, ItemStack item) { + super(innerMap); + this.factory = factory; + this.item = item; + } + + @Override + public void close() { + List lore = new ArrayList<>(); + Map loreMap = new LinkedHashMap<>(); + + var position = 0; + for (Map.Entry> entry : innerMap.entrySet()) { + var key = entry.getKey(); + var value = entry.getValue(); + + var size = value.size(); + if (!key.equals(UNKNOWN_LORE_KEY)) { + var loreEntry = new LoreEntry(key, position, size); + loreMap.put(key, loreEntry); + } + + lore.addAll(value); + + position += size; + } + + factory.loreEntries(item).set(loreMap); + item.lore(lore); + } +} diff --git a/plugin-utils/src/main/java/org/axolotsh/pluginutils/modules/itemlore/entities/LoreEntry.java b/plugin-utils/src/main/java/org/axolotsh/pluginutils/modules/itemlore/entities/LoreEntry.java new file mode 100644 index 0000000..ac7b815 --- /dev/null +++ b/plugin-utils/src/main/java/org/axolotsh/pluginutils/modules/itemlore/entities/LoreEntry.java @@ -0,0 +1,40 @@ +package org.axolotsh.pluginutils.modules.itemlore.entities; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NonNull; +import lombok.experimental.Accessors; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.text.Component; + +@Getter +@Accessors(chain = true, fluent = true) +@Builder(toBuilder = true) +@EqualsAndHashCode +@AllArgsConstructor +public class LoreEntry { + @NonNull + private Key id; + + private int position; + private int length; + + public int endPosition() { + return position + length; + } + + public List get(List lore) { + Objects.requireNonNull(lore); + + var toIndex = Math.min(endPosition(), lore.size()); + var fromIndex = Math.min(position, toIndex); + + return new ArrayList<>(lore.subList(fromIndex, toIndex)); + } +} diff --git a/plugin-utils/src/main/java/org/axolotsh/pluginutils/modules/itemlore/entities/LoreEntryType.java b/plugin-utils/src/main/java/org/axolotsh/pluginutils/modules/itemlore/entities/LoreEntryType.java new file mode 100644 index 0000000..af1c897 --- /dev/null +++ b/plugin-utils/src/main/java/org/axolotsh/pluginutils/modules/itemlore/entities/LoreEntryType.java @@ -0,0 +1,60 @@ +package org.axolotsh.pluginutils.modules.itemlore.entities; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.DataInputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.bukkit.persistence.PersistentDataType; +import org.jetbrains.annotations.NotNull; + +import net.kyori.adventure.key.Key; + +public class LoreEntryType implements PersistentDataType { + public static final LoreEntryType INSTANCE = new LoreEntryType(); + + private LoreEntryType() { + }; + + @Override + public @NotNull Class getPrimitiveType() { + return byte[].class; + } + + @Override + public @NotNull Class getComplexType() { + return LoreEntry.class; + } + + @Override + public byte @NotNull [] toPrimitive(@NotNull LoreEntry complex, @NotNull PersistentDataAdapterContext context) { + var baos = new ByteArrayOutputStream(); + try (var dos = new DataOutputStream(baos)) { + dos.writeInt(complex.position()); + dos.writeInt(complex.length()); + + var id = complex.id(); + dos.writeUTF(id.asMinimalString()); + } catch (IOException e) { + throw new RuntimeException(e); + } + return baos.toByteArray(); + } + + @Override + public @NotNull LoreEntry fromPrimitive(byte @NotNull [] primitive, @NotNull PersistentDataAdapterContext context) { + var bais = new ByteArrayInputStream(primitive); + try (var dis = new DataInputStream(bais)) { + var position = dis.readInt(); + var length = dis.readInt(); + + var minimalString = dis.readUTF(); + var key = Key.key(minimalString); + return new LoreEntry(key, position, length); + } catch (IOException e) { + throw new RuntimeException(e); + } + } +}