geforkt von Mirrors/AxiomPaperPlugin
backport1.20.1 #6
@ -3,3 +3,6 @@
|
|||||||
Serverside component for Axiom
|
Serverside component for Axiom
|
||||||
|
|
||||||
(todo: better readme)
|
(todo: better readme)
|
||||||
|
|
||||||
|
## Download
|
||||||
|
https://modrinth.com/plugin/axiom-paper-plugin/
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
plugins {
|
plugins {
|
||||||
`java-library`
|
`java-library`
|
||||||
id("io.papermc.paperweight.userdev") version "1.5.8"
|
id("io.papermc.paperweight.userdev") version "1.5.11"
|
||||||
id("xyz.jpenilla.run-paper") version "2.2.0" // Adds runServer and runMojangMappedServer tasks for testing
|
id("xyz.jpenilla.run-paper") version "2.2.2" // Adds runServer and runMojangMappedServer tasks for testing
|
||||||
|
|
||||||
// Shades and relocates dependencies into our plugin jar. See https://imperceptiblethoughts.com/shadow/introduction/
|
// Shades and relocates dependencies into our plugin jar. See https://imperceptiblethoughts.com/shadow/introduction/
|
||||||
id("com.github.johnrengelman.shadow") version "8.1.1"
|
id("com.github.johnrengelman.shadow") version "8.1.1"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "com.moulberry.axiom"
|
group = "com.moulberry.axiom"
|
||||||
version = "1.5.1"
|
version = "1.5.8"
|
||||||
description = "Serverside component for Axiom on Paper"
|
description = "Serverside component for Axiom on Paper"
|
||||||
|
|
||||||
java {
|
java {
|
||||||
@ -20,8 +20,8 @@ repositories {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
maven("https://s01.oss.sonatype.org/content/repositories/snapshots/")
|
||||||
maven("https://jitpack.io")
|
maven("https://jitpack.io")
|
||||||
maven("https://maven.enginehub.org/repo/")
|
|
||||||
maven("https://repo.papermc.io/repository/maven-public/")
|
maven("https://repo.papermc.io/repository/maven-public/")
|
||||||
|
maven("https://maven.enginehub.org/repo/")
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
@ -1,10 +1,12 @@
|
|||||||
package com.moulberry.axiom;
|
package com.moulberry.axiom;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
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.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 com.moulberry.axiom.world_properties.server.ServerWorldProperty;
|
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import io.netty.channel.Channel;
|
import io.netty.channel.Channel;
|
||||||
import io.papermc.paper.event.player.PlayerFailMoveEvent;
|
import io.papermc.paper.event.player.PlayerFailMoveEvent;
|
||||||
@ -12,14 +14,20 @@ 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.core.BlockPos;
|
||||||
|
import net.minecraft.core.IdMapper;
|
||||||
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;
|
||||||
import net.minecraft.network.protocol.Packet;
|
import net.minecraft.network.protocol.Packet;
|
||||||
import net.minecraft.network.protocol.PacketFlow;
|
import net.minecraft.network.protocol.PacketFlow;
|
||||||
import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket;
|
import net.minecraft.network.protocol.game.ServerboundCustomPayloadPacket;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.server.MinecraftServer;
|
||||||
import org.bukkit.*;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.GameRule;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.configuration.Configuration;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
@ -37,11 +45,33 @@ public class AxiomPaper extends JavaPlugin implements Listener {
|
|||||||
public static AxiomPaper PLUGIN; // tsk tsk tsk
|
public static AxiomPaper PLUGIN; // tsk tsk tsk
|
||||||
|
|
||||||
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, Restrictions> playerRestrictions = new ConcurrentHashMap<>();
|
||||||
|
public Configuration configuration;
|
||||||
|
|
||||||
|
public IdMapper<BlockState> allowedBlockRegistry = null;
|
||||||
|
private boolean logLargeBlockBufferChanges = false;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onEnable() {
|
public void onEnable() {
|
||||||
PLUGIN = this;
|
PLUGIN = this;
|
||||||
|
|
||||||
|
this.saveDefaultConfig();
|
||||||
|
configuration = this.getConfig();
|
||||||
|
|
||||||
|
Set<String> 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'");
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logLargeBlockBufferChanges = this.configuration.getBoolean("log-large-block-buffer-changes");
|
||||||
|
|
||||||
|
List<String> disallowedBlocks = this.configuration.getStringList("disallowed-blocks");
|
||||||
|
this.allowedBlockRegistry = DisallowedBlocks.createAllowedBlockRegistry(disallowedBlocks);
|
||||||
|
|
||||||
Bukkit.getPluginManager().registerEvents(this, this);
|
Bukkit.getPluginManager().registerEvents(this, this);
|
||||||
// Bukkit.getPluginManager().registerEvents(new WorldPropertiesExample(), this);
|
// Bukkit.getPluginManager().registerEvents(new WorldPropertiesExample(), this);
|
||||||
CompressedBlockEntity.initialize(this);
|
CompressedBlockEntity.initialize(this);
|
||||||
@ -55,44 +85,90 @@ 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");
|
||||||
|
msg.registerOutgoingPluginChannel(this, "axiom:marker_data");
|
||||||
|
msg.registerOutgoingPluginChannel(this, "axiom:marker_nbt_response");
|
||||||
|
|
||||||
msg.registerIncomingPluginChannel(this, "axiom:hello", new HelloPacketListener(this, activeAxiomPlayers));
|
if (configuration.getBoolean("packet-handlers.hello")) {
|
||||||
msg.registerIncomingPluginChannel(this, "axiom:set_gamemode", new SetGamemodePacketListener());
|
msg.registerIncomingPluginChannel(this, "axiom:hello", new HelloPacketListener(this));
|
||||||
msg.registerIncomingPluginChannel(this, "axiom:set_fly_speed", new SetFlySpeedPacketListener());
|
}
|
||||||
msg.registerIncomingPluginChannel(this, "axiom:set_world_time", new SetTimePacketListener());
|
if (configuration.getBoolean("packet-handlers.set-gamemode")) {
|
||||||
msg.registerIncomingPluginChannel(this, "axiom:set_world_property", new SetWorldPropertyListener());
|
msg.registerIncomingPluginChannel(this, "axiom:set_gamemode", new SetGamemodePacketListener(this));
|
||||||
msg.registerIncomingPluginChannel(this, "axiom:set_block", new SetBlockPacketListener(this));
|
}
|
||||||
msg.registerIncomingPluginChannel(this, "axiom:set_hotbar_slot", new SetHotbarSlotPacketListener());
|
if (configuration.getBoolean("packet-handlers.set-fly-speed")) {
|
||||||
msg.registerIncomingPluginChannel(this, "axiom:switch_active_hotbar", new SwitchActiveHotbarPacketListener());
|
msg.registerIncomingPluginChannel(this, "axiom:set_fly_speed", new SetFlySpeedPacketListener(this));
|
||||||
msg.registerIncomingPluginChannel(this, "axiom:teleport", new TeleportPacketListener());
|
}
|
||||||
msg.registerIncomingPluginChannel(this, "axiom:set_editor_views", new SetEditorViewsPacketListener());
|
if (configuration.getBoolean("packet-handlers.set-world-time")) {
|
||||||
msg.registerIncomingPluginChannel(this, "axiom:request_chunk_data", new RequestChunkDataPacketListener(this));
|
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));
|
||||||
|
}
|
||||||
|
if (configuration.getBoolean("packet-handlers.spawn-entity")) {
|
||||||
|
msg.registerIncomingPluginChannel(this, "axiom:spawn_entity", new SpawnEntityPacketListener(this));
|
||||||
|
}
|
||||||
|
if (configuration.getBoolean("packet-handlers.manipulate-entity")) {
|
||||||
|
msg.registerIncomingPluginChannel(this, "axiom:manipulate_entity", new ManipulateEntityPacketListener(this));
|
||||||
|
}
|
||||||
|
if (configuration.getBoolean("packet-handlers.delete-entity")) {
|
||||||
|
msg.registerIncomingPluginChannel(this, "axiom:delete_entity", new DeleteEntityPacketListener(this));
|
||||||
|
}
|
||||||
|
if (configuration.getBoolean("packet-handlers.marker-nbt-request")) {
|
||||||
|
msg.registerIncomingPluginChannel(this, "axiom:marker_nbt_request", new MarkerNbtRequestPacketListener(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() {
|
ChannelInitializeListenerHolder.addListener(Key.key("axiom:handle_big_payload"), new ChannelInitializeListener() {
|
||||||
@Override
|
@Override
|
||||||
public void afterInitChannel(@NonNull Channel channel) {
|
public void afterInitChannel(@NonNull Channel channel) {
|
||||||
var packets = ConnectionProtocol.PLAY.getPacketsByIds(PacketFlow.SERVERBOUND);
|
var packets = ConnectionProtocol.PLAY.getPacketsByIds(PacketFlow.SERVERBOUND);
|
||||||
int payloadId = -1;
|
int payloadId = -1;
|
||||||
for (Map.Entry<Integer, Class<? extends Packet<?>>> entry : packets.entrySet()) {
|
for (Map.Entry<Integer, Class<? extends Packet<?>>> entry : packets.entrySet()) {
|
||||||
if (entry.getValue() == ServerboundCustomPayloadPacket.class) {
|
if (entry.getValue() == ServerboundCustomPayloadPacket.class) {
|
||||||
payloadId = entry.getKey();
|
payloadId = entry.getKey();
|
||||||
break;
|
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");
|
Connection connection = (Connection) channel.pipeline().get("packet_handler");
|
||||||
channel.pipeline().addBefore("decoder", "axiom-big-payload-handler",
|
channel.pipeline().addBefore("decoder", "axiom-big-payload-handler",
|
||||||
new AxiomBigPayloadHandler(payloadId, connection, setBlockBufferPacketListener));
|
new AxiomBigPayloadHandler(payloadId, connection, setBlockBufferPacketListener));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
|
Bukkit.getScheduler().scheduleSyncRepeatingTask(this, () -> {
|
||||||
HashSet<UUID> newActiveAxiomPlayers = 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())) {
|
||||||
@ -103,19 +179,100 @@ 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 {
|
||||||
newActiveAxiomPlayers.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.clear();
|
activeAxiomPlayers.retainAll(stillActiveAxiomPlayers);
|
||||||
activeAxiomPlayers.addAll(newActiveAxiomPlayers);
|
playerBlockBufferRateLimiters.keySet().retainAll(stillActiveAxiomPlayers);
|
||||||
|
playerRestrictions.keySet().retainAll(stillActiveAxiomPlayers);
|
||||||
}, 20, 20);
|
}, 20, 20);
|
||||||
|
|
||||||
|
boolean sendMarkers = configuration.getBoolean("send-markers");
|
||||||
|
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(), sendMarkers, maxChunkRelightsPerTick, maxChunkSendsPerTick);
|
||||||
|
}, 1, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean logLargeBlockBufferChanges() {
|
||||||
|
return this.logLargeBlockBufferChanges;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean canUseAxiom(Player player) {
|
||||||
|
return player.hasPermission("axiom.*") && activeAxiomPlayers.contains(player.getUniqueId());
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable RateLimiter getBlockBufferRateLimiter(UUID uuid) {
|
||||||
|
return this.playerBlockBufferRateLimiters.get(uuid);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final WeakHashMap<World, ServerWorldPropertiesRegistry> worldProperties = new WeakHashMap<>();
|
private final WeakHashMap<World, ServerWorldPropertiesRegistry> worldProperties = new WeakHashMap<>();
|
||||||
|
|
||||||
public @Nullable ServerWorldPropertiesRegistry getWorldProperties(World world) {
|
public @Nullable ServerWorldPropertiesRegistry getWorldPropertiesIfPresent(World world) {
|
||||||
|
return worldProperties.get(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
public @Nullable ServerWorldPropertiesRegistry getOrCreateWorldProperties(World world) {
|
||||||
if (worldProperties.containsKey(world)) {
|
if (worldProperties.containsKey(world)) {
|
||||||
return worldProperties.get(world);
|
return worldProperties.get(world);
|
||||||
} else {
|
} else {
|
||||||
@ -125,40 +282,57 @@ public class AxiomPaper extends JavaPlugin implements Listener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean canModifyWorld(Player player, World world) {
|
||||||
|
String whitelist = this.configuration.getString("whitelist-world-regex");
|
||||||
|
if (whitelist != null && !world.getName().matches(whitelist)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String blacklist = this.configuration.getString("blacklist-world-regex");
|
||||||
|
if (blacklist != null && world.getName().matches(blacklist)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(player, world);
|
||||||
|
Bukkit.getPluginManager().callEvent(modifyWorldEvent);
|
||||||
|
return !modifyWorldEvent.isCancelled();
|
||||||
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onFailMove(PlayerFailMoveEvent event) {
|
public void onFailMove(PlayerFailMoveEvent event) {
|
||||||
if (event.getPlayer().hasPermission("axiom.*")) {
|
if (!this.activeAxiomPlayers.contains(event.getPlayer().getUniqueId())) {
|
||||||
if (event.getFailReason() == PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY) {
|
return;
|
||||||
event.setAllowed(true); // Support for arcball camera
|
}
|
||||||
} else if (event.getPlayer().isFlying()) {
|
if (event.getFailReason() == PlayerFailMoveEvent.FailReason.MOVED_TOO_QUICKLY) {
|
||||||
event.setAllowed(true); // Support for noclip
|
event.setAllowed(true); // Support for arcball camera
|
||||||
}
|
} else if (event.getPlayer().isFlying()) {
|
||||||
|
event.setAllowed(true); // Support for noclip
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onChangedWorld(PlayerChangedWorldEvent event) {
|
public void onChangedWorld(PlayerChangedWorldEvent event) {
|
||||||
|
if (!this.activeAxiomPlayers.contains(event.getPlayer().getUniqueId())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
World world = event.getPlayer().getWorld();
|
World world = event.getPlayer().getWorld();
|
||||||
|
|
||||||
ServerWorldPropertiesRegistry properties = getWorldProperties(world);
|
ServerWorldPropertiesRegistry properties = getOrCreateWorldProperties(world);
|
||||||
|
|
||||||
if (properties == null) {
|
if (properties == null) {
|
||||||
event.getPlayer().sendPluginMessage(this, "axiom:register_world_properties", new byte[]{0});
|
event.getPlayer().sendPluginMessage(this, "axiom:register_world_properties", new byte[]{0});
|
||||||
} else {
|
} else {
|
||||||
properties.registerFor(this, event.getPlayer());
|
properties.registerFor(this, event.getPlayer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WorldExtension.onPlayerJoin(world, event.getPlayer());
|
||||||
}
|
}
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onGameRuleChanged(WorldGameRuleChangeEvent event) {
|
public void onGameRuleChanged(WorldGameRuleChangeEvent event) {
|
||||||
if (event.getGameRule() == GameRule.DO_WEATHER_CYCLE) {
|
if (event.getGameRule() == GameRule.DO_WEATHER_CYCLE) {
|
||||||
ServerWorldPropertiesRegistry properties = getWorldProperties(event.getWorld());
|
ServerWorldPropertiesRegistry.PAUSE_WEATHER.setValue(event.getWorld(), !Boolean.parseBoolean(event.getValue()));
|
||||||
if (properties != null) {
|
|
||||||
ServerWorldProperty<?> property = properties.getById(new ResourceLocation("axiom:pause_weather"));
|
|
||||||
if (property != null) {
|
|
||||||
((ServerWorldProperty<Boolean>)property).setValue(event.getWorld(), !Boolean.parseBoolean(event.getValue()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
87
src/main/java/com/moulberry/axiom/DisallowedBlocks.java
Normale Datei
87
src/main/java/com/moulberry/axiom/DisallowedBlocks.java
Normale Datei
@ -0,0 +1,87 @@
|
|||||||
|
package com.moulberry.axiom;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.StringReader;
|
||||||
|
import com.moulberry.axiom.buffer.BlockBuffer;
|
||||||
|
import net.minecraft.commands.arguments.blocks.BlockStateParser;
|
||||||
|
import net.minecraft.core.IdMapper;
|
||||||
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
|
import net.minecraft.world.level.block.state.properties.Property;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
public class DisallowedBlocks {
|
||||||
|
|
||||||
|
public static IdMapper<BlockState> createAllowedBlockRegistry(List<String> disallowedBlocks) {
|
||||||
|
List<Predicate<BlockState>> disallowedPredicates = new ArrayList<>();
|
||||||
|
|
||||||
|
for (String disallowedBlock : disallowedBlocks) {
|
||||||
|
try {
|
||||||
|
var parsed = BlockStateParser.parseForTesting(BuiltInRegistries.BLOCK.asLookup(), new StringReader(disallowedBlock), false);
|
||||||
|
|
||||||
|
parsed.left().ifPresent(result -> {
|
||||||
|
disallowedPredicates.add(blockState -> {
|
||||||
|
if (!blockState.is(result.blockState().getBlock())) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
for (Property<?> property : result.properties().keySet()) {
|
||||||
|
if (blockState.getValue(property) != result.blockState().getValue(property)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
parsed.right().ifPresent(result -> {
|
||||||
|
disallowedPredicates.add(blockState -> {
|
||||||
|
if (!blockState.is(result.tag())) {
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
for(Map.Entry<String, String> entry : result.vagueProperties().entrySet()) {
|
||||||
|
Property<?> property = blockState.getBlock().getStateDefinition().getProperty(entry.getKey());
|
||||||
|
if (property == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
Comparable<?> comparable = property.getValue(entry.getValue()).orElse(null);
|
||||||
|
if (comparable == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (blockState.getValue(property) != comparable) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
} catch (Exception ignored) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
IdMapper<BlockState> allowedBlockRegistry = new IdMapper<>();
|
||||||
|
|
||||||
|
// Create allowedBlockRegistry
|
||||||
|
blocks:
|
||||||
|
for (BlockState blockState : Block.BLOCK_STATE_REGISTRY) {
|
||||||
|
for (Predicate<BlockState> disallowedPredicate : disallowedPredicates) {
|
||||||
|
if (disallowedPredicate.test(blockState)) {
|
||||||
|
allowedBlockRegistry.add(BlockBuffer.EMPTY_STATE);
|
||||||
|
continue blocks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allowedBlockRegistry.add(blockState);
|
||||||
|
}
|
||||||
|
allowedBlockRegistry.addMapping(BlockBuffer.EMPTY_STATE, Block.BLOCK_STATE_REGISTRY.getId(BlockBuffer.EMPTY_STATE));
|
||||||
|
return allowedBlockRegistry;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
63
src/main/java/com/moulberry/axiom/NbtSanitization.java
Normale Datei
63
src/main/java/com/moulberry/axiom/NbtSanitization.java
Normale Datei
@ -0,0 +1,63 @@
|
|||||||
|
package com.moulberry.axiom;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.ListTag;
|
||||||
|
import net.minecraft.nbt.Tag;
|
||||||
|
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
public class NbtSanitization {
|
||||||
|
|
||||||
|
private static final Set<String> ALLOWED_KEYS = Set.of(
|
||||||
|
"id", // entity id
|
||||||
|
// generic
|
||||||
|
"Pos",
|
||||||
|
"Rotation",
|
||||||
|
"Invulnerable",
|
||||||
|
"CustomName",
|
||||||
|
"CustomNameVisible",
|
||||||
|
"Silent",
|
||||||
|
"NoGravity",
|
||||||
|
"Glowing",
|
||||||
|
"Tags",
|
||||||
|
"Passengers",
|
||||||
|
// marker
|
||||||
|
"data",
|
||||||
|
// display entity
|
||||||
|
"transformation",
|
||||||
|
"interpolation_duration",
|
||||||
|
"start_interpolation",
|
||||||
|
"teleport_duration",
|
||||||
|
"billboard",
|
||||||
|
"view_range",
|
||||||
|
"shadow_radius",
|
||||||
|
"shadow_strength",
|
||||||
|
"width",
|
||||||
|
"height",
|
||||||
|
"glow_color_override",
|
||||||
|
"brightness",
|
||||||
|
"line_width",
|
||||||
|
"text_opacity",
|
||||||
|
"background",
|
||||||
|
"shadow",
|
||||||
|
"see_through",
|
||||||
|
"default_background",
|
||||||
|
"alignment",
|
||||||
|
"text",
|
||||||
|
"block_state",
|
||||||
|
"item",
|
||||||
|
"item_display"
|
||||||
|
);
|
||||||
|
|
||||||
|
public static void sanitizeEntity(CompoundTag entityRoot) {
|
||||||
|
entityRoot.getAllKeys().retainAll(ALLOWED_KEYS);
|
||||||
|
|
||||||
|
if (entityRoot.contains("Passengers", Tag.TAG_LIST)) {
|
||||||
|
ListTag listTag = entityRoot.getList("Passengers", Tag.TAG_COMPOUND);
|
||||||
|
for (Tag tag : listTag) {
|
||||||
|
sanitizeEntity((CompoundTag) tag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
64
src/main/java/com/moulberry/axiom/Restrictions.java
Normale Datei
64
src/main/java/com/moulberry/axiom/Restrictions.java
Normale Datei
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -2,8 +2,6 @@ package com.moulberry.axiom;
|
|||||||
|
|
||||||
import com.moulberry.axiom.persistence.UUIDDataType;
|
import com.moulberry.axiom.persistence.UUIDDataType;
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
|
||||||
import net.minecraft.nbt.Tag;
|
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
176
src/main/java/com/moulberry/axiom/WorldExtension.java
Normale Datei
176
src/main/java/com/moulberry/axiom/WorldExtension.java
Normale Datei
@ -0,0 +1,176 @@
|
|||||||
|
package com.moulberry.axiom;
|
||||||
|
|
||||||
|
import com.moulberry.axiom.marker.MarkerData;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongIterator;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
|
||||||
|
import it.unimi.dsi.fastutil.longs.LongSet;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
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.entity.Entity;
|
||||||
|
import net.minecraft.world.entity.Marker;
|
||||||
|
import net.minecraft.world.level.ChunkPos;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.chunk.LevelChunk;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
public class WorldExtension {
|
||||||
|
|
||||||
|
private static final Map<ResourceKey<Level>, 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 onPlayerJoin(World world, Player player) {
|
||||||
|
ServerLevel level = ((CraftWorld)world).getHandle();
|
||||||
|
get(level).onPlayerJoin(player);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void tick(MinecraftServer server, boolean sendMarkers, int maxChunkRelightsPerTick, int maxChunkSendsPerTick) {
|
||||||
|
extensions.keySet().retainAll(server.levelKeys());
|
||||||
|
|
||||||
|
for (ServerLevel level : server.getAllLevels()) {
|
||||||
|
get(level).tick(sendMarkers, maxChunkRelightsPerTick, maxChunkSendsPerTick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private ServerLevel level;
|
||||||
|
|
||||||
|
private final LongSet pendingChunksToSend = new LongOpenHashSet();
|
||||||
|
private final LongSet pendingChunksToLight = new LongOpenHashSet();
|
||||||
|
private final Map<UUID, MarkerData> previousMarkerData = new HashMap<>();
|
||||||
|
|
||||||
|
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 onPlayerJoin(Player player) {
|
||||||
|
if (!this.previousMarkerData.isEmpty()) {
|
||||||
|
List<MarkerData> markerData = new ArrayList<>(this.previousMarkerData.values());
|
||||||
|
|
||||||
|
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||||
|
buf.writeCollection(markerData, MarkerData::write);
|
||||||
|
buf.writeCollection(Set.of(), FriendlyByteBuf::writeUUID);
|
||||||
|
byte[] bytes = new byte[buf.writerIndex()];
|
||||||
|
buf.getBytes(0, bytes);
|
||||||
|
|
||||||
|
player.sendPluginMessage(AxiomPaper.PLUGIN, "axiom:marker_data", bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void tick(boolean sendMarkers, int maxChunkRelightsPerTick, int maxChunkSendsPerTick) {
|
||||||
|
if (sendMarkers) {
|
||||||
|
this.tickMarkers();
|
||||||
|
}
|
||||||
|
this.tickChunkRelight(maxChunkRelightsPerTick, maxChunkSendsPerTick);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tickMarkers() {
|
||||||
|
List<MarkerData> changedData = new ArrayList<>();
|
||||||
|
|
||||||
|
Set<UUID> allMarkers = new HashSet<>();
|
||||||
|
|
||||||
|
for (Entity entity : this.level.getEntities().getAll()) {
|
||||||
|
if (entity instanceof Marker marker) {
|
||||||
|
MarkerData currentData = MarkerData.createFrom(marker);
|
||||||
|
|
||||||
|
MarkerData previousData = this.previousMarkerData.get(marker.getUUID());
|
||||||
|
if (!Objects.equals(currentData, previousData)) {
|
||||||
|
this.previousMarkerData.put(marker.getUUID(), currentData);
|
||||||
|
changedData.add(currentData);
|
||||||
|
}
|
||||||
|
|
||||||
|
allMarkers.add(marker.getUUID());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<UUID> oldUuids = new HashSet<>(this.previousMarkerData.keySet());
|
||||||
|
oldUuids.removeAll(allMarkers);
|
||||||
|
this.previousMarkerData.keySet().removeAll(oldUuids);
|
||||||
|
|
||||||
|
if (!changedData.isEmpty() || !oldUuids.isEmpty()) {
|
||||||
|
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||||
|
buf.writeCollection(changedData, MarkerData::write);
|
||||||
|
buf.writeCollection(oldUuids, FriendlyByteBuf::writeUUID);
|
||||||
|
byte[] bytes = new byte[buf.writerIndex()];
|
||||||
|
buf.getBytes(0, bytes);
|
||||||
|
|
||||||
|
for (ServerPlayer player : this.level.players()) {
|
||||||
|
if (AxiomPaper.PLUGIN.activeAxiomPlayers.contains(player.getUUID())) {
|
||||||
|
player.getBukkitEntity().sendPluginMessage(AxiomPaper.PLUGIN, "axiom:marker_data", bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tickChunkRelight(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<ServerPlayer> 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<ChunkPos> 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 -> {});
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,11 +3,10 @@ package com.moulberry.axiom;
|
|||||||
import com.moulberry.axiom.event.AxiomCreateWorldPropertiesEvent;
|
import com.moulberry.axiom.event.AxiomCreateWorldPropertiesEvent;
|
||||||
import com.moulberry.axiom.world_properties.WorldPropertyCategory;
|
import com.moulberry.axiom.world_properties.WorldPropertyCategory;
|
||||||
import com.moulberry.axiom.world_properties.WorldPropertyWidgetType;
|
import com.moulberry.axiom.world_properties.WorldPropertyWidgetType;
|
||||||
|
import com.moulberry.axiom.world_properties.server.PropertyUpdateResult;
|
||||||
import com.moulberry.axiom.world_properties.server.ServerWorldProperty;
|
import com.moulberry.axiom.world_properties.server.ServerWorldProperty;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import org.bukkit.NamespacedKey;
|
||||||
import net.minecraft.util.Unit;
|
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
|
|
||||||
@ -15,41 +14,53 @@ import java.util.List;
|
|||||||
|
|
||||||
public class WorldPropertiesExample implements Listener {
|
public class WorldPropertiesExample implements Listener {
|
||||||
|
|
||||||
|
private static final ServerWorldProperty<Boolean> CHECKBOX = new ServerWorldProperty<>(
|
||||||
|
new NamespacedKey("axiom", "checkbox"),
|
||||||
|
"Checkbox",
|
||||||
|
false, WorldPropertyWidgetType.CHECKBOX, world -> false,
|
||||||
|
(player, world, bool) -> {
|
||||||
|
world.sendMessage(Component.text("Checkbox: " + bool)); // Do something with input
|
||||||
|
return PropertyUpdateResult.UPDATE_AND_SYNC; // sync with client
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final ServerWorldProperty<Integer> SLIDER = new ServerWorldProperty<>(
|
||||||
|
new NamespacedKey("axiom", "slider"),
|
||||||
|
"Slider",
|
||||||
|
false, new WorldPropertyWidgetType.Slider(0, 8),
|
||||||
|
world -> 4,
|
||||||
|
(player, world, integer) -> {
|
||||||
|
world.sendMessage(Component.text("Slider: " + integer)); // Do something with input
|
||||||
|
return PropertyUpdateResult.UPDATE_AND_SYNC; // sync with client
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final ServerWorldProperty<String> TEXTBOX = new ServerWorldProperty<>(
|
||||||
|
new NamespacedKey("axiom", "textbox"),
|
||||||
|
"Textbox",
|
||||||
|
false, WorldPropertyWidgetType.TEXTBOX,
|
||||||
|
world -> "Hello",
|
||||||
|
(player, world, string) -> {
|
||||||
|
world.sendMessage(Component.text("Textbox: " + string)); // Do something with input
|
||||||
|
return PropertyUpdateResult.UPDATE_AND_SYNC; // sync with client
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final ServerWorldProperty<Void> BUTTON = new ServerWorldProperty<>(
|
||||||
|
new NamespacedKey("axiom", "button"),
|
||||||
|
"Button",
|
||||||
|
false, WorldPropertyWidgetType.BUTTON,
|
||||||
|
world -> null,
|
||||||
|
(player, world, unit) -> {
|
||||||
|
world.sendMessage(Component.text("Button pressed")); // Do something with input
|
||||||
|
return PropertyUpdateResult.UPDATE_AND_SYNC; // sync with client
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
@EventHandler
|
@EventHandler
|
||||||
public void onCreateWorldProperties(AxiomCreateWorldPropertiesEvent event) {
|
public void onCreateWorldProperties(AxiomCreateWorldPropertiesEvent event) {
|
||||||
WorldPropertyCategory category = new WorldPropertyCategory("Examples", false);
|
WorldPropertyCategory category = new WorldPropertyCategory("Examples", false);
|
||||||
|
event.addCategory(category, List.of(CHECKBOX, SLIDER, TEXTBOX, BUTTON));
|
||||||
World world = event.getWorld();
|
|
||||||
|
|
||||||
ServerWorldProperty<Boolean> checkbox = new ServerWorldProperty<>(new ResourceLocation("axiom:checkbox"),
|
|
||||||
"Checkbox",
|
|
||||||
false, WorldPropertyWidgetType.CHECKBOX, false, bool -> {
|
|
||||||
world.sendMessage(Component.text("Checkbox: " + bool)); // Do something with input
|
|
||||||
return true; // true to sync with client
|
|
||||||
});
|
|
||||||
|
|
||||||
ServerWorldProperty<Integer> slider = new ServerWorldProperty<>(new ResourceLocation("axiom:slider"),
|
|
||||||
"Slider",
|
|
||||||
false, new WorldPropertyWidgetType.Slider(0, 8), 4, integer -> {
|
|
||||||
world.sendMessage(Component.text("Slider: " + integer)); // Do something with input
|
|
||||||
return true; // true to sync with client
|
|
||||||
});
|
|
||||||
|
|
||||||
ServerWorldProperty<String> textbox = new ServerWorldProperty<>(new ResourceLocation("axiom:textbox"),
|
|
||||||
"Textbox",
|
|
||||||
false, WorldPropertyWidgetType.TEXTBOX, "Hello", string -> {
|
|
||||||
world.sendMessage(Component.text("Textbox: " + string)); // Do something with input
|
|
||||||
return true; // true to sync with client
|
|
||||||
});
|
|
||||||
|
|
||||||
ServerWorldProperty<Unit> button = new ServerWorldProperty<>(new ResourceLocation("axiom:button"),
|
|
||||||
"Button",
|
|
||||||
false, WorldPropertyWidgetType.BUTTON, Unit.INSTANCE, unit -> {
|
|
||||||
world.sendMessage(Component.text("Button pressed")); // Do something with input
|
|
||||||
return true; // true to sync with client
|
|
||||||
});
|
|
||||||
|
|
||||||
event.addCategory(category, List.of(checkbox, slider, textbox, button));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
package com.moulberry.axiom.buffer;
|
package com.moulberry.axiom.buffer;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ByteMap;
|
import it.unimi.dsi.fastutil.objects.Object2ByteMap;
|
||||||
import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap;
|
import it.unimi.dsi.fastutil.objects.Object2ByteOpenHashMap;
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.world.level.biome.Biome;
|
import net.minecraft.world.level.biome.Biome;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class BiomeBuffer {
|
public class BiomeBuffer {
|
||||||
|
|
||||||
@ -27,6 +31,10 @@ public class BiomeBuffer {
|
|||||||
this.paletteSize = this.paletteReverse.size();
|
this.paletteSize = this.paletteReverse.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return this.map.size();
|
||||||
|
}
|
||||||
|
|
||||||
public void save(FriendlyByteBuf friendlyByteBuf) {
|
public void save(FriendlyByteBuf friendlyByteBuf) {
|
||||||
friendlyByteBuf.writeByte(this.paletteSize);
|
friendlyByteBuf.writeByte(this.paletteSize);
|
||||||
for (int i = 0; i < this.paletteSize; i++) {
|
for (int i = 0; i < this.paletteSize; i++) {
|
||||||
@ -35,7 +43,7 @@ public class BiomeBuffer {
|
|||||||
this.map.save(friendlyByteBuf);
|
this.map.save(friendlyByteBuf);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BiomeBuffer load(FriendlyByteBuf friendlyByteBuf) {
|
public static BiomeBuffer load(FriendlyByteBuf friendlyByteBuf, @Nullable RateLimiter rateLimiter, AtomicBoolean reachedRateLimit) {
|
||||||
int paletteSize = friendlyByteBuf.readByte();
|
int paletteSize = friendlyByteBuf.readByte();
|
||||||
ResourceKey<Biome>[] palette = new ResourceKey[255];
|
ResourceKey<Biome>[] palette = new ResourceKey[255];
|
||||||
Object2ByteMap<ResourceKey<Biome>> paletteReverse = new Object2ByteOpenHashMap<>();
|
Object2ByteMap<ResourceKey<Biome>> paletteReverse = new Object2ByteOpenHashMap<>();
|
||||||
@ -44,7 +52,7 @@ public class BiomeBuffer {
|
|||||||
palette[i] = key;
|
palette[i] = key;
|
||||||
paletteReverse.put(key, (byte)(i+1));
|
paletteReverse.put(key, (byte)(i+1));
|
||||||
}
|
}
|
||||||
Position2ByteMap map = Position2ByteMap.load(friendlyByteBuf);
|
Position2ByteMap map = Position2ByteMap.load(friendlyByteBuf, rateLimiter, reachedRateLimit);
|
||||||
return new BiomeBuffer(map, palette, paletteReverse);
|
return new BiomeBuffer(map, palette, paletteReverse);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.moulberry.axiom.buffer;
|
package com.moulberry.axiom.buffer;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
import com.moulberry.axiom.AxiomConstants;
|
import com.moulberry.axiom.AxiomConstants;
|
||||||
import com.moulberry.axiom.AxiomPaper;
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||||
@ -9,12 +10,13 @@ import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
|
|||||||
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.shorts.Short2ObjectOpenHashMap;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.world.level.block.Block;
|
|
||||||
import net.minecraft.world.level.block.Blocks;
|
import net.minecraft.world.level.block.Blocks;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class BlockBuffer {
|
public class BlockBuffer {
|
||||||
|
|
||||||
public static final BlockState EMPTY_STATE = Blocks.STRUCTURE_VOID.defaultBlockState();
|
public static final BlockState EMPTY_STATE = Blocks.STRUCTURE_VOID.defaultBlockState();
|
||||||
@ -24,6 +26,8 @@ public class BlockBuffer {
|
|||||||
private PalettedContainer<BlockState> last = null;
|
private PalettedContainer<BlockState> last = null;
|
||||||
private long lastId = AxiomConstants.MIN_POSITION_LONG;
|
private long lastId = AxiomConstants.MIN_POSITION_LONG;
|
||||||
private final Long2ObjectMap<Short2ObjectMap<CompressedBlockEntity>> blockEntities = new Long2ObjectOpenHashMap<>();
|
private final Long2ObjectMap<Short2ObjectMap<CompressedBlockEntity>> blockEntities = new Long2ObjectOpenHashMap<>();
|
||||||
|
private long totalBlockEntities = 0;
|
||||||
|
private long totalBlockEntityBytes = 0;
|
||||||
|
|
||||||
public BlockBuffer() {
|
public BlockBuffer() {
|
||||||
this.values = new Long2ObjectOpenHashMap<>();
|
this.values = new Long2ObjectOpenHashMap<>();
|
||||||
@ -53,54 +57,57 @@ public class BlockBuffer {
|
|||||||
friendlyByteBuf.writeLong(AxiomConstants.MIN_POSITION_LONG);
|
friendlyByteBuf.writeLong(AxiomConstants.MIN_POSITION_LONG);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static BlockBuffer load(FriendlyByteBuf friendlyByteBuf) {
|
public static BlockBuffer load(FriendlyByteBuf friendlyByteBuf, @Nullable RateLimiter rateLimiter, AtomicBoolean reachedRateLimit) {
|
||||||
BlockBuffer buffer = new BlockBuffer();
|
BlockBuffer buffer = new BlockBuffer();
|
||||||
|
|
||||||
|
long totalBlockEntities = 0;
|
||||||
|
long totalBlockEntityBytes = 0;
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
long index = friendlyByteBuf.readLong();
|
long index = friendlyByteBuf.readLong();
|
||||||
if (index == AxiomConstants.MIN_POSITION_LONG) break;
|
if (index == AxiomConstants.MIN_POSITION_LONG) break;
|
||||||
|
|
||||||
|
if (rateLimiter != null) {
|
||||||
|
if (!rateLimiter.tryAcquire()) {
|
||||||
|
reachedRateLimit.set(true);
|
||||||
|
buffer.totalBlockEntities = totalBlockEntities;
|
||||||
|
buffer.totalBlockEntityBytes = totalBlockEntityBytes;
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
PalettedContainer<BlockState> palettedContainer = buffer.getOrCreateSection(index);
|
PalettedContainer<BlockState> palettedContainer = buffer.getOrCreateSection(index);
|
||||||
palettedContainer.read(friendlyByteBuf);
|
palettedContainer.read(friendlyByteBuf);
|
||||||
|
|
||||||
int blockEntitySize = Math.min(4096, friendlyByteBuf.readVarInt());
|
int blockEntitySize = Math.min(4096, friendlyByteBuf.readVarInt());
|
||||||
if (blockEntitySize > 0) {
|
if (blockEntitySize > 0) {
|
||||||
Short2ObjectMap<CompressedBlockEntity> map = new Short2ObjectOpenHashMap<>(blockEntitySize);
|
Short2ObjectMap<CompressedBlockEntity> map = new Short2ObjectOpenHashMap<>(blockEntitySize);
|
||||||
|
|
||||||
|
int startIndex = friendlyByteBuf.readerIndex();
|
||||||
|
|
||||||
for (int i = 0; i < blockEntitySize; i++) {
|
for (int i = 0; i < blockEntitySize; i++) {
|
||||||
short offset = friendlyByteBuf.readShort();
|
short offset = friendlyByteBuf.readShort();
|
||||||
CompressedBlockEntity blockEntity = CompressedBlockEntity.read(friendlyByteBuf);
|
CompressedBlockEntity blockEntity = CompressedBlockEntity.read(friendlyByteBuf);
|
||||||
map.put(offset, blockEntity);
|
map.put(offset, blockEntity);
|
||||||
}
|
}
|
||||||
|
|
||||||
buffer.blockEntities.put(index, map);
|
buffer.blockEntities.put(index, map);
|
||||||
|
totalBlockEntities += blockEntitySize;
|
||||||
|
totalBlockEntityBytes += friendlyByteBuf.readerIndex() - startIndex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
buffer.totalBlockEntities = totalBlockEntities;
|
||||||
|
buffer.totalBlockEntityBytes = totalBlockEntityBytes;
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void clear() {
|
public long getTotalBlockEntities() {
|
||||||
this.last = null;
|
return this.totalBlockEntities;
|
||||||
this.lastId = AxiomConstants.MIN_POSITION_LONG;
|
|
||||||
this.values.clear();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void putBlockEntity(int x, int y, int z, CompressedBlockEntity blockEntity) {
|
public long getTotalBlockEntityBytes() {
|
||||||
long cpos = BlockPos.asLong(x >> 4, y >> 4, z >> 4);
|
return this.totalBlockEntityBytes;
|
||||||
Short2ObjectMap<CompressedBlockEntity> chunkMap = this.blockEntities.computeIfAbsent(cpos, k -> new Short2ObjectOpenHashMap<>());
|
|
||||||
|
|
||||||
int key = (x & 0xF) | ((y & 0xF) << 4) | ((z & 0xF) << 8);
|
|
||||||
chunkMap.put((short)key, blockEntity);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable
|
|
||||||
public CompressedBlockEntity getBlockEntity(int x, int y, int z) {
|
|
||||||
long cpos = BlockPos.asLong(x >> 4, y >> 4, z >> 4);
|
|
||||||
Short2ObjectMap<CompressedBlockEntity> chunkMap = this.blockEntities.get(cpos);
|
|
||||||
|
|
||||||
if (chunkMap == null) return null;
|
|
||||||
|
|
||||||
int key = (x & 0xF) | ((y & 0xF) << 4) | ((z & 0xF) << 8);
|
|
||||||
return chunkMap.get((short)key);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -170,7 +177,7 @@ public class BlockBuffer {
|
|||||||
public PalettedContainer<BlockState> getOrCreateSection(long id) {
|
public PalettedContainer<BlockState> getOrCreateSection(long id) {
|
||||||
if (this.last == null || id != this.lastId) {
|
if (this.last == null || id != this.lastId) {
|
||||||
this.lastId = id;
|
this.lastId = id;
|
||||||
this.last = this.values.computeIfAbsent(id, k -> new PalettedContainer<>(Block.BLOCK_STATE_REGISTRY,
|
this.last = this.values.computeIfAbsent(id, k -> new PalettedContainer<>(AxiomPaper.PLUGIN.allowedBlockRegistry,
|
||||||
EMPTY_STATE, PalettedContainer.Strategy.SECTION_STATES));
|
EMPTY_STATE, PalettedContainer.Strategy.SECTION_STATES));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ import com.github.luben.zstd.ZstdDictCompress;
|
|||||||
import com.github.luben.zstd.ZstdDictDecompress;
|
import com.github.luben.zstd.ZstdDictDecompress;
|
||||||
import com.moulberry.axiom.AxiomPaper;
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.NbtAccounter;
|
||||||
import net.minecraft.nbt.NbtIo;
|
import net.minecraft.nbt.NbtIo;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
|
||||||
@ -44,7 +45,7 @@ public record CompressedBlockEntity(int originalSize, byte compressionDict, byte
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
byte[] nbt = Zstd.decompress(this.compressed, zstdDictDecompress, this.originalSize);
|
byte[] nbt = Zstd.decompress(this.compressed, zstdDictDecompress, this.originalSize);
|
||||||
return NbtIo.read(new DataInputStream(new ByteArrayInputStream(nbt)));
|
return NbtIo.read(new DataInputStream(new ByteArrayInputStream(nbt)), new NbtAccounter(131072));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException(e);
|
throw new RuntimeException(e);
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,15 @@
|
|||||||
package com.moulberry.axiom.buffer;
|
package com.moulberry.axiom.buffer;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
import com.moulberry.axiom.AxiomConstants;
|
import com.moulberry.axiom.AxiomConstants;
|
||||||
import com.moulberry.axiom.AxiomPaper;
|
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.function.LongFunction;
|
import java.util.function.LongFunction;
|
||||||
|
|
||||||
public class Position2ByteMap {
|
public class Position2ByteMap {
|
||||||
@ -42,6 +44,10 @@ public class Position2ByteMap {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int size() {
|
||||||
|
return this.map.size();
|
||||||
|
}
|
||||||
|
|
||||||
public void save(FriendlyByteBuf friendlyByteBuf) {
|
public void save(FriendlyByteBuf friendlyByteBuf) {
|
||||||
friendlyByteBuf.writeByte(this.defaultValue);
|
friendlyByteBuf.writeByte(this.defaultValue);
|
||||||
for (Long2ObjectMap.Entry<byte[]> entry : this.map.long2ObjectEntrySet()) {
|
for (Long2ObjectMap.Entry<byte[]> entry : this.map.long2ObjectEntrySet()) {
|
||||||
@ -51,13 +57,20 @@ public class Position2ByteMap {
|
|||||||
friendlyByteBuf.writeLong(AxiomConstants.MIN_POSITION_LONG);
|
friendlyByteBuf.writeLong(AxiomConstants.MIN_POSITION_LONG);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Position2ByteMap load(FriendlyByteBuf friendlyByteBuf) {
|
public static Position2ByteMap load(FriendlyByteBuf friendlyByteBuf, @Nullable RateLimiter rateLimiter, AtomicBoolean reachedRateLimit) {
|
||||||
Position2ByteMap map = new Position2ByteMap(friendlyByteBuf.readByte());
|
Position2ByteMap map = new Position2ByteMap(friendlyByteBuf.readByte());
|
||||||
|
|
||||||
while (true) {
|
while (true) {
|
||||||
long pos = friendlyByteBuf.readLong();
|
long pos = friendlyByteBuf.readLong();
|
||||||
if (pos == AxiomConstants.MIN_POSITION_LONG) break;
|
if (pos == AxiomConstants.MIN_POSITION_LONG) break;
|
||||||
|
|
||||||
|
if (rateLimiter != null) {
|
||||||
|
if (!rateLimiter.tryAcquire()) {
|
||||||
|
reachedRateLimit.set(true);
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
byte[] bytes = new byte[16*16*16];
|
byte[] bytes = new byte[16*16*16];
|
||||||
friendlyByteBuf.readBytes(bytes);
|
friendlyByteBuf.readBytes(bytes);
|
||||||
map.map.put(pos, bytes);
|
map.map.put(pos, bytes);
|
||||||
|
@ -2,14 +2,12 @@ package com.moulberry.axiom.event;
|
|||||||
|
|
||||||
import com.moulberry.axiom.world_properties.WorldPropertyCategory;
|
import com.moulberry.axiom.world_properties.WorldPropertyCategory;
|
||||||
import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry;
|
import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry;
|
||||||
import com.moulberry.axiom.world_properties.server.ServerWorldProperty;
|
import com.moulberry.axiom.world_properties.server.ServerWorldPropertyBase;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.entity.Player;
|
|
||||||
import org.bukkit.event.Cancellable;
|
import org.bukkit.event.Cancellable;
|
||||||
import org.bukkit.event.Event;
|
import org.bukkit.event.Event;
|
||||||
import org.bukkit.event.HandlerList;
|
import org.bukkit.event.HandlerList;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -30,7 +28,7 @@ public class AxiomCreateWorldPropertiesEvent extends Event implements Cancellabl
|
|||||||
return world;
|
return world;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCategory(WorldPropertyCategory category, List<ServerWorldProperty<?>> properties) {
|
public void addCategory(WorldPropertyCategory category, List<ServerWorldPropertyBase<?>> properties) {
|
||||||
this.registry.addCategory(category, properties);
|
this.registry.addCategory(category, properties);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
package com.moulberry.axiom.event;
|
package com.moulberry.axiom.event;
|
||||||
|
|
||||||
import org.bukkit.GameMode;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.Cancellable;
|
import org.bukkit.event.Cancellable;
|
||||||
import org.bukkit.event.Event;
|
import org.bukkit.event.Event;
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package com.moulberry.axiom.event;
|
package com.moulberry.axiom.event;
|
||||||
|
|
||||||
import org.bukkit.GameMode;
|
import org.bukkit.GameMode;
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.Cancellable;
|
import org.bukkit.event.Cancellable;
|
||||||
import org.bukkit.event.Event;
|
import org.bukkit.event.Event;
|
||||||
|
@ -12,10 +12,11 @@ public class AxiomHandshakeEvent extends Event implements Cancellable {
|
|||||||
|
|
||||||
private final Player player;
|
private final Player player;
|
||||||
private boolean cancelled = false;
|
private boolean cancelled = false;
|
||||||
private int maxBufferSize = Short.MAX_VALUE;
|
private int maxBufferSize;
|
||||||
|
|
||||||
public AxiomHandshakeEvent(Player player) {
|
public AxiomHandshakeEvent(Player player, int maxBufferSize) {
|
||||||
this.player = player;
|
this.player = player;
|
||||||
|
this.maxBufferSize = maxBufferSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Player getPlayer() {
|
public Player getPlayer() {
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package com.moulberry.axiom.event;
|
package com.moulberry.axiom.event;
|
||||||
|
|
||||||
import org.bukkit.Location;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.World;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.Cancellable;
|
import org.bukkit.event.Cancellable;
|
||||||
import org.bukkit.event.Event;
|
import org.bukkit.event.Event;
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
package com.moulberry.axiom.event;
|
||||||
|
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.Cancellable;
|
||||||
|
import org.bukkit.event.Event;
|
||||||
|
import org.bukkit.event.HandlerList;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
public class AxiomUnknownTeleportEvent extends Event implements Cancellable {
|
||||||
|
|
||||||
|
private static final HandlerList HANDLERS = new HandlerList();
|
||||||
|
|
||||||
|
private final Player player;
|
||||||
|
private final NamespacedKey world;
|
||||||
|
private final double x;
|
||||||
|
private final double y;
|
||||||
|
private final double z;
|
||||||
|
private final float yaw;
|
||||||
|
private final float pitch;
|
||||||
|
private boolean cancelled = false;
|
||||||
|
|
||||||
|
public AxiomUnknownTeleportEvent(Player player, NamespacedKey world, double x, double y, double z, float yaw, float pitch) {
|
||||||
|
this.player = player;
|
||||||
|
this.world = world;
|
||||||
|
this.x = x;
|
||||||
|
this.y = y;
|
||||||
|
this.z = z;
|
||||||
|
this.yaw = yaw;
|
||||||
|
this.pitch = pitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Player getPlayer() {
|
||||||
|
return this.player;
|
||||||
|
}
|
||||||
|
|
||||||
|
public NamespacedKey getWorld() {
|
||||||
|
return this.world;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getX() {
|
||||||
|
return this.x;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getY() {
|
||||||
|
return this.y;
|
||||||
|
}
|
||||||
|
|
||||||
|
public double getZ() {
|
||||||
|
return this.z;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getYaw() {
|
||||||
|
return this.yaw;
|
||||||
|
}
|
||||||
|
|
||||||
|
public float getPitch() {
|
||||||
|
return this.pitch;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isCancelled() {
|
||||||
|
return this.cancelled;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setCancelled(boolean cancel) {
|
||||||
|
this.cancelled = cancel;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HandlerList getHandlerList() {
|
||||||
|
return HANDLERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @NotNull HandlerList getHandlers() {
|
||||||
|
return HANDLERS;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
67
src/main/java/com/moulberry/axiom/integration/Box.java
Normale Datei
67
src/main/java/com/moulberry/axiom/integration/Box.java
Normale Datei
@ -0,0 +1,67 @@
|
|||||||
|
package com.moulberry.axiom.integration;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
public record Box(int minX, int minY, int minZ, int maxX, int maxY, int maxZ) {
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
public Box tryCombine(Box other) {
|
||||||
|
if (this.completelyOverlaps(other)) {
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other.completelyOverlaps(this)) {
|
||||||
|
return other;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (other.minX == this.minX && other.maxX == this.maxX) {
|
||||||
|
if (other.minY == this.minY && other.maxY == this.maxY) {
|
||||||
|
if (areLineSegmentsContinuous(other.minZ, other.maxZ, this.minZ, this.maxZ)) {
|
||||||
|
return new Box(
|
||||||
|
other.minX, other.minY, Math.min(other.minZ, this.minZ),
|
||||||
|
other.maxX, other.maxY, Math.max(other.maxZ, this.maxZ)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else if (other.minZ == this.minZ && other.maxZ == this.maxZ) {
|
||||||
|
if (areLineSegmentsContinuous(other.minY, other.maxY, this.minY, this.maxY)) {
|
||||||
|
return new Box(
|
||||||
|
other.minX, Math.min(other.minY, this.minY), other.minZ,
|
||||||
|
other.maxX, Math.max(other.maxY, this.maxY), other.maxZ
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (other.minY == this.minY && other.maxY == this.maxY &&
|
||||||
|
other.minZ == this.minZ && other.maxZ == this.maxZ) {
|
||||||
|
if (areLineSegmentsContinuous(other.minX, other.maxX, this.minX, this.maxX)) {
|
||||||
|
return new Box(
|
||||||
|
Math.min(other.minX, this.minX), other.minY, other.minZ,
|
||||||
|
Math.max(other.maxX, this.maxX), other.maxY, other.maxZ
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null; // Not able to combine
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean completelyOverlaps(Box other) {
|
||||||
|
return this.minX() <= other.minX() && this.minY() <= other.minY() && this.minZ() <= other.minZ() &&
|
||||||
|
this.maxX() >= other.maxX() && this.maxY() >= other.maxY() && this.maxZ() >= other.maxZ();
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean contains(int x, int y, int z) {
|
||||||
|
return this.minX() <= x && this.minY() <= y && this.minZ() <= z &&
|
||||||
|
this.maxX() >= x && this.maxY() >= y && this.maxZ() >= z;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean areLineSegmentsContinuous(int min1, int max1, int min2, int max2) {
|
||||||
|
int size1 = max1 - min1 + 1;
|
||||||
|
int size2 = max2 - min2 + 1;
|
||||||
|
|
||||||
|
float mid1 = (min1 + max1);
|
||||||
|
float mid2 = (min2 + max2);
|
||||||
|
|
||||||
|
float midDiff = Math.abs(mid1 - mid2);
|
||||||
|
return midDiff <= size1 + size2;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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<SectionProtection> 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -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<ProtectedRegion, StateFlag.State> consideredValues = new HashMap<>();
|
|
||||||
Set<ProtectedRegion> 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<V>) 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<ProtectedRegion, StateFlag.State> 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<ProtectedRegion> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,137 @@
|
|||||||
|
package com.moulberry.axiom.integration;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
public interface SectionPermissionChecker {
|
||||||
|
|
||||||
|
boolean allAllowed();
|
||||||
|
boolean noneAllowed();
|
||||||
|
boolean allowed(int x, int y, int z);
|
||||||
|
Box bounds();
|
||||||
|
|
||||||
|
static SectionPermissionChecker fromAllowedBoxes(List<Box> allowed) {
|
||||||
|
if (allowed.isEmpty()) return NONE_ALLOWED;
|
||||||
|
|
||||||
|
if (allowed.size() == 1) {
|
||||||
|
Box allowedBox = allowed.get(0);
|
||||||
|
if (allowedBox.completelyOverlaps(FULL_BOUNDS)) {
|
||||||
|
return ALL_ALLOWED;
|
||||||
|
} else {
|
||||||
|
return new AllAllowedInBox(allowedBox);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int minBoundsX = 15;
|
||||||
|
int minBoundsY = 15;
|
||||||
|
int minBoundsZ = 15;
|
||||||
|
int maxBoundsX = 0;
|
||||||
|
int maxBoundsY = 0;
|
||||||
|
int maxBoundsZ = 0;
|
||||||
|
|
||||||
|
for (Box box : allowed) {
|
||||||
|
minBoundsX = Math.min(box.minX(), minBoundsX);
|
||||||
|
minBoundsY = Math.min(box.minY(), minBoundsY);
|
||||||
|
minBoundsZ = Math.min(box.minZ(), minBoundsZ);
|
||||||
|
maxBoundsX = Math.max(box.maxX(), maxBoundsX);
|
||||||
|
maxBoundsY = Math.max(box.maxY(), maxBoundsY);
|
||||||
|
maxBoundsZ = Math.max(box.maxZ(), maxBoundsZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new AllAllowedBoxes(new Box(minBoundsX, minBoundsY, minBoundsZ, maxBoundsX, maxBoundsY, maxBoundsZ), allowed);
|
||||||
|
}
|
||||||
|
|
||||||
|
record AllAllowedInBox(Box box) implements SectionPermissionChecker {
|
||||||
|
@Override
|
||||||
|
public boolean allAllowed() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean noneAllowed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allowed(int x, int y, int z) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Box bounds() {
|
||||||
|
return box;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
record AllAllowedBoxes(Box bounds, List<Box> allowed) implements SectionPermissionChecker {
|
||||||
|
@Override
|
||||||
|
public boolean allAllowed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean noneAllowed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allowed(int x, int y, int z) {
|
||||||
|
for (Box box : this.allowed) {
|
||||||
|
if (box.contains(x, y, z)) return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Box bounds() {
|
||||||
|
return this.bounds;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Box FULL_BOUNDS = new Box(0, 0, 0, 15, 15, 15);
|
||||||
|
SectionPermissionChecker ALL_ALLOWED = new SectionPermissionChecker() {
|
||||||
|
@Override
|
||||||
|
public boolean allAllowed() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean noneAllowed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allowed(int x, int y, int z) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Box bounds() {
|
||||||
|
return FULL_BOUNDS;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
Box EMPTY_BOUNDS = new Box(0, 0, 0, 0, 0, 0);
|
||||||
|
SectionPermissionChecker NONE_ALLOWED = new SectionPermissionChecker() {
|
||||||
|
@Override
|
||||||
|
public boolean allAllowed() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean noneAllowed() {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean allowed(int x, int y, int z) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Box bounds() {
|
||||||
|
return EMPTY_BOUNDS;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
@ -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);
|
|
||||||
|
|
||||||
}
|
|
@ -0,0 +1,68 @@
|
|||||||
|
package com.moulberry.axiom.integration.plotsquared;
|
||||||
|
|
||||||
|
|
||||||
|
import com.moulberry.axiom.integration.SectionPermissionChecker;
|
||||||
|
import com.sk89q.worldedit.regions.CuboidRegion;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if (!Bukkit.getPluginManager().isPluginEnabled("PlotSquared")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return PlotSquaredIntegrationImpl.canBreakBlock(player, block);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean canPlaceBlock(Player player, org.bukkit.Location loc) {
|
||||||
|
if (!Bukkit.getPluginManager().isPluginEnabled("PlotSquared")) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return PlotSquaredIntegrationImpl.canPlaceBlock(player, loc);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static boolean isPlotWorld(World world) {
|
||||||
|
if (!Bukkit.getPluginManager().isPluginEnabled("PlotSquared")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
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) {
|
||||||
|
if (!Bukkit.getPluginManager().isPluginEnabled("PlotSquared")) {
|
||||||
|
return SectionPermissionChecker.ALL_ALLOWED;
|
||||||
|
}
|
||||||
|
return PlotSquaredIntegrationImpl.checkSection(player, world, sectionX, sectionY, sectionZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,268 @@
|
|||||||
|
package com.moulberry.axiom.integration.plotsquared;
|
||||||
|
|
||||||
|
import com.moulberry.axiom.integration.Box;
|
||||||
|
import com.moulberry.axiom.integration.SectionPermissionChecker;
|
||||||
|
import com.plotsquared.bukkit.player.BukkitPlayer;
|
||||||
|
import com.plotsquared.bukkit.util.BukkitUtil;
|
||||||
|
import com.plotsquared.core.PlotSquared;
|
||||||
|
import com.plotsquared.core.configuration.Settings;
|
||||||
|
import com.plotsquared.core.location.Location;
|
||||||
|
import com.plotsquared.core.permissions.Permission;
|
||||||
|
import com.plotsquared.core.plot.Plot;
|
||||||
|
import com.plotsquared.core.plot.PlotArea;
|
||||||
|
import com.plotsquared.core.plot.PlotId;
|
||||||
|
import com.plotsquared.core.plot.flag.implementations.BreakFlag;
|
||||||
|
import com.plotsquared.core.plot.flag.implementations.DoneFlag;
|
||||||
|
import com.plotsquared.core.plot.flag.types.BlockTypeWrapper;
|
||||||
|
import com.sk89q.worldedit.bukkit.BukkitAdapter;
|
||||||
|
import com.sk89q.worldedit.math.BlockVector3;
|
||||||
|
import com.sk89q.worldedit.regions.CuboidRegion;
|
||||||
|
import com.sk89q.worldedit.world.block.BlockType;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.block.Block;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
public class PlotSquaredIntegrationImpl {
|
||||||
|
|
||||||
|
static boolean canBreakBlock(Player player, Block block) {
|
||||||
|
Location location = BukkitUtil.adapt(block.getLocation());
|
||||||
|
PlotArea area = location.getPlotArea();
|
||||||
|
if (area == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Plot plot = area.getPlot(location);
|
||||||
|
if (plot != null) {
|
||||||
|
BukkitPlayer plotPlayer = BukkitUtil.adapt(player);
|
||||||
|
// == rather than <= as we only care about the "ground level" not being destroyed
|
||||||
|
if (block.getY() == area.getMinGenHeight()) {
|
||||||
|
if (!plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_GROUNDLEVEL, true)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (area.notifyIfOutsideBuildArea(plotPlayer, location.getY())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// check unowned plots
|
||||||
|
if (!plot.hasOwner()) {
|
||||||
|
return plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_UNOWNED, true);
|
||||||
|
}
|
||||||
|
// player is breaking another player's plot
|
||||||
|
if (!plot.isAdded(plotPlayer.getUUID())) {
|
||||||
|
List<BlockTypeWrapper> destroy = plot.getFlag(BreakFlag.class);
|
||||||
|
final BlockType blockType = BukkitAdapter.asBlockType(block.getType());
|
||||||
|
for (final BlockTypeWrapper blockTypeWrapper : destroy) {
|
||||||
|
if (blockTypeWrapper.accepts(blockType)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_OTHER, true);
|
||||||
|
}
|
||||||
|
// plot is 'done'
|
||||||
|
if (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(plot)) {
|
||||||
|
return plotPlayer.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER, true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
BukkitPlayer pp = BukkitUtil.adapt(player);
|
||||||
|
return pp.hasPermission(Permission.PERMISSION_ADMIN_DESTROY_ROAD, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
static boolean canPlaceBlock(Player player, org.bukkit.Location loc) {
|
||||||
|
Location location = BukkitUtil.adapt(loc);
|
||||||
|
PlotArea area = location.getPlotArea();
|
||||||
|
if (area == null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
BukkitPlayer pp = BukkitUtil.adapt(player);
|
||||||
|
Plot plot = area.getPlot(location);
|
||||||
|
if (plot != null) {
|
||||||
|
if (area.notifyIfOutsideBuildArea(pp, location.getY())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// check unowned plots
|
||||||
|
if (!plot.hasOwner()) {
|
||||||
|
return pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_UNOWNED, true);
|
||||||
|
}
|
||||||
|
// player is breaking another player's plot
|
||||||
|
if (!plot.isAdded(pp.getUUID())) {
|
||||||
|
return pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER, true);
|
||||||
|
}
|
||||||
|
// plot is 'done'
|
||||||
|
if (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(plot)) {
|
||||||
|
return pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_OTHER, true);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pp.hasPermission(Permission.PERMISSION_ADMIN_BUILD_ROAD, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final WeakHashMap<World, Boolean> plotWorldCache = new WeakHashMap<>();
|
||||||
|
|
||||||
|
static boolean isPlotWorld(World world) {
|
||||||
|
if (plotWorldCache.containsKey(world)) {
|
||||||
|
return plotWorldCache.get(world);
|
||||||
|
}
|
||||||
|
|
||||||
|
String worldName = world.getName();
|
||||||
|
PlotArea[] plotAreas = PlotSquared.get().getPlotAreaManager().getPlotAreas(worldName, null);
|
||||||
|
boolean isPlotWorld = plotAreas.length > 0;
|
||||||
|
plotWorldCache.put(world, 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) {
|
||||||
|
int minX = sectionX * 16;
|
||||||
|
int minY = sectionY * 16;
|
||||||
|
int minZ = sectionZ * 16;
|
||||||
|
int maxX = sectionX * 16 + 15;
|
||||||
|
int maxY = sectionY * 16 + 15;
|
||||||
|
int maxZ = sectionZ * 16 + 15;
|
||||||
|
|
||||||
|
PlotArea[] plotAreas = PlotSquared.get().getPlotAreaManager().getPlotAreas(world.getName(), new CuboidRegion(
|
||||||
|
BlockVector3.at(minX, minY, minZ),
|
||||||
|
BlockVector3.at(maxX, maxY, maxZ)
|
||||||
|
));
|
||||||
|
|
||||||
|
if (plotAreas.length == 0) {
|
||||||
|
return SectionPermissionChecker.ALL_ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<Plot> checkedPlots = new HashSet<>();
|
||||||
|
List<Box> allowed = new ArrayList<>();
|
||||||
|
|
||||||
|
for (PlotArea plotArea : plotAreas) {
|
||||||
|
for (int px = minX; px <= maxX; px += 15) {
|
||||||
|
for (int py = minY; py <= maxY; py += 15) {
|
||||||
|
for (int pz = minZ; pz <= maxZ; pz += 15) {
|
||||||
|
PlotId pid = plotArea.getPlotManager().getPlotId(px, py, pz);
|
||||||
|
if (pid == null) continue;
|
||||||
|
Plot plot = plotArea.getOwnedPlot(pid);
|
||||||
|
if (plot == null) continue;
|
||||||
|
|
||||||
|
if (!checkedPlots.add(plot)) continue;
|
||||||
|
|
||||||
|
if (!plot.hasOwner()) continue;
|
||||||
|
if (!plot.isAdded(player.getUniqueId())) continue;
|
||||||
|
if (Settings.Done.RESTRICT_BUILDING && DoneFlag.isDone(plot)) continue;
|
||||||
|
|
||||||
|
for (CuboidRegion region : plot.getRegions()) {
|
||||||
|
BlockVector3 minPoint = region.getMinimumPoint();
|
||||||
|
BlockVector3 maxPoint = region.getMaximumPoint();
|
||||||
|
|
||||||
|
int minPlotX = Math.max(minPoint.getX(), minX);
|
||||||
|
int minPlotY = Math.max(minPoint.getY(), minY);
|
||||||
|
int minPlotZ = Math.max(minPoint.getZ(), minZ);
|
||||||
|
int maxPlotX = Math.min(maxPoint.getX(), maxX);
|
||||||
|
int maxPlotY = Math.min(maxPoint.getY(), maxY);
|
||||||
|
int maxPlotZ = Math.min(maxPoint.getZ(), maxZ);
|
||||||
|
|
||||||
|
if (minPlotX > maxPlotX) continue;
|
||||||
|
if (minPlotY > maxPlotY) continue;
|
||||||
|
if (minPlotZ > maxPlotZ) continue;
|
||||||
|
|
||||||
|
if (minPlotX <= minX && minPlotY <= minY && minPlotZ <= minZ &&
|
||||||
|
maxPlotX >= maxX && maxPlotY >= maxY && maxPlotZ >= maxZ) {
|
||||||
|
return SectionPermissionChecker.ALL_ALLOWED;
|
||||||
|
}
|
||||||
|
|
||||||
|
allowed.add(new Box(minPlotX - minX, minPlotY - minY, minPlotZ - minZ,
|
||||||
|
maxPlotX - minX, maxPlotY - minY, maxPlotZ - minZ));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Combine
|
||||||
|
main:
|
||||||
|
while (allowed.size() >= 2) {
|
||||||
|
for (int i = 0; i < allowed.size() - 1; i++) {
|
||||||
|
Box first = allowed.get(i);
|
||||||
|
for (int j = i + 1; j < allowed.size(); j++) {
|
||||||
|
Box second = allowed.get(j);
|
||||||
|
|
||||||
|
Box combined = first.tryCombine(second);
|
||||||
|
if (combined != null) {
|
||||||
|
allowed.remove(j);
|
||||||
|
allowed.remove(i);
|
||||||
|
allowed.add(combined);
|
||||||
|
continue main;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return SectionPermissionChecker.fromAllowedBoxes(allowed);
|
||||||
|
}
|
||||||
|
}
|
102
src/main/java/com/moulberry/axiom/marker/MarkerData.java
Normale Datei
102
src/main/java/com/moulberry/axiom/marker/MarkerData.java
Normale Datei
@ -0,0 +1,102 @@
|
|||||||
|
package com.moulberry.axiom.marker;
|
||||||
|
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.ListTag;
|
||||||
|
import net.minecraft.nbt.Tag;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.world.entity.Marker;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
import xyz.jpenilla.reflectionremapper.ReflectionRemapper;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public record MarkerData(UUID uuid, Vec3 position, @Nullable String name, @Nullable Vec3 minRegion, @Nullable Vec3 maxRegion) {
|
||||||
|
public static MarkerData read(FriendlyByteBuf friendlyByteBuf) {
|
||||||
|
UUID uuid = friendlyByteBuf.readUUID();
|
||||||
|
Vec3 position = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
|
||||||
|
String name = friendlyByteBuf.readNullable(FriendlyByteBuf::readUtf);
|
||||||
|
|
||||||
|
Vec3 minRegion = null;
|
||||||
|
Vec3 maxRegion = null;
|
||||||
|
if (friendlyByteBuf.readBoolean()) {
|
||||||
|
minRegion = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
|
||||||
|
maxRegion = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MarkerData(uuid, position, name, minRegion, maxRegion);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void write(FriendlyByteBuf friendlyByteBuf, MarkerData markerData) {
|
||||||
|
friendlyByteBuf.writeUUID(markerData.uuid);
|
||||||
|
friendlyByteBuf.writeDouble(markerData.position.x);
|
||||||
|
friendlyByteBuf.writeDouble(markerData.position.y);
|
||||||
|
friendlyByteBuf.writeDouble(markerData.position.z);
|
||||||
|
friendlyByteBuf.writeNullable(markerData.name, FriendlyByteBuf::writeUtf);
|
||||||
|
|
||||||
|
if (markerData.minRegion != null && markerData.maxRegion != null) {
|
||||||
|
friendlyByteBuf.writeBoolean(true);
|
||||||
|
friendlyByteBuf.writeDouble(markerData.minRegion.x);
|
||||||
|
friendlyByteBuf.writeDouble(markerData.minRegion.y);
|
||||||
|
friendlyByteBuf.writeDouble(markerData.minRegion.z);
|
||||||
|
friendlyByteBuf.writeDouble(markerData.maxRegion.x);
|
||||||
|
friendlyByteBuf.writeDouble(markerData.maxRegion.y);
|
||||||
|
friendlyByteBuf.writeDouble(markerData.maxRegion.z);
|
||||||
|
} else {
|
||||||
|
friendlyByteBuf.writeBoolean(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Field dataField;
|
||||||
|
static {
|
||||||
|
ReflectionRemapper reflectionRemapper = ReflectionRemapper.forReobfMappingsInPaperJar();
|
||||||
|
String fieldName = reflectionRemapper.remapFieldName(Marker.class, "data");
|
||||||
|
|
||||||
|
try {
|
||||||
|
dataField = Marker.class.getDeclaredField(fieldName);
|
||||||
|
dataField.setAccessible(true);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static CompoundTag getData(Marker marker) {
|
||||||
|
try {
|
||||||
|
return (CompoundTag) dataField.get(marker);
|
||||||
|
} catch (Exception e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static MarkerData createFrom(Marker marker) {
|
||||||
|
Vec3 position = marker.position();
|
||||||
|
CompoundTag data = getData(marker);
|
||||||
|
|
||||||
|
String name = data.getString("name").trim();
|
||||||
|
if (name.isEmpty()) name = null;
|
||||||
|
|
||||||
|
Vec3 minRegion = null;
|
||||||
|
Vec3 maxRegion = null;
|
||||||
|
if (data.contains("min", Tag.TAG_LIST) && data.contains("max", Tag.TAG_LIST)) {
|
||||||
|
ListTag min = data.getList("min", Tag.TAG_DOUBLE);
|
||||||
|
ListTag max = data.getList("max", Tag.TAG_DOUBLE);
|
||||||
|
|
||||||
|
if (min.size() == 3 && max.size() == 3) {
|
||||||
|
double minX = min.getDouble(0);
|
||||||
|
double minY = min.getDouble(1);
|
||||||
|
double minZ = min.getDouble(2);
|
||||||
|
double maxX = max.getDouble(0);
|
||||||
|
double maxY = max.getDouble(1);
|
||||||
|
double maxZ = max.getDouble(2);
|
||||||
|
minRegion = new Vec3(minX, minY, minZ);
|
||||||
|
maxRegion = new Vec3(maxX, maxY, maxZ);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return new MarkerData(marker.getUUID(), position, name, minRegion, maxRegion);
|
||||||
|
}
|
||||||
|
}
|
@ -26,10 +26,17 @@ public class AxiomBigPayloadHandler extends ByteToMessageDecoder {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
|
||||||
try {
|
// Don't process if channel isn't active
|
||||||
|
if (!ctx.channel().isActive()) {
|
||||||
|
in.skipBytes(in.readableBytes());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int i = in.readableBytes();
|
||||||
|
if (i != 0) {
|
||||||
int readerIndex = in.readerIndex();
|
int readerIndex = in.readerIndex();
|
||||||
int i = in.readableBytes();
|
boolean success = false;
|
||||||
if (i != 0) {
|
try {
|
||||||
FriendlyByteBuf buf = new FriendlyByteBuf(in);
|
FriendlyByteBuf buf = new FriendlyByteBuf(in);
|
||||||
int packetId = buf.readVarInt();
|
int packetId = buf.readVarInt();
|
||||||
|
|
||||||
@ -39,18 +46,27 @@ public class AxiomBigPayloadHandler extends ByteToMessageDecoder {
|
|||||||
ServerPlayer player = connection.getPlayer();
|
ServerPlayer player = connection.getPlayer();
|
||||||
if (player != null && player.getBukkitEntity().hasPermission("axiom.*")) {
|
if (player != null && player.getBukkitEntity().hasPermission("axiom.*")) {
|
||||||
if (listener.onReceive(player, buf)) {
|
if (listener.onReceive(player, buf)) {
|
||||||
|
success = true;
|
||||||
|
in.skipBytes(in.readableBytes());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (Throwable ignored) {
|
||||||
|
} finally {
|
||||||
|
if (!success) {
|
||||||
|
in.readerIndex(readerIndex);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
in.readerIndex(readerIndex);
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx.fireChannelRead(in.retain());
|
ctx.fireChannelRead(in.retain());
|
||||||
|
|
||||||
|
// Skip remaining bytes
|
||||||
|
if (in.readableBytes() > 0) {
|
||||||
|
in.skipBytes(in.readableBytes());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.entity.EntityType;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class DeleteEntityPacketListener implements PluginMessageListener {
|
||||||
|
|
||||||
|
private final AxiomPaper plugin;
|
||||||
|
public DeleteEntityPacketListener(AxiomPaper plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||||
|
if (!this.plugin.canUseAxiom(player)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.hasPermission("axiom.entity.*") && !player.hasPermission("axiom.entity.delete")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.plugin.canModifyWorld(player, player.getWorld())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||||
|
List<UUID> delete = friendlyByteBuf.readList(FriendlyByteBuf::readUUID);
|
||||||
|
|
||||||
|
ServerLevel serverLevel = ((CraftWorld)player.getWorld()).getHandle();
|
||||||
|
|
||||||
|
List<String> whitelistedEntities = this.plugin.configuration.getStringList("whitelist-entities");
|
||||||
|
List<String> blacklistedEntities = this.plugin.configuration.getStringList("blacklist-entities");
|
||||||
|
|
||||||
|
for (UUID uuid : delete) {
|
||||||
|
Entity entity = serverLevel.getEntity(uuid);
|
||||||
|
if (entity == null || entity instanceof net.minecraft.world.entity.player.Player || entity.hasPassenger(e -> e instanceof net.minecraft.world.entity.player.Player)) continue;
|
||||||
|
|
||||||
|
String type = EntityType.getKey(entity.getType()).toString();
|
||||||
|
|
||||||
|
if (!whitelistedEntities.isEmpty() && !whitelistedEntities.contains(type)) continue;
|
||||||
|
if (blacklistedEntities.contains(type)) continue;
|
||||||
|
|
||||||
|
entity.remove(Entity.RemovalReason.DISCARDED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,14 +1,17 @@
|
|||||||
package com.moulberry.axiom.packet;
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
import com.moulberry.axiom.AxiomConstants;
|
import com.moulberry.axiom.AxiomConstants;
|
||||||
import com.moulberry.axiom.AxiomPaper;
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import com.moulberry.axiom.View;
|
import com.moulberry.axiom.View;
|
||||||
|
import com.moulberry.axiom.WorldExtension;
|
||||||
import com.moulberry.axiom.event.AxiomHandshakeEvent;
|
import com.moulberry.axiom.event.AxiomHandshakeEvent;
|
||||||
import com.moulberry.axiom.persistence.ItemStackDataType;
|
import com.moulberry.axiom.persistence.ItemStackDataType;
|
||||||
import com.moulberry.axiom.persistence.UUIDDataType;
|
import com.moulberry.axiom.persistence.UUIDDataType;
|
||||||
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;
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
import net.minecraft.SharedConstants;
|
import net.minecraft.SharedConstants;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
@ -22,17 +25,14 @@ import org.bukkit.persistence.PersistentDataType;
|
|||||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
public class HelloPacketListener implements PluginMessageListener {
|
public class HelloPacketListener implements PluginMessageListener {
|
||||||
|
|
||||||
private final AxiomPaper plugin;
|
private final AxiomPaper plugin;
|
||||||
private final Set<UUID> activeAxiomPlayers;
|
|
||||||
|
|
||||||
public HelloPacketListener(AxiomPaper plugin, Set<UUID> activeAxiomPlayers) {
|
public HelloPacketListener(AxiomPaper plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.activeAxiomPlayers = activeAxiomPlayers;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -46,25 +46,64 @@ public class HelloPacketListener implements PluginMessageListener {
|
|||||||
int dataVersion = friendlyByteBuf.readVarInt();
|
int dataVersion = friendlyByteBuf.readVarInt();
|
||||||
friendlyByteBuf.readNbt(); // Discard
|
friendlyByteBuf.readNbt(); // Discard
|
||||||
|
|
||||||
if (dataVersion != SharedConstants.getCurrentVersion().getDataVersion().getVersion()) {
|
int serverDataVersion = SharedConstants.getCurrentVersion().getDataVersion().getVersion();
|
||||||
player.kick(Component.text("Axiom: Incompatible data version detected, are you using ViaVersion?"));
|
if (dataVersion != serverDataVersion) {
|
||||||
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) {
|
if (apiVersion != AxiomConstants.API_VERSION) {
|
||||||
player.kick(Component.text("Unsupported Axiom API Version. Server supports " + AxiomConstants.API_VERSION +
|
Component text = Component.text("Unsupported Axiom API Version. Server supports " + AxiomConstants.API_VERSION +
|
||||||
", while client is " + apiVersion));
|
", while client is " + apiVersion);
|
||||||
return;
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
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);
|
Bukkit.getPluginManager().callEvent(handshakeEvent);
|
||||||
if (handshakeEvent.isCancelled()) {
|
if (handshakeEvent.isCancelled()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
activeAxiomPlayers.add(player.getUniqueId());
|
this.plugin.activeAxiomPlayers.add(player.getUniqueId());
|
||||||
|
int rateLimit = this.plugin.configuration.getInt("block-buffer-rate-limit");
|
||||||
|
if (rateLimit > 0) {
|
||||||
|
this.plugin.playerBlockBufferRateLimiters.putIfAbsent(player.getUniqueId(), RateLimiter.create(rateLimit));
|
||||||
|
}
|
||||||
|
|
||||||
// Enable
|
// Enable
|
||||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||||
@ -121,13 +160,15 @@ public class HelloPacketListener implements PluginMessageListener {
|
|||||||
|
|
||||||
// Register world properties
|
// Register world properties
|
||||||
World world = player.getWorld();
|
World world = player.getWorld();
|
||||||
ServerWorldPropertiesRegistry properties = plugin.getWorldProperties(world);
|
ServerWorldPropertiesRegistry properties = plugin.getOrCreateWorldProperties(world);
|
||||||
|
|
||||||
if (properties == null) {
|
if (properties == null) {
|
||||||
player.sendPluginMessage(plugin, "axiom:register_world_properties", new byte[]{0});
|
player.sendPluginMessage(plugin, "axiom:register_world_properties", new byte[]{0});
|
||||||
} else {
|
} else {
|
||||||
properties.registerFor(plugin, player);
|
properties.registerFor(plugin, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WorldExtension.onPlayerJoin(world, player);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,212 @@
|
|||||||
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
|
import com.moulberry.axiom.NbtSanitization;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.nbt.Tag;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.entity.EntityType;
|
||||||
|
import net.minecraft.world.entity.RelativeMovement;
|
||||||
|
import net.minecraft.world.entity.decoration.HangingEntity;
|
||||||
|
import net.minecraft.world.entity.decoration.ItemFrame;
|
||||||
|
import net.minecraft.world.level.block.Rotation;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class ManipulateEntityPacketListener implements PluginMessageListener {
|
||||||
|
|
||||||
|
private final AxiomPaper plugin;
|
||||||
|
public ManipulateEntityPacketListener(AxiomPaper plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
public enum PassengerManipulation {
|
||||||
|
NONE,
|
||||||
|
REMOVE_ALL,
|
||||||
|
ADD_LIST,
|
||||||
|
REMOVE_LIST
|
||||||
|
}
|
||||||
|
|
||||||
|
public record ManipulateEntry(UUID uuid, @Nullable Set<RelativeMovement> relativeMovementSet, @Nullable Vec3 position,
|
||||||
|
float yaw, float pitch, CompoundTag merge, PassengerManipulation passengerManipulation, List<UUID> passengers) {
|
||||||
|
public static ManipulateEntry read(FriendlyByteBuf friendlyByteBuf) {
|
||||||
|
UUID uuid = friendlyByteBuf.readUUID();
|
||||||
|
|
||||||
|
int flags = friendlyByteBuf.readByte();
|
||||||
|
Set<RelativeMovement> relativeMovementSet = null;
|
||||||
|
Vec3 position = null;
|
||||||
|
float yaw = 0;
|
||||||
|
float pitch = 0;
|
||||||
|
if (flags >= 0) {
|
||||||
|
relativeMovementSet = RelativeMovement.unpack(flags);
|
||||||
|
position = new Vec3(friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble());
|
||||||
|
yaw = friendlyByteBuf.readFloat();
|
||||||
|
pitch = friendlyByteBuf.readFloat();
|
||||||
|
}
|
||||||
|
|
||||||
|
CompoundTag nbt = friendlyByteBuf.readNbt();
|
||||||
|
|
||||||
|
PassengerManipulation passengerManipulation = friendlyByteBuf.readEnum(PassengerManipulation.class);
|
||||||
|
List<UUID> passengers = List.of();
|
||||||
|
if (passengerManipulation == PassengerManipulation.ADD_LIST || passengerManipulation == PassengerManipulation.REMOVE_LIST) {
|
||||||
|
passengers = friendlyByteBuf.readList(FriendlyByteBuf::readUUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
return new ManipulateEntry(uuid, relativeMovementSet, position, yaw, pitch, nbt,
|
||||||
|
passengerManipulation, passengers);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Rotation[] ROTATION_VALUES = Rotation.values();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||||
|
if (!this.plugin.canUseAxiom(player)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.hasPermission("axiom.entity.*") && !player.hasPermission("axiom.entity.manipulate")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.plugin.canModifyWorld(player, player.getWorld())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||||
|
List<ManipulateEntry> entries = friendlyByteBuf.readList(ManipulateEntry::read);
|
||||||
|
|
||||||
|
ServerLevel serverLevel = ((CraftWorld)player.getWorld()).getHandle();
|
||||||
|
|
||||||
|
List<String> whitelistedEntities = this.plugin.configuration.getStringList("whitelist-entities");
|
||||||
|
List<String> blacklistedEntities = this.plugin.configuration.getStringList("blacklist-entities");
|
||||||
|
|
||||||
|
for (ManipulateEntry entry : entries) {
|
||||||
|
Entity entity = serverLevel.getEntity(entry.uuid);
|
||||||
|
if (entity == null || entity instanceof net.minecraft.world.entity.player.Player || entity.hasPassenger(ManipulateEntityPacketListener::isPlayer)) continue;
|
||||||
|
|
||||||
|
String type = EntityType.getKey(entity.getType()).toString();
|
||||||
|
|
||||||
|
if (!whitelistedEntities.isEmpty() && !whitelistedEntities.contains(type)) continue;
|
||||||
|
if (blacklistedEntities.contains(type)) continue;
|
||||||
|
|
||||||
|
if (entry.merge != null && !entry.merge.isEmpty()) {
|
||||||
|
NbtSanitization.sanitizeEntity(entry.merge);
|
||||||
|
|
||||||
|
CompoundTag compoundTag = entity.saveWithoutId(new CompoundTag());
|
||||||
|
compoundTag = merge(compoundTag, entry.merge);
|
||||||
|
entity.load(compoundTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec3 entryPos = entry.position();
|
||||||
|
if (entryPos != null && entry.relativeMovementSet != null) {
|
||||||
|
double newX = entry.relativeMovementSet.contains(RelativeMovement.X) ? entity.position().x + entryPos.x : entryPos.x;
|
||||||
|
double newY = entry.relativeMovementSet.contains(RelativeMovement.Y) ? entity.position().y + entryPos.y : entryPos.y;
|
||||||
|
double newZ = entry.relativeMovementSet.contains(RelativeMovement.Z) ? entity.position().z + entryPos.z : entryPos.z;
|
||||||
|
float newYaw = entry.relativeMovementSet.contains(RelativeMovement.Y_ROT) ? entity.getYRot() + entry.yaw : entry.yaw;
|
||||||
|
float newPitch = entry.relativeMovementSet.contains(RelativeMovement.X_ROT) ? entity.getXRot() + entry.pitch : entry.pitch;
|
||||||
|
|
||||||
|
if (entity instanceof HangingEntity hangingEntity) {
|
||||||
|
float changedYaw = newYaw - entity.getYRot();
|
||||||
|
int rotations = Math.round(changedYaw / 90);
|
||||||
|
hangingEntity.rotate(ROTATION_VALUES[rotations & 3]);
|
||||||
|
|
||||||
|
if (entity instanceof ItemFrame itemFrame && itemFrame.getDirection().getAxis() == Direction.Axis.Y) {
|
||||||
|
itemFrame.setRotation(itemFrame.getRotation() - Math.round(changedYaw / 45));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.teleportTo(serverLevel, newX, newY, newZ, Set.of(), newYaw, newPitch);
|
||||||
|
entity.setYHeadRot(newYaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (entry.passengerManipulation) {
|
||||||
|
case NONE -> {}
|
||||||
|
case REMOVE_ALL -> entity.ejectPassengers();
|
||||||
|
case ADD_LIST -> {
|
||||||
|
for (UUID passengerUuid : entry.passengers) {
|
||||||
|
Entity passenger = serverLevel.getEntity(passengerUuid);
|
||||||
|
|
||||||
|
if (passenger == null || passenger.isPassenger() ||
|
||||||
|
passenger instanceof net.minecraft.world.entity.player.Player || passenger.hasPassenger(ManipulateEntityPacketListener::isPlayer)) continue;
|
||||||
|
|
||||||
|
String passengerType = EntityType.getKey(passenger.getType()).toString();
|
||||||
|
|
||||||
|
if (!whitelistedEntities.isEmpty() && !whitelistedEntities.contains(passengerType)) continue;
|
||||||
|
if (blacklistedEntities.contains(passengerType)) continue;
|
||||||
|
|
||||||
|
// Prevent mounting loop
|
||||||
|
if (passenger.getSelfAndPassengers().anyMatch(entity2 -> entity2 == entity)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
passenger.startRiding(entity, true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case REMOVE_LIST -> {
|
||||||
|
for (UUID passengerUuid : entry.passengers) {
|
||||||
|
Entity passenger = serverLevel.getEntity(passengerUuid);
|
||||||
|
if (passenger == null || passenger == entity || passenger instanceof net.minecraft.world.entity.player.Player ||
|
||||||
|
passenger.hasPassenger(ManipulateEntityPacketListener::isPlayer)) continue;
|
||||||
|
|
||||||
|
String passengerType = EntityType.getKey(passenger.getType()).toString();
|
||||||
|
|
||||||
|
if (!whitelistedEntities.isEmpty() && !whitelistedEntities.contains(passengerType)) continue;
|
||||||
|
if (blacklistedEntities.contains(passengerType)) continue;
|
||||||
|
|
||||||
|
Entity vehicle = passenger.getVehicle();
|
||||||
|
if (vehicle == entity) {
|
||||||
|
passenger.stopRiding();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static CompoundTag merge(CompoundTag left, CompoundTag right) {
|
||||||
|
if (right.contains("axiom:modify")) {
|
||||||
|
right.remove("axiom:modify");
|
||||||
|
return right;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String key : right.getAllKeys()) {
|
||||||
|
Tag tag = right.get(key);
|
||||||
|
if (tag instanceof CompoundTag compound) {
|
||||||
|
if (compound.isEmpty()) {
|
||||||
|
left.remove(key);
|
||||||
|
} else if (left.contains(key, Tag.TAG_COMPOUND)) {
|
||||||
|
CompoundTag child = left.getCompound(key);
|
||||||
|
child = merge(child, compound);
|
||||||
|
left.put(key, child);
|
||||||
|
} else {
|
||||||
|
CompoundTag copied = compound.copy();
|
||||||
|
if (copied.contains("axiom:modify")) {
|
||||||
|
copied.remove("axiom:modify");
|
||||||
|
}
|
||||||
|
left.put(key, copied);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
left.put(key, tag.copy());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return left;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isPlayer(Entity entity) {
|
||||||
|
return entity instanceof net.minecraft.world.entity.player.Player;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,58 @@
|
|||||||
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
|
import com.moulberry.axiom.marker.MarkerData;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.entity.Marker;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class MarkerNbtRequestPacketListener implements PluginMessageListener {
|
||||||
|
|
||||||
|
private final AxiomPaper plugin;
|
||||||
|
public MarkerNbtRequestPacketListener(AxiomPaper plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||||
|
if (!this.plugin.canUseAxiom(player)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.hasPermission("axiom.entity.*") && !player.hasPermission("axiom.entity.manipulate")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.plugin.canModifyWorld(player, player.getWorld())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||||
|
UUID uuid = friendlyByteBuf.readUUID();
|
||||||
|
|
||||||
|
ServerLevel serverLevel = ((CraftWorld)player.getWorld()).getHandle();
|
||||||
|
|
||||||
|
Entity entity = serverLevel.getEntity(uuid);
|
||||||
|
if (entity instanceof Marker marker) {
|
||||||
|
CompoundTag data = MarkerData.getData(marker);
|
||||||
|
|
||||||
|
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||||
|
buf.writeUUID(uuid);
|
||||||
|
buf.writeNbt(data);
|
||||||
|
byte[] bytes = new byte[buf.writerIndex()];
|
||||||
|
buf.getBytes(0, bytes);
|
||||||
|
|
||||||
|
player.sendPluginMessage(AxiomPaper.PLUGIN, "axiom:marker_nbt_response", bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -3,8 +3,10 @@ package com.moulberry.axiom.packet;
|
|||||||
import com.moulberry.axiom.AxiomConstants;
|
import com.moulberry.axiom.AxiomConstants;
|
||||||
import com.moulberry.axiom.AxiomPaper;
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
||||||
|
import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import it.unimi.dsi.fastutil.longs.*;
|
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
|
||||||
|
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.nbt.CompoundTag;
|
import net.minecraft.nbt.CompoundTag;
|
||||||
@ -33,7 +35,6 @@ public class RequestChunkDataPacketListener implements PluginMessageListener {
|
|||||||
private static final ResourceLocation RESPONSE_ID = new ResourceLocation("axiom:response_chunk_data");
|
private static final ResourceLocation RESPONSE_ID = new ResourceLocation("axiom:response_chunk_data");
|
||||||
|
|
||||||
private final AxiomPaper plugin;
|
private final AxiomPaper plugin;
|
||||||
|
|
||||||
public RequestChunkDataPacketListener(AxiomPaper plugin) {
|
public RequestChunkDataPacketListener(AxiomPaper plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
}
|
}
|
||||||
@ -44,12 +45,17 @@ public class RequestChunkDataPacketListener implements PluginMessageListener {
|
|||||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||||
long id = friendlyByteBuf.readLong();
|
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
|
// We always send an 'empty' response in order to make the client happy
|
||||||
sendEmptyResponse(player, id);
|
sendEmptyResponse(player, id);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!this.plugin.canModifyWorld(bukkitPlayer, bukkitPlayer.getWorld())) {
|
||||||
|
sendEmptyResponse(player, id);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
MinecraftServer server = player.getServer();
|
MinecraftServer server = player.getServer();
|
||||||
if (server == null) {
|
if (server == null) {
|
||||||
sendEmptyResponse(player, id);
|
sendEmptyResponse(player, id);
|
||||||
@ -63,7 +69,7 @@ public class RequestChunkDataPacketListener implements PluginMessageListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean sendBlockEntitiesInChunks= friendlyByteBuf.readBoolean();
|
boolean sendBlockEntitiesInChunks = friendlyByteBuf.readBoolean();
|
||||||
|
|
||||||
Long2ObjectMap<CompressedBlockEntity> blockEntityMap = new Long2ObjectOpenHashMap<>();
|
Long2ObjectMap<CompressedBlockEntity> blockEntityMap = new Long2ObjectOpenHashMap<>();
|
||||||
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
ByteArrayOutputStream baos = new ByteArrayOutputStream();
|
||||||
@ -84,39 +90,49 @@ public class RequestChunkDataPacketListener implements PluginMessageListener {
|
|||||||
int playerSectionZ = player.getBlockZ() >> 4;
|
int playerSectionZ = player.getBlockZ() >> 4;
|
||||||
|
|
||||||
Long2ObjectMap<PalettedContainer<BlockState>> sections = new Long2ObjectOpenHashMap<>();
|
Long2ObjectMap<PalettedContainer<BlockState>> sections = new Long2ObjectOpenHashMap<>();
|
||||||
count = friendlyByteBuf.readVarInt();
|
|
||||||
for (int i = 0; i < count; i++) {
|
|
||||||
long pos = friendlyByteBuf.readLong();
|
|
||||||
|
|
||||||
int sx = BlockPos.getX(pos);
|
int maxChunkLoadDistance = this.plugin.configuration.getInt("max-chunk-load-distance");
|
||||||
int sy = BlockPos.getY(pos);
|
|
||||||
int sz = BlockPos.getZ(pos);
|
|
||||||
|
|
||||||
int distance = Math.abs(playerSectionX - sx) + Math.abs(playerSectionZ - sz);
|
// Don't allow loading chunks outside render distance for plot worlds
|
||||||
if (distance > 128) continue;
|
if (PlotSquaredIntegration.isPlotWorld(level.getWorld())) {
|
||||||
|
maxChunkLoadDistance = 0;
|
||||||
|
}
|
||||||
|
|
||||||
LevelChunk chunk = level.getChunk(sx, sz);
|
if (maxChunkLoadDistance > 0) {
|
||||||
int sectionIndex = chunk.getSectionIndexFromSectionY(sy);
|
count = friendlyByteBuf.readVarInt();
|
||||||
if (sectionIndex < 0 || sectionIndex >= chunk.getSectionsCount()) continue;
|
for (int i = 0; i < count; i++) {
|
||||||
LevelChunkSection section = chunk.getSection(sectionIndex);
|
long pos = friendlyByteBuf.readLong();
|
||||||
|
|
||||||
if (section.hasOnlyAir()) {
|
int sx = BlockPos.getX(pos);
|
||||||
sections.put(pos, null);
|
int sy = BlockPos.getY(pos);
|
||||||
} else {
|
int sz = BlockPos.getZ(pos);
|
||||||
PalettedContainer<BlockState> container = section.getStates();
|
|
||||||
sections.put(pos, container);
|
|
||||||
|
|
||||||
if (sendBlockEntitiesInChunks && section.maybeHas(BlockState::hasBlockEntity)) {
|
int distance = Math.abs(playerSectionX - sx) + Math.abs(playerSectionZ - sz);
|
||||||
for (int x = 0; x < 16; x++) {
|
if (distance > maxChunkLoadDistance) continue;
|
||||||
for (int y = 0; y < 16; y++) {
|
|
||||||
for (int z = 0; z < 16; z++) {
|
LevelChunk chunk = level.getChunk(sx, sz);
|
||||||
BlockState blockState = container.get(x, y, z);
|
int sectionIndex = chunk.getSectionIndexFromSectionY(sy);
|
||||||
if (blockState.hasBlockEntity()) {
|
if (sectionIndex < 0 || sectionIndex >= chunk.getSectionsCount()) continue;
|
||||||
mutableBlockPos.set(sx*16 + x, sy*16 + y, sz*16 + z);
|
LevelChunkSection section = chunk.getSection(sectionIndex);
|
||||||
BlockEntity blockEntity = chunk.getBlockEntity(mutableBlockPos, LevelChunk.EntityCreationType.CHECK);
|
|
||||||
if (blockEntity != null) {
|
if (section.hasOnlyAir()) {
|
||||||
CompoundTag tag = blockEntity.saveWithoutMetadata();
|
sections.put(pos, null);
|
||||||
blockEntityMap.put(mutableBlockPos.asLong(), CompressedBlockEntity.compress(tag, baos));
|
} else {
|
||||||
|
PalettedContainer<BlockState> container = section.getStates();
|
||||||
|
sections.put(pos, container);
|
||||||
|
|
||||||
|
if (sendBlockEntitiesInChunks && section.maybeHas(BlockState::hasBlockEntity)) {
|
||||||
|
for (int x = 0; x < 16; x++) {
|
||||||
|
for (int y = 0; y < 16; y++) {
|
||||||
|
for (int z = 0; z < 16; z++) {
|
||||||
|
BlockState blockState = container.get(x, y, z);
|
||||||
|
if (blockState.hasBlockEntity()) {
|
||||||
|
mutableBlockPos.set(sx*16 + x, sy*16 + y, sz*16 + z);
|
||||||
|
BlockEntity blockEntity = chunk.getBlockEntity(mutableBlockPos, LevelChunk.EntityCreationType.CHECK);
|
||||||
|
if (blockEntity != null) {
|
||||||
|
CompoundTag tag = blockEntity.saveWithoutMetadata();
|
||||||
|
blockEntityMap.put(mutableBlockPos.asLong(), CompressedBlockEntity.compress(tag, baos));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -126,7 +142,6 @@ public class RequestChunkDataPacketListener implements PluginMessageListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
// Send response packet
|
// Send response packet
|
||||||
|
|
||||||
boolean firstPart = true;
|
boolean firstPart = true;
|
||||||
|
@ -1,35 +1,23 @@
|
|||||||
package com.moulberry.axiom.packet;
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
|
import com.google.common.util.concurrent.RateLimiter;
|
||||||
import com.moulberry.axiom.AxiomPaper;
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
|
import com.moulberry.axiom.WorldExtension;
|
||||||
import com.moulberry.axiom.buffer.BiomeBuffer;
|
import com.moulberry.axiom.buffer.BiomeBuffer;
|
||||||
import com.moulberry.axiom.buffer.BlockBuffer;
|
import com.moulberry.axiom.buffer.BlockBuffer;
|
||||||
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
import com.moulberry.axiom.buffer.CompressedBlockEntity;
|
||||||
import com.moulberry.axiom.event.AxiomModifyWorldEvent;
|
import com.moulberry.axiom.integration.SectionPermissionChecker;
|
||||||
import com.moulberry.axiom.integration.RegionProtection;
|
import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration;
|
||||||
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.longs.Long2ObjectMap;
|
||||||
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
|
import it.unimi.dsi.fastutil.shorts.Short2ObjectMap;
|
||||||
|
import net.minecraft.ChatFormatting;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.Holder;
|
import net.minecraft.core.Holder;
|
||||||
import net.minecraft.core.Registry;
|
import net.minecraft.core.Registry;
|
||||||
import net.minecraft.core.SectionPos;
|
import net.minecraft.core.SectionPos;
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.network.chat.Component;
|
||||||
import net.minecraft.network.protocol.game.ClientboundChunksBiomesPacket;
|
import net.minecraft.network.protocol.game.ClientboundChunksBiomesPacket;
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.server.MinecraftServer;
|
import net.minecraft.server.MinecraftServer;
|
||||||
@ -41,7 +29,6 @@ import net.minecraft.world.level.ChunkPos;
|
|||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import net.minecraft.world.level.biome.Biome;
|
import net.minecraft.world.level.biome.Biome;
|
||||||
import net.minecraft.world.level.block.Block;
|
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.EntityBlock;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
import net.minecraft.world.level.block.state.BlockState;
|
import net.minecraft.world.level.block.state.BlockState;
|
||||||
@ -51,17 +38,19 @@ import net.minecraft.world.level.chunk.LevelChunkSection;
|
|||||||
import net.minecraft.world.level.chunk.PalettedContainer;
|
import net.minecraft.world.level.chunk.PalettedContainer;
|
||||||
import net.minecraft.world.level.levelgen.Heightmap;
|
import net.minecraft.world.level.levelgen.Heightmap;
|
||||||
import net.minecraft.world.level.lighting.LightEngine;
|
import net.minecraft.world.level.lighting.LightEngine;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Location;
|
||||||
import xyz.jpenilla.reflectionremapper.ReflectionRemapper;
|
import xyz.jpenilla.reflectionremapper.ReflectionRemapper;
|
||||||
|
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
public class SetBlockBufferPacketListener {
|
public class SetBlockBufferPacketListener {
|
||||||
|
|
||||||
private final AxiomPaper plugin;
|
private final AxiomPaper plugin;
|
||||||
private final Method updateBlockEntityTicker;
|
private final Method updateBlockEntityTicker;
|
||||||
|
private final WeakHashMap<ServerPlayer, RateLimiter> packetRateLimiter = new WeakHashMap<>();
|
||||||
|
|
||||||
public SetBlockBufferPacketListener(AxiomPaper plugin) {
|
public SetBlockBufferPacketListener(AxiomPaper plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
@ -90,13 +79,39 @@ public class SetBlockBufferPacketListener {
|
|||||||
friendlyByteBuf.readNbt(); // Discard sourceInfo
|
friendlyByteBuf.readNbt(); // Discard sourceInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RateLimiter rateLimiter = this.plugin.getBlockBufferRateLimiter(player.getUUID());
|
||||||
|
|
||||||
byte type = friendlyByteBuf.readByte();
|
byte type = friendlyByteBuf.readByte();
|
||||||
if (type == 0) {
|
if (type == 0) {
|
||||||
BlockBuffer buffer = BlockBuffer.load(friendlyByteBuf);
|
AtomicBoolean reachedRateLimit = new AtomicBoolean(false);
|
||||||
|
BlockBuffer buffer = BlockBuffer.load(friendlyByteBuf, rateLimiter, reachedRateLimit);
|
||||||
|
if (reachedRateLimit.get()) {
|
||||||
|
player.sendSystemMessage(Component.literal("[Axiom] Exceeded server rate-limit of " + (int)rateLimiter.getRate() + " sections per second")
|
||||||
|
.withStyle(ChatFormatting.RED));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.plugin.logLargeBlockBufferChanges()) {
|
||||||
|
this.plugin.getLogger().info("Player " + player.getUUID() + " modified " + buffer.entrySet().size() + " chunk sections (blocks)");
|
||||||
|
if (buffer.getTotalBlockEntities() > 0) {
|
||||||
|
this.plugin.getLogger().info("Player " + player.getUUID() + " modified " + buffer.getTotalBlockEntities() + " block entities, compressed bytes = " +
|
||||||
|
buffer.getTotalBlockEntityBytes());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
applyBlockBuffer(player, server, buffer, worldKey);
|
applyBlockBuffer(player, server, buffer, worldKey);
|
||||||
} else if (type == 1) {
|
} else if (type == 1) {
|
||||||
BiomeBuffer buffer = BiomeBuffer.load(friendlyByteBuf);
|
AtomicBoolean reachedRateLimit = new AtomicBoolean(false);
|
||||||
applyBiomeBuffer(server, buffer, worldKey);
|
BiomeBuffer buffer = BiomeBuffer.load(friendlyByteBuf, rateLimiter, reachedRateLimit);
|
||||||
|
if (reachedRateLimit.get()) {
|
||||||
|
player.sendSystemMessage(Component.literal("[Axiom] Exceeded server rate-limit of " + (int)rateLimiter.getRate() + " sections per second")
|
||||||
|
.withStyle(ChatFormatting.RED));
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.plugin.logLargeBlockBufferChanges()) {
|
||||||
|
this.plugin.getLogger().info("Player " + player.getUUID() + " modified " + buffer.size() + " chunk sections (biomes)");
|
||||||
|
}
|
||||||
|
|
||||||
|
applyBiomeBuffer(player, server, buffer, worldKey);
|
||||||
} else {
|
} else {
|
||||||
throw new RuntimeException("Unknown buffer type: " + type);
|
throw new RuntimeException("Unknown buffer type: " + type);
|
||||||
}
|
}
|
||||||
@ -106,219 +121,252 @@ public class SetBlockBufferPacketListener {
|
|||||||
|
|
||||||
private void applyBlockBuffer(ServerPlayer player, MinecraftServer server, BlockBuffer buffer, ResourceKey<Level> worldKey) {
|
private void applyBlockBuffer(ServerPlayer player, MinecraftServer server, BlockBuffer buffer, ResourceKey<Level> worldKey) {
|
||||||
server.execute(() -> {
|
server.execute(() -> {
|
||||||
ServerLevel world = server.getLevel(worldKey);
|
try {
|
||||||
if (world == null) return;
|
ServerLevel world = player.serverLevel();
|
||||||
|
if (!world.dimension().equals(worldKey)) return;
|
||||||
|
|
||||||
// Call AxiomModifyWorldEvent event
|
if (!this.plugin.canUseAxiom(player.getBukkitEntity())) {
|
||||||
AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(player.getBukkitEntity(), world.getWorld());
|
return;
|
||||||
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();
|
|
||||||
|
|
||||||
BlockState emptyState = BlockBuffer.EMPTY_STATE;
|
|
||||||
|
|
||||||
for (Long2ObjectMap.Entry<PalettedContainer<BlockState>> entry : buffer.entrySet()) {
|
|
||||||
int cx = BlockPos.getX(entry.getLongKey());
|
|
||||||
int cy = BlockPos.getY(entry.getLongKey());
|
|
||||||
int cz = BlockPos.getZ(entry.getLongKey());
|
|
||||||
PalettedContainer<BlockState> container = entry.getValue();
|
|
||||||
|
|
||||||
if (cy < world.getMinSection() || cy >= world.getMaxSection()) {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SectionProtection sectionProtection = regionProtection.getSection(cx, cy, cz);
|
if (!this.plugin.canModifyWorld(player.getBukkitEntity(), world.getWorld())) {
|
||||||
// switch (sectionProtection.getSectionState()) {
|
return;
|
||||||
// case ALLOW -> sectionProtection = null;
|
}
|
||||||
// case DENY -> {
|
|
||||||
// continue;
|
|
||||||
// }
|
|
||||||
// case CHECK -> {}
|
|
||||||
// }
|
|
||||||
|
|
||||||
LevelChunk chunk = world.getChunk(cx, cz);
|
// Allowed, apply buffer
|
||||||
chunk.setUnsaved(true);
|
BlockPos.MutableBlockPos blockPos = new BlockPos.MutableBlockPos();
|
||||||
|
WorldExtension extension = WorldExtension.get(world);
|
||||||
|
|
||||||
LevelChunkSection section = chunk.getSection(world.getSectionIndexFromSectionY(cy));
|
BlockState emptyState = BlockBuffer.EMPTY_STATE;
|
||||||
PalettedContainer<BlockState> sectionStates = section.getStates();
|
|
||||||
boolean hasOnlyAir = section.hasOnlyAir();
|
|
||||||
|
|
||||||
Heightmap worldSurface = null;
|
for (Long2ObjectMap.Entry<PalettedContainer<BlockState>> entry : buffer.entrySet()) {
|
||||||
Heightmap oceanFloor = null;
|
int cx = BlockPos.getX(entry.getLongKey());
|
||||||
Heightmap motionBlocking = null;
|
int cy = BlockPos.getY(entry.getLongKey());
|
||||||
Heightmap motionBlockingNoLeaves = null;
|
int cz = BlockPos.getZ(entry.getLongKey());
|
||||||
for (Map.Entry<Heightmap.Types, Heightmap> heightmap : chunk.getHeightmaps()) {
|
PalettedContainer<BlockState> container = entry.getValue();
|
||||||
switch (heightmap.getKey()) {
|
|
||||||
case WORLD_SURFACE -> worldSurface = heightmap.getValue();
|
if (cy < world.getMinSection() || cy >= world.getMaxSection()) {
|
||||||
case OCEAN_FLOOR -> oceanFloor = heightmap.getValue();
|
continue;
|
||||||
case MOTION_BLOCKING -> motionBlocking = heightmap.getValue();
|
|
||||||
case MOTION_BLOCKING_NO_LEAVES -> motionBlockingNoLeaves = heightmap.getValue();
|
|
||||||
default -> {}
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
Short2ObjectMap<CompressedBlockEntity> blockEntityChunkMap = buffer.getBlockEntityChunkMap(entry.getLongKey());
|
SectionPermissionChecker checker = PlotSquaredIntegration.checkSection(player.getBukkitEntity(), world.getWorld(), cx, cy, cz);
|
||||||
|
if (checker != null && checker.noneAllowed()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
for (int x = 0; x < 16; x++) {
|
LevelChunk chunk = world.getChunk(cx, cz);
|
||||||
for (int y = 0; y < 16; y++) {
|
|
||||||
for (int z = 0; z < 16; z++) {
|
|
||||||
BlockState blockState = container.get(x, y, z);
|
|
||||||
if (blockState == emptyState) continue;
|
|
||||||
|
|
||||||
// switch (sectionProtection.getSectionState()) {
|
LevelChunkSection section = chunk.getSection(world.getSectionIndexFromSectionY(cy));
|
||||||
// case ALLOW -> {}
|
PalettedContainer<BlockState> sectionStates = section.getStates();
|
||||||
// case DENY -> blockState = Blocks.REDSTONE_BLOCK.defaultBlockState();
|
boolean hasOnlyAir = section.hasOnlyAir();
|
||||||
// case CHECK -> blockState = Blocks.DIAMOND_BLOCK.defaultBlockState();
|
|
||||||
// }
|
|
||||||
|
|
||||||
int bx = cx*16 + x;
|
Heightmap worldSurface = null;
|
||||||
int by = cy*16 + y;
|
Heightmap oceanFloor = null;
|
||||||
int bz = cz*16 + z;
|
Heightmap motionBlocking = null;
|
||||||
|
Heightmap motionBlockingNoLeaves = null;
|
||||||
|
for (Map.Entry<Heightmap.Types, Heightmap> heightmap : chunk.getHeightmaps()) {
|
||||||
|
switch (heightmap.getKey()) {
|
||||||
|
case WORLD_SURFACE -> worldSurface = heightmap.getValue();
|
||||||
|
case OCEAN_FLOOR -> oceanFloor = heightmap.getValue();
|
||||||
|
case MOTION_BLOCKING -> motionBlocking = heightmap.getValue();
|
||||||
|
case MOTION_BLOCKING_NO_LEAVES -> motionBlockingNoLeaves = heightmap.getValue();
|
||||||
|
default -> {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// if (!regionProtection.canBuild(bx, by, bz)) {
|
boolean sectionChanged = false;
|
||||||
// continue;
|
boolean sectionLightChanged = false;
|
||||||
// }
|
|
||||||
|
|
||||||
blockPos.set(bx, by, bz);
|
boolean containerMaybeHasPoi = container.maybeHas(PoiTypes::hasPoi);
|
||||||
|
boolean sectionMaybeHasPoi = section.maybeHas(PoiTypes::hasPoi);
|
||||||
|
|
||||||
if (hasOnlyAir && blockState.isAir()) {
|
Short2ObjectMap<CompressedBlockEntity> blockEntityChunkMap = buffer.getBlockEntityChunkMap(entry.getLongKey());
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
BlockState old = section.setBlockState(x, y, z, blockState, true);
|
int minX = 0;
|
||||||
if (blockState != old) {
|
int minY = 0;
|
||||||
Block block = blockState.getBlock();
|
int minZ = 0;
|
||||||
motionBlocking.update(x, by, z, blockState);
|
int maxX = 15;
|
||||||
motionBlockingNoLeaves.update(x, by, z, blockState);
|
int maxY = 15;
|
||||||
oceanFloor.update(x, by, z, blockState);
|
int maxZ = 15;
|
||||||
worldSurface.update(x, by, z, blockState);
|
|
||||||
|
|
||||||
if (false) { // Full update
|
if (checker != null) {
|
||||||
old.onRemove(world, blockPos, blockState, false);
|
minX = checker.bounds().minX();
|
||||||
|
minY = checker.bounds().minY();
|
||||||
|
minZ = checker.bounds().minZ();
|
||||||
|
maxX = checker.bounds().maxX();
|
||||||
|
maxY = checker.bounds().maxY();
|
||||||
|
maxZ = checker.bounds().maxZ();
|
||||||
|
if (checker.allAllowed()) {
|
||||||
|
checker = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (sectionStates.get(x, y, z).is(block)) {
|
for (int x = minX; x <= maxX; x++) {
|
||||||
blockState.onPlace(world, blockPos, old, false);
|
for (int y = minY; y <= maxY; y++) {
|
||||||
}
|
for (int z = minZ; z <= maxZ; z++) {
|
||||||
|
BlockState blockState = container.get(x, y, z);
|
||||||
|
if (blockState == emptyState) continue;
|
||||||
|
|
||||||
|
int bx = cx*16 + x;
|
||||||
|
int by = cy*16 + y;
|
||||||
|
int bz = cz*16 + z;
|
||||||
|
|
||||||
|
if (hasOnlyAir && blockState.isAir()) {
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (blockState.hasBlockEntity()) {
|
if (checker != null && !checker.allowed(x, y, z)) continue;
|
||||||
BlockEntity blockEntity = chunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK);
|
|
||||||
|
|
||||||
if (blockEntity == null) {
|
BlockState old = section.setBlockState(x, y, z, blockState, true);
|
||||||
// There isn't a block entity here, create it!
|
if (blockState != old) {
|
||||||
blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState);
|
sectionChanged = true;
|
||||||
if (blockEntity != null) {
|
blockPos.set(bx, by, bz);
|
||||||
chunk.addAndRegisterBlockEntity(blockEntity);
|
|
||||||
}
|
|
||||||
} else if (blockEntity.getType().isValid(blockState)) {
|
|
||||||
// Block entity is here and the type is correct
|
|
||||||
blockEntity.setBlockState(blockState);
|
|
||||||
|
|
||||||
try {
|
Block block = blockState.getBlock();
|
||||||
this.updateBlockEntityTicker.invoke(chunk, blockEntity);
|
motionBlocking.update(x, by, z, blockState);
|
||||||
} catch (IllegalAccessException | InvocationTargetException e) {
|
motionBlockingNoLeaves.update(x, by, z, blockState);
|
||||||
throw new RuntimeException(e);
|
oceanFloor.update(x, by, z, blockState);
|
||||||
|
worldSurface.update(x, by, z, blockState);
|
||||||
|
|
||||||
|
if (false) { // Full update
|
||||||
|
old.onRemove(world, blockPos, blockState, false);
|
||||||
|
|
||||||
|
if (sectionStates.get(x, y, z).is(block)) {
|
||||||
|
blockState.onPlace(world, blockPos, old, false);
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
// Block entity type isn't correct, we need to recreate it
|
|
||||||
|
if (blockState.hasBlockEntity()) {
|
||||||
|
BlockEntity blockEntity = chunk.getBlockEntity(blockPos, LevelChunk.EntityCreationType.CHECK);
|
||||||
|
|
||||||
|
if (blockEntity == null) {
|
||||||
|
// There isn't a block entity here, create it!
|
||||||
|
blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState);
|
||||||
|
if (blockEntity != null) {
|
||||||
|
chunk.addAndRegisterBlockEntity(blockEntity);
|
||||||
|
}
|
||||||
|
} else if (blockEntity.getType().isValid(blockState)) {
|
||||||
|
// Block entity is here and the type is correct
|
||||||
|
blockEntity.setBlockState(blockState);
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.updateBlockEntityTicker.invoke(chunk, blockEntity);
|
||||||
|
} catch (IllegalAccessException | InvocationTargetException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Block entity type isn't correct, we need to recreate it
|
||||||
|
chunk.removeBlockEntity(blockPos);
|
||||||
|
|
||||||
|
blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState);
|
||||||
|
if (blockEntity != null) {
|
||||||
|
chunk.addAndRegisterBlockEntity(blockEntity);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (blockEntity != null && blockEntityChunkMap != null) {
|
||||||
|
int key = x | (y << 4) | (z << 8);
|
||||||
|
CompressedBlockEntity savedBlockEntity = blockEntityChunkMap.get((short) key);
|
||||||
|
if (savedBlockEntity != null) {
|
||||||
|
blockEntity.load(savedBlockEntity.decompress());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (old.hasBlockEntity()) {
|
||||||
chunk.removeBlockEntity(blockPos);
|
chunk.removeBlockEntity(blockPos);
|
||||||
|
|
||||||
blockEntity = ((EntityBlock)block).newBlockEntity(blockPos, blockState);
|
|
||||||
if (blockEntity != null) {
|
|
||||||
chunk.addAndRegisterBlockEntity(blockEntity);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (blockEntity != null && blockEntityChunkMap != null) {
|
|
||||||
int key = x | (y << 4) | (z << 8);
|
// Update Light
|
||||||
CompressedBlockEntity savedBlockEntity = blockEntityChunkMap.get((short) key);
|
sectionLightChanged |= LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState);
|
||||||
if (savedBlockEntity != null) {
|
|
||||||
blockEntity.load(savedBlockEntity.decompress());
|
// Update Poi
|
||||||
}
|
Optional<Holder<PoiType>> newPoi = containerMaybeHasPoi ? PoiTypes.forState(blockState) : Optional.empty();
|
||||||
|
Optional<Holder<PoiType>> 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());
|
||||||
}
|
}
|
||||||
} else if (old.hasBlockEntity()) {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update Poi
|
|
||||||
Optional<Holder<PoiType>> newPoi = PoiTypes.forState(blockState);
|
|
||||||
Optional<Holder<PoiType>> oldPoi = PoiTypes.forState(old);
|
|
||||||
if (!Objects.equals(oldPoi, newPoi)) {
|
|
||||||
if (oldPoi.isPresent()) world.getPoiManager().remove(blockPos);
|
|
||||||
if (newPoi.isPresent()) world.getPoiManager().add(blockPos, newPoi.get());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
boolean nowHasOnlyAir = section.hasOnlyAir();
|
boolean nowHasOnlyAir = section.hasOnlyAir();
|
||||||
if (hasOnlyAir != nowHasOnlyAir) {
|
if (hasOnlyAir != nowHasOnlyAir) {
|
||||||
world.getChunkSource().getLightEngine().updateSectionStatus(SectionPos.of(cx, cy, cz), 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);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
} catch (Throwable t) {
|
||||||
|
player.getBukkitEntity().kick(net.kyori.adventure.text.Component.text("An error occured while processing block change: " + t.getMessage()));
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void applyBiomeBuffer(ServerPlayer player, MinecraftServer server, BiomeBuffer biomeBuffer, ResourceKey<Level> worldKey) {
|
||||||
private void applyBiomeBuffer(MinecraftServer server, BiomeBuffer biomeBuffer, ResourceKey<Level> worldKey) {
|
|
||||||
server.execute(() -> {
|
server.execute(() -> {
|
||||||
ServerLevel world = server.getLevel(worldKey);
|
try {
|
||||||
if (world == null) return;
|
ServerLevel world = player.serverLevel();
|
||||||
|
if (!world.dimension().equals(worldKey)) return;
|
||||||
|
|
||||||
Set<LevelChunk> changedChunks = new HashSet<>();
|
if (!this.plugin.canUseAxiom(player.getBukkitEntity())) {
|
||||||
|
|
||||||
int minSection = world.getMinSection();
|
|
||||||
int maxSection = world.getMaxSection();
|
|
||||||
|
|
||||||
Optional<Registry<Biome>> registryOptional = world.registryAccess().registry(Registries.BIOME);
|
|
||||||
if (registryOptional.isEmpty()) return;
|
|
||||||
|
|
||||||
Registry<Biome> registry = registryOptional.get();
|
|
||||||
|
|
||||||
biomeBuffer.forEachEntry((x, y, z, biome) -> {
|
|
||||||
int cy = y >> 2;
|
|
||||||
if (cy < minSection || cy >= maxSection) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var chunk = (LevelChunk) world.getChunk(x >> 2, z >> 2, ChunkStatus.FULL, false);
|
if (!this.plugin.canModifyWorld(player.getBukkitEntity(), world.getWorld())) {
|
||||||
if (chunk == null) return;
|
return;
|
||||||
|
|
||||||
var section = chunk.getSection(cy - minSection);
|
|
||||||
PalettedContainer<Holder<Biome>> container = (PalettedContainer<Holder<Biome>>) section.getBiomes();
|
|
||||||
|
|
||||||
var holder = registry.getHolder(biome);
|
|
||||||
if (holder.isPresent()) {
|
|
||||||
container.set(x & 3, y & 3, z & 3, holder.get());
|
|
||||||
changedChunks.add(chunk);
|
|
||||||
}
|
}
|
||||||
});
|
|
||||||
|
|
||||||
var chunkMap = world.getChunkSource().chunkMap;
|
Set<LevelChunk> changedChunks = new HashSet<>();
|
||||||
HashMap<ServerPlayer, List<LevelChunk>> map = new HashMap<>();
|
|
||||||
for (LevelChunk chunk : changedChunks) {
|
int minSection = world.getMinSection();
|
||||||
chunk.setUnsaved(true);
|
int maxSection = world.getMaxSection();
|
||||||
ChunkPos chunkPos = chunk.getPos();
|
|
||||||
for (ServerPlayer serverPlayer2 : chunkMap.getPlayers(chunkPos, false)) {
|
Optional<Registry<Biome>> registryOptional = world.registryAccess().registry(Registries.BIOME);
|
||||||
map.computeIfAbsent(serverPlayer2, serverPlayer -> new ArrayList<>()).add(chunk);
|
if (registryOptional.isEmpty()) return;
|
||||||
|
|
||||||
|
Registry<Biome> registry = registryOptional.get();
|
||||||
|
|
||||||
|
biomeBuffer.forEachEntry((x, y, z, biome) -> {
|
||||||
|
int cy = y >> 2;
|
||||||
|
if (cy < minSection || cy >= maxSection) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
var chunk = (LevelChunk) world.getChunk(x >> 2, z >> 2, ChunkStatus.FULL, false);
|
||||||
|
if (chunk == null) return;
|
||||||
|
|
||||||
|
var section = chunk.getSection(cy - minSection);
|
||||||
|
PalettedContainer<Holder<Biome>> container = (PalettedContainer<Holder<Biome>>) section.getBiomes();
|
||||||
|
|
||||||
|
var holder = registry.getHolder(biome);
|
||||||
|
if (holder.isPresent()) {
|
||||||
|
if (!PlotSquaredIntegration.canPlaceBlock(player.getBukkitEntity(),
|
||||||
|
new Location(player.getBukkitEntity().getWorld(), x+1, y+1, z+1))) return;
|
||||||
|
|
||||||
|
container.set(x & 3, y & 3, z & 3, holder.get());
|
||||||
|
changedChunks.add(chunk);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var chunkMap = world.getChunkSource().chunkMap;
|
||||||
|
HashMap<ServerPlayer, List<LevelChunk>> map = new HashMap<>();
|
||||||
|
for (LevelChunk chunk : changedChunks) {
|
||||||
|
chunk.setUnsaved(true);
|
||||||
|
ChunkPos chunkPos = chunk.getPos();
|
||||||
|
for (ServerPlayer serverPlayer2 : chunkMap.getPlayers(chunkPos, false)) {
|
||||||
|
map.computeIfAbsent(serverPlayer2, serverPlayer -> new ArrayList<>()).add(chunk);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
map.forEach((serverPlayer, list) -> serverPlayer.connection.send(ClientboundChunksBiomesPacket.forChunks(list)));
|
||||||
|
} catch (Throwable t) {
|
||||||
|
player.getBukkitEntity().kick(net.kyori.adventure.text.Component.text("An error occured while processing biome change: " + t.getMessage()));
|
||||||
}
|
}
|
||||||
map.forEach((serverPlayer, list) -> serverPlayer.connection.send(ClientboundChunksBiomesPacket.forChunks(list)));
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
package com.moulberry.axiom.packet;
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
|
import com.google.common.collect.Maps;
|
||||||
import com.moulberry.axiom.AxiomPaper;
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import com.moulberry.axiom.event.AxiomModifyWorldEvent;
|
import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import net.minecraft.core.BlockPos;
|
import net.minecraft.core.BlockPos;
|
||||||
import net.minecraft.core.Holder;
|
import net.minecraft.core.Holder;
|
||||||
@ -12,7 +13,9 @@ import net.minecraft.server.level.ServerPlayer;
|
|||||||
import net.minecraft.world.InteractionHand;
|
import net.minecraft.world.InteractionHand;
|
||||||
import net.minecraft.world.entity.ai.village.poi.PoiType;
|
import net.minecraft.world.entity.ai.village.poi.PoiType;
|
||||||
import net.minecraft.world.entity.ai.village.poi.PoiTypes;
|
import net.minecraft.world.entity.ai.village.poi.PoiTypes;
|
||||||
|
import net.minecraft.world.item.BlockItem;
|
||||||
import net.minecraft.world.item.ItemStack;
|
import net.minecraft.world.item.ItemStack;
|
||||||
|
import net.minecraft.world.item.context.BlockPlaceContext;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
import net.minecraft.world.level.block.EntityBlock;
|
import net.minecraft.world.level.block.EntityBlock;
|
||||||
import net.minecraft.world.level.block.entity.BlockEntity;
|
import net.minecraft.world.level.block.entity.BlockEntity;
|
||||||
@ -22,8 +25,9 @@ import net.minecraft.world.level.chunk.LevelChunkSection;
|
|||||||
import net.minecraft.world.level.levelgen.Heightmap;
|
import net.minecraft.world.level.levelgen.Heightmap;
|
||||||
import net.minecraft.world.level.lighting.LightEngine;
|
import net.minecraft.world.level.lighting.LightEngine;
|
||||||
import net.minecraft.world.phys.BlockHitResult;
|
import net.minecraft.world.phys.BlockHitResult;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Location;
|
||||||
import org.bukkit.block.BlockFace;
|
import org.bukkit.block.BlockFace;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
|
||||||
import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlock;
|
import org.bukkit.craftbukkit.v1_20_R1.block.CraftBlock;
|
||||||
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
|
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
@ -38,6 +42,7 @@ import java.lang.reflect.Method;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.function.IntFunction;
|
||||||
|
|
||||||
public class SetBlockPacketListener implements PluginMessageListener {
|
public class SetBlockPacketListener implements PluginMessageListener {
|
||||||
|
|
||||||
@ -61,18 +66,19 @@ public class SetBlockPacketListener implements PluginMessageListener {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player bukkitPlayer, @NotNull byte[] message) {
|
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player bukkitPlayer, @NotNull byte[] message) {
|
||||||
if (!bukkitPlayer.hasPermission("axiom.*")) {
|
if (!this.plugin.canUseAxiom(bukkitPlayer)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if player is allowed to modify this world
|
if (!this.plugin.canModifyWorld(bukkitPlayer, bukkitPlayer.getWorld())) {
|
||||||
AxiomModifyWorldEvent modifyWorldEvent = new AxiomModifyWorldEvent(bukkitPlayer, bukkitPlayer.getWorld());
|
return;
|
||||||
Bukkit.getPluginManager().callEvent(modifyWorldEvent);
|
}
|
||||||
if (modifyWorldEvent.isCancelled()) return;
|
|
||||||
|
|
||||||
// Read packet
|
// Read packet
|
||||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||||
Map<BlockPos, BlockState> blocks = friendlyByteBuf.readMap(FriendlyByteBuf::readBlockPos, buf -> buf.readById(Block.BLOCK_STATE_REGISTRY));
|
IntFunction<Map<BlockPos, BlockState>> mapFunction = FriendlyByteBuf.limitValue(Maps::newLinkedHashMapWithExpectedSize, 512);
|
||||||
|
Map<BlockPos, BlockState> blocks = friendlyByteBuf.readMap(mapFunction,
|
||||||
|
FriendlyByteBuf::readBlockPos, buf -> buf.readById(this.plugin.allowedBlockRegistry));
|
||||||
boolean updateNeighbors = friendlyByteBuf.readBoolean();
|
boolean updateNeighbors = friendlyByteBuf.readBoolean();
|
||||||
|
|
||||||
int reason = friendlyByteBuf.readVarInt();
|
int reason = friendlyByteBuf.readVarInt();
|
||||||
@ -105,16 +111,49 @@ public class SetBlockPacketListener implements PluginMessageListener {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
CraftWorld world = player.level().getWorld();
|
||||||
|
|
||||||
|
BlockPlaceContext blockPlaceContext = new BlockPlaceContext(player, hand, player.getItemInHand(hand), blockHit);
|
||||||
|
|
||||||
// Update blocks
|
// Update blocks
|
||||||
if (updateNeighbors) {
|
if (updateNeighbors) {
|
||||||
|
int count = 0;
|
||||||
for (Map.Entry<BlockPos, BlockState> entry : blocks.entrySet()) {
|
for (Map.Entry<BlockPos, BlockState> entry : blocks.entrySet()) {
|
||||||
player.level().setBlock(entry.getKey(), entry.getValue(), 3);
|
if (count++ > 64) break;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (Map.Entry<BlockPos, BlockState> entry : blocks.entrySet()) {
|
|
||||||
BlockPos blockPos = entry.getKey();
|
BlockPos blockPos = entry.getKey();
|
||||||
BlockState blockState = entry.getValue();
|
BlockState blockState = entry.getValue();
|
||||||
|
|
||||||
|
// Check PlotSquared
|
||||||
|
if (blockState.isAir()) {
|
||||||
|
if (!PlotSquaredIntegration.canBreakBlock(bukkitPlayer, world.getBlockAt(blockPos.getX(), blockPos.getY(), blockPos.getZ()))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (!PlotSquaredIntegration.canPlaceBlock(bukkitPlayer, new Location(world, blockPos.getX(), blockPos.getY(), blockPos.getZ()))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place block
|
||||||
|
player.level().setBlock(blockPos, blockState, 3);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
int count = 0;
|
||||||
|
for (Map.Entry<BlockPos, BlockState> entry : blocks.entrySet()) {
|
||||||
|
if (count++ > 64) break;
|
||||||
|
|
||||||
|
BlockPos blockPos = entry.getKey();
|
||||||
|
BlockState blockState = entry.getValue();
|
||||||
|
|
||||||
|
// Check PlotSquared
|
||||||
|
if (blockState.isAir()) {
|
||||||
|
if (!PlotSquaredIntegration.canBreakBlock(bukkitPlayer, world.getBlockAt(blockPos.getX(), blockPos.getY(), blockPos.getZ()))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (!PlotSquaredIntegration.canPlaceBlock(bukkitPlayer, new Location(world, blockPos.getX(), blockPos.getY(), blockPos.getZ()))) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Place block
|
||||||
int bx = blockPos.getX();
|
int bx = blockPos.getX();
|
||||||
int by = blockPos.getY();
|
int by = blockPos.getY();
|
||||||
int bz = blockPos.getZ();
|
int bz = blockPos.getZ();
|
||||||
@ -191,7 +230,9 @@ public class SetBlockPacketListener implements PluginMessageListener {
|
|||||||
|
|
||||||
// Update Light
|
// Update Light
|
||||||
if (LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState)) {
|
if (LightEngine.hasDifferentLightProperties(chunk, blockPos, old, blockState)) {
|
||||||
chunk.getSkyLightSources().update(chunk, x, by, z);
|
// Note: Skylight Sources not currently needed on Paper due to Starlight
|
||||||
|
// This might change in the future, so be careful!
|
||||||
|
// chunk.getSkyLightSources().update(chunk, x, by, z);
|
||||||
level.getChunkSource().getLightEngine().checkBlock(blockPos);
|
level.getChunkSource().getLightEngine().checkBlock(blockPos);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,6 +252,15 @@ public class SetBlockPacketListener implements PluginMessageListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!breaking) {
|
||||||
|
BlockPos clickedPos = blockPlaceContext.getClickedPos();
|
||||||
|
ItemStack inHand = player.getItemInHand(hand);
|
||||||
|
BlockState blockState = player.level().getBlockState(clickedPos);
|
||||||
|
|
||||||
|
BlockItem.updateCustomBlockEntityTag(player.level(), player, clickedPos, inHand);
|
||||||
|
blockState.getBlock().setPlacedBy(player.level(), clickedPos, blockState, player, inHand);
|
||||||
|
}
|
||||||
|
|
||||||
if (sequenceId >= 0) {
|
if (sequenceId >= 0) {
|
||||||
player.connection.ackBlockChangesUpTo(sequenceId);
|
player.connection.ackBlockChangesUpTo(sequenceId);
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.moulberry.axiom.packet;
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
|
import com.google.common.collect.Lists;
|
||||||
import com.moulberry.axiom.AxiomConstants;
|
import com.moulberry.axiom.AxiomConstants;
|
||||||
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import com.moulberry.axiom.View;
|
import com.moulberry.axiom.View;
|
||||||
import com.moulberry.axiom.persistence.UUIDDataType;
|
import com.moulberry.axiom.persistence.UUIDDataType;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
@ -13,18 +15,25 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
import java.util.function.IntFunction;
|
||||||
|
|
||||||
public class SetEditorViewsPacketListener implements PluginMessageListener {
|
public class SetEditorViewsPacketListener implements PluginMessageListener {
|
||||||
|
|
||||||
|
private final AxiomPaper plugin;
|
||||||
|
public SetEditorViewsPacketListener(AxiomPaper plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||||
if (!player.hasPermission("axiom.*")) {
|
if (!this.plugin.canUseAxiom(player)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||||
UUID uuid = friendlyByteBuf.readUUID();
|
UUID uuid = friendlyByteBuf.readUUID();
|
||||||
List<View> views = friendlyByteBuf.readList(View::read);
|
IntFunction<List<View>> listFunction = FriendlyByteBuf.limitValue(Lists::newArrayListWithCapacity, 64);
|
||||||
|
List<View> views = friendlyByteBuf.readCollection(listFunction, View::read);
|
||||||
|
|
||||||
PersistentDataContainer container = player.getPersistentDataContainer();
|
PersistentDataContainer container = player.getPersistentDataContainer();
|
||||||
container.set(AxiomConstants.ACTIVE_VIEW, UUIDDataType.INSTANCE, uuid);
|
container.set(AxiomConstants.ACTIVE_VIEW, UUIDDataType.INSTANCE, uuid);
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.moulberry.axiom.packet;
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import com.moulberry.axiom.event.AxiomFlySpeedChangeEvent;
|
import com.moulberry.axiom.event.AxiomFlySpeedChangeEvent;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
@ -11,9 +12,14 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
public class SetFlySpeedPacketListener implements PluginMessageListener {
|
public class SetFlySpeedPacketListener implements PluginMessageListener {
|
||||||
|
|
||||||
|
private final AxiomPaper plugin;
|
||||||
|
public SetFlySpeedPacketListener(AxiomPaper plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||||
if (!player.hasPermission("axiom.*")) {
|
if (!this.plugin.canUseAxiom(player)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.moulberry.axiom.packet;
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import com.moulberry.axiom.event.AxiomGameModeChangeEvent;
|
import com.moulberry.axiom.event.AxiomGameModeChangeEvent;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
@ -13,9 +14,14 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
public class SetGamemodePacketListener implements PluginMessageListener {
|
public class SetGamemodePacketListener implements PluginMessageListener {
|
||||||
|
|
||||||
|
private final AxiomPaper plugin;
|
||||||
|
public SetGamemodePacketListener(AxiomPaper plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||||
if (!player.hasPermission("axiom.*")) {
|
if (!this.plugin.canUseAxiom(player)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
package com.moulberry.axiom.packet;
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
import com.moulberry.axiom.AxiomConstants;
|
import com.moulberry.axiom.AxiomConstants;
|
||||||
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import com.moulberry.axiom.persistence.ItemStackDataType;
|
import com.moulberry.axiom.persistence.ItemStackDataType;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import org.bukkit.NamespacedKey;
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
|
|
||||||
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack;
|
import org.bukkit.craftbukkit.v1_20_R1.inventory.CraftItemStack;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.persistence.PersistentDataContainer;
|
import org.bukkit.persistence.PersistentDataContainer;
|
||||||
@ -15,9 +15,14 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
public class SetHotbarSlotPacketListener implements PluginMessageListener {
|
public class SetHotbarSlotPacketListener implements PluginMessageListener {
|
||||||
|
|
||||||
|
private final AxiomPaper plugin;
|
||||||
|
public SetHotbarSlotPacketListener(AxiomPaper plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||||
if (!player.hasPermission("axiom.*")) {
|
if (!this.plugin.canUseAxiom(player)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
package com.moulberry.axiom.packet;
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import com.moulberry.axiom.event.AxiomTimeChangeEvent;
|
import com.moulberry.axiom.event.AxiomTimeChangeEvent;
|
||||||
|
import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
@ -16,9 +18,14 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
public class SetTimePacketListener implements PluginMessageListener {
|
public class SetTimePacketListener implements PluginMessageListener {
|
||||||
|
|
||||||
|
private final AxiomPaper plugin;
|
||||||
|
public SetTimePacketListener(AxiomPaper plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||||
if (!player.hasPermission("axiom.*")) {
|
if (!this.plugin.canUseAxiom(player)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -32,7 +39,17 @@ public class SetTimePacketListener implements PluginMessageListener {
|
|||||||
ServerLevel level = ((CraftWorld)player.getWorld()).getHandle();
|
ServerLevel level = ((CraftWorld)player.getWorld()).getHandle();
|
||||||
if (!level.dimension().equals(key)) return;
|
if (!level.dimension().equals(key)) return;
|
||||||
|
|
||||||
// Call event
|
// Call modify world
|
||||||
|
if (!this.plugin.canModifyWorld(player, player.getWorld())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Don't allow on plot worlds
|
||||||
|
if (PlotSquaredIntegration.isPlotWorld(player.getWorld())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call time change event
|
||||||
AxiomTimeChangeEvent timeChangeEvent = new AxiomTimeChangeEvent(player, time, freezeTime);
|
AxiomTimeChangeEvent timeChangeEvent = new AxiomTimeChangeEvent(player, time, freezeTime);
|
||||||
Bukkit.getPluginManager().callEvent(timeChangeEvent);
|
Bukkit.getPluginManager().callEvent(timeChangeEvent);
|
||||||
if (timeChangeEvent.isCancelled()) return;
|
if (timeChangeEvent.isCancelled()) return;
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
package com.moulberry.axiom.packet;
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
import com.moulberry.axiom.AxiomPaper;
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
|
import com.moulberry.axiom.integration.plotsquared.PlotSquaredIntegration;
|
||||||
import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry;
|
import com.moulberry.axiom.world_properties.server.ServerWorldPropertiesRegistry;
|
||||||
import com.moulberry.axiom.world_properties.server.ServerWorldProperty;
|
import com.moulberry.axiom.world_properties.server.ServerWorldPropertyHolder;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
@ -12,9 +13,14 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
public class SetWorldPropertyListener implements PluginMessageListener {
|
public class SetWorldPropertyListener implements PluginMessageListener {
|
||||||
|
|
||||||
|
private final AxiomPaper plugin;
|
||||||
|
public SetWorldPropertyListener(AxiomPaper plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||||
if (!player.hasPermission("axiom.*")) {
|
if (!this.plugin.canUseAxiom(player)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -24,14 +30,33 @@ public class SetWorldPropertyListener implements PluginMessageListener {
|
|||||||
byte[] data = friendlyByteBuf.readByteArray();
|
byte[] data = friendlyByteBuf.readByteArray();
|
||||||
int updateId = friendlyByteBuf.readVarInt();
|
int updateId = friendlyByteBuf.readVarInt();
|
||||||
|
|
||||||
ServerWorldPropertiesRegistry registry = AxiomPaper.PLUGIN.getWorldProperties(player.getWorld());
|
// Call modify world
|
||||||
if (registry == null) return;
|
if (!this.plugin.canModifyWorld(player, player.getWorld())) {
|
||||||
|
sendAck(player, updateId);
|
||||||
ServerWorldProperty<?> property = registry.getById(id);
|
return;
|
||||||
if (property != null && property.getType().getTypeId() == type) {
|
|
||||||
property.update(player.getWorld(), data);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't allow on plot worlds
|
||||||
|
if (PlotSquaredIntegration.isPlotWorld(player.getWorld())) {
|
||||||
|
sendAck(player, updateId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerWorldPropertiesRegistry registry = AxiomPaper.PLUGIN.getOrCreateWorldProperties(player.getWorld());
|
||||||
|
if (registry == null) {
|
||||||
|
sendAck(player, updateId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ServerWorldPropertyHolder<?> property = registry.getById(id);
|
||||||
|
if (property != null && property.getType().getTypeId() == type) {
|
||||||
|
property.update(player, player.getWorld(), data);
|
||||||
|
}
|
||||||
|
|
||||||
|
sendAck(player, updateId);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendAck(Player player, int updateId) {
|
||||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||||
buf.writeVarInt(updateId);
|
buf.writeVarInt(updateId);
|
||||||
|
|
||||||
|
123
src/main/java/com/moulberry/axiom/packet/SpawnEntityPacketListener.java
Normale Datei
123
src/main/java/com/moulberry/axiom/packet/SpawnEntityPacketListener.java
Normale Datei
@ -0,0 +1,123 @@
|
|||||||
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
|
import com.moulberry.axiom.NbtSanitization;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import net.minecraft.core.BlockPos;
|
||||||
|
import net.minecraft.core.Direction;
|
||||||
|
import net.minecraft.nbt.CompoundTag;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.server.level.ServerLevel;
|
||||||
|
import net.minecraft.world.entity.Entity;
|
||||||
|
import net.minecraft.world.entity.EntityType;
|
||||||
|
import net.minecraft.world.entity.decoration.HangingEntity;
|
||||||
|
import net.minecraft.world.entity.decoration.ItemFrame;
|
||||||
|
import net.minecraft.world.level.Level;
|
||||||
|
import net.minecraft.world.level.block.Rotation;
|
||||||
|
import net.minecraft.world.phys.Vec3;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.UUID;
|
||||||
|
|
||||||
|
public class SpawnEntityPacketListener implements PluginMessageListener {
|
||||||
|
|
||||||
|
private final AxiomPaper plugin;
|
||||||
|
public SpawnEntityPacketListener(AxiomPaper plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record SpawnEntry(UUID newUuid, double x, double y, double z, float yaw, float pitch,
|
||||||
|
@Nullable UUID copyFrom, CompoundTag tag) {
|
||||||
|
public SpawnEntry(FriendlyByteBuf friendlyByteBuf) {
|
||||||
|
this(friendlyByteBuf.readUUID(), friendlyByteBuf.readDouble(), friendlyByteBuf.readDouble(),
|
||||||
|
friendlyByteBuf.readDouble(), friendlyByteBuf.readFloat(), friendlyByteBuf.readFloat(),
|
||||||
|
friendlyByteBuf.readNullable(FriendlyByteBuf::readUUID), friendlyByteBuf.readNbt());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final Rotation[] ROTATION_VALUES = Rotation.values();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||||
|
if (!this.plugin.canUseAxiom(player)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!player.hasPermission("axiom.entity.*") && !player.hasPermission("axiom.entity.spawn")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.plugin.canModifyWorld(player, player.getWorld())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
FriendlyByteBuf friendlyByteBuf = new FriendlyByteBuf(Unpooled.wrappedBuffer(message));
|
||||||
|
List<SpawnEntry> entries = friendlyByteBuf.readList(SpawnEntry::new);
|
||||||
|
|
||||||
|
ServerLevel serverLevel = ((CraftWorld)player.getWorld()).getHandle();
|
||||||
|
|
||||||
|
List<String> whitelistedEntities = this.plugin.configuration.getStringList("whitelist-entities");
|
||||||
|
List<String> blacklistedEntities = this.plugin.configuration.getStringList("blacklist-entities");
|
||||||
|
|
||||||
|
for (SpawnEntry entry : entries) {
|
||||||
|
Vec3 position = new Vec3(entry.x, entry.y, entry.z);
|
||||||
|
|
||||||
|
BlockPos blockPos = BlockPos.containing(position);
|
||||||
|
if (!Level.isInSpawnableBounds(blockPos)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (serverLevel.getEntity(entry.newUuid) != null) continue;
|
||||||
|
|
||||||
|
CompoundTag tag = entry.tag == null ? new CompoundTag() : entry.tag;
|
||||||
|
|
||||||
|
NbtSanitization.sanitizeEntity(tag);
|
||||||
|
|
||||||
|
if (entry.copyFrom != null) {
|
||||||
|
Entity entityCopyFrom = serverLevel.getEntity(entry.copyFrom);
|
||||||
|
if (entityCopyFrom != null) {
|
||||||
|
CompoundTag compoundTag = new CompoundTag();
|
||||||
|
if (entityCopyFrom.saveAsPassenger(compoundTag)) {
|
||||||
|
compoundTag.remove("Dimension");
|
||||||
|
tag = tag.merge(compoundTag);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tag.contains("id")) continue;
|
||||||
|
|
||||||
|
Entity spawned = EntityType.loadEntityRecursive(tag, serverLevel, entity -> {
|
||||||
|
String type = EntityType.getKey(entity.getType()).toString();
|
||||||
|
if (!whitelistedEntities.isEmpty() && !whitelistedEntities.contains(type)) return null;
|
||||||
|
if (blacklistedEntities.contains(type)) return null;
|
||||||
|
|
||||||
|
entity.setUUID(entry.newUuid);
|
||||||
|
|
||||||
|
if (entity instanceof HangingEntity hangingEntity) {
|
||||||
|
float changedYaw = entry.yaw - entity.getYRot();
|
||||||
|
int rotations = Math.round(changedYaw / 90);
|
||||||
|
hangingEntity.rotate(ROTATION_VALUES[rotations & 3]);
|
||||||
|
|
||||||
|
if (entity instanceof ItemFrame itemFrame && itemFrame.getDirection().getAxis() == Direction.Axis.Y) {
|
||||||
|
itemFrame.setRotation(itemFrame.getRotation() - Math.round(changedYaw / 45));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
entity.moveTo(position.x, position.y, position.z, entry.yaw, entry.pitch);
|
||||||
|
entity.setYHeadRot(entity.getYRot());
|
||||||
|
|
||||||
|
return entity;
|
||||||
|
});
|
||||||
|
|
||||||
|
if (spawned != null) {
|
||||||
|
serverLevel.tryAddFreshEntityWithPassengers(spawned);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package com.moulberry.axiom.packet;
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
import com.moulberry.axiom.AxiomConstants;
|
import com.moulberry.axiom.AxiomConstants;
|
||||||
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import com.moulberry.axiom.persistence.ItemStackDataType;
|
import com.moulberry.axiom.persistence.ItemStackDataType;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
@ -17,9 +18,14 @@ import org.jetbrains.annotations.NotNull;
|
|||||||
|
|
||||||
public class SwitchActiveHotbarPacketListener implements PluginMessageListener {
|
public class SwitchActiveHotbarPacketListener implements PluginMessageListener {
|
||||||
|
|
||||||
|
private final AxiomPaper plugin;
|
||||||
|
public SwitchActiveHotbarPacketListener(AxiomPaper plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||||
if (!player.hasPermission("axiom.*")) {
|
if (!this.plugin.canUseAxiom(player)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,22 +1,33 @@
|
|||||||
package com.moulberry.axiom.packet;
|
package com.moulberry.axiom.packet;
|
||||||
|
|
||||||
import com.moulberry.axiom.event.AxiomGameModeChangeEvent;
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
import com.moulberry.axiom.event.AxiomTeleportEvent;
|
import com.moulberry.axiom.event.AxiomTeleportEvent;
|
||||||
|
import com.moulberry.axiom.event.AxiomUnknownTeleportEvent;
|
||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import net.minecraft.core.registries.Registries;
|
import net.minecraft.core.registries.Registries;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.resources.ResourceKey;
|
import net.minecraft.resources.ResourceKey;
|
||||||
import net.minecraft.world.level.Level;
|
import net.minecraft.world.level.Level;
|
||||||
import org.bukkit.*;
|
import org.bukkit.Bukkit;
|
||||||
|
import org.bukkit.Location;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R1.entity.CraftPlayer;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R1.util.CraftNamespacedKey;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
public class TeleportPacketListener implements PluginMessageListener {
|
public class TeleportPacketListener implements PluginMessageListener {
|
||||||
|
|
||||||
|
private final AxiomPaper plugin;
|
||||||
|
public TeleportPacketListener(AxiomPaper plugin) {
|
||||||
|
this.plugin = plugin;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
public void onPluginMessageReceived(@NotNull String channel, @NotNull Player player, @NotNull byte[] message) {
|
||||||
if (!player.hasPermission("axiom.*")) {
|
if (!this.plugin.canUseAxiom(player)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -28,10 +39,28 @@ public class TeleportPacketListener implements PluginMessageListener {
|
|||||||
float yRot = friendlyByteBuf.readFloat();
|
float yRot = friendlyByteBuf.readFloat();
|
||||||
float xRot = friendlyByteBuf.readFloat();
|
float xRot = friendlyByteBuf.readFloat();
|
||||||
|
|
||||||
|
// Prevent teleport based on config value
|
||||||
|
boolean allowTeleportBetweenWorlds = this.plugin.configuration.getBoolean("allow-teleport-between-worlds");
|
||||||
|
if (!allowTeleportBetweenWorlds && !((CraftPlayer)player).getHandle().serverLevel().dimension().equals(resourceKey)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Call unknown teleport event
|
||||||
|
AxiomUnknownTeleportEvent preTeleportEvent = new AxiomUnknownTeleportEvent(player,
|
||||||
|
CraftNamespacedKey.fromMinecraft(resourceKey.location()), x, y, z, yRot, xRot);
|
||||||
|
Bukkit.getPluginManager().callEvent(preTeleportEvent);
|
||||||
|
if (preTeleportEvent.isCancelled()) return;
|
||||||
|
|
||||||
|
// Get bukkit world
|
||||||
NamespacedKey namespacedKey = new NamespacedKey(resourceKey.location().getNamespace(), resourceKey.location().getPath());
|
NamespacedKey namespacedKey = new NamespacedKey(resourceKey.location().getNamespace(), resourceKey.location().getPath());
|
||||||
World world = Bukkit.getWorld(namespacedKey);
|
World world = Bukkit.getWorld(namespacedKey);
|
||||||
if (world == null) return;
|
if (world == null) return;
|
||||||
|
|
||||||
|
// Prevent teleport based on config value
|
||||||
|
if (!allowTeleportBetweenWorlds && world != player.getWorld()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Call event
|
// Call event
|
||||||
AxiomTeleportEvent teleportEvent = new AxiomTeleportEvent(player, new Location(world, x, y, z, yRot, xRot));
|
AxiomTeleportEvent teleportEvent = new AxiomTeleportEvent(player, new Location(world, x, y, z, yRot, xRot));
|
||||||
Bukkit.getPluginManager().callEvent(teleportEvent);
|
Bukkit.getPluginManager().callEvent(teleportEvent);
|
||||||
|
@ -0,0 +1,18 @@
|
|||||||
|
package com.moulberry.axiom.world_properties;
|
||||||
|
|
||||||
|
import com.moulberry.axiom.world_properties.server.PropertyUpdateResult;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
@FunctionalInterface
|
||||||
|
public interface PropertyUpdateHandler<T> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param player the player that updated the property
|
||||||
|
* @param world the world for which the property has been updated
|
||||||
|
* @param value the new value of the property
|
||||||
|
* @return {@link PropertyUpdateResult}
|
||||||
|
*/
|
||||||
|
PropertyUpdateResult update(Player player, World world, T value);
|
||||||
|
|
||||||
|
}
|
@ -3,9 +3,10 @@ package com.moulberry.axiom.world_properties;
|
|||||||
import io.netty.buffer.Unpooled;
|
import io.netty.buffer.Unpooled;
|
||||||
import net.minecraft.core.registries.BuiltInRegistries;
|
import net.minecraft.core.registries.BuiltInRegistries;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.util.Unit;
|
|
||||||
import net.minecraft.world.item.Item;
|
import net.minecraft.world.item.Item;
|
||||||
|
import net.minecraft.world.item.Items;
|
||||||
import net.minecraft.world.level.block.Block;
|
import net.minecraft.world.level.block.Block;
|
||||||
|
import net.minecraft.world.level.block.Blocks;
|
||||||
|
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
@ -23,7 +24,7 @@ public abstract class WorldPropertyDataType<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] serialize(Boolean value) {
|
public byte[] serialize(Boolean value) {
|
||||||
return new byte[] { value ? (byte)1 : (byte)0 };
|
return new byte[] { value != null && value ? (byte)1 : (byte)0 };
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -40,6 +41,8 @@ public abstract class WorldPropertyDataType<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] serialize(Integer value) {
|
public byte[] serialize(Integer value) {
|
||||||
|
if (value == null) value = 0;
|
||||||
|
|
||||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(8));
|
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(8));
|
||||||
buf.writeVarInt(value);
|
buf.writeVarInt(value);
|
||||||
|
|
||||||
@ -63,6 +66,7 @@ public abstract class WorldPropertyDataType<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] serialize(String value) {
|
public byte[] serialize(String value) {
|
||||||
|
if (value == null) value = "";
|
||||||
return value.getBytes(StandardCharsets.UTF_8);
|
return value.getBytes(StandardCharsets.UTF_8);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -80,6 +84,8 @@ public abstract class WorldPropertyDataType<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] serialize(Item value) {
|
public byte[] serialize(Item value) {
|
||||||
|
if (value == null) value = Items.AIR;
|
||||||
|
|
||||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(8));
|
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(8));
|
||||||
buf.writeId(BuiltInRegistries.ITEM, value);
|
buf.writeId(BuiltInRegistries.ITEM, value);
|
||||||
|
|
||||||
@ -103,6 +109,8 @@ public abstract class WorldPropertyDataType<T> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] serialize(Block value) {
|
public byte[] serialize(Block value) {
|
||||||
|
if (value == null) value = Blocks.AIR;
|
||||||
|
|
||||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(8));
|
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer(8));
|
||||||
buf.writeId(BuiltInRegistries.BLOCK, value);
|
buf.writeId(BuiltInRegistries.BLOCK, value);
|
||||||
|
|
||||||
@ -118,20 +126,20 @@ public abstract class WorldPropertyDataType<T> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
public static WorldPropertyDataType<Unit> EMPTY = new WorldPropertyDataType<>() {
|
public static WorldPropertyDataType<Void> EMPTY = new WorldPropertyDataType<>() {
|
||||||
@Override
|
@Override
|
||||||
public int getTypeId() {
|
public int getTypeId() {
|
||||||
return 5;
|
return 5;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] serialize(Unit value) {
|
public byte[] serialize(Void value) {
|
||||||
return new byte[0];
|
return new byte[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Unit deserialize(byte[] bytes) {
|
public Void deserialize(byte[] bytes) {
|
||||||
return Unit.INSTANCE;
|
return null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,8 +1,6 @@
|
|||||||
package com.moulberry.axiom.world_properties;
|
package com.moulberry.axiom.world_properties;
|
||||||
|
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import net.minecraft.util.Unit;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -61,10 +59,9 @@ public interface WorldPropertyWidgetType<T> {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
WorldPropertyWidgetType<Void> BUTTON = new WorldPropertyWidgetType<>() {
|
||||||
WorldPropertyWidgetType<Unit> BUTTON = new WorldPropertyWidgetType<>() {
|
|
||||||
@Override
|
@Override
|
||||||
public WorldPropertyDataType<Unit> dataType() {
|
public WorldPropertyDataType<Void> dataType() {
|
||||||
return WorldPropertyDataType.EMPTY;
|
return WorldPropertyDataType.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,25 @@
|
|||||||
|
package com.moulberry.axiom.world_properties.server;
|
||||||
|
|
||||||
|
public enum PropertyUpdateResult {
|
||||||
|
|
||||||
|
UPDATE_AND_SYNC(true, true),
|
||||||
|
UPDATE_WITHOUT_SYNC(true, false),
|
||||||
|
CANCEL(false, false);
|
||||||
|
|
||||||
|
private final boolean update;
|
||||||
|
private final boolean sync;
|
||||||
|
|
||||||
|
PropertyUpdateResult(boolean update, boolean sync) {
|
||||||
|
this.update = update;
|
||||||
|
this.sync = sync;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isUpdate() {
|
||||||
|
return this.update;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isSync() {
|
||||||
|
return this.sync;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -6,40 +6,46 @@ import io.netty.buffer.Unpooled;
|
|||||||
import net.minecraft.network.FriendlyByteBuf;
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
import net.minecraft.resources.ResourceLocation;
|
import net.minecraft.resources.ResourceLocation;
|
||||||
import net.minecraft.server.level.ServerLevel;
|
import net.minecraft.server.level.ServerLevel;
|
||||||
import net.minecraft.world.level.GameRules;
|
|
||||||
import org.bukkit.GameRule;
|
import org.bukkit.GameRule;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
|
import org.bukkit.craftbukkit.v1_20_R1.CraftWorld;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
import java.util.HashMap;
|
import java.util.*;
|
||||||
import java.util.LinkedHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
public class ServerWorldPropertiesRegistry {
|
public class ServerWorldPropertiesRegistry {
|
||||||
|
|
||||||
private final LinkedHashMap<WorldPropertyCategory, List<ServerWorldProperty<?>>> propertyList = new LinkedHashMap<>();
|
private final LinkedHashMap<WorldPropertyCategory, List<ServerWorldPropertyHolder<?>>> propertyList = new LinkedHashMap<>();
|
||||||
private final Map<ResourceLocation, ServerWorldProperty<?>> propertyMap = new HashMap<>();
|
private final Map<ResourceLocation, ServerWorldPropertyHolder<?>> propertyMap = new HashMap<>();
|
||||||
|
private final World world;
|
||||||
|
|
||||||
public ServerWorldPropertiesRegistry(World world) {
|
public ServerWorldPropertiesRegistry(World world) {
|
||||||
this.registerDefault(world);
|
this.world = world;
|
||||||
|
this.registerDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ServerWorldProperty<?> getById(ResourceLocation resourceLocation) {
|
public ServerWorldPropertyHolder<?> getById(ResourceLocation resourceLocation) {
|
||||||
return propertyMap.get(resourceLocation);
|
return propertyMap.get(resourceLocation);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addCategory(WorldPropertyCategory category, List<ServerWorldProperty<?>> properties) {
|
@SuppressWarnings("unchecked")
|
||||||
this.propertyList.put(category, properties);
|
public void addCategory(WorldPropertyCategory category, List<ServerWorldPropertyBase<?>> properties) {
|
||||||
|
List<ServerWorldPropertyHolder<?>> holders = new ArrayList<>();
|
||||||
|
for (ServerWorldPropertyBase<?> property : properties) {
|
||||||
|
Object defaultValue = property.getDefaultValue(this.world);
|
||||||
|
holders.add(new ServerWorldPropertyHolder<>(defaultValue, (ServerWorldPropertyBase<Object>) property));
|
||||||
|
}
|
||||||
|
|
||||||
for (ServerWorldProperty<?> property : properties) {
|
this.propertyList.put(category, holders);
|
||||||
ResourceLocation id = property.getId();
|
|
||||||
|
for (ServerWorldPropertyHolder<?> holder : holders) {
|
||||||
|
ResourceLocation id = holder.getId();
|
||||||
if (this.propertyMap.containsKey(id)) {
|
if (this.propertyMap.containsKey(id)) {
|
||||||
throw new RuntimeException("Duplicate property: " + id);
|
throw new RuntimeException("Duplicate property: " + id);
|
||||||
}
|
}
|
||||||
this.propertyMap.put(id, property);
|
this.propertyMap.put(id, holder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -48,7 +54,7 @@ public class ServerWorldPropertiesRegistry {
|
|||||||
|
|
||||||
buf.writeVarInt(this.propertyList.size());
|
buf.writeVarInt(this.propertyList.size());
|
||||||
|
|
||||||
for (Map.Entry<WorldPropertyCategory, List<ServerWorldProperty<?>>> entry : this.propertyList.entrySet()) {
|
for (Map.Entry<WorldPropertyCategory, List<ServerWorldPropertyHolder<?>>> entry : this.propertyList.entrySet()) {
|
||||||
entry.getKey().write(buf);
|
entry.getKey().write(buf);
|
||||||
buf.writeCollection(entry.getValue(), (buffer, p) -> p.write(buffer));
|
buf.writeCollection(entry.getValue(), (buffer, p) -> p.write(buffer));
|
||||||
}
|
}
|
||||||
@ -58,46 +64,51 @@ public class ServerWorldPropertiesRegistry {
|
|||||||
bukkitPlayer.sendPluginMessage(plugin, "axiom:register_world_properties", bytes);
|
bukkitPlayer.sendPluginMessage(plugin, "axiom:register_world_properties", bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void registerDefault(World world) {
|
private static final ServerWorldProperty<Integer> TIME = new ServerWorldProperty<>(
|
||||||
ServerLevel serverLevel = ((CraftWorld)world).getHandle();
|
new NamespacedKey("axiom", "time"),
|
||||||
|
"axiom.editorui.window.world_properties.time",
|
||||||
|
true, WorldPropertyWidgetType.TIME, world -> 0,
|
||||||
|
(player, w, integer) -> PropertyUpdateResult.UPDATE_WITHOUT_SYNC
|
||||||
|
);
|
||||||
|
|
||||||
|
public static final ServerWorldProperty<Boolean> PAUSE_WEATHER = new ServerWorldProperty<>(
|
||||||
|
new NamespacedKey("axiom", "pause_weather"),
|
||||||
|
"axiom.editorui.window.world_properties.pause_weather",
|
||||||
|
true, WorldPropertyWidgetType.CHECKBOX, world -> !world.getGameRuleValue(GameRule.DO_WEATHER_CYCLE),
|
||||||
|
(player, world, bool) -> {
|
||||||
|
world.setGameRule(GameRule.DO_WEATHER_CYCLE, !bool);
|
||||||
|
return PropertyUpdateResult.UPDATE_WITHOUT_SYNC;
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final ServerWorldProperty<Integer> WEATHER_TYPE = new ServerWorldProperty<>(
|
||||||
|
new NamespacedKey("axiom", "weather_type"),
|
||||||
|
"axiom.editorui.window.world_properties.clear_weather",
|
||||||
|
true, new WorldPropertyWidgetType.ButtonArray(
|
||||||
|
List.of("axiom.editorui.window.world_properties.rain_weather", "axiom.editorui.window.world_properties.thunder_weather")
|
||||||
|
), world -> 0, (player, world, index) -> {
|
||||||
|
ServerLevel serverLevel = ((CraftWorld)world).getHandle();
|
||||||
|
if (index == 0) {
|
||||||
|
serverLevel.setWeatherParameters(ServerLevel.RAIN_DELAY.sample(serverLevel.random), 0, false, false);
|
||||||
|
} else if (index == 1) {
|
||||||
|
serverLevel.setWeatherParameters(0, ServerLevel.RAIN_DURATION.sample(serverLevel.random), true, false);
|
||||||
|
} else if (index == 2) {
|
||||||
|
serverLevel.setWeatherParameters(0, ServerLevel.THUNDER_DURATION.sample(serverLevel.random), true, true);
|
||||||
|
}
|
||||||
|
return PropertyUpdateResult.UPDATE_WITHOUT_SYNC;
|
||||||
|
});
|
||||||
|
|
||||||
|
public void registerDefault() {
|
||||||
// Time
|
// Time
|
||||||
WorldPropertyCategory timeCategory = new WorldPropertyCategory("axiom.editorui.window.world_properties.time", true);
|
WorldPropertyCategory timeCategory = new WorldPropertyCategory("axiom.editorui.window.world_properties.time", true);
|
||||||
|
|
||||||
ServerWorldProperty<Integer> time = new ServerWorldProperty<>(new ResourceLocation("axiom:time"),
|
this.addCategory(timeCategory, List.of(TIME));
|
||||||
"axiom.editorui.window.world_properties.time",
|
|
||||||
true, WorldPropertyWidgetType.TIME, 0, integer -> false
|
|
||||||
);
|
|
||||||
|
|
||||||
this.addCategory(timeCategory, List.of(time));
|
|
||||||
|
|
||||||
// Weather
|
// Weather
|
||||||
WorldPropertyCategory weatherCategory = new WorldPropertyCategory("axiom.editorui.window.world_properties.weather",
|
WorldPropertyCategory weatherCategory = new WorldPropertyCategory("axiom.editorui.window.world_properties.weather",
|
||||||
true);
|
true);
|
||||||
|
|
||||||
ServerWorldProperty<Boolean> pauseWeather = new ServerWorldProperty<>(new ResourceLocation("axiom:pause_weather"),
|
this.addCategory(weatherCategory, List.of(PAUSE_WEATHER, WEATHER_TYPE));
|
||||||
"axiom.editorui.window.world_properties.pause_weather",
|
|
||||||
true, WorldPropertyWidgetType.CHECKBOX, !world.getGameRuleValue(GameRule.DO_WEATHER_CYCLE), bool -> {
|
|
||||||
world.setGameRule(GameRule.DO_WEATHER_CYCLE, !bool);
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
ServerWorldProperty<Integer> weatherType = new ServerWorldProperty<>(new ResourceLocation("axiom:weather_type"),
|
|
||||||
"axiom.editorui.window.world_properties.clear_weather",
|
|
||||||
true, new WorldPropertyWidgetType.ButtonArray(
|
|
||||||
List.of("axiom.editorui.window.world_properties.rain_weather", "axiom.editorui.window.world_properties.thunder_weather")
|
|
||||||
), 0, index -> {
|
|
||||||
if (index == 0) {
|
|
||||||
serverLevel.setWeatherParameters(ServerLevel.RAIN_DELAY.sample(serverLevel.random), 0, false, false);
|
|
||||||
} else if (index == 1) {
|
|
||||||
serverLevel.setWeatherParameters(0, ServerLevel.RAIN_DURATION.sample(serverLevel.random), true, false);
|
|
||||||
} else if (index == 2) {
|
|
||||||
serverLevel.setWeatherParameters(0, ServerLevel.THUNDER_DURATION.sample(serverLevel.random), true, true);
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
});
|
|
||||||
|
|
||||||
this.addCategory(weatherCategory, List.of(pauseWeather, weatherType));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,81 +1,33 @@
|
|||||||
package com.moulberry.axiom.world_properties.server;
|
package com.moulberry.axiom.world_properties.server;
|
||||||
|
|
||||||
import com.moulberry.axiom.AxiomPaper;
|
import com.moulberry.axiom.world_properties.PropertyUpdateHandler;
|
||||||
import com.moulberry.axiom.world_properties.WorldPropertyDataType;
|
|
||||||
import com.moulberry.axiom.world_properties.WorldPropertyWidgetType;
|
import com.moulberry.axiom.world_properties.WorldPropertyWidgetType;
|
||||||
import io.netty.buffer.Unpooled;
|
import org.bukkit.NamespacedKey;
|
||||||
import net.minecraft.network.FriendlyByteBuf;
|
|
||||||
import net.minecraft.resources.ResourceLocation;
|
|
||||||
import org.bukkit.World;
|
import org.bukkit.World;
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Function;
|
||||||
|
|
||||||
public class ServerWorldProperty<T> {
|
public class ServerWorldProperty<T> extends ServerWorldPropertyBase<T> {
|
||||||
|
|
||||||
private final ResourceLocation id;
|
private final Function<World, T> defaultValueFunction;
|
||||||
private final String name;
|
private final PropertyUpdateHandler<T> handler;
|
||||||
private final boolean localizeName;
|
|
||||||
private WorldPropertyWidgetType<T> widget;
|
|
||||||
private T value;
|
|
||||||
private Predicate<T> handler;
|
|
||||||
|
|
||||||
public ServerWorldProperty(ResourceLocation id, String name, boolean localizeName, WorldPropertyWidgetType<T> widget,
|
public ServerWorldProperty(NamespacedKey id, String name, boolean localizeName, WorldPropertyWidgetType<T> widget,
|
||||||
T value, Predicate<T> handler) {
|
Function<World, T> defaultValueFunction, PropertyUpdateHandler<T> handler) {
|
||||||
this.id = id;
|
super(id, name, localizeName, widget);
|
||||||
this.name = name;
|
this.defaultValueFunction = defaultValueFunction;
|
||||||
this.localizeName = localizeName;
|
|
||||||
this.widget = widget;
|
|
||||||
this.value = value;
|
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourceLocation getId() {
|
@Override
|
||||||
return this.id;
|
public T getDefaultValue(World world) {
|
||||||
|
return this.defaultValueFunction.apply(world);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WorldPropertyDataType<T> getType() {
|
@Override
|
||||||
return this.widget.dataType();
|
public PropertyUpdateResult handleUpdateProperty(Player player, World world, T value) {
|
||||||
}
|
return this.handler.update(player, world, value);
|
||||||
|
|
||||||
public void update(World world, byte[] data) {
|
|
||||||
this.value = this.widget.dataType().deserialize(data);
|
|
||||||
if (this.handler.test(this.value)) {
|
|
||||||
this.sync(world);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValueWithoutSyncing(T value) {
|
|
||||||
this.value = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setValue(World world, T value) {
|
|
||||||
this.value = value;
|
|
||||||
this.sync(world);
|
|
||||||
}
|
|
||||||
|
|
||||||
public void sync(World world) {
|
|
||||||
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
|
||||||
|
|
||||||
buf.writeResourceLocation(this.id);
|
|
||||||
buf.writeVarInt(this.widget.dataType().getTypeId());
|
|
||||||
buf.writeByteArray(this.widget.dataType().serialize(this.value));
|
|
||||||
|
|
||||||
byte[] message = new byte[buf.writerIndex()];
|
|
||||||
buf.getBytes(0, message);
|
|
||||||
for (Player player : world.getPlayers()) {
|
|
||||||
if (AxiomPaper.PLUGIN.activeAxiomPlayers.contains(player.getUniqueId())) {
|
|
||||||
player.sendPluginMessage(AxiomPaper.PLUGIN, "axiom:set_world_property", message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void write(FriendlyByteBuf friendlyByteBuf) {
|
|
||||||
friendlyByteBuf.writeResourceLocation(this.id);
|
|
||||||
friendlyByteBuf.writeUtf(this.name);
|
|
||||||
friendlyByteBuf.writeBoolean(this.localizeName);
|
|
||||||
this.widget.write(friendlyByteBuf);
|
|
||||||
friendlyByteBuf.writeByteArray(this.widget.dataType().serialize(this.value));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
package com.moulberry.axiom.world_properties.server;
|
||||||
|
|
||||||
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
|
import com.moulberry.axiom.world_properties.WorldPropertyDataType;
|
||||||
|
import com.moulberry.axiom.world_properties.WorldPropertyWidgetType;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import org.bukkit.NamespacedKey;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.craftbukkit.v1_20_R1.util.CraftNamespacedKey;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
public abstract class ServerWorldPropertyBase<T> {
|
||||||
|
|
||||||
|
private final ResourceLocation id;
|
||||||
|
/*package-private*/ final String name;
|
||||||
|
/*package-private*/ final boolean localizeName;
|
||||||
|
/*package-private*/ WorldPropertyWidgetType<T> widget;
|
||||||
|
|
||||||
|
public ServerWorldPropertyBase(NamespacedKey id, String name, boolean localizeName, WorldPropertyWidgetType<T> widget) {
|
||||||
|
this.id = CraftNamespacedKey.toMinecraft(id);
|
||||||
|
this.name = name;
|
||||||
|
this.localizeName = localizeName;
|
||||||
|
this.widget = widget;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract T getDefaultValue(World world);
|
||||||
|
|
||||||
|
public abstract PropertyUpdateResult handleUpdateProperty(Player player, World world, T value);
|
||||||
|
|
||||||
|
public ResourceLocation getId() {
|
||||||
|
return this.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorldPropertyDataType<T> getType() {
|
||||||
|
return this.widget.dataType();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public boolean setValueWithoutSyncing(World world, T value) {
|
||||||
|
ServerWorldPropertiesRegistry properties = AxiomPaper.PLUGIN.getWorldPropertiesIfPresent(world);
|
||||||
|
if (properties != null) {
|
||||||
|
ServerWorldPropertyHolder<?> property = properties.getById(this.id);
|
||||||
|
if (property != null && property.getProperty() == this) {
|
||||||
|
((ServerWorldPropertyHolder<T>)property).setValueWithoutSyncing(value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public boolean setValue(World world, T value) {
|
||||||
|
ServerWorldPropertiesRegistry properties = AxiomPaper.PLUGIN.getWorldPropertiesIfPresent(world);
|
||||||
|
if (properties != null) {
|
||||||
|
ServerWorldPropertyHolder<?> property = properties.getById(this.id);
|
||||||
|
if (property != null && property.getProperty() == this) {
|
||||||
|
((ServerWorldPropertyHolder<T>)property).setValue(world, value);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,90 @@
|
|||||||
|
package com.moulberry.axiom.world_properties.server;
|
||||||
|
|
||||||
|
import com.moulberry.axiom.AxiomPaper;
|
||||||
|
import com.moulberry.axiom.world_properties.WorldPropertyDataType;
|
||||||
|
import io.netty.buffer.Unpooled;
|
||||||
|
import net.minecraft.network.FriendlyByteBuf;
|
||||||
|
import net.minecraft.resources.ResourceLocation;
|
||||||
|
import org.bukkit.World;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
public class ServerWorldPropertyHolder<T> {
|
||||||
|
|
||||||
|
private T value;
|
||||||
|
private ServerWorldPropertyBase<T> property;
|
||||||
|
private boolean unsyncedValue = false;
|
||||||
|
|
||||||
|
public ServerWorldPropertyHolder(T value, ServerWorldPropertyBase<T> property) {
|
||||||
|
this.value = value;
|
||||||
|
this.property = property;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResourceLocation getId() {
|
||||||
|
return this.property.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
public WorldPropertyDataType<T> getType() {
|
||||||
|
return this.property.widget.dataType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ServerWorldPropertyBase<T> getProperty() {
|
||||||
|
return property;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(Player player, World world, byte[] data) {
|
||||||
|
T newValue = this.property.widget.dataType().deserialize(data);
|
||||||
|
|
||||||
|
PropertyUpdateResult result = this.property.handleUpdateProperty(player, world, newValue);
|
||||||
|
|
||||||
|
if (result.isUpdate()) {
|
||||||
|
this.value = newValue;
|
||||||
|
|
||||||
|
if (result.isSync()) {
|
||||||
|
this.sync(world);
|
||||||
|
} else {
|
||||||
|
this.unsyncedValue = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValueWithoutSyncing(T value) {
|
||||||
|
this.value = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setValue(World world, T value) {
|
||||||
|
boolean sync = this.unsyncedValue || !Objects.equals(value, this.value);
|
||||||
|
this.value = value;
|
||||||
|
if (sync) {
|
||||||
|
this.sync(world);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void sync(World world) {
|
||||||
|
FriendlyByteBuf buf = new FriendlyByteBuf(Unpooled.buffer());
|
||||||
|
|
||||||
|
buf.writeResourceLocation(this.getId());
|
||||||
|
buf.writeVarInt(this.property.widget.dataType().getTypeId());
|
||||||
|
buf.writeByteArray(this.property.widget.dataType().serialize(this.value));
|
||||||
|
|
||||||
|
byte[] message = new byte[buf.writerIndex()];
|
||||||
|
buf.getBytes(0, message);
|
||||||
|
for (Player player : world.getPlayers()) {
|
||||||
|
if (AxiomPaper.PLUGIN.activeAxiomPlayers.contains(player.getUniqueId())) {
|
||||||
|
player.sendPluginMessage(AxiomPaper.PLUGIN, "axiom:set_world_property", message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this.unsyncedValue = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void write(FriendlyByteBuf friendlyByteBuf) {
|
||||||
|
friendlyByteBuf.writeResourceLocation(this.getId());
|
||||||
|
friendlyByteBuf.writeUtf(this.property.name);
|
||||||
|
friendlyByteBuf.writeBoolean(this.property.localizeName);
|
||||||
|
this.property.widget.write(friendlyByteBuf);
|
||||||
|
friendlyByteBuf.writeByteArray(this.property.widget.dataType().serialize(this.value));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
78
src/main/resources/config.yml
Normale Datei
78
src/main/resources/config.yml
Normale Datei
@ -0,0 +1,78 @@
|
|||||||
|
# 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
|
||||||
|
|
||||||
|
# Maximum distance from which players can load chunks
|
||||||
|
# Set to 0 to only allow editing within render distance
|
||||||
|
max-chunk-load-distance: 128
|
||||||
|
|
||||||
|
# Whether players are allowed to teleport between worlds using views
|
||||||
|
allow-teleport-between-worlds: true
|
||||||
|
|
||||||
|
# Action to take when a user with an incompatible Minecraft version or Axiom version joins
|
||||||
|
# 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
|
||||||
|
incompatible-data-version: "warn"
|
||||||
|
unsupported-axiom-version: "warn"
|
||||||
|
client-doesnt-support-restrictions: "ignore"
|
||||||
|
|
||||||
|
# Maximum packet size. Must not be less than 32767
|
||||||
|
max-block-buffer-packet-size: 0x100000
|
||||||
|
|
||||||
|
# Regex for whitelisting worlds
|
||||||
|
# The world can only be modified if the regex matches the world's name
|
||||||
|
whitelist-world-regex: null
|
||||||
|
|
||||||
|
# Regex for blacklisting worlds
|
||||||
|
# If the regex matches the world's name, the world can't be modified
|
||||||
|
blacklist-world-regex: null
|
||||||
|
|
||||||
|
# Block buffer rate-limit (in chunk sections per second), 0 = no limit
|
||||||
|
block-buffer-rate-limit: 0
|
||||||
|
|
||||||
|
# Log large block buffer changes
|
||||||
|
log-large-block-buffer-changes: false
|
||||||
|
|
||||||
|
# Whitelist entities that can be spawned/manipulated/deleted by the client
|
||||||
|
whitelist-entities:
|
||||||
|
# - "minecraft:item_display"
|
||||||
|
# - "minecraft:block_display"
|
||||||
|
# - "minecraft:text_display"
|
||||||
|
# - "minecraft:painting"
|
||||||
|
# - "minecraft:armor_stand"
|
||||||
|
# - "minecraft:item_frame"
|
||||||
|
# - "minecraft:glow_item_frame"
|
||||||
|
|
||||||
|
# Blacklist entities that can be spawned/manipulated/deleted by the client
|
||||||
|
blacklist-entities:
|
||||||
|
# - "minecraft:ender_dragon"
|
||||||
|
|
||||||
|
# True allows players to see/manipulate marker entities
|
||||||
|
send-markers: false
|
||||||
|
|
||||||
|
# Disallowed blocks
|
||||||
|
disallowed-blocks:
|
||||||
|
# - "minecraft:wheat"
|
||||||
|
# - "minecraft:oak_stairs[waterlogged=true]"
|
||||||
|
|
||||||
|
# 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
|
||||||
|
spawn-entity: true
|
||||||
|
manipulate-entity: true
|
||||||
|
delete-entity: true
|
||||||
|
marker-nbt-request: true
|
@ -7,6 +7,22 @@ authors:
|
|||||||
api-version: "$apiVersion"
|
api-version: "$apiVersion"
|
||||||
permissions:
|
permissions:
|
||||||
axiom.*:
|
axiom.*:
|
||||||
description: Allows use of all Axiom features
|
description: Allows use of all default Axiom features
|
||||||
default: op
|
default: op
|
||||||
|
|
||||||
|
axiom.entity.*:
|
||||||
|
description: Allows use of all entity-related features (spawning, manipulating, deleting)
|
||||||
|
default: op
|
||||||
|
axiom.entity.spawn:
|
||||||
|
description: Allows entity spawning
|
||||||
|
axiom.entity.manipulate:
|
||||||
|
description: Allows entity manipulation
|
||||||
|
axiom.entity.delete:
|
||||||
|
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
|
||||||
|
@ -6,7 +6,7 @@ build:
|
|||||||
- "JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 ./gradlew --stop"
|
- "JAVA_HOME=/usr/lib/jvm/java-17-openjdk-amd64 ./gradlew --stop"
|
||||||
|
|
||||||
artifacts:
|
artifacts:
|
||||||
"/binarys/AxiomPaper.jar": "build/libs/AxiomPaper-1.5.1.jar"
|
"/binarys/AxiomPaper.jar": "build/libs/AxiomPaper-1.5.8.jar"
|
||||||
|
|
||||||
release:
|
release:
|
||||||
- "mvn deploy:deploy-file -DgroupId=de.steamwar -DartifactId=axiompaper -Dversion=RELEASE -Dpackaging=jar -Dfile=build/libs/AxiomPaper-1.5.1.jar -Durl=file:///var/www/html/maven/"
|
- "mvn deploy:deploy-file -DgroupId=de.steamwar -DartifactId=axiompaper -Dversion=RELEASE -Dpackaging=jar -Dfile=build/libs/AxiomPaper-1.5.8.jar -Durl=file:///var/www/html/maven/"
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren