diff --git a/src/main/java/com/moulberry/axiom/AxiomPaper.java b/src/main/java/com/moulberry/axiom/AxiomPaper.java index c2de0a0..880199f 100644 --- a/src/main/java/com/moulberry/axiom/AxiomPaper.java +++ b/src/main/java/com/moulberry/axiom/AxiomPaper.java @@ -19,7 +19,10 @@ import net.minecraft.network.protocol.Packet; import net.minecraft.network.protocol.PacketFlow; import net.minecraft.network.protocol.common.ServerboundCustomPayloadPacket; import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.MinecraftServer; import org.bukkit.*; +import org.bukkit.configuration.Configuration; +import org.bukkit.configuration.file.FileConfiguration; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.Listener; @@ -37,11 +40,23 @@ public class AxiomPaper extends JavaPlugin implements Listener { public static AxiomPaper PLUGIN; // tsk tsk tsk public final Set activeAxiomPlayers = Collections.newSetFromMap(new ConcurrentHashMap<>()); + public Configuration configuration; @Override public void onEnable() { PLUGIN = this; + this.saveDefaultConfig(); + configuration = this.getConfig(); + + Set validResolutions = Set.of("kick", "warn", "ignore"); + if (!validResolutions.contains(configuration.getString("incompatible-data-version"))) { + this.getLogger().warning("Invalid value for incompatible-data-version, expected 'kick', 'warn' or 'ignore'"); + } + if (!validResolutions.contains(configuration.getString("unsupported-axiom-version"))) { + this.getLogger().warning("Invalid value for unsupported-axiom-version, expected 'kick', 'warn' or 'ignore'"); + } + Bukkit.getPluginManager().registerEvents(this, this); // Bukkit.getPluginManager().registerEvents(new WorldPropertiesExample(), this); CompressedBlockEntity.initialize(this); @@ -56,40 +71,64 @@ public class AxiomPaper extends JavaPlugin implements Listener { msg.registerOutgoingPluginChannel(this, "axiom:set_world_property"); msg.registerOutgoingPluginChannel(this, "axiom:ack_world_properties"); - msg.registerIncomingPluginChannel(this, "axiom:hello", new HelloPacketListener(this, activeAxiomPlayers)); - msg.registerIncomingPluginChannel(this, "axiom:set_gamemode", new SetGamemodePacketListener()); - msg.registerIncomingPluginChannel(this, "axiom:set_fly_speed", new SetFlySpeedPacketListener()); - msg.registerIncomingPluginChannel(this, "axiom:set_world_time", new SetTimePacketListener()); - msg.registerIncomingPluginChannel(this, "axiom:set_world_property", new SetWorldPropertyListener()); - msg.registerIncomingPluginChannel(this, "axiom:set_block", new SetBlockPacketListener(this)); - msg.registerIncomingPluginChannel(this, "axiom:set_hotbar_slot", new SetHotbarSlotPacketListener()); - msg.registerIncomingPluginChannel(this, "axiom:switch_active_hotbar", new SwitchActiveHotbarPacketListener()); - msg.registerIncomingPluginChannel(this, "axiom:teleport", new TeleportPacketListener()); - msg.registerIncomingPluginChannel(this, "axiom:set_editor_views", new SetEditorViewsPacketListener()); - msg.registerIncomingPluginChannel(this, "axiom:request_chunk_data", new RequestChunkDataPacketListener(this)); + if (configuration.getBoolean("packet-handlers.hello")) { + msg.registerIncomingPluginChannel(this, "axiom:hello", new HelloPacketListener(this, activeAxiomPlayers)); + } + if (configuration.getBoolean("packet-handlers.set-gamemode")) { + msg.registerIncomingPluginChannel(this, "axiom:set_gamemode", new SetGamemodePacketListener(this)); + } + if (configuration.getBoolean("packet-handlers.set-fly-speed")) { + msg.registerIncomingPluginChannel(this, "axiom:set_fly_speed", new SetFlySpeedPacketListener(this)); + } + if (configuration.getBoolean("packet-handlers.set-world-time")) { + msg.registerIncomingPluginChannel(this, "axiom:set_world_time", new SetTimePacketListener(this)); + } + if (configuration.getBoolean("packet-handlers.set-world-property")) { + msg.registerIncomingPluginChannel(this, "axiom:set_world_property", new SetWorldPropertyListener(this)); + } + if (configuration.getBoolean("packet-handlers.set-single-block")) { + msg.registerIncomingPluginChannel(this, "axiom:set_block", new SetBlockPacketListener(this)); + } + if (configuration.getBoolean("packet-handlers.set-hotbar-slot")) { + msg.registerIncomingPluginChannel(this, "axiom:set_hotbar_slot", new SetHotbarSlotPacketListener(this)); + } + if (configuration.getBoolean("packet-handlers.switch-active-hotbar")) { + msg.registerIncomingPluginChannel(this, "axiom:switch_active_hotbar", new SwitchActiveHotbarPacketListener(this)); + } + if (configuration.getBoolean("packet-handlers.teleport")) { + msg.registerIncomingPluginChannel(this, "axiom:teleport", new TeleportPacketListener(this)); + } + if (configuration.getBoolean("packet-handlers.set-editor-views")) { + msg.registerIncomingPluginChannel(this, "axiom:set_editor_views", new SetEditorViewsPacketListener(this)); + } + if (configuration.getBoolean("packet-handlers.request-chunk-data")) { + msg.registerIncomingPluginChannel(this, "axiom:request_chunk_data", new RequestChunkDataPacketListener(this)); + } - SetBlockBufferPacketListener setBlockBufferPacketListener = new SetBlockBufferPacketListener(this); + if (configuration.getBoolean("packet-handlers.set-buffer")) { + SetBlockBufferPacketListener setBlockBufferPacketListener = new SetBlockBufferPacketListener(this); - ChannelInitializeListenerHolder.addListener(Key.key("axiom:handle_big_payload"), new ChannelInitializeListener() { - @Override - public void afterInitChannel(@NonNull Channel channel) { - var packets = ConnectionProtocol.PLAY.getPacketsByIds(PacketFlow.SERVERBOUND); - int payloadId = -1; - for (Map.Entry>> entry : packets.entrySet()) { - if (entry.getValue() == ServerboundCustomPayloadPacket.class) { - payloadId = entry.getKey(); - break; + ChannelInitializeListenerHolder.addListener(Key.key("axiom:handle_big_payload"), new ChannelInitializeListener() { + @Override + public void afterInitChannel(@NonNull Channel channel) { + var packets = ConnectionProtocol.PLAY.getPacketsByIds(PacketFlow.SERVERBOUND); + int payloadId = -1; + for (Map.Entry>> entry : packets.entrySet()) { + if (entry.getValue() == ServerboundCustomPayloadPacket.class) { + payloadId = entry.getKey(); + break; + } + } + if (payloadId < 0) { + throw new RuntimeException("Failed to find ServerboundCustomPayloadPacket id"); } - } - if (payloadId < 0) { - throw new RuntimeException("Failed to find ServerboundCustomPayloadPacket id"); - } - Connection connection = (Connection) channel.pipeline().get("packet_handler"); - channel.pipeline().addBefore("decoder", "axiom-big-payload-handler", - new AxiomBigPayloadHandler(payloadId, connection, setBlockBufferPacketListener)); - } - }); + Connection connection = (Connection) channel.pipeline().get("packet_handler"); + channel.pipeline().addBefore("decoder", "axiom-big-payload-handler", + new AxiomBigPayloadHandler(payloadId, connection, setBlockBufferPacketListener)); + } + }); + } Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> { HashSet newActiveAxiomPlayers = new HashSet<>(); @@ -111,6 +150,17 @@ public class AxiomPaper extends JavaPlugin implements Listener { activeAxiomPlayers.clear(); activeAxiomPlayers.addAll(newActiveAxiomPlayers); }, 20, 20); + + int maxChunkRelightsPerTick = configuration.getInt("max-chunk-relights-per-tick"); + int maxChunkSendsPerTick = configuration.getInt("max-chunk-sends-per-tick"); + + Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> { + WorldExtension.tick(MinecraftServer.getServer(), maxChunkRelightsPerTick, maxChunkSendsPerTick); + }, 1, 1); + } + + public boolean canUseAxiom(Player player) { + return player.hasPermission("axiom.*") && activeAxiomPlayers.contains(player.getUniqueId()); } private final WeakHashMap worldProperties = new WeakHashMap<>(); diff --git a/src/main/java/com/moulberry/axiom/WorldExtension.java b/src/main/java/com/moulberry/axiom/WorldExtension.java new file mode 100644 index 0000000..e18c2b8 --- /dev/null +++ b/src/main/java/com/moulberry/axiom/WorldExtension.java @@ -0,0 +1,105 @@ +package com.moulberry.axiom; + +import it.unimi.dsi.fastutil.longs.*; +import net.minecraft.network.protocol.game.ClientboundLevelChunkWithLightPacket; +import net.minecraft.resources.ResourceKey; +import net.minecraft.server.MinecraftServer; +import net.minecraft.server.level.ChunkMap; +import net.minecraft.server.level.ServerLevel; +import net.minecraft.server.level.ServerPlayer; +import net.minecraft.world.level.ChunkPos; +import net.minecraft.world.level.Level; +import net.minecraft.world.level.chunk.LevelChunk; + +import java.util.*; + +public class WorldExtension { + + private static final Map, WorldExtension> extensions = new HashMap<>(); + + public static WorldExtension get(ServerLevel serverLevel) { + WorldExtension extension = extensions.computeIfAbsent(serverLevel.dimension(), k -> new WorldExtension()); + extension.level = serverLevel; + return extension; + } + + public static void tick(MinecraftServer server, int maxChunkRelightsPerTick, int maxChunkSendsPerTick) { + extensions.keySet().retainAll(server.levelKeys()); + + for (ServerLevel level : server.getAllLevels()) { + WorldExtension extension = extensions.get(level.dimension()); + if (extension != null) { + extension.level = level; + extension.tick(maxChunkRelightsPerTick, maxChunkSendsPerTick); + } + } + } + + private ServerLevel level; + + private final LongSet pendingChunksToSend = new LongOpenHashSet(); + private final LongSet pendingChunksToLight = new LongOpenHashSet(); + + public void sendChunk(int cx, int cz) { + this.pendingChunksToSend.add(ChunkPos.asLong(cx, cz)); + } + + public void lightChunk(int cx, int cz) { + this.pendingChunksToLight.add(ChunkPos.asLong(cx, cz)); + } + + public void tick(int maxChunkRelightsPerTick, int maxChunkSendsPerTick) { + ChunkMap chunkMap = this.level.getChunkSource().chunkMap; + + boolean sendAll = maxChunkSendsPerTick <= 0; + + // Send chunks + LongIterator longIterator = this.pendingChunksToSend.longIterator(); + while (longIterator.hasNext()) { + ChunkPos chunkPos = new ChunkPos(longIterator.nextLong()); + List players = chunkMap.getPlayers(chunkPos, false); + if (players.isEmpty()) continue; + + LevelChunk chunk = this.level.getChunk(chunkPos.x, chunkPos.z); + var packet = new ClientboundLevelChunkWithLightPacket(chunk, this.level.getLightEngine(), null, null, false); + for (ServerPlayer player : players) { + player.connection.send(packet); + } + + if (!sendAll) { + longIterator.remove(); + + maxChunkSendsPerTick -= 1; + if (maxChunkSendsPerTick <= 0) { + break; + } + } + } + if (sendAll) { + this.pendingChunksToSend.clear(); + } + + // Relight chunks + Set chunkSet = new HashSet<>(); + longIterator = this.pendingChunksToLight.longIterator(); + if (maxChunkRelightsPerTick <= 0) { + while (longIterator.hasNext()) { + chunkSet.add(new ChunkPos(longIterator.nextLong())); + } + this.pendingChunksToLight.clear(); + } else { + while (longIterator.hasNext()) { + chunkSet.add(new ChunkPos(longIterator.nextLong())); + longIterator.remove(); + + maxChunkRelightsPerTick -= 1; + if (maxChunkRelightsPerTick <= 0) { + break; + } + } + } + + this.level.getChunkSource().getLightEngine().relight(chunkSet, pos -> {}, count -> {}); + } + +} diff --git a/src/main/java/com/moulberry/axiom/event/AxiomHandshakeEvent.java b/src/main/java/com/moulberry/axiom/event/AxiomHandshakeEvent.java index 735c762..749ec91 100644 --- a/src/main/java/com/moulberry/axiom/event/AxiomHandshakeEvent.java +++ b/src/main/java/com/moulberry/axiom/event/AxiomHandshakeEvent.java @@ -12,10 +12,11 @@ public class AxiomHandshakeEvent extends Event implements Cancellable { private final Player player; private boolean cancelled = false; - private int maxBufferSize = 0x100000; + private int maxBufferSize; - public AxiomHandshakeEvent(Player player) { + public AxiomHandshakeEvent(Player player, int maxBufferSize) { this.player = player; + this.maxBufferSize = maxBufferSize; } public Player getPlayer() { diff --git a/src/main/java/com/moulberry/axiom/integration/RegionProtection.java b/src/main/java/com/moulberry/axiom/integration/RegionProtection.java deleted file mode 100644 index cef48d9..0000000 --- a/src/main/java/com/moulberry/axiom/integration/RegionProtection.java +++ /dev/null @@ -1,39 +0,0 @@ -package com.moulberry.axiom.integration; - -import org.bukkit.Bukkit; -import org.bukkit.World; -import org.bukkit.entity.Player; - -import java.util.ArrayList; -import java.util.List; - -public class RegionProtection { - - private final RegionProtectionWorldGuard worldGuard; - - public RegionProtection(Player player, World world) { - if (Bukkit.getPluginManager().isPluginEnabled("WorldGuard")) { - this.worldGuard = RegionProtectionWorldGuard.tryCreate(player, world); - } else { - this.worldGuard = null; - } - } - - public SectionProtection getSection(int cx, int cy, int cz) { - List protections = new ArrayList<>(); - if (this.worldGuard != null) { - return this.worldGuard.getSection(cx, cy, cz); - } - // todo: PlotSquared - return SectionProtection.ALLOW; - } - - public boolean canBuild(int x, int y, int z) { - if (this.worldGuard != null && !this.worldGuard.canBuild(x, y, z)) return false; - // todo: PlotSquared - return true; - } - - - -} diff --git a/src/main/java/com/moulberry/axiom/integration/RegionProtectionWorldGuard.java b/src/main/java/com/moulberry/axiom/integration/RegionProtectionWorldGuard.java deleted file mode 100644 index 5158140..0000000 --- a/src/main/java/com/moulberry/axiom/integration/RegionProtectionWorldGuard.java +++ /dev/null @@ -1,183 +0,0 @@ -package com.moulberry.axiom.integration; - -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Sets; -import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldguard.LocalPlayer; -import com.sk89q.worldguard.WorldGuard; -import com.sk89q.worldguard.bukkit.WorldGuardPlugin; -import com.sk89q.worldguard.internal.platform.WorldGuardPlatform; -import com.sk89q.worldguard.protection.ApplicableRegionSet; -import com.sk89q.worldguard.protection.FlagValueCalculator; -import com.sk89q.worldguard.protection.association.RegionAssociable; -import com.sk89q.worldguard.protection.flags.Flags; -import com.sk89q.worldguard.protection.flags.RegionGroup; -import com.sk89q.worldguard.protection.flags.StateFlag; -import com.sk89q.worldguard.protection.managers.RegionManager; -import com.sk89q.worldguard.protection.regions.*; -import org.bukkit.World; -import org.bukkit.entity.Player; -import org.jetbrains.annotations.Nullable; - -import java.util.*; - -import static com.google.common.base.Preconditions.checkNotNull; - -public class RegionProtectionWorldGuard { - - private final LocalPlayer player; - private final RegionManager regionManager; - - public RegionProtectionWorldGuard(LocalPlayer player, RegionManager regionManager) { - this.player = player; - this.regionManager = regionManager; - } - - @Nullable - public static RegionProtectionWorldGuard tryCreate(Player player, World world) { - WorldGuardPlatform platform = WorldGuard.getInstance().getPlatform(); - - RegionContainer regionContainer = platform.getRegionContainer(); - if (regionContainer == null) return null; - - com.sk89q.worldedit.world.World worldEditWorld = BukkitAdapter.adapt(world); - LocalPlayer worldGuardPlayer = WorldGuardPlugin.inst().wrapPlayer(player); - - // Don't do any protection if player has bypass - if (platform.getSessionManager().hasBypass(worldGuardPlayer, worldEditWorld)) { - // todo: enable bypass - return null; - } - - RegionManager regionManager = regionContainer.get(worldEditWorld); - if (regionManager == null) return null; - - return new RegionProtectionWorldGuard(worldGuardPlayer, regionManager); - } - - public SectionProtection getSection(int cx, int cy, int cz) { - BlockVector3 min = BlockVector3.at(cx*16, cy*16, cz*16); - BlockVector3 max = BlockVector3.at(cx*16+15, cy*16+15, cz*16+15); - ProtectedRegion test = new ProtectedCuboidRegion("dummy", min, max); - ApplicableRegionSet regions = this.regionManager.getApplicableRegions(test, RegionQuery.QueryOption.COMPUTE_PARENTS); - - int minimumPriority = Integer.MIN_VALUE; - - Map consideredValues = new HashMap<>(); - Set ignoredParents = new HashSet<>(); - - for (ProtectedRegion region : regions) { - int priority = FlagValueCalculator.getPriorityOf(region); - - // todo: this logic doesn't work for us in determining ALLOW, DENY, CHECK - if (priority < minimumPriority) { - break; - } - - // todo: have to keep track of 2 booleans: partialAllow & partialDeny - - if (ignoredParents.contains(region)) { - continue; - } - - StateFlag.State value = FlagValueCalculator.getEffectiveFlagOf(region, Flags.BUILD, this.player); - if (value != null) { - minimumPriority = priority; - consideredValues.put(region, value); - } - - addParents(ignoredParents, region); - - // The BUILD flag is implicitly set on every region where - // PASSTHROUGH is not set to ALLOW - if (minimumPriority != priority && Flags.BUILD.implicitlySetWithMembership() && - FlagValueCalculator.getEffectiveFlagOf(region, Flags.PASSTHROUGH, this.player) != StateFlag.State.ALLOW) { - minimumPriority = priority; - } - } - - if (consideredValues.isEmpty()) { - if (Flags.BUILD.usesMembershipAsDefault()) { - // todo -// switch (getMembership(subject)) { -// case FAIL: -// return ImmutableList.of(); -// case SUCCESS: -// return (Collection) ImmutableList.of(StateFlag.State.ALLOW); -// } - } - - // System.out.println("returning default"); - StateFlag.State fallback = Flags.BUILD.getDefault(); - return fallback == StateFlag.State.DENY ? SectionProtection.DENY : SectionProtection.ALLOW; - } - - boolean hasPartialDeny = false; - for (Map.Entry entry : consideredValues.entrySet()) { - ProtectedRegion region = entry.getKey(); - if (entry.getValue() == StateFlag.State.DENY) { - // System.out.println("found region with deny!"); - if (region instanceof GlobalProtectedRegion) { - return SectionProtection.DENY; - } else if (region instanceof ProtectedCuboidRegion && doesRegionCompletelyContainSection(region, cx, cy, cz)) { - return SectionProtection.DENY; - } - hasPartialDeny = true; - } - } - - if (hasPartialDeny) { - // System.out.println("returning check!"); - return new SectionProtection() { - @Override - public SectionState getSectionState() { - return SectionState.CHECK; - } - - @Override - public boolean check(int wx, int wy, int wz) { - return true; - } - }; - // return complex thing - } - - // System.out.println("returning allow!"); - return SectionProtection.ALLOW; - } - - private boolean doesRegionCompletelyContainSection(ProtectedRegion region, int cx, int cy, int cz) { - BlockVector3 regionMin = region.getMinimumPoint(); - - if (regionMin.getBlockX() > cx*16) return false; - if (regionMin.getBlockY() > cy*16) return false; - if (regionMin.getBlockZ() > cz*16) return false; - - BlockVector3 regionMax = region.getMaximumPoint(); - - if (regionMax.getBlockX() < cx*16+15) return false; - if (regionMax.getBlockY() < cy*16+15) return false; - if (regionMax.getBlockZ() < cz*16+15) return false; - - return true; - } - - private void addParents(Set ignored, ProtectedRegion region) { - ProtectedRegion parent = region.getParent(); - - while (parent != null) { - ignored.add(parent); - parent = parent.getParent(); - } - } - - public boolean canBuild(int x, int y, int z) { - return this.regionManager.getApplicableRegions(BlockVector3.at(x, y, z)).testState(this.player, Flags.BUILD); - } - - public boolean isAllowed(LocalPlayer player, ProtectedRegion protectedRegion) { - return protectedRegion.isOwner(player) || protectedRegion.isMember(player); - } - -} diff --git a/src/main/java/com/moulberry/axiom/integration/SectionProtection.java b/src/main/java/com/moulberry/axiom/integration/SectionProtection.java deleted file mode 100644 index 07b8d60..0000000 --- a/src/main/java/com/moulberry/axiom/integration/SectionProtection.java +++ /dev/null @@ -1,38 +0,0 @@ -package com.moulberry.axiom.integration; - -public interface SectionProtection { - - SectionProtection ALLOW = new SectionProtection() { - @Override - public SectionState getSectionState() { - return SectionState.ALLOW; - } - - @Override - public boolean check(int wx, int wy, int wz) { - return true; - } - }; - - SectionProtection DENY = new SectionProtection() { - @Override - public SectionState getSectionState() { - return SectionState.DENY; - } - - @Override - public boolean check(int wx, int wy, int wz) { - return false; - } - }; - - enum SectionState { - ALLOW, - DENY, - CHECK - } - - SectionState getSectionState(); - boolean check(int wx, int wy, int wz); - -} diff --git a/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java b/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java index 6b6101e..9a7a261 100644 --- a/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/HelloPacketListener.java @@ -9,6 +9,7 @@ import com.moulberry.axiom.persistence.UUIDDataType; import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry; import io.netty.buffer.Unpooled; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.format.NamedTextColor; import net.minecraft.SharedConstants; import net.minecraft.network.FriendlyByteBuf; import org.bukkit.Bukkit; @@ -48,19 +49,38 @@ public class HelloPacketListener implements PluginMessageListener { int serverDataVersion = SharedConstants.getCurrentVersion().getDataVersion().getVersion(); if (dataVersion != serverDataVersion) { - player.kick(Component.text("Axiom: Incompatible data version detected (client " + dataVersion + - ", server " + serverDataVersion + "), are you using ViaVersion?")); - return; + Component text = Component.text("Axiom: Incompatible data version detected (client " + dataVersion + + ", server " + serverDataVersion + "), are you using ViaVersion?"); + + String incompatibleDataVersion = plugin.configuration.getString("incompatible-data-version"); + if (incompatibleDataVersion == null) incompatibleDataVersion = "kick"; + if (incompatibleDataVersion.equals("warn")) { + player.sendMessage(text.color(NamedTextColor.RED)); + return; + } else if (!incompatibleDataVersion.equals("ignore")) { + player.kick(text); + return; + } } if (apiVersion != AxiomConstants.API_VERSION) { - player.kick(Component.text("Unsupported Axiom API Version. Server supports " + AxiomConstants.API_VERSION + - ", while client is " + apiVersion)); - return; + Component text = Component.text("Unsupported Axiom API Version. Server supports " + AxiomConstants.API_VERSION + + ", while client is " + apiVersion); + + String unsupportedAxiomVersion = plugin.configuration.getString("unsupported-axiom-version"); + if (unsupportedAxiomVersion == null) unsupportedAxiomVersion = "kick"; + if (unsupportedAxiomVersion.equals("warn")) { + player.sendMessage(text.color(NamedTextColor.RED)); + return; + } else if (!unsupportedAxiomVersion.equals("ignore")) { + player.kick(text); + return; + } } // Call handshake event - AxiomHandshakeEvent handshakeEvent = new AxiomHandshakeEvent(player); + int maxBufferSize = plugin.configuration.getInt("max-block-buffer-packet-size"); + AxiomHandshakeEvent handshakeEvent = new AxiomHandshakeEvent(player, maxBufferSize); Bukkit.getPluginManager().callEvent(handshakeEvent); if (handshakeEvent.isCancelled()) { return; diff --git a/src/main/java/com/moulberry/axiom/packet/RequestChunkDataPacketListener.java b/src/main/java/com/moulberry/axiom/packet/RequestChunkDataPacketListener.java index 4f83a2a..db92663 100644 --- a/src/main/java/com/moulberry/axiom/packet/RequestChunkDataPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/RequestChunkDataPacketListener.java @@ -3,6 +3,7 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.AxiomConstants; import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.buffer.CompressedBlockEntity; +import com.moulberry.axiom.event.AxiomModifyWorldEvent; import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.longs.*; import net.minecraft.core.BlockPos; @@ -21,6 +22,7 @@ import net.minecraft.world.level.block.state.BlockState; import net.minecraft.world.level.chunk.LevelChunk; import net.minecraft.world.level.chunk.LevelChunkSection; import net.minecraft.world.level.chunk.PalettedContainer; +import org.bukkit.Bukkit; import org.bukkit.craftbukkit.v1_20_R2.entity.CraftPlayer; import org.bukkit.entity.Player; import org.bukkit.plugin.messaging.PluginMessageListener; @@ -33,7 +35,6 @@ public class RequestChunkDataPacketListener implements PluginMessageListener { private static final ResourceLocation RESPONSE_ID = new ResourceLocation("axiom:response_chunk_data"); private final AxiomPaper plugin; - public RequestChunkDataPacketListener(AxiomPaper plugin) { this.plugin = plugin; } @@ -44,12 +45,20 @@ public class RequestChunkDataPacketListener implements PluginMessageListener { FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message)); long id = friendlyByteBuf.readLong(); - if (!bukkitPlayer.hasPermission("axiom.*")) { + if (!this.plugin.canUseAxiom(bukkitPlayer)) { // We always send an 'empty' response in order to make the client happy sendEmptyResponse(player, id); return; } + // Call AxiomModifyWorldEvent event + AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(bukkitPlayer, bukkitPlayer.getWorld()); + Bukkit.getPluginManager().callEvent(modifyWorldEvent); + if (modifyWorldEvent.isCancelled()) { + sendEmptyResponse(player, id); + return; + } + MinecraftServer server = player.getServer(); if (server == null) { sendEmptyResponse(player, id); diff --git a/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java index c7883ac..9733e53 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockBufferPacketListener.java @@ -1,27 +1,11 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.AxiomPaper; +import com.moulberry.axiom.WorldExtension; import com.moulberry.axiom.buffer.BiomeBuffer; import com.moulberry.axiom.buffer.BlockBuffer; import com.moulberry.axiom.buffer.CompressedBlockEntity; import com.moulberry.axiom.event.AxiomModifyWorldEvent; -import com.moulberry.axiom.integration.RegionProtection; -import com.moulberry.axiom.integration.SectionProtection; -import com.sk89q.worldedit.bukkit.BukkitAdapter; -import com.sk89q.worldedit.math.BlockVector3; -import com.sk89q.worldedit.world.World; -import com.sk89q.worldguard.LocalPlayer; -import com.sk89q.worldguard.WorldGuard; -import com.sk89q.worldguard.bukkit.WorldGuardPlugin; -import com.sk89q.worldguard.protection.ApplicableRegionSet; -import com.sk89q.worldguard.protection.flags.Flags; -import com.sk89q.worldguard.protection.flags.StateFlag; -import com.sk89q.worldguard.protection.managers.RegionManager; -import com.sk89q.worldguard.protection.regions.ProtectedCuboidRegion; -import com.sk89q.worldguard.protection.regions.ProtectedRegion; -import com.sk89q.worldguard.protection.regions.RegionContainer; -import com.sk89q.worldguard.protection.regions.RegionQuery; -import io.netty.buffer.Unpooled; import it.unimi.dsi.fastutil.longs.Long2ObjectMap; import it.unimi.dsi.fastutil.shorts.Short2ObjectMap; import net.minecraft.core.BlockPos; @@ -41,7 +25,6 @@ import net.minecraft.world.level.ChunkPos; import net.minecraft.world.level.Level; import net.minecraft.world.level.biome.Biome; import net.minecraft.world.level.block.Block; -import net.minecraft.world.level.block.Blocks; import net.minecraft.world.level.block.EntityBlock; import net.minecraft.world.level.block.entity.BlockEntity; import net.minecraft.world.level.block.state.BlockState; @@ -96,7 +79,7 @@ public class SetBlockBufferPacketListener { applyBlockBuffer(player, server, buffer, worldKey); } else if (type == 1) { BiomeBuffer buffer = BiomeBuffer.load(friendlyByteBuf); - applyBiomeBuffer(server, buffer, worldKey); + applyBiomeBuffer(player, server, buffer, worldKey); } else { throw new RuntimeException("Unknown buffer type: " + type); } @@ -109,17 +92,18 @@ public class SetBlockBufferPacketListener { ServerLevel world = server.getLevel(worldKey); if (world == null) return; + if (!this.plugin.canUseAxiom(player.getBukkitEntity())) { + return; + } + // Call AxiomModifyWorldEvent event AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(player.getBukkitEntity(), world.getWorld()); Bukkit.getPluginManager().callEvent(modifyWorldEvent); if (modifyWorldEvent.isCancelled()) return; - // RegionProtection regionProtection = new RegionProtection(player.getBukkitEntity(), world.getWorld()); - // Allowed, apply buffer BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos(); - - var lightEngine = world.getChunkSource().getLightEngine(); + WorldExtension extension = WorldExtension.get(world); BlockState emptyState = BlockBuffer.EMPTY_STATE; @@ -133,17 +117,7 @@ public class SetBlockBufferPacketListener { continue; } -// SectionProtection sectionProtection = regionProtection.getSection(cx, cy, cz); -// switch (sectionProtection.getSectionState()) { -// case ALLOW -> sectionProtection = null; -// case DENY -> { -// continue; -// } -// case CHECK -> {} -// } - LevelChunk chunk = world.getChunk(cx, cz); - chunk.setUnsaved(true); LevelChunkSection section = chunk.getSection(world.getSectionIndexFromSectionY(cy)); PalettedContainer sectionStates = section.getStates(); @@ -163,6 +137,12 @@ public class SetBlockBufferPacketListener { } } + boolean sectionChanged = false; + boolean sectionLightChanged = false; + + boolean containerMaybeHasPoi = container.maybeHas(PoiTypes::hasPoi); + boolean sectionMaybeHasPoi = section.maybeHas(PoiTypes::hasPoi); + Short2ObjectMap blockEntityChunkMap = buffer.getBlockEntityChunkMap(entry.getLongKey()); for (int x = 0; x < 16; x++) { @@ -171,28 +151,19 @@ public class SetBlockBufferPacketListener { BlockState blockState = container.get(x, y, z); if (blockState == emptyState) continue; -// switch (sectionProtection.getSectionState()) { -// case ALLOW -> {} -// case DENY -> blockState = Blocks.REDSTONE_BLOCK.defaultBlockState(); -// case CHECK -> blockState = Blocks.DIAMOND_BLOCK.defaultBlockState(); -// } - int bx = cx*16 + x; int by = cy*16 + y; int bz = cz*16 + z; -// if (!regionProtection.canBuild(bx, by, bz)) { -// continue; -// } - - blockPos.set(bx, by, bz); - if (hasOnlyAir && blockState.isAir()) { continue; } BlockState old = section.setBlockState(x, y, z, blockState, true); if (blockState != old) { + sectionChanged = true; + blockPos.set(bx, by, bz); + Block block = blockState.getBlock(); motionBlocking.update(x, by, z, blockState); motionBlockingNoLeaves.update(x, by, z, blockState); @@ -245,18 +216,12 @@ public class SetBlockBufferPacketListener { chunk.removeBlockEntity(blockPos); } - // Mark block changed - world.getChunkSource().blockChanged(blockPos); // todo: maybe simply resend chunk instead of this? - // Update Light - if (LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState)) { - chunk.getSkyLightSources().update(chunk, x, by, z); - lightEngine.checkBlock(blockPos); - } + sectionLightChanged |= LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState); // Update Poi - Optional> newPoi = PoiTypes.forState(blockState); - Optional> oldPoi = PoiTypes.forState(old); + Optional> newPoi = containerMaybeHasPoi ? PoiTypes.forState(blockState) : Optional.empty(); + Optional> oldPoi = sectionMaybeHasPoi ? PoiTypes.forState(old) : Optional.empty(); if (!Objects.equals(oldPoi, newPoi)) { if (oldPoi.isPresent()) world.getPoiManager().remove(blockPos); if (newPoi.isPresent()) world.getPoiManager().add(blockPos, newPoi.get()); @@ -270,16 +235,33 @@ public class SetBlockBufferPacketListener { if (hasOnlyAir != nowHasOnlyAir) { world.getChunkSource().getLightEngine().updateSectionStatus(SectionPos.of(cx, cy, cz), nowHasOnlyAir); } + + if (sectionChanged) { + extension.sendChunk(cx, cz); + chunk.setUnsaved(true); + } + if (sectionLightChanged) { + extension.lightChunk(cx, cz); + } } }); } - private void applyBiomeBuffer(MinecraftServer server, BiomeBuffer biomeBuffer, ResourceKey worldKey) { + private void applyBiomeBuffer(ServerPlayer player, MinecraftServer server, BiomeBuffer biomeBuffer, ResourceKey worldKey) { server.execute(() -> { ServerLevel world = server.getLevel(worldKey); if (world == null) return; + if (!this.plugin.canUseAxiom(player.getBukkitEntity())) { + return; + } + + // Call AxiomModifyWorldEvent event + AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(player.getBukkitEntity(), world.getWorld()); + Bukkit.getPluginManager().callEvent(modifyWorldEvent); + if (modifyWorldEvent.isCancelled()) return; + Set changedChunks = new HashSet<>(); int minSection = world.getMinSection(); diff --git a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java index 7db3fb8..247af05 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetBlockPacketListener.java @@ -62,7 +62,7 @@ public class SetBlockPacketListener implements PluginMessageListener { @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player bukkitPlayer, @NotNull byte[] message) { - if (!bukkitPlayer.hasPermission("axiom.*")) { + if (!this.plugin.canUseAxiom(bukkitPlayer)) { return; } diff --git a/src/main/java/com/moulberry/axiom/packet/SetEditorViewsPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetEditorViewsPacketListener.java index 1fd9bda..96c257a 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetEditorViewsPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetEditorViewsPacketListener.java @@ -1,6 +1,7 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.AxiomConstants; +import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.View; import com.moulberry.axiom.persistence.UUIDDataType; import io.netty.buffer.Unpooled; @@ -16,9 +17,14 @@ import java.util.UUID; public class SetEditorViewsPacketListener implements PluginMessageListener { + private final AxiomPaper plugin; + public SetEditorViewsPacketListener(AxiomPaper plugin) { + this.plugin = plugin; + } + @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!player.hasPermission("axiom.*")) { + if (!this.plugin.canUseAxiom(player)) { return; } diff --git a/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java index bc516fb..2e246a5 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetFlySpeedPacketListener.java @@ -1,5 +1,6 @@ package com.moulberry.axiom.packet; +import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.event.AxiomFlySpeedChangeEvent; import io.netty.buffer.Unpooled; import net.minecraft.network.FriendlyByteBuf; @@ -11,9 +12,14 @@ import org.jetbrains.annotations.NotNull; public class SetFlySpeedPacketListener implements PluginMessageListener { + private final AxiomPaper plugin; + public SetFlySpeedPacketListener(AxiomPaper plugin) { + this.plugin = plugin; + } + @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!player.hasPermission("axiom.*")) { + if (!this.plugin.canUseAxiom(player)) { return; } diff --git a/src/main/java/com/moulberry/axiom/packet/SetGamemodePacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetGamemodePacketListener.java index 17f321a..dd66081 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetGamemodePacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetGamemodePacketListener.java @@ -1,5 +1,6 @@ package com.moulberry.axiom.packet; +import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.event.AxiomGameModeChangeEvent; import io.netty.buffer.Unpooled; import net.minecraft.network.FriendlyByteBuf; @@ -13,9 +14,14 @@ import org.jetbrains.annotations.NotNull; public class SetGamemodePacketListener implements PluginMessageListener { + private final AxiomPaper plugin; + public SetGamemodePacketListener(AxiomPaper plugin) { + this.plugin = plugin; + } + @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!player.hasPermission("axiom.*")) { + if (!this.plugin.canUseAxiom(player)) { return; } diff --git a/src/main/java/com/moulberry/axiom/packet/SetHotbarSlotPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetHotbarSlotPacketListener.java index 7d18a7d..5c299b0 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetHotbarSlotPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetHotbarSlotPacketListener.java @@ -1,6 +1,7 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.AxiomConstants; +import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.persistence.ItemStackDataType; import io.netty.buffer.Unpooled; import net.minecraft.network.FriendlyByteBuf; @@ -15,9 +16,14 @@ import org.jetbrains.annotations.NotNull; public class SetHotbarSlotPacketListener implements PluginMessageListener { + private final AxiomPaper plugin; + public SetHotbarSlotPacketListener(AxiomPaper plugin) { + this.plugin = plugin; + } + @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!player.hasPermission("axiom.*")) { + if (!this.plugin.canUseAxiom(player)) { return; } diff --git a/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java b/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java index f650fd6..6b858fc 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetTimePacketListener.java @@ -1,5 +1,7 @@ package com.moulberry.axiom.packet; +import com.moulberry.axiom.AxiomPaper; +import com.moulberry.axiom.event.AxiomModifyWorldEvent; import com.moulberry.axiom.event.AxiomTimeChangeEvent; import io.netty.buffer.Unpooled; import net.minecraft.core.registries.Registries; @@ -16,9 +18,14 @@ import org.jetbrains.annotations.NotNull; public class SetTimePacketListener implements PluginMessageListener { + private final AxiomPaper plugin; + public SetTimePacketListener(AxiomPaper plugin) { + this.plugin = plugin; + } + @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!player.hasPermission("axiom.*")) { + if (!this.plugin.canUseAxiom(player)) { return; } @@ -32,7 +39,12 @@ public class SetTimePacketListener implements PluginMessageListener { ServerLevel level = ((CraftWorld)player.getWorld()).getHandle(); if (!level.dimension().equals(key)) return; - // Call event + // Call modify world + AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(player, player.getWorld()); + Bukkit.getPluginManager().callEvent(modifyWorldEvent); + if (modifyWorldEvent.isCancelled()) return; + + // Call time change event AxiomTimeChangeEvent timeChangeEvent = new AxiomTimeChangeEvent(player, time, freezeTime); Bukkit.getPluginManager().callEvent(timeChangeEvent); if (timeChangeEvent.isCancelled()) return; diff --git a/src/main/java/com/moulberry/axiom/packet/SetWorldPropertyListener.java b/src/main/java/com/moulberry/axiom/packet/SetWorldPropertyListener.java index c18a72f..d9a4ce8 100644 --- a/src/main/java/com/moulberry/axiom/packet/SetWorldPropertyListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SetWorldPropertyListener.java @@ -12,9 +12,14 @@ import org.jetbrains.annotations.NotNull; public class SetWorldPropertyListener implements PluginMessageListener { + private final AxiomPaper plugin; + public SetWorldPropertyListener(AxiomPaper plugin) { + this.plugin = plugin; + } + @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!player.hasPermission("axiom.*")) { + if (!this.plugin.canUseAxiom(player)) { return; } diff --git a/src/main/java/com/moulberry/axiom/packet/SwitchActiveHotbarPacketListener.java b/src/main/java/com/moulberry/axiom/packet/SwitchActiveHotbarPacketListener.java index 2c98b73..edd0d45 100644 --- a/src/main/java/com/moulberry/axiom/packet/SwitchActiveHotbarPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/SwitchActiveHotbarPacketListener.java @@ -1,6 +1,7 @@ package com.moulberry.axiom.packet; import com.moulberry.axiom.AxiomConstants; +import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.persistence.ItemStackDataType; import io.netty.buffer.Unpooled; import net.minecraft.network.FriendlyByteBuf; @@ -17,9 +18,14 @@ import org.jetbrains.annotations.NotNull; public class SwitchActiveHotbarPacketListener implements PluginMessageListener { + private final AxiomPaper plugin; + public SwitchActiveHotbarPacketListener(AxiomPaper plugin) { + this.plugin = plugin; + } + @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!player.hasPermission("axiom.*")) { + if (!this.plugin.canUseAxiom(player)) { return; } diff --git a/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java b/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java index 871cc2a..7fff066 100644 --- a/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java +++ b/src/main/java/com/moulberry/axiom/packet/TeleportPacketListener.java @@ -1,5 +1,6 @@ package com.moulberry.axiom.packet; +import com.moulberry.axiom.AxiomPaper; import com.moulberry.axiom.event.AxiomGameModeChangeEvent; import com.moulberry.axiom.event.AxiomTeleportEvent; import io.netty.buffer.Unpooled; @@ -14,9 +15,14 @@ import org.jetbrains.annotations.NotNull; public class TeleportPacketListener implements PluginMessageListener { + private final AxiomPaper plugin; + public TeleportPacketListener(AxiomPaper plugin) { + this.plugin = plugin; + } + @Override public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) { - if (!player.hasPermission("axiom.*")) { + if (!this.plugin.canUseAxiom(player)) { return; } diff --git a/src/main/resources/config.yml b/src/main/resources/config.yml new file mode 100644 index 0000000..065daf9 --- /dev/null +++ b/src/main/resources/config.yml @@ -0,0 +1,29 @@ +# Max chunk sends per tick (per-world), 0 = no limit +max-chunk-sends-per-tick: 0 + +# Max chunk relights per tick (per-world), 0 = no limit +max-chunk-relights-per-tick: 0 + +# Action to take when a user with an incompatible Minecraft version or Axiom version joins +# Valid actions are 'kick', 'warn' and 'ignore' +# Using 'ignore' may result in corruption and is only provided for debugging purposes +incompatible-data-version: "kick" +unsupported-axiom-version: "kick" + +# Maximum packet size. Must not be less than 32767 +max-block-buffer-packet-size: 0x100000 + +# Toggles for individual packet handlers. May break certain features +packet-handlers: + hello: true + set-gamemode: true + set-fly-speed: true + set-world-time: true + set-world-property: true + set-single-block: true + set-hotbar-slot: true + switch-active-hotbar: true + teleport: true + set-editor-views: true + request-chunk-data: true + set-buffer: true