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
4 changes: 4 additions & 0 deletions build-data/paper.at
Original file line number Diff line number Diff line change
Expand Up @@ -628,6 +628,10 @@ 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.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
134 changes: 133 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,139 @@
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.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 getDisplayLootTable();
Warriorrrr marked this conversation as resolved.
Show resolved Hide resolved

/**
* Sets the loot table that this vault will display items from.
*
* @param lootTable The new display loot table, or {@code null} to clear this display override.
*/
void setDisplayLootTable(@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 set of player uuids.
*
* @apiNote Only the most recent 128 player UUIDs will be stored by vault blocks.
*/
@Unmodifiable
Set<UUID> getRewardedPlayers();
Warriorrrr marked this conversation as resolved.
Show resolved Hide resolved

/**
* 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);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
--- a/net/minecraft/world/level/block/entity/vault/VaultServerData.java
+++ b/net/minecraft/world/level/block/entity/vault/VaultServerData.java
@@ -64,9 +_,22 @@
return this.rewardedPlayers.contains(player.getUUID());
}

+ // Paper start - Vault API
+ public boolean removeFromRewardedPlayers(UUID uuid) {
+ final boolean result = this.rewardedPlayers.remove(uuid);
+ if (result)
+ this.markChanged();
+
+ return result;
+ }
+
@VisibleForTesting
- public void addToRewardedPlayers(Player player) {
- this.rewardedPlayers.add(player.getUUID());
+ public boolean addToRewardedPlayers(Player player) {
+ return addToRewardedPlayers(player.getUUID());
+ }
+
+ public boolean addToRewardedPlayers(UUID uuid) {
+ final boolean result = this.rewardedPlayers.add(uuid);
if (this.rewardedPlayers.size() > 128) {
Iterator<UUID> iterator = this.rewardedPlayers.iterator();
if (iterator.hasNext()) {
@@ -76,6 +_,8 @@
}

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

public long stateUpdatingResumesAt() {
Original file line number Diff line number Diff line change
@@ -1,17 +1,32 @@
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.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 +39,96 @@ 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), "deactivation range must be a number");
Warriorrrr marked this conversation as resolved.
Show resolved Hide resolved
Preconditions.checkArgument(activationRange <= getDeactivationRange(), "New activation range (%f) must be less or equal to deactivation range (%f)".formatted(activationRange, getDeactivationRange()));
Warriorrrr marked this conversation as resolved.
Show resolved Hide resolved

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 be a number");
Preconditions.checkArgument(deactivationRange >= getActivationRange(), "New deactivation range (%f) must be more or equal to activation range (%f)".formatted(deactivationRange, 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.checkNotNull(key, "key must not be null");
Warriorrrr marked this conversation as resolved.
Show resolved Hide resolved

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.checkNotNull(lootTableKey, "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 getDisplayLootTable() {
return this.getSnapshot().getConfig().overrideLootTableToDisplay().map(CraftLootTable::minecraftToBukkit).orElse(null);
}

@Override
public void setDisplayLootTable(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 Set<UUID> getRewardedPlayers() {
Warriorrrr marked this conversation as resolved.
Show resolved Hide resolved
return ImmutableSet.copyOf(this.getSnapshot().serverData.getRewardedPlayers());
}

@Override
public boolean addRewardedPlayer(final UUID playerUUID) {
Warriorrrr marked this conversation as resolved.
Show resolved Hide resolved
return this.getSnapshot().serverData.addToRewardedPlayers(playerUUID);
}

@Override
public boolean removeRewardedPlayer(final UUID playerUUID) {
return this.getSnapshot().serverData.removeFromRewardedPlayers(playerUUID);
}
}
Loading