Added Object & Jigsaw Loot Tables

- Added the ability for placed objects and jigsaws to have select loot tables
    - Includes the ability to filter loot tables based on block type/block state
    - Overrides loot tables from the biome/region, but if no loot tables are provided for an object, they will still be used
    - Uses weight based system for determining which table to pick (so it's guaranteed rather than by chance)
- Added WeightedRandom util class
- Fixed loot tables not being random based on the seed
- Fixed jigsaws breaking bedrock
- Fixed enchantments in loot tables not working for enchanted books
- Fixed mobs spawned in Jigsaws not being spawned in the center like they should be
This commit is contained in:
StrangeOne101 2021-06-29 00:24:36 +12:00
parent 4a11ed6dc4
commit 7d59edc8a5
11 changed files with 261 additions and 15 deletions

View File

@ -2,7 +2,6 @@ package com.volmit.iris.generator;
import com.volmit.iris.Iris;
import com.volmit.iris.manager.IrisDataManager;
import com.volmit.iris.nms.INMS;
import com.volmit.iris.object.IrisDimension;
import com.volmit.iris.object.IrisDimensionIndex;
import com.volmit.iris.object.IrisPosition;
@ -15,14 +14,11 @@ import com.volmit.iris.scaffold.parallel.MultiBurst;
import com.volmit.iris.util.*;
import lombok.Getter;
import lombok.Setter;
import net.minecraft.core.BlockPosition;
import net.minecraft.world.level.levelgen.feature.StructureGenerator;
import org.bukkit.Bukkit;
import org.bukkit.World;
import org.bukkit.block.Biome;
import org.bukkit.block.data.BlockData;
import org.bukkit.command.CommandSender;
import org.bukkit.craftbukkit.v1_17_R1.generator.InternalChunkGenerator;
import org.bukkit.event.EventHandler;
import org.bukkit.event.world.WorldSaveEvent;
import org.bukkit.generator.BlockPopulator;

View File

@ -27,6 +27,9 @@ public class IrisEngineEffects extends EngineAssignedComponent implements Engine
@Override
public void updatePlayerMap() {
List<Player> pr = getEngine().getWorld().getPlayers();
if (pr == null) return; //Fix for paper returning a world with a null playerlist
for(Player i : pr)
{
Location l = i.getLocation();

View File

@ -2,6 +2,7 @@ package com.volmit.iris.manager.command.studio;
import com.volmit.iris.Iris;
import com.volmit.iris.IrisSettings;
import com.volmit.iris.manager.IrisDataManager;
import com.volmit.iris.object.InventorySlotType;
import com.volmit.iris.object.IrisLootTable;
import com.volmit.iris.scaffold.IrisWorlds;
@ -41,9 +42,9 @@ public class CommandIrisStudioLoot extends MortarCommand
Player p = sender.player();
IrisAccess prov = IrisWorlds.access(sender.player().getWorld());
if(prov == null)
if (!Iris.proj.isProjectOpen())
{
sender.sendMessage("You can only use /iris loot in a studio world of iris.");
sender.sendMessage("You can only use /iris studio loot in a studio world of iris.");
return true;
}

View File

@ -3,6 +3,7 @@ package com.volmit.iris.object;
import java.lang.reflect.Field;
import org.bukkit.enchantments.Enchantment;
import org.bukkit.inventory.meta.EnchantmentStorageMeta;
import org.bukkit.inventory.meta.ItemMeta;
import com.volmit.iris.util.Desc;
@ -52,6 +53,10 @@ public class IrisEnchantment
{
if(rng.nextDouble() < chance)
{
if (meta instanceof EnchantmentStorageMeta) {
((EnchantmentStorageMeta) meta).addStoredEnchant(getEnchant(), getLevel(rng), true);
return;
}
meta.addEnchant(getEnchant(), getLevel(rng), true);
}
}

View File

@ -666,7 +666,12 @@ public class IrisObject extends IrisRegistrant
for(BlockData k : j.getFind(rdata))
{
if (j.isExact() ? k.matches(data) : k.getMaterial().equals(data.getMaterial())) {
data = j.getReplace(rng, i.getX() + x, i.getY() + y, i.getZ() + z, rdata).clone();
BlockData newData = j.getReplace(rng, i.getX() + x, i.getY() + y, i.getZ() + z, rdata).clone();
if (newData.getMaterial() == data.getMaterial())
data = data.merge(newData);
else
data = newData;
}
}
}
@ -726,7 +731,6 @@ public class IrisObject extends IrisRegistrant
{
placer.setTile(xx, yy, zz, tile);
}
}
}
readLock.unlock();

View File

@ -0,0 +1,72 @@
package com.volmit.iris.object;
import com.volmit.iris.Iris;
import com.volmit.iris.manager.IrisDataManager;
import com.volmit.iris.scaffold.cache.AtomicCache;
import com.volmit.iris.util.ArrayType;
import com.volmit.iris.util.Desc;
import com.volmit.iris.util.DontObfuscate;
import com.volmit.iris.util.KList;
import com.volmit.iris.util.RegistryListLoot;
import com.volmit.iris.util.Required;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.bukkit.block.data.BlockData;
@Accessors(chain = true)
@NoArgsConstructor
@AllArgsConstructor
@Desc("Represents loot within this object or jigsaw piece")
@Data
public class IrisObjectLoot {
@DontObfuscate
@ArrayType(min = 1, type = IrisBlockData.class)
@Desc("The list of blocks this loot table should apply to")
private KList<IrisBlockData> filter = new KList<>();
@Desc("Exactly match the block data or not")
@DontObfuscate
private boolean exact = false;
@DontObfuscate
@Desc("The loot table name")
@Required
@RegistryListLoot
private String name;
@DontObfuscate
@Desc("The weight of this loot table being chosen")
private int weight = 1;
private final transient AtomicCache<KList<BlockData>> filterCache = new AtomicCache<>();
public KList<BlockData> getFilter(IrisDataManager rdata)
{
return filterCache.aquire(() ->
{
KList<BlockData> b = new KList<>();
for(IrisBlockData i : filter)
{
BlockData bx = i.getBlockData(rdata);
if(bx != null)
{
b.add(bx);
}
}
return b;
});
}
public boolean matchesFilter(IrisDataManager manager, BlockData data) {
for (BlockData filterData : getFilter(manager)) {
if (filterData.matches(data)) return true;
}
return false;
}
}

View File

@ -1,6 +1,8 @@
package com.volmit.iris.object;
import com.volmit.iris.Iris;
import com.volmit.iris.generator.noise.CNG;
import com.volmit.iris.manager.IrisDataManager;
import com.volmit.iris.scaffold.cache.AtomicCache;
import com.volmit.iris.scaffold.data.DataProvider;
import com.volmit.iris.util.*;
@ -9,6 +11,8 @@ import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.experimental.Accessors;
import org.bukkit.Material;
import org.bukkit.block.data.BlockData;
@EqualsAndHashCode()
@Accessors(chain = true)
@ -121,6 +125,11 @@ public class IrisObjectPlacement
@Desc("Translate this object's placement")
private IrisObjectTranslate translate = new IrisObjectTranslate();
@ArrayType(min = 1, type = IrisObjectLoot.class)
@DontObfuscate
@Desc("The loot tables to apply to these objects")
private KList<IrisObjectLoot> loot = new KList<>();
public IrisObjectPlacement toPlacement(String... place)
{
IrisObjectPlacement p = new IrisObjectPlacement();
@ -145,6 +154,7 @@ public class IrisObjectPlacement
p.setSnow(snow);
p.setClamp(clamp);
p.setRotation(rotation);
p.setLoot(loot);
return p;
}
@ -191,4 +201,74 @@ public class IrisObjectPlacement
public boolean isVacuum() {
return getMode().equals(ObjectPlaceMode.VACUUM);
}
private transient AtomicCache<TableCache> cache = new AtomicCache<>();
private class TableCache {
transient WeightedRandom<IrisLootTable> global = new WeightedRandom<>();
transient KMap<Material, WeightedRandom<IrisLootTable>> basic = new KMap<>();
transient KMap<Material, KMap<BlockData, WeightedRandom<IrisLootTable>>> exact = new KMap<>();
}
private TableCache getCache(IrisDataManager manager) {
return cache.aquire(() -> {
TableCache tc = new TableCache();
for (IrisObjectLoot loot : getLoot()) {
IrisLootTable table = manager.getLootLoader().load(loot.getName());
if (table == null) {
Iris.warn("Couldn't find loot table " + loot.getName());
continue;
}
if (loot.getFilter().isEmpty()) //Table applies to all containers
{
tc.global.put(table, loot.getWeight());
} else if (!loot.isExact()) //Table is meant to be by type
{
for (BlockData filterData : loot.getFilter(manager)) {
if (!tc.basic.containsKey(filterData.getMaterial())) {
tc.basic.put(filterData.getMaterial(), new WeightedRandom<>());
}
tc.basic.get(filterData.getMaterial()).put(table, loot.getWeight());
}
} else //Filter is exact
{
for (BlockData filterData : loot.getFilter(manager)) {
if (!tc.exact.containsKey(filterData.getMaterial())) {
tc.exact.put(filterData.getMaterial(), new KMap<>());
}
if (!tc.exact.get(filterData.getMaterial()).containsKey(filterData)) {
tc.exact.get(filterData.getMaterial()).put(filterData, new WeightedRandom<>());
}
tc.exact.get(filterData.getMaterial()).get(filterData).put(table, loot.getWeight());
}
}
}
return tc;
});
}
public IrisLootTable getTable(BlockData data, IrisDataManager dataManager) {
TableCache cache = getCache(dataManager);
if(B.isStorageChest(data))
{
IrisLootTable picked = null;
if (cache.exact.containsKey(data.getMaterial()) && cache.exact.containsKey(data)) {
picked = cache.exact.get(data.getMaterial()).get(data).pullRandom();
} else if (cache.basic.containsKey(data.getMaterial())) {
picked = cache.basic.get(data.getMaterial()).pullRandom();
} else if (cache.global.getSize() > 0){
picked = cache.global.pullRandom();
}
return picked;
}
return null;
}
}

View File

@ -302,6 +302,17 @@ public interface Engine extends DataProvider, Fallible, GeneratorAccess, LootPro
int rx = b.getX();
int rz = b.getZ();
double he = getFramework().getComplex().getHeightStream().get(rx, rz);
PlacedObject po = getFramework().getEngine().getObjectPlacement(rx, b.getY(), rz);
if (po != null && po.getPlacement() != null) {
if(B.isStorageChest(b.getBlockData()))
{
IrisLootTable table = po.getPlacement().getTable(b.getBlockData(), getData());
if (table != null) {
return new KList<>(table);
}
}
}
IrisRegion region = getFramework().getComplex().getRegionStream().get(rx, rz);
IrisBiome biomeSurface = getFramework().getComplex().getTrueBiomeStream().get(rx, rz);
IrisBiome biomeUnder = b.getY() < he ? getFramework().getComplex().getCaveBiomeStream().get(rx, rz) : biomeSurface;

View File

@ -1,17 +1,25 @@
package com.volmit.iris.scaffold.jigsaw;
import com.volmit.iris.Iris;
import com.volmit.iris.generator.IrisEngine;
import com.volmit.iris.manager.IrisDataManager;
import com.volmit.iris.object.*;
import com.volmit.iris.object.tile.TileData;
import com.volmit.iris.scaffold.IrisWorlds;
import com.volmit.iris.scaffold.engine.Engine;
import com.volmit.iris.scaffold.engine.IrisAccess;
import com.volmit.iris.util.AxisAlignedBB;
import com.volmit.iris.util.IObjectPlacer;
import com.volmit.iris.util.KList;
import com.volmit.iris.util.RNG;
import lombok.Data;
import org.bukkit.Material;
import org.bukkit.World;
import org.bukkit.block.Block;
import org.bukkit.block.BlockState;
import org.bukkit.block.TileState;
import org.bukkit.block.data.BlockData;
import org.bukkit.inventory.InventoryHolder;
import org.bukkit.util.BlockVector;
@Data
@ -129,8 +137,17 @@ public class PlannedPiece {
}
public void place(World world) {
IrisAccess a = IrisWorlds.access(world);
int minY = 0;
if (a != null) {
minY = a.getCompound().getDefaultEngine().getMinHeight();
if (!a.getCompound().getRootDimension().isBedrock()) minY--; //If the dimension has no bedrock, allow it to go a block lower
}
getPiece().getPlacementOptions().getRotation().setEnabled(false);
int finalMinY = minY;
getObject().place(position.getX()+getObject().getCenter().getBlockX(), position.getY()+getObject().getCenter().getBlockY(), position.getZ()+getObject().getCenter().getBlockZ(), new IObjectPlacer() {
@Override
public int getHighest(int x, int z) {
@ -144,7 +161,22 @@ public class PlannedPiece {
@Override
public void set(int x, int y, int z, BlockData d) {
world.getBlockAt(x,y,z).setBlockData(d);
Block block = world.getBlockAt(x, y, z);
//Prevent blocks being set in or bellow bedrock
if (y <= finalMinY || block.getType() == Material.BEDROCK) return;
block.setBlockData(d);
if (a != null && getPiece().getPlacementOptions().getLoot().isNotEmpty() &&
block.getState() instanceof InventoryHolder) {
IrisLootTable table = getPiece().getPlacementOptions().getTable(block.getBlockData(), getData());
if (table == null) return;
Engine engine = a.getCompound().getEngineForHeight(y);
engine.addItems(false, ((InventoryHolder) block.getState()).getInventory(), getStructure().getRng(),
new KList<>(table), InventorySlotType.STORAGE, x, y, z, 15);
}
}
@Override
@ -183,6 +215,6 @@ public class PlannedPiece {
tile.toBukkitTry(state);
state.update();
}
}, piece.getPlacementOptions(), new RNG(), getData());
}, piece.getPlacementOptions(), getStructure().getRng(), getData());
}
}

View File

@ -162,14 +162,15 @@ public class PlannedStructure {
{
if(j.getSpawnEntity() != null)
{
IrisAccess a = IrisWorlds.access(world);
if (a == null) {
Iris.warn("Cannot spawn entities from jigsaw in non Iris world!");
break;
}
IrisPosition p = i.getWorldPosition(j).add(new IrisPosition(j.getDirection().toVector().multiply(2)));
IrisEntity e = getData().getEntityLoader().load(j.getSpawnEntity());
IrisAccess a = IrisWorlds.access(world);
if(a != null)
{
e.spawn(a.getCompound().getEngineForHeight(p.getY()), new Location(world, p.getX(), p.getY(), p.getZ()), rng);
}
e.spawn(a.getCompound().getEngineForHeight(p.getY()), new Location(world, p.getX() + 0.5, p.getY(), p.getZ() + 0.5), rng);
}
}
});

View File

@ -0,0 +1,41 @@
package com.volmit.iris.util;
import java.util.Random;
public class WeightedRandom<T> {
private KList<KeyPair<T, Integer>> weightedObjects = new KList<>();
private Random random;
private int totalWeight = 0;
public WeightedRandom(Random random) {
this.random = random;
}
public WeightedRandom() {
this.random = new Random();
}
public void put(T object, int weight) {
weightedObjects.add(new KeyPair<>(object, weight));
totalWeight += weight;
}
public T pullRandom() {
int pull = random.nextInt(totalWeight);
int index = 0;
while (pull > 0) {
pull -= weightedObjects.get(index).getV();
index++;
}
return weightedObjects.get(index).getK();
}
public int getSize() {
return weightedObjects.size();
}
public void shuffle() {
weightedObjects.shuffle(random);
}
}