New item type, manager update & introduction of categories

This commit is contained in:
Roch Blonndiaux 2023-02-24 14:27:50 +01:00
parent 8768f1b9fe
commit 78fb3c94c9
11 changed files with 323 additions and 416 deletions

View File

@ -0,0 +1,64 @@
package net.Indyuce.mmoitems.api.item.category;
import net.Indyuce.mmoitems.api.item.type.MMOItemType;
import net.Indyuce.mmoitems.stat.type.ItemStat;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.List;
/**
* mmoitems
* 24/02/2023
*
* @author Roch Blondiaux (Kiwix).
*/
public class MMOTypeCategory {
private final String id;
private final String name;
private final ItemStack item;
private final String loreFormat;
private final List<ItemStat<?, ?>> stats;
public MMOTypeCategory(String id, String name, ItemStack item, String loreFormat, List<ItemStat<?, ?>> stats) {
this.id = id;
this.name = name;
this.item = item;
this.loreFormat = loreFormat;
this.stats = stats;
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public ItemStack getItem() {
return item;
}
public String getLoreFormat() {
return loreFormat;
}
public List<ItemStat<?, ?>> getStats() {
return stats;
}
public static MMOTypeCategory load(@NotNull ConfigurationSection section) {
final String id = section.getName();
final String name = section.getString("name");
final ItemStack item = MMOItemType.read(section.getString("display", Material.STONE.toString()));
final String loreFormat = section.getString("lore-format");
final List<ItemStat<?, ?>> stats = new ArrayList<>();
return new MMOTypeCategory(id, name, item, loreFormat, stats);
}
}

View File

@ -1,61 +1,157 @@
package net.Indyuce.mmoitems.api.item.type;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.player.modifier.ModifierSource;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.ConfigFile;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.item.type.set.MMOTypeSet;
import net.Indyuce.mmoitems.api.item.category.MMOTypeCategory;
import net.Indyuce.mmoitems.api.item.util.identify.UnidentifiedItem;
import net.Indyuce.mmoitems.manager.TypeManager;
import net.Indyuce.mmoitems.stat.type.ItemStat;
import org.apache.commons.lang.Validate;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* mmoitems
* 20/02/2023
* 24/02/2023
*
* @author Roch Blondiaux (Kiwix).
*/
public interface MMOItemType {
public class MMOItemType {
@NotNull
String getId();
private final String id;
private final String name;
private final ModifierSource modifierSource;
private final boolean weapon;
private final String loreFormat;
private final MMOTypeCategory category;
private final ItemStack item;
private final UnidentifiedItem unidentifiedTemplate;
private final List<ItemStat<?, ?>> stats;
@NotNull
String getName();
private MMOItemType(String id, String name, ModifierSource modifierSource, boolean weapon, String loreFormat, MMOTypeCategory category, ItemStack item, List<ItemStat<?, ?>> stats) {
this.id = id;
this.name = name;
this.modifierSource = modifierSource;
this.weapon = weapon;
this.loreFormat = loreFormat;
this.category = category;
this.item = item;
this.unidentifiedTemplate = new UnidentifiedItem(this);
this.stats = stats;
}
boolean isWeapon();
public String getId() {
return id;
}
ModifierSource getModifierSource();
public String getName() {
return name;
}
ItemStack toItemStack();
public ModifierSource getModifierSource() {
return modifierSource;
}
boolean isFourGUIMode();
public boolean isWeapon() {
return weapon;
}
String getLoreFormat();
public String getLoreFormat() {
return loreFormat;
}
List<ItemStat<?, ?>> getAvailableStats();
public MMOTypeCategory getCategory() {
return category;
}
ConfigFile getConfiguration();
public ItemStack getItem() {
return item;
}
UnidentifiedItem getUnidentifiedItem();
public UnidentifiedItem getUnidentifiedTemplate() {
return unidentifiedTemplate;
}
void load(ConfigurationSection config);
public List<ItemStat<?, ?>> getStats() {
return stats;
}
/**
* Used in command executors and completions for easier manipulation
*
* @param id The type id
* @return If a registered type with this ID could be found
*/
static boolean isValid(@Nullable String id) {
return id != null && MMOItems.plugin.getTypes().has(id.toUpperCase().replace("-", "_").replace(" ", "_"));
public boolean isFourGUIMode() {
return this.modifierSource == ModifierSource.ARMOR;
}
public ConfigFile getConfigFile() {
return new ConfigFile("/item", getId().toLowerCase());
}
public static MMOItemType load(@NotNull TypeManager manager, @NotNull ConfigurationSection section) {
final String id = section.getName();
final String name = section.getString("name");
final ModifierSource modifierSource = ModifierSource.valueOf(section.getString("modifier-source"));
final boolean weapon = section.getBoolean("weapon");
final String loreFormat = section.getString("lore-format");
final ItemStack item = read(section.getString("display", Material.STONE.toString()));
// TODO: Load the category
final String categoryId = section.getString("category");
final MMOTypeCategory category = manager.getCategories()
.stream()
.filter(mmoTypeCategory -> mmoTypeCategory.getId().equalsIgnoreCase(categoryId))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(String.format("Category %s does not exist.", categoryId)));
// TODO: Load the stats
final List<ItemStat<?, ?>> stats = new ArrayList<>();
MMOItemType type = new MMOItemType(id, name, modifierSource, weapon, loreFormat, category, item, stats);
type.getUnidentifiedTemplate().update(section.getConfigurationSection("unident-item"));
return type;
}
public static ItemStack read(String str) {
Validate.notNull(str, "Input must not be null");
String[] split = str.split(":");
Material material = Material.valueOf(split[0]);
return split.length > 1 ? MythicLib.plugin.getVersion().getWrapper().textureItem(material, Integer.parseInt(split[1])) : new ItemStack(material);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MMOItemType type = (MMOItemType) o;
return id.equals(type.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return "MMOItemType{" +
"id='" + id + '\'' +
", name='" + name + '\'' +
", modifierSource=" + modifierSource +
", weapon=" + weapon +
", loreFormat='" + loreFormat + '\'' +
", category=" + category +
'}';
}
/**
@ -66,7 +162,8 @@ public interface MMOItemType {
*/
@Nullable
@Contract("null -> null")
static MMOTypeSet get(@Nullable ItemStack item) {
public static MMOItemType get(@Nullable ItemStack item) {
if (item == null) return null;
return get(NBTItem.get(item).getType());
}
@ -78,8 +175,19 @@ public interface MMOItemType {
*/
@Nullable
@Contract("null -> null")
static MMOTypeSet get(@Nullable String id) {
public static MMOItemType get(@Nullable String id) {
if (id == null) return null;
String format = id.toUpperCase().replace("-", "_").replace(" ", "_");
return MMOItems.plugin.getTypes().has(format) ? MMOItems.plugin.getTypes().get(format) : null;
}
/**
* Used in command executors and completions for easier manipulation
*
* @param id The type id
* @return If a registered type with this ID could be found
*/
public static boolean isValid(@Nullable String id) {
return id != null && MMOItems.plugin.getTypes().has(id.toUpperCase().replace("-", "_").replace(" ", "_"));
}
}

View File

@ -1,165 +0,0 @@
package net.Indyuce.mmoitems.api.item.type;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.player.modifier.ModifierSource;
import net.Indyuce.mmoitems.api.ConfigFile;
import net.Indyuce.mmoitems.api.TypeSet;
import net.Indyuce.mmoitems.api.item.type.set.MMOTypeSet;
import net.Indyuce.mmoitems.api.item.util.identify.UnidentifiedItem;
import net.Indyuce.mmoitems.manager.TypeManager;
import net.Indyuce.mmoitems.stat.type.ItemStat;
import org.apache.commons.lang.Validate;
import org.bukkit.Material;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import javax.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* mmoitems
* 20/02/2023
*
* @author Roch Blondiaux (Kiwix).
*/
public class MMOPredefinedType implements MMOItemType {
private final String id;
private final MMOTypeSet set;
private final ModifierSource modifierSource;
private final boolean weapon;
// Configurable
private String name;
@Nullable
private String loreFormat;
/**
* Used to display the item in the item explorer and in the item recipes
* list in the advanced workbench. can also be edited using the config
* files.
*/
private ItemStack item;
private UnidentifiedItem unidentifiedTemplate;
/**
* List of stats which can be applied onto an item which has this type. This
* improves performance when generating an item by a significant amount.
*/
private final List<ItemStat<?, ?>> stats = new ArrayList<>();
public MMOPredefinedType(String id, boolean weapon, ModifierSource modSource) {
this.id = id.toUpperCase().replace("-", "_").replace(" ", "_");
this.modifierSource = modSource;
this.weapon = weapon;
this.loreFormat = null;
}
public MMOPredefinedType(@NotNull TypeManager manager, @NotNull ConfigurationSection config) {
id = config.getName().toUpperCase().replace("-", "_").replace(" ", "_");
parent = manager.get(config.getString("parent", "").toUpperCase().replace("-", "_").replace(" ", "_"));
set = (parent != null ? parent.set : TypeSet.EXTRA);
weapon = (parent != null && parent.weapon);
modifierSource = (parent != null ? parent.modifierSource : ModifierSource.OTHER);
this.loreFormat = config.getString("LoreFormat", (parent != null ? parent.loreFormat : null));
}
@NotNull
@Override
public String getId() {
return this.id;
}
@NotNull
@Override
public String getName() {
return this.name;
}
@Override
public boolean isWeapon() {
return this.weapon;
}
@Override
public ModifierSource getModifierSource() {
return this.modifierSource;
}
@Override
public ItemStack toItemStack() {
return this.item.clone();
}
@Override
public boolean isFourGUIMode() {
}
@Override
public String getLoreFormat() {
return this.loreFormat;
}
@Override
public List<ItemStat<?, ?>> getAvailableStats() {
return this.stats;
}
@Override
public ConfigFile getConfiguration() {
return new ConfigFile("/item", this.id.toLowerCase());
}
@Override
public UnidentifiedItem getUnidentifiedItem() {
return this.unidentifiedTemplate;
}
@Override
public void load(ConfigurationSection config) {
Validate.notNull(config, String.format("Could not find config for %s", getId()));
name = config.getString("name", name);
item = read(config.getString("display", item == null ? Material.STONE.toString() : item.getType().toString()));
(unidentifiedTemplate = new UnidentifiedItem(this)).update(config.getConfigurationSection("unident-item"));
// Getting overridden?
loreFormat = config.getString("LoreFormat", (parent != null ? parent.loreFormat : loreFormat));
}
private ItemStack read(String data) {
Validate.notNull(data, "Input must not be null");
String[] split = data.split(":");
Material material = Material.valueOf(split[0]);
return split.length > 1 ? MythicLib.plugin.getVersion().getWrapper().textureItem(material, Integer.parseInt(split[1])) : new ItemStack(material);
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
MMOTypeSet type = (MMOTypeSet) o;
return id.equals(type.id);
}
@Override
public int hashCode() {
return Objects.hash(id);
}
@Override
public String toString() {
return "Type{" +
"id='" + id + '\'' +
'}';
}
}

View File

@ -1,31 +0,0 @@
package net.Indyuce.mmoitems.api.item.type.set;
import org.jetbrains.annotations.ApiStatus;
/**
* mmoitems
* 21/02/2023
*
* @author Roch Blondiaux (Kiwix).
*/
@ApiStatus.Internal
public abstract class AbstractTypeSet implements MMOTypeSet {
private final String name;
private final boolean hasAttackEffect;
public AbstractTypeSet(String name, boolean hasAttackEffect) {
this.name = name;
this.hasAttackEffect = hasAttackEffect;
}
@Override
public String getName() {
return name;
}
@Override
public boolean hasAttackEffect() {
return hasAttackEffect;
}
}

View File

@ -1,23 +0,0 @@
package net.Indyuce.mmoitems.api.item.type.set;
import io.lumine.mythic.lib.damage.AttackMetadata;
import net.Indyuce.mmoitems.api.interaction.weapon.Weapon;
import net.Indyuce.mmoitems.api.player.PlayerData;
import org.bukkit.entity.LivingEntity;
/**
* mmoitems
* 21/02/2023
*
* @author Roch Blondiaux (Kiwix).
*/
public class DummyTypeSet extends AbstractTypeSet {
public DummyTypeSet(String name) {
super(name, false);
}
@Override
public void apply(AttackMetadata attackData, PlayerData source, LivingEntity target, Weapon weapon) {
}
}

View File

@ -1,27 +0,0 @@
package net.Indyuce.mmoitems.api.item.type.set;
import io.lumine.mythic.lib.damage.AttackMetadata;
import net.Indyuce.mmoitems.api.interaction.weapon.Weapon;
import net.Indyuce.mmoitems.api.player.PlayerData;
import org.bukkit.entity.LivingEntity;
/**
* mmoitems
* 20/02/2023
*
* @author Roch Blondiaux (Kiwix).
*/
public interface MMOTypeSet {
String getName();
void apply(AttackMetadata attackData, PlayerData source, LivingEntity target, Weapon weapon);
boolean hasAttackEffect();
default void applyAttackEffect(AttackMetadata attackMeta, PlayerData damager, LivingEntity target, Weapon weapon) {
if (this.hasAttackEffect())
this.apply(attackMeta, damager, target, weapon);
}
}

View File

@ -1,53 +0,0 @@
package net.Indyuce.mmoitems.api.item.type.set;
import io.lumine.mythic.lib.MythicLib;
import io.lumine.mythic.lib.damage.AttackMetadata;
import io.lumine.mythic.lib.script.Script;
import io.lumine.mythic.lib.skill.SkillMetadata;
import net.Indyuce.mmoitems.api.interaction.weapon.Weapon;
import net.Indyuce.mmoitems.api.player.PlayerData;
import org.apache.commons.lang3.Validate;
import org.bukkit.configuration.ConfigurationSection;
import org.bukkit.entity.LivingEntity;
import org.jetbrains.annotations.NotNull;
/**
* mmoitems
* 21/02/2023
*
* @author Roch Blondiaux (Kiwix).
*/
public class ScriptTypeSet extends AbstractTypeSet {
private final Script script;
public ScriptTypeSet(String name, boolean hasAttackEffect, Script script) {
super(name, hasAttackEffect);
this.script = script;
}
@Override
public void apply(AttackMetadata attackData, PlayerData source, LivingEntity target, Weapon weapon) {
final SkillMetadata skillMetadata = new SkillMetadata(null, source.getMMOPlayerData());
// TODO: check if the scripts are working
script.cast(skillMetadata);
}
public Script getScript() {
return script;
}
public static ScriptTypeSet load(@NotNull ConfigurationSection section) {
Validate.notNull(section, "The section cannot be null");
Validate.isTrue(section.contains("name"), "The section must contain a name");
Validate.isTrue(section.contains("attack-effect"), "The section must contain a has-attack-effect");
Validate.isTrue(section.contains("script"), "The section must contain a script");
final String name = section.getString("name");
final boolean hasAttackEffect = section.getBoolean("has-attack-effect");
final Script script = MythicLib.plugin.getSkills().getScriptOrThrow(section.getString("script"));
return new ScriptTypeSet(name, hasAttackEffect, script);
}
}

View File

@ -6,9 +6,9 @@ import io.lumine.mythic.lib.api.item.NBTItem;
import io.lumine.mythic.lib.util.AdventureUtils;
import net.Indyuce.mmoitems.ItemStats;
import net.Indyuce.mmoitems.api.ItemTier;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.Indyuce.mmoitems.api.item.mmoitem.VolatileMMOItem;
import net.Indyuce.mmoitems.api.item.type.MMOItemType;
import net.Indyuce.mmoitems.api.item.util.ConfigItem;
import net.Indyuce.mmoitems.stat.data.DoubleData;
import org.bukkit.inventory.ItemFlag;
@ -22,7 +22,7 @@ import java.io.ByteArrayOutputStream;
import java.util.*;
public class UnidentifiedItem extends ConfigItem {
public UnidentifiedItem(Type type) {
public UnidentifiedItem(MMOItemType type) {
super("unidentified", type.getItem().getType());
setName("#prefix#Unidentified " + type.getName());

View File

@ -2,35 +2,31 @@ package net.Indyuce.mmoitems.manager;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.ConfigFile;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.item.category.MMOTypeCategory;
import net.Indyuce.mmoitems.api.item.type.MMOItemType;
import net.Indyuce.mmoitems.manager.ConfigManager.DefaultFile;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.Contract;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.*;
import java.util.logging.Level;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
public class TypeManager implements Reloadable {
private final Map<String, Type> map = new LinkedHashMap<>();
private final List<MMOItemType> types = new ArrayList<>();
private final List<MMOTypeCategory> categories = new ArrayList<>();
/**
* Reloads the type manager. It entirely empties the currently registered
* item types, registers default item types again and reads item-types.yml
*/
public void reload() {
map.clear();
// Load default types
for (Field field : Type.class.getFields())
try {
if (Modifier.isStatic(field.getModifiers()) && Modifier.isFinal(field.getModifiers()) && field.get(null) instanceof Type)
register((Type) field.get(null));
} catch (Exception exception) {
MMOItems.plugin.getLogger().log(Level.WARNING, "Couldn't register type called '" + field.getName() + "': " + exception.getMessage());
}
this.categories.clear();
this.types.clear();
/*
* Register all other types. Important: check if the map already
@ -39,74 +35,96 @@ public class TypeManager implements Reloadable {
*/
DefaultFile.ITEM_TYPES.checkFile();
ConfigFile config = new ConfigFile("item-types");
for (String id : config.getConfig().getKeys(false))
if (!map.containsKey(id))
try {
register(new Type(this, config.getConfig().getConfigurationSection(id)));
} catch (IllegalArgumentException exception) {
MMOItems.plugin.getLogger().log(Level.WARNING, "Could not register type '" + id + "': " + exception.getMessage());
// Categories
ConfigFile categoriesConfig = new ConfigFile("type-categories.yml");
ConfigurationSection section = categoriesConfig.getConfig();
section.getKeys(false)
.stream()
.filter(section::isConfigurationSection)
.map(section::getConfigurationSection)
.filter(Objects::nonNull)
.map(MMOTypeCategory::load)
.peek(this.categories::add)
.peek(category -> category.getStats().clear())
.forEach(category -> MMOItems.plugin.getStats()
.getAll()
.forEach(stat -> category.getStats().add(stat)));
// DEBUG
MMOItems.log("Loaded " + this.categories.size() + " item categories.");
// Types
ConfigFile typesConfig = new ConfigFile("item-types.yml");
ConfigurationSection typesSection = typesConfig.getConfig();
typesSection.getKeys(false)
.stream()
.filter(typesSection::isConfigurationSection)
.map(typesSection::getConfigurationSection)
.filter(Objects::nonNull)
.map(s -> MMOItemType.load(this, s))
.peek(this.types::add)
.peek(mmoItemType -> mmoItemType.getStats().clear())
.forEach(mmoItemType -> MMOItems.plugin.getStats()
.getAll()
.forEach(stat -> mmoItemType.getStats().add(stat)));
// TODO: Add stats from type category
// DEBUG
MMOItems.log("Loaded " + this.types.size() + " item types.");
}
for (Iterator<Type> iterator = map.values().iterator(); iterator.hasNext();) {
Type type = iterator.next();
try {
type.load(config.getConfig().getConfigurationSection(type.getId()));
} catch (IllegalArgumentException exception) {
MMOItems.plugin.getLogger().log(Level.WARNING, "Could not register type '" + type.getId() + "': " + exception.getMessage());
iterator.remove();
continue;
public void register(MMOItemType type) {
this.types.add(type);
}
/*
* caches all the stats which the type can have to reduce future
* both item generation (and GUI) calculations. probably the thing
* which takes the most time when loading item types.
*/
type.getAvailableStats().clear();
MMOItems.plugin.getStats().getAll().stream().filter(stat -> stat.isCompatible(type)).forEach(stat -> type.getAvailableStats().add(stat));
}
}
public void register(Type type) {
map.put(type.getId(), type);
}
public void registerAll(Type... types) {
for (Type type : types)
public void registerAll(MMOItemType... types) {
for (MMOItemType type : types)
register(type);
}
/**
* @param id Internal ID of the type
*
* @return The MMOItem Type if it found.
*/
@Nullable public Type get(@Nullable String id) {
if (id == null) { return null; }
return map.get(id);
@Contract("null -> null")
@Nullable
public MMOItemType get(@Nullable String id) {
if (id == null) return null;
return this.types.stream()
.filter(mmoItemType -> mmoItemType.getId().equals(id))
.findFirst()
.orElse(null);
}
@NotNull public Type getOrThrow(@Nullable String id) {
Validate.isTrue(map.containsKey(id), "Could not find item type with ID '" + id + "'");
return map.get(id);
@Contract("null -> fail")
@NotNull
public MMOItemType getOrThrow(@Nullable String id) {
if (id == null) throw new IllegalArgumentException(String.format("No type found with id '%s'", id));
return this.types.stream()
.filter(mmoItemType -> mmoItemType.getId().equals(id))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException(String.format("No type found with id '%s'", id)));
}
public boolean has(String id) {
return map.containsKey(id);
return this.types.stream()
.anyMatch(mmoItemType -> mmoItemType.getId().equals(id));
}
public Collection<Type> getAll() {
return map.values();
public List<MMOItemType> getAll() {
return this.types;
}
public List<MMOTypeCategory> getCategories() {
return this.categories;
}
/**
* @return The names of all loaded types.
*/
public ArrayList<String> getAllTypeNames() {
ArrayList<String> ret = new ArrayList<>();
for (Type t : getAll()) { ret.add(t.getId()); }
return ret;
public List<String> getAllTypeNames() {
return this.types.stream()
.map(MMOItemType::getName)
.collect(Collectors.toList());
}
}

View File

@ -1,4 +1,3 @@
# Default item types. These cannot be removed. They
# may be used as 'parents' to create new item types
# which will behave just like another item type. Parents
@ -20,6 +19,11 @@ SWORD:
# Name displayed in the item lore.
name: 'Sword'
weapon: true
modifier: "MELEE_WEAPON"
lore-format: ""
category: "SPLASHING"
# Template of an unidentified item.
unident-item:
name: '&f#prefix#Unidentified Sword'

View File

@ -0,0 +1,12 @@
SPLASHING:
# The display parameter is used to display the item in both
# the item brower and the recipe list when shifting a
# workbench. You can use durability for custom textures.
# In 1.14, durability is replaced by CustomModelData.
display: IRON_SWORD:0
# Name displayed in the item lore.
name: 'Splashing'
lore-format: ""