Fixed chance not working in some modifier groups

This commit is contained in:
Jules 2024-04-16 22:25:13 -07:00
parent d2c74df6d2
commit c8b75bc43b
7 changed files with 166 additions and 263 deletions

View File

@ -6,9 +6,9 @@ import net.Indyuce.mmoitems.api.ItemTier;
import net.Indyuce.mmoitems.api.item.mmoitem.MMOItem;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate.TemplateOption;
import net.Indyuce.mmoitems.api.item.template.ModifierNode;
import net.Indyuce.mmoitems.api.item.template.NameModifier;
import net.Indyuce.mmoitems.api.item.template.NameModifier.ModifierType;
import net.Indyuce.mmoitems.api.item.template.TemplateModifier;
import net.Indyuce.mmoitems.stat.data.DoubleData;
import net.Indyuce.mmoitems.stat.data.StringData;
import net.Indyuce.mmoitems.stat.data.type.Mergeable;
@ -190,10 +190,10 @@ public class MMOItemBuilder extends Buildable<MMOItem> {
*/
@NotNull
@Deprecated
public static Collection<TemplateModifier> rollModifiers(@NotNull MMOItemTemplate template) {
public static Collection<ModifierNode> rollModifiers(@NotNull MMOItemTemplate template) {
if (!template.hasOption(TemplateOption.ROLL_MODIFIER_CHECK_ORDER)) return template.getModifiers().values();
List<TemplateModifier> modifiers = new ArrayList<>(template.getModifiers().values());
List<ModifierNode> modifiers = new ArrayList<>(template.getModifiers().values());
Collections.shuffle(modifiers);
return modifiers;
}

View File

@ -41,7 +41,7 @@ public class MMOItemTemplate implements ItemReference, PreloadedObject {
private NumericStatFormula modifierCapacity;
@Nullable
private ModifierGroup modifierGroup;
private ModifierNode modifierGroup;
private final Set<TemplateOption> options = new HashSet<>();
private final PostLoadAction postLoadAction = new PostLoadAction(config -> {
@ -64,7 +64,7 @@ public class MMOItemTemplate implements ItemReference, PreloadedObject {
// Read modifiers
try {
modifierGroup = config.contains("modifiers") ? new ModifierGroup(getId(), config) : null;
modifierGroup = config.contains("modifiers") ? new ModifierNode(getId(), config) : null;
if (modifierGroup != null) modifierGroup.getPostLoadAction().performAction();
} catch (Exception exception) {
ffp.log(FriendlyFeedbackCategory.ERROR, "Could not load modifier group: {0}", exception.getMessage());
@ -133,7 +133,7 @@ public class MMOItemTemplate implements ItemReference, PreloadedObject {
}
@Nullable
public ModifierGroup getModifierGroup() {
public ModifierNode getModifierGroup() {
return modifierGroup;
}
@ -152,16 +152,15 @@ public class MMOItemTemplate implements ItemReference, PreloadedObject {
@NotNull
@Deprecated
public Map<String, TemplateModifier> getModifiers() {
Map<String, TemplateModifier> built = new HashMap<>();
exploreMap(built, modifierGroup);
public Map<String, ModifierNode> getModifiers() {
Map<String, ModifierNode> built = new HashMap<>();
if (modifierGroup != null) exploreMap(built, modifierGroup);
return built;
}
private void exploreMap(Map<String, TemplateModifier> built, ModifierNode node) {
if (node instanceof ModifierGroup)
((ModifierGroup) node).getChildren().forEach(child -> exploreMap(built, child));
else if (node instanceof TemplateModifier) built.put(node.getId(), (TemplateModifier) node);
private void exploreMap(Map<String, ModifierNode> built, ModifierNode node) {
node.getChildren().forEach(child -> exploreMap(built, child));
built.put(node.getId(), node);
}
@Deprecated
@ -171,7 +170,7 @@ public class MMOItemTemplate implements ItemReference, PreloadedObject {
@Nullable
@Deprecated
public TemplateModifier getModifier(String id) {
public ModifierNode getModifier(String id) {
return getModifiers().get(id);
}

View File

@ -1,109 +0,0 @@
package net.Indyuce.mmoitems.api.item.template;
import io.lumine.mythic.lib.util.PostLoadAction;
import io.lumine.mythic.lib.util.PreloadedObject;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.item.build.MMOItemBuilder;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
public class ModifierGroup extends ModifierNode {
private final int min, max;
private final List<ModifierNode> children;
private final PostLoadAction postLoadAction = new PostLoadAction(config -> {
for (String key : config.getConfigurationSection("modifiers").getKeys(false))
try {
final ModifierNode child = ModifierNode.fromConfig(key, config.get("modifiers." + key));
if (child instanceof PreloadedObject) ((PreloadedObject) child).getPostLoadAction().performAction();
ModifierGroup.this.children.add(child);
} catch (RuntimeException exception) {
MMOItems.plugin.getLogger().log(Level.WARNING, "Could not load parent modifier node '" + key + "' of modifier group '" + getId() + "': " + exception.getMessage());
}
});
public ModifierGroup(@NotNull String nodeId, @NotNull Object configObject) {
super(nodeId, configObject);
if (getReferenceNode() != null) {
Validate.isTrue(getReferenceNode() instanceof ModifierGroup, "Reference node should be a modifier group");
final ModifierGroup parent = (ModifierGroup) getReferenceNode();
children = parent.children;
final ConfigurationSection config = configObject instanceof ConfigurationSection ? (ConfigurationSection) configObject : null;
min = config != null && config.contains("min") ? config.getInt("min") : parent.min;
max = config != null && config.contains("max") ? config.getInt("max") : parent.max;
return;
}
Validate.isTrue(configObject instanceof ConfigurationSection, "Must provide a config section when not using a reference node");
final ConfigurationSection config = (ConfigurationSection) configObject;
postLoadAction.cacheConfig(config);
Validate.isTrue(config.contains("modifiers"), "You must provide a modifier list");
min = config.getInt("min");
max = config.getInt("max", config.getConfigurationSection("modifiers").getKeys(false).size());
children = new ArrayList<>();
}
@Override
public PostLoadAction getPostLoadAction() {
return postLoadAction;
}
public List<ModifierNode> getChildren() {
return children;
}
@Override
public void whenCollected(@NotNull MMOItemBuilder builder, @NotNull UUID modifierId) {
// Get deep working copy of children list
final List<ModifierNode> children = new ArrayList<>();
this.children.forEach(child -> children.add(child));
if (builder.getTemplate().hasOption(MMOItemTemplate.TemplateOption.ROLL_MODIFIER_CHECK_ORDER))
Collections.shuffle(children);
final int effectiveMax = max <= 0 ? children.size() : Math.min(max, children.size());
// Normally roll all modifiers until max amount is reached
int i = 0;
while (this.children.size() - children.size() < effectiveMax && i < children.size()) {
final ModifierNode next = children.get(i);
if (next.collect(builder)) children.remove(i);
else i++;
}
// If needed, select more using chances as probability distributions
while (this.children.size() - children.size() < min) {
final ModifierNode node = children.remove(rollModifier(children));
node.collect(builder);
}
}
@NotNull
private int rollModifier(@NotNull List<ModifierNode> children) {
// Calculate cumulated weights and total weight
final double[] cumulatedWeights = new double[children.size()];
double totalWeight = 0;
for (int i = 0; i < children.size(); i++) {
totalWeight += children.get(i).getChance();
cumulatedWeights[i] = totalWeight;
}
final double random = RANDOM.nextDouble() * totalWeight;
for (int i = 0; i < children.size(); i++)
if (random <= cumulatedWeights[i])
return i;
throw new IllegalArgumentException("Could not roll new modifier from group '" + getId() + "'");
}
}

View File

@ -1,58 +1,81 @@
package net.Indyuce.mmoitems.api.item.template;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.util.PostLoadAction;
import io.lumine.mythic.lib.util.PreloadedObject;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.item.build.MMOItemBuilder;
import net.Indyuce.mmoitems.stat.data.random.RandomStatData;
import net.Indyuce.mmoitems.stat.type.ItemStat;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.Random;
import java.util.UUID;
import java.util.*;
import java.util.logging.Level;
/**
* An abstraction that groups both template modifier and modifier groups.
* Groups and modifiers create a tree where simple modifiers are the leaves
* and modifiers are nodes with arbitrarily many children.
* An abstraction that groups both template modifiers and modifier groups.
* Modifiers apply stats and groups apply multiple modifiers at the same time.
* Nodes can both apply stats and modifiers, like the nodes of a modifier tree.
*
* @author jules
* @author Jules
*/
public abstract class ModifierNode implements PreloadedObject {
public class ModifierNode implements PreloadedObject {
private final String id;
private final double chance, weight;
private final int min, max;
@Nullable
private final NameModifier nameModifier;
@Nullable
private final List<ModifierNode> children;
@Nullable
private final Map<ItemStat, RandomStatData> data;
private static final Random RANDOM = new Random();
/**
* Should not be confused with the parent node. Instead of fully
* defining a new modifier node, the user can reference another
* public node, given that it has been registered publically
* in the /modifiers folder.
* public node, given that it has been registered publicly in
* the /modifiers folder.
* <p>
* The reference node points to that public node.
*/
private final ModifierNode referenceNode;
protected static final Random RANDOM = new Random();
private final PostLoadAction postLoadAction = new PostLoadAction(config -> {
// Post-load further references
final ConfigurationSection modSection = config.getConfigurationSection("modifiers");
if (modSection != null) for (String key : modSection.getKeys(false))
try {
final ModifierNode child = ModifierNode.fromConfig(key, config.get("modifiers." + key));
child.getPostLoadAction().performAction();
ModifierNode.this.children.add(child);
} catch (RuntimeException exception) {
MMOItems.plugin.getLogger().log(Level.WARNING, "Could not load parent modifier node '" + key + "' of modifier group '" + getId() + "': " + exception.getMessage());
}
});
public ModifierNode(@NotNull String nodeId, @NotNull Object configObject) {
this.id = nodeId;
// Number -> simple reference
if (configObject instanceof Number) {
chance = 1;
weight = ((Number) configObject).doubleValue();
referenceNode = findReferenceNode(id);
chance = referenceNode.chance;
weight = ((Number) configObject).doubleValue();
nameModifier = referenceNode.nameModifier;
}
// Path -> simple reference
else if (configObject instanceof String) {
chance = 1;
weight = 0;
referenceNode = findReferenceNode(configObject.toString());
chance = referenceNode.chance;
weight = referenceNode.weight;
nameModifier = referenceNode.nameModifier;
}
@ -70,23 +93,50 @@ public abstract class ModifierNode implements PreloadedObject {
this.chance = config.getDouble("chance", referenceNode != null ? referenceNode.getChance() : 1);
this.weight = config.getDouble("weight", referenceNode != null ? referenceNode.getWeight() : 0);
this.nameModifier = config.contains("suffix") ? new NameModifier(NameModifier.ModifierType.SUFFIX, config.get("suffix"))
: config.contains("prefix") ? new NameModifier(NameModifier.ModifierType.PREFIX, config.get("prefix"))
: referenceNode != null ? referenceNode.nameModifier : null;
this.nameModifier = config.contains("suffix") ? new NameModifier(NameModifier.ModifierType.SUFFIX, config.get("suffix")) : config.contains("prefix") ? new NameModifier(NameModifier.ModifierType.PREFIX, config.get("prefix")) : referenceNode != null ? referenceNode.nameModifier : null;
Validate.isTrue(chance > 0, "Chance must be strictly positive");
}
// No type found
else
throw new IllegalArgumentException("Must be either a string or config section");
}
else throw new IllegalArgumentException("Must be either a string, number config section");
@NotNull
private ModifierNode findReferenceNode(@NotNull String id) {
final ModifierNode node = MMOItems.plugin.getTemplates().getModifierNode(id);
Validate.notNull(node, "Could not find public modifier with ID '" + id + "'");
return node;
if (referenceNode != null) {
// Group
children = referenceNode.children;
final ConfigurationSection config = configObject instanceof ConfigurationSection ? (ConfigurationSection) configObject : null;
min = config != null && config.contains("min") ? config.getInt("min") : referenceNode.min;
max = config != null && config.contains("max") ? config.getInt("max") : referenceNode.max;
// Modifier
data = referenceNode.data;
}
// No reference node
else {
Validate.isTrue(configObject instanceof ConfigurationSection, "Must provide a config section when not using a reference node");
final ConfigurationSection config = (ConfigurationSection) configObject;
// Group
min = config.getInt("min");
max = config.getInt("max", -1);
children = new ArrayList<>();
postLoadAction.cacheConfig(config);
// Modifier
this.data = new HashMap<>();
final ConfigurationSection statSection = config.getConfigurationSection("stats");
if (statSection != null) for (String key : statSection.getKeys(false))
try {
final String statId = UtilityMethods.enumName(key);
final ItemStat stat = MMOItems.plugin.getStats().get(statId);
Validate.notNull(stat, "Could not find stat with ID '" + statId + "'");
ModifierNode.this.data.put(stat, stat.whenInitialized(statSection.get(key)));
} catch (IllegalArgumentException exception) {
MMOItems.plugin.getLogger().log(Level.INFO, "An error occurred while trying to load modifier node " + getId() + ": " + exception.getMessage());
}
}
}
@NotNull
@ -102,6 +152,11 @@ public abstract class ModifierNode implements PreloadedObject {
return chance;
}
@NotNull
public Map<ItemStat, RandomStatData> getItemData() {
return data;
}
@Nullable
public ModifierNode getReferenceNode() {
return referenceNode;
@ -120,6 +175,15 @@ public abstract class ModifierNode implements PreloadedObject {
return RANDOM.nextDouble() < chance;
}
@Override
public PostLoadAction getPostLoadAction() {
return postLoadAction;
}
public List<ModifierNode> getChildren() {
return children;
}
public boolean collect(@NotNull MMOItemBuilder builder) {
// Roll modifier node chance
@ -136,35 +200,65 @@ public abstract class ModifierNode implements PreloadedObject {
return true;
}
/**
* This should apply the stats of the modifier nodes
* and call subsequent modifier nodes if necessary.
*/
public abstract void whenCollected(@NotNull MMOItemBuilder builder, @NotNull UUID modifierId);
public void whenCollected(@NotNull MMOItemBuilder builder, @NotNull UUID modifierId) {
// VANILLA MODIFIER SECTION
data.forEach((itemStat, statData) -> builder.addModifierData(itemStat, statData.randomize(builder), modifierId));
// MODIFIER GROUP SECTION
// Get deep working copy of children list
final List<ModifierNode> children = new ArrayList<>(this.children);
if (builder.getTemplate().hasOption(MMOItemTemplate.TemplateOption.ROLL_MODIFIER_CHECK_ORDER))
Collections.shuffle(children);
final int effectiveMax = max <= 0 ? children.size() : Math.min(max, children.size());
// Normally roll all modifiers until max amount is reached
int i = 0;
while (this.children.size() - children.size() < effectiveMax && i < children.size()) {
final ModifierNode next = children.get(i);
if (next.collect(builder)) children.remove(i);
else i++;
}
// If needed, select more using chances as probability distributions
while (this.children.size() - children.size() < min) {
final ModifierNode node = children.remove(rollModifier(children));
node.collect(builder);
}
}
@NotNull
public static ModifierNode fromConfig(@NotNull String nodeId, @NotNull Object configObject) {
if (configObject instanceof ConfigurationSection) {
final ConfigurationSection config = (ConfigurationSection) configObject;
if (config.contains("modifiers")) return new ModifierGroup(nodeId, configObject);
if (config.contains("stats")) return new TemplateModifier(nodeId, configObject);
return fromReference(nodeId, configObject);
}
if (configObject instanceof Number) return fromReference(nodeId, configObject);
if (configObject instanceof String)
return MMOItems.plugin.getTemplates().getModifierNode(configObject.toString());
throw new IllegalArgumentException("You must provide either a string, config section or number");
if (configObject instanceof Number || configObject instanceof String)
Validate.notNull(findReferenceNode(nodeId), "Could not find reference node called '" + nodeId + "'");
return new ModifierNode(nodeId, configObject);
}
@NotNull
private static ModifierNode fromReference(@NotNull String nodeId, @NotNull Object configObject) {
final ModifierNode ref = MMOItems.plugin.getTemplates().getModifierNode(nodeId);
Validate.notNull(ref, "Could not find reference node called '" + nodeId + "'");
if (ref instanceof TemplateModifier) return new TemplateModifier(nodeId, configObject);
if (ref instanceof ModifierGroup) return new ModifierGroup(nodeId, configObject);
throw new IllegalArgumentException("Could not match modifier node");
private static ModifierNode findReferenceNode(@NotNull String id) {
final ModifierNode node = MMOItems.plugin.getTemplates().getModifierNode(id);
Validate.notNull(node, "Could not find public modifier with ID '" + id + "'");
return node;
}
@NotNull
private int rollModifier(@NotNull List<ModifierNode> children) {
// Calculate cumulated weights and total weight
final double[] cumulatedWeights = new double[children.size()];
double totalWeight = 0;
for (int i = 0; i < children.size(); i++) {
totalWeight += children.get(i).getChance();
cumulatedWeights[i] = totalWeight;
}
final double random = RANDOM.nextDouble() * totalWeight;
for (int i = 0; i < children.size(); i++)
if (random <= cumulatedWeights[i]) return i;
throw new IllegalArgumentException("Could not roll new modifier from group '" + getId() + "'");
}
}

View File

@ -1,77 +0,0 @@
package net.Indyuce.mmoitems.api.item.template;
import io.lumine.mythic.lib.UtilityMethods;
import io.lumine.mythic.lib.util.PostLoadAction;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.item.build.MMOItemBuilder;
import net.Indyuce.mmoitems.stat.data.random.RandomStatData;
import net.Indyuce.mmoitems.stat.type.ItemStat;
import org.apache.commons.lang.Validate;
import org.bukkit.configuration.ConfigurationSection;
import org.jetbrains.annotations.NotNull;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.logging.Level;
public class TemplateModifier extends ModifierNode {
private final Map<ItemStat, RandomStatData> data;
private final PostLoadAction postLoadAction = new PostLoadAction(config -> {
Validate.notNull(config.getConfigurationSection("stats"), "Could not find base item data");
for (String key : config.getConfigurationSection("stats").getKeys(false))
try {
final String statId = UtilityMethods.enumName(key);
final ItemStat stat = MMOItems.plugin.getStats().get(statId);
Validate.notNull(stat, "Could not find stat with ID '" + statId + "'");
TemplateModifier.this.data.put(stat, stat.whenInitialized(config.get("stats." + key)));
} catch (IllegalArgumentException exception) {
MMOItems.plugin.getLogger().log(Level.INFO, "An error occurred while trying to load modifier node " + getId() + ": " + exception.getMessage());
}
});
public TemplateModifier(@NotNull ConfigurationSection config) {
this(config.getName(), config);
}
/**
* Loads an item gen modifier from a configuration section. If you provide
* the ItemGenManager, you will be able to use the 'parent' option to
* redirect that modifier to a public gen modifier.
*
* @param nodeId Internal ID of the modifier
* @param configObject Either a string or a config section containing the template data
*/
public TemplateModifier(@NotNull String nodeId, @NotNull Object configObject) {
super(nodeId, configObject);
// Use reference node
if (getReferenceNode() != null) {
Validate.isTrue(getReferenceNode() instanceof TemplateModifier, "Reference node should be a simple modifier");
final TemplateModifier parent = (TemplateModifier) getReferenceNode();
data = parent.data;
return;
}
// Make sure it's a config section
Validate.isTrue(configObject instanceof ConfigurationSection, "Must provide a config section when not using a reference node");
postLoadAction.cacheConfig((ConfigurationSection) configObject);
this.data = new HashMap<>();
}
@Override
public PostLoadAction getPostLoadAction() {
return postLoadAction;
}
@NotNull
public Map<ItemStat, RandomStatData> getItemData() {
return data;
}
@Override
public void whenCollected(@NotNull MMOItemBuilder builder, @NotNull UUID modifierId) {
data.forEach((itemStat, statData) -> builder.addModifierData(itemStat, statData.randomize(builder), modifierId));
}
}

View File

@ -5,7 +5,7 @@ import io.lumine.mythic.lib.api.util.ui.FriendlyFeedbackProvider;
import net.Indyuce.mmoitems.MMOItems;
import net.Indyuce.mmoitems.api.ConfigFile;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.item.template.TemplateModifier;
import net.Indyuce.mmoitems.api.item.template.ModifierNode;
import net.Indyuce.mmoitems.api.player.PlayerData;
import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import net.Indyuce.mmoitems.gui.PluginInventory;
@ -58,7 +58,7 @@ public abstract class EditionInventory extends PluginInventory {
* edit the base item data.
*/
@Deprecated
private TemplateModifier editedModifier = null;
private ModifierNode editedModifier = null;
private ItemStack cachedItem;
private int previousPage;

View File

@ -9,7 +9,6 @@ import net.Indyuce.mmoitems.api.ItemTier;
import net.Indyuce.mmoitems.api.Type;
import net.Indyuce.mmoitems.api.item.template.MMOItemTemplate;
import net.Indyuce.mmoitems.api.item.template.ModifierNode;
import net.Indyuce.mmoitems.api.item.template.TemplateModifier;
import net.Indyuce.mmoitems.api.util.TemplateMap;
import net.Indyuce.mmoitems.api.util.message.FFPMMOItems;
import org.apache.commons.lang.Validate;
@ -21,7 +20,6 @@ import org.jetbrains.annotations.Nullable;
import java.io.File;
import java.util.*;
import java.util.logging.Level;
import java.util.stream.Collectors;
public class TemplateManager implements Reloadable {
@ -184,8 +182,8 @@ public class TemplateManager implements Reloadable {
}
/**
* @return Collects all existing mmoitem templates into a set so that it can
* be filtered afterwards to generate random loot
* @return Collects all existing MMOItems templates into a set
* so that it can be filtered afterward to generate random loot
*/
public Collection<MMOItemTemplate> collectTemplates() {
return templates.collectValues();
@ -193,20 +191,18 @@ public class TemplateManager implements Reloadable {
@Deprecated
public boolean hasModifier(String id) {
final ModifierNode node = modifierNodes.get(id);
return node != null && node instanceof TemplateModifier;
return modifierNodes.get(id) != null;
}
@Deprecated
@Nullable
public TemplateModifier getModifier(String id) {
final ModifierNode node = modifierNodes.get(id);
return node instanceof TemplateModifier ? (TemplateModifier) node : null;
public ModifierNode getModifier(String id) {
return modifierNodes.get(id);
}
@Deprecated
public Collection<TemplateModifier> getModifiers() {
return modifierNodes.values().stream().filter(node -> node instanceof TemplateModifier).map(node -> (TemplateModifier) node).collect(Collectors.toList());
public Collection<ModifierNode> getModifiers() {
return modifierNodes.values();
}
public boolean hasModifierNode(@NotNull String id) {