Compare commits

..

23 Commits

Author SHA1 Message Date
5a100ef988 feat: inheritance-aware plot limits / permission ranges 2025-05-31 15:28:26 +02:00
711fba0b2a Update junit5 monorepo (#4662)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-30 12:06:20 +00:00
671a27fa6f Update dependency com.diffplug.spotless to v7.0.4 (#4661)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-27 22:39:24 +00:00
03de685dc4 Update dependency net.essentialsx:EssentialsX to v2.21.1 (#4660)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-25 21:59:36 +00:00
f616885206 Update dependency net.luckperms:api to v5.5 (#4659)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-25 10:29:47 +00:00
7da4eb1ab5 fix: correctly parse help messages with prefix (#4652) 2025-05-24 14:39:31 +02:00
629646ab06 fix: cancel structure locate in classic plot worlds (#4653) 2025-05-24 14:39:14 +02:00
74a1a1f954 Update dependency gradle to v8.14.1 (#4658)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-22 17:04:52 +00:00
aa44078018 Back to snapshot for development
Signed-off-by: Alexander Brandes <mc.cache@web.de>
2025-05-18 19:30:24 +02:00
bfbf406418 Release 7.5.3
Signed-off-by: Alexander Brandes <mc.cache@web.de>
2025-05-18 19:27:33 +02:00
2accedf264 fix: Don't use list to proof block is copper. Instead check with name (#4641)
* fix: Don't use list to proof block is copper. Instead check with name

* chore: Code-Cleanup
2025-05-18 14:04:39 +02:00
6ef0d58480 fix: cancel teleport to denied plot (#4650)
* chore: add 1.21.5 to runServer task, download Paper FAWE jar

* fix: don't allow teleport to denied plots
2025-05-18 14:03:51 +02:00
fbf4a638b4 chore/fix: use #getBlock in PlayerBucketEmptyEvent (#4651)
chore/fix: use #getBlock method for block retrieval in PlayerBucketEmptyEvent
2025-05-18 14:03:28 +02:00
9abfa21078 Add 'world' placeholder to status messages (#4632)
Signed-off-by: Alexander Brandes <mc.cache@web.de>
2025-05-17 23:20:03 +02:00
b84599b4b3 Add server brand to debugpaste (#4640)
Signed-off-by: Alexander Brandes <mc.cache@web.de>
2025-05-17 23:18:52 +02:00
058983cdd5 Update dependency com.vanniktech.maven.publish to v0.32.0 (#4648)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-14 19:09:18 +00:00
6ba3694121 Update dependency net.kyori:adventure-platform-bukkit to v4.4.0 (#4646)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-11 02:14:59 +00:00
8e8e31b80e Update dependency org.checkerframework:checker-qual to v3.49.3 (#4643)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-05-02 17:47:32 +00:00
da2e66c1f8 Update adventure to v4.21.0 (#4642)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-30 08:27:55 +00:00
d5d6fcb859 Update dependency gradle to v8.14 (#4638)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-25 12:06:27 +00:00
96f73331f9 Update dependency com.intellectualsites.paster:Paster to v1.1.7 (#4635)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-22 13:07:09 +00:00
020947d90c Update dependency com.intellectualsites.arkitektonika:Arkitektonika-Client to v2.1.4 (#4634)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-22 13:06:42 +00:00
7dbd0bcff8 Update dependency com.intellectualsites.informative-annotations:informative-annotations to v1.6 (#4633)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2025-04-21 21:57:17 +00:00
24 changed files with 506 additions and 177 deletions

View File

@ -252,6 +252,11 @@ public final class BukkitPlatform extends JavaPlugin implements Listener, PlotPl
return Bukkit.getVersion();
}
@Override
public @NonNull String serverBrand() {
return Bukkit.getName();
}
@Override
@SuppressWarnings("deprecation") // Paper deprecation
public void onEnable() {

View File

@ -22,12 +22,20 @@ import com.google.inject.AbstractModule;
import com.google.inject.Provides;
import com.google.inject.Singleton;
import com.plotsquared.bukkit.permissions.BukkitPermissionHandler;
import com.plotsquared.bukkit.permissions.BukkitRangedPermissionResolver;
import com.plotsquared.bukkit.permissions.LuckPermsRangedPermissionResolver;
import com.plotsquared.bukkit.permissions.VaultPermissionHandler;
import com.plotsquared.core.configuration.Settings;
import com.plotsquared.core.permissions.PermissionHandler;
import com.plotsquared.core.permissions.RangedPermissionResolver;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.bukkit.Bukkit;
public class PermissionModule extends AbstractModule {
private static final Logger LOGGER = LogManager.getLogger("PlotSquared/" + PermissionModule.class.getSimpleName());
@Provides
@Singleton
PermissionHandler providePermissionHandler() {
@ -40,4 +48,18 @@ public class PermissionModule extends AbstractModule {
return new BukkitPermissionHandler();
}
@Provides
@Singleton
RangedPermissionResolver provideRangedPermissionResolver() {
if (Settings.Permissions.USE_LUCKPERMS_RANGE_RESOLVER) {
if (Bukkit.getPluginManager().isPluginEnabled("LuckPerms")) {
LOGGER.info("Using experimental LuckPerms ranged permission resolver");
return new LuckPermsRangedPermissionResolver();
}
LOGGER.warn("Enabled LuckPerms ranged permission resolver, but LuckPerms is not installed. " +
"Falling back to default Bukkit ranged permission resolver");
}
return new BukkitRangedPermissionResolver();
}
}

View File

@ -28,7 +28,6 @@ import com.plotsquared.core.plot.flag.implementations.CopperOxideFlag;
import com.plotsquared.core.plot.flag.implementations.MiscInteractFlag;
import com.plotsquared.core.plot.flag.implementations.SculkSensorInteractFlag;
import com.plotsquared.core.util.PlotFlagUtil;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.entity.Entity;
import org.bukkit.entity.Item;
@ -42,31 +41,11 @@ import org.bukkit.event.block.BlockReceiveGameEvent;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
@SuppressWarnings("unused")
public class BlockEventListener117 implements Listener {
private static final Set<Material> COPPER_OXIDIZING = Set.of(
Material.COPPER_BLOCK,
Material.EXPOSED_COPPER,
Material.WEATHERED_COPPER,
Material.OXIDIZED_COPPER,
Material.CUT_COPPER,
Material.EXPOSED_CUT_COPPER,
Material.WEATHERED_CUT_COPPER,
Material.OXIDIZED_CUT_COPPER,
Material.CUT_COPPER_STAIRS,
Material.EXPOSED_CUT_COPPER_STAIRS,
Material.WEATHERED_CUT_COPPER_STAIRS,
Material.OXIDIZED_CUT_COPPER_STAIRS,
Material.CUT_COPPER_SLAB,
Material.EXPOSED_CUT_COPPER_SLAB,
Material.WEATHERED_CUT_COPPER_SLAB,
Material.OXIDIZED_CUT_COPPER_SLAB
);
@Inject
public BlockEventListener117() {
}
@ -184,7 +163,7 @@ public class BlockEventListener117 implements Listener {
if (plot == null) {
return;
}
if (COPPER_OXIDIZING.contains(event.getNewState().getType())) {
if (event.getNewState().getType().name().contains("COPPER")) {
if (!plot.getFlag(CopperOxideFlag.class)) {
plot.debug("Copper could not oxide because copper-oxide = false");
event.setCancelled(true);

View File

@ -29,6 +29,7 @@ import com.destroystokyo.paper.event.player.PlayerLaunchProjectileEvent;
import com.destroystokyo.paper.event.server.AsyncTabCompleteEvent;
import com.google.inject.Inject;
import com.plotsquared.bukkit.util.BukkitUtil;
import com.plotsquared.core.PlotSquared;
import com.plotsquared.core.command.Command;
import com.plotsquared.core.command.MainCommand;
import com.plotsquared.core.configuration.Settings;
@ -38,6 +39,7 @@ import com.plotsquared.core.permissions.Permission;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.Plot;
import com.plotsquared.core.plot.PlotArea;
import com.plotsquared.core.plot.PlotAreaType;
import com.plotsquared.core.plot.flag.FlagContainer;
import com.plotsquared.core.plot.flag.implementations.BeaconEffectsFlag;
import com.plotsquared.core.plot.flag.implementations.DoneFlag;
@ -48,6 +50,7 @@ import com.plotsquared.core.plot.flag.types.BooleanFlag;
import com.plotsquared.core.plot.world.PlotAreaManager;
import com.plotsquared.core.util.PlotFlagUtil;
import io.papermc.paper.event.entity.EntityMoveEvent;
import io.papermc.paper.event.world.StructuresLocateEvent;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
@ -458,6 +461,21 @@ public class PaperListener implements Listener {
}
}
/**
* Don't let the server die when populating cartographers (villager offering maps) in classic plot worlds
* (as those don't generate POIs)
*/
@EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST)
public void onStructuresLocate(StructuresLocateEvent event) {
if (!PlotSquared.get().getPlotAreaManager().hasPlotArea(event.getWorld().getName())) {
return;
}
final PlotArea area = PlotSquared.get().getPlotAreaManager().getPlotAreaByString(event.getWorld().getName());
if (area != null && area.getType() == PlotAreaType.NORMAL) {
event.setCancelled(true);
}
}
private boolean getBooleanFlagValue(
@NonNull FlagContainer container,
@NonNull Class<? extends BooleanFlag<?>> flagClass,

View File

@ -88,10 +88,8 @@ import org.bukkit.Bukkit;
import org.bukkit.FluidCollisionMode;
import org.bukkit.Material;
import org.bukkit.block.Block;
import org.bukkit.block.BlockFace;
import org.bukkit.block.BlockState;
import org.bukkit.block.Sign;
import org.bukkit.block.data.Waterlogged;
import org.bukkit.command.PluginCommand;
import org.bukkit.entity.ArmorStand;
import org.bukkit.entity.Boat;
@ -600,7 +598,11 @@ public class PlayerEventListener implements Listener {
// i.e. untrusted-visit can override deny-teleport
// this is acceptable, because otherwise it wouldn't make sense to have both flags set
if (result || (plot.getFlag(UntrustedVisitFlag.class) && plot.getHomeSynchronous().equals(BukkitUtil.adaptComplete(to)))) {
plotListener.plotEntry(pp, plot);
// returns false if the player is not allowed to enter the plot (if they are denied, for example)
// don't let the move event cancel the entry after teleport, but rather catch and cancel early (#4647)
if (!plotListener.plotEntry(pp, plot)) {
event.setCancelled(true);
}
} else {
pp.sendMessage(
TranslatableCaption.of("deny.no_enter"),
@ -1370,22 +1372,7 @@ public class PlayerEventListener implements Listener {
@EventHandler(priority = EventPriority.HIGHEST, ignoreCancelled = true)
public void onBucketEmpty(PlayerBucketEmptyEvent event) {
BlockFace bf = event.getBlockFace();
// Note: a month after Bukkit 1.14.4 released, they added the API method
// PlayerBucketEmptyEvent#getBlock(), which returns the block the
// bucket contents is going to be placed at. Currently we determine this
// block ourselves to retain compatibility with 1.13.
final Block block;
// if the block can be waterlogged, the event might waterlog the block
// sometimes
if (event.getBlockClicked().getBlockData() instanceof Waterlogged waterlogged
&& !waterlogged.isWaterlogged() && event.getBucket() != Material.LAVA_BUCKET) {
block = event.getBlockClicked();
} else {
block = event.getBlockClicked().getLocation()
.add(bf.getModX(), bf.getModY(), bf.getModZ())
.getBlock();
}
final Block block = event.getBlock();
Location location = BukkitUtil.adapt(block.getLocation());
PlotArea area = location.getPlotArea();
if (area == null) {

View File

@ -0,0 +1,107 @@
/*
* PlotSquared, a land and world management plugin for Minecraft.
* Copyright (C) IntellectualSites <https://intellectualsites.com>
* Copyright (C) IntellectualSites team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.bukkit.permissions;
import com.plotsquared.bukkit.player.BukkitPlayer;
import com.plotsquared.core.permissions.RangedPermissionResolver;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.util.MathMan;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Set;
import java.util.stream.IntStream;
public class BukkitRangedPermissionResolver implements RangedPermissionResolver {
private static boolean CHECK_EFFECTIVE = true;
@Override
public @NonNegative int getPermissionRange(
final @NonNull PlotPlayer<?> generic,
final @NonNull String stub,
final @Nullable String worldContext,
@NonNegative final int range
) {
if (!(generic instanceof BukkitPlayer player)) {
throw new IllegalArgumentException("PlotPlayer is not a BukkitPlayer");
}
if (hasWildcardRange(player, stub, worldContext)) {
return INFINITE_RANGE_VALUE;
}
int max = 0;
if (CHECK_EFFECTIVE) {
boolean hasAny = false;
String stubPlus = stub + ".";
final Set<PermissionAttachmentInfo> effective = player.getPlatformPlayer().getEffectivePermissions();
if (!effective.isEmpty()) {
for (PermissionAttachmentInfo attach : effective) {
// Ignore all "false" permissions
if (!attach.getValue()) {
continue;
}
String permStr = attach.getPermission();
if (permStr.startsWith(stubPlus)) {
hasAny = true;
String end = permStr.substring(stubPlus.length());
if (MathMan.isInteger(end)) {
int val = Integer.parseInt(end);
if (val > range) {
return val;
}
if (val > max) {
max = val;
}
}
}
}
if (hasAny) {
return max;
}
// Workaround
for (PermissionAttachmentInfo attach : effective) {
String permStr = attach.getPermission();
if (permStr.startsWith("plots.") && !permStr.equals("plots.use")) {
return max;
}
}
CHECK_EFFECTIVE = false;
}
}
for (int i = range; i > 0; i--) {
if (player.hasPermission(worldContext, stub + "." + i)) {
return i;
}
}
return max;
}
@Override
public @NonNull IntStream streamFullPermissionRange(
final @NonNull PlotPlayer<?> player,
final @NonNull String stub,
final @Nullable String worldContext,
@NonNegative final int range
) {
return IntStream.of(getPermissionRange(player, stub, worldContext, range));
}
}

View File

@ -0,0 +1,95 @@
/*
* PlotSquared, a land and world management plugin for Minecraft.
* Copyright (C) IntellectualSites <https://intellectualsites.com>
* Copyright (C) IntellectualSites team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.bukkit.permissions;
import com.plotsquared.core.permissions.RangedPermissionResolver;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.util.MathMan;
import net.luckperms.api.LuckPerms;
import net.luckperms.api.context.ImmutableContextSet;
import net.luckperms.api.model.user.User;
import net.luckperms.api.node.NodeType;
import net.luckperms.api.node.types.PermissionNode;
import net.luckperms.api.query.QueryOptions;
import org.bukkit.Bukkit;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.Objects;
import java.util.stream.IntStream;
public class LuckPermsRangedPermissionResolver implements RangedPermissionResolver {
private final LuckPerms luckPerms;
public LuckPermsRangedPermissionResolver() {
this.luckPerms = Objects.requireNonNull(
Bukkit.getServicesManager().getRegistration(LuckPerms.class),
"LuckPerms is not available"
).getProvider();
}
@Override
public @NonNegative int getPermissionRange(
final @NonNull PlotPlayer<?> player,
final @NonNull String stub,
final @Nullable String worldContext,
final @NonNegative int range
) {
// no need to use LuckPerms for basic checks
if (this.hasWildcardRange(player, stub, worldContext)) {
return INFINITE_RANGE_VALUE;
}
return this.streamFullPermissionRange(player, stub, worldContext, range)
.sorted()
.reduce((first, second) -> second)
.orElse(0);
}
@NonNull
public IntStream streamFullPermissionRange(
final @NonNull PlotPlayer<?> player,
final @NonNull String stub,
final @Nullable String worldContext,
@NonNegative final int range
) {
final User user = this.luckPerms.getUserManager().getUser(player.getUUID());
if (user == null) {
throw new IllegalStateException("Luckperms User is null - is the Player online? (UUID: %s)".formatted(player.getUUID()));
}
final QueryOptions queryOptions = worldContext == null ?
QueryOptions.nonContextual() :
QueryOptions.contextual(ImmutableContextSet.of("world", worldContext));
return user.resolveInheritedNodes(queryOptions).stream()
// only support normal permission nodes (regex permission nodes would be a pita to support)
.filter(NodeType.PERMISSION::matches)
.map(node -> ((PermissionNode) node).getPermission())
// check that the node actually has additional data after the stub
.filter(permission -> permission.startsWith(stub + "."))
// extract the raw data after the stub
.map(permission -> permission.substring(stub.length() + 1))
// check if data is integer and parse
.filter(MathMan::isInteger)
.mapToInt(Integer::parseInt)
// only use values that are positive
.filter(value -> value > -1);
}
}

View File

@ -24,14 +24,12 @@ import com.plotsquared.core.PlotSquared;
import com.plotsquared.core.configuration.Settings;
import com.plotsquared.core.events.TeleportCause;
import com.plotsquared.core.location.Location;
import com.plotsquared.core.permissions.Permission;
import com.plotsquared.core.permissions.PermissionHandler;
import com.plotsquared.core.player.ConsolePlayer;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.PlotWeather;
import com.plotsquared.core.plot.world.PlotAreaManager;
import com.plotsquared.core.util.EventDispatcher;
import com.plotsquared.core.util.MathMan;
import com.plotsquared.core.util.WorldUtil;
import com.sk89q.worldedit.bukkit.BukkitAdapter;
import com.sk89q.worldedit.extension.platform.Actor;
@ -47,13 +45,11 @@ import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.EventException;
import org.bukkit.event.player.PlayerTeleportEvent;
import org.bukkit.permissions.PermissionAttachmentInfo;
import org.bukkit.plugin.RegisteredListener;
import org.bukkit.potion.PotionEffectType;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import java.util.Set;
import java.util.UUID;
import static com.sk89q.worldedit.world.gamemode.GameModes.ADVENTURE;
@ -150,77 +146,13 @@ public class BukkitPlayer extends PlotPlayer<Player> {
}
}
@SuppressWarnings("StringSplitter")
@Override
@NonNegative
public int hasPermissionRange(
final @NonNull String stub,
@NonNegative final int range
) {
if (hasPermission(Permission.PERMISSION_ADMIN.toString())) {
return Integer.MAX_VALUE;
}
final String[] nodes = stub.split("\\.");
final StringBuilder n = new StringBuilder();
// Wildcard check from less specific permission to more specific permission
for (int i = 0; i < (nodes.length - 1); i++) {
n.append(nodes[i]).append(".");
if (!stub.equals(n + Permission.PERMISSION_STAR.toString())) {
if (hasPermission(n + Permission.PERMISSION_STAR.toString())) {
return Integer.MAX_VALUE;
}
}
}
// Wildcard check for the full permission
if (hasPermission(stub + ".*")) {
return Integer.MAX_VALUE;
}
// Permission value cache for iterative check
int max = 0;
if (CHECK_EFFECTIVE) {
boolean hasAny = false;
String stubPlus = stub + ".";
final Set<PermissionAttachmentInfo> effective = player.getEffectivePermissions();
if (!effective.isEmpty()) {
for (PermissionAttachmentInfo attach : effective) {
// Ignore all "false" permissions
if (!attach.getValue()) {
continue;
}
String permStr = attach.getPermission();
if (permStr.startsWith(stubPlus)) {
hasAny = true;
String end = permStr.substring(stubPlus.length());
if (MathMan.isInteger(end)) {
int val = Integer.parseInt(end);
if (val > range) {
return val;
}
if (val > max) {
max = val;
}
}
}
}
if (hasAny) {
return max;
}
// Workaround
for (PermissionAttachmentInfo attach : effective) {
String permStr = attach.getPermission();
if (permStr.startsWith("plots.") && !permStr.equals("plots.use")) {
return max;
}
}
CHECK_EFFECTIVE = false;
}
}
for (int i = range; i > 0; i--) {
if (hasPermission(stub + "." + i)) {
return i;
}
}
return max;
return PlotSquared.platform().rangedPermissionResolver().getPermissionRange(this, stub, null, range);
}
@Override

View File

@ -31,6 +31,7 @@ import com.plotsquared.core.generator.IndependentPlotGenerator;
import com.plotsquared.core.inject.annotations.DefaultGenerator;
import com.plotsquared.core.location.World;
import com.plotsquared.core.permissions.PermissionHandler;
import com.plotsquared.core.permissions.RangedPermissionResolver;
import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.plot.expiration.ExpireManager;
import com.plotsquared.core.plot.world.PlotAreaManager;
@ -120,6 +121,14 @@ public interface PlotPlatform<P> extends LocaleHolder {
*/
@NonNull String serverImplementation();
/**
* Gets the server brand name
*
* @return server brand
* @since 7.5.3
*/
@NonNull String serverBrand();
/**
* Gets the native server code package prefix.
*
@ -341,6 +350,10 @@ public interface PlotPlatform<P> extends LocaleHolder {
return injector().getInstance(PermissionHandler.class);
}
default @NonNull RangedPermissionResolver rangedPermissionResolver() {
return injector().getInstance(RangedPermissionResolver.class);
}
/**
* Get the {@link ServicePipeline} implementation
*

View File

@ -24,6 +24,7 @@ import com.plotsquared.core.configuration.caption.TranslatableCaption;
import com.plotsquared.core.player.PlotPlayer;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.NotNull;
@ -98,6 +99,14 @@ public enum CommandCategory implements Caption {
return MiniMessage.miniMessage().deserialize(getComponent(localeHolder));
}
@Override
public @NonNull Component toComponent(
@NonNull final LocaleHolder localeHolder,
final @NonNull TagResolver @NonNull ... tagResolvers
) {
return MiniMessage.miniMessage().deserialize(getComponent(localeHolder));
}
/**
* Checks if a player has access to this command category
*

View File

@ -86,7 +86,8 @@ public class DebugPaste extends SubCommand {
b.append("# WorldEdit implementation:\n");
b.append(PlotSquared.platform().worldEditImplementations()).append("\n\n");
b.append("# Server Information\n");
b.append("Server Version: ").append(PlotSquared.platform().serverImplementation())
b.append("Server Version: ").append(PlotSquared.platform().serverBrand()).append(": ")
.append(PlotSquared.platform().serverImplementation()).append("\n")
.append("\n");
b.append("online_mode: ").append(!Settings.UUID.OFFLINE).append(';')
.append(!Settings.UUID.OFFLINE).append('\n');

View File

@ -113,38 +113,34 @@ public class Help extends Command {
}
if (cat == null && page == 0) {
TextComponent.Builder builder = Component.text();
builder.append(MINI_MESSAGE.deserialize(TranslatableCaption.of("help.help_header").getComponent(player)));
builder.append(TranslatableCaption.of("help.help_header").toComponent(player));
for (CommandCategory c : CommandCategory.values()) {
if (!c.canAccess(player)) {
continue;
}
builder.append(Component.newline()).append(MINI_MESSAGE
.deserialize(
TranslatableCaption.of("help.help_info_item").getComponent(player),
TagResolver.builder()
.tag("command", Tag.inserting(Component.text("/plot help")))
.tag("category", Tag.inserting(Component.text(c.name().toLowerCase())))
.tag("category_desc", Tag.inserting(c.toComponent(player)))
.build()
));
builder.append(Component.newline());
builder.append(TranslatableCaption.of("help.help_info_item").toComponent(
player, TagResolver.builder()
.tag("command", Tag.inserting(Component.text("/plot help")))
.tag("category", Tag.inserting(Component.text(c.name().toLowerCase())))
.tag("category_desc", Tag.inserting(c.toComponent(player)))
.build()
));
}
builder.append(Component.newline()).append(MINI_MESSAGE
.deserialize(
TranslatableCaption.of("help.help_info_item").getComponent(player),
TagResolver.builder()
.tag("command", Tag.inserting(Component.text("/plot help")))
.tag("category", Tag.inserting(Component.text("all")))
.tag(
"category_desc",
Tag.inserting(TranslatableCaption
.of("help.help_display_all_commands")
.toComponent(player))
)
.build()
));
builder.append(Component.newline()).append(MINI_MESSAGE.deserialize(TranslatableCaption
.of("help.help_footer")
.getComponent(player)));
builder.append(Component.newline());
builder.append(TranslatableCaption.of("help.help_info_item").toComponent(
player, TagResolver.builder()
.tag("command", Tag.inserting(Component.text("/plot help")))
.tag("category", Tag.inserting(Component.text("all")))
.tag(
"category_desc", Tag.inserting(TranslatableCaption
.of("help.help_display_all_commands")
.toComponent(player))
)
.build()
));
builder.append(Component.newline());
builder.append(TranslatableCaption.of("help.help_footer").toComponent(player));
player.sendMessage(StaticCaption.of(MINI_MESSAGE.serialize(builder.asComponent())));
return true;
}

View File

@ -525,8 +525,8 @@ public class Settings extends Config {
public static final class Limit {
@Comment("Should the limit be global (over multiple worlds)")
public static boolean GLOBAL =
false;
public static boolean GLOBAL = false;
@Comment({"The max range of integer permissions to check for, e.g. 'plots.plot.127' or 'plots.set.flag.mob-cap.127'",
"The value covers the permission range to check, you need to assign the permission to players/groups still",
"Modifying the value does NOT change the amount of plots players can claim"})
@ -737,6 +737,7 @@ public class Settings extends Config {
@Comment("If \"instabreak\" should consider the used tool.")
public static boolean INSTABREAK_CONSIDER_TOOL = false;
}
@Comment({"Enable or disable parts of the plugin",
@ -830,4 +831,17 @@ public class Settings extends Config {
}
@Comment({"Permission-Resolver specific settings"})
public static final class Permissions {
@Comment({
"use the new LuckPerms resolver for ranged permissions (e.g., plots.plot.<number>).",
"in contrary to the default resolver, this resolver can handle inheritance of permissions.",
"e.g. if the player has multiple matching permissions, for example, due to multiple group memberships " +
"(plots.plot.5 & plots.plot.15), this resolver would use 15 as the limit as opposed to the previous 5."
})
public static boolean USE_LUCKPERMS_RANGE_RESOLVER = false;
}
}

View File

@ -20,6 +20,7 @@ package com.plotsquared.core.configuration.caption;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
@ -44,6 +45,16 @@ public interface Caption {
*/
@NonNull Component toComponent(@NonNull LocaleHolder localeHolder);
/**
* Get the Adventure {@link ComponentLike} for this caption while applying custom {@link TagResolver}
* (apart from the default {@code core.prefix})
* @param localeHolder Local holder
* @param tagResolvers custom tag resolvers to replace placeholders / parameters
* @return {@link ComponentLike}
* @since TODO
*/
@NonNull Component toComponent(@NonNull LocaleHolder localeHolder, @NonNull TagResolver @NonNull... tagResolvers);
@NonNull String toString();
}

View File

@ -21,6 +21,7 @@ package com.plotsquared.core.configuration.caption;
import com.google.common.base.Preconditions;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.checkerframework.checker.nullness.qual.NonNull;
public final class StaticCaption implements Caption {
@ -51,6 +52,14 @@ public final class StaticCaption implements Caption {
return MiniMessage.miniMessage().deserialize(this.value);
}
@Override
public @NonNull Component toComponent(
@NonNull final LocaleHolder localeHolder,
final @NonNull TagResolver @NonNull ... tagResolvers
) {
return MiniMessage.miniMessage().deserialize(this.value, tagResolvers);
}
@Override
public @NonNull String toString() {
return "StaticCaption(" + value + ")";

View File

@ -27,6 +27,7 @@ import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.jetbrains.annotations.NotNull;
import java.util.Arrays;
import java.util.Locale;
import java.util.regex.Pattern;
@ -96,13 +97,23 @@ public final class TranslatableCaption implements NamespacedCaption {
@Override
public @NonNull Component toComponent(@NonNull final LocaleHolder localeHolder) {
return this.toComponent(localeHolder, new TagResolver[0]);
}
@Override
public @NonNull Component toComponent(
@NonNull final LocaleHolder localeHolder,
final @NonNull TagResolver @NonNull ... tagResolvers
) {
if (getKey().equals("core.prefix")) {
return MiniMessage.miniMessage().deserialize(getComponent(localeHolder));
}
return MiniMessage.miniMessage().deserialize(getComponent(localeHolder), TagResolver.resolver(
TagResolver[] finalResolvers = Arrays.copyOf(tagResolvers, tagResolvers.length + 1);
finalResolvers[finalResolvers.length - 1] = TagResolver.resolver(
"prefix",
Tag.inserting(TranslatableCaption.of("core.prefix").toComponent(localeHolder))
));
);
return MiniMessage.miniMessage().deserialize(getComponent(localeHolder), finalResolvers);
}
@Override

View File

@ -0,0 +1,126 @@
/*
* PlotSquared, a land and world management plugin for Minecraft.
* Copyright (C) IntellectualSites <https://intellectualsites.com>
* Copyright (C) IntellectualSites team and contributors
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.plotsquared.core.permissions;
import com.plotsquared.core.player.PlotPlayer;
import org.checkerframework.checker.index.qual.NonNegative;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.checkerframework.checker.nullness.qual.Nullable;
import java.util.stream.IntStream;
/**
* Represents a resolver for ranged permissions. Return values depend on the actual implementation (see Bukkit module).
* <br>
* Even though this interface is not linked to platform implementations by design, implementation-specific details are added to
* the Javadocs.
*
* @since TODO
*/
public interface RangedPermissionResolver {
int INFINITE_RANGE_VALUE = Integer.MAX_VALUE;
/**
* Gets the applicable range value of a player for a specific permission stub
* ({@code plots.plot} would check for {@code plots.plot.<numeric>}).
* <br>
* The standard bukkit implementation would return the lowest numeric value, while the LuckPerms specific resolver would
* try returning the highest possible value.
*
* @param player the permission holder
* @param stub the permission stub to check against
* @param worldContext the optional world context of the action requiring the range
* @param range the maximum permission range to check against (for the default bukkit resolver)
* @return the applicable range value of the player for the given permission stub
* @since TODO
*/
@NonNegative
int getPermissionRange(
final @NonNull PlotPlayer<?> player,
final @NonNull String stub,
final @Nullable String worldContext,
final @NonNegative int range
);
/**
* Gets a stream of all applicable permission range values for the given stub. The stream is unordered by default. If a
* specific order is needed, use the stateful {@link IntStream#sorted()} operation.
* <br>
* The standard bukkit implementation will only return a stream containing a single value equal to
* {@link #getPermissionRange(PlotPlayer, String, String, int)}. For LuckPerms, all applicable node values will be in the
* stream.
*
* @param player the permission holder
* @param stub the permission stub to check against
* @param worldContext the optional world context of the action requiring the range
* @param range the maximum permission range to check against (for the default bukkit resolver)
* @return a stream of all applicable permission range values for the given stub
* @since TODO
*/
@NonNull
IntStream streamFullPermissionRange(
final @NonNull PlotPlayer<?> player,
final @NonNull String stub,
final @Nullable String worldContext,
final @NonNegative int range
);
/**
* Checks if the given player has a wildcard range for the given permission stub.
* <br>
* For example, if checking for the stub {@code plots.plot}, this method would check for:
* <ul>
* <li>{@code *}</li>
* <li>{@code plots.admin}</li>
* <li>{@code plots.plot.*}</li>
* <li>{@code plots.*}</li>
* </ul>
*
* @param player the permission holder
* @param stub the permission stub to check against
* @param worldContext the optional world context of the action requiring the range
* @return {@code true} if the player has a wildcard range for the given permission stub, else {@code false}
* @since TODO
*/
default boolean hasWildcardRange(
final @NonNull PlotPlayer<?> player,
final @NonNull String stub,
final @Nullable String worldContext
) {
if (player.hasPermission(Permission.PERMISSION_STAR) ||
player.hasPermission(Permission.PERMISSION_ADMIN) ||
player.hasPermission(worldContext, stub + ".*")) {
return true;
}
String node = stub;
while (true) {
int lastIndex = node.lastIndexOf('.');
if (lastIndex == -1) {
break;
}
node = node.substring(0, lastIndex);
if (player.hasPermission(worldContext, node + ".*")) {
return true;
}
}
return false;
}
}

View File

@ -25,30 +25,24 @@ import com.plotsquared.core.player.PlotPlayer;
import com.plotsquared.core.util.StringMan;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ComponentLike;
import net.kyori.adventure.text.minimessage.MiniMessage;
import net.kyori.adventure.text.minimessage.tag.Tag;
import net.kyori.adventure.text.minimessage.tag.resolver.TagResolver;
import org.jetbrains.annotations.NotNull;
public class HelpObject implements ComponentLike {
static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage();
private final Component rendered;
public HelpObject(final Command command, final String label, final PlotPlayer<?> audience) {
rendered = MINI_MESSAGE.deserialize(
TranslatableCaption.of("help.help_item").getComponent(audience),
TagResolver.builder()
.tag("usage", Tag.inserting(Component.text(command.getUsage().replace("{label}", label))))
.tag("alias", Tag.inserting(Component.text(
command.getAliases().isEmpty() ? "" : StringMan.join(command.getAliases(), " | ")
)))
.tag("desc", Tag.inserting(command.getDescription().toComponent(audience)))
.tag("arguments", Tag.inserting(Component.text(buildArgumentList(command.getRequiredArguments()))))
.tag("label", Tag.inserting(Component.text(label)))
.build()
);
this.rendered = TranslatableCaption.of("help.help_item").toComponent(audience, TagResolver.builder()
.tag("usage", Tag.inserting(Component.text(command.getUsage().replace("{label}", label))))
.tag("alias", Tag.inserting(Component.text(
command.getAliases().isEmpty() ? "" : StringMan.join(command.getAliases(), " | ")
)))
.tag("desc", Tag.inserting(command.getDescription().toComponent(audience)))
.tag("arguments", Tag.inserting(Component.text(buildArgumentList(command.getRequiredArguments()))))
.tag("label", Tag.inserting(Component.text(label)))
.build());
}
private String buildArgumentList(final Argument<?>[] arguments) {

View File

@ -22,7 +22,7 @@ plugins {
}
group = "com.intellectualsites.plotsquared"
version = "7.5.3-SNAPSHOT"
version = "7.5.4-SNAPSHOT"
if (!File("$rootDir/.git").exists()) {
logger.lifecycle("""
@ -69,8 +69,8 @@ subprojects {
dependencies {
// Tests
testImplementation("org.junit.jupiter:junit-jupiter:5.12.2")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.12.2")
testImplementation("org.junit.jupiter:junit-jupiter:5.13.0")
testRuntimeOnly("org.junit.platform:junit-platform-launcher:1.13.0")
}
plugins.withId("java") {
@ -206,14 +206,14 @@ tasks.getByName<Jar>("jar") {
enabled = false
}
val supportedVersions = listOf("1.19.4", "1.20.6", "1.21.1", "1.21.3", "1.21.4")
val supportedVersions = listOf("1.19.4", "1.20.6", "1.21.1", "1.21.3", "1.21.4", "1.21.5")
tasks {
register("cacheLatestFaweArtifact") {
val lastSuccessfulBuildUrl = uri("https://ci.athion.net/job/FastAsyncWorldEdit/lastSuccessfulBuild/api/json").toURL()
val artifact = ((JsonSlurper().parse(lastSuccessfulBuildUrl) as Map<*, *>)["artifacts"] as List<*>)
.map { it as Map<*, *> }
.map { it["fileName"] as String }
.first { it -> it.contains("Bukkit") }
.first { it -> it.contains("Paper") }
project.ext["faweArtifact"] = artifact
}

View File

@ -3,40 +3,40 @@
paper = "1.20.4-R0.1-SNAPSHOT"
guice = "7.0.0"
spotbugs = "4.9.3"
checkerqual = "3.49.2"
checkerqual = "3.49.3"
gson = "2.10"
guava = "31.1-jre"
snakeyaml = "2.0"
adventure = "4.20.0"
adventure-bukkit = "4.3.4"
adventure = "4.21.0"
adventure-bukkit = "4.4.0"
log4j = "2.19.0"
# Plugins
worldedit = "7.2.20"
fawe = "2.13.0"
placeholderapi = "2.11.6"
luckperms = "5.4"
essentialsx = "2.21.0"
luckperms = "5.5"
essentialsx = "2.21.1"
mvdwapi = "3.1.1"
# Third party
prtree = "2.0.1"
aopalliance = "1.0"
cloud-services = "1.8.4"
arkitektonika = "2.1.3"
arkitektonika = "2.1.4"
squirrelid = "0.3.2"
paster = "1.1.6"
paster = "1.1.7"
bstats = "3.1.0"
paperlib = "1.0.8"
informative-annotations = "1.5"
informative-annotations = "1.6"
vault = "1.7.1"
serverlib = "2.3.7"
# Gradle plugins
shadow = "8.3.6"
grgit = "4.1.1"
spotless = "7.0.3"
publish = "0.31.0"
spotless = "7.0.4"
publish = "0.32.0"
runPaper = "2.3.1"
[libraries]

Binary file not shown.

View File

@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip
networkTimeout=10000
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME

4
gradlew vendored
View File

@ -114,7 +114,7 @@ case "$( uname )" in #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
CLASSPATH="\\\"\\\""
# Determine the Java command to use to start the JVM.
@ -213,7 +213,7 @@ DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
-jar "$APP_HOME/gradle/wrapper/gradle-wrapper.jar" \
"$@"
# Stop when "xargs" is not available.

4
gradlew.bat vendored
View File

@ -70,11 +70,11 @@ goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
set CLASSPATH=
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" -jar "%APP_HOME%\gradle\wrapper\gradle-wrapper.jar" %*
:end
@rem End local scope for the variables with windows NT shell