feature: add itemlore module

This commit is contained in:
AXOLOTsh
2026-09-14 17:01:14 +03:00
parent 6853a16ddb
commit 0e81e7d92e
5 changed files with 258 additions and 0 deletions

View File

@@ -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<Key, List<Component>> 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<Component> 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<Key, LoreEntry> loreEntries(ItemStack item) {
return new PersistentMap<Key, LoreEntry>(
configuration.loreEntries(), LoreEntryType.INSTANCE, item,
(entry) -> Map.entry(entry.id(), entry), LinkedHashMap::new);
}
}

View File

@@ -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);
}
}

View File

@@ -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<Key, List<Component>> 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<Key, List<Component>> innerMap, ItemStack item) {
super(innerMap);
this.factory = factory;
this.item = item;
}
@Override
public void close() {
List<Component> lore = new ArrayList<>();
Map<Key, LoreEntry> loreMap = new LinkedHashMap<>();
var position = 0;
for (Map.Entry<Key, List<Component>> 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);
}
}

View File

@@ -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<Component> get(List<Component> lore) {
Objects.requireNonNull(lore);
var toIndex = Math.min(endPosition(), lore.size());
var fromIndex = Math.min(position, toIndex);
return new ArrayList<>(lore.subList(fromIndex, toIndex));
}
}

View File

@@ -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<byte[], LoreEntry> {
public static final LoreEntryType INSTANCE = new LoreEntryType();
private LoreEntryType() {
};
@Override
public @NotNull Class<byte[]> getPrimitiveType() {
return byte[].class;
}
@Override
public @NotNull Class<LoreEntry> 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);
}
}
}