LootTable API and replenishable lootables
Provides an API to control the loot table for an object. Also provides a feature that any Lootable Inventory (Chests in Structures) can automatically replenish after a given time. This feature is good for long term worlds so that newer players do not suffer with "Every chest has been looted" == AT == public org.bukkit.craftbukkit.block.CraftBlockEntityState getTileEntity()Lnet/minecraft/world/level/block/entity/BlockEntity; public org.bukkit.craftbukkit.block.CraftLootable setLootTable(Lorg/bukkit/loot/LootTable;J)V public org.bukkit.craftbukkit.entity.CraftMinecartContainer setLootTable(Lorg/bukkit/loot/LootTable;J)V
This commit is contained in:
@@ -0,0 +1,92 @@
|
||||
--- a/net/minecraft/world/RandomizableContainer.java
|
||||
+++ b/net/minecraft/world/RandomizableContainer.java
|
||||
@@ -28,7 +28,7 @@
|
||||
|
||||
void setLootTable(@Nullable ResourceKey<LootTable> lootTable);
|
||||
|
||||
- default void setLootTable(ResourceKey<LootTable> lootTableId, long lootTableSeed) {
|
||||
+ default void setLootTable(@Nullable ResourceKey<LootTable> lootTableId, long lootTableSeed) { // Paper - add nullable
|
||||
this.setLootTable(lootTableId);
|
||||
this.setLootTableSeed(lootTableSeed);
|
||||
}
|
||||
@@ -51,13 +51,14 @@
|
||||
default boolean tryLoadLootTable(CompoundTag nbt) {
|
||||
if (nbt.contains("LootTable", 8)) {
|
||||
this.setLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable"))));
|
||||
+ if (this.lootableData() != null && this.getLootTable() != null) this.lootableData().loadNbt(nbt); // Paper - LootTable API
|
||||
if (nbt.contains("LootTableSeed", 4)) {
|
||||
this.setLootTableSeed(nbt.getLong("LootTableSeed"));
|
||||
} else {
|
||||
this.setLootTableSeed(0L);
|
||||
}
|
||||
|
||||
- return true;
|
||||
+ return this.lootableData() == null; // Paper - only track the loot table if there is chance for replenish
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
@@ -69,26 +70,44 @@
|
||||
return false;
|
||||
} else {
|
||||
nbt.putString("LootTable", resourceKey.location().toString());
|
||||
+ if (this.lootableData() != null) this.lootableData().saveNbt(nbt); // Paper - LootTable API
|
||||
long l = this.getLootTableSeed();
|
||||
if (l != 0L) {
|
||||
nbt.putLong("LootTableSeed", l);
|
||||
}
|
||||
|
||||
- return true;
|
||||
+ return this.lootableData() == null; // Paper - only track the loot table if there is chance for replenish
|
||||
}
|
||||
}
|
||||
|
||||
default void unpackLootTable(@Nullable Player player) {
|
||||
+ // Paper start - LootTable API
|
||||
+ this.unpackLootTable(player, false);
|
||||
+ }
|
||||
+ default void unpackLootTable(@Nullable final Player player, final boolean forceClearLootTable) {
|
||||
+ // Paper end - LootTable API
|
||||
Level level = this.getLevel();
|
||||
BlockPos blockPos = this.getBlockPos();
|
||||
ResourceKey<LootTable> resourceKey = this.getLootTable();
|
||||
- if (resourceKey != null && level != null && level.getServer() != null) {
|
||||
+ // Paper start - LootTable API
|
||||
+ lootReplenish: if (resourceKey != null && level != null && level.getServer() != null) {
|
||||
+ if (this.lootableData() != null && !this.lootableData().shouldReplenish(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.CONTAINER, player)) {
|
||||
+ if (forceClearLootTable) {
|
||||
+ this.setLootTable(null);
|
||||
+ }
|
||||
+ break lootReplenish;
|
||||
+ }
|
||||
+ // Paper end - LootTable API
|
||||
LootTable lootTable = level.getServer().reloadableRegistries().getLootTable(resourceKey);
|
||||
if (player instanceof ServerPlayer) {
|
||||
CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, resourceKey);
|
||||
}
|
||||
|
||||
- this.setLootTable(null);
|
||||
+ // Paper start - LootTable API
|
||||
+ if (forceClearLootTable || this.lootableData() == null || this.lootableData().shouldClearLootTable(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.CONTAINER, player)) {
|
||||
+ this.setLootTable(null);
|
||||
+ }
|
||||
+ // Paper end - LootTable API
|
||||
LootParams.Builder builder = new LootParams.Builder((ServerLevel)level).withParameter(LootContextParams.ORIGIN, Vec3.atCenterOf(blockPos));
|
||||
if (player != null) {
|
||||
builder.withLuck(player.getLuck()).withParameter(LootContextParams.THIS_ENTITY, player);
|
||||
@@ -97,4 +116,16 @@
|
||||
lootTable.fill(this, builder.create(LootContextParamSets.CHEST), this.getLootTableSeed());
|
||||
}
|
||||
}
|
||||
+
|
||||
+ // Paper start - LootTable API
|
||||
+ @Nullable @org.jetbrains.annotations.Contract(pure = true)
|
||||
+ default com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
|
||||
+ return null; // some containers don't really have a "replenish" ability like decorated pots
|
||||
+ }
|
||||
+
|
||||
+ default com.destroystokyo.paper.loottable.PaperLootableInventory getLootableInventory() {
|
||||
+ final org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(java.util.Objects.requireNonNull(this.getLevel(), "Cannot manage loot tables on block entities not in world"), this.getBlockPos());
|
||||
+ return (com.destroystokyo.paper.loottable.PaperLootableInventory) block.getState(false);
|
||||
+ }
|
||||
+ // Paper end - LootTable API
|
||||
}
|
||||
@@ -37,11 +37,28 @@
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -212,4 +228,51 @@
|
||||
@@ -165,7 +181,7 @@
|
||||
@Nullable
|
||||
@Override
|
||||
public AbstractContainerMenu createMenu(int syncId, Inventory playerInventory, Player player) {
|
||||
- if (this.lootTable != null && player.isSpectator()) {
|
||||
+ if (this.lootTable != null && player.isSpectator()) { // Paper - LootTable API (TODO spectators can open chests that aren't ready to be re-generated but this doesn't support that)
|
||||
return null;
|
||||
} else {
|
||||
this.unpackLootTable(playerInventory.player);
|
||||
@@ -212,4 +228,59 @@
|
||||
public void stopOpen(Player player) {
|
||||
this.level().gameEvent((Holder) GameEvent.CONTAINER_CLOSE, this.position(), GameEvent.Context.of((Entity) player));
|
||||
}
|
||||
+
|
||||
+ // Paper start - LootTable API
|
||||
+ final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData();
|
||||
+
|
||||
+ @Override
|
||||
+ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
|
||||
+ return this.lootableData;
|
||||
+ }
|
||||
+ // Paper end - LootTable API
|
||||
+ // CraftBukkit start
|
||||
+ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
|
||||
+ private int maxStack = MAX_STACK;
|
||||
|
||||
@@ -15,10 +15,18 @@
|
||||
|
||||
public abstract class AbstractMinecartContainer extends AbstractMinecart implements ContainerEntity {
|
||||
|
||||
@@ -28,9 +36,50 @@
|
||||
@@ -28,9 +36,58 @@
|
||||
public ResourceKey<LootTable> lootTable;
|
||||
public long lootTableSeed;
|
||||
|
||||
+ // Paper start - LootTable API
|
||||
+ final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData();
|
||||
+
|
||||
+ @Override
|
||||
+ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
|
||||
+ return this.lootableData;
|
||||
+ }
|
||||
+ // Paper end - LootTable API
|
||||
+ // CraftBukkit start
|
||||
+ public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
|
||||
+ private int maxStack = MAX_STACK;
|
||||
@@ -67,7 +75,7 @@
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -74,11 +123,18 @@
|
||||
@@ -74,11 +131,18 @@
|
||||
|
||||
@Override
|
||||
public void remove(Entity.RemovalReason reason) {
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
--- a/net/minecraft/world/entity/vehicle/ContainerEntity.java
|
||||
+++ b/net/minecraft/world/entity/vehicle/ContainerEntity.java
|
||||
@@ -62,22 +62,26 @@
|
||||
default void addChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registries) {
|
||||
if (this.getContainerLootTable() != null) {
|
||||
nbt.putString("LootTable", this.getContainerLootTable().location().toString());
|
||||
+ this.lootableData().saveNbt(nbt); // Paper
|
||||
if (this.getContainerLootTableSeed() != 0L) {
|
||||
nbt.putLong("LootTableSeed", this.getContainerLootTableSeed());
|
||||
}
|
||||
- } else {
|
||||
- ContainerHelper.saveAllItems(nbt, this.getItemStacks(), registries);
|
||||
}
|
||||
+ ContainerHelper.saveAllItems(nbt, this.getItemStacks(), registries); // Paper - always save the items, table may still remain
|
||||
}
|
||||
|
||||
default void readChestVehicleSaveData(CompoundTag nbt, HolderLookup.Provider registries) {
|
||||
this.clearItemStacks();
|
||||
if (nbt.contains("LootTable", 8)) {
|
||||
this.setContainerLootTable(ResourceKey.create(Registries.LOOT_TABLE, ResourceLocation.parse(nbt.getString("LootTable"))));
|
||||
+ // Paper start - LootTable API
|
||||
+ if (this.getContainerLootTable() != null) {
|
||||
+ this.lootableData().loadNbt(nbt);
|
||||
+ }
|
||||
+ // Paper end - LootTable API
|
||||
this.setContainerLootTableSeed(nbt.getLong("LootTableSeed"));
|
||||
- } else {
|
||||
- ContainerHelper.loadAllItems(nbt, this.getItemStacks(), registries);
|
||||
}
|
||||
+ ContainerHelper.loadAllItems(nbt, this.getItemStacks(), registries); // Paper - always save the items, table may still remain
|
||||
}
|
||||
|
||||
default void chestVehicleDestroyed(DamageSource source, ServerLevel world, Entity vehicle) {
|
||||
@@ -97,13 +101,18 @@
|
||||
|
||||
default void unpackChestVehicleLootTable(@Nullable Player player) {
|
||||
MinecraftServer minecraftServer = this.level().getServer();
|
||||
- if (this.getContainerLootTable() != null && minecraftServer != null) {
|
||||
+ if (minecraftServer != null && this.lootableData().shouldReplenish(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.ENTITY, player)) { // Paper - LootTable API
|
||||
LootTable lootTable = minecraftServer.reloadableRegistries().getLootTable(this.getContainerLootTable());
|
||||
if (player != null) {
|
||||
CriteriaTriggers.GENERATE_LOOT.trigger((ServerPlayer)player, this.getContainerLootTable());
|
||||
}
|
||||
|
||||
- this.setContainerLootTable(null);
|
||||
+ // Paper start - LootTable API
|
||||
+ if (this.lootableData().shouldClearLootTable(this, com.destroystokyo.paper.loottable.PaperLootableInventoryData.ENTITY, player)) {
|
||||
+ this.setContainerLootTable(null);
|
||||
+ }
|
||||
+ // Paper end - LootTable API
|
||||
+
|
||||
LootParams.Builder builder = new LootParams.Builder((ServerLevel)this.level()).withParameter(LootContextParams.ORIGIN, this.position());
|
||||
if (player != null) {
|
||||
builder.withLuck(player.getLuck()).withParameter(LootContextParams.THIS_ENTITY, player);
|
||||
@@ -173,4 +182,14 @@
|
||||
default boolean isChestVehicleStillValid(Player player) {
|
||||
return !this.isRemoved() && player.canInteractWithEntity(this.getBoundingBox(), 4.0);
|
||||
}
|
||||
+
|
||||
+ // Paper start - LootTable API
|
||||
+ default com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
|
||||
+ throw new UnsupportedOperationException("Implement this method");
|
||||
+ }
|
||||
+
|
||||
+ default com.destroystokyo.paper.loottable.PaperLootableInventory getLootableInventory() {
|
||||
+ return ((com.destroystokyo.paper.loottable.PaperLootableInventory) ((net.minecraft.world.entity.Entity) this).getBukkitEntity());
|
||||
+ }
|
||||
+ // Paper end - LootTable API
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
--- a/net/minecraft/world/level/block/ShulkerBoxBlock.java
|
||||
+++ b/net/minecraft/world/level/block/ShulkerBoxBlock.java
|
||||
@@ -137,7 +137,7 @@
|
||||
itemEntity.setDefaultPickUpDelay();
|
||||
world.addFreshEntity(itemEntity);
|
||||
} else {
|
||||
- shulkerBoxBlockEntity.unpackLootTable(player);
|
||||
+ shulkerBoxBlockEntity.unpackLootTable(player, true); // Paper - force clear loot table so replenish data isn't persisted in the stack
|
||||
}
|
||||
}
|
||||
|
||||
@@ -147,7 +147,15 @@
|
||||
@Override
|
||||
protected List<ItemStack> getDrops(BlockState state, LootParams.Builder builder) {
|
||||
BlockEntity blockEntity = builder.getOptionalParameter(LootContextParams.BLOCK_ENTITY);
|
||||
+ Runnable reAdd = null; // Paper
|
||||
if (blockEntity instanceof ShulkerBoxBlockEntity shulkerBoxBlockEntity) {
|
||||
+ // Paper start - clear loot table if it was already used
|
||||
+ if (shulkerBoxBlockEntity.lootableData().getLastFill() != -1 || !builder.getLevel().paperConfig().lootables.retainUnlootedShulkerBoxLootTableOnNonPlayerBreak) {
|
||||
+ net.minecraft.resources.ResourceKey<net.minecraft.world.level.storage.loot.LootTable> lootTableResourceKey = shulkerBoxBlockEntity.getLootTable();
|
||||
+ reAdd = () -> shulkerBoxBlockEntity.setLootTable(lootTableResourceKey);
|
||||
+ shulkerBoxBlockEntity.setLootTable(null);
|
||||
+ }
|
||||
+ // Paper end
|
||||
builder = builder.withDynamicDrop(CONTENTS, lootConsumer -> {
|
||||
for (int i = 0; i < shulkerBoxBlockEntity.getContainerSize(); i++) {
|
||||
lootConsumer.accept(shulkerBoxBlockEntity.getItem(i));
|
||||
@@ -155,7 +163,13 @@
|
||||
});
|
||||
}
|
||||
|
||||
+ // Paper start - re-set loot table if it was cleared
|
||||
+ try {
|
||||
return super.getDrops(state, builder);
|
||||
+ } finally {
|
||||
+ if (reAdd != null) reAdd.run();
|
||||
+ }
|
||||
+ // Paper end - re-set loot table if it was cleared
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -0,0 +1,16 @@
|
||||
--- a/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
|
||||
+++ b/net/minecraft/world/level/block/entity/RandomizableContainerBlockEntity.java
|
||||
@@ -115,4 +115,13 @@
|
||||
nbt.remove("LootTable");
|
||||
nbt.remove("LootTableSeed");
|
||||
}
|
||||
+
|
||||
+ // Paper start - LootTable API
|
||||
+ final com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData = new com.destroystokyo.paper.loottable.PaperLootableInventoryData(); // Paper
|
||||
+
|
||||
+ @Override
|
||||
+ public com.destroystokyo.paper.loottable.PaperLootableInventoryData lootableData() {
|
||||
+ return this.lootableData;
|
||||
+ }
|
||||
+ // Paper end - LootTable API
|
||||
}
|
||||
Reference in New Issue
Block a user