|
|
Using MMOItems API you may register custom stats as complex as abilities or item restrictions. The first thing you will need to do is define a class which extends `DoubleStat` which is an abstract class with many functions, we will go over what each does, but first let's deal with the ItemStat constructor you need to call.
|
|
|
|
|
|
```super("GEM_SOCKETS", new ItemStack(Material.EMERALD), "Gem Sockets", new String[] { "The amount of gem", "sockets your weapon has." }, new String[] { "piercing", "slashing", "blunt", "offhand", "range", "tool", "armor", "accessory" });``` |
|
|
\ No newline at end of file |
|
|
## ItemStat constructor
|
|
|
```public ItemStat(String id, ItemStack item, String name, String[] lore, String[] types, Material... materials)```
|
|
|
|
|
|
The string id corresponds to your internal stat ID. You may only use upper case letters and _ dashes. This stat is used to calculate player stats in MMOLib, used to saved stat data in MMOItems config files, literally anywhere in the plugin. Do never change it once your stat is setup otherwise you might "lose" precious created item data.
|
|
|
|
|
|
The ItemStack parameter corresponds to the icon your stat has in the item edition GUI. `name` corresponds to your stat name, it's also used to display your stat in the edition GUI. This piece of string cannot be seen by normal players. `lore` is your stat description (edition GUI).
|
|
|
|
|
|
`types` is the list of the item types which are supported by your item stat.\
|
|
|
`new String[] { "!armor", "!gem_stone", "all" }` would ban armors and gem stones from using your stat. Using `!<type_name>` you can ban a specific item type. Make sure you put type restrictions at the beginning of the string array. Using `all` you can make the stat compatible with any item type. Make sure you use `all` after type restrictions.
|
|
|
|
|
|
`materials` are the item materials supported. This is specific to some stats like `Dye Color` or `Shield Pattern` which depend on the ItemMeta instance and which are type-specific. If the given array is empty, the stat is obviously available for any type by default.
|
|
|
|
|
|
## Methods to override
|
|
|
This is the class for numeric stats.
|
|
|
```
|
|
|
@Override
|
|
|
public StatData whenInitialized(Object object) {
|
|
|
return new DoubleData(object);
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public RandomStatData whenInitializedGeneration(Object object) {
|
|
|
|
|
|
if (object instanceof Number)
|
|
|
return new NumericStatFormula(Double.valueOf(object.toString()), 0, 0, 0);
|
|
|
|
|
|
if (object instanceof ConfigurationSection)
|
|
|
return new NumericStatFormula((ConfigurationSection) object);
|
|
|
|
|
|
throw new IllegalArgumentException("Must specify a number or a config section");
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void whenApplied(MMOItemBuilder item, StatData data) {
|
|
|
double value = ((DoubleData) data).generateNewValue();
|
|
|
item.addItemTag(new ItemTag(getNBTPath(), value));
|
|
|
item.getLore().insert(getPath(), format(value, "#", new StatFormat("##").format(value)));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public boolean whenClicked(EditionInventory inv, InventoryClickEvent event) {
|
|
|
if (event.getAction() == InventoryAction.PICKUP_HALF) {
|
|
|
ConfigFile config = inv.getItemType().getConfigFile();
|
|
|
config.getConfig().set(inv.getItemId() + "." + getPath(), null);
|
|
|
inv.registerItemEdition(config);
|
|
|
inv.open();
|
|
|
inv.getPlayer().sendMessage(MMOItems.plugin.getPrefix() + "Successfully removed " + getName() + ChatColor.GRAY + ".");
|
|
|
return true;
|
|
|
}
|
|
|
new StatEdition(inv, this).enable("Write in the chat the numeric value you want.",
|
|
|
"Or write [MIN-VALUE]=[MAX-VALUE] to make the stat random.");
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public boolean whenInput(EditionInventory inv, ConfigFile config, String message, Object... info) {
|
|
|
String[] split = message.split("\\=");
|
|
|
double value = 0;
|
|
|
double value1 = 0;
|
|
|
try {
|
|
|
value = Double.parseDouble(split[0]);
|
|
|
} catch (Exception e1) {
|
|
|
inv.getPlayer().sendMessage(MMOItems.plugin.getPrefix() + ChatColor.RED + split[0] + " is not a valid number.");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// second value
|
|
|
if (split.length > 1)
|
|
|
try {
|
|
|
value1 = Double.parseDouble(split[1]);
|
|
|
} catch (Exception e1) {
|
|
|
inv.getPlayer().sendMessage(MMOItems.plugin.getPrefix() + ChatColor.RED + split[1] + " is not a valid number.");
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// STRING if length == 2
|
|
|
// DOUBLE if length == 1
|
|
|
config.getConfig().set(inv.getItemId() + "." + getPath(), split.length > 1 ? value + "=" + value1 : value);
|
|
|
if (value == 0 && value1 == 0)
|
|
|
config.getConfig().set(inv.getItemId() + "." + getPath(), null);
|
|
|
inv.registerItemEdition(config);
|
|
|
inv.open();
|
|
|
inv.getPlayer().sendMessage(MMOItems.plugin.getPrefix() + getName() + " successfully changed to "
|
|
|
+ (value1 != 0 ? "{between " + value + " and " + value1 + "}" : "" + value) + ".");
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void whenLoaded(MMOItem mmoitem, NBTItem item) {
|
|
|
if (item.hasTag(getNBTPath()))
|
|
|
mmoitem.setData(this, new DoubleData(item.getDouble(getNBTPath())));
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void whenDisplayed(List<String> lore, FileConfiguration config, String id) {
|
|
|
lore.add("");
|
|
|
|
|
|
String[] split = config.contains(id + "." + getPath()) ? config.getString(id + "." + getPath()).split("\\=") : new String[] { "0" };
|
|
|
String format = split.length > 1 ? tryParse(split[0]) + " -> " + tryParse(split[1]) : "" + config.getDouble(id + "." + getPath());
|
|
|
|
|
|
lore.add(ChatColor.GRAY + "Current Value: " + ChatColor.GREEN + format);
|
|
|
lore.add("");
|
|
|
lore.add(ChatColor.YELLOW + AltChar.listDash + " Left click to change this value.");
|
|
|
lore.add(ChatColor.YELLOW + AltChar.listDash + " Right click to remove this value.");
|
|
|
}
|
|
|
```
|
|
|
|
|
|
### whenInitialized(Object)
|
|
|
`whenInitialized(Object)` is called when MMOItems is requesting to read stat data from the config file. `obj` could be anything, it's the object obtained using `ConfigurationSection#get(#yourStatPath#)`. Here, users can either use a double to have a set stat value, or a config section and in that case MMOItems reads a numeric stat formula from that config section.
|
|
|
|
|
|
This method must return a class which extends the `StatData` interface. It's a **purely cosmetic** interface because it has no function to override but I might need to add some functions to it in a later update. **You can see a StatData class example under the _Mergeable Stats_ section**.
|
|
|
|
|
|
### whenInitializedGeneration(Object)
|
|
|
`whenInitializedGeneration(Object)` is almost the same thing, except that should return a `RandomStatData` instance. The `RandomStatData` interface is used to store data about stat data from an item generation template. For further info, see the RandomStatData paragraph below.
|
|
|
|
|
|
### whenApplied(MMOItemBuilder, StatData)
|
|
|
This function is used by MMOItems when generating an `ItemStack` instance based on a list of `StatData` instances. You can access the item meta, item lore using the `MMOItemBuilder` instance. You will need to cast the `StatData` instance given as input to whatever your stat is compatible with. MMOItems only gives you as input what you gave it as output in the `whenInitialized(Generation)` methods.
|
|
|
|
|
|
### whenClicked(EditionInventory, InventoryClickEvent)
|
|
|
This is the function used to do stuff when a player just clicked the stat icon in the item edition GUI.
|
|
|
|
|
|
### whenInput(EditionInventory, ConfigFile config, String message, Object...)
|
|
|
Used to do stuff when a player just inputs some message in the chat. You can request for a player chat input using the following method:
|
|
|
```new StatEdition(inv, this, objectArray).enable("Write in the chat the numeric value you want.", "Or write [MIN-VALUE]=[MAX-VALUE] to make the stat random.");```
|
|
|
That function would typically be called when the player clicks the stat icon in the edition GUI. The `objectArray` parameter in the `StatEdition` class constructor can be replaced by **any object at all**, it's a way for more complex stats like elements or abilities what the player is editing (is it the ability casting mode, what ability, etc??). That array is kept intact and given as input in the `whenInput` method.
|
|
|
|
|
|
### whenLoaded(MMOItem, NBTItem)
|
|
|
Used to load stat data from a random ItemStack.
|
|
|
|
|
|
### whenDisplayed(List<String>, ConfigurationSection, String)
|
|
|
It's a really old method which will be updated soon. It's the function used to display the current item stat data in the edition GUI. It is still using config sections as input, it will be soon replaced by StatDatas for cleaner functionality.
|
|
|
|
|
|
## RandomStatDatas
|
|
|
They are the equivalent of StatData's but with a random factor needed for random item generation. A good example is the NumericStatFormula which is used for any numeric stats.
|
|
|
```
|
|
|
public class NumericStatFormula implements RandomStatData {
|
|
|
private final double base, scale, spread, maxSpread;
|
|
|
|
|
|
private static final Random random = new Random();
|
|
|
|
|
|
public static final NumericStatFormula ZERO = new NumericStatFormula(0, 0, 0, 0);
|
|
|
|
|
|
public NumericStatFormula(Object object) {
|
|
|
Validate.notNull(object, "Config must not be null");
|
|
|
|
|
|
if (object instanceof Number) {
|
|
|
base = Double.valueOf(object.toString());
|
|
|
scale = 0;
|
|
|
spread = 0;
|
|
|
maxSpread = 0;
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (object instanceof ConfigurationSection) {
|
|
|
ConfigurationSection config = (ConfigurationSection) object;
|
|
|
base = config.getDouble("base");
|
|
|
scale = config.getDouble("scale");
|
|
|
spread = config.getDouble("spread");
|
|
|
maxSpread = config.getDouble("max-spread");
|
|
|
|
|
|
Validate.isTrue(spread >= 0, "Spread must be positive");
|
|
|
Validate.isTrue(maxSpread >= 0, "Max spread must be positive");
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
throw new IllegalArgumentException("Must specify a config section or a number");
|
|
|
}
|
|
|
|
|
|
/*
|
|
|
* used as a StatData class to generate a DoubleData instance!
|
|
|
*/
|
|
|
public NumericStatFormula(double base, double scale, double spread, double maxSpread) {
|
|
|
this.base = base;
|
|
|
this.scale = scale;
|
|
|
this.spread = spread;
|
|
|
this.maxSpread = maxSpread;
|
|
|
}
|
|
|
|
|
|
public double getBase() {
|
|
|
return base;
|
|
|
}
|
|
|
|
|
|
public double getScale() {
|
|
|
return scale;
|
|
|
}
|
|
|
|
|
|
public double calculate(double x) {
|
|
|
|
|
|
// calculate linear value
|
|
|
double linear = base + scale * x;
|
|
|
|
|
|
// apply gaussian distribution to add +- maxSpread%
|
|
|
// spread represents the standard deviation in % of the calculated
|
|
|
// linear value
|
|
|
double gaussian = linear * (1 + Math.min(Math.max(random.nextGaussian() * spread, -maxSpread), maxSpread));
|
|
|
|
|
|
return gaussian;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public StatData randomize(GeneratedItemBuilder builder) {
|
|
|
return new DoubleData(calculate(builder.getLevel()));
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
The `randomize` function is the key here. This takes as input a GeneratedItemBuilder which contains info about the item level, tier etc. and outputs a randomized `RandomStatData`, i.e a `StatData` instance which will be later used to build the item!
|
|
|
|
|
|
The `GeneratedItemBuilder` class can be seen as the equivalent of the `MMOItemBuilder` for random stat generation. When a random item is being generated from an item template, MMOItems first creates a `GeneratedItemBuilder`, rolls the item modifiers and gather the randomized `StatData` when generates the item using a `MMOItemBuilder`.
|
|
|
|
|
|
## Mergeable stats
|
|
|
Mergeable stats are stats which can be stacked with each others, for instance when a player tries to apply a gem stone onto another item, where MMOItems needs to "merge" the two stat datas together. A good example is the `DoubleData` class.
|
|
|
```
|
|
|
public class DoubleData implements StatData, Mergeable {
|
|
|
private static final Random random = new Random();
|
|
|
|
|
|
private double min, max;
|
|
|
|
|
|
public DoubleData(Object object) {
|
|
|
if (object instanceof Number) {
|
|
|
min = Double.valueOf(object.toString());
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
if (object instanceof String) {
|
|
|
String[] split = ((String) object).split("\\=");
|
|
|
Validate.isTrue(split.length == 2, "Must specify a valid range");
|
|
|
min = Double.parseDouble(split[0]);
|
|
|
max = Double.parseDouble(split[1]);
|
|
|
return;
|
|
|
}
|
|
|
|
|
|
throw new IllegalArgumentException("Must specify a range or a number");
|
|
|
}
|
|
|
|
|
|
public DoubleData(double min, double max) {
|
|
|
this.min = min;
|
|
|
this.max = max;
|
|
|
}
|
|
|
|
|
|
public boolean hasMax() {
|
|
|
return max > min && max != 0;
|
|
|
}
|
|
|
|
|
|
public double getMin() {
|
|
|
return min;
|
|
|
}
|
|
|
|
|
|
public double getMax() {
|
|
|
return max;
|
|
|
}
|
|
|
|
|
|
public void setMax(double value) {
|
|
|
max = value;
|
|
|
}
|
|
|
|
|
|
public void setMin(double value) {
|
|
|
min = value;
|
|
|
}
|
|
|
|
|
|
public void add(double value) {
|
|
|
min = min + value;
|
|
|
}
|
|
|
|
|
|
public void addRelative(double value) {
|
|
|
min *= 1 + value;
|
|
|
}
|
|
|
|
|
|
public double generateNewValue() {
|
|
|
return hasMax() ? min + random.nextDouble() * (max - min) : min;
|
|
|
}
|
|
|
|
|
|
@Override
|
|
|
public void merge(StatData data) {
|
|
|
Validate.isTrue(data instanceof DoubleData, "Cannot merge two different stat data types");
|
|
|
min += ((DoubleData) data).min;
|
|
|
// TODO if hasMax()
|
|
|
}
|
|
|
}
|
|
|
```
|
|
|
Simple enough: MMOItems just adds the two values together. A little specification here: the `min` and `max` options are used to have a stat range but whenever that `StatData` is loaded from an NBTItem using `whenLoading(MMOItem mmoitem, NBTItem)`, only the `min` variable is used because a generated item does not have stat ranges anymore, they have already been rolled on item generation. |
|
|
\ No newline at end of file |