diff --git a/README.md b/README.md index 1afa503..e1803b5 100644 --- a/README.md +++ b/README.md @@ -29,9 +29,9 @@ Pojo Scripts are powered by [MangoScript](https://github.com/MangoPlex/MangoScri + [ ] Stats component + [x] Vanilla attributes + [ ] Hook with other plugins (mainly RPG plugins) - + [ ] Gemstones component + + [x] Gemstones component + [x] Allow other components to modify a component's output (quite complicated I'd say) - + [ ] Item type filtering (eg: only apply effects from `pojo:gemstones` type) + + [x] ~~Item type filtering (eg: only apply effects from `pojo:gemstones` type)~~ Replaced with gemstone slot ID - [ ] Custom blocks - [ ] Scripting support + [x] [MangoScript](https://github.com/MangoPlex/MangoScript) diff --git a/pojo-api/src/main/java/io/github/nahkd123/pojo/api/internal/PojoKeys.java b/pojo-api/src/main/java/io/github/nahkd123/pojo/api/internal/PojoKeys.java index e2339ce..24cc930 100644 --- a/pojo-api/src/main/java/io/github/nahkd123/pojo/api/internal/PojoKeys.java +++ b/pojo-api/src/main/java/io/github/nahkd123/pojo/api/internal/PojoKeys.java @@ -11,9 +11,11 @@ public class PojoKeys { public final NamespacedKey id; public final NamespacedKey displayMode; + public final NamespacedKey seed; public PojoKeys(Plugin plugin) { id = new NamespacedKey(plugin, "id"); displayMode = new NamespacedKey(plugin, "display_mode"); + seed = new NamespacedKey(plugin, "seed"); } } diff --git a/pojo-api/src/main/java/io/github/nahkd123/pojo/api/item/PojoItem.java b/pojo-api/src/main/java/io/github/nahkd123/pojo/api/item/PojoItem.java index d6d787a..113a607 100644 --- a/pojo-api/src/main/java/io/github/nahkd123/pojo/api/item/PojoItem.java +++ b/pojo-api/src/main/java/io/github/nahkd123/pojo/api/item/PojoItem.java @@ -112,6 +112,10 @@ public static boolean isDisplayMode(ItemMeta meta) { return poc.getOrDefault(keys().displayMode, PersistentDataType.BOOLEAN, false); } + public static PojoItem getFrom(UserDefinedId id) { + return PojoInternal.instance().getItems().get(id); + } + /** *

* Get the {@link PojoItem} by getting ID from {@link ItemMeta}. diff --git a/pojo-api/src/main/java/io/github/nahkd123/pojo/api/item/standard/StandardPojoItem.java b/pojo-api/src/main/java/io/github/nahkd123/pojo/api/item/standard/StandardPojoItem.java index fcd5a60..6586fdb 100644 --- a/pojo-api/src/main/java/io/github/nahkd123/pojo/api/item/standard/StandardPojoItem.java +++ b/pojo-api/src/main/java/io/github/nahkd123/pojo/api/item/standard/StandardPojoItem.java @@ -8,6 +8,7 @@ import org.bukkit.configuration.ConfigurationSection; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.persistence.PersistentDataContainer; import io.github.nahkd123.pojo.api.internal.PojoInternal; import io.github.nahkd123.pojo.api.item.PojoItem; @@ -101,10 +102,10 @@ public ItemStack createNew(boolean displayMode) { LoreSorter lore = new LoreSorter(loreSections); for (Component component : components) { - Object obj = dataHolder.get(component); - mat = component.applyMaterial(obj, mat, displayMode); - name = component.applyName(obj, name, displayMode); - component.applyLore(obj, lore, displayMode); + Object data = dataHolder.get(component); + mat = component.applyMaterial(data, mat, displayMode); + name = component.applyName(data, name, displayMode); + component.applyLore(data, lore, displayMode); } List loreList = lore.build(); @@ -146,7 +147,7 @@ public ItemMeta updateMeta(ItemMeta source, boolean displayMode) { } List loreList = lore.build(); - if (name != null) source.setLocalizedName(name); + if (name != null) source.setDisplayName(name); if (loreList.size() > 0) source.setLore(loreList); // 3. Post display (storing data is not included atm) @@ -155,22 +156,26 @@ public ItemMeta updateMeta(ItemMeta source, boolean displayMode) { return source; } + public ComponentDataHolder loadDataFrom(ItemMeta meta, boolean manipulate) { + return loadDataFrom(meta.getPersistentDataContainer(), manipulate); + } + /** *

- * Load all component data from {@link ItemMeta}. + * Load all component data from {@link PersistentDataContainer}. *

* - * @param meta The meta to load. + * @param meta The persistent data container to load. * @param manipulate {@code true} will allows components to manipulate others, * like applying to computed stats for example. * @return Loaded component data. */ - @SuppressWarnings({ "rawtypes", "unchecked" }) - public ComponentDataHolder loadDataFrom(ItemMeta meta, boolean manipulate) { + @SuppressWarnings({ "unchecked", "rawtypes" }) + public ComponentDataHolder loadDataFrom(PersistentDataContainer container, boolean manipulate) { ComponentDataHolder dataHolder = ComponentDataHolder.newHolder(); for (Component component : components) { - Object data = component.loadDataFrom(meta.getPersistentDataContainer()); + Object data = component.loadDataFrom(container); dataHolder.addRaw(component, data); } @@ -192,16 +197,21 @@ public void saveDataTo(ItemMeta meta, ComponentDataHolder dataHolder) { } List loreList = lore.build(); - if (name != null) meta.setLocalizedName(name); + if (name != null) meta.setDisplayName(name); if (loreList.size() > 0) meta.setLore(loreList); // 2. Post display & store data for (Component component : components) component.applyPostDisplay(dataHolder.get(component), meta, displayMode); + saveDataTo(meta.getPersistentDataContainer(), dataHolder); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + public void saveDataTo(PersistentDataContainer container, ComponentDataHolder dataHolder) { for (Component component : components) { Object obj = dataHolder.get(component); - component.storeDataTo(meta.getPersistentDataContainer(), obj); + component.storeDataTo(container, obj); } } } diff --git a/pojo-api/src/main/java/io/github/nahkd123/pojo/api/item/standard/component/ComponentDataHolder.java b/pojo-api/src/main/java/io/github/nahkd123/pojo/api/item/standard/component/ComponentDataHolder.java index 7e6796a..a464cfb 100644 --- a/pojo-api/src/main/java/io/github/nahkd123/pojo/api/item/standard/component/ComponentDataHolder.java +++ b/pojo-api/src/main/java/io/github/nahkd123/pojo/api/item/standard/component/ComponentDataHolder.java @@ -36,7 +36,7 @@ public , T> void add(C component, T data) { List list = mapOfLists.get(component.getClass()); if (list == null) mapOfLists.put(component.getClass(), list = new ArrayList()); list.add(data); - mapOfData.put(component, mapOfData); + mapOfData.put(component, data); } @SuppressWarnings("unchecked") diff --git a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/PojoExpansionPlugin.java b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/PojoExpansionPlugin.java index 2625615..462adbb 100644 --- a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/PojoExpansionPlugin.java +++ b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/PojoExpansionPlugin.java @@ -4,22 +4,28 @@ import java.util.HashMap; import java.util.Map; import java.util.Random; +import java.util.function.LongSupplier; +import java.util.function.Supplier; import java.util.random.RandomGenerator; import org.bukkit.NamespacedKey; import org.bukkit.configuration.file.YamlConfiguration; import org.bukkit.plugin.java.JavaPlugin; +import io.github.nahkd123.pojo.expansion.event.InventoryEventsListener; +import io.github.nahkd123.pojo.expansion.item.standard.GemstoneComponent; +import io.github.nahkd123.pojo.expansion.item.standard.GemstoneSlotsComponent; import io.github.nahkd123.pojo.expansion.item.standard.StatsComponent; import io.github.nahkd123.pojo.expansion.stat.StatFactory; -import io.github.nahkd123.pojo.expansion.stat.StatLocalizer; -import io.github.nahkd123.pojo.expansion.stat.StatsLocalizer; +import io.github.nahkd123.pojo.expansion.stat.localize.GemstonesLocalizer; +import io.github.nahkd123.pojo.expansion.stat.localize.StatsLocalizer; import io.github.nahkd123.pojo.expansion.stat.provided.AttributeStat; public class PojoExpansionPlugin extends JavaPlugin { // Shared private RandomGenerator randomGenerator = new Random(); private StatsLocalizer statsLocalizer; + private GemstonesLocalizer gemstonesLocalizer; // TODO Weird ass registry; replace this later private Map stats = new HashMap<>(); @@ -29,17 +35,33 @@ public void onLoad() { // Stats AttributeStat.registerFactory(new NamespacedKey(this, "attribute")); + // Getters (lazy/late) + Supplier statsLocalizer = () -> this.statsLocalizer; + Supplier gemstonesLocalizer = () -> this.gemstonesLocalizer; + LongSupplier seedGenerator = randomGenerator::nextLong; + // Item components - StatsComponent.registerFactory(new NamespacedKey(this, "stats"), () -> statsLocalizer, - randomGenerator::nextLong); + StatsComponent.registerFactory(new NamespacedKey(this, "stats"), statsLocalizer, seedGenerator); + GemstoneSlotsComponent.registerFactory(new NamespacedKey(this, "gemstone_slots"), gemstonesLocalizer); + GemstoneComponent.registerFactory( + new NamespacedKey(this, "gemstone"), + gemstonesLocalizer, + statsLocalizer, + seedGenerator); } @Override public void onEnable() { + // Events + getServer().getPluginManager().registerEvents(new InventoryEventsListener(), this); + // Configurations // @formatter:off YamlConfiguration statsConfig = getOrSaveConfig("stats.yml"); - statsLocalizer = new StatsLocalizer(StatLocalizer.DEFAULT).fromConfig(statsConfig.getConfigurationSection("localization")); + statsLocalizer = new StatsLocalizer().fromConfig(statsConfig.getConfigurationSection("localization")); + + YamlConfiguration gemstonesConfig = getOrSaveConfig("gemstones.yml"); + gemstonesLocalizer = new GemstonesLocalizer().fromConfig(gemstonesConfig.getConfigurationSection("localization")); // @formatter:on } diff --git a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/event/InventoryEventsListener.java b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/event/InventoryEventsListener.java new file mode 100644 index 0000000..5659b5d --- /dev/null +++ b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/event/InventoryEventsListener.java @@ -0,0 +1,68 @@ +package io.github.nahkd123.pojo.expansion.event; + +import java.util.Optional; + +import org.bukkit.Sound; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.inventory.InventoryClickEvent; +import org.bukkit.event.inventory.InventoryType; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +import io.github.nahkd123.pojo.api.item.PojoItem; +import io.github.nahkd123.pojo.api.item.standard.StandardPojoItem; +import io.github.nahkd123.pojo.api.item.standard.component.ComponentDataHolder; +import io.github.nahkd123.pojo.expansion.item.standard.GemstoneComponent; +import io.github.nahkd123.pojo.expansion.item.standard.GemstoneSlotsComponent; +import io.github.nahkd123.pojo.expansion.stat.gemstone.Gemstone; +import io.github.nahkd123.pojo.expansion.stat.gemstone.GemstonesHolder; + +public class InventoryEventsListener implements Listener { + @EventHandler + public void onInventoryClick(InventoryClickEvent event) { + if (event.isCancelled()) return; + if (event.getInventory().getType() != InventoryType.CRAFTING) return; + if (checkGemstoneApply(event)) return; + } + + private boolean checkGemstoneApply(InventoryClickEvent event) { + ItemStack cursorStack = event.getCursor(); + if (!(PojoItem.getFrom(cursorStack) instanceof StandardPojoItem cursorStd)) return false; + + ItemStack clickedStack = event.getCurrentItem(); + if (!(PojoItem.getFrom(clickedStack) instanceof StandardPojoItem clickedStd)) return false; + + Optional gemstoneComponent = cursorStd.getComponents().stream() + .filter(s -> s instanceof GemstoneComponent) + .findAny().map(s -> (GemstoneComponent) s); + if (gemstoneComponent.isEmpty()) return false; + + Optional gemstoneSlotComponent = clickedStd.getComponents().stream() + .filter(s -> s instanceof GemstoneSlotsComponent) + .findAny().map(s -> (GemstoneSlotsComponent) s); + if (gemstoneSlotComponent.isEmpty()) return false; + + // Test + ComponentDataHolder clickedDataHolder = clickedStd.loadDataFrom(clickedStack.getItemMeta(), true); + GemstonesHolder slots = clickedDataHolder.get(gemstoneSlotComponent.get()); + + ComponentDataHolder cursorDataHolder = cursorStd.loadDataFrom(cursorStack.getItemMeta(), true); + Gemstone gemstone = new Gemstone(PojoItem.getId(cursorStack), cursorDataHolder); + if (!slots.tryAttachGemstone(gemstoneComponent.get().getSlotId(), gemstone)) return false; + + // Apply + clickedStack = clickedStack.clone(); + ItemMeta meta = clickedStack.getItemMeta(); + clickedStd.saveDataTo(meta.getPersistentDataContainer(), clickedDataHolder); + clickedStd.updateMeta(meta); + clickedStack.setItemMeta(meta); + event.setCancelled(true); + event.setCurrentItem(clickedStack); + event.getWhoClicked().setItemOnCursor(null); + if (event.getWhoClicked() instanceof Player p) + p.playSound(p.getEyeLocation(), Sound.BLOCK_SMITHING_TABLE_USE, 1f, 1f); + return true; + } +} diff --git a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/item/standard/GemstoneComponent.java b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/item/standard/GemstoneComponent.java new file mode 100644 index 0000000..2d2edde --- /dev/null +++ b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/item/standard/GemstoneComponent.java @@ -0,0 +1,191 @@ +package io.github.nahkd123.pojo.expansion.item.standard; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.LongSupplier; +import java.util.function.Supplier; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import io.github.nahkd123.pojo.api.editor.Editable; +import io.github.nahkd123.pojo.api.editor.EditableObject; +import io.github.nahkd123.pojo.api.editor.EditableString; +import io.github.nahkd123.pojo.api.editor.NodeDescription; +import io.github.nahkd123.pojo.api.item.standard.component.ComponentsFactory; +import io.github.nahkd123.pojo.api.item.standard.component.EditorComponentsFactory; +import io.github.nahkd123.pojo.api.item.standard.component.EditorSupportedComponent; +import io.github.nahkd123.pojo.api.registry.UserDefinedId; +import io.github.nahkd123.pojo.api.utils.TextUtils; +import io.github.nahkd123.pojo.api.utils.lore.LoreSection; +import io.github.nahkd123.pojo.api.utils.lore.LoreSorter; +import io.github.nahkd123.pojo.expansion.stat.Stat; +import io.github.nahkd123.pojo.expansion.stat.compute.ComputedStats; +import io.github.nahkd123.pojo.expansion.stat.localize.GemstoneLocalizer; +import io.github.nahkd123.pojo.expansion.stat.localize.GemstonesLocalizer; +import io.github.nahkd123.pojo.expansion.stat.localize.StatLocalizer; +import io.github.nahkd123.pojo.expansion.stat.localize.StatsLocalizer; +import io.github.nahkd123.pojo.expansion.utils.PlaceholderUtils; +import io.github.nahkd123.pojo.expansion.utils.PojoEquipmentSlot; + +public class GemstoneComponent implements EditorSupportedComponent { + private static final NodeDescription DESCRIPTION = new NodeDescription(Material.EMERALD, "Pojo Expansion: Gemstone", "Turn this item into gemstone."); + + private NamespacedKey typeId; + private Supplier gemstonesLocalizer; + private Supplier statsLocalizer; + private LongSupplier seedGenerator; + private UserDefinedId slotId; + private PojoEquipmentSlot equipmentSlot; + private List modifiers; + + public GemstoneComponent(NamespacedKey typeId, Supplier gemstonesLocalizer, Supplier statsLocalizer, LongSupplier seedGenerator, UserDefinedId slotId, PojoEquipmentSlot equipmentSlot, List modifiers) { + this.typeId = typeId; + this.gemstonesLocalizer = gemstonesLocalizer; + this.statsLocalizer = statsLocalizer; + this.seedGenerator = seedGenerator; + this.slotId = slotId; + this.equipmentSlot = equipmentSlot; + this.modifiers = modifiers; + } + + public UserDefinedId getSlotId() { return slotId; } + + public void setSlotId(UserDefinedId slotId) { this.slotId = slotId; } + + public PojoEquipmentSlot getEquipmentSlot() { return equipmentSlot; } + + public void setEquipmentSlot(PojoEquipmentSlot equipmentSlot) { this.equipmentSlot = equipmentSlot; } + + public List getModifiers() { return modifiers; } + + public static void registerFactory(NamespacedKey typeId, Supplier gemstonesLocalizer, Supplier statsLocalizer, LongSupplier seedGenerator) { + ComponentsFactory factory = new EditorComponentsFactory<>() { + @Override + public GemstoneComponent createDefault() { + return new GemstoneComponent(typeId, gemstonesLocalizer, statsLocalizer, seedGenerator, new UserDefinedId("mynamespace", "sample_slot"), PojoEquipmentSlot.ALL, new ArrayList<>()); + } + + @Override + public GemstoneComponent createFromConfig(ConfigurationSection config) { + UserDefinedId slotId = config.contains("gemstoneSlot") + ? UserDefinedId.fromString(config.getString("gemstoneSlot")) + : new UserDefinedId("mynamespace", "sample_slot"); + PojoEquipmentSlot equipmentSlot = config.contains("equipmentSlot") + ? PojoEquipmentSlot.valueOf(config.getString("equipmentSlot")) + : PojoEquipmentSlot.ALL; + List modifiers = config.contains("modifiers") + ? Stat.loadStatsListFrom(config.getConfigurationSection("modifiers")) + : new ArrayList<>(); + return new GemstoneComponent(typeId, gemstonesLocalizer, statsLocalizer, seedGenerator, slotId, equipmentSlot, modifiers); + } + + @Override + public NodeDescription getEditorDescription() { return DESCRIPTION; } + }; + factory.register(typeId); + } + + @Override + public void saveComponentTo(ConfigurationSection config) { + if (slotId != null) config.set("gemstoneSlot", slotId.toString()); + if (equipmentSlot != null) config.set("equipmentSlot", equipmentSlot.toString()); + + ConfigurationSection allModifiersConfig = config.createSection("modifiers"); + Stat.saveStatsListTo(allModifiersConfig, modifiers); + } + + @Override + public NamespacedKey getTypeId() { return typeId; } + + @Override + public ComputedStats createNewData() { + return new ComputedStats(seedGenerator.getAsLong(), equipmentSlot, modifiers); + } + + @Override + public void storeDataTo(PersistentDataContainer container, ComputedStats data) { + container.set(typeId, PersistentDataType.LONG, data.getSeed()); + } + + @Override + public ComputedStats loadDataFrom(PersistentDataContainer container) { + return new ComputedStats(container.get(typeId, PersistentDataType.LONG), equipmentSlot, modifiers); + } + + @Override + public NodeDescription getEditorDescription() { return DESCRIPTION; } + + @Override + public List getEditorNodes() { + // @formatter:off + return Arrays.asList( + new EditableString( + new NodeDescription( + Material.EMERALD, + "Slot ID", + "The gemstone slot ID that this gemstone", + "can be placed." + ), + () -> slotId.toString(), + s -> slotId = s != null? UserDefinedId.fromString(s) : slotId + ), + EditableObject.enumToBooleans( + new NodeDescription( + Material.GOLDEN_CHESTPLATE, + "Equipment Slot", + "Specify an equipment slot or leave it to", + "'All' to apply to all stats components." + ), + Arrays.asList(PojoEquipmentSlot.values()), + () -> equipmentSlot, + newSlot -> equipmentSlot = newSlot, + slot -> slot.getDescription() + ), + Stat.createStatsEditableList( + new NodeDescription(Material.GOLDEN_SWORD, "Modifiers", "Configure a list of stat modifiers"), + modifiers + ) + ); + // @formatter:on + } + + @Override + public void applyLore(ComputedStats data, LoreSorter lore, boolean displayMode) { + LoreSection selfSection = lore.getOrCreate(new UserDefinedId(typeId)); + GemstonesLocalizer gemstoneLocalizer = this.gemstonesLocalizer.get(); + GemstoneLocalizer slotLocalizer = gemstoneLocalizer.getLocalizer(slotId); + String slotName = slotLocalizer.getName() != null ? slotLocalizer.getName() : slotId.toString(); + + selfSection.getLines().addAll(gemstoneLocalizer.getGemstoneLore().stream() + .map(template -> PlaceholderUtils.apply(s -> switch (s) { + case "name" -> slotName; + default -> null; + }, TextUtils.colorize(template))) + .toList()); + + LoreSection statsSection = lore.getOrCreate(new UserDefinedId("pojoexpansion", "stats")); + + if (displayMode) { + for (Stat modifier : modifiers) { + if (modifier == null || !modifier.canBeDisplayed()) return; + StatLocalizer localizer = this.statsLocalizer.get().getLocalizer(modifier); + String statName = localizer.getName() != null ? localizer.getName() : modifier.getTranslationKey(); + String lineContent = localizer.localizeBase(statName, modifier.getOperation(), modifier.getStatValue()); + statsSection.getLines().add(lineContent); + } + } else { + for (Stat modifiers : modifiers) { + if (modifiers == null || !modifiers.canBeDisplayed()) return; + double value = data.get(modifiers).getValue(); + StatLocalizer localizer = this.statsLocalizer.get().getLocalizer(modifiers); + String statName = localizer.getName() != null ? localizer.getName() : modifiers.getTranslationKey(); + statsSection.getLines().add(localizer.localizeBase(statName, modifiers.getOperation(), value)); + } + } + } +} diff --git a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/item/standard/GemstoneSlotsComponent.java b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/item/standard/GemstoneSlotsComponent.java new file mode 100644 index 0000000..e3fa66f --- /dev/null +++ b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/item/standard/GemstoneSlotsComponent.java @@ -0,0 +1,169 @@ +package io.github.nahkd123.pojo.expansion.item.standard; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; + +import org.bukkit.Material; +import org.bukkit.NamespacedKey; +import org.bukkit.configuration.ConfigurationSection; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import io.github.nahkd123.pojo.api.editor.Editable; +import io.github.nahkd123.pojo.api.editor.EditableList; +import io.github.nahkd123.pojo.api.editor.EditableString; +import io.github.nahkd123.pojo.api.editor.NodeDescription; +import io.github.nahkd123.pojo.api.item.standard.component.ComponentDataHolder; +import io.github.nahkd123.pojo.api.item.standard.component.ComponentsFactory; +import io.github.nahkd123.pojo.api.item.standard.component.EditorComponentsFactory; +import io.github.nahkd123.pojo.api.item.standard.component.EditorSupportedComponent; +import io.github.nahkd123.pojo.api.registry.UserDefinedId; +import io.github.nahkd123.pojo.api.utils.lore.LoreSection; +import io.github.nahkd123.pojo.api.utils.lore.LoreSorter; +import io.github.nahkd123.pojo.expansion.stat.Stat; +import io.github.nahkd123.pojo.expansion.stat.compute.ComputedStat; +import io.github.nahkd123.pojo.expansion.stat.compute.ComputedStats; +import io.github.nahkd123.pojo.expansion.stat.gemstone.Gemstone; +import io.github.nahkd123.pojo.expansion.stat.gemstone.GemstonesHolder; +import io.github.nahkd123.pojo.expansion.stat.localize.GemstoneLocalizer; +import io.github.nahkd123.pojo.expansion.stat.localize.GemstonesLocalizer; +import io.github.nahkd123.pojo.expansion.utils.PojoEquipmentSlot; + +public class GemstoneSlotsComponent implements EditorSupportedComponent { + private static final NodeDescription DESCRIPTION = new NodeDescription(Material.EMERALD, "Pojo Expansion: Gemstone Slots", new String[] { + "Add gemstone slots to this item.", + "Gemstones can be applied if the gemstone item", + "have gemstone component. It can be applied by", + "clicking an item with suitable empty slots while", + "having gemstone item on cursor." + }); + private NamespacedKey typeId; + private List layout; + private Supplier localizer; + + public GemstoneSlotsComponent(NamespacedKey typeId, List layout, Supplier localizer) { + this.typeId = typeId; + this.layout = layout; + this.localizer = localizer; + } + + public List getLayout() { return layout; } + + public static void registerFactory(NamespacedKey typeId, Supplier localizer) { + ComponentsFactory factory = new EditorComponentsFactory<>() { + @Override + public GemstoneSlotsComponent createDefault() { + return new GemstoneSlotsComponent(typeId, new ArrayList<>(), localizer); + } + + @Override + public GemstoneSlotsComponent createFromConfig(ConfigurationSection config) { + List layout = new ArrayList<>(); + config.getStringList("gemstoneSlots").stream() + .map(UserDefinedId::fromString) + .forEach(layout::add); + return new GemstoneSlotsComponent(typeId, layout, localizer); + } + + @Override + public NodeDescription getEditorDescription() { return DESCRIPTION; } + }; + factory.register(typeId); + } + + @Override + public void saveComponentTo(ConfigurationSection config) { + config.set("gemstoneSlots", layout.stream().map(Object::toString).toList()); + } + + @Override + public NamespacedKey getTypeId() { return typeId; } + + @Override + public GemstonesHolder createNewData() { + return new GemstonesHolder(layout); + } + + @Override + public void storeDataTo(PersistentDataContainer container, GemstonesHolder data) { + List slots = data.toContainers(container.getAdapterContext()); + container.set(typeId, PersistentDataType.LIST.dataContainers(), slots); + } + + @Override + public GemstonesHolder loadDataFrom(PersistentDataContainer container) { + return new GemstonesHolder(layout).load(container.get(typeId, PersistentDataType.LIST.dataContainers())); + } + + @Override + public void applyLore(GemstonesHolder data, LoreSorter lore, boolean displayMode) { + GemstonesLocalizer localizer = this.localizer.get(); + LoreSection section = lore.getOrCreate(new UserDefinedId(typeId)); + + for (int i = 0; i < layout.size(); i++) { + UserDefinedId slotId = layout.get(i); + Gemstone gemstone = data.getGemstones().get(i); + GemstoneLocalizer slotLocalizer = localizer.getLocalizer(slotId); + + String slotName = slotLocalizer.getName() != null ? slotLocalizer.getName() : slotId.toString(); + String text = gemstone != null + ? slotLocalizer.localizeFilledSlot(slotName, gemstone.getGemstoneName()) + : slotLocalizer.localizeEmptySlot(slotName); + + section.getLines().add(text); + } + } + + @Override + public void applyToOtherComponent(GemstonesHolder data, ComponentDataHolder others) { + List allComputedStats = others.getList(StatsComponent.class); + GemstonesLocalizer localizer = this.localizer.get(); + + for (int i = 0; i < layout.size(); i++) { + Gemstone gemstone = data.getGemstones().get(i); + if (gemstone == null) continue; + + UserDefinedId slotId = layout.get(i); + GemstoneLocalizer slotLocalizer = localizer.getLocalizer(slotId); + ComponentDataHolder gemstoneDataHolder = gemstone.dataHolder(); + List modifiersList = gemstoneDataHolder.getList(GemstoneComponent.class); + ComputedStats modifiers = modifiersList.get(0); + + for (ComputedStats computedStats : allComputedStats) { + if (modifiers.getEquipmentSlot() != PojoEquipmentSlot.ALL && + modifiers.getEquipmentSlot() != computedStats.getEquipmentSlot()) continue; + + for (Stat modifier : modifiers.getStats()) { + ComputedStat computedModifier = modifiers.get(modifier); + String text = slotLocalizer.localize(computedModifier); + computedStats.get(modifier).modify(modifier.getOperation(), computedModifier.getValue(), text); + } + } + } + } + + @Override + public NodeDescription getEditorDescription() { return DESCRIPTION; } + + @Override + public List getEditorNodes() { + // @formatter:off + return Arrays.asList( + new EditableList( + new NodeDescription(Material.EMERALD, "Slots", "Add or remove slots"), + () -> layout.size(), + index -> new EditableString( + new NodeDescription(Material.EMERALD, "Slot #" + (index + 1), "Assign a gemstone slot ID"), + () -> layout.get(index).toString(), + s -> layout.set(index, s != null? UserDefinedId.fromString(s) : layout.get(index))), + index -> layout.add(index, new UserDefinedId("mynamespace", "sample_slot")), + index -> layout.remove(index), + (from, to) -> { + UserDefinedId id = layout.remove((int) from); + layout.add(to, id); + })); + // @formatter:on + } +} diff --git a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/item/standard/StatsComponent.java b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/item/standard/StatsComponent.java index 68f569f..d873f22 100644 --- a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/item/standard/StatsComponent.java +++ b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/item/standard/StatsComponent.java @@ -14,35 +14,26 @@ import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.persistence.PersistentDataContainer; import org.bukkit.persistence.PersistentDataType; -import org.bukkit.plugin.java.JavaPlugin; import io.github.nahkd123.pojo.api.editor.Editable; -import io.github.nahkd123.pojo.api.editor.EditableList; import io.github.nahkd123.pojo.api.editor.EditableObject; import io.github.nahkd123.pojo.api.editor.NodeDescription; import io.github.nahkd123.pojo.api.item.PojoItem; import io.github.nahkd123.pojo.api.item.standard.StandardPojoItem; -import io.github.nahkd123.pojo.api.item.standard.component.Component; import io.github.nahkd123.pojo.api.item.standard.component.ComponentDataHolder; import io.github.nahkd123.pojo.api.item.standard.component.ComponentsFactory; import io.github.nahkd123.pojo.api.item.standard.component.EditorComponentsFactory; import io.github.nahkd123.pojo.api.item.standard.component.EditorSupportedComponent; import io.github.nahkd123.pojo.api.registry.UserDefinedId; -import io.github.nahkd123.pojo.api.utils.EnumUtils; import io.github.nahkd123.pojo.api.utils.lore.LoreSection; import io.github.nahkd123.pojo.api.utils.lore.LoreSorter; -import io.github.nahkd123.pojo.expansion.PojoExpansionPlugin; import io.github.nahkd123.pojo.expansion.stat.Stat; -import io.github.nahkd123.pojo.expansion.stat.StatFactory; -import io.github.nahkd123.pojo.expansion.stat.StatLocalizer; -import io.github.nahkd123.pojo.expansion.stat.StatOperation; -import io.github.nahkd123.pojo.expansion.stat.StatsLocalizer; import io.github.nahkd123.pojo.expansion.stat.compute.ComputedStats; -import io.github.nahkd123.pojo.expansion.stat.value.StatConstantValue; -import io.github.nahkd123.pojo.expansion.stat.value.StatValue; +import io.github.nahkd123.pojo.expansion.stat.localize.StatLocalizer; +import io.github.nahkd123.pojo.expansion.stat.localize.StatsLocalizer; import io.github.nahkd123.pojo.expansion.utils.PojoEquipmentSlot; -public class StatsComponent implements Component, EditorSupportedComponent { +public class StatsComponent implements EditorSupportedComponent { private static final NodeDescription DESCRIPTION = new NodeDescription(Material.DIAMOND_SWORD, "Pojo Expansion: Stats", new String[] { "Add stats to this item." }); @@ -79,37 +70,8 @@ public StatsComponent createFromConfig(ConfigurationSection config) { StatsComponent component = createDefault(); if (config.contains("equipmentSlot")) component.slot = PojoEquipmentSlot.valueOf(config.getString("equipmentSlot")); - - if (config.contains("stats")) { - ConfigurationSection allStatsConfig = config.getConfigurationSection("stats"); - - for (String name : allStatsConfig.getKeys(false)) { - ConfigurationSection statConfig = allStatsConfig.getConfigurationSection(name); - - if (!statConfig.contains("type")) { - component.stats.add(null); - continue; - } - - NamespacedKey type = NamespacedKey.fromString(statConfig.getString("type"), - JavaPlugin.getPlugin(PojoExpansionPlugin.class)); - StatFactory statFactory = StatFactory.getAllFactories().get(type); - - if (statFactory == null) { - // TODO warn - component.stats.add(null); - continue; - } - - StatOperation operation = statConfig.contains("operation") - ? StatOperation.valueOf(statConfig.getString("operation")) - : StatOperation.ADD_VALUE; - StatValue value = StatValue.fromConfig(statConfig, "value"); - Stat stat = statFactory.createFromConfig(operation, value, statConfig); - component.stats.add(stat); - } - } - + if (config.contains("stats")) + component.stats = Stat.loadStatsListFrom(config.getConfigurationSection("stats")); return component; } @@ -124,18 +86,7 @@ public void saveComponentTo(ConfigurationSection config) { if (slot != null) config.set("equipmentSlot", slot.toString()); ConfigurationSection allStatsConfig = config.createSection("stats"); - int counter = 0; - - for (Stat stat : stats) { - ConfigurationSection statConfig = allStatsConfig.createSection("Autogenerated Name " + counter); - counter++; - if (stat == null) continue; - - statConfig.set("type", stat.getTypeId().toString()); - statConfig.set("operation", stat.getOperation().toString()); - StatValue.toConfig(statConfig, "value", stat.getStatValue()); - stat.saveToConfig(statConfig); - } + Stat.saveStatsListTo(allStatsConfig, stats); } @Override @@ -155,70 +106,17 @@ public List getEditorNodes() { Arrays.asList(PojoEquipmentSlot.values()), () -> slot, newSlot -> slot = newSlot, - slot -> slot.getDescription()), - new EditableList( + slot -> slot.getDescription() + ), + Stat.createStatsEditableList( new NodeDescription( Material.DIAMOND_SWORD, "Stat slots", "Add and remove stat slots." ), - () -> stats.size(), - index -> new EditableObject( - new NodeDescription(Material.GOLDEN_SWORD, "Stat slot #" + (index + 1), "Edit the stat in this slot"), - () -> { - List list = new ArrayList<>(); - List types = new ArrayList<>(); - types.add(null); - types.addAll(StatFactory.getAllFactories().keySet()); - - list.add(EditableObject.enumToBooleans( - new NodeDescription(Material.PAPER, "Type", "The stat type for this slot"), - types, - () -> stats.get(index) != null? stats.get(index).getTypeId() : null, - newType -> { - Stat oldStat = stats.get(index); - if (newType == null) { - stats.set(index, null); - return; - } - - if (oldStat != null && oldStat.getTypeId().equals(newType)) return; - StatFactory factory = StatFactory.getAllFactories().get(newType); - if (factory == null) return; - Stat newStat = factory.createDefault( - oldStat != null ? oldStat.getOperation() : StatOperation.ADD_VALUE, - oldStat != null ? oldStat.getStatValue() : new StatConstantValue(0d)); - stats.set(index, newStat); - }, - type -> type != null - ? new NodeDescription(type.toString()) - : new NodeDescription("None", "Default type."))); - if (stats.get(index) == null) return list; - - Stat current = stats.get(index); - list.add(EditableObject.enumToBooleans( - new NodeDescription(Material.REDSTONE, "Operation", "Change how this slot modify the stat"), - Arrays.asList(StatOperation.values()), - current::getOperation, - current::setOperation, - op -> new NodeDescription(EnumUtils.toFriendlyName(op), switch (op) { - case ADD_VALUE -> new String[] { "Add value to player's stat" }; - case ADD_PERCENTAGE -> new String[] { "Add value to player's stat multiplier" }; - case MULTIPLIER -> new String[] { "Multply player's stat by a value" }; - default -> new String[0]; - }))); - list.addAll(Stat.createValueTypeEditables(current::getStatValue, current::setStatValue)); - list.addAll(current.getEditableParameters()); - return list; - } - ) - .setCustomPreviewLines(() -> Collections.singletonList(stats.get(index) != null ? stats.get(index).getEditorText() : "&7&o(Empty slot)")), - index -> stats.add(index, null), - index -> stats.remove(index), - (from, to) -> { - Stat stat = stats.remove((int) from); - stats.add(to, stat); - })); + stats + ) + ); // @formatter:on } @@ -250,16 +148,14 @@ public void applyLore(ComputedStats data, LoreSorter lore, boolean displayMode) if (stat == null || !stat.canBeDisplayed()) return; StatLocalizer localizer = this.localizer.get().getLocalizer(stat); String name = localizer.getName() != null ? localizer.getName() : stat.getTranslationKey(); - String lineContent = localizer.localize(name, stat.getOperation(), stat.getStatValue()); + String lineContent = localizer.localizeBase(name, stat.getOperation(), stat.getStatValue()); section.getLines().add(lineContent); } } else { - for (Stat stat : stats) { + for (Stat stat : data.getStats()) { if (stat == null || !stat.canBeDisplayed()) return; - double value = data.get(stat).getValue(); StatLocalizer localizer = this.localizer.get().getLocalizer(stat); - String name = localizer.getName() != null ? localizer.getName() : stat.getTranslationKey(); - section.getLines().add(localizer.localize(name, stat.getOperation(), value)); + section.getLines().add(localizer.localize(data.get(stat))); } } } diff --git a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/Stat.java b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/Stat.java index 11d0d08..dd40765 100644 --- a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/Stat.java +++ b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/Stat.java @@ -2,6 +2,7 @@ import java.util.ArrayList; import java.util.Arrays; +import java.util.Collections; import java.util.List; import java.util.function.Consumer; import java.util.function.Supplier; @@ -11,12 +12,16 @@ import org.bukkit.attribute.Attribute; import org.bukkit.configuration.ConfigurationSection; import org.bukkit.inventory.meta.ItemMeta; +import org.bukkit.plugin.java.JavaPlugin; import io.github.nahkd123.pojo.api.editor.Editable; import io.github.nahkd123.pojo.api.editor.EditableDouble; +import io.github.nahkd123.pojo.api.editor.EditableList; import io.github.nahkd123.pojo.api.editor.EditableObject; import io.github.nahkd123.pojo.api.editor.EditableString; import io.github.nahkd123.pojo.api.editor.NodeDescription; +import io.github.nahkd123.pojo.api.utils.EnumUtils; +import io.github.nahkd123.pojo.expansion.PojoExpansionPlugin; import io.github.nahkd123.pojo.expansion.stat.provided.AttributeStat; import io.github.nahkd123.pojo.expansion.stat.value.StatConstantValue; import io.github.nahkd123.pojo.expansion.stat.value.StatFormulaValue; @@ -166,4 +171,116 @@ public static List createValueTypeEditables(Supplier getter // @formatter:on return list; } + + public static void saveStatsListTo(ConfigurationSection allStatsConfig, Iterable stats) { + int counter = 0; + + for (Stat stat : stats) { + ConfigurationSection statConfig = allStatsConfig.createSection("Autogenerated Name " + counter); + counter++; + if (stat == null) continue; + + statConfig.set("type", stat.getTypeId().toString()); + statConfig.set("operation", stat.getOperation().toString()); + StatValue.toConfig(statConfig, "value", stat.getStatValue()); + stat.saveToConfig(statConfig); + } + } + + public static List loadStatsListFrom(ConfigurationSection allStatsConfig) { + List stats = new ArrayList<>(); + + for (String name : allStatsConfig.getKeys(false)) { + ConfigurationSection statConfig = allStatsConfig.getConfigurationSection(name); + + if (!statConfig.contains("type")) { + stats.add(null); + continue; + } + + NamespacedKey type = NamespacedKey.fromString(statConfig.getString("type"), + JavaPlugin.getPlugin(PojoExpansionPlugin.class)); + StatFactory statFactory = StatFactory.getAllFactories().get(type); + + if (statFactory == null) { + // TODO warn + stats.add(null); + continue; + } + + StatOperation operation = statConfig.contains("operation") + ? StatOperation.valueOf(statConfig.getString("operation")) + : StatOperation.ADD_VALUE; + StatValue value = StatValue.fromConfig(statConfig, "value"); + Stat stat = statFactory.createFromConfig(operation, value, statConfig); + stats.add(stat); + } + + return stats; + } + + public static EditableList createStatsEditableList(NodeDescription description, List stats) { + // @formatter:off + return new EditableList( + description, + () -> stats.size(), + index -> new EditableObject( + new NodeDescription(Material.GOLDEN_SWORD, "Stat slot #" + (index + 1), "Edit the stat in this slot"), + () -> { + List list = new ArrayList<>(); + List types = new ArrayList<>(); + types.add(null); + types.addAll(StatFactory.getAllFactories().keySet()); + + list.add(EditableObject.enumToBooleans( + new NodeDescription(Material.PAPER, "Type", "The stat type for this slot"), + types, + () -> stats.get(index) != null? stats.get(index).getTypeId() : null, + newType -> { + Stat oldStat = stats.get(index); + if (newType == null) { + stats.set(index, null); + return; + } + + if (oldStat != null && oldStat.getTypeId().equals(newType)) return; + StatFactory factory = StatFactory.getAllFactories().get(newType); + if (factory == null) return; + Stat newStat = factory.createDefault( + oldStat != null ? oldStat.getOperation() : StatOperation.ADD_VALUE, + oldStat != null ? oldStat.getStatValue() : new StatConstantValue(0d)); + stats.set(index, newStat); + }, + type -> type != null + ? new NodeDescription(type.toString()) + : new NodeDescription("None", "Default type."))); + if (stats.get(index) == null) return list; + + Stat current = stats.get(index); + list.add(EditableObject.enumToBooleans( + new NodeDescription(Material.REDSTONE, "Operation", "Change how this slot modify the stat"), + Arrays.asList(StatOperation.values()), + current::getOperation, + current::setOperation, + op -> new NodeDescription(EnumUtils.toFriendlyName(op), switch (op) { + case ADD_VALUE -> new String[] { "Add value to player's stat" }; + case ADD_PERCENTAGE -> new String[] { "Add value to player's stat multiplier" }; + case MULTIPLIER -> new String[] { "Multply player's stat by a value" }; + default -> new String[0]; + }))); + list.addAll(Stat.createValueTypeEditables(current::getStatValue, current::setStatValue)); + list.addAll(current.getEditableParameters()); + return list; + } + ) + .setCustomPreviewLines(() -> Collections.singletonList(stats.get(index) != null ? stats.get(index).getEditorText() : "&7&o(Empty slot)")), + index -> stats.add(index, null), + index -> stats.remove(index), + (from, to) -> { + Stat stat = stats.remove((int) from); + stats.add(to, stat); + } + ); + // @formatter:on + } } diff --git a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/compute/ComputedStat.java b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/compute/ComputedStat.java index 5f62d43..4fafc14 100644 --- a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/compute/ComputedStat.java +++ b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/compute/ComputedStat.java @@ -7,6 +7,7 @@ public class ComputedStat { private Stat stat; private double value; private double percentage = 1d; + private String modifiers = ""; // TODO modifiers chain: "Stat Name: +X% (+5)" public ComputedStat(Stat stat, double value) { @@ -18,7 +19,16 @@ public ComputedStat(Stat stat, double value) { public double getValue() { return value * Math.max(percentage, 0d); } - public void modify(StatOperation operation, double modifier) { + public String getModifiers() { return modifiers; } + + public void modify(StatOperation operation, double modifier, String modifierText) { + if (stat.getOperation() == StatOperation.ADD_VALUE) modifyWithAddValueSource(operation, modifier); + if (stat.getOperation() == StatOperation.ADD_PERCENTAGE) modifyWithAddPercentageSource(operation, modifier); + if (stat.getOperation() == StatOperation.MULTIPLIER) modifyWithMultiplierSource(operation, modifier); + modifiers += modifierText; + } + + private void modifyWithAddValueSource(StatOperation operation, double modifier) { switch (operation) { case ADD_VALUE: value += modifier; @@ -34,5 +44,28 @@ public void modify(StatOperation operation, double modifier) { } } + private void modifyWithAddPercentageSource(StatOperation operation, double modifier) { + switch (operation) { + case ADD_PERCENTAGE: + value += modifier; + break; + case MULTIPLIER: + value *= modifier; + break; + default: + break; + } + } + + private void modifyWithMultiplierSource(StatOperation operation, double modifier) { + switch (operation) { + case MULTIPLIER: + value *= modifier; + break; + default: + break; + } + } + public Stat getStat() { return stat; } } diff --git a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/compute/ComputedStats.java b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/compute/ComputedStats.java index c3a4d32..614efa3 100644 --- a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/compute/ComputedStats.java +++ b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/compute/ComputedStats.java @@ -1,5 +1,6 @@ package io.github.nahkd123.pojo.expansion.stat.compute; +import java.util.ArrayList; import java.util.HashMap; import java.util.HashSet; import java.util.List; @@ -8,12 +9,13 @@ import java.util.Set; import io.github.nahkd123.pojo.expansion.stat.Stat; +import io.github.nahkd123.pojo.expansion.stat.StatOperation; import io.github.nahkd123.pojo.expansion.utils.PojoEquipmentSlot; public class ComputedStats { private Long seed; private PojoEquipmentSlot equipmentSlot; - private List stats; + private List stats = new ArrayList<>(); private Random random; private Map map = new HashMap<>(); private Set matchingKeys = new HashSet<>(); @@ -21,12 +23,14 @@ public class ComputedStats { public ComputedStats(Long seed, PojoEquipmentSlot equipmentSlot, List stats) { this.seed = seed; this.equipmentSlot = equipmentSlot; - this.stats = stats; - stats.stream().map(s -> s.getMatchingKey()).forEach(matchingKeys::add); this.random = new Random(seed != null ? seed : 0L); // Compute in sequential order - for (Stat stat : stats) get(stat); + for (Stat stat : stats) { + matchingKeys.add(stat.getMatchingKey()); + this.stats.add(stat); + map.put(stat.getMatchingKey(), new ComputedStat(stat, stat.getStatValue().get(random))); + } } public Long getSeed() { return seed; } @@ -45,15 +49,18 @@ public ComputedStat get(Stat stat) { stats.add(stat); } - map.put(stat.getMatchingKey(), computed = new ComputedStat(stat, stat.getStatValue().get(random))); + // The 0d value is used instead of generating new value + // This is because such stat does not exists yet + map.put(stat.getMatchingKey(), + computed = new ComputedStat(stat, stat.getOperation() == StatOperation.MULTIPLIER ? 1d : 0d)); } return computed; } - public ComputedStat modify(Stat modifier) { + public ComputedStat modify(Stat modifier, String modifierText) { ComputedStat computed = get(modifier); - computed.modify(modifier.getOperation(), modifier.getStatValue().get(random)); + computed.modify(modifier.getOperation(), modifier.getStatValue().get(random), modifierText); return computed; } } diff --git a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/gemstone/Gemstone.java b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/gemstone/Gemstone.java new file mode 100644 index 0000000..5bfbdcf --- /dev/null +++ b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/gemstone/Gemstone.java @@ -0,0 +1,71 @@ +package io.github.nahkd123.pojo.expansion.stat.gemstone; + +import org.bukkit.Material; +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.bukkit.persistence.PersistentDataContainer; +import org.bukkit.persistence.PersistentDataType; + +import io.github.nahkd123.pojo.api.internal.PojoInternal; +import io.github.nahkd123.pojo.api.item.PojoItem; +import io.github.nahkd123.pojo.api.item.standard.StandardPojoItem; +import io.github.nahkd123.pojo.api.item.standard.component.Component; +import io.github.nahkd123.pojo.api.item.standard.component.ComponentDataHolder; +import io.github.nahkd123.pojo.api.registry.UserDefinedId; +import io.github.nahkd123.pojo.api.utils.EnumUtils; + +public record Gemstone(UserDefinedId id, ComponentDataHolder dataHolder) { + + public static final PersistentDataType TYPE = new PersistentDataType<>() { + @Override + public PersistentDataContainer toPrimitive(Gemstone complex, PersistentDataAdapterContext context) { + PersistentDataContainer primitive = context.newPersistentDataContainer(); + primitive.set(PojoInternal.keys().id, PersistentDataType.STRING, complex.id.toString()); + + PojoItem item = PojoItem.getFrom(complex.id); + if (item == null || !(item instanceof StandardPojoItem std)) return primitive; + std.saveDataTo(primitive, complex.dataHolder); + return primitive; + } + + @Override + public Class getPrimitiveType() { return PersistentDataContainer.class; } + + @Override + public Class getComplexType() { return Gemstone.class; } + + @Override + public Gemstone fromPrimitive(PersistentDataContainer primitive, PersistentDataAdapterContext context) { + String stringId = primitive.get(PojoInternal.keys().id, PersistentDataType.STRING); + if (stringId == null) return null; + + UserDefinedId id; + try { + id = UserDefinedId.fromString(stringId); + } catch (IllegalArgumentException e) { + return null; + } + + PojoItem item = PojoItem.getFrom(id); + if (item == null || !(item instanceof StandardPojoItem std)) return null; + ComponentDataHolder dataHolder = std.loadDataFrom(primitive, true); + return new Gemstone(id, dataHolder); + } + }; + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public String getGemstoneName() { + PojoItem item = PojoItem.getFrom(id); + if (item == null || !(item instanceof StandardPojoItem std)) return null; + + Material mat = Material.STONE; + String name = null; + + for (Component component : std.getComponents()) { + Object data = dataHolder.get(component); + mat = component.applyMaterial(data, mat, false); + name = component.applyName(data, name, false); + } + + return name != null ? name : EnumUtils.toFriendlyName(mat); + } +} diff --git a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/gemstone/GemstonesHolder.java b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/gemstone/GemstonesHolder.java new file mode 100644 index 0000000..6e46e79 --- /dev/null +++ b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/gemstone/GemstonesHolder.java @@ -0,0 +1,73 @@ +package io.github.nahkd123.pojo.expansion.stat.gemstone; + +import java.util.ArrayList; +import java.util.List; + +import org.bukkit.persistence.PersistentDataAdapterContext; +import org.bukkit.persistence.PersistentDataContainer; + +import io.github.nahkd123.pojo.api.registry.UserDefinedId; + +public class GemstonesHolder { + private List layout; + private List gemstones; + + public GemstonesHolder(List layout) { + this.layout = layout; + this.gemstones = new ArrayList<>(); + for (int i = 0; i < layout.size(); i++) gemstones.add(null); + } + + public List getLayout() { return layout; } + + public List getGemstones() { return gemstones; } + + public boolean tryAttachGemstone(UserDefinedId slotId, Gemstone gemstone) { + for (int i = 0; i < layout.size(); i++) { + UserDefinedId destSlotId = layout.get(i); + if (!destSlotId.equals(slotId)) continue; + if (gemstones.get(i) != null) continue; + + gemstones.set(i, gemstone); + return true; + } + + return false; + } + + public GemstonesHolder load(List slots) { + if (slots == null) return this; + + for (int i = 0; i < layout.size(); i++) { + if (i >= slots.size()) return this; + + PersistentDataContainer slot = slots.get(i); + Gemstone gemstone = Gemstone.TYPE.fromPrimitive(slot, slot.getAdapterContext()); + gemstones.set(i, gemstone); + } + + return this; + } + + public List toContainers(PersistentDataAdapterContext context) { + List slots = new ArrayList<>(); + + for (int i = 0; i < layout.size(); i++) { + if (i >= gemstones.size()) { + slots.add(context.newPersistentDataContainer()); + continue; + } + + Gemstone gemstone = gemstones.get(i); + + if (gemstone == null) { + slots.add(context.newPersistentDataContainer()); + continue; + } + + slots.add(Gemstone.TYPE.toPrimitive(gemstone, context)); + } + + return slots; + } +} diff --git a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/localize/GemstoneLocalizer.java b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/localize/GemstoneLocalizer.java new file mode 100644 index 0000000..4d1bce1 --- /dev/null +++ b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/localize/GemstoneLocalizer.java @@ -0,0 +1,60 @@ +package io.github.nahkd123.pojo.expansion.stat.localize; + +import org.bukkit.configuration.ConfigurationSection; + +import io.github.nahkd123.pojo.api.utils.TextUtils; +import io.github.nahkd123.pojo.expansion.utils.PlaceholderUtils; + +public class GemstoneLocalizer extends StatLocalizer { + // @formatter:off + public static final GemstoneLocalizer DEFAULT = new GemstoneLocalizer( + null, null, + " &9{base}", + "+{value;number}", + "+(value;percentage}", + "x{value;number}", + "+{min;number} -> {max;number}", + "+(min;percentage} -> {max;percentage}", + "x{min;number} -> {max;number}", + "&7[&o{name}&7]", + "&7[&f{gemstoneName}&7]"); + // @formatter:on + + private String emptySlot; + private String filledSlot; + + public GemstoneLocalizer(GemstoneLocalizer parent, String name, String format, String addValue, String addPercentage, String multiplier, String addValueRange, String addPercentageRange, String multiplierRange, String emptySlot, String filledSlot) { + super(parent, name, format, addValue, addPercentage, multiplier, addValueRange, addPercentageRange, multiplierRange); + this.emptySlot = emptySlot != null ? emptySlot : parent.emptySlot; + this.filledSlot = filledSlot != null ? filledSlot : parent.filledSlot; + } + + public static GemstoneLocalizer fromConfig(GemstoneLocalizer parent, ConfigurationSection config) { + String name = config.getString("name"); + String format = config.getString("format"); + String addValue = config.getString("addValue"); + String addPercentage = config.getString("addPercentage"); + String multiplier = config.getString("multiplier"); + String addValueRange = config.getString("addValueRange"); + String addPercentageRange = config.getString("addPercentageRange"); + String multiplierRange = config.getString("multiplierRange"); + String emptySlot = config.getString("emptySlot"); + String filledSlot = config.getString("filledSlot"); + return new GemstoneLocalizer(parent, name, format, addValue, addPercentage, multiplier, addValueRange, addPercentageRange, multiplierRange, emptySlot, filledSlot); + } + + public String localizeEmptySlot(String slotName) { + return TextUtils.colorize(PlaceholderUtils.apply(s -> switch (s) { + case "name" -> slotName; + default -> null; + }, emptySlot)); + } + + public String localizeFilledSlot(String slotName, String gemstoneName) { + return TextUtils.colorize(PlaceholderUtils.apply(s -> switch (s) { + case "name" -> slotName; + case "gemstoneName" -> gemstoneName; + default -> null; + }, filledSlot)); + } +} diff --git a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/localize/GemstonesLocalizer.java b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/localize/GemstonesLocalizer.java new file mode 100644 index 0000000..f48f6e3 --- /dev/null +++ b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/localize/GemstonesLocalizer.java @@ -0,0 +1,63 @@ +package io.github.nahkd123.pojo.expansion.stat.localize; + +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.bukkit.configuration.ConfigurationSection; + +import io.github.nahkd123.pojo.api.registry.UserDefinedId; + +public class GemstonesLocalizer { + private Map localizers = new HashMap<>(); + private GemstoneLocalizer defaultLocalizer; + private List gemstoneLore; + + public GemstonesLocalizer(GemstoneLocalizer defaultLocalizer, List gemstoneLore) { + this.defaultLocalizer = defaultLocalizer; + this.gemstoneLore = gemstoneLore; + } + + public GemstonesLocalizer() { + this(GemstoneLocalizer.DEFAULT, Collections.emptyList()); + } + + public GemstonesLocalizer fromConfig(ConfigurationSection config) { + if (config.contains("defaults")) defaultLocalizer = GemstoneLocalizer.fromConfig( + GemstoneLocalizer.DEFAULT, + config.getConfigurationSection("defaults")); + + for (String namespace : config.getKeys(false)) { + if (namespace.equals("defaults")) continue; + ConfigurationSection namespaceSection = config.getConfigurationSection(namespace); + + for (String id : namespaceSection.getKeys(false)) { + UserDefinedId udid = new UserDefinedId(namespace, id); + Object obj = namespaceSection.get(id); + GemstoneLocalizer localizer; + + if (obj instanceof String str) + localizer = new GemstoneLocalizer(defaultLocalizer, str, null, null, null, null, null, null, null, null, null); + else if (obj instanceof ConfigurationSection s) + localizer = GemstoneLocalizer.fromConfig(defaultLocalizer, s); + else continue; + + localizers.put(udid, localizer); + } + } + + gemstoneLore = config.getStringList("defaults.gemstoneLore"); + return this; + } + + public List getGemstoneLore() { return gemstoneLore; } + + public GemstoneLocalizer getDefaultLocalizer() { return defaultLocalizer; } + + public void setDefaultLocalizer(GemstoneLocalizer defaultLocalizer) { this.defaultLocalizer = defaultLocalizer; } + + public GemstoneLocalizer getLocalizer(UserDefinedId id) { + return localizers.getOrDefault(id, defaultLocalizer); + } +} diff --git a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/StatLocalizer.java b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/localize/StatLocalizer.java similarity index 62% rename from pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/StatLocalizer.java rename to pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/localize/StatLocalizer.java index ffcd44f..b771a18 100644 --- a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/StatLocalizer.java +++ b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/localize/StatLocalizer.java @@ -1,10 +1,12 @@ -package io.github.nahkd123.pojo.expansion.stat; +package io.github.nahkd123.pojo.expansion.stat.localize; import java.util.Random; import org.bukkit.configuration.ConfigurationSection; import io.github.nahkd123.pojo.api.utils.TextUtils; +import io.github.nahkd123.pojo.expansion.stat.StatOperation; +import io.github.nahkd123.pojo.expansion.stat.compute.ComputedStat; import io.github.nahkd123.pojo.expansion.stat.value.StatConstantValue; import io.github.nahkd123.pojo.expansion.stat.value.StatValue; import io.github.nahkd123.pojo.expansion.stat.value.StatValueWithMaxMin; @@ -14,6 +16,7 @@ public class StatLocalizer { // @formatter:off public static final StatLocalizer DEFAULT = new StatLocalizer( null, null, + "{base}{modifiers}", "&7{name}: &a+{value}", "&7{name}: &a+{value;percentage}%", "&7{name}: &ex{value}", @@ -23,6 +26,7 @@ public class StatLocalizer { // @formatter:on private String name; + private String format; private String addValue; private String addPercentage; private String multiplier; @@ -30,8 +34,9 @@ public class StatLocalizer { private String addPercentageRange; private String multiplierRange; - public StatLocalizer(StatLocalizer parent, String name, String addValue, String addPercentage, String multiplier, String addValueRange, String addPercentageRange, String multiplierRange) { + public StatLocalizer(StatLocalizer parent, String name, String format, String addValue, String addPercentage, String multiplier, String addValueRange, String addPercentageRange, String multiplierRange) { this.name = name; + this.format = format != null ? format : parent.format; this.addValue = addValue != null ? addValue : parent.addValue; this.addPercentage = addPercentage != null ? addPercentage : parent.addPercentage; this.multiplier = multiplier != null ? multiplier : parent.multiplier; @@ -40,20 +45,23 @@ public StatLocalizer(StatLocalizer parent, String name, String addValue, String this.multiplierRange = multiplierRange != null ? multiplierRange : parent.multiplierRange; } - public static StatLocalizer fromConfig(ConfigurationSection config) { + public static StatLocalizer fromConfig(StatLocalizer parent, ConfigurationSection config) { String name = config.getString("name"); + String format = config.getString("format"); String addValue = config.getString("addValue"); String addPercentage = config.getString("addPercentage"); String multiplier = config.getString("multiplier"); String addValueRange = config.getString("addValueRange"); String addPercentageRange = config.getString("addPercentageRange"); String multiplierRange = config.getString("multiplierRange"); - return new StatLocalizer(DEFAULT, name, addValue, addPercentage, multiplier, addValueRange, addPercentageRange, multiplierRange); + return new StatLocalizer(parent, name, format, addValue, addPercentage, multiplier, addValueRange, addPercentageRange, multiplierRange); } public String getName() { return name; } - public String localize(String name, StatOperation operation, double value) { + public String getFormat() { return format; } + + public String localizeBase(String name, StatOperation operation, double value) { String s = switch (operation) { case ADD_VALUE -> addValue; case ADD_PERCENTAGE -> addPercentage; @@ -68,7 +76,7 @@ public String localize(String name, StatOperation operation, double value) { return TextUtils.colorize(s); } - public String localize(String name, StatOperation operation, double min, double max) { + public String localizeBase(String name, StatOperation operation, double min, double max) { String s = switch (operation) { case ADD_VALUE -> addValueRange; case ADD_PERCENTAGE -> addPercentageRange; @@ -85,9 +93,22 @@ public String localize(String name, StatOperation operation, double min, double return TextUtils.colorize(s); } - public String localize(String name, StatOperation operation, StatValue value) { - if (value instanceof StatValueWithMaxMin mm) return localize(name, operation, mm.getMin(), mm.getMax()); - if (value instanceof StatConstantValue cons) return localize(name, operation, cons.value()); - return localize(name, operation, value.get(new Random())); + public String localizeBase(String name, StatOperation operation, StatValue value) { + if (value instanceof StatValueWithMaxMin mm) return localizeBase(name, operation, mm.getMin(), mm.getMax()); + if (value instanceof StatConstantValue cons) return localizeBase(name, operation, cons.value()); + return localizeBase(name, operation, value.get(new Random())); + } + + public String localize(ComputedStat computed) { + String base = localizeBase( + name != null ? name : computed.getStat().getTranslationKey(), + computed.getStat().getOperation(), + computed.getValue()); + + return PlaceholderUtils.apply(s -> switch (s) { + case "base" -> base; + case "modifiers" -> computed.getModifiers(); + default -> null; + }, TextUtils.colorize(format)); } } diff --git a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/StatsLocalizer.java b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/localize/StatsLocalizer.java similarity index 82% rename from pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/StatsLocalizer.java rename to pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/localize/StatsLocalizer.java index b5b4782..d6310a6 100644 --- a/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/StatsLocalizer.java +++ b/pojo-expansion/src/main/java/io/github/nahkd123/pojo/expansion/stat/localize/StatsLocalizer.java @@ -1,4 +1,4 @@ -package io.github.nahkd123.pojo.expansion.stat; +package io.github.nahkd123.pojo.expansion.stat.localize; import java.util.HashMap; import java.util.Map; @@ -6,6 +6,7 @@ import org.bukkit.configuration.ConfigurationSection; import io.github.nahkd123.pojo.api.registry.UserDefinedId; +import io.github.nahkd123.pojo.expansion.stat.Stat; public class StatsLocalizer { private Map> localizers = new HashMap<>(); @@ -15,9 +16,14 @@ public StatsLocalizer(StatLocalizer defaultLocalizer) { this.defaultLocalizer = defaultLocalizer; } + public StatsLocalizer() { + this(StatLocalizer.DEFAULT); + } + public StatsLocalizer fromConfig(ConfigurationSection config) { - if (config.contains("defaults")) - defaultLocalizer = StatLocalizer.fromConfig(config.getConfigurationSection("defaults")); + if (config.contains("defaults")) defaultLocalizer = StatLocalizer.fromConfig( + StatLocalizer.DEFAULT, + config.getConfigurationSection("defaults")); for (String namespace : config.getKeys(false)) { if (namespace.equals("defaults")) continue; @@ -32,9 +38,9 @@ public StatsLocalizer fromConfig(ConfigurationSection config) { StatLocalizer localizer; if (obj instanceof String str) - localizer = new StatLocalizer(defaultLocalizer, str, null, null, null, null, null, null); + localizer = new StatLocalizer(defaultLocalizer, str, null, null, null, null, null, null, null); else if (obj instanceof ConfigurationSection s) - localizer = StatLocalizer.fromConfig(s); + localizer = StatLocalizer.fromConfig(defaultLocalizer, s); else continue; define(udid, key, localizer); diff --git a/pojo-expansion/src/main/resources/gemstones.yml b/pojo-expansion/src/main/resources/gemstones.yml new file mode 100644 index 0000000..9dc10b3 --- /dev/null +++ b/pojo-expansion/src/main/resources/gemstones.yml @@ -0,0 +1,48 @@ +# Configuration for "pojoexpansion:gemstone" and "pojoexpansion:gemstone_slots" +# components. + +localization: + # Placeholders: + # {base}: The base modifier text + # {value} (non-range): The modifier's numerical value + # {min} and {max} (range): The modifier's value range + # {name}: The name of modifier (in this case, it is the gemstone slot name) + # {gemstoneName}: The name of gemstone item + defaults: + format: " &9{base}" + addValue: "+{value;number}" + addPercentage: "+{value;percentage}" + multiplier: "x{value;number}" + addValueRange: "+{min;number} -> {max;number}" + addPercentageRange: "+{min;percentage} -> {max;percentage}" + multiplierRange: "x{min;number} -> {max;number}" + + # Gemstone slots + emptySlot: "&7[&o{name}&7]" + filledSlot: "&7[&f{gemstoneName}&7]" + + # Gemstone + gemstoneLore: + - "&7This gemstone can be applied to" + - "&7slot &f{name}&7." + + # Gemstone slot ID is specified in the gemstone: + # Component: + # type: pojoexpansion:gemstone + # gemstoneSlot: mynamespace:gemstone_slot_id + # equipmentSlot: OFF_HAND + # modifiers: {} # A collection of stat modifiers + # + # To add a gemstone slot that only accepts gemstones with given slot ID: + # Component: + # type: pojoexpansion:gemstone_slots + # gemstoneSlots: + # - mynamespace:gemstone_slot_id + # - mynamespace:another_slot_id + + # Below is the localization configuration for "mynamespace:sample_slot" + # gemstone slot (follows the "defaults" structure): + mynamespace: + sample_slot: + name: "Sample Slot" # Name of this gemstone slot (mynamespace:sample_slot) + format: " &d{base}" \ No newline at end of file diff --git a/pojo-expansion/src/main/resources/stats.yml b/pojo-expansion/src/main/resources/stats.yml index 1e7dd3e..f6f2cbc 100644 --- a/pojo-expansion/src/main/resources/stats.yml +++ b/pojo-expansion/src/main/resources/stats.yml @@ -3,12 +3,18 @@ # Localization for all stats localization: # Default stats display, applies to all unconfigured stats - # addValue: Add a value to player's stats - # addPercentage: Add a percentage to player's stats multiplier - # multiplier: Multiplies player's stats with a value - # <...>Range: Ranged version - # Placeholders + # addValue: Add a value to player's stats + # addPercentage: Add a percentage to player's stats multiplier + # multiplier: Multiplies player's stats with a value + # <...>Range: Ranged version + # Placeholders: + # {base} (format only): The base stats text + # {modifiers} (format only): A bunch of modifiers, separated with a space + # {name}: The name of the stat + # {value} (non-range): The current value + # {min} and {max} (range only): The value range from {min} to {max} defaults: + format: "{base}{modifiers}" addValue: "&7{name}: &a+{value;number}" addPercentage: "&7{name}: &a+{value;percentage}" multiplier: "&7{name}: &ex{value;number}"