Loot table entry types + Defaults for loot table functions and conditions

This commit is contained in:
jglrxavpok 2020-05-19 18:34:11 +02:00
parent 5cb31171e6
commit fe3025fce5
19 changed files with 376 additions and 40 deletions

View File

@ -14,4 +14,7 @@ public interface Condition {
* @return 'true' if the condition passed, 'false' otherwise
*/
boolean test(Data data);
Condition ALWAYS_YES = (_d) -> true;
Condition ALWAYS_NO = (_d) -> false;
}

View File

@ -83,12 +83,19 @@ public class LootTable {
int rollCount = rng.nextInt(maxRollCount - minRollCount +1 /*inclusive*/) + minRollCount;
int bonusRollCount = rng.nextInt(bonusMaxRollCount - bonusMinRollCount +1 /*inclusive*/) + bonusMinRollCount;
bonusRollCount *= luck;
// TODO: implement luck (quality/weight) weight=floor( weight + (quality * generic.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);
boolean shouldGenerate = true;
for(Condition c : entry.getConditions()) {
if(!c.test(arguments)) {
shouldGenerate = false;
break;
}
}
if(shouldGenerate) {
entry.generateStacks(output, arguments);
}
}
}
@ -98,11 +105,17 @@ public class LootTable {
private final LootTableEntryType type;
private final int weight;
private final int quality;
private final List<Condition> conditions;
public Entry(LootTableEntryType type, int weight, int quality) {
public Entry(LootTableEntryType type, int weight, int quality, List<Condition> conditions) {
this.type = type;
this.weight = weight;
this.quality = quality;
this.conditions = conditions;
}
public List<Condition> getConditions() {
return conditions;
}
public int getQuality() {
@ -117,6 +130,6 @@ public class LootTable {
return type;
}
public abstract ItemStack generateStack(Data arguments);
public abstract void generateStacks(List<ItemStack> output, Data arguments);
}
}

View File

@ -16,5 +16,7 @@ public interface LootTableFunction {
* @return
*/
ItemStack apply(ItemStack stack, Data data);
LootTableFunction IDENTITY = (stack, _d) -> stack;
}

View File

@ -88,19 +88,43 @@ public class LootTableManager {
return container.createTable(this);
}
/**
* Returns the registered condition corresponding to the given namespace ID. If none is registered, returns {@link Condition#ALWAYS_NO}.
* @param id
* @return
*/
public Condition getCondition(NamespaceID id) {
return conditions.get(id);
return conditions.getOrDefault(id, Condition.ALWAYS_NO);
}
/**
* Returns the registered table type corresponding to the given namespace ID. If none is registered, throws {@link IllegalArgumentException}
* @param id
* @return
*/
public LootTableType getTableType(NamespaceID id) {
if(!tableTypes.containsKey(id))
throw new IllegalArgumentException("Unknown table type: "+id);
return tableTypes.get(id);
}
/**
* Returns the registered entry type corresponding to the given namespace ID. If none is registered, throws {@link IllegalArgumentException}
* @param id
* @return
*/
public LootTableEntryType getEntryType(NamespaceID id) {
if(!entryTypes.containsKey(id))
throw new IllegalArgumentException("Unknown entry type: "+id);
return entryTypes.get(id);
}
/**
* Returns the registered table type corresponding to the given namespace ID. If none is registered, returns {@link LootTableFunction#IDENTITY}
* @param id
* @return
*/
public LootTableFunction getFunction(NamespaceID id) {
return functions.get(id);
return functions.getOrDefault(id, LootTableFunction.IDENTITY);
}
}

View File

@ -0,0 +1,29 @@
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.item.ItemStack;
import java.util.List;
public class AlternativesEntry extends LootTable.Entry {
private final List<LootTable.Entry> children;
public AlternativesEntry(AlternativesType type, List<LootTable.Entry> children, int weight, int quality, List<Condition> conditions) {
super(type, weight, quality, conditions);
this.children = children;
}
@Override
public void generateStacks(List<ItemStack> output, Data arguments) {
for(LootTable.Entry c : children) {
int previousSize = output.size();
c.generateStacks(output, arguments);
int newSize = previousSize;
if(newSize != previousSize) { // an entry managed to generate, stop here
return;
}
}
}
}

View File

@ -0,0 +1,19 @@
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 java.util.List;
/**
* minecraft:alternatives
*/
public class AlternativesType 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) {
return new AlternativesEntry(this, children, weight, quality, conditions);
}
}

View File

@ -0,0 +1,22 @@
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.item.ItemStack;
import java.util.List;
public class AnotherLootTableEntry extends LootTable.Entry {
private final LootTable table;
public AnotherLootTableEntry(AnotherLootTableType type, LootTable table, int weight, int quality, List<Condition> conditions) {
super(type, weight, quality, conditions);
this.table = table;
}
@Override
public void generateStacks(List<ItemStack> output, Data arguments) {
output.addAll(table.generate(arguments));
}
}

View File

@ -0,0 +1,27 @@
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.utils.NamespaceID;
import java.io.FileNotFoundException;
import java.util.List;
/**
* Allows to sample from a different loot table
*
* minecraft:loot_table
*/
public class AnotherLootTableType 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) {
try {
return new AnotherLootTableEntry(this, lootTableManager.load(NamespaceID.from(name)), weight, quality, conditions);
} catch (FileNotFoundException e) {
throw new IllegalArgumentException(name+" is not a valid loot table name", e);
}
}
}

View File

@ -0,0 +1,36 @@
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.item.ItemStack;
import java.util.Collections;
import java.util.List;
public class DynamicEntry extends LootTable.Entry {
public static final String DROP_LIST_KEY = "minestom:loot_table_drop_list";
private final DynamicEntry.Type entryType;
public DynamicEntry(DynamicType type, DynamicEntry.Type entryType, int weight, int quality, List<Condition> conditions) {
super(type, weight, quality, conditions);
this.entryType = entryType;
}
@Override
public void generateStacks(List<ItemStack> output, Data arguments) {
List<ItemStack> toDrop = arguments.getOrDefault(DROP_LIST_KEY, Collections.emptyList());
output.addAll(toDrop);
}
public DynamicEntry.Type getEntryType() {
return entryType;
}
public enum Type {
SELF,
CONTENTS;
}
}

View File

@ -0,0 +1,19 @@
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 java.util.List;
/**
* minecraft:dynamic
*/
public class DynamicType 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) {
return new DynamicEntry(this, DynamicEntry.Type.valueOf(name.toUpperCase()), weight, quality, conditions);
}
}

View File

@ -0,0 +1,24 @@
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.item.ItemStack;
import java.util.List;
public class GroupEntry extends LootTable.Entry {
private final List<LootTable.Entry> children;
public GroupEntry(GroupType type, List<LootTable.Entry> children, int weight, int quality, List<Condition> conditions) {
super(type, weight, quality, conditions);
this.children = children;
}
@Override
public void generateStacks(List<ItemStack> output, Data arguments) {
for (LootTable.Entry child : children) {
child.generateStacks(output, arguments);
}
}
}

View File

@ -0,0 +1,19 @@
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 java.util.List;
/**
* minecraft:group
*/
public class GroupType 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) {
return new GroupEntry(this, children, weight, quality, conditions);
}
}

View File

@ -13,37 +13,29 @@ 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);
super(type, weight, quality, conditions);
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();
}
public void generateStacks(List<ItemStack> output, Data arguments) {
ItemStack stack = new ItemStack(item, (byte)1);
for (LootTableFunction function : functions) {
stack = function.apply(stack, arguments);
}
return stack;
if(!stack.isAir()) {
output.add(stack);
}
}
public List<LootTableFunction> getFunctions() {
return functions;
}
public List<Condition> getConditions() {
return conditions;
}
public Material getItem() {
return item;
}

View File

@ -10,6 +10,9 @@ import net.minestom.server.utils.NamespaceID;
import java.util.List;
/**
* minecraft:item
*/
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) {

View File

@ -0,0 +1,29 @@
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.item.ItemStack;
import java.util.List;
public class SequenceEntry extends LootTable.Entry {
private final List<LootTable.Entry> children;
public SequenceEntry(SequenceType type, List<LootTable.Entry> children, int weight, int quality, List<Condition> conditions) {
super(type, weight, quality, conditions);
this.children = children;
}
@Override
public void generateStacks(List<ItemStack> output, Data arguments) {
for(LootTable.Entry c : children) {
int previousSize = output.size();
c.generateStacks(output, arguments);
int newSize = previousSize;
if(newSize == previousSize) { // an entry failed to generate, stop here
return;
}
}
}
}

View File

@ -0,0 +1,19 @@
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 java.util.List;
/**
* minecraft:sequence
*/
public class SequenceType 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) {
return new SequenceEntry(this, children, weight, quality, conditions);
}
}

View File

@ -0,0 +1,30 @@
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.item.ItemStack;
import java.util.List;
public class TagEntry extends LootTable.Entry {
// TODO: replace with Tag reference
private final String name;
private final boolean expand;
TagEntry(TagType type, String name, boolean expand, int weight, int quality, List<Condition> conditions) {
super(type, weight, quality, conditions);
this.name = name;
this.expand = expand;
}
@Override
public void generateStacks(List<ItemStack> output, Data arguments) {
// TODO: load tags
if(expand) {
// TODO: choose a single random item from the tag
} else {
// TODO: add all items from the tag
}
}
}

View File

@ -0,0 +1,19 @@
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 java.util.List;
/**
* minecraft:tag
*/
public class TagType 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) {
return new TagEntry(this, name, expand, weight, quality, conditions);
}
}

View File

@ -72,26 +72,6 @@ public class TestLootTables {
@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());
@ -132,4 +112,31 @@ public class TestLootTables {
List<ItemStack> stacks = lootTable.generate(arguments);
Assert.assertEquals(0, stacks.size());
}
@Test
public void unknownCondition() {
// 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\": \"minestom:unknown\"\n" +
" }\n" +
" ]\n" +
" }\n" +
" ]\n" +
"}";
LootTable lootTable = tableManager.load(NamespaceID.from("blocks/none"), new StringReader(lootTableJson));
List<ItemStack> stacks = lootTable.generate(Data.EMPTY);
Assert.assertEquals(0, stacks.size());
}
}