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);
}