mirror of
https://github.com/Minestom/Minestom.git
synced 2024-09-28 14:37:31 +02:00
Loot table support
This commit is contained in:
parent
c50030cd6b
commit
3c0d351f15
@ -5,6 +5,7 @@ import net.minestom.server.command.CommandManager;
|
|||||||
import net.minestom.server.data.DataManager;
|
import net.minestom.server.data.DataManager;
|
||||||
import net.minestom.server.entity.EntityManager;
|
import net.minestom.server.entity.EntityManager;
|
||||||
import net.minestom.server.entity.Player;
|
import net.minestom.server.entity.Player;
|
||||||
|
import net.minestom.server.gamedata.loottables.LootTableManager;
|
||||||
import net.minestom.server.instance.InstanceManager;
|
import net.minestom.server.instance.InstanceManager;
|
||||||
import net.minestom.server.instance.block.BlockManager;
|
import net.minestom.server.instance.block.BlockManager;
|
||||||
import net.minestom.server.listener.manager.PacketListenerManager;
|
import net.minestom.server.listener.manager.PacketListenerManager;
|
||||||
@ -90,6 +91,7 @@ public class MinecraftServer {
|
|||||||
// Data
|
// Data
|
||||||
private static ResponseDataConsumer responseDataConsumer;
|
private static ResponseDataConsumer responseDataConsumer;
|
||||||
private static Difficulty difficulty = Difficulty.NORMAL;
|
private static Difficulty difficulty = Difficulty.NORMAL;
|
||||||
|
private static LootTableManager lootTableManager;
|
||||||
|
|
||||||
public static MinecraftServer init() {
|
public static MinecraftServer init() {
|
||||||
connectionManager = new ConnectionManager();
|
connectionManager = new ConnectionManager();
|
||||||
@ -109,6 +111,8 @@ public class MinecraftServer {
|
|||||||
|
|
||||||
updateManager = new UpdateManager();
|
updateManager = new UpdateManager();
|
||||||
|
|
||||||
|
lootTableManager = new LootTableManager();
|
||||||
|
|
||||||
nettyServer = new NettyServer(packetProcessor);
|
nettyServer = new NettyServer(packetProcessor);
|
||||||
|
|
||||||
// Registry
|
// Registry
|
||||||
@ -201,6 +205,10 @@ public class MinecraftServer {
|
|||||||
return responseDataConsumer;
|
return responseDataConsumer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static LootTableManager getLootTableManager() {
|
||||||
|
return lootTableManager;
|
||||||
|
}
|
||||||
|
|
||||||
public void start(String address, int port, ResponseDataConsumer responseDataConsumer) {
|
public void start(String address, int port, ResponseDataConsumer responseDataConsumer) {
|
||||||
LOGGER.info("Starting Minestom server.");
|
LOGGER.info("Starting Minestom server.");
|
||||||
MinecraftServer.responseDataConsumer = responseDataConsumer;
|
MinecraftServer.responseDataConsumer = responseDataConsumer;
|
||||||
|
@ -6,6 +6,26 @@ import java.util.concurrent.ConcurrentHashMap;
|
|||||||
|
|
||||||
public class Data {
|
public class Data {
|
||||||
|
|
||||||
|
public static final Data EMPTY = new Data() {
|
||||||
|
@Override
|
||||||
|
public <T> void set(String key, T value, Class<T> type) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T get(String key) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean hasKey(String key) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public <T> T getOrDefault(String key, T defaultValue) {
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
protected ConcurrentHashMap<String, Object> data = new ConcurrentHashMap();
|
protected ConcurrentHashMap<String, Object> data = new ConcurrentHashMap();
|
||||||
|
|
||||||
public <T> void set(String key, T value, Class<T> type) {
|
public <T> void set(String key, T value, Class<T> type) {
|
||||||
|
17
src/main/java/net/minestom/server/gamedata/Condition.java
Normal file
17
src/main/java/net/minestom/server/gamedata/Condition.java
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package net.minestom.server.gamedata;
|
||||||
|
|
||||||
|
import net.minestom.server.data.Data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a condition, used by predicates in MC functions and in loot tables.
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface Condition {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests this condition. Subclasses are free to throw runtime exceptions if the arguments passed through data are not valid or missing
|
||||||
|
* @param data arguments to give to the condition. May be null if the condition supports it
|
||||||
|
* @return 'true' if the condition passed, 'false' otherwise
|
||||||
|
*/
|
||||||
|
boolean test(Data data);
|
||||||
|
}
|
@ -0,0 +1,22 @@
|
|||||||
|
package net.minestom.server.gamedata.conditions;
|
||||||
|
|
||||||
|
import net.minestom.server.data.Data;
|
||||||
|
import net.minestom.server.gamedata.Condition;
|
||||||
|
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Requires 'explosionPower' double argument
|
||||||
|
*/
|
||||||
|
public class SurvivesExplosionCondition implements Condition {
|
||||||
|
private Random rng = new Random();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean test(Data data) {
|
||||||
|
if(data == null)
|
||||||
|
return true; // no explosion here
|
||||||
|
if(!data.hasKey("explosionPower"))
|
||||||
|
return true; // no explosion here
|
||||||
|
return rng.nextDouble() <= 1.0/data.<Double>get("explosionPower");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,30 @@
|
|||||||
|
package net.minestom.server.gamedata.loottables;
|
||||||
|
|
||||||
|
import net.minestom.server.data.Data;
|
||||||
|
import net.minestom.server.gamedata.Condition;
|
||||||
|
import net.minestom.server.item.ItemStack;
|
||||||
|
|
||||||
|
import java.util.Collection;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loot table function that must meet some conditions to be applied
|
||||||
|
*/
|
||||||
|
public class ConditionedFunctionWrapper implements LootTableFunction {
|
||||||
|
|
||||||
|
private final LootTableFunction baseFunction;
|
||||||
|
private final Collection<Condition> conditions;
|
||||||
|
|
||||||
|
public ConditionedFunctionWrapper(LootTableFunction baseFunction, Collection<Condition> conditions) {
|
||||||
|
this.baseFunction = baseFunction;
|
||||||
|
this.conditions = conditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ItemStack apply(ItemStack stack, Data data) {
|
||||||
|
for (Condition c : conditions) {
|
||||||
|
if(!c.test(data))
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
return baseFunction.apply(stack, data);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,122 @@
|
|||||||
|
package net.minestom.server.gamedata.loottables;
|
||||||
|
|
||||||
|
import net.minestom.server.data.Data;
|
||||||
|
import net.minestom.server.gamedata.Condition;
|
||||||
|
import net.minestom.server.item.ItemStack;
|
||||||
|
import net.minestom.server.utils.WeightedRandom;
|
||||||
|
import net.minestom.server.utils.WeightedRandomItem;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
public class LootTable {
|
||||||
|
|
||||||
|
public static final String LUCK_KEY = "minecraft:luck";
|
||||||
|
|
||||||
|
private final LootTableType type;
|
||||||
|
private final List<LootTable.Pool> pools;
|
||||||
|
|
||||||
|
public LootTable(LootTableType type, List<Pool> pools) {
|
||||||
|
this.type = type;
|
||||||
|
this.pools = pools;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LootTableType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Pool> getPools() {
|
||||||
|
return pools;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<ItemStack> generate(Data arguments) {
|
||||||
|
if(arguments == null)
|
||||||
|
arguments = Data.EMPTY;
|
||||||
|
List<ItemStack> output = new LinkedList<>();
|
||||||
|
for(Pool p : pools) {
|
||||||
|
p.generate(output, arguments);
|
||||||
|
}
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Pool {
|
||||||
|
private final int minRollCount;
|
||||||
|
private final int maxRollCount;
|
||||||
|
private final int bonusMinRollCount;
|
||||||
|
private final int bonusMaxRollCount;
|
||||||
|
private final List<LootTable.Entry> entries;
|
||||||
|
private final List<Condition> conditions;
|
||||||
|
|
||||||
|
public Pool(int minRollCount, int maxRollCount, int bonusMinRollCount, int bonusMaxRollCount, List<Entry> entries, List<Condition> conditions) {
|
||||||
|
this.minRollCount = minRollCount;
|
||||||
|
this.maxRollCount = maxRollCount;
|
||||||
|
this.bonusMinRollCount = bonusMinRollCount;
|
||||||
|
this.bonusMaxRollCount = bonusMaxRollCount;
|
||||||
|
this.entries = entries;
|
||||||
|
this.conditions = conditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Condition> getConditions() {
|
||||||
|
return conditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMinRollCount() {
|
||||||
|
return minRollCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMaxRollCount() {
|
||||||
|
return maxRollCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Entry> getEntries() {
|
||||||
|
return entries;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void generate(List<ItemStack> output, Data arguments) {
|
||||||
|
for(Condition c : conditions) {
|
||||||
|
if(!c.test(arguments))
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Random rng = new Random();
|
||||||
|
int luck = arguments.getOrDefault(LUCK_KEY, 0);
|
||||||
|
int rollCount = rng.nextInt(maxRollCount - minRollCount +1 /*inclusive*/) + minRollCount;
|
||||||
|
int bonusRollCount = rng.nextInt(bonusMaxRollCount - bonusMinRollCount +1 /*inclusive*/) + bonusMinRollCount;
|
||||||
|
bonusRollCount *= luck;
|
||||||
|
WeightedRandom<Entry> weightedRandom = new WeightedRandom<>(entries);
|
||||||
|
for (int i = 0; i < rollCount+bonusRollCount; i++) {
|
||||||
|
Entry entry = weightedRandom.get(rng);
|
||||||
|
ItemStack stack = entry.generateStack(arguments);
|
||||||
|
if(!stack.isAir()) {
|
||||||
|
output.add(stack);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract static class Entry implements WeightedRandomItem {
|
||||||
|
private final LootTableEntryType type;
|
||||||
|
private final int weight;
|
||||||
|
private final int quality;
|
||||||
|
|
||||||
|
public Entry(LootTableEntryType type, int weight, int quality) {
|
||||||
|
this.type = type;
|
||||||
|
this.weight = weight;
|
||||||
|
this.quality = quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getQuality() {
|
||||||
|
return quality;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getWeight() {
|
||||||
|
return weight;
|
||||||
|
}
|
||||||
|
|
||||||
|
public LootTableEntryType getType() {
|
||||||
|
return type;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract ItemStack generateStack(Data arguments);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,125 @@
|
|||||||
|
package net.minestom.server.gamedata.loottables;
|
||||||
|
|
||||||
|
import net.minestom.server.gamedata.Condition;
|
||||||
|
import net.minestom.server.utils.NamespaceID;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Meant only for parsing loot tables
|
||||||
|
*/
|
||||||
|
class LootTableContainer {
|
||||||
|
|
||||||
|
|
||||||
|
private String type;
|
||||||
|
private LootTableContainer.Pool[] pools;
|
||||||
|
|
||||||
|
private LootTableContainer() {}
|
||||||
|
|
||||||
|
public LootTable createTable(LootTableManager lootTableManager) {
|
||||||
|
LootTableType type = lootTableManager.getTableType(NamespaceID.from(this.type));
|
||||||
|
List<LootTable.Pool> pools = new LinkedList<>();
|
||||||
|
if(this.pools != null) {
|
||||||
|
for(Pool p : this.pools) {
|
||||||
|
pools.add(p.create(lootTableManager));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new LootTable(type, pools);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Pool {
|
||||||
|
private ConditionContainer[] conditions;
|
||||||
|
private FunctionContainer[] functions;
|
||||||
|
private RangeContainer rolls;
|
||||||
|
private RangeContainer bonus_rools;
|
||||||
|
|
||||||
|
private Entry[] entries;
|
||||||
|
|
||||||
|
private Pool() {}
|
||||||
|
|
||||||
|
public LootTable.Pool create(LootTableManager lootTableManager) {
|
||||||
|
List<LootTable.Entry> entries = new LinkedList<>();
|
||||||
|
List<Condition> conditions = new LinkedList<>();
|
||||||
|
if(this.entries != null) {
|
||||||
|
for (Entry e : this.entries) {
|
||||||
|
entries.add(e.create(lootTableManager));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(this.conditions != null) {
|
||||||
|
for (ConditionContainer c : this.conditions) {
|
||||||
|
conditions.add(c.create(lootTableManager));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(rolls == null)
|
||||||
|
rolls = new RangeContainer(0,0);
|
||||||
|
if(bonus_rools == null)
|
||||||
|
bonus_rools = new RangeContainer(0,0);
|
||||||
|
return new LootTable.Pool(rolls.getMin(), rolls.getMax(), bonus_rools.getMin(), bonus_rools.getMax(), entries, conditions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class Entry {
|
||||||
|
private ConditionContainer[] conditions;
|
||||||
|
private String type;
|
||||||
|
private String name;
|
||||||
|
private Entry[] children;
|
||||||
|
private boolean expand;
|
||||||
|
private FunctionContainer[] functions;
|
||||||
|
private int weight;
|
||||||
|
private int quality;
|
||||||
|
|
||||||
|
private Entry() {}
|
||||||
|
|
||||||
|
public LootTable.Entry create(LootTableManager lootTableManager) {
|
||||||
|
LootTableEntryType entryType = lootTableManager.getEntryType(NamespaceID.from(type));
|
||||||
|
List<Condition> conditions = new LinkedList<>();
|
||||||
|
if(this.conditions != null) {
|
||||||
|
for(ConditionContainer c : this.conditions) {
|
||||||
|
conditions.add(c.create(lootTableManager));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<LootTable.Entry> children = new LinkedList<>();
|
||||||
|
if(this.children != null) {
|
||||||
|
for (Entry c : this.children) {
|
||||||
|
children.add(c.create(lootTableManager));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
List<LootTableFunction> functions = new LinkedList<>();
|
||||||
|
if(this.functions != null) {
|
||||||
|
for(FunctionContainer c : this.functions) {
|
||||||
|
functions.add(c.create(lootTableManager));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entryType.create(lootTableManager, name, conditions, children, expand, functions, weight, quality);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class ConditionContainer {
|
||||||
|
private String condition;
|
||||||
|
|
||||||
|
private ConditionContainer() {}
|
||||||
|
|
||||||
|
public Condition create(LootTableManager lootTableManager) {
|
||||||
|
return lootTableManager.getCondition(NamespaceID.from(condition));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class FunctionContainer {
|
||||||
|
private String function;
|
||||||
|
private ConditionContainer[] conditions;
|
||||||
|
|
||||||
|
private FunctionContainer() {}
|
||||||
|
|
||||||
|
public LootTableFunction create(LootTableManager lootTableManager) {
|
||||||
|
List<Condition> conditions = new LinkedList<>();
|
||||||
|
if(this.conditions != null) {
|
||||||
|
for(ConditionContainer c : this.conditions) {
|
||||||
|
conditions.add(c.create(lootTableManager));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ConditionedFunctionWrapper(lootTableManager.getFunction(NamespaceID.from(function)), conditions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package net.minestom.server.gamedata.loottables;
|
||||||
|
|
||||||
|
import net.minestom.server.gamedata.Condition;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface LootTableEntryType {
|
||||||
|
LootTable.Entry create(LootTableManager lootTableManager, String name, List<Condition> conditions, List<LootTable.Entry> children, boolean expand, List<LootTableFunction> functions, int weight, int quality);
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package net.minestom.server.gamedata.loottables;
|
||||||
|
|
||||||
|
import net.minestom.server.data.Data;
|
||||||
|
import net.minestom.server.item.ItemStack;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Changes to apply to the stack being produced
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface LootTableFunction {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Applies changes to the stack being produced
|
||||||
|
* @param stack
|
||||||
|
* @param data arguments to pass to the function.
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
ItemStack apply(ItemStack stack, Data data);
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,106 @@
|
|||||||
|
package net.minestom.server.gamedata.loottables;
|
||||||
|
|
||||||
|
import com.google.gson.Gson;
|
||||||
|
import com.google.gson.GsonBuilder;
|
||||||
|
import net.minestom.server.gamedata.Condition;
|
||||||
|
import net.minestom.server.registry.ResourceGatherer;
|
||||||
|
import net.minestom.server.utils.NamespaceID;
|
||||||
|
import net.minestom.server.utils.NamespaceIDHashMap;
|
||||||
|
|
||||||
|
import java.io.*;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles loading and configuration of loot tables
|
||||||
|
*/
|
||||||
|
public class LootTableManager {
|
||||||
|
|
||||||
|
private NamespaceIDHashMap<Condition> conditions = new NamespaceIDHashMap<>();
|
||||||
|
private NamespaceIDHashMap<LootTableType> tableTypes = new NamespaceIDHashMap<>();
|
||||||
|
private NamespaceIDHashMap<LootTableEntryType> entryTypes = new NamespaceIDHashMap<>();
|
||||||
|
private NamespaceIDHashMap<LootTableFunction> functions = new NamespaceIDHashMap<>();
|
||||||
|
private NamespaceIDHashMap<LootTable> cache = new NamespaceIDHashMap<>();
|
||||||
|
private static Gson gson;
|
||||||
|
|
||||||
|
static {
|
||||||
|
gson = new GsonBuilder()
|
||||||
|
.registerTypeAdapter(RangeContainer.class, new RangeContainer.Deserializer())
|
||||||
|
.create();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a condition to the given namespaceID
|
||||||
|
* @param namespaceID
|
||||||
|
* @param condition
|
||||||
|
*/
|
||||||
|
public void registerCondition(NamespaceID namespaceID, Condition condition) {
|
||||||
|
conditions.put(namespaceID, condition);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a loot table type to the given namespaceID
|
||||||
|
* @param namespaceID
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
|
public void registerTableType(NamespaceID namespaceID, LootTableType type) {
|
||||||
|
tableTypes.put(namespaceID, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a loot table entry type to the given namespaceID
|
||||||
|
* @param namespaceID
|
||||||
|
* @param type
|
||||||
|
*/
|
||||||
|
public void registerEntryType(NamespaceID namespaceID, LootTableEntryType type) {
|
||||||
|
entryTypes.put(namespaceID, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Registers a loot table function to the given namespaceID
|
||||||
|
* @param namespaceID
|
||||||
|
* @param function
|
||||||
|
*/
|
||||||
|
public void registerFunction(NamespaceID namespaceID, LootTableFunction function) {
|
||||||
|
functions.put(namespaceID, function);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LootTable load(NamespaceID name) throws FileNotFoundException {
|
||||||
|
return load(name, new FileReader(new File(ResourceGatherer.DATA_FOLDER, "data/"+name.getDomain()+"/loot_tables/"+name.getPath()+".json")));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads a loot table with the given name. Loot tables can be cached, so 'reader' is used only on cache misses
|
||||||
|
* @param name the name to cache the loot table with
|
||||||
|
* @param reader the reader to read the loot table from, if none cached. **Will** be closed no matter the results of this call
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public LootTable load(NamespaceID name, Reader reader) {
|
||||||
|
try {
|
||||||
|
return cache.computeIfAbsent(name, _name -> create(reader));
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
reader.close();
|
||||||
|
} catch (IOException e) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private LootTable create(Reader reader) {
|
||||||
|
LootTableContainer container = gson.fromJson(reader, LootTableContainer.class);
|
||||||
|
return container.createTable(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
public Condition getCondition(NamespaceID id) {
|
||||||
|
return conditions.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LootTableType getTableType(NamespaceID id) {
|
||||||
|
return tableTypes.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LootTableEntryType getEntryType(NamespaceID id) {
|
||||||
|
return entryTypes.get(id);
|
||||||
|
}
|
||||||
|
|
||||||
|
public LootTableFunction getFunction(NamespaceID id) {
|
||||||
|
return functions.get(id);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package net.minestom.server.gamedata.loottables;
|
||||||
|
|
||||||
|
public interface LootTableType {
|
||||||
|
|
||||||
|
// TODO
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package net.minestom.server.gamedata.loottables;
|
||||||
|
|
||||||
|
import com.google.gson.*;
|
||||||
|
|
||||||
|
import java.lang.reflect.Type;
|
||||||
|
|
||||||
|
public class RangeContainer {
|
||||||
|
|
||||||
|
private int min;
|
||||||
|
private int max;
|
||||||
|
|
||||||
|
RangeContainer() {}
|
||||||
|
|
||||||
|
public RangeContainer(int min, int max) {
|
||||||
|
this.min = min;
|
||||||
|
this.max = max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMin() {
|
||||||
|
return min;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getMax() {
|
||||||
|
return max;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class Deserializer implements JsonDeserializer<RangeContainer> {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RangeContainer deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
|
||||||
|
RangeContainer range = new RangeContainer();
|
||||||
|
if(json.isJsonPrimitive()) {
|
||||||
|
range.min = json.getAsInt();
|
||||||
|
range.max = json.getAsInt();
|
||||||
|
} else if(json.isJsonObject()) {
|
||||||
|
JsonObject obj = json.getAsJsonObject();
|
||||||
|
if(!obj.has("min"))
|
||||||
|
throw new IllegalArgumentException("Missing 'min' property");
|
||||||
|
if(!obj.has("max"))
|
||||||
|
throw new IllegalArgumentException("Missing 'max' property");
|
||||||
|
range.min = obj.get("min").getAsInt();
|
||||||
|
range.max = obj.get("max").getAsInt();
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Range must be single integer or an object with 'min' and 'max' properties");
|
||||||
|
}
|
||||||
|
return range;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,50 @@
|
|||||||
|
package net.minestom.server.gamedata.loottables.entries;
|
||||||
|
|
||||||
|
import net.minestom.server.data.Data;
|
||||||
|
import net.minestom.server.gamedata.Condition;
|
||||||
|
import net.minestom.server.gamedata.loottables.LootTable;
|
||||||
|
import net.minestom.server.gamedata.loottables.LootTableFunction;
|
||||||
|
import net.minestom.server.item.ItemStack;
|
||||||
|
import net.minestom.server.item.Material;
|
||||||
|
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ItemEntry extends LootTable.Entry {
|
||||||
|
|
||||||
|
private final List<LootTableFunction> functions;
|
||||||
|
private final List<Condition> conditions;
|
||||||
|
private final Material item;
|
||||||
|
|
||||||
|
ItemEntry(ItemType type, Material baseItem, int weight, int quality, List<LootTableFunction> functions, List<Condition> conditions) {
|
||||||
|
super(type, weight, quality);
|
||||||
|
this.item = baseItem;
|
||||||
|
this.functions = new LinkedList<>(functions);
|
||||||
|
this.conditions = new LinkedList<>(conditions);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ItemStack generateStack(Data arguments) {
|
||||||
|
for(Condition c : conditions) {
|
||||||
|
if(!c.test(arguments))
|
||||||
|
return ItemStack.getAirItem();
|
||||||
|
}
|
||||||
|
ItemStack stack = new ItemStack(item, (byte)1);
|
||||||
|
for (LootTableFunction function : functions) {
|
||||||
|
stack = function.apply(stack, arguments);
|
||||||
|
}
|
||||||
|
return stack;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<LootTableFunction> getFunctions() {
|
||||||
|
return functions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Condition> getConditions() {
|
||||||
|
return conditions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Material getItem() {
|
||||||
|
return item;
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,20 @@
|
|||||||
|
package net.minestom.server.gamedata.loottables.entries;
|
||||||
|
|
||||||
|
import net.minestom.server.gamedata.Condition;
|
||||||
|
import net.minestom.server.gamedata.loottables.LootTable;
|
||||||
|
import net.minestom.server.gamedata.loottables.LootTableEntryType;
|
||||||
|
import net.minestom.server.gamedata.loottables.LootTableFunction;
|
||||||
|
import net.minestom.server.gamedata.loottables.LootTableManager;
|
||||||
|
import net.minestom.server.item.Material;
|
||||||
|
import net.minestom.server.utils.NamespaceID;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class ItemType implements LootTableEntryType {
|
||||||
|
@Override
|
||||||
|
public LootTable.Entry create(LootTableManager lootTableManager, String name, List<Condition> conditions, List<LootTable.Entry> children, boolean expand, List<LootTableFunction> functions, int weight, int quality) {
|
||||||
|
NamespaceID itemID = NamespaceID.from(name);
|
||||||
|
// TODO: handle non-vanilla IDs ?
|
||||||
|
return new ItemEntry(this, Material.valueOf(itemID.getPath().toUpperCase()), weight, quality, functions, conditions);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,6 @@
|
|||||||
|
package net.minestom.server.gamedata.loottables.tabletypes;
|
||||||
|
|
||||||
|
import net.minestom.server.gamedata.loottables.LootTableType;
|
||||||
|
|
||||||
|
public class BlockType implements LootTableType {
|
||||||
|
}
|
119
src/main/java/net/minestom/server/utils/NamespaceID.java
Normal file
119
src/main/java/net/minestom/server/utils/NamespaceID.java
Normal file
@ -0,0 +1,119 @@
|
|||||||
|
package net.minestom.server.utils;
|
||||||
|
|
||||||
|
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a namespaced ID
|
||||||
|
* https://minecraft.gamepedia.com/Namespaced_ID
|
||||||
|
*
|
||||||
|
* TODO: Implement validity conditions
|
||||||
|
*/
|
||||||
|
public class NamespaceID implements CharSequence {
|
||||||
|
private static final Int2ObjectOpenHashMap<NamespaceID> cache = new Int2ObjectOpenHashMap<>();
|
||||||
|
|
||||||
|
private final String domain;
|
||||||
|
private final String path;
|
||||||
|
private final String full;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the domain from the namespace ID. "minecraft:stone" would return "minecraft".
|
||||||
|
* If no ':' character is found, "minecraft" is returned.
|
||||||
|
* @param namespaceID
|
||||||
|
* @return the domain of the namespace ID
|
||||||
|
*/
|
||||||
|
public static String getDomain(String namespaceID) {
|
||||||
|
int index = namespaceID.indexOf(':');
|
||||||
|
if(index < 0)
|
||||||
|
return "minecraft";
|
||||||
|
return namespaceID.substring(0, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extracts the path from the namespace ID. "minecraft:blocks/stone" would return "blocks/stone".
|
||||||
|
* If no ':' character is found, the <pre>namespaceID</pre> is returned.
|
||||||
|
* @param namespaceID
|
||||||
|
* @return the path of the namespace ID
|
||||||
|
*/
|
||||||
|
public static String getPath(String namespaceID) {
|
||||||
|
int index = namespaceID.indexOf(':');
|
||||||
|
if(index < 0)
|
||||||
|
return namespaceID;
|
||||||
|
return namespaceID.substring(index+1);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int hash(String domain, String path) {
|
||||||
|
return Objects.hash(domain, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NamespaceID from(String domain, String path) {
|
||||||
|
int hash = hash(domain, path);
|
||||||
|
return cache.computeIfAbsent(hash, _unused -> new NamespaceID(domain, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static NamespaceID from(String id) {
|
||||||
|
return from(getDomain(id), getPath(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
private NamespaceID(String path) {
|
||||||
|
int index = path.indexOf(':');
|
||||||
|
if(index < 0) {
|
||||||
|
this.domain = "minecraft";
|
||||||
|
this.path = path;
|
||||||
|
} else {
|
||||||
|
this.domain = path.substring(0, index);
|
||||||
|
this.path = path.substring(index+1);
|
||||||
|
}
|
||||||
|
this.full = toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private NamespaceID(String domain, String path) {
|
||||||
|
this.domain = domain;
|
||||||
|
this.path = path;
|
||||||
|
this.full = toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getDomain() {
|
||||||
|
return domain;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPath() {
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
NamespaceID that = (NamespaceID) o;
|
||||||
|
return Objects.equals(domain, that.domain) &&
|
||||||
|
Objects.equals(path, that.path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return hash(domain, path);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int length() {
|
||||||
|
return full.length();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public char charAt(int index) {
|
||||||
|
return full.charAt(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CharSequence subSequence(int start, int end) {
|
||||||
|
return full.subSequence(start, end);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return domain+":"+path;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
package net.minestom.server.utils;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
public class NamespaceIDHashMap<V> extends AbstractMap<NamespaceID, V> {
|
||||||
|
|
||||||
|
private final Map<NamespaceID, V> backing = new HashMap<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Set<Entry<NamespaceID, V>> entrySet() {
|
||||||
|
return backing.entrySet();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V get(Object key) {
|
||||||
|
return backing.get(key);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public V put(NamespaceID key, V value) {
|
||||||
|
return backing.put(key, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsKey(String id) {
|
||||||
|
return containsKey(NamespaceID.getDomain(id), NamespaceID.getPath(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean containsKey(String domain, String path) {
|
||||||
|
return backing.containsKey(NamespaceID.from(domain, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public V get(String id) {
|
||||||
|
return get(NamespaceID.getDomain(id), NamespaceID.getPath(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
public V get(String domain, String path) {
|
||||||
|
return backing.get(NamespaceID.from(domain, path));
|
||||||
|
}
|
||||||
|
|
||||||
|
public V put(String domain, String path, V value) {
|
||||||
|
return put(NamespaceID.from(domain, path), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public V computeIfAbsent(String domain, String path, Function<? super NamespaceID, ? extends V> mappingFunction) {
|
||||||
|
return computeIfAbsent(NamespaceID.from(domain, path), mappingFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public V put(String id, V value) {
|
||||||
|
return put(NamespaceID.from(id), value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public V computeIfAbsent(String id, Function<? super NamespaceID, ? extends V> mappingFunction) {
|
||||||
|
return computeIfAbsent(NamespaceID.from(id), mappingFunction);
|
||||||
|
}
|
||||||
|
|
||||||
|
public V getOrDefault(String id, V defaultValue) {
|
||||||
|
return getOrDefault(NamespaceID.from(id), defaultValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
public V getOrDefault(String domain, String path, V defaultValue) {
|
||||||
|
return getOrDefault(NamespaceID.from(domain, path), defaultValue);
|
||||||
|
}
|
||||||
|
}
|
46
src/main/java/net/minestom/server/utils/WeightedRandom.java
Normal file
46
src/main/java/net/minestom/server/utils/WeightedRandom.java
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
package net.minestom.server.utils;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Random;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces a random element from a given set, with weights applied
|
||||||
|
* @param <E>
|
||||||
|
*/
|
||||||
|
public class WeightedRandom<E extends WeightedRandomItem> {
|
||||||
|
|
||||||
|
private final List<E> entries;
|
||||||
|
private final List<Double> weightSums;
|
||||||
|
private final double totalWeight;
|
||||||
|
|
||||||
|
public WeightedRandom(Collection<E> items) {
|
||||||
|
if(items.isEmpty())
|
||||||
|
throw new IllegalArgumentException("items must not be empty");
|
||||||
|
this.entries = new ArrayList<>(items);
|
||||||
|
this.weightSums = new ArrayList<>(items.size());
|
||||||
|
double sum = 0.0;
|
||||||
|
for(E item : items) {
|
||||||
|
sum += item.getWeight();
|
||||||
|
weightSums.add(sum);
|
||||||
|
}
|
||||||
|
this.totalWeight = sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets a random element from this set
|
||||||
|
* @param rng Random Number Generator to generate random numbers with
|
||||||
|
* @return
|
||||||
|
*/
|
||||||
|
public E get(Random rng) {
|
||||||
|
double p = rng.nextDouble()*totalWeight;
|
||||||
|
for (int i = 0; i < entries.size(); i++) {
|
||||||
|
double weightSum = weightSums.get(i);
|
||||||
|
if(weightSum >= p) {
|
||||||
|
return entries.get(i);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return entries.get(entries.size()-1);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,7 @@
|
|||||||
|
package net.minestom.server.utils;
|
||||||
|
|
||||||
|
public interface WeightedRandomItem {
|
||||||
|
|
||||||
|
double getWeight();
|
||||||
|
|
||||||
|
}
|
135
src/test/java/loottables/TestLootTables.java
Normal file
135
src/test/java/loottables/TestLootTables.java
Normal file
@ -0,0 +1,135 @@
|
|||||||
|
package loottables;
|
||||||
|
|
||||||
|
import net.minestom.server.data.Data;
|
||||||
|
import net.minestom.server.gamedata.conditions.SurvivesExplosionCondition;
|
||||||
|
import net.minestom.server.gamedata.loottables.LootTable;
|
||||||
|
import net.minestom.server.gamedata.loottables.LootTableManager;
|
||||||
|
import net.minestom.server.gamedata.loottables.entries.ItemEntry;
|
||||||
|
import net.minestom.server.gamedata.loottables.entries.ItemType;
|
||||||
|
import net.minestom.server.gamedata.loottables.tabletypes.BlockType;
|
||||||
|
import net.minestom.server.item.ItemStack;
|
||||||
|
import net.minestom.server.item.Material;
|
||||||
|
import net.minestom.server.registry.RegistryMain;
|
||||||
|
import net.minestom.server.utils.NamespaceID;
|
||||||
|
import org.junit.Assert;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
|
import java.io.StringReader;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public class TestLootTables {
|
||||||
|
|
||||||
|
private LootTableManager tableManager;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void init() {
|
||||||
|
RegistryMain.registerBlocks();
|
||||||
|
RegistryMain.registerItems();
|
||||||
|
tableManager = new LootTableManager();
|
||||||
|
tableManager.registerCondition(NamespaceID.from("minecraft:survives_explosion"), new SurvivesExplosionCondition());
|
||||||
|
tableManager.registerTableType(NamespaceID.from("minecraft:block"), new BlockType());
|
||||||
|
tableManager.registerEntryType(NamespaceID.from("minecraft:item"), new ItemType());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loadFromString() {
|
||||||
|
// from acacia_button.json
|
||||||
|
final String lootTableJson = "{\n" +
|
||||||
|
" \"type\": \"minecraft:block\",\n" +
|
||||||
|
" \"pools\": [\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"rolls\": 1,\n" +
|
||||||
|
" \"entries\": [\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"type\": \"minecraft:item\",\n" +
|
||||||
|
" \"name\": \"minecraft:acacia_button\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
" ],\n" +
|
||||||
|
" \"conditions\": [\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"condition\": \"minecraft:survives_explosion\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
" ]\n" +
|
||||||
|
" }\n" +
|
||||||
|
" ]\n" +
|
||||||
|
"}";
|
||||||
|
LootTable lootTable = tableManager.load(NamespaceID.from("blocks/acacia_button"), new StringReader(lootTableJson));
|
||||||
|
Assert.assertTrue(lootTable.getType() instanceof BlockType);
|
||||||
|
Assert.assertEquals(1, lootTable.getPools().size());
|
||||||
|
Assert.assertEquals(1, lootTable.getPools().get(0).getMinRollCount());
|
||||||
|
Assert.assertEquals(1, lootTable.getPools().get(0).getMaxRollCount());
|
||||||
|
Assert.assertEquals(1, lootTable.getPools().get(0).getEntries().size());
|
||||||
|
Assert.assertTrue(lootTable.getPools().get(0).getEntries().get(0).getType() instanceof ItemType);
|
||||||
|
Assert.assertTrue(lootTable.getPools().get(0).getEntries().get(0) instanceof ItemEntry);
|
||||||
|
ItemEntry entry = (ItemEntry) lootTable.getPools().get(0).getEntries().get(0);
|
||||||
|
Assert.assertEquals(Material.ACACIA_BUTTON, entry.getItem());
|
||||||
|
Assert.assertEquals(0, entry.getFunctions().size());
|
||||||
|
Assert.assertEquals(1, lootTable.getPools().get(0).getConditions().size());
|
||||||
|
Assert.assertTrue(lootTable.getPools().get(0).getConditions().get(0) instanceof SurvivesExplosionCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void loadFromFile() throws FileNotFoundException {
|
||||||
|
// from acacia_button.json
|
||||||
|
final String lootTableJson = "{\n" +
|
||||||
|
" \"type\": \"minecraft:block\",\n" +
|
||||||
|
" \"pools\": [\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"rolls\": 1,\n" +
|
||||||
|
" \"entries\": [\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"type\": \"minecraft:item\",\n" +
|
||||||
|
" \"name\": \"minecraft:acacia_button\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
" ],\n" +
|
||||||
|
" \"conditions\": [\n" +
|
||||||
|
" {\n" +
|
||||||
|
" \"condition\": \"minecraft:survives_explosion\"\n" +
|
||||||
|
" }\n" +
|
||||||
|
" ]\n" +
|
||||||
|
" }\n" +
|
||||||
|
" ]\n" +
|
||||||
|
"}";
|
||||||
|
LootTable lootTable = tableManager.load(NamespaceID.from("blocks/acacia_button"));
|
||||||
|
Assert.assertTrue(lootTable.getType() instanceof BlockType);
|
||||||
|
Assert.assertEquals(1, lootTable.getPools().size());
|
||||||
|
Assert.assertEquals(1, lootTable.getPools().get(0).getMinRollCount());
|
||||||
|
Assert.assertEquals(1, lootTable.getPools().get(0).getMaxRollCount());
|
||||||
|
Assert.assertEquals(1, lootTable.getPools().get(0).getEntries().size());
|
||||||
|
Assert.assertTrue(lootTable.getPools().get(0).getEntries().get(0).getType() instanceof ItemType);
|
||||||
|
Assert.assertTrue(lootTable.getPools().get(0).getEntries().get(0) instanceof ItemEntry);
|
||||||
|
ItemEntry entry = (ItemEntry) lootTable.getPools().get(0).getEntries().get(0);
|
||||||
|
Assert.assertEquals(Material.ACACIA_BUTTON, entry.getItem());
|
||||||
|
Assert.assertEquals(0, entry.getFunctions().size());
|
||||||
|
Assert.assertEquals(1, lootTable.getPools().get(0).getConditions().size());
|
||||||
|
Assert.assertTrue(lootTable.getPools().get(0).getConditions().get(0) instanceof SurvivesExplosionCondition);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void caching() throws FileNotFoundException {
|
||||||
|
LootTable lootTable1 = tableManager.load(NamespaceID.from("blocks/acacia_button"));
|
||||||
|
LootTable lootTable2 = tableManager.load(NamespaceID.from("blocks/acacia_button"));
|
||||||
|
Assert.assertSame(lootTable1, lootTable2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void simpleGenerate() throws FileNotFoundException {
|
||||||
|
LootTable lootTable = tableManager.load(NamespaceID.from("blocks/acacia_button"));
|
||||||
|
Data arguments = new Data();
|
||||||
|
List<ItemStack> stacks = lootTable.generate(arguments);
|
||||||
|
Assert.assertEquals(1, stacks.size());
|
||||||
|
Assert.assertEquals(Material.ACACIA_BUTTON, stacks.get(0).getMaterial());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testExplosion() throws FileNotFoundException {
|
||||||
|
LootTable lootTable = tableManager.load(NamespaceID.from("blocks/acacia_button"));
|
||||||
|
Data arguments = new Data();
|
||||||
|
// negative value will force the condition to fail
|
||||||
|
arguments.set("explosionPower", -1.0, Double.class);
|
||||||
|
List<ItemStack> stacks = lootTable.generate(arguments);
|
||||||
|
Assert.assertEquals(0, stacks.size());
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user