diff --git a/patches/api/LootTable-API.patch b/patches/api/LootTable-API.patch index 125998a5dc..19cc692a86 100644 --- a/patches/api/LootTable-API.patch +++ b/patches/api/LootTable-API.patch @@ -106,6 +106,15 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + /** ++ * Checks if this player can loot this block. Takes into account the "restrict player reloot" settings ++ * ++ * @param player the player to check ++ * ++ * @return Whether this player can loot this block ++ */ ++ boolean canPlayerLoot(@NotNull UUID player); ++ ++ /** + * Has this player ever looted this block + * @param player The player to check + * @return Whether or not this player has looted this block diff --git a/patches/server/LootTable-API-Replenishable-Lootables-Feature.patch b/patches/server/LootTable-API-Replenishable-Lootables-Feature.patch index 5fa69bc847..17ef1643c0 100644 --- a/patches/server/LootTable-API-Replenishable-Lootables-Feature.patch +++ b/patches/server/LootTable-API-Replenishable-Lootables-Feature.patch @@ -196,6 +196,11 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + + @Override ++ default boolean canPlayerLoot(final UUID player) { ++ return getLootableData().canPlayerLoot(player, this.getNMSWorld().paperConfig()); ++ } ++ ++ @Override + default Long getLastLooted(UUID player) { + return getLootableData().getLastLooted(player); + } @@ -242,6 +247,9 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +package com.destroystokyo.paper.loottable; + +import io.papermc.paper.configuration.WorldConfiguration; ++import io.papermc.paper.configuration.type.DurationOrDisabled; ++import java.time.temporal.ChronoUnit; ++import java.util.concurrent.TimeUnit; +import org.bukkit.entity.Player; +import org.bukkit.loot.LootTable; +import javax.annotation.Nullable; @@ -318,9 +326,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + + final Player bukkitPlayer = (Player) player.getBukkitEntity(); + LootableInventoryReplenishEvent event = new LootableInventoryReplenishEvent(bukkitPlayer, lootable.getAPILootableInventory()); -+ if (paperConfig.lootables.restrictPlayerReloot && hasPlayerLooted(player.getUUID())) { -+ event.setCancelled(true); -+ } ++ event.setCancelled(!canPlayerLoot(player.getUUID(), paperConfig)); + return event.callEvent(); + } + public void processRefill(@Nullable net.minecraft.world.entity.player.Player player) { @@ -402,14 +408,22 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + this.lootedPlayers = new HashMap<>(); + } + if (looted) { -+ if (!this.lootedPlayers.containsKey(player)) { -+ this.lootedPlayers.put(player, System.currentTimeMillis()); -+ } ++ this.lootedPlayers.put(player, System.currentTimeMillis()); + } else if (this.lootedPlayers != null) { + this.lootedPlayers.remove(player); + } + } + ++ boolean canPlayerLoot(final UUID player, final WorldConfiguration worldConfiguration) { ++ final Long lastLooted = getLastLooted(player); ++ if (!worldConfiguration.lootables.restrictPlayerReloot || lastLooted == null) return true; ++ ++ final DurationOrDisabled restrictPlayerRelootTime = worldConfiguration.lootables.restrictPlayerRelootTime; ++ if (restrictPlayerRelootTime.value().isEmpty()) return true; ++ ++ return TimeUnit.SECONDS.toMillis(restrictPlayerRelootTime.value().get().seconds()) + lastLooted < System.currentTimeMillis(); ++ } ++ + boolean hasPlayerLooted(UUID player) { + return this.lootedPlayers != null && this.lootedPlayers.containsKey(player); + } diff --git a/patches/server/Paper-config-files.patch b/patches/server/Paper-config-files.patch index 548af43696..1259fa9620 100644 --- a/patches/server/Paper-config-files.patch +++ b/patches/server/Paper-config-files.patch @@ -978,6 +978,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import io.papermc.paper.configuration.type.BooleanOrDefault; +import io.papermc.paper.configuration.type.DoubleOrDefault; +import io.papermc.paper.configuration.type.Duration; ++import io.papermc.paper.configuration.type.DurationOrDisabled; +import io.papermc.paper.configuration.type.EngineMode; +import io.papermc.paper.configuration.type.IntOr; +import io.papermc.paper.configuration.type.fallback.FallbackValueSerializer; @@ -1166,6 +1167,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + .register(DoubleOrDefault.SERIALIZER) + .register(BooleanOrDefault.SERIALIZER) + .register(Duration.SERIALIZER) ++ .register(DurationOrDisabled.SERIALIZER) + .register(EngineMode.SERIALIZER) + .register(NbtPathSerializer.SERIALIZER) + .register(FallbackValueSerializer.create(contextMap.require(SPIGOT_WORLD_CONFIG_CONTEXT_KEY).get(), MinecraftServer::getServer)) @@ -1496,6 +1498,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 +import io.papermc.paper.configuration.type.BooleanOrDefault; +import io.papermc.paper.configuration.type.DoubleOrDefault; +import io.papermc.paper.configuration.type.Duration; ++import io.papermc.paper.configuration.type.DurationOrDisabled; +import io.papermc.paper.configuration.type.EngineMode; +import io.papermc.paper.configuration.type.IntOr; +import io.papermc.paper.configuration.type.fallback.ArrowDespawnRate; @@ -1775,6 +1778,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + public class Lootables extends ConfigurationPart { + public boolean autoReplenish = false; + public boolean restrictPlayerReloot = true; ++ public DurationOrDisabled restrictPlayerRelootTime = DurationOrDisabled.USE_DISABLED; + public boolean resetSeedOnFill = true; + public int maxRefills = -1; + public Duration refreshMin = Duration.of("12h"); @@ -4021,7 +4025,7 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + return (int) num; + } + -+ private static final class Serializer extends ScalarSerializer { ++ static final class Serializer extends ScalarSerializer { + private Serializer() { + super(Duration.class); + } @@ -4037,6 +4041,66 @@ index 0000000000000000000000000000000000000000..00000000000000000000000000000000 + } + } +} +diff --git a/src/main/java/io/papermc/paper/configuration/type/DurationOrDisabled.java b/src/main/java/io/papermc/paper/configuration/type/DurationOrDisabled.java +new file mode 100644 +index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 +--- /dev/null ++++ b/src/main/java/io/papermc/paper/configuration/type/DurationOrDisabled.java +@@ -0,0 +0,0 @@ ++package io.papermc.paper.configuration.type; ++ ++import java.lang.reflect.Type; ++import java.util.Optional; ++import java.util.function.Predicate; ++import org.spongepowered.configurate.serialize.ScalarSerializer; ++import org.spongepowered.configurate.serialize.SerializationException; ++ ++@SuppressWarnings("OptionalUsedAsFieldOrParameterType") ++public final class DurationOrDisabled { ++ private static final String DISABLE_VALUE = "disabled"; ++ public static final DurationOrDisabled USE_DISABLED = new DurationOrDisabled(Optional.empty()); ++ public static final ScalarSerializer SERIALIZER = new Serializer(); ++ ++ private Optional value; ++ ++ public DurationOrDisabled(final Optional value) { ++ this.value = value; ++ } ++ ++ public Optional value() { ++ return this.value; ++ } ++ ++ public void value(final Optional value) { ++ this.value = value; ++ } ++ ++ public Duration or(final Duration fallback) { ++ return this.value.orElse(fallback); ++ } ++ ++ private static final class Serializer extends ScalarSerializer { ++ Serializer() { ++ super(DurationOrDisabled.class); ++ } ++ ++ @Override ++ public DurationOrDisabled deserialize(final Type type, final Object obj) throws SerializationException { ++ if (obj instanceof final String string) { ++ if (DISABLE_VALUE.equalsIgnoreCase(string)) { ++ return USE_DISABLED; ++ } ++ return new DurationOrDisabled(Optional.of(Duration.SERIALIZER.deserialize(string))); ++ } ++ throw new SerializationException(obj + "(" + type + ") is not a duration or '" + DISABLE_VALUE + "'"); ++ } ++ ++ @Override ++ protected Object serialize(final DurationOrDisabled item, final Predicate> typeSupported) { ++ return item.value.map(Duration::value).orElse(DISABLE_VALUE); ++ } ++ } ++} diff --git a/src/main/java/io/papermc/paper/configuration/type/EngineMode.java b/src/main/java/io/papermc/paper/configuration/type/EngineMode.java new file mode 100644 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000