diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java index e57d3602f..2b895c5a8 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/BukkitPlayer.java @@ -62,6 +62,7 @@ import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.permissions.PermissionAttachment; +import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Locale; import java.util.Map; @@ -283,7 +284,7 @@ public class BukkitPlayer extends AbstractPlayerActor { if (params.length > 0) { send = send + "|" + StringUtil.joinString(params, "|"); } - player.sendPluginMessage(plugin, WorldEditPlugin.CUI_PLUGIN_CHANNEL, send.getBytes(CUIChannelListener.UTF_8_CHARSET)); + player.sendPluginMessage(plugin, WorldEditPlugin.CUI_PLUGIN_CHANNEL, send.getBytes(StandardCharsets.UTF_8)); } public Player getPlayer() { @@ -343,18 +344,18 @@ public class BukkitPlayer extends AbstractPlayerActor { @Override public SessionKey getSessionKey() { - return new SessionKeyImpl(this.player.getUniqueId(), player.getName()); + return new SessionKeyImpl(this.player); } - private static class SessionKeyImpl implements SessionKey { + static class SessionKeyImpl implements SessionKey { // If not static, this will leak a reference private final UUID uuid; private final String name; - private SessionKeyImpl(UUID uuid, String name) { - this.uuid = uuid; - this.name = name; + SessionKeyImpl(Player player) { + this.uuid = player.getUniqueId(); + this.name = player.getName(); } @Override diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/CUIChannelListener.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/CUIChannelListener.java index bed4eebd4..2c6b0f11f 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/CUIChannelListener.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/CUIChannelListener.java @@ -31,7 +31,6 @@ import java.nio.charset.StandardCharsets; */ public class CUIChannelListener implements PluginMessageListener { - public static final Charset UTF_8_CHARSET = StandardCharsets.UTF_8; private final WorldEditPlugin plugin; public CUIChannelListener(WorldEditPlugin plugin) { @@ -41,10 +40,9 @@ public class CUIChannelListener implements PluginMessageListener { @Override public void onPluginMessageReceived(String channel, Player player, byte[] message) { LocalSession session = plugin.getSession(player); - String text = new String(message, UTF_8_CHARSET); + String text = new String(message, StandardCharsets.UTF_8); final BukkitPlayer actor = plugin.wrapPlayer(player); session.handleCUIInitializationMessage(text, actor); - session.describeCUI(actor); } } diff --git a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditListener.java b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditListener.java index 388310f3e..fbaf2dedb 100644 --- a/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditListener.java +++ b/worldedit-bukkit/src/main/java/com/sk89q/worldedit/bukkit/WorldEditListener.java @@ -23,6 +23,7 @@ package com.sk89q.worldedit.bukkit; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.entity.Player; +import com.sk89q.worldedit.event.platform.SessionIdleEvent; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.util.Location; import com.sk89q.worldedit.world.World; @@ -35,6 +36,7 @@ import org.bukkit.event.block.Action; import org.bukkit.event.player.PlayerCommandSendEvent; import org.bukkit.event.player.PlayerGameModeChangeEvent; import org.bukkit.event.player.PlayerInteractEvent; +import org.bukkit.event.player.PlayerQuitEvent; import org.bukkit.inventory.EquipmentSlot; import org.enginehub.piston.CommandManager; import org.enginehub.piston.inject.InjectedValueStore; @@ -142,4 +144,9 @@ public class WorldEditListener implements Listener { } } } + + @EventHandler + public void onPlayerQuit(PlayerQuitEvent event) { + plugin.getWorldEdit().getEventBus().post(new SessionIdleEvent(new BukkitPlayer.SessionKeyImpl(event.getPlayer()))); + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java index 127ae1fb0..3eed26198 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/LocalSession.java @@ -107,12 +107,17 @@ import static com.google.common.base.Preconditions.checkNotNull; */ public class LocalSession implements TextureHolder { + private static final transient int CUI_VERSION_UNINITIALIZED = -1; public static transient int MAX_HISTORY_SIZE = 15; // Non-session related fields private transient LocalConfiguration config; private final transient AtomicBoolean dirty = new AtomicBoolean(); + + // Single-connection lifetime fields private transient int failedCuiAttempts = 0; + private transient boolean hasCUISupport = false; + private transient int cuiVersion = CUI_VERSION_UNINITIALIZED; // Session related private transient RegionSelector selector = new CuboidRegionSelector(); @@ -145,8 +150,6 @@ public class LocalSession implements TextureHolder { private transient boolean useInventory; private transient com.sk89q.worldedit.world.snapshot.Snapshot snapshot; private transient Snapshot snapshotExperimental; - private transient boolean hasCUISupport = false; - private transient int cuiVersion = -1; private transient SideEffectSet sideEffectSet = SideEffectSet.defaults(); private transient Mask mask; private transient Mask sourceMask; @@ -1398,7 +1401,12 @@ public class LocalSession implements TextureHolder { */ public void handleCUIInitializationMessage(String text, Actor actor) { checkNotNull(text); - if (this.hasCUISupport || this.failedCuiAttempts > 3) { + if (this.hasCUISupport) { + // WECUI is a bit aggressive about re-initializing itself + // the last attempt to touch handshakes didn't go well, so this will do... for now + dispatchCUISelection(actor); + return; + } else if (this.failedCuiAttempts > 3) { return; } @@ -1456,6 +1464,10 @@ public class LocalSession implements TextureHolder { * @param cuiVersion the CUI version */ public void setCUIVersion(int cuiVersion) { + if (cuiVersion < 0) { + throw new IllegalArgumentException("CUI protocol version must be non-negative, but '" + cuiVersion + "' was received."); + } + this.cuiVersion = cuiVersion; } @@ -1687,4 +1699,15 @@ public class LocalSession implements TextureHolder { this.transform = transform; } + + /** + * Call when this session has become inactive. + * + *

This is for internal use only.

+ */ + public void onIdle() { + this.cuiVersion = CUI_VERSION_UNINITIALIZED; + this.hasCUISupport = false; + this.failedCuiAttempts = 0; + } } diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/event/platform/SessionIdleEvent.java b/worldedit-core/src/main/java/com/sk89q/worldedit/event/platform/SessionIdleEvent.java new file mode 100644 index 000000000..a304d0b29 --- /dev/null +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/event/platform/SessionIdleEvent.java @@ -0,0 +1,45 @@ +/* + * WorldEdit, a Minecraft world manipulation toolkit + * Copyright (C) sk89q + * Copyright (C) WorldEdit 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 . + */ + +package com.sk89q.worldedit.event.platform; + +import com.sk89q.worldedit.event.Event; +import com.sk89q.worldedit.session.SessionKey; + +/** + * An event fired when a session becomes idle. + * + *

This can happen when a player leaves the server.

+ */ +public final class SessionIdleEvent extends Event { + private final SessionKey key; + + public SessionIdleEvent(SessionKey key) { + this.key = key; + } + + /** + * Get a key identifying the session that has become idle. + * + * @return the key for the session + */ + public SessionKey getKey() { + return this.key; + } +} diff --git a/worldedit-core/src/main/java/com/sk89q/worldedit/session/SessionManager.java b/worldedit-core/src/main/java/com/sk89q/worldedit/session/SessionManager.java index 4f98906c7..1e3841147 100644 --- a/worldedit-core/src/main/java/com/sk89q/worldedit/session/SessionManager.java +++ b/worldedit-core/src/main/java/com/sk89q/worldedit/session/SessionManager.java @@ -30,6 +30,7 @@ import com.sk89q.worldedit.command.tool.InvalidToolBindException; import com.sk89q.worldedit.command.tool.Tool; import com.sk89q.worldedit.entity.Player; import com.sk89q.worldedit.event.platform.ConfigurationLoadEvent; +import com.sk89q.worldedit.event.platform.SessionIdleEvent; import com.sk89q.worldedit.extension.platform.Locatable; import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.session.request.Request; @@ -132,6 +133,9 @@ public class SessionManager { checkNotNull(owner); SessionHolder stored = sessions.get(getKey(owner)); if (stored != null) { + if (stored.sessionIdle && stored.key.isActive()) { + stored.sessionIdle = false; + } return stored.session; } else { return null; @@ -350,6 +354,18 @@ public class SessionManager { store = new JsonFileSessionStore(dir); } + @Subscribe + public void onSessionIdle(final SessionIdleEvent event) { + SessionHolder holder = this.sessions.get(getKey(event.getKey())); + if (holder != null && !holder.sessionIdle) { + holder.sessionIdle = true; + LocalSession session = holder.session; + + // Perform any session cleanup for data that should not be persisted. + session.onIdle(); + } + } + /** * Stores the owner of a session, the session, and the last active time. */ @@ -357,6 +373,7 @@ public class SessionManager { private final SessionKey key; private final LocalSession session; private long lastActive = System.currentTimeMillis(); + private boolean sessionIdle = false; private SessionHolder(SessionKey key, LocalSession session) { this.key = key; diff --git a/worldedit-fabric/build.gradle.kts b/worldedit-fabric/build.gradle.kts index 83f736638..88f1998b4 100644 --- a/worldedit-fabric/build.gradle.kts +++ b/worldedit-fabric/build.gradle.kts @@ -1,15 +1,33 @@ import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar +import net.fabricmc.loom.LoomGradleExtension import net.fabricmc.loom.task.RemapJarTask +buildscript { + repositories { + mavenCentral() + maven { + name = "Fabric" + url = uri("https://maven.fabricmc.net/") + } + } + dependencies { + classpath("net.fabricmc:fabric-loom:${versions.loom}") + } +} + applyPlatformAndCoreConfiguration() applyShadowConfiguration() apply(plugin = "fabric-loom") apply(plugin = "java-library") -val minecraftVersion = "1.15.2" -val yarnMappings = "1.15.2+build.14:v2" -val loaderVersion = "0.7.8+build.189" +configure { + accessWidener("src/main/resources/worldedit.accesswidener") +} + +val minecraftVersion = "1.16.3" +val yarnMappings = "1.16.3+build.1:v2" +val loaderVersion = "0.10.8" configurations.all { resolutionStrategy { @@ -17,32 +35,77 @@ configurations.all { } } +val fabricApiConfiguration: Configuration = configurations.create("fabricApi") + +repositories { + maven { + name = "Fabric" + url = uri("https://maven.fabricmc.net/") + } +} + dependencies { "api"(project(":worldedit-core")) - "implementation"(enforcedPlatform("org.apache.logging.log4j:log4j-bom:2.8.1") { - because("Mojang provides Log4J at 2.8.1") - }) + "implementation"("org.apache.logging.log4j:log4j-slf4j-impl:2.8.1") "minecraft"("com.mojang:minecraft:$minecraftVersion") "mappings"("net.fabricmc:yarn:$yarnMappings") - "modCompile"("net.fabricmc:fabric-loader:$loaderVersion") + "modImplementation"("net.fabricmc:fabric-loader:$loaderVersion") - listOf( - "net.fabricmc.fabric-api:fabric-api-base:0.1.2+28f8190f42", - "net.fabricmc.fabric-api:fabric-events-interaction-v0:0.2.6+12515ed975", - "net.fabricmc.fabric-api:fabric-events-lifecycle-v0:0.1.2+b7f9825de8", - "net.fabricmc.fabric-api:fabric-networking-v0:0.1.7+12515ed975" - ).forEach { + // [1] declare fabric-api dependency... + "fabricApi"("net.fabricmc.fabric-api:fabric-api:0.29.3+1.16") + + // [2] Load the API dependencies from the fabric mod json... + @Suppress("UNCHECKED_CAST") + val fabricModJson = file("src/main/resources/fabric.mod.json").bufferedReader().use { + groovy.json.JsonSlurper().parse(it) as Map> + } + val wantedDependencies = (fabricModJson["depends"] ?: error("no depends in fabric.mod.json")).keys + .filter { it == "fabric-api-base" || it.contains(Regex("v\\d$")) } + .map { "net.fabricmc.fabric-api:$it" } + logger.lifecycle("Looking for these dependencies:") + for (wantedDependency in wantedDependencies) { + logger.lifecycle(wantedDependency) + } + // [3] and now we resolve it to pick out what we want :D + val fabricApiDependencies = fabricApiConfiguration.incoming.resolutionResult.allDependencies + .onEach { + if (it is UnresolvedDependencyResult) { + throw kotlin.IllegalStateException("Failed to resolve Fabric API", it.failure) + } + } + .filterIsInstance() + // pick out transitive dependencies + .flatMap { + it.selected.dependencies + } + // grab the requested versions + .map { it.requested } + .filterIsInstance() + // map to standard notation + .associateByTo( + mutableMapOf(), + keySelector = { "${it.group}:${it.module}" }, + valueTransform = { "${it.group}:${it.module}:${it.version}" } + ) + fabricApiDependencies.keys.retainAll(wantedDependencies) + // sanity check + for (wantedDep in wantedDependencies) { + check(wantedDep in fabricApiDependencies) { "Fabric API library $wantedDep is missing!" } + } + + fabricApiDependencies.values.forEach { "include"(it) "modImplementation"(it) } + // No need for this at runtime + "modCompileOnly"("me.lucko:fabric-permissions-api:0.1-SNAPSHOT") + // Hook these up manually, because Fabric doesn't seem to quite do it properly. - "compileClasspath"("net.fabricmc:sponge-mixin:${project.versions.mixin}") + "compileOnly"("net.fabricmc:sponge-mixin:${project.versions.mixin}") "annotationProcessor"("net.fabricmc:sponge-mixin:${project.versions.mixin}") "annotationProcessor"("net.fabricmc:fabric-loom:${project.versions.loom}") - - "testCompile"("org.mockito:mockito-core:3.11.0") } configure { @@ -64,18 +127,17 @@ tasks.named("processResources") { } } -tasks.named("jar") { - manifest { - attributes("Class-Path" to CLASSPATH, - "WorldEdit-Version" to project.version) - } -} +addJarManifest(includeClasspath = true) tasks.named("shadowJar") { archiveClassifier.set("dist-dev") dependencies { + relocate("org.slf4j", "com.sk89q.worldedit.slf4j") + relocate("org.apache.logging.slf4j", "com.sk89q.worldedit.log4jbridge") relocate("org.antlr.v4", "com.sk89q.worldedit.antlr4") + include(dependency("org.slf4j:slf4j-api")) + include(dependency("org.apache.logging.log4j:log4j-slf4j-impl")) include(dependency("org.antlr:antlr4-runtime")) } } @@ -95,6 +157,7 @@ tasks.register("remapShadowJar") { input.set(shadowJar.archiveFile) archiveFileName.set(shadowJar.archiveFileName.get().replace(Regex("-dev\\.jar$"), ".jar")) addNestedDependencies.set(true) + remapAccessWidener.set(true) } tasks.named("assemble").configure { diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricPlayer.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricPlayer.java index 01d06bd9e..4efd085d9 100644 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricPlayer.java +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricPlayer.java @@ -42,11 +42,11 @@ import com.sk89q.worldedit.world.block.BaseBlock; import com.sk89q.worldedit.world.block.BlockStateHolder; import com.sk89q.worldedit.world.block.BlockTypes; import io.netty.buffer.Unpooled; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.block.Block; import net.minecraft.item.ItemStack; import net.minecraft.network.packet.s2c.play.BlockEntityUpdateS2CPacket; import net.minecraft.network.packet.s2c.play.BlockUpdateS2CPacket; -import net.minecraft.network.packet.s2c.play.CustomPayloadS2CPacket; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.text.LiteralText; import net.minecraft.text.Text; @@ -58,6 +58,7 @@ import net.minecraft.util.math.BlockPos; import javax.annotation.Nullable; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.UUID; @@ -126,9 +127,11 @@ public class FabricPlayer extends AbstractPlayerActor { if (params.length > 0) { send = send + "|" + StringUtil.joinString(params, "|"); } - PacketByteBuf buffer = new PacketByteBuf(Unpooled.copiedBuffer(send.getBytes(WECUIPacketHandler.UTF_8_CHARSET))); - CustomPayloadS2CPacket packet = new CustomPayloadS2CPacket(new Identifier(FabricWorldEdit.MOD_ID, FabricWorldEdit.CUI_PLUGIN_CHANNEL), buffer); - this.player.networkHandler.sendPacket(packet); + ServerPlayNetworking.send( + this.player, + WECUIPacketHandler.CUI_IDENTIFIER, + new PacketByteBuf(Unpooled.copiedBuffer(send, StandardCharsets.UTF_8)) + ); } @Override @@ -248,18 +251,18 @@ public class FabricPlayer extends AbstractPlayerActor { @Override public SessionKey getSessionKey() { - return new SessionKeyImpl(player.getUuid(), player.getName().getString()); + return new SessionKeyImpl(player); } - private static class SessionKeyImpl implements SessionKey { + static class SessionKeyImpl implements SessionKey { // If not static, this will leak a reference private final UUID uuid; private final String name; - private SessionKeyImpl(UUID uuid, String name) { - this.uuid = uuid; - this.name = name; + SessionKeyImpl(ServerPlayerEntity player) { + this.uuid = player.getUuid(); + this.name = player.getName().getString(); } @Override diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorldEdit.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorldEdit.java index 79f28ab0e..755ee118e 100644 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorldEdit.java +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/FabricWorldEdit.java @@ -19,16 +19,17 @@ package com.sk89q.worldedit.fabric; -import static com.google.common.base.Preconditions.checkNotNull; -import static com.sk89q.worldedit.fabric.FabricAdapter.adaptPlayer; - +import com.mojang.brigadier.CommandDispatcher; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.event.platform.PlatformReadyEvent; +import com.sk89q.worldedit.event.platform.SessionIdleEvent; +import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Platform; +import com.sk89q.worldedit.extension.platform.PlatformManager; import com.sk89q.worldedit.fabric.net.handler.WECUIPacketHandler; +import com.sk89q.worldedit.internal.anvil.ChunkDeleter; import com.sk89q.worldedit.util.Location; -import com.sk89q.worldedit.internal.util.LogManagerCompat; import com.sk89q.worldedit.world.biome.BiomeType; import com.sk89q.worldedit.world.block.BlockCategory; import com.sk89q.worldedit.world.block.BlockType; @@ -36,41 +37,50 @@ import com.sk89q.worldedit.world.entity.EntityType; import com.sk89q.worldedit.world.item.ItemCategory; import com.sk89q.worldedit.world.item.ItemType; import net.fabricmc.api.ModInitializer; +import net.fabricmc.fabric.api.command.v1.CommandRegistrationCallback; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerLifecycleEvents; +import net.fabricmc.fabric.api.event.lifecycle.v1.ServerTickEvents; import net.fabricmc.fabric.api.event.player.AttackBlockCallback; import net.fabricmc.fabric.api.event.player.UseBlockCallback; import net.fabricmc.fabric.api.event.player.UseItemCallback; -import net.fabricmc.fabric.api.event.server.ServerStartCallback; -import net.fabricmc.fabric.api.event.server.ServerStopCallback; -import net.fabricmc.fabric.api.event.server.ServerTickCallback; +import net.fabricmc.fabric.api.networking.v1.ServerPlayConnectionEvents; import net.fabricmc.loader.api.FabricLoader; import net.fabricmc.loader.api.ModContainer; import net.minecraft.entity.player.PlayerEntity; +import net.minecraft.item.ItemStack; import net.minecraft.server.MinecraftServer; +import net.minecraft.server.command.ServerCommandSource; +import net.minecraft.server.network.ServerPlayNetworkHandler; import net.minecraft.server.network.ServerPlayerEntity; import net.minecraft.tag.BlockTags; import net.minecraft.tag.ItemTags; import net.minecraft.util.ActionResult; import net.minecraft.util.Hand; import net.minecraft.util.Identifier; +import net.minecraft.util.TypedActionResult; import net.minecraft.util.hit.BlockHitResult; import net.minecraft.util.math.BlockPos; import net.minecraft.util.math.Direction; import net.minecraft.util.registry.Registry; import net.minecraft.world.World; +import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import java.io.File; import java.io.IOException; import java.io.UncheckedIOException; import java.nio.file.Files; import java.nio.file.Path; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.sk89q.worldedit.fabric.FabricAdapter.adaptPlayer; +import static com.sk89q.worldedit.internal.anvil.ChunkDeleter.DELCHUNKS_FILE_NAME; + /** * The Fabric implementation of WorldEdit. */ public class FabricWorldEdit implements ModInitializer { - private static final Logger LOGGER = LogManagerCompat.getLogger(); + private static final Logger LOGGER = LogManager.getLogger(); public static final String MOD_ID = "worldedit"; public static final String CUI_PLUGIN_CHANNEL = "cui"; @@ -91,11 +101,11 @@ public class FabricWorldEdit implements ModInitializer { @Override public void onInitialize() { this.container = FabricLoader.getInstance().getModContainer("worldedit").orElseThrow( - () -> new IllegalStateException("WorldEdit mod missing in Fabric") + () -> new IllegalStateException("WorldEdit mod missing in Fabric") ); // Setup working directory - workingDir = new File(FabricLoader.getInstance().getConfigDirectory(), "worldedit").toPath(); + workingDir = FabricLoader.getInstance().getConfigDir().resolve("worldedit"); if (!Files.exists(workingDir)) { try { Files.createDirectory(workingDir); @@ -106,24 +116,55 @@ public class FabricWorldEdit implements ModInitializer { WECUIPacketHandler.init(); - ServerTickCallback.EVENT.register(ThreadSafeCache.getInstance()); - ServerStartCallback.EVENT.register(this::onStartServer); - ServerStopCallback.EVENT.register(this::onStopServer); + ServerTickEvents.END_SERVER_TICK.register(ThreadSafeCache.getInstance()); + CommandRegistrationCallback.EVENT.register(this::registerCommands); + ServerLifecycleEvents.SERVER_STARTING.register(this::onStartingServer); + ServerLifecycleEvents.SERVER_STARTED.register(this::onStartServer); + ServerLifecycleEvents.SERVER_STOPPING.register(this::onStopServer); + ServerPlayConnectionEvents.DISCONNECT.register(this::onPlayerDisconnect); AttackBlockCallback.EVENT.register(this::onLeftClickBlock); UseBlockCallback.EVENT.register(this::onRightClickBlock); UseItemCallback.EVENT.register(this::onRightClickAir); LOGGER.info("WorldEdit for Fabric (version " + getInternalVersion() + ") is loaded"); } + private void registerCommands(CommandDispatcher dispatcher, boolean dedicated) { + PlatformManager manager = WorldEdit.getInstance().getPlatformManager(); + if (manager.getPlatforms().isEmpty()) { + // We'll register as part of our platform initialization later. + return; + } + + // This is a re-register (due to /reload), we must add our commands now + + Platform commandsPlatform = manager.queryCapability(Capability.USER_COMMANDS); + if (commandsPlatform != platform || !platform.isHookingEvents()) { + // We're not in control of commands/events -- do not re-register. + return; + } + platform.setNativeDispatcher(dispatcher); + platform.registerCommands(manager.getPlatformCommandManager().getCommandManager()); + } + private void setupPlatform(MinecraftServer server) { this.platform = new FabricPlatform(this, server); WorldEdit.getInstance().getPlatformManager().register(platform); - this.provider = new FabricPermissionsProvider.VanillaPermissionsProvider(platform); + this.provider = getInitialPermissionsProvider(); } - private void setupRegistries() { + private FabricPermissionsProvider getInitialPermissionsProvider() { + try { + Class.forName("me.lucko.fabric.api.permissions.v0.Permissions", false, getClass().getClassLoader()); + return new FabricPermissionsProvider.LuckoFabricPermissionsProvider(platform); + } catch (ClassNotFoundException ignored) { + // fallback to vanilla + } + return new FabricPermissionsProvider.VanillaPermissionsProvider(platform); + } + + private void setupRegistries(MinecraftServer server) { // Blocks for (Identifier name : Registry.BLOCK.getIds()) { if (BlockType.REGISTRY.get(name.toString()) == null) { @@ -144,31 +185,42 @@ public class FabricWorldEdit implements ModInitializer { } } // Biomes - for (Identifier name : Registry.BIOME.getIds()) { + for (Identifier name : server.getRegistryManager().get(Registry.BIOME_KEY).getIds()) { if (BiomeType.REGISTRY.get(name.toString()) == null) { BiomeType.REGISTRY.register(name.toString(), new BiomeType(name.toString())); } } // Tags - for (Identifier name : BlockTags.getContainer().getKeys()) { + for (Identifier name : BlockTags.getTagGroup().getTagIds()) { if (BlockCategory.REGISTRY.get(name.toString()) == null) { BlockCategory.REGISTRY.register(name.toString(), new BlockCategory(name.toString())); } } - for (Identifier name : ItemTags.getContainer().getKeys()) { + for (Identifier name : ItemTags.getTagGroup().getTagIds()) { if (ItemCategory.REGISTRY.get(name.toString()) == null) { ItemCategory.REGISTRY.register(name.toString(), new ItemCategory(name.toString())); } } } + private void onStartingServer(MinecraftServer minecraftServer) { + final Path delChunks = workingDir.resolve(DELCHUNKS_FILE_NAME); + if (Files.exists(delChunks)) { + ChunkDeleter.runFromFile(delChunks, true); + } + } + private void onStartServer(MinecraftServer minecraftServer) { + FabricAdapter.setServer(minecraftServer); setupPlatform(minecraftServer); - setupRegistries(); + setupRegistries(minecraftServer); config = new FabricConfiguration(this); config.load(); WorldEdit.getInstance().getEventBus().post(new PlatformReadyEvent()); + minecraftServer.reloadResources( + minecraftServer.getDataPackManager().getEnabledNames() + ); } private void onStopServer(MinecraftServer minecraftServer) { @@ -198,8 +250,9 @@ public class FabricWorldEdit implements ModInitializer { blockPos.getY(), blockPos.getZ() ); + com.sk89q.worldedit.util.Direction weDirection = FabricAdapter.adaptEnumFacing(direction); - if (we.handleBlockLeftClick(player, pos)) { + if (we.handleBlockLeftClick(player, pos, weDirection)) { return ActionResult.SUCCESS; } @@ -210,12 +263,6 @@ public class FabricWorldEdit implements ModInitializer { return ActionResult.PASS; } - public void onLeftClickAir(PlayerEntity playerEntity, World world, Hand hand) { - WorldEdit we = WorldEdit.getInstance(); - FabricPlayer player = adaptPlayer((ServerPlayerEntity) playerEntity); - we.handleArmSwing(player); - } - private ActionResult onRightClickBlock(PlayerEntity playerEntity, World world, Hand hand, BlockHitResult blockHitResult) { if (shouldSkip() || hand == Hand.OFF_HAND || world.isClient) { return ActionResult.PASS; @@ -229,8 +276,9 @@ public class FabricWorldEdit implements ModInitializer { blockHitResult.getBlockPos().getY(), blockHitResult.getBlockPos().getZ() ); + com.sk89q.worldedit.util.Direction direction = FabricAdapter.adaptEnumFacing(blockHitResult.getSide()); - if (we.handleBlockRightClick(player, pos)) { + if (we.handleBlockRightClick(player, pos, direction)) { return ActionResult.SUCCESS; } @@ -241,23 +289,29 @@ public class FabricWorldEdit implements ModInitializer { return ActionResult.PASS; } - private ActionResult onRightClickAir(PlayerEntity playerEntity, World world, Hand hand) { + private TypedActionResult onRightClickAir(PlayerEntity playerEntity, World world, Hand hand) { + ItemStack stackInHand = playerEntity.getStackInHand(hand); if (shouldSkip() || hand == Hand.OFF_HAND || world.isClient) { - return ActionResult.PASS; + return TypedActionResult.pass(stackInHand); } WorldEdit we = WorldEdit.getInstance(); FabricPlayer player = adaptPlayer((ServerPlayerEntity) playerEntity); if (we.handleRightClick(player)) { - return ActionResult.SUCCESS; + return TypedActionResult.success(stackInHand); } - return ActionResult.PASS; + return TypedActionResult.pass(stackInHand); } // TODO Pass empty left click to server + private void onPlayerDisconnect(ServerPlayNetworkHandler handler, MinecraftServer server) { + WorldEdit.getInstance().getEventBus() + .post(new SessionIdleEvent(new FabricPlayer.SessionKeyImpl(handler.player))); + } + /** * Get the configuration. * @@ -303,8 +357,8 @@ public class FabricWorldEdit implements ModInitializer { * * @return the working directory */ - public File getWorkingDir() { - return this.workingDir.toFile(); + public Path getWorkingDir() { + return this.workingDir; } /** diff --git a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/net/handler/WECUIPacketHandler.java b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/net/handler/WECUIPacketHandler.java index adafd6553..089b9529b 100644 --- a/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/net/handler/WECUIPacketHandler.java +++ b/worldedit-fabric/src/main/java/com/sk89q/worldedit/fabric/net/handler/WECUIPacketHandler.java @@ -23,33 +23,23 @@ import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.fabric.FabricAdapter; import com.sk89q.worldedit.fabric.FabricPlayer; import com.sk89q.worldedit.fabric.FabricWorldEdit; -import net.fabricmc.fabric.api.network.ServerSidePacketRegistry; -import net.minecraft.server.network.ServerPlayerEntity; +import net.fabricmc.fabric.api.networking.v1.ServerPlayNetworking; import net.minecraft.util.Identifier; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; public final class WECUIPacketHandler { private WECUIPacketHandler() { } - public static final Charset UTF_8_CHARSET = StandardCharsets.UTF_8; - private static final Identifier CUI_IDENTIFIER = new Identifier(FabricWorldEdit.MOD_ID, FabricWorldEdit.CUI_PLUGIN_CHANNEL); + public static final Identifier CUI_IDENTIFIER = new Identifier(FabricWorldEdit.MOD_ID, FabricWorldEdit.CUI_PLUGIN_CHANNEL); public static void init() { - ServerSidePacketRegistry.INSTANCE.register(CUI_IDENTIFIER, (packetContext, packetByteBuf) -> { - ServerPlayerEntity player = (ServerPlayerEntity) packetContext.getPlayer(); + ServerPlayNetworking.registerGlobalReceiver(CUI_IDENTIFIER, (server, player, handler, buf, responder) -> { LocalSession session = FabricWorldEdit.inst.getSession(player); - - if (session.hasCUISupport()) { - return; - } - - String text = packetByteBuf.toString(UTF_8_CHARSET); - final FabricPlayer actor = FabricAdapter.adaptPlayer(player); + String text = buf.toString(StandardCharsets.UTF_8); + FabricPlayer actor = FabricAdapter.adaptPlayer(player); session.handleCUIInitializationMessage(text, actor); - session.describeCUI(actor); }); } } diff --git a/worldedit-fabric/src/main/resources/fabric.mod.json b/worldedit-fabric/src/main/resources/fabric.mod.json index 705320db0..60b713ceb 100644 --- a/worldedit-fabric/src/main/resources/fabric.mod.json +++ b/worldedit-fabric/src/main/resources/fabric.mod.json @@ -32,7 +32,7 @@ "fabric-api-base": "*", "fabric-events-lifecycle-v0": "*", "fabric-events-interaction-v0": "*", - "fabric-networking-v0": "*" + "fabric-networking-api-v1": "*" }, "mixins": [ "worldedit.mixins.json" diff --git a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java index 21665ac4e..8e4a4c04b 100644 --- a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java +++ b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgePlayer.java @@ -26,7 +26,6 @@ import com.sk89q.worldedit.entity.BaseEntity; import com.sk89q.worldedit.extension.platform.AbstractPlayerActor; import com.sk89q.worldedit.extent.inventory.BlockBag; import com.sk89q.worldedit.forge.internal.NBTConverter; -import com.sk89q.worldedit.forge.net.handler.WECUIPacketHandler; import com.sk89q.worldedit.internal.cui.CUIEvent; import com.sk89q.worldedit.math.BlockVector3; import com.sk89q.worldedit.math.Vector3; @@ -57,6 +56,7 @@ import net.minecraft.util.text.StringTextComponent; import net.minecraft.util.text.TextFormatting; import java.io.IOException; +import java.nio.charset.StandardCharsets; import java.util.Locale; import java.util.UUID; @@ -127,7 +127,7 @@ public class ForgePlayer extends AbstractPlayerActor { if (params.length > 0) { send = send + "|" + StringUtil.joinString(params, "|"); } - PacketBuffer buffer = new PacketBuffer(Unpooled.copiedBuffer(send.getBytes(WECUIPacketHandler.UTF_8_CHARSET))); + PacketBuffer buffer = new PacketBuffer(Unpooled.copiedBuffer(send, StandardCharsets.UTF_8)); SCustomPayloadPlayPacket packet = new SCustomPayloadPlayPacket(new ResourceLocation(ForgeWorldEdit.MOD_ID, ForgeWorldEdit.CUI_PLUGIN_CHANNEL), buffer); this.player.connection.sendPacket(packet); } @@ -249,18 +249,18 @@ public class ForgePlayer extends AbstractPlayerActor { @Override public SessionKey getSessionKey() { - return new SessionKeyImpl(player.getUniqueID(), player.getName().getString()); + return new SessionKeyImpl(player); } - private static class SessionKeyImpl implements SessionKey { + static class SessionKeyImpl implements SessionKey { // If not static, this will leak a reference private final UUID uuid; private final String name; - private SessionKeyImpl(UUID uuid, String name) { - this.uuid = uuid; - this.name = name; + SessionKeyImpl(ServerPlayerEntity player) { + this.uuid = player.getUniqueID(); + this.name = player.getName().getString(); } @Override diff --git a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java index e6153b114..5709aaeb7 100644 --- a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java +++ b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/ForgeWorldEdit.java @@ -24,6 +24,7 @@ import com.mojang.brigadier.exceptions.CommandSyntaxException; import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.event.platform.PlatformReadyEvent; +import com.sk89q.worldedit.event.platform.SessionIdleEvent; import com.sk89q.worldedit.extension.platform.Platform; import com.sk89q.worldedit.forge.net.handler.InternalPacketHandler; import com.sk89q.worldedit.forge.net.handler.WECUIPacketHandler; @@ -49,6 +50,7 @@ import net.minecraft.util.ResourceLocation; import net.minecraft.world.World; import net.minecraftforge.common.MinecraftForge; import net.minecraftforge.event.CommandEvent; +import net.minecraftforge.event.entity.player.PlayerEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent; import net.minecraftforge.event.entity.player.PlayerInteractEvent.LeftClickEmpty; import net.minecraftforge.eventbus.api.Event; @@ -284,6 +286,14 @@ public class ForgeWorldEdit { )); } + @SubscribeEvent + public void onPlayerLogOut(PlayerEvent.PlayerLoggedOutEvent event) { + if (event.getPlayer() instanceof ServerPlayerEntity) { + WorldEdit.getInstance().getEventBus() + .post(new SessionIdleEvent(new ForgePlayer.SessionKeyImpl((ServerPlayerEntity) event.getPlayer()))); + } + } + /** * Get the configuration. * diff --git a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/net/handler/WECUIPacketHandler.java b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/net/handler/WECUIPacketHandler.java index c4b85dd2a..1f9f73126 100644 --- a/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/net/handler/WECUIPacketHandler.java +++ b/worldedit-forge/src/main/java/com/sk89q/worldedit/forge/net/handler/WECUIPacketHandler.java @@ -27,6 +27,7 @@ import net.minecraftforge.fml.network.NetworkEvent.ClientCustomPayloadEvent; import net.minecraftforge.fml.network.event.EventNetworkChannel; import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; import static com.sk89q.worldedit.forge.ForgeAdapter.adaptPlayer; @@ -34,7 +35,6 @@ public final class WECUIPacketHandler { private WECUIPacketHandler() { } - public static final Charset UTF_8_CHARSET = Charset.forName("UTF-8"); private static final int PROTOCOL_VERSION = 1; private static EventNetworkChannel HANDLER = PacketHandlerUtil .buildLenientHandler(ForgeWorldEdit.CUI_PLUGIN_CHANNEL, PROTOCOL_VERSION) @@ -47,15 +47,9 @@ public final class WECUIPacketHandler { public static void onPacketData(ClientCustomPayloadEvent event) { ServerPlayerEntity player = event.getSource().get().getSender(); LocalSession session = ForgeWorldEdit.inst.getSession(player); - - if (session.hasCUISupport()) { - return; - } - - String text = event.getPayload().toString(UTF_8_CHARSET); + String text = event.getPayload().toString(StandardCharsets.UTF_8); final ForgePlayer actor = adaptPlayer(player); session.handleCUIInitializationMessage(text, actor); - session.describeCUI(actor); } } diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/CUIChannelHandler.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/CUIChannelHandler.java index ce02fe6f9..8e342f79c 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/CUIChannelHandler.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/CUIChannelHandler.java @@ -53,14 +53,9 @@ public class CUIChannelHandler implements RawDataListener { LocalSession session = SpongeWorldEdit.inst().getSession(player); - if (session.hasCUISupport()) { - return; - } - final SpongePlayer actor = SpongeWorldEdit.inst().wrapPlayer(player); session.handleCUIInitializationMessage(new String(data.readBytes(data.available()), StandardCharsets.UTF_8), actor); - session.describeCUI(actor); } } } diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlayer.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlayer.java index e6cc24619..5ca4bba23 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlayer.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongePlayer.java @@ -239,18 +239,18 @@ public class SpongePlayer extends AbstractPlayerActor { @Override public SessionKey getSessionKey() { - return new SessionKeyImpl(player.getUniqueId(), player.getName()); + return new SessionKeyImpl(player); } - private static class SessionKeyImpl implements SessionKey { + static class SessionKeyImpl implements SessionKey { // If not static, this will leak a reference private final UUID uuid; private final String name; - private SessionKeyImpl(UUID uuid, String name) { - this.uuid = uuid; - this.name = name; + SessionKeyImpl(Player player) { + this.uuid = player.getUniqueId(); + this.name = player.getName(); } @Override diff --git a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java index 41afcc804..194ffee15 100644 --- a/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java +++ b/worldedit-sponge/src/main/java/com/sk89q/worldedit/sponge/SpongeWorldEdit.java @@ -27,6 +27,7 @@ import com.sk89q.worldedit.LocalSession; import com.sk89q.worldedit.WorldEdit; import com.sk89q.worldedit.blocks.BaseItemStack; import com.sk89q.worldedit.event.platform.PlatformReadyEvent; +import com.sk89q.worldedit.event.platform.SessionIdleEvent; import com.sk89q.worldedit.extension.platform.Actor; import com.sk89q.worldedit.extension.platform.Capability; import com.sk89q.worldedit.extension.platform.Platform; @@ -54,6 +55,7 @@ import org.spongepowered.api.event.game.state.GamePreInitializationEvent; import org.spongepowered.api.event.game.state.GameStartedServerEvent; import org.spongepowered.api.event.game.state.GameStoppingServerEvent; import org.spongepowered.api.event.item.inventory.InteractItemEvent; +import org.spongepowered.api.event.network.ClientConnectionEvent; import org.spongepowered.api.item.ItemType; import org.spongepowered.api.item.inventory.ItemStack; import org.spongepowered.api.plugin.Plugin; @@ -290,6 +292,12 @@ public class SpongeWorldEdit { } } + @Listener + public void onPlayerQuit(ClientConnectionEvent.Disconnect event) { + WorldEdit.getInstance().getEventBus() + .post(new SessionIdleEvent(new SpongePlayer.SessionKeyImpl(event.getTargetEntity()))); + } + public static ItemStack toSpongeItemStack(BaseItemStack item) { return inst().getAdapter().makeSpongeStack(item); }