Loot table support

This commit is contained in:
jglrxavpok 2020-05-18 21:11:59 +02:00
parent c50030cd6b
commit 3c0d351f15
20 changed files with 982 additions and 0 deletions

View File

@ -5,6 +5,7 @@ import net.minestom.server.command.CommandManager;
import net.minestom.server.data.DataManager;
import net.minestom.server.entity.EntityManager;
import net.minestom.server.entity.Player;
import net.minestom.server.gamedata.loottables.LootTableManager;
import net.minestom.server.instance.InstanceManager;
import net.minestom.server.instance.block.BlockManager;
import net.minestom.server.listener.manager.PacketListenerManager;
@ -90,6 +91,7 @@ public class MinecraftServer {
// Data
private static ResponseDataConsumer responseDataConsumer;
private static Difficulty difficulty = Difficulty.NORMAL;
private static LootTableManager lootTableManager;
public static MinecraftServer init() {
connectionManager = new ConnectionManager();
@ -109,6 +111,8 @@ public class MinecraftServer {
updateManager = new UpdateManager();
lootTableManager = new LootTableManager();
nettyServer = new NettyServer(packetProcessor);
// Registry
@ -201,6 +205,10 @@ public class MinecraftServer {
return responseDataConsumer;
}
public static LootTableManager getLootTableManager() {
return lootTableManager;
}
public void start(String address, int port, ResponseDataConsumer responseDataConsumer) {
LOGGER.info("Starting Minestom server.");
MinecraftServer.responseDataConsumer = responseDataConsumer;

View File

@ -6,6 +6,26 @@ import java.util.concurrent.ConcurrentHashMap;
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();
public <T> void set(String key, T value, Class<T> type) {

View 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);
}

View File

@ -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");
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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);
}

View File

@ -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);
}

View File

@ -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);
}
}

View File

@ -0,0 +1,6 @@
package net.minestom.server.gamedata.loottables;
public interface LootTableType {
// TODO
}

View File

@ -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;
}
}
}

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -0,0 +1,6 @@
package net.minestom.server.gamedata.loottables.tabletypes;
import net.minestom.server.gamedata.loottables.LootTableType;
public class BlockType implements LootTableType {
}

View 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;
}
}

View File

@ -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);
}
}

View 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);
}
}

View File

@ -0,0 +1,7 @@
package net.minestom.server.utils;
public interface WeightedRandomItem {
double getWeight();
}

View 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());
}
}