Use axiom:restrictions packet to control client behaviour

Dieser Commit ist enthalten in:
Moulberry 2024-01-14 17:50:02 +08:00
Ursprung 8fd71e59ba
Commit 8631549a4a
7 geänderte Dateien mit 241 neuen und 21 gelöschten Zeilen

Datei anzeigen

@ -1,11 +1,10 @@
package com.moulberry.axiom; package com.moulberry.axiom;
import com.google.common.util.concurrent.RateLimiter; import com.google.common.util.concurrent.RateLimiter;
import com.mojang.brigadier.StringReader;
import com.moulberry.axiom.buffer.BlockBuffer;
import com.moulberry.axiom.buffer.CompressedBlockEntity; import com.moulberry.axiom.buffer.CompressedBlockEntity;
import com.moulberry.axiom.event.AxiomCreateWorldPropertiesEvent; import com.moulberry.axiom.event.AxiomCreateWorldPropertiesEvent;
import com.moulberry.axiom.event.AxiomModifyWorldEvent; import com.moulberry.axiom.event.AxiomModifyWorldEvent;
import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration;
import com.moulberry.axiom.packet.*; import com.moulberry.axiom.packet.*;
import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry; import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
@ -15,11 +14,8 @@ import io.papermc.paper.event.world.WorldGameRuleChangeEvent;
import io.papermc.paper.network.ChannelInitializeListener; import io.papermc.paper.network.ChannelInitializeListener;
import io.papermc.paper.network.ChannelInitializeListenerHolder; import io.papermc.paper.network.ChannelInitializeListenerHolder;
import net.kyori.adventure.key.Key; import net.kyori.adventure.key.Key;
import net.minecraft.commands.arguments.blocks.BlockPredicateArgument; import net.minecraft.core.BlockPos;
import net.minecraft.commands.arguments.blocks.BlockStateArgument;
import net.minecraft.commands.arguments.blocks.BlockStateParser;
import net.minecraft.core.IdMapper; import net.minecraft.core.IdMapper;
import net.minecraft.core.registries.BuiltInRegistries;
import net.minecraft.network.Connection; import net.minecraft.network.Connection;
import net.minecraft.network.ConnectionProtocol; import net.minecraft.network.ConnectionProtocol;
import net.minecraft.network.FriendlyByteBuf; import net.minecraft.network.FriendlyByteBuf;
@ -27,8 +23,6 @@ import net.minecraft.network.protocol.Packet;
import net.minecraft.network.protocol.PacketFlow; import net.minecraft.network.protocol.PacketFlow;
import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket;
import net.minecraft.server.MinecraftServer; import net.minecraft.server.MinecraftServer;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.block.state.BlockState;
import org.bukkit.*; import org.bukkit.*;
import org.bukkit.configuration.Configuration; import org.bukkit.configuration.Configuration;
@ -43,8 +37,6 @@ import org.jetbrains.annotations.Nullable;
import java.util.*; import java.util.*;
import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
public class AxiomPaper extends JavaPlugin implements Listener { public class AxiomPaper extends JavaPlugin implements Listener {
@ -52,6 +44,7 @@ public class AxiomPaper extends JavaPlugin implements Listener {
public final Set<UUID> activeAxiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>()); public final Set<UUID> activeAxiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>());
public final Map<UUID, RateLimiter> playerBlockBufferRateLimiters = new ConcurrentHashMap<>(); public final Map<UUID, RateLimiter> playerBlockBufferRateLimiters = new ConcurrentHashMap<>();
public final Map<UUID, Restrictions> playerRestrictions = new ConcurrentHashMap<>();
public Configuration configuration; public Configuration configuration;
public IdMapper<BlockState> allowedBlockRegistry = null; public IdMapper<BlockState> allowedBlockRegistry = null;
@ -90,6 +83,7 @@ public class AxiomPaper extends JavaPlugin implements Listener {
msg.registerOutgoingPluginChannel(this, "axiom:register_world_properties"); msg.registerOutgoingPluginChannel(this, "axiom:register_world_properties");
msg.registerOutgoingPluginChannel(this, "axiom:set_world_property"); msg.registerOutgoingPluginChannel(this, "axiom:set_world_property");
msg.registerOutgoingPluginChannel(this, "axiom:ack_world_properties"); msg.registerOutgoingPluginChannel(this, "axiom:ack_world_properties");
msg.registerOutgoingPluginChannel(this, "axiom:restrictions");
if (configuration.getBoolean("packet-handlers.hello")) { if (configuration.getBoolean("packet-handlers.hello")) {
msg.registerIncomingPluginChannel(this, "axiom:hello", new HelloPacketListener(this)); msg.registerIncomingPluginChannel(this, "axiom:hello", new HelloPacketListener(this));
@ -162,6 +156,13 @@ public class AxiomPaper extends JavaPlugin implements Listener {
Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> { Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
HashSet<UUID> stillActiveAxiomPlayers = new HashSet<>(); HashSet<UUID> stillActiveAxiomPlayers = new HashSet<>();
int rateLimit = this.configuration.getInt("block-buffer-rate-limit");
if (rateLimit > 0) {
// Reduce by 20% just to prevent synchronization/timing issues
rateLimit = rateLimit * 8/10;
if (rateLimit <= 0) rateLimit = 1;
}
for (Player player : Bukkit.getServer().getOnlinePlayers()) { for (Player player : Bukkit.getServer().getOnlinePlayers()) {
if (activeAxiomPlayers.contains(player.getUniqueId())) { if (activeAxiomPlayers.contains(player.getUniqueId())) {
if (!player.hasPermission("axiom.*")) { if (!player.hasPermission("axiom.*")) {
@ -171,13 +172,70 @@ public class AxiomPaper extends JavaPlugin implements Listener {
buf.getBytes(0, bytes); buf.getBytes(0, bytes);
player.sendPluginMessage(this, "axiom:enable", bytes); player.sendPluginMessage(this, "axiom:enable", bytes);
} else { } else {
stillActiveAxiomPlayers.add(player.getUniqueId()); UUID uuid = player.getUniqueId();
stillActiveAxiomPlayers.add(uuid);
boolean send = false;
Restrictions restrictions = playerRestrictions.get(uuid);
if (restrictions == null) {
restrictions = new Restrictions();
playerRestrictions.put(uuid, restrictions);
send = true;
}
BlockPos boundsMin = null;
BlockPos boundsMax = null;
if (!player.hasPermission("axiom.allow_copying_other_plots")) {
if (PlotSquaredIntegration.isPlotWorld(player.getWorld())) {
PlotSquaredIntegration.PlotBounds editable = PlotSquaredIntegration.getCurrentEditablePlot(player);
if (editable != null) {
restrictions.lastPlotBounds = editable;
boundsMin = editable.min();
boundsMax = editable.max();
} else if (restrictions.lastPlotBounds != null && restrictions.lastPlotBounds.worldName().equals(player.getWorld().getName())) {
boundsMin = restrictions.lastPlotBounds.min();
boundsMax = restrictions.lastPlotBounds.max();
} else {
boundsMin = BlockPos.ZERO;
boundsMax = BlockPos.ZERO;
}
}
int min = Integer.MIN_VALUE;
int max = Integer.MAX_VALUE;
if (boundsMin != null && boundsMax != null &&
boundsMin.getX() == min && boundsMin.getY() == min && boundsMin.getZ() == min &&
boundsMax.getX() == max && boundsMax.getY() == max && boundsMax.getZ() == max) {
boundsMin = null;
boundsMax = null;
}
}
boolean allowImportingBlocks = player.hasPermission("axiom.can_import_blocks");
if (restrictions.maxSectionsPerSecond != rateLimit ||
restrictions.canImportBlocks != allowImportingBlocks ||
!Objects.equals(restrictions.boundsMin, boundsMin) ||
!Objects.equals(restrictions.boundsMax, boundsMax)) {
restrictions.maxSectionsPerSecond = rateLimit;
restrictions.canImportBlocks = allowImportingBlocks;
restrictions.boundsMin = boundsMin;
restrictions.boundsMax = boundsMax;
send = true;
}
if (send) {
restrictions.send(this, player);
}
} }
} }
} }
activeAxiomPlayers.retainAll(stillActiveAxiomPlayers); activeAxiomPlayers.retainAll(stillActiveAxiomPlayers);
playerBlockBufferRateLimiters.keySet().retainAll(stillActiveAxiomPlayers); playerBlockBufferRateLimiters.keySet().retainAll(stillActiveAxiomPlayers);
playerRestrictions.keySet().retainAll(stillActiveAxiomPlayers);
}, 20, 20); }, 20, 20);
int maxChunkRelightsPerTick = configuration.getInt("max-chunk-relights-per-tick"); int maxChunkRelightsPerTick = configuration.getInt("max-chunk-relights-per-tick");

Datei anzeigen

@ -0,0 +1,64 @@
package com.moulberry.axiom;
import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration;
import io.netty.buffer.Unpooled;
import net.minecraft.core.BlockPos;
import net.minecraft.network.FriendlyByteBuf;
import org.bukkit.entity.Player;
public class Restrictions {
public boolean canImportBlocks = true;
public boolean canUseEditor = true;
public boolean canEditDisplayEntities = true;
public int maxSectionsPerSecond = 0;
public BlockPos boundsMin = null;
public BlockPos boundsMax = null;
public PlotSquaredIntegration.PlotBounds lastPlotBounds = null;
public void send(AxiomPaper plugin, Player player) {
if (player.getListeningPluginChannels().contains("axiom:restrictions")) {
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
buf.writeBoolean(this.canImportBlocks);
buf.writeBoolean(this.canUseEditor);
buf.writeBoolean(this.canEditDisplayEntities);
buf.writeVarInt(this.maxSectionsPerSecond);
if (this.boundsMin == null || this.boundsMax == null) {
buf.writeBoolean(false);
} else {
buf.writeBoolean(true);
int minX = this.boundsMin.getX();
int minY = this.boundsMin.getY();
int minZ = this.boundsMin.getZ();
int maxX = this.boundsMax.getX();
int maxY = this.boundsMax.getY();
int maxZ = this.boundsMax.getZ();
if (minX < -33554431) minX = -33554431;
if (minX > 33554431) minX = 33554431;
if (minY < -2047) minY = -2047;
if (minY > 2047) minY = 2047;
if (minZ < -33554431) minZ = -33554431;
if (minZ > 33554431) minZ = 33554431;
if (maxX < -33554431) maxX = -33554431;
if (maxX > 33554431) maxX = 33554431;
if (maxY < -2047) maxY = -2047;
if (maxY > 2047) maxY = 2047;
if (maxZ < -33554431) maxZ = -33554431;
if (maxZ > 33554431) maxZ = 33554431;
buf.writeBlockPos(new BlockPos(minX, minY, minZ));
buf.writeBlockPos(new BlockPos(maxX, maxY, maxZ));
}
byte[] bytes = new byte[buf.writerIndex()];
buf.getBytes(0, bytes);
player.sendPluginMessage(plugin, "axiom:restrictions", bytes);
}
}
}

Datei anzeigen

@ -2,7 +2,10 @@ package com.moulberry.axiom.integration.plotsquared;
import com.moulberry.axiom.integration.SectionPermissionChecker; import com.moulberry.axiom.integration.SectionPermissionChecker;
import com.sk89q.worldedit.regions.CuboidRegion;
import net.minecraft.core.BlockPos;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.Location;
import org.bukkit.World; import org.bukkit.World;
import org.bukkit.block.Block; import org.bukkit.block.Block;
import org.bukkit.entity.Player; import org.bukkit.entity.Player;
@ -10,6 +13,24 @@ import org.bukkit.entity.Player;
public class PlotSquaredIntegration { public class PlotSquaredIntegration {
public record PlotBounds(BlockPos min, BlockPos max, String worldName) {
public PlotBounds(CuboidRegion cuboidRegion, String worldName) {
this(
new BlockPos(
cuboidRegion.getMinimumPoint().getBlockX(),
cuboidRegion.getMinimumPoint().getBlockY(),
cuboidRegion.getMinimumPoint().getBlockZ()
),
new BlockPos(
cuboidRegion.getMaximumPoint().getBlockX(),
cuboidRegion.getMaximumPoint().getBlockY(),
cuboidRegion.getMaximumPoint().getBlockZ()
),
worldName
);
}
}
public static boolean canBreakBlock(Player player, Block block) { public static boolean canBreakBlock(Player player, Block block) {
if (!Bukkit.getPluginManager().isPluginEnabled("PlotSquared")) { if (!Bukkit.getPluginManager().isPluginEnabled("PlotSquared")) {
return true; return true;
@ -31,6 +52,13 @@ public class PlotSquaredIntegration {
return PlotSquaredIntegrationImpl.isPlotWorld(world); return PlotSquaredIntegrationImpl.isPlotWorld(world);
} }
public static PlotSquaredIntegration.PlotBounds getCurrentEditablePlot(Player player) {
if (!Bukkit.getPluginManager().isPluginEnabled("PlotSquared")) {
return null;
}
return PlotSquaredIntegrationImpl.getCurrentEditablePlot(player);
}
public static SectionPermissionChecker checkSection(Player player, World world, int sectionX, int sectionY, int sectionZ) { public static SectionPermissionChecker checkSection(Player player, World world, int sectionX, int sectionY, int sectionZ) {
if (!Bukkit.getPluginManager().isPluginEnabled("PlotSquared")) { if (!Bukkit.getPluginManager().isPluginEnabled("PlotSquared")) {
return SectionPermissionChecker.ALL_ALLOWED; return SectionPermissionChecker.ALL_ALLOWED;

Datei anzeigen

@ -135,6 +135,52 @@ public class PlotSquaredIntegrationImpl {
return isPlotWorld; return isPlotWorld;
} }
static PlotSquaredIntegration.PlotBounds getCurrentEditablePlot(Player player) {
org.bukkit.Location loc = player.getLocation();
Location location = BukkitUtil.adapt(loc);
PlotArea area = location.getPlotArea();
if (area == null) {
return null;
}
BukkitPlayer pp = BukkitUtil.adapt(player);
Plot plot = area.getPlot(location);
if (plot != null) {
Location bottom = plot.getExtendedBottomAbs();
Location top = plot.getExtendedTopAbs();
CuboidRegion cuboidRegion = new CuboidRegion(bottom.getBlockVector3(), top.getBlockVector3());
// check unowned plots
if (!plot.hasOwner()) {
if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_UNOWNED, false)) {
return null;
} else {
return new PlotSquaredIntegration.PlotBounds(cuboidRegion, player.getWorld().getName());
}
}
// player is breaking another player's plot
if (!plot.isAdded(pp.getUUID())) {
if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER, false)) {
return null;
} else {
return new PlotSquaredIntegration.PlotBounds(cuboidRegion, player.getWorld().getName());
}
}
// plot is 'done'
if (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(plot)) {
if (!pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER, false)) {
return null;
} else {
return new PlotSquaredIntegration.PlotBounds(cuboidRegion, player.getWorld().getName());
}
}
return new PlotSquaredIntegration.PlotBounds(cuboidRegion, player.getWorld().getName());
}
return null;
}
static SectionPermissionChecker checkSection(Player player, World world, int sectionX, int sectionY, int sectionZ) { static SectionPermissionChecker checkSection(Player player, World world, int sectionX, int sectionY, int sectionZ) {
int minX = sectionX * 16; int minX = sectionX * 16;
int minY = sectionY * 16; int minY = sectionY * 16;

Datei anzeigen

@ -77,6 +77,20 @@ public class HelloPacketListener implements PluginMessageListener {
} }
} }
if (!player.getListeningPluginChannels().contains("axiom:restrictions")) {
Component text = Component.text("This server requires the use of Axiom 2.3 or later. Contact the server administrator if you believe this is unintentional");
String unsupportedRestrictions = plugin.configuration.getString("client-doesnt-support-restrictions");
if (unsupportedRestrictions == null) unsupportedRestrictions = "kick";
if (unsupportedRestrictions.equals("warn")) {
player.sendMessage(text.color(NamedTextColor.RED));
return;
} else if (!unsupportedRestrictions.equals("ignore")) {
player.kick(text);
return;
}
}
// Call handshake event // Call handshake event
int maxBufferSize = plugin.configuration.getInt("max-block-buffer-packet-size"); int maxBufferSize = plugin.configuration.getInt("max-block-buffer-packet-size");
AxiomHandshakeEvent handshakeEvent = new AxiomHandshakeEvent(player, maxBufferSize); AxiomHandshakeEvent handshakeEvent = new AxiomHandshakeEvent(player, maxBufferSize);

Datei anzeigen

@ -13,9 +13,12 @@ allow-teleport-between-worlds: true
# Action to take when a user with an incompatible Minecraft version or Axiom version joins # Action to take when a user with an incompatible Minecraft version or Axiom version joins
# Valid actions are 'kick', 'warn' and 'ignore' # Valid actions are 'kick', 'warn' and 'ignore'
# 'warn' will give the player a warning and disable Axiom
# Using 'ignore' may result in corruption and is only provided for debugging purposes # Using 'ignore' may result in corruption and is only provided for debugging purposes
incompatible-data-version: "kick" incompatible-data-version: "warn"
unsupported-axiom-version: "kick" unsupported-axiom-version: "warn"
client-doesnt-support-restrictions: "ignore"
# Maximum packet size. Must not be less than 32767 # Maximum packet size. Must not be less than 32767
max-block-buffer-packet-size: 0x100000 max-block-buffer-packet-size: 0x100000
@ -36,13 +39,13 @@ log-large-block-buffer-changes: false
# Whitelist entities that can be spawned/manipulated/deleted by the client # Whitelist entities that can be spawned/manipulated/deleted by the client
whitelist-entities: whitelist-entities:
- "minecraft:item_display" # - "minecraft:item_display"
- "minecraft:block_display" # - "minecraft:block_display"
- "minecraft:text_display" # - "minecraft:text_display"
- "minecraft:painting" # - "minecraft:painting"
- "minecraft:armor_stand" # - "minecraft:armor_stand"
- "minecraft:item_frame" # - "minecraft:item_frame"
- "minecraft:glow_item_frame" # - "minecraft:glow_item_frame"
# Blacklist entities that can be spawned/manipulated/deleted by the client # Blacklist entities that can be spawned/manipulated/deleted by the client
blacklist-entities: blacklist-entities:

Datei anzeigen

@ -19,3 +19,10 @@ permissions:
description: Allows entity manipulation description: Allows entity manipulation
axiom.entity.delete: axiom.entity.delete:
description: Allows entity deletion description: Allows entity deletion
axiom.allow_copying_other_plots:
description: This permission allows users to copy other user's plots
default: true
axiom.can_import_blocks:
description: Allows players to import schematics/blueprints into Axiom
default: true