Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Vault block API #12068

Merged
merged 15 commits into from
Feb 12, 2025
5 changes: 5 additions & 0 deletions build-data/paper.at
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,11 @@ public net.minecraft.world.level.block.entity.trialspawner.TrialSpawner stateAcc
public net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerData currentMobs
public net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerData detectedPlayers
public net.minecraft.world.level.block.entity.trialspawner.TrialSpawnerData nextSpawnData
public net.minecraft.world.level.block.entity.vault.VaultBlockEntity serverData
public net.minecraft.world.level.block.entity.vault.VaultServerData getRewardedPlayers()Ljava/util/Set;
public net.minecraft.world.level.block.entity.vault.VaultServerData pauseStateUpdatingUntil(J)V
public net.minecraft.world.level.block.entity.vault.VaultServerData stateUpdatingResumesAt()J
public net.minecraft.world.level.block.entity.vault.VaultSharedData getConnectedPlayers()Ljava/util/Set;
public net.minecraft.world.level.block.state.BlockBehaviour getMenuProvider(Lnet/minecraft/world/level/block/state/BlockState;Lnet/minecraft/world/level/Level;Lnet/minecraft/core/BlockPos;)Lnet/minecraft/world/MenuProvider;
public net.minecraft.world.level.block.state.BlockBehaviour hasCollision
public net.minecraft.world.level.block.state.BlockBehaviour$BlockStateBase destroySpeed
Expand Down
179 changes: 178 additions & 1 deletion paper-api/src/main/java/org/bukkit/block/Vault.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,184 @@
package org.bukkit.block;

import org.bukkit.World;
import org.bukkit.inventory.ItemStack;
import org.bukkit.loot.LootTable;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.Collection;
import java.util.Set;
import java.util.UUID;

/**
* Represents a captured state of a trial spawner.
* Represents a captured state of a vault.
*/
@NullMarked
public interface Vault extends TileState {
/**
* Gets the range in blocks at which this vault will become active when a player is near.
*
* @return This vault's activation range.
*/
double getActivationRange();

/**
* Sets the range in blocks at which the vault will become active when a player is near.
*
* @param activationRange The new activation range.
* @throws IllegalArgumentException if the new range is not a number, or if the new range is more than {@link #getDeactivationRange()}.
*/
void setActivationRange(double activationRange);

/**
* Gets the range in blocks at which this vault will become inactive when a player is not near.
*
* @return This vault's deactivation range.
*/
double getDeactivationRange();

/**
* Sets the range in blocks at which this vault will become inactive when a player is not near.
*
* @param deactivationRange The new deactivation range
* @throws IllegalArgumentException if the new range is not a number, or if the new range is less than {@link #getActivationRange()}.
*/
void setDeactivationRange(double deactivationRange);

/**
* Gets the {@link ItemStack} that players must use to unlock this vault.
*
* @return The item that players must use to unlock this vault.
*/
ItemStack getKeyItem();

/**
* Sets the {@link ItemStack} that players must use to unlock this vault.
*
* @param key The key item.
*/
void setKeyItem(ItemStack key);

/**
* Gets the {@link LootTable} that this vault will select rewards from.
*
* @return The loot table.
*/
LootTable getLootTable();

/**
* Sets the {@link LootTable} that this vault will select rewards from.
*
* @param lootTable The new loot table.
*/
void setLootTable(LootTable lootTable);

/**
* Gets the loot table that this vault will display items from.
* <p>
* Falls back to the regular {@link #getLootTable() loot table} if unset.
*
* @return The {@link LootTable} that will be used to display items.
*/
@Nullable
LootTable getDisplayedLootTable();

/**
* Sets the loot table that this vault will display items from.
*
* @param lootTable The new loot table to display, or {@code null} to clear this display override.
*/
void setDisplayedLootTable(@Nullable LootTable lootTable);

/**
* Gets the next time (in {@link World#getGameTime() game time}) that this vault block will be updated/ticked at.
*
* @return The next time that this vault block will be updated/ticked at.
* @see World#getGameTime()
*/
long getNextStateUpdateTime();

/**
* Sets the next time that this vault block will be updated/ticked at, if this value is less than or equals to the current
* {@link World#getGameTime()}, then it will be updated in the first possible tick.
*
* @param nextStateUpdateTime The next time that this vault block will be updated/ticked at.
* @see World#getGameTime()
*/
void setNextStateUpdateTime(long nextStateUpdateTime);

/**
* Gets the players who have used a key on this vault and unlocked it.
*
* @return An unmodifiable collection of player uuids.
*
* @apiNote Only the most recent 128 player UUIDs will be stored by vault blocks.
*/
@Unmodifiable
Collection<UUID> getRewardedPlayers();

/**
* Adds a player as rewarded for this vault.
*
* @param playerUUID The player's uuid.
* @return {@code true} if this player was previously not rewarded, and has been added as a result of this operation.
*
* @apiNote Only the most recent 128 player UUIDs will be stored by vault blocks. Attempting to add more will result in
* the first player UUID being removed.
*/
boolean addRewardedPlayer(UUID playerUUID);

/**
* Removes a player as rewarded for this vault, allowing them to use a {@link #getKeyItem() key item} again to receive rewards.
*
* @param playerUUID The player's uuid.
* @return {@code true} if this player was previously rewarded, and has been removed as a result of this operation.
*
* @apiNote Only the most recent 128 player UUIDs will be stored by vault blocks.
*/
boolean removeRewardedPlayer(UUID playerUUID);

/**
* Returns whether a given player has already been rewarded by this vault.
*
* @param playerUUID The player's uuid.
* @return Whether this player was previously rewarded by this vault.
*/
boolean hasRewardedPlayer(UUID playerUUID);

/**
* Gets an unmodifiable set of "connected players"; players who are inside this vault's activation range and who have not received rewards yet.
*
* @apiNote Vaults will only periodically scan for nearby players, so it may take until the next {@link #getNextStateUpdateTime() update time} for this
* collection to be updated upon a player entering its range.
*
* @return An unmodifiable set of connected player uuids.
Warriorrrr marked this conversation as resolved.
Show resolved Hide resolved
*/
@Unmodifiable
Set<UUID> getConnectedPlayers();

/**
* Returns whether a given player is currently connected to this vault.
*
* @param playerUUID the player's uuid
* @return {@code true} if this player is currently connected to this vault.
*
* @see #getConnectedPlayers()
*/
boolean hasConnectedPlayer(UUID playerUUID);

/**
* Gets the item currently being displayed inside this vault. Displayed items will automatically cycle between random items from the {@link #getDisplayedLootTable()}
* or {@link #getLootTable()} loot tables while this vault is active.
*
* @return The item currently being displayed inside this vault.
*/
ItemStack getDisplayedItem();

/**
* Sets the item to display inside this vault until the next cycle.
*
* @param displayedItem The item to display
*/
void setDisplayedItem(ItemStack displayedItem);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
--- a/net/minecraft/world/level/block/entity/vault/VaultServerData.java
+++ b/net/minecraft/world/level/block/entity/vault/VaultServerData.java
@@ -66,7 +_,12 @@

@VisibleForTesting
public void addToRewardedPlayers(Player player) {
- this.rewardedPlayers.add(player.getUUID());
+ // Paper start - Vault API
+ addToRewardedPlayers(player.getUUID());
+ }
+ public boolean addToRewardedPlayers(final java.util.UUID player) {
+ final boolean removed = this.rewardedPlayers.add(player);
+ // Paper end - Vault API
if (this.rewardedPlayers.size() > 128) {
Iterator<UUID> iterator = this.rewardedPlayers.iterator();
if (iterator.hasNext()) {
@@ -76,6 +_,7 @@
}

this.markChanged();
+ return removed; // Paper - Vault API
}

public long stateUpdatingResumesAt() {
@@ -131,4 +_,15 @@
public float ejectionProgress() {
return this.totalEjectionsNeeded == 1 ? 1.0F : 1.0F - Mth.inverseLerp((float)this.getItemsToEject().size(), 1.0F, (float)this.totalEjectionsNeeded);
}
+
+ // Paper start - Vault API
+ public boolean removeFromRewardedPlayers(final UUID uuid) {
+ if (this.rewardedPlayers.remove(uuid)) {
+ this.markChanged();
+ return true;
+ }
+
+ return false;
+ }
+ // Paper end - Vault API
}
Original file line number Diff line number Diff line change
@@ -1,17 +1,33 @@
package org.bukkit.craftbukkit.block;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableSet;
import net.minecraft.resources.ResourceKey;
import net.minecraft.world.level.block.entity.vault.VaultBlockEntity;
import net.minecraft.world.level.block.entity.vault.VaultConfig;
import org.bukkit.Location;
import org.bukkit.World;
import org.bukkit.block.Vault;
import org.bukkit.craftbukkit.CraftLootTable;
import org.bukkit.craftbukkit.inventory.CraftItemStack;
import org.bukkit.inventory.ItemStack;
import org.bukkit.loot.LootTable;
import org.jetbrains.annotations.Unmodifiable;
import org.jspecify.annotations.NullMarked;
import org.jspecify.annotations.Nullable;
import java.util.Collection;
import java.util.Optional;
import java.util.Set;
import java.util.UUID;

@NullMarked
Warriorrrr marked this conversation as resolved.
Show resolved Hide resolved
public class CraftVault extends CraftBlockEntityState<VaultBlockEntity> implements Vault {

public CraftVault(World world, VaultBlockEntity tileEntity) {
super(world, tileEntity);
}

protected CraftVault(CraftVault state, Location location) {
protected CraftVault(CraftVault state, @Nullable Location location) {
super(state, location);
}

Expand All @@ -24,4 +40,124 @@ public CraftVault copy() {
public CraftVault copy(Location location) {
return new CraftVault(this, location);
}

@Override
public double getActivationRange() {
return this.getSnapshot().getConfig().activationRange();
}

@Override
public void setActivationRange(final double activationRange) {
Preconditions.checkArgument(Double.isFinite(activationRange), "activation range must not be NaN or infinite");
Preconditions.checkArgument(activationRange <= this.getDeactivationRange(), "New activation range (%s) must be less or equal to deactivation range (%s)", activationRange, this.getDeactivationRange());

final VaultConfig config = this.getSnapshot().getConfig();
this.getSnapshot().setConfig(new VaultConfig(config.lootTable(), activationRange, config.deactivationRange(), config.keyItem(), config.overrideLootTableToDisplay()));
}

@Override
public double getDeactivationRange() {
return this.getSnapshot().getConfig().deactivationRange();
}

@Override
public void setDeactivationRange(final double deactivationRange) {
Preconditions.checkArgument(Double.isFinite(deactivationRange), "deactivation range must not be NaN or infinite");
Preconditions.checkArgument(deactivationRange >= this.getActivationRange(), "New deactivation range (%s) must be more or equal to activation range (%s)", deactivationRange, this.getActivationRange());

final VaultConfig config = this.getSnapshot().getConfig();
this.getSnapshot().setConfig(new VaultConfig(config.lootTable(), config.activationRange(), deactivationRange, config.keyItem(), config.overrideLootTableToDisplay()));
}

@Override
public ItemStack getKeyItem() {
return this.getSnapshot().getConfig().keyItem().asBukkitCopy();
}

@Override
public void setKeyItem(final ItemStack key) {
Preconditions.checkArgument(key != null, "key must not be null");

final VaultConfig config = this.getSnapshot().getConfig();
this.getSnapshot().setConfig(new VaultConfig(config.lootTable(), config.activationRange(), config.deactivationRange(), CraftItemStack.asNMSCopy(key), config.overrideLootTableToDisplay()));
}

@Override
public LootTable getLootTable() {
return CraftLootTable.minecraftToBukkit(this.getSnapshot().getConfig().lootTable());
}

@Override
public void setLootTable(final LootTable lootTable) {
final ResourceKey<net.minecraft.world.level.storage.loot.LootTable> lootTableKey = CraftLootTable.bukkitToMinecraft(lootTable);
Preconditions.checkArgument(lootTableKey != null, "lootTable must not be null");

final VaultConfig config = this.getSnapshot().getConfig();
this.getSnapshot().setConfig(new VaultConfig(lootTableKey, config.activationRange(), config.deactivationRange(), config.keyItem(), config.overrideLootTableToDisplay()));
}

@Override
public @Nullable LootTable getDisplayedLootTable() {
return this.getSnapshot().getConfig().overrideLootTableToDisplay().map(CraftLootTable::minecraftToBukkit).orElse(null);
}

@Override
public void setDisplayedLootTable(final @Nullable LootTable lootTable) {
final VaultConfig config = this.getSnapshot().getConfig();

this.getSnapshot().setConfig(new VaultConfig(config.lootTable(), config.activationRange(), config.deactivationRange(), config.keyItem(), Optional.ofNullable(CraftLootTable.bukkitToMinecraft(lootTable))));
}

@Override
public long getNextStateUpdateTime() {
return this.getSnapshot().serverData.stateUpdatingResumesAt();
}

@Override
public void setNextStateUpdateTime(final long nextStateUpdateTime) {
this.getSnapshot().serverData.pauseStateUpdatingUntil(nextStateUpdateTime);
}

@Override
public @Unmodifiable Collection<UUID> getRewardedPlayers() {
return ImmutableSet.copyOf(this.getSnapshot().serverData.getRewardedPlayers());
}

@Override
public boolean addRewardedPlayer(final UUID playerUUID) {
Warriorrrr marked this conversation as resolved.
Show resolved Hide resolved
Preconditions.checkArgument(playerUUID != null, "playerUUID must not be null");
return this.getSnapshot().serverData.addToRewardedPlayers(playerUUID);
}

@Override
public boolean removeRewardedPlayer(final UUID playerUUID) {
Preconditions.checkArgument(playerUUID != null, "playerUUID must not be null");
return this.getSnapshot().serverData.removeFromRewardedPlayers(playerUUID);
}

@Override
public boolean hasRewardedPlayer(final UUID playerUUID) {
return this.getSnapshot().serverData.getRewardedPlayers().contains(playerUUID);
}

@Override
public @Unmodifiable Set<UUID> getConnectedPlayers() {
return ImmutableSet.copyOf(this.getSnapshot().getSharedData().getConnectedPlayers());
}

@Override
public boolean hasConnectedPlayer(final UUID playerUUID) {
return this.getSnapshot().getSharedData().getConnectedPlayers().contains(playerUUID);
}

@Override
public ItemStack getDisplayedItem() {
return CraftItemStack.asBukkitCopy(this.getSnapshot().getSharedData().getDisplayItem());
}

@Override
public void setDisplayedItem(final ItemStack displayedItem) {
Warriorrrr marked this conversation as resolved.
Show resolved Hide resolved
Preconditions.checkArgument(displayedItem != null, "displayedItem must not be null");
this.getSnapshot().getSharedData().setDisplayItem(CraftItemStack.asNMSCopy(displayedItem));
}
}
Loading